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