1 /* ctl_backups.c -- tool for managing replication-based backup files
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 <sys/types.h>
47 #include <sys/stat.h>
48 #include <sys/wait.h>
49
50 #include <assert.h>
51 #include <errno.h>
52 #include <jansson.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <syslog.h>
56 #include <sysexits.h>
57 #include <unistd.h>
58
59 #include "lib/cyrusdb.h"
60 #include "lib/util.h"
61 #include "lib/xmalloc.h"
62
63 #include "imap/global.h"
64 #include "imap/imap_err.h"
65
66 #include "backup/backup.h"
67
68 static struct namespace ctl_backups_namespace;
69
fatal(const char * error,int code)70 EXPORTED void fatal(const char *error, int code)
71 {
72 fprintf(stderr, "fatal error: %s\n", error);
73 cyrus_done();
74 exit(code);
75 }
76
77 static const char *argv0 = NULL;
usage(void)78 static void usage(void)
79 {
80 fprintf(stderr, "Usage:\n");
81 fprintf(stderr, " %s [options] compact [mode] backup...\n", argv0);
82 fprintf(stderr, " %s [options] list [list_opts] [[mode] backup...]\n", argv0);
83 fprintf(stderr, " %s [options] lock [lock_opts] [mode] backup\n", argv0);
84 fprintf(stderr, " %s [options] reindex [mode] backup...\n", argv0);
85 fprintf(stderr, " %s [options] stat [mode] backup...\n", argv0);
86 fprintf(stderr, " %s [options] verify [mode] backup...\n", argv0);
87
88 fprintf(stderr, "\n%s\n",
89 "Commands:\n"
90 " compact # compact specified backups\n"
91 " list [list_opts] # list backups (all if none specified)\n"
92 " lock [lock_opts] # lock specified backup\n"
93 " reindex # reindex specified backups\n"
94 " stat # show stats of specified backups\n"
95 " verify # verify specified backups\n"
96 );
97
98 fprintf(stderr, "%s\n",
99 "Options:\n"
100 " -C alt_config # alternate config file\n"
101 " -F # force (run command even if not needed)\n"
102 " -S # stop on error\n"
103 " -V # don't verify checksums (faster read-only ops)\n"
104 " -j # output in JSON format\n"
105 " -v # verbose (repeat for more verbosity)\n"
106 " -w # wait for locks (don't skip locked backups)\n"
107 );
108
109 fprintf(stderr, "%s\n",
110 "List options:\n"
111 " -t [hours] # stale (no update in hours) backups only (default: 24)\n"
112 );
113
114 fprintf(stderr, "%s\n",
115 "Lock options:\n"
116 " -c # exclusively create backup\n"
117 " -p # lock backup and wait for eof on stdin (default)\n"
118 " -s # lock backup and open index in sqlite3\n"
119 " -x command # lock backup and execute command\n"
120 );
121
122 fprintf(stderr, "%s\n",
123 "Modes:\n"
124 " -A # all known backups\n"
125 " -D # specified backups interpreted as domains\n"
126 " -P # specified backups interpreted as userid prefixes\n"
127 " -f # specified backups interpreted as filenames\n"
128 " -m # specified backups interpreted as mboxnames\n"
129 " -u # specified backups interpreted as userids (default)\n"
130 "\n"
131 " Modes -A, -D, -P not available for all commands\n" /* FIXME which */
132 );
133
134 exit(EX_USAGE);
135 }
136
137 enum ctlbu_mode {
138 CTLBU_MODE_UNSPECIFIED = 0,
139 CTLBU_MODE_FILENAME,
140 CTLBU_MODE_MBOXNAME,
141 CTLBU_MODE_USERNAME,
142 CTLBU_MODE_DOMAIN,
143 CTLBU_MODE_PREFIX,
144 CTLBU_MODE_ALL,
145 };
146
147 enum ctlbu_lock_mode {
148 CTLBU_LOCK_MODE_UNSPECIFIED = 0,
149 CTLBU_LOCK_MODE_PIPE,
150 CTLBU_LOCK_MODE_SQL,
151 CTLBU_LOCK_MODE_EXEC,
152 };
153
154 struct ctlbu_cmd_options {
155 enum ctlbu_mode mode;
156 enum ctlbu_lock_mode lock_mode;
157 enum backup_open_nonblock wait;
158 enum backup_open_create create;
159 int verbose;
160 int stop_on_error;
161 int list_stale;
162 int force;
163 int noverify;
164 int jsonout;
165 const char *lock_exec_cmd;
166 const char *domain;
167 };
168
169 enum ctlbu_cmd {
170 CTLBU_CMD_UNSPECIFIED = 0,
171 CTLBU_CMD_COMPACT,
172 CTLBU_CMD_DELETE,
173 CTLBU_CMD_LIST,
174 CTLBU_CMD_LOCK,
175 CTLBU_CMD_MOVE,
176 CTLBU_CMD_RECONSTRUCT,
177 CTLBU_CMD_REINDEX,
178 CTLBU_CMD_STAT,
179 CTLBU_CMD_VERIFY,
180 };
181
182 static int ctlbu_skips_fails = 0;
183
184 /* same signature as foreach_cb */
185 static int cmd_compact_one(void *rock,
186 const char *userid, size_t userid_len,
187 const char *fname, size_t fname_len);
188 static int cmd_delete_one(void *rock,
189 const char *userid, size_t userid_len,
190 const char *fname, size_t fname_len);
191 static int cmd_list_one(void *rock,
192 const char *userid, size_t userid_len,
193 const char *fname, size_t fname_len);
194 static int cmd_lock_one(void *rock,
195 const char *userid, size_t userid_len,
196 const char *fname, size_t fname_len);
197 static int cmd_move_one(void *rock,
198 const char *userid, size_t userid_len,
199 const char *fname, size_t fname_len);
200 static int cmd_reindex_one(void *rock,
201 const char *userid, size_t userid_len,
202 const char *fname, size_t fname_len);
203 static int cmd_stat_one(void *rock,
204 const char *userid, size_t userid_len,
205 const char *fname, size_t fname_len);
206 static int cmd_verify_one(void *rock,
207 const char *userid, size_t userid_len,
208 const char *fname, size_t fname_len);
209
210 static foreach_cb *const cmd_func[] = {
211 NULL,
212 cmd_compact_one,
213 cmd_delete_one,
214 cmd_list_one,
215 cmd_lock_one,
216 cmd_move_one,
217 NULL, /* reconstruct one doesn't make sense */
218 cmd_reindex_one,
219 cmd_stat_one,
220 cmd_verify_one,
221 };
222
223 static int lock_run_pipe(const char *userid, const char *fname,
224 enum backup_open_nonblock nonblock,
225 enum backup_open_create create);
226 static int lock_run_sqlite(const char *userid, const char *fname,
227 enum backup_open_nonblock nonblock,
228 enum backup_open_create create);
229 static int lock_run_exec(const char *userid, const char *fname,
230 const char *cmd,
231 enum backup_open_nonblock nonblock,
232 enum backup_open_create create);
233
parse_cmd_string(const char * cmd)234 static enum ctlbu_cmd parse_cmd_string(const char *cmd)
235 {
236 assert(cmd != NULL);
237
238 switch(cmd[0]) {
239 case 'c':
240 if (strcmp(cmd, "compact") == 0) return CTLBU_CMD_COMPACT;
241 break;
242 case 'd':
243 if (strcmp(cmd, "delete") == 0) return CTLBU_CMD_DELETE;
244 break;
245 case 'l':
246 if (strcmp(cmd, "list") == 0) return CTLBU_CMD_LIST;
247 if (strcmp(cmd, "lock") == 0) return CTLBU_CMD_LOCK;
248 break;
249 case 'm':
250 if (strcmp(cmd, "move") == 0) return CTLBU_CMD_MOVE;
251 break;
252 case 'r':
253 if (strcmp(cmd, "reconstruct") == 0) return CTLBU_CMD_RECONSTRUCT;
254 if (strcmp(cmd, "reindex") == 0) return CTLBU_CMD_REINDEX;
255 break;
256 case 's':
257 if (strcmp(cmd, "stat") == 0) return CTLBU_CMD_STAT;
258 break;
259 case 'v':
260 if (strcmp(cmd, "verify") == 0) return CTLBU_CMD_VERIFY;
261 break;
262 };
263
264 return CTLBU_CMD_UNSPECIFIED;
265 }
266
print_status(const char * cmd,const char * userid,const char * fname,const struct ctlbu_cmd_options * options,int r)267 static void print_status(const char *cmd,
268 const char *userid, const char *fname,
269 const struct ctlbu_cmd_options *options,
270 int r)
271 {
272 char *status = NULL;
273 switch (r) {
274 case 1:
275 status = xstrdup("skipped");
276 break;
277 case 0:
278 status = xstrdup("ok");
279 break;
280 case IMAP_MAILBOX_LOCKED:
281 status = xstrdup("locked");
282 break;
283 default:
284 status = strconcat("failed (", error_message(r), ")", NULL);
285 break;
286 }
287
288 if (options->jsonout) {
289 json_t *out = json_object();
290 json_object_set_new(out, "command", json_string(cmd));
291 json_object_set_new(out, "userid", json_string(userid));
292 json_object_set_new(out, "fname", json_string(fname));
293 json_object_set_new(out, "status", json_string(status));
294
295 const size_t flags = JSON_INDENT(2) | JSON_PRESERVE_ORDER;
296 json_dumpf(out, stdout, flags);
297 puts("");
298 json_decref(out);
299 }
300 else {
301 printf("%s %s: %s\n", cmd, userid ? userid : fname, status);
302 }
303
304 free(status);
305 }
306
domain_filter(void * rock,const char * key,size_t key_len,const char * data,size_t data_len)307 static int domain_filter(void *rock,
308 const char *key, size_t key_len,
309 const char *data __attribute__((unused)),
310 size_t data_len __attribute__((unused)))
311 {
312 struct ctlbu_cmd_options *options = (struct ctlbu_cmd_options *) rock;
313 char *userid = NULL;
314 mbname_t *mbname = NULL;
315 const char *domain = NULL;
316 int doit = 0;
317
318 /* input args might not be 0-terminated, so make a safe copy */
319 if (!key_len) return 0;
320 userid = xstrndup(key, key_len);
321
322 mbname = mbname_from_userid(userid);
323 domain = mbname_domain(mbname);
324 if (!domain)
325 domain = config_defdomain;
326
327 if (domain)
328 doit = !strcmp(domain, options->domain);
329
330 mbname_free(&mbname);
331 free(userid);
332
333 return doit;
334 }
335
save_argv0(const char * s)336 static void save_argv0(const char *s)
337 {
338 const char *slash = strrchr(s, '/');
339 if (slash)
340 argv0 = slash + 1;
341 else
342 argv0 = s;
343 }
344
main(int argc,char ** argv)345 int main(int argc, char **argv)
346 {
347 save_argv0(argv[0]);
348
349 int opt, r = 0;
350 const char *alt_config = NULL;
351 enum ctlbu_cmd cmd = CTLBU_CMD_UNSPECIFIED;
352 struct ctlbu_cmd_options options = {0};
353 options.wait = BACKUP_OPEN_NONBLOCK;
354
355 while ((opt = getopt(argc, argv, ":AC:DFPSVcfjmpst:x:uvw")) != EOF) {
356 switch (opt) {
357 case 'A':
358 if (options.mode != CTLBU_MODE_UNSPECIFIED) usage();
359 options.mode = CTLBU_MODE_ALL;
360 break;
361 case 'C':
362 alt_config = optarg;
363 break;
364 case 'D':
365 if (options.mode != CTLBU_MODE_UNSPECIFIED) usage();
366 options.mode = CTLBU_MODE_DOMAIN;
367 break;
368 case 'F':
369 options.force = 1;
370 break;
371 case 'P':
372 if (options.mode != CTLBU_MODE_UNSPECIFIED) usage();
373 options.mode = CTLBU_MODE_PREFIX;
374 break;
375 case 'S':
376 options.stop_on_error = 1;
377 break;
378 case 'V':
379 options.noverify = 1;
380 break;
381 case 'c':
382 options.create = BACKUP_OPEN_CREATE_EXCL;
383 break;
384 case 'j':
385 if (options.verbose) usage();
386 options.jsonout = 1;
387 break;
388 case 'f':
389 if (options.mode != CTLBU_MODE_UNSPECIFIED) usage();
390 options.mode = CTLBU_MODE_FILENAME;
391 break;
392 case 'm':
393 if (options.mode != CTLBU_MODE_UNSPECIFIED) usage();
394 options.mode = CTLBU_MODE_MBOXNAME;
395 break;
396 case 'p':
397 if (options.lock_mode != CTLBU_LOCK_MODE_UNSPECIFIED) usage();
398 options.lock_mode = CTLBU_LOCK_MODE_PIPE;
399 break;
400 case 's':
401 if (options.lock_mode != CTLBU_LOCK_MODE_UNSPECIFIED) usage();
402 options.lock_mode = CTLBU_LOCK_MODE_SQL;
403 break;
404 case 't':
405 options.list_stale = atoi(optarg);
406 if (!options.list_stale) usage();
407 break;
408 case 'u':
409 if (options.mode != CTLBU_MODE_UNSPECIFIED) usage();
410 options.mode = CTLBU_MODE_USERNAME;
411 break;
412 case 'v':
413 if (options.jsonout) usage();
414 options.verbose ++;
415 break;
416 case 'x':
417 if (options.lock_mode != CTLBU_LOCK_MODE_UNSPECIFIED) usage();
418 options.lock_mode = CTLBU_LOCK_MODE_EXEC;
419 options.lock_exec_cmd = optarg;
420 break;
421 case 'w':
422 options.wait = BACKUP_OPEN_BLOCK;
423 break;
424 case ':':
425 if (optopt == 't') options.list_stale = 24;
426 else usage();
427 break;
428 default:
429 usage();
430 break;
431 }
432 }
433
434 /* get the command */
435 if (optind == argc) usage();
436 cmd = parse_cmd_string(argv[optind++]);
437 if (cmd == CTLBU_CMD_UNSPECIFIED) usage();
438
439 if (options.lock_mode != CTLBU_LOCK_MODE_UNSPECIFIED
440 && cmd != CTLBU_CMD_LOCK)
441 usage();
442
443 switch (cmd) {
444 /* list defaults to all */
445 case CTLBU_CMD_LIST:
446 if (options.mode == CTLBU_MODE_UNSPECIFIED && argc - optind == 0)
447 options.mode = CTLBU_MODE_ALL;
448 break;
449
450 /* some commands only accept one backup at a time */
451 case CTLBU_CMD_LOCK:
452 case CTLBU_CMD_MOVE:
453 case CTLBU_CMD_DELETE:
454 if (options.mode == CTLBU_MODE_ALL) usage();
455 if (options.mode == CTLBU_MODE_DOMAIN) usage();
456 if (options.mode == CTLBU_MODE_PREFIX) usage();
457 if (argc - optind > 1) usage();
458 break;
459
460 /* reconstruct doesn't accept named backups */
461 case CTLBU_CMD_RECONSTRUCT:
462 if (options.mode != CTLBU_MODE_UNSPECIFIED) usage();
463 if (optind != argc) usage();
464 break;
465
466 default:
467 break;
468 }
469
470 /* default mode is username */
471 if (options.mode == CTLBU_MODE_UNSPECIFIED)
472 options.mode = CTLBU_MODE_USERNAME;
473
474 /* mode all doesn't want any named backups */
475 if (options.mode == CTLBU_MODE_ALL && optind != argc) usage();
476
477 cyrus_init(alt_config, "ctl_backups", 0, 0);
478
479 if ((r = mboxname_init_namespace(&ctl_backups_namespace, 1)) != 0) {
480 fatal(error_message(r), EX_CONFIG);
481 }
482 mboxevent_setnamespace(&ctl_backups_namespace);
483
484 if (cmd == CTLBU_CMD_RECONSTRUCT) {
485 /* special handling for reconstruct */
486 // FIXME
487 }
488 else if (options.mode == CTLBU_MODE_ALL) {
489 /* loop over entire backups.db */
490 struct db *backups_db = NULL;
491
492 r = backupdb_open(&backups_db, NULL);
493
494 if (!r)
495 r = cyrusdb_foreach(backups_db, NULL, 0, NULL,
496 cmd_func[cmd], &options,
497 NULL);
498
499 if (backups_db)
500 cyrusdb_close(backups_db);
501 }
502 else if (options.mode == CTLBU_MODE_DOMAIN) {
503 /* loop over domains named on command line */
504 struct db *backups_db = NULL;
505 int i;
506
507 r = backupdb_open(&backups_db, NULL);
508
509 for (i = optind; i < argc && !r; i++) {
510 options.domain = argv[i];
511
512 r = cyrusdb_foreach(backups_db, NULL, 0,
513 domain_filter,
514 cmd_func[cmd], &options,
515 NULL);
516 }
517
518 if (backups_db)
519 cyrusdb_close(backups_db);
520 }
521 else if (options.mode == CTLBU_MODE_PREFIX) {
522 /* loop over prefixes named on command line */
523 struct db *backups_db = NULL;
524 int i;
525
526 r = backupdb_open(&backups_db, NULL);
527
528 for (i = optind; i < argc && !r; i++) {
529 r = cyrusdb_foreach(backups_db,
530 argv[i], strlen(argv[i]),
531 NULL,
532 cmd_func[cmd], &options,
533 NULL);
534 }
535
536 if (backups_db)
537 cyrusdb_close(backups_db);
538 }
539 else {
540 /* loop over backups named on command line */
541 struct buf userid = BUF_INITIALIZER;
542 struct buf fname = BUF_INITIALIZER;
543 int i;
544
545 for (i = optind; i < argc; i++) {
546 buf_reset(&userid);
547 buf_reset(&fname);
548 mbname_t *mbname = NULL;
549
550 if (options.mode == CTLBU_MODE_USERNAME)
551 mbname = mbname_from_userid(argv[i]);
552 else if (options.mode == CTLBU_MODE_MBOXNAME)
553 mbname = mbname_from_extname(argv[i], &ctl_backups_namespace, NULL);
554
555 if (mbname) {
556 r = backup_get_paths(mbname, &fname, NULL, BACKUP_OPEN_NOCREATE);
557 if (r) {
558 /* XXX this should print cyrusdb_strerror(r), except that
559 * just returns "cyrusdb error", unhelpfully */
560 fprintf(stderr, "%s: not found in backups.db\n", argv[i]);
561 syslog(LOG_DEBUG, "ctl_backups '%s': %s (%d)", argv[i], cyrusdb_strerror(r), r);
562 mbname_free(&mbname);
563 continue;
564 }
565 if (mbname_userid(mbname)) {
566 buf_setcstr(&userid, mbname_userid(mbname));
567 }
568 }
569 else
570 buf_setcstr(&fname, argv[i]);
571
572 if (!r && cmd_func[cmd])
573 r = cmd_func[cmd](&options,
574 buf_cstring(&userid),
575 buf_len(&userid),
576 buf_cstring(&fname),
577 buf_len(&fname));
578
579 if (mbname) mbname_free(&mbname);
580
581 if (r) break;
582 }
583
584 buf_free(&userid);
585 buf_free(&fname);
586 }
587
588 backup_cleanup_staging_path();
589 cyrus_done();
590 exit(r || ctlbu_skips_fails ? EX_TEMPFAIL : EX_OK);
591 }
592
cmd_compact_one(void * rock,const char * key,size_t key_len,const char * data,size_t data_len)593 static int cmd_compact_one(void *rock,
594 const char *key, size_t key_len,
595 const char *data, size_t data_len)
596 {
597 struct ctlbu_cmd_options *options = (struct ctlbu_cmd_options *) rock;
598 char *userid = NULL;
599 char *fname = NULL;
600 int r = 0;
601
602 /* input args might not be 0-terminated, so make a safe copy */
603 if (key_len)
604 userid = xstrndup(key, key_len);
605 if (data_len)
606 fname = xstrndup(data, data_len);
607
608 r = backup_compact(fname, options->wait, options->force,
609 options->verbose, stdout);
610
611 print_status("compact", userid, fname, options, r);
612
613 if (userid) free(userid);
614 if (fname) free(fname);
615
616 if (r) ++ctlbu_skips_fails;
617 if (r == IMAP_MAILBOX_LOCKED) r = 0;
618 return options->stop_on_error ? r : 0;
619 }
620
cmd_delete_one(void * rock,const char * userid,size_t userid_len,const char * fname,size_t fname_len)621 static int cmd_delete_one(void *rock,
622 const char *userid, size_t userid_len,
623 const char *fname, size_t fname_len)
624 {
625 struct ctlbu_cmd_options *options = (struct ctlbu_cmd_options *) rock;
626 (void) options;
627 fprintf(stderr, "unimplemented: %s %s[%zu] %s[%zu]\n", __func__,
628 userid, userid_len, fname, fname_len);
629 return -1;
630 }
631
cmd_list_one(void * rock,const char * key,size_t key_len,const char * data,size_t data_len)632 static int cmd_list_one(void *rock,
633 const char *key, size_t key_len,
634 const char *data, size_t data_len)
635 {
636 struct ctlbu_cmd_options *options = (struct ctlbu_cmd_options *) rock;
637 struct backup *backup = NULL;
638 struct backup_chunk *latest_chunk = NULL;
639 struct stat data_stat_buf = {0};
640 char *userid = NULL;
641 char *fname = NULL;
642 char timestamp[32] = "[unknown]";
643 int r = 0;
644
645 /* input args might not be 0-terminated, so make a safe copy */
646 if (key_len)
647 userid = xstrndup(key, key_len);
648 if (data_len)
649 fname = xstrndup(data, data_len);
650
651 r = backup_open_paths(&backup, fname, NULL,
652 options->wait, BACKUP_OPEN_NOCREATE);
653 if (!r && !options->noverify)
654 r = backup_verify(backup, BACKUP_VERIFY_QUICK, 0, NULL);
655
656 if (r) {
657 fprintf(stderr, "%s: %s\n", userid ? userid : fname, error_message(r));
658 goto done;
659 }
660
661 latest_chunk = backup_get_latest_chunk(backup);
662 if (latest_chunk) {
663 if (options->list_stale) {
664 /* skip out early if it's not stale */
665 if (time(NULL) - latest_chunk->ts_end < 3600 * options->list_stale)
666 goto done;
667 }
668
669 strftime(timestamp, sizeof(timestamp), "%F %T",
670 localtime(&latest_chunk->ts_end));
671 }
672
673 r = backup_stat(backup, &data_stat_buf, NULL);
674 if (r) {
675 fprintf(stderr, "fstat %s: %s\n", userid ? userid : fname, strerror(errno));
676 data_stat_buf.st_size = -1;
677 }
678
679 if (options->jsonout) {
680 json_t *out = json_object();
681 json_object_set_new(out, "timestamp", json_string(timestamp));
682 json_object_set_new(out, "compressed", json_integer(data_stat_buf.st_size));
683 json_object_set_new(out, "userid", json_string(userid));
684 json_object_set_new(out, "fname", json_string(fname));
685
686 const size_t flags = JSON_INDENT(2) | JSON_PRESERVE_ORDER;
687 json_dumpf(out, stdout, flags);
688 puts("");
689 json_decref(out);
690 }
691 else {
692 printf("%s\t" OFF_T_FMT "\t%s\t%s\n",
693 timestamp,
694 data_stat_buf.st_size,
695 userid ? userid : "[unknown]",
696 fname);
697 }
698
699 done:
700 if (latest_chunk) backup_chunk_free(&latest_chunk);
701 if (backup) backup_close(&backup);
702 if (userid) free(userid);
703 if (fname) free(fname);
704
705 if (r) ++ctlbu_skips_fails;
706 if (r == IMAP_MAILBOX_LOCKED) r = 0;
707 return options->stop_on_error ? r : 0;
708 }
709
cmd_lock_one(void * rock,const char * key,size_t key_len,const char * data,size_t data_len)710 static int cmd_lock_one(void *rock,
711 const char *key, size_t key_len,
712 const char *data, size_t data_len)
713 {
714 struct ctlbu_cmd_options *options = (struct ctlbu_cmd_options *) rock;
715 char *userid = NULL;
716 char *fname = NULL;
717 int r = 0;
718
719 /* input args might not be 0-terminated, so make a safe copy */
720 if (key_len)
721 userid = xstrndup(key, key_len);
722 if (data_len)
723 fname = xstrndup(data, data_len);
724
725 switch (options->lock_mode) {
726 case CTLBU_LOCK_MODE_UNSPECIFIED:
727 case CTLBU_LOCK_MODE_PIPE:
728 r = lock_run_pipe(userid, fname, options->wait, options->create);
729 break;
730 case CTLBU_LOCK_MODE_SQL:
731 r = lock_run_sqlite(userid, fname, options->wait, options->create);
732 break;
733 case CTLBU_LOCK_MODE_EXEC:
734 r = lock_run_exec(userid, fname, options->lock_exec_cmd,
735 options->wait, options->create);
736 break;
737 }
738
739 if (userid) free(userid);
740 if (fname) free(fname);
741
742 /* don't care about stop_on_error: lock command only accepts one backup */
743 if (r) ++ctlbu_skips_fails;
744 return r;
745 }
746
cmd_move_one(void * rock,const char * userid,size_t userid_len,const char * fname,size_t fname_len)747 static int cmd_move_one(void *rock,
748 const char *userid, size_t userid_len,
749 const char *fname, size_t fname_len)
750 {
751 struct ctlbu_cmd_options *options = (struct ctlbu_cmd_options *) rock;
752 (void) options;
753 fprintf(stderr, "unimplemented: %s %s[%zu] %s[%zu]\n", __func__,
754 userid, userid_len, fname, fname_len);
755 return -1;
756 }
757
cmd_reindex_one(void * rock,const char * key,size_t key_len,const char * data,size_t data_len)758 static int cmd_reindex_one(void *rock,
759 const char *key, size_t key_len,
760 const char *data, size_t data_len)
761 {
762 struct ctlbu_cmd_options *options = (struct ctlbu_cmd_options *) rock;
763 char *userid = NULL;
764 char *fname = NULL;
765 int r;
766
767 /* input args might not be 0-terminated, so make a safe copy */
768 if (key_len)
769 userid = xstrndup(key, key_len);
770 if (data_len)
771 fname = xstrndup(data, data_len);
772
773 r = backup_reindex(fname, options->wait, options->verbose, stdout);
774
775 print_status("reindex", userid, fname, options, r);
776
777 if (userid) free(userid);
778 if (fname) free(fname);
779
780 if (r) ++ctlbu_skips_fails;
781 if (r == IMAP_MAILBOX_LOCKED) r = 0;
782 return options->stop_on_error ? r : 0;
783 }
784
_sum_message_lengths_cb(const struct backup_message * message,void * rock)785 static int _sum_message_lengths_cb(const struct backup_message *message,
786 void *rock)
787 {
788 size_t *sum = (size_t *) rock;
789
790 *sum += message->length;
791
792 return 0;
793 }
794
cmd_stat_one(void * rock,const char * key,size_t key_len,const char * data,size_t data_len)795 static int cmd_stat_one(void *rock,
796 const char *key, size_t key_len,
797 const char *data, size_t data_len)
798 {
799 struct ctlbu_cmd_options *options = (struct ctlbu_cmd_options *) rock;
800 struct backup *backup = NULL;
801 char *userid = NULL;
802 char *fname = NULL;
803 struct backup_chunk_list *all_chunks = NULL, *keep_chunks = NULL;
804 struct backup_chunk *latest_chunk = NULL;
805 struct backup_chunk *chunk;
806 time_t since;
807 size_t compactable = 0, uncompressed = 0;
808 struct stat data_stat;
809 double cmp_ratio, utl_ratio;
810 char start_time[32] = "[unknown]";
811 char end_time[32] = "[unknown]";
812 int r;
813
814 /* input args might not be 0-terminated, so make a safe copy */
815 if (key_len)
816 userid = xstrndup(key, key_len);
817 if (data_len)
818 fname = xstrndup(data, data_len);
819
820 r = backup_open_paths(&backup, fname, NULL,
821 options->wait, BACKUP_OPEN_NOCREATE);
822 if (r) goto done;
823
824 if (!options->noverify) {
825 r = backup_verify(backup, BACKUP_VERIFY_QUICK, 0, NULL);
826 if (r) goto done;
827 }
828
829 const int retention = config_getduration(IMAPOPT_BACKUP_RETENTION, 'd');
830 if (retention > 0) {
831 since = time(0) - retention;
832 }
833 else {
834 /* zero or negative retention means "keep forever" */
835 since = -1;
836 }
837
838 all_chunks = backup_get_chunks(backup);
839 keep_chunks = backup_get_live_chunks(backup, since);
840
841 if (!all_chunks || !keep_chunks) {
842 r = IMAP_INTERNAL;
843 goto done;
844 }
845
846 /* uncompressed length is sum of lengths of all chunks */
847 for (chunk = all_chunks->head; chunk; chunk = chunk->next) {
848 uncompressed += chunk->length;
849 }
850
851 /* compactable length is sum of live message lengths in keep chunks */
852 for (chunk = keep_chunks->head; chunk; chunk = chunk->next) {
853 backup_message_foreach(backup, chunk->id, &since,
854 _sum_message_lengths_cb, &compactable);
855 }
856
857 /* compression ratio is compressed length / uncompressed length */
858 r = backup_stat(backup, &data_stat, NULL);
859 if (r) goto done;
860 cmp_ratio = 100.0 * data_stat.st_size / uncompressed;
861
862 /* utilisation ratio is compactable length / uncompressed length */
863 utl_ratio = 100.0 * compactable / uncompressed;
864
865 /* start/end time are from latest chunk */
866 latest_chunk = backup_get_latest_chunk(backup);
867 if (latest_chunk) {
868 strftime(start_time, sizeof(start_time), "%F %T",
869 localtime(&latest_chunk->ts_start));
870 strftime(end_time, sizeof(end_time), "%F %T",
871 localtime(&latest_chunk->ts_end));
872 }
873
874 if (options->jsonout) {
875 json_t *out = json_object();
876 json_object_set_new(out, "userid", json_string(userid));
877 json_object_set_new(out, "fname", json_string(fname));
878 json_object_set_new(out, "compressed", json_integer(data_stat.st_size));
879 json_object_set_new(out, "uncompressed", json_integer(uncompressed));
880 json_object_set_new(out, "compactable", json_integer(compactable));
881 json_object_set_new(out, "compression_ratio", json_real(cmp_ratio));
882 json_object_set_new(out, "utilisation_ratio", json_real(utl_ratio));
883 json_object_set_new(out, "last_start_time", json_string(start_time));
884 json_object_set_new(out, "last_end_time", json_string(end_time));
885
886 const size_t flags = JSON_INDENT(2) | JSON_PRESERVE_ORDER;
887 json_dumpf(out, stdout, flags);
888 puts("");
889 json_decref(out);
890 }
891 else {
892 printf("%s\t" OFF_T_FMT "\t" SIZE_T_FMT "\t" SIZE_T_FMT "\t%6.1f%%\t%6.1f%%\t%21s\t%s\n",
893 userid ? userid : fname,
894 data_stat.st_size,
895 uncompressed,
896 compactable,
897 cmp_ratio,
898 utl_ratio,
899 start_time,
900 end_time);
901 }
902
903 done:
904 if (r) {
905 fprintf(stderr, "%s: %s\n", userid ? userid : fname, error_message(r));
906 }
907
908 if (latest_chunk) backup_chunk_free(&latest_chunk);
909 if (all_chunks) backup_chunk_list_free(&all_chunks);
910 if (keep_chunks) backup_chunk_list_free(&keep_chunks);
911 if (backup) backup_close(&backup);
912 if (fname) free(fname);
913 if (userid) free(userid);
914
915 return options->stop_on_error ? r : 0;
916 }
917
cmd_verify_one(void * rock,const char * key,size_t key_len,const char * data,size_t data_len)918 static int cmd_verify_one(void *rock,
919 const char *key, size_t key_len,
920 const char *data, size_t data_len)
921 {
922 struct ctlbu_cmd_options *options = (struct ctlbu_cmd_options *) rock;
923 struct backup *backup = NULL;
924 char *userid = NULL;
925 char *fname = NULL;
926 int r;
927
928 /* input args might not be 0-terminated, so make a safe copy */
929 if (key_len)
930 userid = xstrndup(key, key_len);
931 if (data_len)
932 fname = xstrndup(data, data_len);
933
934 r = backup_open_paths(&backup, fname, NULL,
935 options->wait, BACKUP_OPEN_NOCREATE);
936
937 /* n.b. deliberately ignoring nonsensical noverify option here */
938 if (!r) r = backup_verify(backup, BACKUP_VERIFY_FULL, options->verbose, stdout);
939
940 print_status("verify", userid, fname, options, r);
941
942 if (backup) backup_close(&backup);
943 if (userid) free(userid);
944 if (fname) free(fname);
945
946 if (r) ++ctlbu_skips_fails;
947 if (r == IMAP_MAILBOX_LOCKED) r = 0;
948 return options->stop_on_error ? r : 0;
949 }
950
lock_run_pipe(const char * userid,const char * fname,enum backup_open_nonblock nonblock,enum backup_open_create create)951 static int lock_run_pipe(const char *userid, const char *fname,
952 enum backup_open_nonblock nonblock,
953 enum backup_open_create create)
954 {
955 printf("* Trying to obtain lock on %s...\n", userid ? userid : fname);
956
957 struct backup *backup = NULL;
958 int r;
959
960 r = backup_open_paths(&backup, fname, NULL, nonblock, create);
961
962 if (!r)
963 r = backup_verify(backup, BACKUP_VERIFY_QUICK, 0, NULL);
964
965 if (r) {
966 printf("NO failed (%s)\n", error_message(r));
967 r = backup_close(&backup);
968 return EX_SOFTWARE; // FIXME would something else be more appropriate?
969 }
970
971 printf("OK locked\n");
972
973 /* wait until stdin closes */
974 char buf[PROT_BUFSIZE] = {0};
975 while (!feof(stdin)) {
976 if (!fgets(buf, sizeof(buf), stdin))
977 break;
978 }
979
980 r = backup_close(&backup);
981 if (r) fprintf(stderr, "warning: backup_close() returned %i\n", r);
982
983 return 0;
984 }
985
lock_run_sqlite(const char * userid,const char * fname,enum backup_open_nonblock nonblock,enum backup_open_create create)986 static int lock_run_sqlite(const char *userid, const char *fname,
987 enum backup_open_nonblock nonblock,
988 enum backup_open_create create)
989 {
990 fprintf(stderr, "trying to obtain lock on %s...\n", userid ? userid : fname);
991
992 struct backup *backup = NULL;
993 const char *index_fname = NULL;
994 int r, status;
995 pid_t pid;
996
997 r = backup_open_paths(&backup, fname, NULL, nonblock, create);
998
999 if (!r)
1000 r = backup_verify(backup, BACKUP_VERIFY_QUICK, 0, NULL);
1001
1002 if (r) {
1003 fprintf(stderr, "unable to lock %s: %s\n",
1004 userid ? userid : fname,
1005 error_message(r));
1006 r = backup_close(&backup);
1007 return EX_SOFTWARE;
1008 }
1009
1010 index_fname = backup_get_index_fname(backup);
1011
1012 /* FIXME probably need to do something with signals here */
1013
1014 pid = fork();
1015
1016 switch (pid) {
1017 case -1:
1018 perror("fork");
1019 r = EX_SOFTWARE;
1020 break;
1021
1022 case 0:
1023 /* child */
1024 fprintf(stderr, "execlp: %s %s\n", "sqlite3", index_fname);
1025 execlp("sqlite3", "sqlite3", index_fname, NULL);
1026 /* execlp never returns */
1027 perror("execlp sqlite3");
1028 _exit(EX_SOFTWARE);
1029 break;
1030
1031 default:
1032 /* parent */
1033 waitpid(pid, &status, 0);
1034 if (WIFEXITED(status))
1035 r = WEXITSTATUS(status);
1036 else
1037 r = EX_SOFTWARE;
1038 break;
1039 }
1040
1041 backup_close(&backup);
1042 return r;
1043 }
1044
1045 static const char data_fname_env[] = "ctl_backups_lock_data_fname";
1046 static const char index_fname_env[] = "ctl_backups_lock_index_fname";
1047
lock_run_exec(const char * userid,const char * fname,const char * cmd,enum backup_open_nonblock nonblock,enum backup_open_create create)1048 static int lock_run_exec(const char *userid, const char *fname,
1049 const char *cmd,
1050 enum backup_open_nonblock nonblock,
1051 enum backup_open_create create)
1052 {
1053 fprintf(stderr, "trying to obtain lock on %s...\n", userid ? userid : fname);
1054
1055 struct backup *backup = NULL;
1056 int r;
1057
1058 r = backup_open_paths(&backup, fname, NULL, nonblock, create);
1059
1060 if (!r)
1061 r = backup_verify(backup, BACKUP_VERIFY_QUICK, 0, NULL);
1062
1063 if (r) {
1064 fprintf(stderr, "unable to lock %s: %s\n",
1065 userid ? userid : fname,
1066 error_message(r));
1067 r = backup_close(&backup);
1068 return EX_SOFTWARE;
1069 }
1070
1071 setenv(data_fname_env, fname, 1);
1072 setenv(index_fname_env, backup_get_index_fname(backup), 1);
1073
1074 r = system(cmd);
1075
1076 unsetenv(data_fname_env);
1077 unsetenv(index_fname_env);
1078
1079 if (r == -1)
1080 r = EX_SOFTWARE;
1081 else if (WIFEXITED(r))
1082 r = WEXITSTATUS(r);
1083 else
1084 r = EX_SOFTWARE;
1085
1086 backup_close(&backup);
1087 return r;
1088 }
1089