Blame src/libopts/text_mmap.c

Packit 549fdc
/**
Packit 549fdc
 * @file text_mmap.c
Packit 549fdc
 *
Packit 549fdc
 * Map a text file, ensuring the text always has an ending NUL byte.
Packit 549fdc
 *
Packit 549fdc
 * @addtogroup autoopts
Packit 549fdc
 * @{
Packit 549fdc
 */
Packit 549fdc
/*
Packit 549fdc
 *  This file is part of AutoOpts, a companion to AutoGen.
Packit 549fdc
 *  AutoOpts is free software.
Packit 549fdc
 *  AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved
Packit 549fdc
 *
Packit 549fdc
 *  AutoOpts is available under any one of two licenses.  The license
Packit 549fdc
 *  in use must be one of these two and the choice is under the control
Packit 549fdc
 *  of the user of the license.
Packit 549fdc
 *
Packit 549fdc
 *   The GNU Lesser General Public License, version 3 or later
Packit 549fdc
 *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
Packit 549fdc
 *
Packit 549fdc
 *   The Modified Berkeley Software Distribution License
Packit 549fdc
 *      See the file "COPYING.mbsd"
Packit 549fdc
 *
Packit 549fdc
 *  These files have the following sha256 sums:
Packit 549fdc
 *
Packit 549fdc
 *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
Packit 549fdc
 *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
Packit 549fdc
 *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
Packit 549fdc
 */
Packit 549fdc
#if defined(HAVE_MMAP)
Packit 549fdc
#  ifndef      MAP_ANONYMOUS
Packit 549fdc
#    ifdef     MAP_ANON
Packit 549fdc
#      define  MAP_ANONYMOUS   MAP_ANON
Packit 549fdc
#    endif
Packit 549fdc
#  endif
Packit 549fdc
Packit 549fdc
#  if ! defined(MAP_ANONYMOUS) && ! defined(HAVE_DEV_ZERO)
Packit 549fdc
     /*
Packit 549fdc
      * We must have either /dev/zero or anonymous mapping for
Packit 549fdc
      * this to work.
Packit 549fdc
      */
Packit 549fdc
#    undef HAVE_MMAP
Packit 549fdc
Packit 549fdc
#  else
Packit 549fdc
#    ifdef _SC_PAGESIZE
Packit 549fdc
#      define GETPAGESIZE() sysconf(_SC_PAGESIZE)
Packit 549fdc
#    else
Packit 549fdc
#      define GETPAGESIZE() getpagesize()
Packit 549fdc
#    endif
Packit 549fdc
#  endif
Packit 549fdc
#endif
Packit 549fdc
Packit 549fdc
/*
Packit 549fdc
 *  Some weird systems require that a specifically invalid FD number
Packit 549fdc
 *  get passed in as an argument value.  Which value is that?  Well,
Packit 549fdc
 *  as everybody knows, if open(2) fails, it returns -1, so that must
Packit 549fdc
 *  be the value.  :)
Packit 549fdc
 */
Packit 549fdc
#define AO_INVALID_FD  -1
Packit 549fdc
Packit 549fdc
#define FILE_WRITABLE(_prt,_flg) \
Packit 549fdc
        (   (_prt & PROT_WRITE) \
Packit 549fdc
         && ((_flg & (MAP_SHARED|MAP_PRIVATE)) == MAP_SHARED))
Packit 549fdc
#define MAP_FAILED_PTR (VOIDP(MAP_FAILED))
Packit 549fdc
Packit 549fdc
/**
Packit 549fdc
 * Load the contents of a text file.  There are two separate implementations,
Packit 549fdc
 * depending up on whether mmap(3) is available.
Packit 549fdc
 *
Packit 549fdc
 *  If not available, malloc the file length plus one byte.  Read it in
Packit 549fdc
 *  and NUL terminate.
Packit 549fdc
 *
Packit 549fdc
 *  If available, first check to see if the text file size is a multiple of a
Packit 549fdc
 *  page size.  If it is, map the file size plus an extra page from either
Packit 549fdc
 *  anonymous memory or from /dev/zero.  Then map the file text on top of the
Packit 549fdc
 *  first pages of the anonymous/zero pages.  Otherwise, just map the file
Packit 549fdc
 *  because there will be NUL bytes provided at the end.
Packit 549fdc
 *
Packit 549fdc
 * @param mapinfo a structure holding everything we need to know
Packit 549fdc
 *        about the mapping.
Packit 549fdc
 *
Packit 549fdc
 * @param pzFile name of the file, for error reporting.
Packit 549fdc
 */
