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