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