Packit 549fdc
static void
Packit 549fdc
load_text_file(tmap_info_t * mapinfo, char const * pzFile)
Packit 549fdc
{
Packit 549fdc
#if ! defined(HAVE_MMAP)
Packit 549fdc
    mapinfo->txt_data = AGALOC(mapinfo->txt_size+1, "file text");
Packit 549fdc
    if (mapinfo->txt_data == NULL) {
Packit 549fdc
        mapinfo->txt_errno = ENOMEM;
Packit 549fdc
        return;
Packit 549fdc
    }
Packit 549fdc
Packit 549fdc
    {
Packit 549fdc
        size_t sz = mapinfo->txt_size;
Packit 549fdc
        char * pz = mapinfo->txt_data;
Packit 549fdc
Packit 549fdc
        while (sz > 0) {
Packit 549fdc
            ssize_t rdct = read(mapinfo->txt_fd, pz, sz);
Packit 549fdc
            if (rdct <= 0) {
Packit 549fdc
                mapinfo->txt_errno = errno;
Packit 549fdc
                fserr_warn("libopts", "read", pzFile);
Packit 549fdc
                free(mapinfo->txt_data);
Packit 549fdc
                return;
Packit 549fdc
            }
Packit 549fdc
Packit 549fdc
            pz += rdct;
Packit 549fdc
            sz -= rdct;
Packit 549fdc
        }
Packit 549fdc
Packit 549fdc
        *pz = NUL;
Packit 549fdc
    }
Packit 549fdc
Packit 549fdc
    mapinfo->txt_errno   = 0;
Packit 549fdc
Packit 549fdc
#else /* HAVE mmap */
Packit 549fdc
    size_t const pgsz = (size_t)GETPAGESIZE();
Packit 549fdc
    void * map_addr   = NULL;
Packit 549fdc
Packit 549fdc
    (void)pzFile;
Packit 549fdc
Packit 549fdc
    mapinfo->txt_full_size = (mapinfo->txt_size + pgsz) & ~(pgsz - 1);
Packit 549fdc
    if (mapinfo->txt_full_size == (mapinfo->txt_size + pgsz)) {
Packit 549fdc
        /*
Packit 549fdc
         * The text is a multiple of a page boundary.  We must map an
Packit 549fdc
         * extra page so the text ends with a NUL.
Packit 549fdc
         */
Packit 549fdc
#if defined(MAP_ANONYMOUS)
Packit 549fdc
        map_addr = mmap(NULL, mapinfo->txt_full_size, PROT_READ|PROT_WRITE,
Packit 549fdc
                        MAP_ANONYMOUS|MAP_PRIVATE, AO_INVALID_FD, 0);
Packit 549fdc
#else
Packit 549fdc
        mapinfo->txt_zero_fd = open("/dev/zero", O_RDONLY);
Packit 549fdc
Packit 549fdc
        if (mapinfo->txt_zero_fd == AO_INVALID_FD) {
Packit 549fdc
            mapinfo->txt_errno = errno;
Packit 549fdc
            return;
Packit 549fdc
        }
Packit 549fdc
        map_addr = mmap(NULL, mapinfo->txt_full_size, PROT_READ|PROT_WRITE,
Packit 549fdc
                        MAP_PRIVATE, mapinfo->txt_zero_fd, 0);
Packit 549fdc
#endif
Packit 549fdc
        if (map_addr == MAP_FAILED_PTR) {
Packit 549fdc
            mapinfo->txt_errno = errno;
Packit 549fdc
            return;
Packit 549fdc
        }
Packit 549fdc
        mapinfo->txt_flags |= MAP_FIXED;
Packit 549fdc
    }
Packit 549fdc
Packit 549fdc
    mapinfo->txt_data =
Packit 549fdc
        mmap(map_addr, mapinfo->txt_size, mapinfo->txt_prot,
Packit 549fdc
             mapinfo->txt_flags, mapinfo->txt_fd, 0);
Packit 549fdc
Packit 549fdc
    if (mapinfo->txt_data == MAP_FAILED_PTR)
Packit 549fdc
        mapinfo->txt_errno = errno;
Packit 549fdc
#endif /* HAVE_MMAP */
Packit 549fdc
}
Packit 549fdc
Packit 549fdc
/**
Packit 549fdc
 * Make sure all the parameters are correct:  we have a file name that
Packit 549fdc
 * is a text file that we can read.
Packit 549fdc
 *
Packit 549fdc
 * @param fname the text file to map
Packit 549fdc
 * @param prot  the memory protections requested (read/write/etc.)
Packit 549fdc
 * @param flags mmap flags
Packit 549fdc
 * @param mapinfo a structure holding everything we need to know
Packit 549fdc
 *        about the mapping.
Packit 549fdc
 */
