1 /* mappedfile - interface to a mmaped, lockable, writable file
2  *
3  * Copyright (c) 1994-2011 Carnegie Mellon University.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * 3. The name "Carnegie Mellon University" must not be used to
18  *    endorse or promote products derived from this software without
19  *    prior written permission. For permission or any legal
20  *    details, please contact
21  *      Carnegie Mellon University
22  *      Center for Technology Transfer and Enterprise Creation
23  *      4615 Forbes Avenue
24  *      Suite 302
25  *      Pittsburgh, PA  15213
26  *      (412) 268-7393, fax: (412) 268-7395
27  *      innovation@andrew.cmu.edu
28  *
29  * 4. Redistributions of any form whatsoever must retain the following
30  *    acknowledgment:
31  *    "This product includes software developed by Computing Services
32  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33  *
34  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41  */
42 
43 /* Interface to an mmaped file, including locking.
44  *
45  * Many different modules within Cyrus, including most of the
46  * database engines, wrap an mmaped file with locking semantics,
47  * refreshing the map on re-locking, and writing to a location
48  * within the file.
49  *
50  * This module provides handy wrapper interfaces to each of those
51  * items.  NOTE - it doesn't provide a guarantee that the same file
52  * isn't opened twice, stomping all over the locks in the process.
53  * To get that, you need to protect in the caller.
54  *
55  */
56 
57 
58 #include "mappedfile.h"
59 
60 #include <config.h>
61 
62 #include <libgen.h>
63 #include <stdlib.h>
64 #include <limits.h>
65 #include <errno.h>
66 #include <syslog.h>
67 #include <sys/types.h>
68 #include <sys/stat.h>
69 #include <fcntl.h>
70 #ifdef HAVE_UNISTD_H
71 #include <unistd.h>
72 #endif
73 
74 #include "assert.h"
75 #include "cyr_lock.h"
76 #include "libcyr_cfg.h"
77 #include "map.h"
78 #include "retry.h"
79 #include "util.h"
80 #include "xmalloc.h"
81 
82 #define MF_UNLOCKED 0
83 #define MF_READLOCKED 1
84 #define MF_WRITELOCKED 2
85 
86 struct mappedfile {
87     char *fname;
88 
89     /* obviously you will need 64 bit size_t for 64 bit files... */
90     struct buf map_buf;
91     size_t map_size;
92 
93     /* the file itself */
94     int fd;
95 
96     /* tracking */
97     int lock_status;
98     int dirty;
99     int was_resized;
100     int is_rw;
101 };
102 
_ensure_mapped(struct mappedfile * mf,size_t offset,int update)103 static void _ensure_mapped(struct mappedfile *mf, size_t offset, int update)
104 {
105     /* we may be rewriting inside a file, so don't shrink, only extend */
106     if (update) {
107         if (offset > mf->map_size)
108             mf->was_resized = 1;
109         else
110             offset = mf->map_size;
111     }
112 
113     /* always give refresh another go, we may be map_nommap */
114     buf_init_mmap(&mf->map_buf, /*onceonly*/0, mf->fd, mf->fname,
115                   offset, /*mboxname*/NULL);
116 
117     mf->map_size = offset;
118 }
119 
120 /* NOTE - we don't provide any guarantees that the file isn't open multiple
121  * times.  So don't do that.  It will mess with your locking no end */
mappedfile_open(struct mappedfile ** mfp,const char * fname,int flags)122 EXPORTED int mappedfile_open(struct mappedfile **mfp,
123                              const char *fname, int flags)
124 {
125     struct mappedfile *mf;
126     struct stat sbuf;
127     int openmode = (flags & MAPPEDFILE_RW) ? O_RDWR : O_RDONLY;
128     int create = (flags & MAPPEDFILE_CREATE) ? 1 : 0;
129     int r;
130 
131     assert(fname);
132     assert(!*mfp);
133 
134     mf = xzmalloc(sizeof(struct mappedfile));
135     mf->fname = xstrdup(fname);
136     mf->is_rw = (flags & MAPPEDFILE_RW) ? 1 : 0;
137 
138     mf->fd = open(mf->fname, openmode, 0644);
139     if (mf->fd < 0 && errno == ENOENT) {
140         if (!create || !mf->is_rw) {
141             r = -errno;
142             goto err;
143         }
144         r = cyrus_mkdir(mf->fname, 0755);
145         if (r < 0) {
146             syslog(LOG_ERR, "IOERROR: cyrus_mkdir %s: %m", mf->fname);
147             goto err;
148         }
149         mf->fd = open(mf->fname, O_RDWR | O_CREAT, 0644);
150     }
151 
152     if (mf->fd == -1) {
153         syslog(LOG_ERR, "IOERROR: open %s: %m", mf->fname);
154         r = -errno;
155         goto err;
156     }
157 
158     /* it's zero, but set it anyway */
159     mf->lock_status = MF_UNLOCKED;
160     mf->dirty = 0;
161 
162     r = fstat(mf->fd, &sbuf);
163     if (r < 0) {
164         syslog(LOG_ERR, "IOERROR: fstat %s: %m", mf->fname);
165         goto err;
166     }
167 
168     _ensure_mapped(mf, sbuf.st_size, /*update*/0);
169 
170     *mfp = mf;
171 
172     return 0;
173 
174 err:
175     mappedfile_close(&mf);
176     return r;
177 }
178 
mappedfile_close(struct mappedfile ** mfp)179 EXPORTED int mappedfile_close(struct mappedfile **mfp)
180 {
181     struct mappedfile *mf = *mfp;
182     int r = 0;
183 
184     /* make this safe to call multiple times */
185     if (!mf) return 0;
186 
187     assert(mf->lock_status == MF_UNLOCKED);
188     assert(!mf->dirty);
189 
190     if (mf->fd >= 0)
191         r = close(mf->fd);
192 
193     buf_free(&mf->map_buf);
194     free(mf->fname);
195     free(mf);
196 
197     *mfp = NULL;
198 
199     return r;
200 }
201 
mappedfile_readlock(struct mappedfile * mf)202 EXPORTED int mappedfile_readlock(struct mappedfile *mf)
203 {
204     struct stat sbuf, sbuffile;
205     int newfd = -1;
206 
207     assert(mf->lock_status == MF_UNLOCKED);
208     assert(mf->fd != -1);
209     assert(!mf->dirty);
210 
211     for (;;) {
212         if (lock_shared(mf->fd, mf->fname) < 0) {
213             syslog(LOG_ERR, "IOERROR: lock_shared %s: %m", mf->fname);
214             return -EIO;
215         }
216 
217         if (fstat(mf->fd, &sbuf) == -1) {
218             syslog(LOG_ERR, "IOERROR: fstat %s: %m", mf->fname);
219             lock_unlock(mf->fd, mf->fname);
220             return -EIO;
221         }
222 
223         if (stat(mf->fname, &sbuffile) == -1) {
224             syslog(LOG_ERR, "IOERROR: stat %s: %m", mf->fname);
225             lock_unlock(mf->fd, mf->fname);
226             return -EIO;
227         }
228         if (sbuf.st_ino == sbuffile.st_ino) break;
229         buf_free(&mf->map_buf);
230 
231         newfd = open(mf->fname, O_RDWR, 0644);
232         if (newfd == -1) {
233             syslog(LOG_ERR, "IOERROR: open %s: %m", mf->fname);
234             lock_unlock(mf->fd, mf->fname);
235             return -EIO;
236         }
237 
238         dup2(newfd, mf->fd);
239         close(newfd);
240     }
241 
242     mf->lock_status = MF_READLOCKED;
243 
244     _ensure_mapped(mf, sbuf.st_size, /*update*/0);
245 
246     return 0;
247 }
248 
mappedfile_writelock(struct mappedfile * mf)249 EXPORTED int mappedfile_writelock(struct mappedfile *mf)
250 {
251     int r;
252     struct stat sbuf;
253     const char *lockfailaction;
254     int changed = 0;
255 
256     assert(mf->lock_status == MF_UNLOCKED);
257     assert(mf->fd != -1);
258     assert(mf->is_rw);
259     assert(!mf->dirty);
260 
261     r = lock_reopen_ex(mf->fd, mf->fname, &sbuf, &lockfailaction, &changed);
262     if (r < 0) {
263         syslog(LOG_ERR, "IOERROR: %s %s: %m", lockfailaction, mf->fname);
264         return r;
265     }
266     mf->lock_status = MF_WRITELOCKED;
267 
268     if (changed) buf_free(&mf->map_buf);
269 
270     _ensure_mapped(mf, sbuf.st_size, /*update*/0);
271 
272     return 0;
273 }
274 
mappedfile_unlock(struct mappedfile * mf)275 EXPORTED int mappedfile_unlock(struct mappedfile *mf)
276 {
277     int r;
278 
279     /* make this safe to call multiple times */
280     if (!mf) return 0;
281     if (mf->lock_status == MF_UNLOCKED) return 0;
282 
283     assert(mf->fd != -1);
284     assert(!mf->dirty);
285 
286     r = lock_unlock(mf->fd, mf->fname);
287     if (r < 0) {
288         syslog(LOG_ERR, "IOERROR: lock_unlock %s: %m", mf->fname);
289         return r;
290     }
291 
292     mf->lock_status = MF_UNLOCKED;
293 
294     return 0;
295 }
296 
mappedfile_commit(struct mappedfile * mf)297 EXPORTED int mappedfile_commit(struct mappedfile *mf)
298 {
299     assert(mf->fd != -1);
300 
301     if (!mf->dirty)
302         return 0; /* nice, nothing to do */
303 
304     assert(mf->is_rw);
305 
306     if (mf->was_resized) {
307         if (fsync(mf->fd) < 0) {
308             syslog(LOG_ERR, "IOERROR: %s fsync: %m", mf->fname);
309             return -EIO;
310         }
311     }
312     else {
313         if (fdatasync(mf->fd) < 0) {
314             syslog(LOG_ERR, "IOERROR: %s fdatasync: %m", mf->fname);
315             return -EIO;
316         }
317     }
318 
319     mf->dirty = 0;
320     mf->was_resized = 0;
321 
322     return 0;
323 }
324 
mappedfile_pwrite(struct mappedfile * mf,const char * base,size_t len,off_t offset)325 EXPORTED ssize_t mappedfile_pwrite(struct mappedfile *mf,
326                                    const char *base, size_t len,
327                                    off_t offset)
328 {
329     ssize_t written;
330     off_t pos;
331 
332     assert(mf->is_rw);
333     assert(mf->fd != -1);
334     assert(base);
335 
336     if (!len) return 0; /* nothing to write! */
337 
338     /* XXX - memcmp and don't both writing if it matches? */
339 
340     mf->dirty++;
341 
342     /* locate the file handle */
343     pos = lseek(mf->fd, offset, SEEK_SET);
344     if (pos < 0) {
345         syslog(LOG_ERR, "IOERROR: %s seek to %llX: %m", mf->fname,
346                (long long unsigned int)offset);
347         return -1;
348     }
349 
350     /* write the buffer */
351     written = retry_write(mf->fd, base, len);
352     if (written < 0) {
353         syslog(LOG_ERR, "IOERROR: %s write %llu bytes at %llX: %m",
354                mf->fname, (long long unsigned int)len,
355                (long long unsigned int)offset);
356         return -1;
357     }
358 
359     _ensure_mapped(mf, pos+written, /*update*/1);
360 
361     return written;
362 }
363 
mappedfile_pwritebuf(struct mappedfile * mf,const struct buf * buf,off_t offset)364 EXPORTED ssize_t mappedfile_pwritebuf(struct mappedfile *mf,
365                                       const struct buf *buf,
366                                       off_t offset)
367 {
368     return mappedfile_pwrite(mf, buf->s, buf->len, offset);
369 }
370 
mappedfile_pwritev(struct mappedfile * mf,const struct iovec * iov,int nio,off_t offset)371 EXPORTED ssize_t mappedfile_pwritev(struct mappedfile *mf,
372                                     const struct iovec *iov, int nio,
373                                     off_t offset)
374 {
375     ssize_t written;
376     off_t pos;
377 
378     assert(mf->is_rw);
379     assert(mf->fd != -1);
380     assert(iov);
381 
382     if (!nio) return 0; /* nothing to write! */
383 
384     /* XXX - memcmp and don't both writing if it matches? */
385 
386     mf->dirty++;
387 
388     /* locate the file handle */
389     pos = lseek(mf->fd, offset, SEEK_SET);
390     if (pos < 0) {
391         syslog(LOG_ERR, "IOERROR: %s seek to %llX: %m", mf->fname,
392                (long long unsigned int)offset);
393         return -1;
394     }
395 
396     /* write the buffer */
397     written = retry_writev(mf->fd, iov, nio);
398     if (written < 0) {
399         size_t len = 0;
400         int i;
401         for (i = 0; i < nio; i++) {
402             len += iov[i].iov_len;
403         }
404         syslog(LOG_ERR, "IOERROR: %s write %llu bytes at %llX: %m",
405                mf->fname, (long long unsigned int)len,
406                (long long unsigned int)offset);
407         return -1;
408     }
409 
410     _ensure_mapped(mf, pos+written, /*update*/1);
411 
412     return written;
413 }
414 
mappedfile_truncate(struct mappedfile * mf,off_t offset)415 EXPORTED int mappedfile_truncate(struct mappedfile *mf, off_t offset)
416 {
417     int r;
418 
419     assert(mf->is_rw);
420     assert(mf->fd != -1);
421 
422     mf->dirty++;
423 
424     r = ftruncate(mf->fd, offset);
425     if (r < 0) {
426         syslog(LOG_ERR, "IOERROR: ftruncate %s: %m", mf->fname);
427         return r;
428     }
429 
430     _ensure_mapped(mf, offset, /*update*/0);
431     mf->was_resized = 1; /* force the fsync */
432 
433     return 0;
434 }
435 
mappedfile_rename(struct mappedfile * mf,const char * newname)436 EXPORTED int mappedfile_rename(struct mappedfile *mf, const char *newname)
437 {
438     char *copy = xstrdup(newname);
439     const char *dir = dirname(copy);
440     int r = 0;
441 
442 #if defined(O_DIRECTORY)
443     int dirfd = open(dir, O_RDONLY|O_DIRECTORY, 0600);
444 #else
445     int dirfd = open(dir, O_RDONLY, 0600);
446 #endif
447     if (dirfd < 0) {
448         syslog(LOG_ERR, "IOERROR: mappedfile opendir (%s, %s): %m", mf->fname, newname);
449         r = dirfd;
450         goto done;
451     }
452 
453     r = rename(mf->fname, newname);
454     if (r < 0) {
455         syslog(LOG_ERR, "IOERROR: mappedfile rename (%s, %s): %m", mf->fname, newname);
456         goto done;
457     }
458 
459     r = fsync(dirfd);
460     if (r < 0) {
461         syslog(LOG_ERR, "IOERROR: mappedfile rename (%s, %s): %m", mf->fname, newname);
462         goto done;
463     }
464 
465     free(mf->fname);
466     mf->fname = xstrdup(newname);
467 
468  done:
469     if (dirfd >= 0) close(dirfd);
470     free(copy);
471     return r;
472 }
473 
474 
mappedfile_islocked(const struct mappedfile * mf)475 EXPORTED int mappedfile_islocked(const struct mappedfile *mf)
476 {
477     return (mf->lock_status != MF_UNLOCKED);
478 }
479 
480 //FIXME this function is nowhere used
mappedfile_isreadlocked(const struct mappedfile * mf)481 EXPORTED int mappedfile_isreadlocked(const struct mappedfile *mf)
482 {
483     return (mf->lock_status == MF_READLOCKED);
484 }
485 
mappedfile_iswritelocked(const struct mappedfile * mf)486 EXPORTED int mappedfile_iswritelocked(const struct mappedfile *mf)
487 {
488     return (mf->lock_status == MF_WRITELOCKED);
489 }
490 
mappedfile_iswritable(const struct mappedfile * mf)491 EXPORTED int mappedfile_iswritable(const struct mappedfile *mf)
492 {
493     return !!mf->is_rw;
494 }
495 
mappedfile_base(const struct mappedfile * mf)496 EXPORTED const char *mappedfile_base(const struct mappedfile *mf)
497 {
498     /* XXX - require locked? */
499     return mf->map_buf.s;
500 }
501 
mappedfile_size(const struct mappedfile * mf)502 EXPORTED size_t mappedfile_size(const struct mappedfile *mf)
503 {
504     return mf->map_size;
505 }
506 
mappedfile_buf(const struct mappedfile * mf)507 EXPORTED const struct buf *mappedfile_buf(const struct mappedfile *mf)
508 {
509     return &mf->map_buf;
510 }
511 
mappedfile_fname(const struct mappedfile * mf)512 EXPORTED const char *mappedfile_fname(const struct mappedfile *mf)
513 {
514     return mf->fname;
515 }
516