add changelog
[platform/upstream/gdbm.git] / src / mmap.c
1 /* This file is part of GDBM.
2    Copyright (C) 2007, 2011, 2013 Free Software Foundation, Inc.
3
4    GDBM is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8
9    GDBM is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with GDBM. If not, see <http://www.gnu.org/licenses/>.   */
16
17 #include "autoconf.h"
18
19 #if HAVE_MMAP
20
21 # include "gdbmdefs.h"
22
23 # include <sys/types.h>
24 # include <sys/time.h>
25 # include <sys/file.h>
26 # include <sys/stat.h>
27 # include <sys/mman.h>
28 # include <stdio.h>
29
30 /* Some systems fail to define this */
31 # ifndef MAP_FAILED
32 #  define MAP_FAILED ((void*)-1)
33 # endif
34
35 /* Translate current offset in the mapped region into the absolute position */
36 # define _GDBM_MMAPPED_POS(dbf) ((dbf)->mapped_off + (dbf)->mapped_pos)
37 /* Return true if the absolute offset OFF lies within the currentlty mmapped
38    region */
39 # define _GDBM_IN_MAPPED_REGION_P(dbf, off) \
40   ((off) >= (dbf)->mapped_off \
41    && ((off) - (dbf)->mapped_off) < (dbf)->mapped_size)
42 /* Return true if the current region needs to be remapped */
43 # define _GDBM_NEED_REMAP(dbf) \
44   (!(dbf)->mapped_region || (dbf)->mapped_pos == (dbf)->mapped_size)
45 /* Return the sum of the currently mapped size and DELTA */
46 # define SUM_FILE_SIZE(dbf, delta) \
47   ((dbf)->mapped_off + (dbf)->mapped_size + (delta))
48
49 /* Store the size of the GDBM file DBF in *PSIZE.
50    Return 0 on success and -1 on failure. */
51 int
52 _gdbm_file_size (GDBM_FILE dbf, off_t *psize)
53 {
54   struct stat sb;
55   if (fstat (dbf->desc, &sb))
56     return -1; 
57   *psize = sb.st_size;
58   return 0;
59 }
60
61 /* Unmap the region. Reset all mapped fields to initial values. */
62 void
63 _gdbm_mapped_unmap (GDBM_FILE dbf)
64 {
65   if (dbf->mapped_region)
66     {
67       munmap (dbf->mapped_region, dbf->mapped_size);
68       dbf->mapped_region = NULL;
69       dbf->mapped_size = 0;
70       dbf->mapped_pos = 0;
71       dbf->mapped_off = 0;
72     }
73 }
74
75 /* Remap the DBF file according to dbf->{mapped_off,mapped_pos,mapped_size}.
76    Take care to recompute {mapped_off,mapped_pos} so that the former lies
77    on a page size boundary. */
78 int
79 _gdbm_internal_remap (GDBM_FILE dbf, size_t size)
80 {
81   void *p;
82   int flags = PROT_READ;
83   size_t page_size = sysconf (_SC_PAGESIZE);
84
85   munmap (dbf->mapped_region, dbf->mapped_size);
86   dbf->mapped_size = size;
87
88   if (size == 0)
89     return 0;
90   
91   dbf->mapped_pos += dbf->mapped_off % page_size;
92   dbf->mapped_off = (dbf->mapped_off / page_size) * page_size;
93
94   if (dbf->read_write)
95     flags |= PROT_WRITE;
96   
97   p = mmap (NULL, dbf->mapped_size, flags, MAP_SHARED,
98             dbf->desc, dbf->mapped_off);
99   if (p == MAP_FAILED)
100     {
101       dbf->mapped_region = NULL;
102       gdbm_errno = GDBM_MALLOC_ERROR;
103       return -1;
104     }
105   
106   dbf->mapped_region = p;
107   return 0;
108 }
109
110 # define _REMAP_DEFAULT 0
111 # define _REMAP_EXTEND  1
112 # define _REMAP_END     2
113
114 /* Remap the GDBM file so that its mapped region ends on SIZEth byte.
115    If the file is opened with write permissions, FLAG controls how
116    it is expanded.  The value _REMAP_DEFAULT truncates SIZE to the
117    actual file size.  The value _REMAP_EXTEND extends the file, if
118    necessary, to accomodate max(SIZE,dbf->header->next_block) bytes.
119    Finally, the value _REMAP_END instructs the function to use 
120    max(SIZE, file_size) as the upper bound of the mapped region.
121
122    If the file is opened read-only, FLAG is ignored and SIZE is
123    truncated to the actual file size.
124
125    The upper bound obtained that way is used as a *hint* to select
126    the actual size of the mapped region. which can never exceed
127    dbf->mapped_size_max.
128    
129    The function returns 0 on success, -1 on failure. */
130 int
131 _gdbm_mapped_remap (GDBM_FILE dbf, off_t size, int flag)
132 {
133   off_t file_size, pos;
134
135   if (_gdbm_file_size (dbf, &file_size))
136     {
137       SAVE_ERRNO (_gdbm_mapped_unmap (dbf));
138       gdbm_errno = GDBM_FILE_STAT_ERROR;
139       return -1; 
140     }
141
142   if (flag == _REMAP_END && size < file_size)
143     size = file_size;
144   
145   if (dbf->read_write)
146     {
147       if (size > file_size)
148         {
149           if (flag != _REMAP_DEFAULT)
150             {
151               char c = 0;
152
153               if (size < dbf->header->next_block)
154                 size = dbf->header->next_block;
155               lseek (dbf->desc, size - 1, SEEK_SET);
156               write (dbf->desc, &c, 1);
157               file_size = size;
158             }
159           else
160             {
161               size = file_size;
162               return 0;
163             }
164         }
165     }
166   else
167     {
168       if (size > file_size)
169         size = file_size;
170       
171       if (size == SUM_FILE_SIZE (dbf, 0))
172         return 0;
173     }
174
175   pos = _GDBM_MMAPPED_POS (dbf);
176   if (size > dbf->mapped_size_max)
177     {
178       dbf->mapped_off = pos;
179       dbf->mapped_pos = 0;
180       size = dbf->mapped_size_max;
181       if (dbf->mapped_off + size > file_size)
182         size = file_size - dbf->mapped_off;
183     }
184   else
185     {
186       dbf->mapped_pos += dbf->mapped_off;
187       dbf->mapped_off = 0;
188     }
189
190   return _gdbm_internal_remap (dbf, size);
191 }
192
193 /* Initialize mapping system. If the file size is less than MAPPED_SIZE_MAX,
194    map the entire file into the memory. Otherwise, map first MAPPED_SIZE_MAX
195    bytes. */
196 int
197 _gdbm_mapped_init (GDBM_FILE dbf)
198 {
199   if (dbf->mapped_size_max == 0)
200     dbf->mapped_size_max = SIZE_T_MAX;
201   return _gdbm_mapped_remap (dbf, 0, _REMAP_END);
202 }
203
204 /* Read LEN bytes from the GDBM file DBF into BUFFER. If mmapping is
205    not initialized or if it fails, fall back to the classical read(1).
206    Return number of bytes read or -1 on failure. */
207 ssize_t
208 _gdbm_mapped_read (GDBM_FILE dbf, void *buffer, size_t len)
209 {
210   if (dbf->memory_mapping)
211     {
212       ssize_t total = 0;
213       char *cbuf = buffer;
214       
215       while (len)
216         {
217           size_t nbytes;
218
219           if (_GDBM_NEED_REMAP (dbf))
220             {
221               off_t pos = _GDBM_MMAPPED_POS (dbf);
222               if (_gdbm_mapped_remap (dbf, SUM_FILE_SIZE (dbf, len),
223                                       _REMAP_DEFAULT))
224                 {
225                   int rc;
226
227                   dbf->memory_mapping = FALSE;
228                   if (lseek (dbf->desc, pos, SEEK_SET) != pos)
229                     return total > 0 ? total : -1;
230                   rc = read (dbf->desc, cbuf, len);
231                   if (rc == -1)
232                     return total > 0 ? total : -1;
233                   return total + rc;
234                 }
235             }
236
237           nbytes = dbf->mapped_size - dbf->mapped_pos;
238           if (nbytes == 0)
239             break;
240           if (nbytes > len)
241             nbytes = len;
242
243           memcpy (cbuf, (char*) dbf->mapped_region + dbf->mapped_pos, nbytes);
244           cbuf += nbytes;
245           dbf->mapped_pos += nbytes;
246           total += nbytes;
247           len -= nbytes;
248         }
249       return total;
250     }
251   return read (dbf->desc, buffer, len);
252 }
253
254 /* Write LEN bytes from BUFFER to the GDBM file DBF. If mmapping is
255    not initialized or if it fails, fall back to the classical write(1).
256    Return number of bytes written or -1 on failure. */
257 ssize_t
258 _gdbm_mapped_write (GDBM_FILE dbf, void *buffer, size_t len)
259 {
260   if (dbf->memory_mapping)
261     {
262       ssize_t total = 0;
263       char *cbuf = buffer;
264
265       while (len)
266         {
267           size_t nbytes;
268
269           if (_GDBM_NEED_REMAP (dbf))
270             {
271               off_t pos = _GDBM_MMAPPED_POS (dbf);
272               if (_gdbm_mapped_remap (dbf, SUM_FILE_SIZE (dbf, len),
273                                       _REMAP_EXTEND))
274                 {
275                   int rc;
276
277                   dbf->memory_mapping = FALSE;
278                   if (lseek (dbf->desc, pos, SEEK_SET) != pos)
279                     return total > 0 ? total : -1;
280                   rc = write (dbf->desc, cbuf, len);
281                   if (rc == -1)
282                     return total > 0 ? total : -1;
283                   return total + rc;
284                 }
285             }
286
287           nbytes = dbf->mapped_size - dbf->mapped_pos;
288           if (nbytes == 0)
289             break;
290           if (nbytes > len)
291             nbytes = len;
292
293           memcpy ((char*) dbf->mapped_region + dbf->mapped_pos, cbuf, nbytes);
294           cbuf += nbytes;
295           dbf->mapped_pos += nbytes;
296           total += nbytes;
297           len -= nbytes;
298         }
299       return total;
300     }
301   return write (dbf->desc, buffer, len);
302 }
303
304 /* Seek to the offset OFFSET in the GDBM file DBF, according to the
305    lseek(1)-style directive WHENCE. Return the resulting absolute
306    offset or -1 in case of failure. If mmapping is not initialized or
307    if it fails, fall back to the classical lseek(1).
308
309    Return number of bytes written or -1 on failure. */
310    
311 off_t
312 _gdbm_mapped_lseek (GDBM_FILE dbf, off_t offset, int whence)
313 {
314   if (dbf->memory_mapping)
315     {
316       off_t needle;
317       
318       switch (whence)
319         {
320         case SEEK_SET:
321           needle = offset;
322           break;
323           
324         case SEEK_CUR:
325           needle = offset + _GDBM_MMAPPED_POS (dbf);
326           break;
327           
328         case SEEK_END:
329           {
330             off_t file_size;
331             if (_gdbm_file_size (dbf, &file_size))
332               {
333                 gdbm_errno = GDBM_FILE_STAT_ERROR;
334                 return -1;
335               }
336             needle = file_size - offset; 
337             break;
338           }
339         }
340
341       if (!_GDBM_IN_MAPPED_REGION_P (dbf, needle))
342         {
343           _gdbm_mapped_unmap (dbf);
344           dbf->mapped_off = needle;
345           dbf->mapped_pos = 0;
346         }
347       else
348         dbf->mapped_pos = needle - dbf->mapped_off;
349       return needle;
350     }
351   return lseek (dbf->desc, offset, whence);
352 }
353
354 /* Sync the mapped region to disk. */
355 int
356 _gdbm_mapped_sync (GDBM_FILE dbf)
357 {
358   if (dbf->mapped_region)
359     {
360       return msync (dbf->mapped_region, dbf->mapped_size,
361                     MS_SYNC | MS_INVALIDATE);
362     }
363   return fsync (dbf->desc);
364 }
365
366 #endif