Packit 549fdc
static void
Packit 549fdc
validate_mmap(char const * fname, int prot, int flags, tmap_info_t * mapinfo)
Packit 549fdc
{
Packit 549fdc
    memset(mapinfo, 0, sizeof(*mapinfo));
Packit 549fdc
#if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS)
Packit 549fdc
    mapinfo->txt_zero_fd = AO_INVALID_FD;
Packit 549fdc
#endif
Packit 549fdc
    mapinfo->txt_fd      = AO_INVALID_FD;
Packit 549fdc
    mapinfo->txt_prot    = prot;
Packit 549fdc
    mapinfo->txt_flags   = flags;
Packit 549fdc
Packit 549fdc
    /*
Packit 549fdc
     *  Map mmap flags and protections into open flags and do the open.
Packit 549fdc
     */
Packit 549fdc
    {
Packit 549fdc
        /*
Packit 549fdc
         *  See if we will be updating the file.  If we can alter the memory
Packit 549fdc
         *  and if we share the data and we are *not* copy-on-writing the data,
Packit 549fdc
         *  then our updates will show in the file, so we must open with
Packit 549fdc
         *  write access.
Packit 549fdc
         */
Packit 549fdc
        int o_flag = FILE_WRITABLE(prot, flags) ? O_RDWR : O_RDONLY;
Packit 549fdc
#ifdef _WIN32
Packit 549fdc
        o_flag |= O_BINARY;
Packit 549fdc
#endif
Packit 549fdc
        /*
Packit 549fdc
         *  If you're not sharing the file and you are writing to it,
Packit 549fdc
         *  then don't let anyone else have access to the file.
Packit 549fdc
         */
Packit 549fdc
        if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE))
Packit 549fdc
            o_flag |= O_EXCL;
Packit 549fdc
Packit 549fdc
        mapinfo->txt_fd = open(fname, o_flag);
Packit 549fdc
        if (mapinfo->txt_fd < 0) {
Packit 549fdc
            mapinfo->txt_errno = errno;
Packit 549fdc
            mapinfo->txt_fd = AO_INVALID_FD;
Packit 549fdc
            return;
Packit 549fdc
        }
Packit 549fdc
    }
Packit 549fdc
Packit 549fdc
    /*
Packit 549fdc
     *  Make sure we can stat the regular file.  Save the file size.
Packit 549fdc
     */
Packit 549fdc
    {
Packit 549fdc
        struct stat sb;
Packit 549fdc
        if (fstat(mapinfo->txt_fd, &sb) != 0) {
Packit 549fdc
            mapinfo->txt_errno = errno;
Packit 549fdc
            close(mapinfo->txt_fd);
Packit 549fdc
            return;
Packit 549fdc
        }
Packit 549fdc
Packit 549fdc
        if (! S_ISREG(sb.st_mode)) {
Packit 549fdc
            mapinfo->txt_errno = errno = EINVAL;
Packit 549fdc
            close(mapinfo->txt_fd);
Packit 549fdc
            return;
Packit 549fdc
        }
Packit 549fdc
Packit 549fdc
        mapinfo->txt_size = (size_t)sb.st_size;
Packit 549fdc
    }
Packit 549fdc
Packit 549fdc
    if (mapinfo->txt_fd == AO_INVALID_FD)
Packit 549fdc
        mapinfo->txt_errno = errno;
