1181834Sroberto/*
2181834Sroberto * $Id: text_mmap.c,v 4.15 2006/11/27 01:52:23 bkorb Exp $
3181834Sroberto *
4181834Sroberto * Time-stamp:      "2006-09-10 14:50:04 bkorb"
5181834Sroberto */
6181834Sroberto
7181834Sroberto#ifndef MAP_ANONYMOUS
8181834Sroberto#  ifdef   MAP_ANON
9181834Sroberto#  define  MAP_ANONYMOUS   MAP_ANON
10181834Sroberto#  endif
11181834Sroberto#endif
12181834Sroberto
13181834Sroberto/*
14181834Sroberto *  Some weird systems require that a specifically invalid FD number
15181834Sroberto *  get passed in as an argument value.  Which value is that?  Well,
16181834Sroberto *  as everybody knows, if open(2) fails, it returns -1, so that must
17181834Sroberto *  be the value.  :)
18181834Sroberto */
19181834Sroberto#define AO_INVALID_FD  -1
20181834Sroberto
21181834Sroberto#define FILE_WRITABLE(_prt,_flg) \
22181834Sroberto        (   (_prt & PROT_WRITE) \
23181834Sroberto         && ((_flg & (MAP_SHARED|MAP_PRIVATE)) == MAP_SHARED))
24181834Sroberto#define MAP_FAILED_PTR ((void*)MAP_FAILED)
25181834Sroberto
26181834Sroberto/*=export_func  text_mmap
27181834Sroberto * private:
28181834Sroberto *
29181834Sroberto * what:  map a text file with terminating NUL
30181834Sroberto *
31181834Sroberto * arg:   char const*,  pzFile,  name of the file to map
32181834Sroberto * arg:   int,          prot,    mmap protections (see mmap(2))
33181834Sroberto * arg:   int,          flags,   mmap flags (see mmap(2))
34181834Sroberto * arg:   tmap_info_t*, mapinfo, returned info about the mapping
35181834Sroberto *
36181834Sroberto * ret-type:   void*
37181834Sroberto * ret-desc:   The mmaped data address
38181834Sroberto *
39181834Sroberto * doc:
40181834Sroberto *
41181834Sroberto * This routine will mmap a file into memory ensuring that there is at least
42181834Sroberto * one @file{NUL} character following the file data.  It will return the
43181834Sroberto * address where the file contents have been mapped into memory.  If there is a
44181834Sroberto * problem, then it will return @code{MAP_FAILED} and set @file{errno}
45181834Sroberto * appropriately.
46181834Sroberto *
47181834Sroberto * The named file does not exist, @code{stat(2)} will set @file{errno} as it
48181834Sroberto * will.  If the file is not a regular file, @file{errno} will be
49181834Sroberto * @code{EINVAL}.  At that point, @code{open(2)} is attempted with the access
50181834Sroberto * bits set appropriately for the requested @code{mmap(2)} protections and flag
51181834Sroberto * bits.  On failure, @file{errno} will be set according to the documentation
52181834Sroberto * for @code{open(2)}.  If @code{mmap(2)} fails, @file{errno} will be set as
53181834Sroberto * that routine sets it.  If @code{text_mmap} works to this point, a valid
54181834Sroberto * address will be returned, but there may still be ``issues''.
55181834Sroberto *
56181834Sroberto * If the file size is not an even multiple of the system page size, then
57181834Sroberto * @code{text_map} will return at this point and @file{errno} will be zero.
58181834Sroberto * Otherwise, an anonymous map is attempted.  If not available, then an attempt
59181834Sroberto * is made to @code{mmap(2)} @file{/dev/zero}.  If any of these fail, the
60181834Sroberto * address of the file's data is returned, bug @code{no} @file{NUL} characters
61181834Sroberto * are mapped after the end of the data.
62181834Sroberto *
63181834Sroberto * see: mmap(2), open(2), stat(2)
64181834Sroberto *
65181834Sroberto * err: Any error code issued by mmap(2), open(2), stat(2) is possible.
66181834Sroberto *      Additionally, if the specified file is not a regular file, then
67181834Sroberto *      errno will be set to @code{EINVAL}.
68181834Sroberto *
69181834Sroberto * example:
70181834Sroberto * #include <mylib.h>
71181834Sroberto * tmap_info_t mi;
72181834Sroberto * int no_nul;
73181834Sroberto * void* data = text_mmap( "file", PROT_WRITE, MAP_PRIVATE, &mi );
74181834Sroberto * if (data == MAP_FAILED) return;
75181834Sroberto * no_nul = (mi.txt_size == mi.txt_full_size);
76181834Sroberto * << use the data >>
77181834Sroberto * text_munmap( &mi );
78181834Sroberto=*/
79181834Srobertovoid*
80181834Srobertotext_mmap( char const* pzFile, int prot, int flags, tmap_info_t* pMI )
81181834Sroberto{
82181834Sroberto    memset( pMI, 0, sizeof(*pMI) );
83181834Sroberto#ifdef HAVE_MMAP
84181834Sroberto    pMI->txt_zero_fd = -1;
85181834Sroberto#endif
86181834Sroberto    pMI->txt_fd = -1;
87181834Sroberto
88181834Sroberto    /*
89181834Sroberto     *  Make sure we can stat the regular file.  Save the file size.
90181834Sroberto     */
91181834Sroberto    {
92181834Sroberto        struct stat sb;
93181834Sroberto        if (stat( pzFile, &sb ) != 0) {
94181834Sroberto            pMI->txt_errno = errno;
95181834Sroberto            return MAP_FAILED_PTR;
96181834Sroberto        }
97181834Sroberto
98181834Sroberto        if (! S_ISREG( sb.st_mode )) {
99181834Sroberto            pMI->txt_errno = errno = EINVAL;
100181834Sroberto            return MAP_FAILED_PTR;
101181834Sroberto        }
102181834Sroberto
103181834Sroberto        pMI->txt_size = sb.st_size;
104181834Sroberto    }
105181834Sroberto
106181834Sroberto    /*
107181834Sroberto     *  Map mmap flags and protections into open flags and do the open.
108181834Sroberto     */
109181834Sroberto    {
110181834Sroberto        int o_flag;
111181834Sroberto        /*
112181834Sroberto         *  See if we will be updating the file.  If we can alter the memory
113181834Sroberto         *  and if we share the data and we are *not* copy-on-writing the data,
114181834Sroberto         *  then our updates will show in the file, so we must open with
115181834Sroberto         *  write access.
116181834Sroberto         */
117181834Sroberto        if (FILE_WRITABLE(prot,flags))
118181834Sroberto            o_flag = O_RDWR;
119181834Sroberto        else
120181834Sroberto            o_flag = O_RDONLY;
121181834Sroberto
122181834Sroberto        /*
123181834Sroberto         *  If you're not sharing the file and you are writing to it,
124181834Sroberto         *  then don't let anyone else have access to the file.
125181834Sroberto         */
126181834Sroberto        if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE))
127181834Sroberto            o_flag |= O_EXCL;
128181834Sroberto
129181834Sroberto        pMI->txt_fd = open( pzFile, o_flag );
130181834Sroberto    }
131181834Sroberto
132181834Sroberto    if (pMI->txt_fd == AO_INVALID_FD) {
133181834Sroberto        pMI->txt_errno = errno;
134181834Sroberto        return MAP_FAILED_PTR;
135181834Sroberto    }
136181834Sroberto
137181834Sroberto#ifdef HAVE_MMAP /* * * * * WITH MMAP * * * * * */
138181834Sroberto    /*
139181834Sroberto     *  do the mmap.  If we fail, then preserve errno, close the file and
140181834Sroberto     *  return the failure.
141181834Sroberto     */
142181834Sroberto    pMI->txt_data =
143181834Sroberto        mmap(NULL, pMI->txt_size+1, prot, flags, pMI->txt_fd, (size_t)0);
144181834Sroberto    if (pMI->txt_data == MAP_FAILED_PTR) {
145181834Sroberto        pMI->txt_errno = errno;
146181834Sroberto        goto fail_return;
147181834Sroberto    }
148181834Sroberto
149181834Sroberto    /*
150181834Sroberto     *  Most likely, everything will turn out fine now.  The only difficult
151181834Sroberto     *  part at this point is coping with files with sizes that are a multiple
152181834Sroberto     *  of the page size.  Handling that is what this whole thing is about.
153181834Sroberto     */
154181834Sroberto    pMI->txt_zero_fd = -1;
155181834Sroberto    pMI->txt_errno   = 0;
156181834Sroberto
157181834Sroberto    {
158181834Sroberto        void* pNuls;
159181834Sroberto#ifdef _SC_PAGESIZE
160181834Sroberto        size_t pgsz = sysconf(_SC_PAGESIZE);
161181834Sroberto#else
162181834Sroberto        size_t pgsz = getpagesize();
163181834Sroberto#endif
164181834Sroberto        /*
165181834Sroberto         *  Compute the pagesize rounded mapped memory size.
166181834Sroberto         *  IF this is not the same as the file size, then there are NUL's
167181834Sroberto         *  at the end of the file mapping and all is okay.
168181834Sroberto         */
169181834Sroberto        pMI->txt_full_size = (pMI->txt_size + (pgsz - 1)) & ~(pgsz - 1);
170181834Sroberto        if (pMI->txt_size != pMI->txt_full_size)
171181834Sroberto            return pMI->txt_data;
172181834Sroberto
173181834Sroberto        /*
174181834Sroberto         *  Still here?  We have to remap the trailing inaccessible page
175181834Sroberto         *  either anonymously or to /dev/zero.
176181834Sroberto         */
177181834Sroberto        pMI->txt_full_size += pgsz;
178181834Sroberto#if defined(MAP_ANONYMOUS)
179181834Sroberto        pNuls = mmap(
180181834Sroberto                (void*)(((char*)pMI->txt_data) + pMI->txt_size),
181181834Sroberto                pgsz, PROT_READ|PROT_WRITE,
182181834Sroberto                MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, AO_INVALID_FD, (size_t)0);
183181834Sroberto
184181834Sroberto        if (pNuls != MAP_FAILED_PTR)
185181834Sroberto            return pMI->txt_data;
186181834Sroberto
187181834Sroberto        pMI->txt_errno = errno;
188181834Sroberto
189181834Sroberto#elif defined(HAVE_DEV_ZERO)
190181834Sroberto        pMI->txt_zero_fd = open( "/dev/zero", O_RDONLY );
191181834Sroberto
192181834Sroberto        if (pMI->txt_zero_fd == AO_INVALID_FD) {
193181834Sroberto            pMI->txt_errno = errno;
194181834Sroberto
195181834Sroberto        } else {
196181834Sroberto            pNuls = mmap(
197181834Sroberto                    (void*)(((char*)pMI->txt_data) + pMI->txt_size), pgsz,
198181834Sroberto                    PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,
199181834Sroberto                    pMI->txt_zero_fd, 0 );
200181834Sroberto
201181834Sroberto            if (pNuls != MAP_FAILED_PTR)
202181834Sroberto                return pMI->txt_data;
203181834Sroberto
204181834Sroberto            pMI->txt_errno = errno;
205181834Sroberto            close( pMI->txt_zero_fd );
206181834Sroberto            pMI->txt_zero_fd = -1;
207181834Sroberto        }
208181834Sroberto#endif
209181834Sroberto
210181834Sroberto        pMI->txt_full_size = pMI->txt_size;
211181834Sroberto    }
212181834Sroberto
213181834Sroberto    {
214181834Sroberto        void* p = AGALOC( pMI->txt_size+1, "file text" );
215181834Sroberto        memcpy( p, pMI->txt_data, pMI->txt_size );
216181834Sroberto        ((char*)p)[pMI->txt_size] = NUL;
217181834Sroberto        munmap(pMI->txt_data, pMI->txt_size );
218181834Sroberto        pMI->txt_data = p;
219181834Sroberto    }
220181834Sroberto    pMI->txt_alloc = 1;
221181834Sroberto    return pMI->txt_data;
222181834Sroberto
223181834Sroberto#else /* * * * * * no HAVE_MMAP * * * * * */
224181834Sroberto
225181834Sroberto    pMI->txt_data = AGALOC( pMI->txt_size+1, "file text" );
226181834Sroberto    if (pMI->txt_data == NULL) {
227181834Sroberto        pMI->txt_errno = ENOMEM;
228181834Sroberto        goto fail_return;
229181834Sroberto    }
230181834Sroberto
231181834Sroberto    {
232181834Sroberto        size_t sz = pMI->txt_size;
233181834Sroberto        char*  pz = pMI->txt_data;
234181834Sroberto
235181834Sroberto        while (sz > 0) {
236181834Sroberto            ssize_t rdct = read( pMI->txt_fd, pz, sz );
237181834Sroberto            if (rdct <= 0) {
238181834Sroberto                pMI->txt_errno = errno;
239181834Sroberto                fprintf( stderr, zFSErrReadFile,
240181834Sroberto                         errno, strerror( errno ), pzFile );
241181834Sroberto                free( pMI->txt_data );
242181834Sroberto                goto fail_return;
243181834Sroberto            }
244181834Sroberto
245181834Sroberto            pz += rdct;
246181834Sroberto            sz -= rdct;
247181834Sroberto        }
248181834Sroberto
249181834Sroberto        *pz = NUL;
250181834Sroberto    }
251181834Sroberto
252181834Sroberto    /*
253181834Sroberto     *  We never need a dummy page mapped in
254181834Sroberto     */
255181834Sroberto    pMI->txt_zero_fd = -1;
256181834Sroberto    pMI->txt_errno   = 0;
257181834Sroberto
258181834Sroberto    return pMI->txt_data;
259181834Sroberto
260181834Sroberto#endif /* * * * * * no HAVE_MMAP * * * * * */
261181834Sroberto
262181834Sroberto fail_return:
263181834Sroberto    if (pMI->txt_fd >= 0) {
264181834Sroberto        close( pMI->txt_fd );
265181834Sroberto        pMI->txt_fd = -1;
266181834Sroberto    }
267181834Sroberto    errno = pMI->txt_errno;
268181834Sroberto    pMI->txt_data = MAP_FAILED_PTR;
269181834Sroberto    return pMI->txt_data;
270181834Sroberto}
271181834Sroberto
272181834Sroberto
273181834Sroberto/*=export_func  text_munmap
274181834Sroberto * private:
275181834Sroberto *
276181834Sroberto * what:  unmap the data mapped in by text_mmap
277181834Sroberto *
278181834Sroberto * arg:   tmap_info_t*, mapinfo, info about the mapping
279181834Sroberto *
280181834Sroberto * ret-type:   int
281181834Sroberto * ret-desc:   -1 or 0.  @file{errno} will have the error code.
282181834Sroberto *
283181834Sroberto * doc:
284181834Sroberto *
285181834Sroberto * This routine will unmap the data mapped in with @code{text_mmap} and close
286181834Sroberto * the associated file descriptors opened by that function.
287181834Sroberto *
288181834Sroberto * see: munmap(2), close(2)
289181834Sroberto *
290181834Sroberto * err: Any error code issued by munmap(2) or close(2) is possible.
291181834Sroberto=*/
292181834Srobertoint
293181834Srobertotext_munmap( tmap_info_t* pMI )
294181834Sroberto{
295181834Sroberto#ifdef HAVE_MMAP
296181834Sroberto    int res = 0;
297181834Sroberto    if (pMI->txt_alloc) {
298181834Sroberto        /*
299181834Sroberto         *  IF the user has write permission and the text is not mapped private,
300181834Sroberto         *  then write back any changes.  Hopefully, nobody else has modified
301181834Sroberto         *  the file in the mean time.
302181834Sroberto         */
303181834Sroberto        if (   ((pMI->txt_prot & PROT_WRITE) != 0)
304181834Sroberto            && ((pMI->txt_flags & MAP_PRIVATE) == 0))  {
305181834Sroberto
306181834Sroberto            if (lseek(pMI->txt_fd, (size_t)0, SEEK_SET) != 0)
307181834Sroberto                goto error_return;
308181834Sroberto
309181834Sroberto            res = (write( pMI->txt_fd, pMI->txt_data, pMI->txt_size ) < 0)
310181834Sroberto                ? errno : 0;
311181834Sroberto        }
312181834Sroberto
313181834Sroberto        AGFREE( pMI->txt_data );
314181834Sroberto        errno = res;
315181834Sroberto    } else {
316181834Sroberto        res = munmap( pMI->txt_data, pMI->txt_full_size );
317181834Sroberto    }
318181834Sroberto    if (res != 0)
319181834Sroberto        goto error_return;
320181834Sroberto
321181834Sroberto    res = close( pMI->txt_fd );
322181834Sroberto    if (res != 0)
323181834Sroberto        goto error_return;
324181834Sroberto
325181834Sroberto    pMI->txt_fd = -1;
326181834Sroberto    errno = 0;
327181834Sroberto    if (pMI->txt_zero_fd != -1) {
328181834Sroberto        res = close( pMI->txt_zero_fd );
329181834Sroberto        pMI->txt_zero_fd = -1;
330181834Sroberto    }
331181834Sroberto
332181834Sroberto error_return:
333181834Sroberto    pMI->txt_errno = errno;
334181834Sroberto    return res;
335181834Sroberto#else  /* HAVE_MMAP */
336181834Sroberto
337181834Sroberto    errno = 0;
338181834Sroberto    /*
339181834Sroberto     *  IF the memory is writable *AND* it is not private (copy-on-write)
340181834Sroberto     *     *AND* the memory is "sharable" (seen by other processes)
341181834Sroberto     *  THEN rewrite the data.
342181834Sroberto     */
343181834Sroberto    if (   FILE_WRITABLE(pMI->txt_prot, pMI->txt_flags)
344181834Sroberto        && (lseek( pMI->txt_fd, 0, SEEK_SET ) >= 0) ) {
345181834Sroberto        write( pMI->txt_fd, pMI->txt_data, pMI->txt_size );
346181834Sroberto    }
347181834Sroberto
348181834Sroberto    close( pMI->txt_fd );
349181834Sroberto    pMI->txt_fd = -1;
350181834Sroberto    pMI->txt_errno = errno;
351181834Sroberto    free( pMI->txt_data );
352181834Sroberto
353181834Sroberto    return pMI->txt_errno;
354181834Sroberto#endif /* HAVE_MMAP */
355181834Sroberto}
356181834Sroberto
357181834Sroberto/*
358181834Sroberto * Local Variables:
359181834Sroberto * mode: C
360181834Sroberto * c-file-style: "stroustrup"
361181834Sroberto * indent-tabs-mode: nil
362181834Sroberto * End:
363181834Sroberto * end of autoopts/text_mmap.c */
364