1/*
2 * $Id: 14e1f51d1a5a31d8395fdf1e93a07bace1c59e87 $
3 *
4 * Time-stamp:      "2007-07-04 11:35:49 bkorb"
5 *
6 *  This file is part of AutoOpts, a companion to AutoGen.
7 *  AutoOpts is free software.
8 *  AutoOpts is copyright (c) 1992-2009 by Bruce Korb - all rights reserved
9 *
10 *  AutoOpts is available under any one of two licenses.  The license
11 *  in use must be one of these two and the choice is under the control
12 *  of the user of the license.
13 *
14 *   The GNU Lesser General Public License, version 3 or later
15 *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
16 *
17 *   The Modified Berkeley Software Distribution License
18 *      See the file "COPYING.mbsd"
19 *
20 *  These files have the following md5sums:
21 *
22 *  43b91e8ca915626ed3818ffb1b71248b pkg/libopts/COPYING.gplv3
23 *  06a1a2e4760c90ea5e1dad8dfaac4d39 pkg/libopts/COPYING.lgplv3
24 *  66a5cedaf62c4b2637025f049f9b826f pkg/libopts/COPYING.mbsd
25 */
26
27#ifndef MAP_ANONYMOUS
28#  ifdef   MAP_ANON
29#  define  MAP_ANONYMOUS   MAP_ANON
30#  endif
31#endif
32
33/*
34 *  Some weird systems require that a specifically invalid FD number
35 *  get passed in as an argument value.  Which value is that?  Well,
36 *  as everybody knows, if open(2) fails, it returns -1, so that must
37 *  be the value.  :)
38 */
39#define AO_INVALID_FD  -1
40
41#define FILE_WRITABLE(_prt,_flg) \
42        (   (_prt & PROT_WRITE) \
43         && ((_flg & (MAP_SHARED|MAP_PRIVATE)) == MAP_SHARED))
44#define MAP_FAILED_PTR ((void*)MAP_FAILED)
45
46/*=export_func  text_mmap
47 * private:
48 *
49 * what:  map a text file with terminating NUL
50 *
51 * arg:   char const*,  pzFile,  name of the file to map
52 * arg:   int,          prot,    mmap protections (see mmap(2))
53 * arg:   int,          flags,   mmap flags (see mmap(2))
54 * arg:   tmap_info_t*, mapinfo, returned info about the mapping
55 *
56 * ret-type:   void*
57 * ret-desc:   The mmaped data address
58 *
59 * doc:
60 *
61 * This routine will mmap a file into memory ensuring that there is at least
62 * one @file{NUL} character following the file data.  It will return the
63 * address where the file contents have been mapped into memory.  If there is a
64 * problem, then it will return @code{MAP_FAILED} and set @file{errno}
65 * appropriately.
66 *
67 * The named file does not exist, @code{stat(2)} will set @file{errno} as it
68 * will.  If the file is not a regular file, @file{errno} will be
69 * @code{EINVAL}.  At that point, @code{open(2)} is attempted with the access
70 * bits set appropriately for the requested @code{mmap(2)} protections and flag
71 * bits.  On failure, @file{errno} will be set according to the documentation
72 * for @code{open(2)}.  If @code{mmap(2)} fails, @file{errno} will be set as
73 * that routine sets it.  If @code{text_mmap} works to this point, a valid
74 * address will be returned, but there may still be ``issues''.
75 *
76 * If the file size is not an even multiple of the system page size, then
77 * @code{text_map} will return at this point and @file{errno} will be zero.
78 * Otherwise, an anonymous map is attempted.  If not available, then an attempt
79 * is made to @code{mmap(2)} @file{/dev/zero}.  If any of these fail, the
80 * address of the file's data is returned, bug @code{no} @file{NUL} characters
81 * are mapped after the end of the data.
82 *
83 * see: mmap(2), open(2), stat(2)
84 *
85 * err: Any error code issued by mmap(2), open(2), stat(2) is possible.
86 *      Additionally, if the specified file is not a regular file, then
87 *      errno will be set to @code{EINVAL}.
88 *
89 * example:
90 * #include <mylib.h>
91 * tmap_info_t mi;
92 * int no_nul;
93 * void* data = text_mmap( "file", PROT_WRITE, MAP_PRIVATE, &mi );
94 * if (data == MAP_FAILED) return;
95 * no_nul = (mi.txt_size == mi.txt_full_size);
96 * << use the data >>
97 * text_munmap( &mi );
98=*/
99void*
100text_mmap( char const* pzFile, int prot, int flags, tmap_info_t* pMI )
101{
102    memset( pMI, 0, sizeof(*pMI) );
103#ifdef HAVE_MMAP
104    pMI->txt_zero_fd = -1;
105#endif
106    pMI->txt_fd = -1;
107
108    /*
109     *  Make sure we can stat the regular file.  Save the file size.
110     */
111    {
112        struct stat sb;
113        if (stat( pzFile, &sb ) != 0) {
114            pMI->txt_errno = errno;
115            return MAP_FAILED_PTR;
116        }
117
118        if (! S_ISREG( sb.st_mode )) {
119            pMI->txt_errno = errno = EINVAL;
120            return MAP_FAILED_PTR;
121        }
122
123        pMI->txt_size = sb.st_size;
124    }
125
126    /*
127     *  Map mmap flags and protections into open flags and do the open.
128     */
129    {
130        int o_flag;
131        /*
132         *  See if we will be updating the file.  If we can alter the memory
133         *  and if we share the data and we are *not* copy-on-writing the data,
134         *  then our updates will show in the file, so we must open with
135         *  write access.
136         */
137        if (FILE_WRITABLE(prot,flags))
138            o_flag = O_RDWR;
139        else
140            o_flag = O_RDONLY;
141
142        /*
143         *  If you're not sharing the file and you are writing to it,
144         *  then don't let anyone else have access to the file.
145         */
146        if (((flags & MAP_SHARED) == 0) && (prot & PROT_WRITE))
147            o_flag |= O_EXCL;
148
149        pMI->txt_fd = open( pzFile, o_flag );
150    }
151
152    if (pMI->txt_fd == AO_INVALID_FD) {
153        pMI->txt_errno = errno;
154        return MAP_FAILED_PTR;
155    }
156
157#ifdef HAVE_MMAP /* * * * * WITH MMAP * * * * * */
158    /*
159     *  do the mmap.  If we fail, then preserve errno, close the file and
160     *  return the failure.
161     */
162    pMI->txt_data =
163        mmap(NULL, pMI->txt_size+1, prot, flags, pMI->txt_fd, (size_t)0);
164    if (pMI->txt_data == MAP_FAILED_PTR) {
165        pMI->txt_errno = errno;
166        goto fail_return;
167    }
168
169    /*
170     *  Most likely, everything will turn out fine now.  The only difficult
171     *  part at this point is coping with files with sizes that are a multiple
172     *  of the page size.  Handling that is what this whole thing is about.
173     */
174    pMI->txt_zero_fd = -1;
175    pMI->txt_errno   = 0;
176
177    {
178        void* pNuls;
179#ifdef _SC_PAGESIZE
180        size_t pgsz = sysconf(_SC_PAGESIZE);
181#else
182        size_t pgsz = getpagesize();
183#endif
184        /*
185         *  Compute the pagesize rounded mapped memory size.
186         *  IF this is not the same as the file size, then there are NUL's
187         *  at the end of the file mapping and all is okay.
188         */
189        pMI->txt_full_size = (pMI->txt_size + (pgsz - 1)) & ~(pgsz - 1);
190        if (pMI->txt_size != pMI->txt_full_size)
191            return pMI->txt_data;
192
193        /*
194         *  Still here?  We have to remap the trailing inaccessible page
195         *  either anonymously or to /dev/zero.
196         */
197        pMI->txt_full_size += pgsz;
198#if defined(MAP_ANONYMOUS)
199        pNuls = mmap(
200                (void*)(((char*)pMI->txt_data) + pMI->txt_size),
201                pgsz, PROT_READ|PROT_WRITE,
202                MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, AO_INVALID_FD, (size_t)0);
203
204        if (pNuls != MAP_FAILED_PTR)
205            return pMI->txt_data;
206
207        pMI->txt_errno = errno;
208
209#elif defined(HAVE_DEV_ZERO)
210        pMI->txt_zero_fd = open( "/dev/zero", O_RDONLY );
211
212        if (pMI->txt_zero_fd == AO_INVALID_FD) {
213            pMI->txt_errno = errno;
214
215        } else {
216            pNuls = mmap(
217                    (void*)(((char*)pMI->txt_data) + pMI->txt_size), pgsz,
218                    PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,
219                    pMI->txt_zero_fd, 0 );
220
221            if (pNuls != MAP_FAILED_PTR)
222                return pMI->txt_data;
223
224            pMI->txt_errno = errno;
225            close( pMI->txt_zero_fd );
226            pMI->txt_zero_fd = -1;
227        }
228#endif
229
230        pMI->txt_full_size = pMI->txt_size;
231    }
232
233    {
234        void* p = AGALOC( pMI->txt_size+1, "file text" );
235        memcpy( p, pMI->txt_data, pMI->txt_size );
236        ((char*)p)[pMI->txt_size] = NUL;
237        munmap(pMI->txt_data, pMI->txt_size );
238        pMI->txt_data = p;
239    }
240    pMI->txt_alloc = 1;
241    return pMI->txt_data;
242
243#else /* * * * * * no HAVE_MMAP * * * * * */
244
245    pMI->txt_data = AGALOC( pMI->txt_size+1, "file text" );
246    if (pMI->txt_data == NULL) {
247        pMI->txt_errno = ENOMEM;
248        goto fail_return;
249    }
250
251    {
252        size_t sz = pMI->txt_size;
253        char*  pz = pMI->txt_data;
254
255        while (sz > 0) {
256            ssize_t rdct = read( pMI->txt_fd, pz, sz );
257            if (rdct <= 0) {
258                pMI->txt_errno = errno;
259                fprintf( stderr, zFSErrReadFile,
260                         errno, strerror( errno ), pzFile );
261                free( pMI->txt_data );
262                goto fail_return;
263            }
264
265            pz += rdct;
266            sz -= rdct;
267        }
268
269        *pz = NUL;
270    }
271
272    /*
273     *  We never need a dummy page mapped in
274     */
275    pMI->txt_zero_fd = -1;
276    pMI->txt_errno   = 0;
277
278    return pMI->txt_data;
279
280#endif /* * * * * * no HAVE_MMAP * * * * * */
281
282 fail_return:
283    if (pMI->txt_fd >= 0) {
284        close( pMI->txt_fd );
285        pMI->txt_fd = -1;
286    }
287    errno = pMI->txt_errno;
288    pMI->txt_data = MAP_FAILED_PTR;
289    return pMI->txt_data;
290}
291
292
293/*=export_func  text_munmap
294 * private:
295 *
296 * what:  unmap the data mapped in by text_mmap
297 *
298 * arg:   tmap_info_t*, mapinfo, info about the mapping
299 *
300 * ret-type:   int
301 * ret-desc:   -1 or 0.  @file{errno} will have the error code.
302 *
303 * doc:
304 *
305 * This routine will unmap the data mapped in with @code{text_mmap} and close
306 * the associated file descriptors opened by that function.
307 *
308 * see: munmap(2), close(2)
309 *
310 * err: Any error code issued by munmap(2) or close(2) is possible.
311=*/
312int
313text_munmap( tmap_info_t* pMI )
314{
315#ifdef HAVE_MMAP
316    int res = 0;
317    if (pMI->txt_alloc) {
318        /*
319         *  IF the user has write permission and the text is not mapped private,
320         *  then write back any changes.  Hopefully, nobody else has modified
321         *  the file in the mean time.
322         */
323        if (   ((pMI->txt_prot & PROT_WRITE) != 0)
324            && ((pMI->txt_flags & MAP_PRIVATE) == 0))  {
325
326            if (lseek(pMI->txt_fd, (size_t)0, SEEK_SET) != 0)
327                goto error_return;
328
329            res = (write( pMI->txt_fd, pMI->txt_data, pMI->txt_size ) < 0)
330                ? errno : 0;
331        }
332
333        AGFREE( pMI->txt_data );
334        errno = res;
335    } else {
336        res = munmap( pMI->txt_data, pMI->txt_full_size );
337    }
338    if (res != 0)
339        goto error_return;
340
341    res = close( pMI->txt_fd );
342    if (res != 0)
343        goto error_return;
344
345    pMI->txt_fd = -1;
346    errno = 0;
347    if (pMI->txt_zero_fd != -1) {
348        res = close( pMI->txt_zero_fd );
349        pMI->txt_zero_fd = -1;
350    }
351
352 error_return:
353    pMI->txt_errno = errno;
354    return res;
355#else  /* HAVE_MMAP */
356
357    errno = 0;
358    /*
359     *  IF the memory is writable *AND* it is not private (copy-on-write)
360     *     *AND* the memory is "sharable" (seen by other processes)
361     *  THEN rewrite the data.
362     */
363    if (   FILE_WRITABLE(pMI->txt_prot, pMI->txt_flags)
364        && (lseek( pMI->txt_fd, 0, SEEK_SET ) >= 0) ) {
365        write( pMI->txt_fd, pMI->txt_data, pMI->txt_size );
366    }
367
368    close( pMI->txt_fd );
369    pMI->txt_fd = -1;
370    pMI->txt_errno = errno;
371    free( pMI->txt_data );
372
373    return pMI->txt_errno;
374#endif /* HAVE_MMAP */
375}
376
377/*
378 * Local Variables:
379 * mode: C
380 * c-file-style: "stroustrup"
381 * indent-tabs-mode: nil
382 * End:
383 * end of autoopts/text_mmap.c */
384