Packit 549fdc
}
Packit 549fdc
Packit 549fdc
/**
Packit 549fdc
 * Close any files opened by the mapping.
Packit 549fdc
 *
Packit 549fdc
 * @param mi a structure holding everything we need to know about the map.
Packit 549fdc
 */
Packit 549fdc
static void
Packit 549fdc
close_mmap_files(tmap_info_t * mi)
Packit 549fdc
{
Packit 549fdc
    if (mi->txt_fd == AO_INVALID_FD)
Packit 549fdc
        return;
Packit 549fdc
Packit 549fdc
    close(mi->txt_fd);
Packit 549fdc
    mi->txt_fd = AO_INVALID_FD;
Packit 549fdc
Packit 549fdc
#if defined(HAVE_MMAP) && ! defined(MAP_ANONYMOUS)
Packit 549fdc
    if (mi->txt_zero_fd == AO_INVALID_FD)
Packit 549fdc
        return;
Packit 549fdc
Packit 549fdc
    close(mi->txt_zero_fd);
Packit 549fdc
    mi->txt_zero_fd = AO_INVALID_FD;
Packit 549fdc
#endif
Packit 549fdc
}
Packit 549fdc
Packit 549fdc
/*=export_func  text_mmap
Packit 549fdc
 * private:
Packit 549fdc
 *
Packit 549fdc
 * what:  map a text file with terminating NUL
Packit 549fdc
 *
Packit 549fdc
 * arg:   char const *,  pzFile,  name of the file to map
Packit 549fdc
 * arg:   int,           prot,    mmap protections (see mmap(2))
Packit 549fdc
 * arg:   int,           flags,   mmap flags (see mmap(2))
Packit 549fdc
 * arg:   tmap_info_t *, mapinfo, returned info about the mapping
Packit 549fdc
 *
Packit 549fdc
 * ret-type:   void *
Packit 549fdc
 * ret-desc:   The mmaped data address
Packit 549fdc
 *
Packit 549fdc
 * doc:
Packit 549fdc
 *
Packit 549fdc
 * This routine will mmap a file into memory ensuring that there is at least
Packit 549fdc
 * one @file{NUL} character following the file data.  It will return the
Packit 549fdc
 * address where the file contents have been mapped into memory.  If there is a
Packit 549fdc
 * problem, then it will return @code{MAP_FAILED} and set @code{errno}
Packit 549fdc
 * appropriately.
Packit 549fdc
 *
Packit 549fdc
 * The named file does not exist, @code{stat(2)} will set @code{errno} as it
Packit 549fdc
 * will.  If the file is not a regular file, @code{errno} will be
Packit 549fdc
 * @code{EINVAL}.  At that point, @code{open(2)} is attempted with the access
Packit 549fdc
 * bits set appropriately for the requested @code{mmap(2)} protections and flag
Packit 549fdc
 * bits.  On failure, @code{errno} will be set according to the documentation
Packit 549fdc
 * for @code{open(2)}.  If @code{mmap(2)} fails, @code{errno} will be set as
Packit 549fdc
 * that routine sets it.  If @code{text_mmap} works to this point, a valid
Packit 549fdc
 * address will be returned, but there may still be ``issues''.
Packit 549fdc
 *
Packit 549fdc
 * If the file size is not an even multiple of the system page size, then
Packit 549fdc
 * @code{text_map} will return at this point and @code{errno} will be zero.
Packit 549fdc
 * Otherwise, an anonymous map is attempted.  If not available, then an attempt
Packit 549fdc
 * is made to @code{mmap(2)} @file{/dev/zero}.  If any of these fail, the
Packit 549fdc
 * address of the file's data is returned, bug @code{no} @file{NUL} characters
Packit 549fdc
 * are mapped after the end of the data.
Packit 549fdc
 *
Packit 549fdc
 * see: mmap(2), open(2), stat(2)
Packit 549fdc
 *
Packit 549fdc
 * err: Any error code issued by mmap(2), open(2), stat(2) is possible.
Packit 549fdc
 *      Additionally, if the specified file is not a regular file, then
Packit 549fdc
 *      errno will be set to @code{EINVAL}.
Packit 549fdc
 *
Packit 549fdc
 * example:
Packit 549fdc
 * #include <mylib.h>
Packit 549fdc
 * tmap_info_t mi;
Packit 549fdc
 * int no_nul;
Packit 549fdc
 * void * data = text_mmap("file", PROT_WRITE, MAP_PRIVATE, &mi);
Packit 549fdc
 * if (data == MAP_FAILED) return;
Packit 549fdc
 * no_nul = (mi.txt_size == mi.txt_full_size);
Packit 549fdc
 * << use the data >>
Packit 549fdc
 * text_munmap(&mi);
Packit 549fdc
=*/
Packit 549fdc
void *
Packit 549fdc
text_mmap(char const * pzFile, int prot, int flags, tmap_info_t * mi)
Packit 549fdc
{
Packit 549fdc
    validate_mmap(pzFile, prot, flags, mi);
Packit 549fdc
    if (mi->txt_errno != 0)
Packit 549fdc
        return MAP_FAILED_PTR;
Packit 549fdc
Packit 549fdc
    load_text_file(mi, pzFile);
Packit 549fdc
Packit 549fdc
    if (mi->txt_errno == 0)
Packit 549fdc
        return mi->txt_data;
Packit 549fdc
Packit 549fdc
    close_mmap_files(mi);
Packit 549fdc
Packit 549fdc
    errno = mi->txt_errno;
Packit 549fdc
    mi->txt_data = MAP_FAILED_PTR;
Packit 549fdc
    return mi->txt_data;
Packit 549fdc
}
Packit 549fdc
Packit 549fdc
Packit 549fdc
/*=export_func  text_munmap
Packit 549fdc
 * private:
Packit 549fdc
 *
Packit 549fdc
 * what:  unmap the data mapped in by text_mmap
Packit 549fdc
 *
Packit 549fdc
 * arg:   tmap_info_t *, mapinfo, info about the mapping
Packit 549fdc
 *
Packit 549fdc
 * ret-type:   int
Packit 549fdc
 * ret-desc:   -1 or 0.  @code{errno} will have the error code.
Packit 549fdc
 *
Packit 549fdc
 * doc:
Packit 549fdc
 *
Packit 549fdc
 * This routine will unmap the data mapped in with @code{text_mmap} and close
Packit 549fdc
 * the associated file descriptors opened by that function.
Packit 549fdc
 *
Packit 549fdc
 * see: munmap(2), close(2)
Packit 549fdc
 *
Packit 549fdc
 * err: Any error code issued by munmap(2) or close(2) is possible.
Packit 549fdc
=*/
Packit 549fdc
int
Packit 549fdc
text_munmap(tmap_info_t * mi)
Packit 549fdc
{
Packit 549fdc
    errno = 0;
Packit 549fdc
Packit 549fdc
#ifdef HAVE_MMAP
Packit 549fdc
    (void)munmap(mi->txt_data, mi->txt_full_size);
Packit 549fdc
Packit 549fdc
#else  /* don't HAVE_MMAP */
Packit 549fdc
    /*
Packit 549fdc
     *  IF the memory is writable *AND* it is not private (copy-on-write)
Packit 549fdc
     *     *AND* the memory is "sharable" (seen by other processes)
Packit 549fdc
     *  THEN rewrite the data.  Emulate mmap visibility.
Packit 549fdc
     */
Packit 549fdc
    if (   FILE_WRITABLE(mi->txt_prot, mi->txt_flags)
Packit 549fdc
        && (lseek(mi->txt_fd, 0, SEEK_SET) >= 0) ) {
Packit 549fdc
        write(mi->txt_fd, mi->txt_data, mi->txt_size);
Packit 549fdc
    }
Packit 549fdc
Packit 549fdc
    free(mi->txt_data);
Packit 549fdc
#endif /* HAVE_MMAP */
Packit 549fdc
Packit 549fdc
    mi->txt_errno = errno;
Packit 549fdc
    close_mmap_files(mi);
Packit 549fdc
Packit 549fdc
    return mi->txt_errno;
Packit 549fdc
}
Packit 549fdc
Packit 549fdc
/** @}
Packit 549fdc
 *
Packit 549fdc
 * Local Variables:
Packit 549fdc
 * mode: C
Packit 549fdc
 * c-file-style: "stroustrup"
Packit 549fdc
 * indent-tabs-mode: nil
Packit 549fdc
 * End:
Packit 549fdc
 * end of autoopts/text_mmap.c */