1 /* lcb.c -- replication-based backup api
2 *
3 * Copyright (c) 1994-2015 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
44 #include <config.h>
45
46 #include <assert.h>
47 #include <dirent.h>
48 #include <errno.h>
49 #include <syslog.h>
50 #include <sysexits.h>
51 #include <zlib.h>
52
53 #include "lib/cyrusdb.h"
54 #include "lib/cyr_lock.h"
55 #include "lib/gzuncat.h"
56 #include "lib/map.h"
57 #include "lib/sqldb.h"
58 #include "lib/util.h"
59 #include "lib/xmalloc.h"
60 #include "lib/xsha1.h"
61 #include "lib/xstrlcat.h"
62 #include "lib/xstrlcpy.h"
63
64 #include "imap/dlist.h"
65 #include "imap/global.h"
66 #include "imap/imap_err.h"
67
68 #include "backup/backup.h"
69
70 #define LIBCYRUS_BACKUP_SOURCE /* this file is part of libcyrus_backup */
71 #include "backup/lcb_internal.h"
72 #include "backup/lcb_sqlconsts.h"
73
74 static const char *NOUSERID = "\%SHARED";
75
76 /* remove this process's staging directory.
77 * will warn about and clean up files that are hanging around - these should
78 * be removed by dlist_unlink_files but may be missed if we're shutdown by a
79 * signal.
80 */
backup_cleanup_staging_path(void)81 EXPORTED void backup_cleanup_staging_path(void)
82 {
83 char name[MAX_MAILBOX_PATH];
84 const char *base = config_backupstagingpath();
85 DIR *dirp;
86 int r;
87
88 r = snprintf(name, MAX_MAILBOX_PATH, "%s/sync./%lu",
89 base, (unsigned long) getpid());
90 if (r >= MAX_MAILBOX_PATH) {
91 /* path was truncated, don't try to delete it */
92 return;
93 }
94
95 /* make sure it's empty */
96 if ((dirp = opendir(name))) {
97 struct dirent *d;
98 while ((d = readdir(dirp))) {
99 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
100 continue;
101
102 char *tmp = strconcat(name, "/", d->d_name, NULL);
103 syslog(LOG_INFO, "%s: unlinking leftover stage file: %s", __func__, tmp);
104 unlink(tmp);
105 free(tmp);
106 }
107 closedir(dirp);
108 }
109
110 r = rmdir(name);
111 if (r && errno != ENOENT)
112 syslog(LOG_WARNING, "%s rmdir %s: %m", __func__, name);
113 }
114
115 /*
116 * use cases:
117 * - backupd needs to be able to append to data stream and update index (exclusive)
118 * - backupd maybe needs to create a new backup from scratch (exclusive)
119 * - reindex needs to gzuc data stream and rewrite index (exclusive)
120 * - compact needs to rewrite data stream and index (exclusive)
121 * - restore needs to read data stream and index (shared)
122 *
123 * with only one shared case, might as well always lock exclusively...
124 */
backup_real_open(struct backup ** backupp,const char * data_fname,const char * index_fname,enum backup_open_reindex reindex,enum backup_open_nonblock nonblock,enum backup_open_create create)125 HIDDEN int backup_real_open(struct backup **backupp,
126 const char *data_fname, const char *index_fname,
127 enum backup_open_reindex reindex,
128 enum backup_open_nonblock nonblock,
129 enum backup_open_create create)
130 {
131 struct backup *backup = xzmalloc(sizeof *backup);
132 int r;
133
134 backup->fd = -1;
135
136 backup->data_fname = xstrdup(data_fname);
137 backup->index_fname = xstrdup(index_fname);
138
139 int open_flags = O_RDWR | O_APPEND;
140
141 switch (create) {
142 case BACKUP_OPEN_CREATE_EXCL: open_flags |= O_EXCL; /* fall thru */
143 case BACKUP_OPEN_CREATE: open_flags |= O_CREAT; /* */
144 case BACKUP_OPEN_NOCREATE: break;
145 }
146
147 while (backup->fd == -1) {
148 struct stat sbuf1, sbuf2;
149
150 int fd = open(backup->data_fname,
151 open_flags,
152 S_IRUSR | S_IWUSR);
153 if (fd < 0) {
154 switch (errno) {
155 case EEXIST:
156 r = IMAP_MAILBOX_EXISTS;
157 break;
158 case ENOENT:
159 r = IMAP_MAILBOX_NONEXISTENT;
160 break;
161 default:
162 syslog(LOG_ERR, "IOERROR: open %s: %m", backup->data_fname);
163 r = IMAP_IOERROR;
164 break;
165 }
166
167 goto error;
168 }
169
170 r = lock_setlock(fd, /*excl*/ 1, nonblock, backup->data_fname);
171 if (r) {
172 if (errno == EWOULDBLOCK || errno == EAGAIN) {
173 r = IMAP_MAILBOX_LOCKED;
174 }
175 else {
176 syslog(LOG_ERR, "IOERROR: lock_setlock: %s: %m", backup->data_fname);
177 r = IMAP_IOERROR;
178 }
179 goto error;
180 }
181
182 r = fstat(fd, &sbuf1);
183 if (!r) r = stat(backup->data_fname, &sbuf2);
184 if (r) {
185 syslog(LOG_ERR, "IOERROR: (f)stat %s: %m", backup->data_fname);
186 r = IMAP_IOERROR;
187 close(fd);
188 goto error;
189 }
190
191 if (sbuf1.st_ino == sbuf2.st_ino) {
192 backup->fd = fd;
193 break;
194 }
195
196 close(fd);
197 }
198
199 if (reindex) {
200 // when reindexing, we want to move the old index out of the way
201 // and create a new, empty one -- while holding the lock
202 char oldindex_fname[PATH_MAX];
203 snprintf(oldindex_fname, sizeof(oldindex_fname), "%s." INT64_FMT,
204 backup->index_fname, (int64_t) time(NULL));
205
206 r = rename(backup->index_fname, oldindex_fname);
207 if (r && errno != ENOENT) {
208 syslog(LOG_ERR, "IOERROR: rename %s %s: %m", backup->index_fname, oldindex_fname);
209 r = IMAP_IOERROR;
210 goto error;
211 }
212
213 backup->oldindex_fname = xstrdup(oldindex_fname);
214 }
215 else {
216 // if there's data in the data file but the index file is empty
217 // or doesn't exist, insist on a reindex before opening
218 struct stat data_statbuf;
219 r = fstat(backup->fd, &data_statbuf);
220 if (r) {
221 syslog(LOG_ERR, "IOERROR: fstat %s: %m", backup->data_fname);
222 r = IMAP_IOERROR;
223 goto error;
224 }
225 if (data_statbuf.st_size > 0) {
226 struct stat index_statbuf;
227 r = stat(backup->index_fname, &index_statbuf);
228 if (r && errno != ENOENT) {
229 syslog(LOG_ERR, "IOERROR: stat %s: %m", backup->index_fname);
230 r = IMAP_IOERROR;
231 goto error;
232 }
233
234 if ((r && errno == ENOENT) || index_statbuf.st_size == 0) {
235 syslog(LOG_ERR, "reindex needed: %s", backup->index_fname);
236 r = IMAP_MAILBOX_BADFORMAT;
237 goto error;
238 }
239 }
240 }
241
242 backup->db = sqldb_open(backup->index_fname, backup_index_initsql,
243 backup_index_version, backup_index_upgrade,
244 SQLDB_DEFAULT_TIMEOUT);
245 if (!backup->db) {
246 r = IMAP_INTERNAL; // FIXME what does it mean to error here?
247 goto error;
248 }
249
250 *backupp = backup;
251 return 0;
252
253 error:
254 backup_close(&backup);
255 if (!r) r = IMAP_INTERNAL;
256 return r;
257 }
258
backup_open(struct backup ** backupp,const mbname_t * mbname,enum backup_open_nonblock nonblock,enum backup_open_create create)259 EXPORTED int backup_open(struct backup **backupp,
260 const mbname_t *mbname,
261 enum backup_open_nonblock nonblock,
262 enum backup_open_create create)
263 {
264 struct buf data_fname = BUF_INITIALIZER;
265 struct buf index_fname = BUF_INITIALIZER;
266
267 int r = backup_get_paths(mbname, &data_fname, &index_fname, create);
268 /* XXX convert CYRUSDB return code to IMAP */
269 if (r) goto done;
270
271 r = backup_real_open(backupp,
272 buf_cstring(&data_fname), buf_cstring(&index_fname),
273 BACKUP_OPEN_NOREINDEX, nonblock, create);
274 if (r) goto done;
275
276 done:
277 buf_free(&data_fname);
278 buf_free(&index_fname);
279
280 return r;
281 }
282
283 /* Uses mkstemp() to create a new, unique, backup path for the given user.
284 *
285 * On success, the file is not unlinked, presuming that it will shortly be
286 * used for storing backup data. This also ensures its uniqueness remains:
287 * this function won't generate the same value again as long as the previous
288 * file is intact, so there's no user-rename race.
289 *
290 * If out_fd is non-NULL, on successful return it will contain an open, locked
291 * file descriptor for the new file. In this case the caller must unlock
292 * and close the fd.
293 *
294 * On error, returns NULL and logs to syslog, without touching out_fd.
295 */
_make_path(const mbname_t * mbname,int * out_fd)296 static const char *_make_path(const mbname_t *mbname, int *out_fd)
297 {
298 static char pathresult[PATH_MAX];
299
300 const char *userid = mbname_userid(mbname);
301 const char *partition = partlist_backup_select();
302 const char *ret = NULL;
303
304 if (!userid) userid = NOUSERID;
305
306 if (!partition) {
307 syslog(LOG_ERR,
308 "unable to make backup path for %s: "
309 "couldn't select partition",
310 userid);
311 return NULL;
312 }
313
314 char hash_buf[2];
315 char *template = strconcat(partition,
316 "/", dir_hash_b(userid, config_fulldirhash, hash_buf),
317 "/", userid, "_XXXXXX",
318 NULL);
319
320 /* make sure the destination directory exists */
321 cyrus_mkdir(template, 0755);
322
323 int fd = mkstemp(template);
324 if (fd < 0) {
325 syslog(LOG_ERR, "unable to make backup path for %s: %m", userid);
326 goto error;
327 }
328
329 /* lock it -- even if we're just going to immediately unlock it */
330 int r = lock_setlock(fd, /*excl*/ 1, /*nb*/ 0, template);
331 if (r) {
332 syslog(LOG_ERR,
333 "unable to obtain exclusive lock on just-created file %s: %m",
334 template);
335 /* don't unlink it, we don't know what's in it */
336 goto error;
337 }
338
339 /* save the path */
340 if (strlcpy(pathresult, template, sizeof(pathresult)) >= sizeof(pathresult)) {
341 syslog(LOG_ERR,
342 "unable to make backup path for %s: path too long",
343 userid);
344 unlink(template);
345 goto error;
346 }
347 ret = pathresult;
348
349 /* save or close the fd */
350 if (out_fd)
351 *out_fd = fd;
352 else
353 close(fd);
354
355 free(template);
356 return ret;
357
358 error:
359 if (fd >= 0) close(fd);
360 free(template);
361 return NULL;
362 }
363
backup_get_paths(const mbname_t * mbname,struct buf * data_fname,struct buf * index_fname,enum backup_open_create create)364 EXPORTED int backup_get_paths(const mbname_t *mbname,
365 struct buf *data_fname, struct buf *index_fname,
366 enum backup_open_create create)
367 {
368 struct db *backups_db = NULL;
369 struct txn *tid = NULL;
370
371 int r = backupdb_open(&backups_db, &tid);
372 if (r) return r;
373
374 const char *userid = mbname_userid(mbname);
375 const char *backup_path = NULL;
376 size_t path_len = 0;
377
378 if (!userid) userid = NOUSERID;
379
380 r = cyrusdb_fetch(backups_db,
381 userid, strlen(userid),
382 &backup_path, &path_len,
383 &tid);
384
385 if (r == CYRUSDB_NOTFOUND && create) {
386 syslog(LOG_DEBUG, "%s not found in backups.db, creating new record", userid);
387 backup_path = _make_path(mbname, NULL);
388 if (!backup_path) {
389 r = CYRUSDB_INTERNAL;
390 goto done;
391 }
392 path_len = strlen(backup_path);
393
394 r = cyrusdb_create(backups_db,
395 userid, strlen(userid),
396 backup_path, path_len,
397 &tid);
398 if (r) cyrusdb_abort(backups_db, tid);
399 else r = cyrusdb_commit(backups_db, tid);
400
401 tid = NULL;
402
403 /* if we didn't store it in the database successfully, trash the file,
404 * it won't be used */
405 if (r) unlink(backup_path);
406 }
407
408 if (r) goto done;
409
410 if (path_len == 0) {
411 syslog(LOG_DEBUG,
412 "unexpectedly got zero length backup path for user %s",
413 userid);
414 r = CYRUSDB_INTERNAL;
415 goto done;
416 }
417
418 if (data_fname)
419 buf_setmap(data_fname, backup_path, path_len);
420
421 if (index_fname) {
422 buf_setmap(index_fname, backup_path, path_len);
423 buf_appendcstr(index_fname, ".index");
424 }
425
426 done:
427 if (backups_db) {
428 if (tid) cyrusdb_abort(backups_db, tid);
429 cyrusdb_close(backups_db);
430 }
431 return r;
432 }
433
434 /*
435 * If index_fname is NULL, it will be automatically derived from data_fname
436 */
backup_open_paths(struct backup ** backupp,const char * data_fname,const char * index_fname,enum backup_open_nonblock nonblock,enum backup_open_create create)437 EXPORTED int backup_open_paths(struct backup **backupp,
438 const char *data_fname,
439 const char *index_fname,
440 enum backup_open_nonblock nonblock,
441 enum backup_open_create create)
442 {
443 if (index_fname)
444 return backup_real_open(backupp, data_fname, index_fname,
445 BACKUP_OPEN_NOREINDEX, nonblock, create);
446
447 char *tmp = strconcat(data_fname, ".index", NULL);
448 int r = backup_real_open(backupp, data_fname, tmp,
449 BACKUP_OPEN_NOREINDEX, nonblock, create);
450 free(tmp);
451
452 return r;
453 }
454
backup_close(struct backup ** backupp)455 EXPORTED int backup_close(struct backup **backupp)
456 {
457 struct backup *backup = *backupp;
458 *backupp = NULL;
459
460 gzFile gzfile = NULL;
461 int r1 = 0, r2 = 0;
462
463 if (!backup) return 0;
464
465 if (backup->append_state) {
466 if (backup->append_state->mode != BACKUP_APPEND_INACTIVE)
467 r1 = backup_append_end(backup, NULL);
468
469 gzfile = backup->append_state->gzfile;
470
471 free(backup->append_state);
472 backup->append_state = NULL;
473 }
474
475 if (backup->db) r2 = sqldb_close(&backup->db);
476
477 if (backup->oldindex_fname) {
478 if (r2) {
479 /* something went wrong closing the new index, put the old one back */
480 rename(backup->oldindex_fname, backup->index_fname);
481 }
482 else {
483 if (!config_getswitch(IMAPOPT_BACKUP_KEEP_PREVIOUS)) {
484 unlink(backup->oldindex_fname);
485 }
486 }
487 }
488
489 if (backup->fd >= 0) {
490 /* closing the file will also release the lock on the fd */
491 if (gzfile)
492 gzclose_w(gzfile);
493 else
494 close(backup->fd);
495 }
496
497 if (backup->index_fname) free(backup->index_fname);
498 if (backup->data_fname) free(backup->data_fname);
499 if (backup->oldindex_fname) free(backup->oldindex_fname);
500
501 free(backup);
502 return r1 ? r1 : r2;
503 }
504
backup_unlink(struct backup ** backupp)505 EXPORTED int backup_unlink(struct backup **backupp)
506 {
507 struct backup *backup = *backupp;
508
509 unlink(backup->index_fname);
510 unlink(backup->data_fname);
511
512 return backup_close(backupp);
513 }
514
backup_get_data_fname(const struct backup * backup)515 EXPORTED const char *backup_get_data_fname(const struct backup *backup)
516 {
517 return backup->data_fname;
518 }
519
backup_get_index_fname(const struct backup * backup)520 EXPORTED const char *backup_get_index_fname(const struct backup *backup)
521 {
522 return backup->index_fname;
523 }
524
backup_stat(const struct backup * backup,struct stat * data_statp,struct stat * index_statp)525 EXPORTED int backup_stat(const struct backup *backup,
526 struct stat *data_statp,
527 struct stat *index_statp)
528 {
529 struct stat data_statbuf, index_statbuf;
530 int r;
531
532 r = fstat(backup->fd, &data_statbuf);
533 if (r) {
534 syslog(LOG_ERR, "IOERROR: fstat %s: %m", backup->data_fname);
535 return IMAP_IOERROR;
536 }
537
538 r = stat(backup->index_fname, &index_statbuf);
539 if (r) {
540 syslog(LOG_ERR, "IOERROR: stat %s: %m", backup->index_fname);
541 return IMAP_IOERROR;
542 }
543
544 if (data_statp)
545 memcpy(data_statp, &data_statbuf, sizeof data_statbuf);
546 if (index_statp)
547 memcpy(index_statp, &index_statbuf, sizeof index_statbuf);
548
549 return 0;
550 }
551
_prot_fill_cb(unsigned char * buf,size_t len,void * rock)552 static ssize_t _prot_fill_cb(unsigned char *buf, size_t len, void *rock)
553 {
554 struct gzuncat *gzuc = (struct gzuncat *) rock;
555 int r = gzuc_read(gzuc, buf, len);
556
557 if (r < 0)
558 syslog(LOG_ERR, "IOERROR: gzuc_read returned %i", r);
559 if (r < -1)
560 errno = EIO;
561
562 return r;
563 }
564
backup_reindex(const char * name,enum backup_open_nonblock nonblock,int verbose,FILE * out)565 EXPORTED int backup_reindex(const char *name,
566 enum backup_open_nonblock nonblock,
567 int verbose, FILE *out)
568 {
569 struct buf data_fname = BUF_INITIALIZER;
570 struct buf index_fname = BUF_INITIALIZER;
571 struct backup *backup = NULL;
572 int r;
573
574 buf_printf(&data_fname, "%s", name);
575 buf_printf(&index_fname, "%s.index", name);
576
577 r = backup_real_open(&backup,
578 buf_cstring(&data_fname), buf_cstring(&index_fname),
579 BACKUP_OPEN_REINDEX, nonblock,
580 BACKUP_OPEN_NOCREATE);
581 buf_free(&index_fname);
582 buf_free(&data_fname);
583 if (r) return r;
584
585 struct gzuncat *gzuc = gzuc_new(backup->fd);
586
587 time_t prev_member_ts = -1;
588
589 struct buf cmd = BUF_INITIALIZER;
590 while (gzuc && !gzuc_eof(gzuc)) {
591 gzuc_member_start(gzuc);
592 off_t member_offset = gzuc_member_offset(gzuc);
593
594 if (verbose)
595 fprintf(out, "\nfound chunk at offset " OFF_T_FMT "\n\n", member_offset);
596
597 struct protstream *member = prot_readcb(_prot_fill_cb, gzuc);
598 prot_setisclient(member, 1); /* don't sync literals */
599
600 // FIXME stricter timestamp sequence checks
601 time_t member_start_ts = -1;
602 time_t member_end_ts = -1;
603 time_t ts = -1;
604
605 while (1) {
606 struct dlist *dl = NULL;
607
608 int c = parse_backup_line(member, &ts, &cmd, &dl);
609 if (c == EOF) {
610 const char *error = prot_error(member);
611 if (error && 0 != strcmp(error, PROT_EOF_STRING)) {
612 syslog(LOG_ERR,
613 "IOERROR: %s: error reading chunk at offset " OFF_T_FMT ", byte %i: %s\n",
614 name, member_offset, prot_bytes_in(member), error);
615
616 if (out)
617 fprintf(out, "error reading chunk at offset " OFF_T_FMT ", byte %i: %s\n",
618 member_offset, prot_bytes_in(member), error);
619
620 r = IMAP_IOERROR;
621 }
622 member_end_ts = ts;
623 break;
624 }
625
626 if (member_start_ts == -1) {
627 if (prev_member_ts != -1 && prev_member_ts > ts) {
628 fatal("member timestamp older than previous", EX_DATAERR);
629 }
630 member_start_ts = ts;
631 char file_sha1[2 * SHA1_DIGEST_LENGTH + 1];
632 sha1_file(backup->fd, backup->data_fname, member_offset, file_sha1);
633 backup_real_append_start(backup, member_start_ts,
634 member_offset, file_sha1, 1, 0);
635 }
636 else if (member_start_ts > ts)
637 fatal("line timestamp older than previous", EX_DATAERR);
638
639 if (strcmp(buf_cstring(&cmd), "APPLY") != 0) {
640 dlist_unlink_files(dl);
641 dlist_free(&dl);
642 continue;
643 }
644
645 ucase(dl->name);
646
647 r = backup_append(backup, dl, &ts, BACKUP_APPEND_NOFLUSH);
648 if (r) {
649 // FIXME do something
650 syslog(LOG_ERR, "backup_append returned %d\n", r);
651 fprintf(out, "backup_append returned %d\n", r);
652 }
653
654 dlist_unlink_files(dl);
655 dlist_free(&dl);
656 }
657
658 if (backup->append_state && backup->append_state->mode)
659 backup_real_append_end(backup, member_end_ts);
660 prot_free(member);
661 gzuc_member_end(gzuc, NULL);
662
663 prev_member_ts = member_start_ts;
664 }
665 buf_free(&cmd);
666
667 if (verbose)
668 fprintf(out, "reached end of file\n");
669
670 gzuc_free(&gzuc);
671 backup_close(&backup);
672
673 return r;
674 }
675
676 struct _rename_meta {
677 const char *userid;
678 char *fname;
679 char *ext_ptr;
680 int fd;
681 };
682 #define RENAME_META_INITIALIZER { NULL, NULL, NULL, -1 }
683
_rename_meta_set_fname(struct _rename_meta * meta,const char * data_fname)684 static void _rename_meta_set_fname(struct _rename_meta *meta, const char *data_fname)
685 {
686 size_t len = strlen(data_fname) + strlen(".index") + 1;
687 meta->fname = xmalloc(len);
688 snprintf(meta->fname, len, "%s.index", data_fname);
689 meta->ext_ptr = strrchr(meta->fname, '.');
690 *meta->ext_ptr = '\0';
691 }
692
_rename_meta_fini(struct _rename_meta * meta)693 static void _rename_meta_fini(struct _rename_meta *meta)
694 {
695 if (meta->fname) free(meta->fname);
696 memset(meta, 0, sizeof(*meta));
697 meta->fd = -1;
698 }
699
backup_rename(const mbname_t * old_mbname,const mbname_t * new_mbname)700 EXPORTED int backup_rename(const mbname_t *old_mbname, const mbname_t *new_mbname)
701 {
702 struct db *backups_db = NULL;
703 struct txn *tid = NULL;
704 struct _rename_meta old = RENAME_META_INITIALIZER;
705 struct _rename_meta new = RENAME_META_INITIALIZER;
706 old.userid = mbname_userid(old_mbname);
707 new.userid = mbname_userid(new_mbname);
708 const char *path;
709 size_t path_len;
710 int r;
711
712 if (!old.userid) old.userid = NOUSERID;
713 if (!new.userid) new.userid = NOUSERID;
714
715 /* bail out if the names are the same */
716 if (strcmp(old.userid, new.userid) == 0)
717 return 0;
718
719 /* exclusively open backups database */
720 r = backupdb_open(&backups_db, &tid);
721 if (r) goto error; // FIXME log
722
723 /* make sure new_mbname isn't already in use */
724 r = cyrusdb_fetch(backups_db,
725 new.userid, strlen(new.userid),
726 &path, &path_len,
727 &tid);
728 if (!r) r = CYRUSDB_EXISTS;
729 if (r) goto error; // FIXME log
730
731 /* locate (but not create) backup for old_mbname, open and lock it */
732 r = cyrusdb_fetch(backups_db,
733 old.userid, strlen(old.userid),
734 &path, &path_len,
735 &tid);
736 if (r) goto error; // FIXME log
737
738 _rename_meta_set_fname(&old, path);
739
740 old.fd = open(old.fname,
741 O_RDWR | O_APPEND, /* no O_CREAT */
742 S_IRUSR | S_IWUSR);
743 if (old.fd < 0) {
744 syslog(LOG_ERR, "IOERROR: open %s: %m", old.fname);
745 r = -1;
746 goto error;
747 }
748
749 /* non-blocking, to avoid deadlock */
750 r = lock_setlock(old.fd, /*excl*/ 1, /*nb*/ 1, old.fname);
751 if (r) {
752 syslog(LOG_ERR, "IOERROR: lock_setlock: %s: %m", old.fname);
753 goto error;
754 }
755
756 /* make a path for new_mbname, open and lock it */
757 path = _make_path(new_mbname, &new.fd);
758 if (!path) goto error; // FIXME log
759 _rename_meta_set_fname(&new, path);
760
761 /* copy old data and index files to new paths */
762 r = cyrus_copyfile(old.fname, new.fname, 0);
763 if (r) goto error; // FIXME log
764 *old.ext_ptr = *new.ext_ptr = '.';
765 r = cyrus_copyfile(old.fname, new.fname, 0);
766 *old.ext_ptr = *new.ext_ptr = '\0';
767 if (r) goto error; // FIXME log
768
769 /* files exist under both names now. try to update the database */
770 r = cyrusdb_create(backups_db,
771 new.userid, strlen(new.userid),
772 new.fname, strlen(new.fname),
773 &tid);
774 if (r) goto error; // FIXME log
775
776 r = cyrusdb_delete(backups_db,
777 old.userid, strlen(old.userid),
778 &tid, 0);
779 if (r) goto error; // FIXME log
780
781 r = cyrusdb_commit(backups_db, tid);
782 tid = NULL;
783 if (r) goto error; // FIXME log
784
785 /* database update succeeded. unlink old names */
786 unlink(old.fname);
787 *old.ext_ptr = '.';
788 unlink(old.fname);
789 *old.ext_ptr = '\0';
790
791 /* unlock and close backup files */
792 lock_unlock(new.fd, new.fname);
793 close(new.fd);
794 lock_unlock(old.fd, old.fname);
795 close(old.fd);
796
797 /* close backups database */
798 cyrusdb_close(backups_db);
799
800 /* clean up and exit */
801 _rename_meta_fini(&old);
802 _rename_meta_fini(&new);
803 return 0;
804
805 error:
806 /* we didn't finish, so unlink the new filenames if we got that far */
807 if (new.fname) {
808 unlink(new.fname);
809 *new.ext_ptr = '.';
810 unlink(new.fname);
811 *new.ext_ptr = '\0';
812 }
813
814 /* close the files if we got that far (also unlocks) */
815 if (new.fd != -1)
816 close(new.fd);
817 if (old.fd != -1)
818 close(old.fd);
819
820 /* abort any transaction and close the database */
821 if (backups_db) {
822 if (tid) cyrusdb_abort(backups_db, tid);
823 cyrusdb_close(backups_db);
824 }
825
826 /* clean up and exit */
827 _rename_meta_fini(&old);
828 _rename_meta_fini(&new);
829 return r;
830 }
831