bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: strfile: new module


From: Bruce Korb
Subject: Re: strfile: new module
Date: Sat, 27 May 2006 08:47:14 -0700
User-agent: Mozilla Thunderbird 1.0.7 (X11/20050923)

Ralf Wildenhues wrote:
* Simon Josefsson wrote on Sat, May 27, 2006 at 12:35:21PM CEST:

I hadn't actually even compiled the module..

Let's put it this way: Using a mailing list to fix trivial
compile warnings doesn't encourage further code reviews.

It might also be useful to use mmap to load the text, too.
It brings you up against the fun problem of what to do when
the text file is a multiple of the page size, but you can
hack around the issue.  (Easiest solution is to just allocate
and read instead of mmap when this happens.)



If anyone cares, the attached does this and I release to
public domain.  There's no configury stuff because I use it in
complex environments that do all the configury stuff for me.
So, it is left as an exercise for the reader.  Enjoy. - Bruce

P.S.  ``cc -Wall -c txtmmap.c'' compiles without complaint on my
Linux box and I have used this code extensively.
/*
 *  txt_mmap copyright 1992-2006 Bruce Korb
 *
 *  txt_mmap is free software.  Completely free.  There are no licensing
 *  requirements.  You can do whatever you want with it, except claim
 *  it as your own.
 */

#ifndef TXTMMAP_H_GUARD
#define TXTMMAP_H_GUARD 1

#include <sys/types.h>
#include <sys/mman.h>

/*
 *  txt_mmap structure.  Only active on platforms with mmap(2).
 */
#ifndef MAP_FAILED
#  define  MAP_FAILED  ((void*)-1)
#endif

/*
 *  This is an output only structure used by txt_mmap and txt_munmap.
 *  Clients must not alter the contents and must provide it to both
 *  the txt_mmap and txt_munmap procedures.  BE ADVISED: if you are
 *  mapping the file with PROT_WRITE the NUL byte at the end MIGHT NOT
 *  BE WRITABLE.  In any event, that byte is not be written back
 *  to the source file.
 */
typedef struct {
    void*       txt_data;      /* text file data   */
    size_t      txt_size;      /* actual file size */
    int         txt_fd;        /* file descriptor  */
    size_t      txt_full_size; /* mmaped mem size  */
    int         txt_zero_fd;   /* fd for /dev/zero */
    int         txt_errno;     /* warning code     */
} tmap_info_t;

extern void*
txt_mmap( const char* pzFile, int prot, int flags, tmap_info_t* pMI );

extern int
txt_munmap( tmap_info_t* pMI );

#endif /* TXTMMAP_H_GUARD */
/*
 * Local Variables:
 * c-file-style: "stroustrup"
 * indent-tabs-mode: nil
 * End:
 * txtmmap.h ends here */
/*
 *  txt_mmap copyright 1992-2006 Bruce Korb
 *
 *  txt_mmap is free software.  Completely free.  There are no licensing
 *  requirements.  You can do whatever you want with it, except claim
 *  it as your own.
 */

#include "txtmmap.h"
#include <unistd.h>

#if defined(HAVE_MMAP)

#ifndef  _SC_PAGESIZE
# ifdef  _SC_PAGE_SIZE
#  define _SC_PAGESIZE _SC_PAGE_SIZE
# endif
#endif

/*=export_func  txt_mmap
 *
 * ifdef: HAVE_MMAP
 *
 * what:  map a text file with terminating NUL
 *
 * arg:   const char*,  pzFile,  name of the file to map
 * arg:   int,          prot,    mmap protections (see mmap(2))
 * arg:   int,          flags,   mmap flags (see mmap(2))
 * arg:   tmap_info_t*, mapinfo, returned info about the mapping
 *
 * ret-type:   void*
 * ret-desc:   The mmaped data address
 *
 * doc:
 *
 * This routine will mmap a file into memory ensuring that there is at least
 * one @file{NUL} character following the file data.  It will return the
 * address where the file contents have been mapped into memory.  If there is a
 * problem, then it will return @code{MAP_FAILED} and set @file{errno}
 * appropriately.
 *
 * The named file does not exist, @code{stat(2)} will set @file{errno} as it
 * will.  If the file is not a regular file, @file{errno} will be
 * @code{EINVAL}.  At that point, @code{open(2)} is attempted with the access
 * bits set appropriately for the requested @code{mmap(2)} protections and flag
 * bits.  On failure, @file{errno} will be set according to the documentation
 * for @code{open(2)}.  If @code{mmap(2)} fails, @file{errno} will be set as
 * that routine sets it.  If @code{txt_mmap} works to this point, a valid
 * address will be returned, but there may still be ``issues''.
 *
 * If the file size is not an even multiple of the system page size, then
 * @code{text_map} will return at this point and @file{errno} will be zero.
 * Otherwise, an anonymous map is attempted.  If not available, then an attempt
 * is made to @code{mmap(2)} @file{/dev/zero}.  If any of these fail, the
 * address of the file's data is returned, bug @code{no} @file{NUL} characters
 * are mapped after the end of the data.
 *
 * see: mmap(2), open(2), stat(2)
 *
 * err: Any error code issued by mmap(2), open(2), stat(2) is possible.
 *      Additionally, if the specified file is not a regular file, then
 *      errno will be set to @code{EINVAL}.
 *
 * example:
 * #include <mylib.h>
 * tmap_info_t mi;
 * int no_nul;
 * void* data = txt_mmap( "file", PROT_WRITE, MAP_PRIVATE, &mi );
 * if (data == MAP_FAILED) return;
 * no_nul = (mi.txt_size == mi.txt_full_size);
 * << use the data >>
 * txt_munmap( &mi );
=*/
void*
txt_mmap( const char* pzFile, int prot, int flags, tmap_info_t* pMI )
{
    /*
     *  Make sure we can stat the regular file.  Save the file size.
     */
    {
        struct stat sb;
        if (stat( pzFile, &sb ) != 0) {
            pMI->txt_errno = errno;
            return MAP_FAILED;
        }

        if (! S_ISREG( sb.st_mode )) {
            pMI->txt_errno = errno = EINVAL;
            return MAP_FAILED;
        }

        pMI->txt_size = sb.st_size;
    }

    /*
     *  Map mmap flags and protections into open flags and do the open.
     */
    {
        int o_flag;
        /*
         *  See if we will be updating the file
         */
        if (prot & PROT_WRITE)
            o_flag = O_RDWR;
        else
            o_flag = O_RDONLY;

        /*
         *  If you're not sharing the file and you are writing to it,
         *  then don't let anyone else have access to the file.
         */
        if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE))
            o_flag |= O_EXCL;

        pMI->txt_fd = open( pzFile, o_flag );
    }

    if (pMI->txt_fd < 0) {
        pMI->txt_errno = errno;
        return MAP_FAILED;
    }

    /*
     *  do the mmap.  If we fail, then preserve errno, close the file and
     *  return the failure.
     */
    pMI->txt_data = mmap( NULL, pMI->txt_size, prot, flags, pMI->txt_fd, 0 );
    if (pMI->txt_data == MAP_FAILED) {
        pMI->txt_errno = errno;
        close( pMI->txt_fd );
        errno = pMI->txt_errno;
        return MAP_FAILED;
    }

    /*
     *  Most likely, everything will turn out fine now.  The only difficult
     *  part at this point is coping with files with sizes that are a multiple
     *  of the page size.  Handling that is what this whole thing is about.
     */
    pMI->txt_zero_fd = -1;
    pMI->txt_errno   = 0;

    do {
#ifdef _SC_PAGESIZE
        size_t pgsz = sysconf(_SC_PAGESIZE);
#else
        size_t pgsz = getpagesize();
#endif
        /*
         *  Compute the pagesize rounded mapped memory size.
         *  IF this is not the same as the file size, then there are NUL's
         *  at the end of the file mapping and all is okay.
         */
        pMI->txt_full_size = (pMI->txt_size + (pgsz - 1)) & ~(pgsz - 1);
        if (pMI->txt_size != pMI->txt_full_size)
            break;

        /*
         *  Still here?  We have to append a page of NUL's
         */
        pMI->txt_full_size += pgsz;
        {
            void* pNuls;

#ifdef MAP_ANONYMOUS
            pNuls = mmap(
                (void*)(((char*)pMI->txt_data) + pMI->txt_size), pgsz,
                PROT_READ, MAP_SHARED|MAP_ANONYMOUS|MAP_FIXED, 0, 0 );
#else
            pMI->txt_zero_fd = open( "/dev/zero", O_RDONLY );
            if (pMI->txt_zero_fd < 0)
                pNuls = MAP_FAILED;

            else pNuls = mmap(
                (void*)(((char*)pMI->txt_data) + pMI->txt_size), pgsz,
                PROT_READ, MAP_PRIVATE|MAP_FIXED, pMI->txt_zero_fd, 0 );
#endif

            /*
             *  If we couldn't map the page of NULs, then the caller can
             *  figure this out by checking that full_size == size and
             *  the txt_errno is not zero.
             */
            if (pNuls == MAP_FAILED) {
                pMI->txt_errno = errno;
                pMI->txt_full_size = pMI->txt_size;
            }
        }
    } while(0);
#if defined(DEBUG_TEXT_MMAP)
    fprintf( stderr,
             "text file name    %s\n"
             "text file data    0x%08lX\n"
             "actual file size  0x%X\n"
             "file descriptor   %d\n"
             "mmaped mem size   0x%X\n"
             "fd for /dev/zero  %d\n"
             "warning code      %d\n",
             pzFile, pMI->txt_data,      pMI->txt_size,    pMI->txt_fd,
                     pMI->txt_full_size, pMI->txt_zero_fd, pMI->txt_errno);

    {
        char ch;
        char* p = pMI->txt_data;
        size_t sz = pMI->txt_size+1;
        while (sz-- > 0)
            ch = *(p++);
    }
    fprintf( stderr, "%d bytes scanned\n", pMI->txt_size+1 );
#endif
    return pMI->txt_data;
}


/*=export_func  txt_munmap
 *
 * what:  unmap the data mapped in by txt_mmap
 *
 * arg:   tmap_info_t*, mapinfo, info about the mapping
 *
 * ret-type:   int
 * ret-desc:   -1 or 0.  @file{errno} will have the error code.
 *
 * doc:
 *
 * This routine will unmap the data mapped in with @code{txt_mmap} and close
 * the associated file descriptors opened by that function.
 *
 * see: munmap(2), close(2)
 *
 * err: Any error code issued by munmap(2) or close(2) is possible.
=*/
int
txt_munmap( tmap_info_t* pMI )
{
    int res = munmap( pMI->txt_data, pMI->txt_full_size );
    if (res != 0)
        goto error_return;

    res = close( pMI->txt_fd );
    if (res != 0)
        goto error_return;

    pMI->txt_fd = -1;
    errno = 0;
    if (pMI->txt_zero_fd != -1) {
        res = close( pMI->txt_zero_fd );
        if (res == 0)
            pMI->txt_zero_fd = -1;
    }

 error_return:
    pMI->txt_errno = errno;
    return res;
}
#endif
/*
 * Local Variables:
 * c-file-style: "stroustrup"
 * indent-tabs-mode: nil
 * End:
 * txtmmap.c ends here */

reply via email to

[Prev in Thread] Current Thread [Next in Thread]