int main(int argc, char **argv) { int opt, i, r; int dousers = 0; int rflag = 0; int mflag = 0; int fflag = 0; int xflag = 0; char buf[MAX_MAILBOX_PATH+1]; strarray_t discovered = STRARRAY_INITIALIZER; char *alt_config = NULL; char *start_part = NULL; if ((geteuid()) == 0 && (become_cyrus(/*is_master*/0) != 0)) { fatal("must run as the Cyrus user", EC_USAGE); } construct_hash_table(&unqid_table, 2047, 1); while ((opt = getopt(argc, argv, "C:kp:rmfsxgGqRUMoOnV:u")) != EOF) { switch (opt) { case 'C': /* alt config file */ alt_config = optarg; break; case 'p': start_part = optarg; break; case 'r': rflag = 1; break; case 'u': dousers = 1; break; case 'm': mflag = 1; break; case 'n': reconstruct_flags &= ~RECONSTRUCT_MAKE_CHANGES; break; case 'g': fprintf(stderr, "reconstruct: deprecated option -g ignored\n"); break; case 'G': reconstruct_flags |= RECONSTRUCT_ALWAYS_PARSE; break; case 'f': fflag = 1; break; case 'x': xflag = 1; break; case 'k': fprintf(stderr, "reconstruct: deprecated option -k ignored\n"); break; case 's': reconstruct_flags &= ~RECONSTRUCT_DO_STAT; break; case 'q': reconstruct_flags |= RECONSTRUCT_QUIET; break; case 'R': reconstruct_flags |= RECONSTRUCT_GUID_REWRITE; break; case 'U': reconstruct_flags |= RECONSTRUCT_GUID_UNLINK; break; case 'o': reconstruct_flags |= RECONSTRUCT_IGNORE_ODDFILES; break; case 'O': reconstruct_flags |= RECONSTRUCT_REMOVE_ODDFILES; break; case 'M': reconstruct_flags |= RECONSTRUCT_PREFER_MBOXLIST; break; case 'V': if (!strcasecmp(optarg, "max")) setversion = MAILBOX_MINOR_VERSION; else setversion = atoi(optarg); break; default: usage(); } } cyrus_init(alt_config, "reconstruct", 0, CONFIG_NEED_PARTITION_DATA); global_sasl_init(1,0,NULL); /* Set namespace -- force standard (internal) */ if ((r = mboxname_init_namespace(&recon_namespace, 1)) != 0) { syslog(LOG_ERR, "%s", error_message(r)); fatal(error_message(r), EC_CONFIG); } sync_log_init(); if (mflag) { if (rflag || fflag || optind != argc) { cyrus_done(); usage(); } do_mboxlist(); } mboxlist_init(0); mboxlist_open(NULL); quotadb_init(0); quotadb_open(NULL); #ifdef WITH_DAV caldav_init(); carddav_init(); webdav_init(); #endif /* Deal with nonexistent mailboxes */ if (start_part) { /* We were handed a mailbox that does not exist currently */ if(optind == argc) { fprintf(stderr, "When using -p, you must specify a mailbox to attempt to reconstruct."); exit(EC_USAGE); } /* do any of the mailboxes exist in mboxlist already? */ /* Do they look like mailboxes? */ for (i = optind; i < argc; i++) { if (strchr(argv[i],'%') || strchr(argv[i],'*')) { fprintf(stderr, "Using wildcards with -p is not supported.\n"); exit(EC_USAGE); } /* Translate mailboxname */ char *intname = mboxname_from_external(argv[i], &recon_namespace, NULL); /* Does it exist */ do { r = mboxlist_lookup(intname, NULL, NULL); } while (r == IMAP_AGAIN); if (r != IMAP_MAILBOX_NONEXISTENT) { fprintf(stderr, "Mailbox %s already exists. Cannot specify -p.\n", argv[i]); exit(EC_USAGE); } free(intname); } /* None of them exist. Create them. */ for (i = optind; i < argc; i++) { /* Translate mailboxname */ char *intname = mboxname_from_external(argv[i], &recon_namespace, NULL); /* don't notify mailbox creation here */ r = mboxlist_createmailbox(intname, 0, start_part, 1, "cyrus", NULL, 0, 0, !xflag, 0, NULL); if (r) { fprintf(stderr, "could not create %s\n", argv[i]); } free(intname); } } /* Normal Operation */ if (optind == argc) { if (rflag || dousers) { fprintf(stderr, "please specify a mailbox to recurse from\n"); cyrus_done(); exit(EC_USAGE); } assert(!rflag); strlcpy(buf, "*", sizeof(buf)); mboxlist_findall(&recon_namespace, buf, 1, 0, 0, do_reconstruct, NULL); } for (i = optind; i < argc; i++) { if (dousers) { mboxlist_usermboxtree(argv[i], do_reconstruct_p, NULL, MBOXTREE_TOMBSTONES|MBOXTREE_DELETED); continue; } char *domain = NULL; /* save domain */ if (config_virtdomains) domain = strchr(argv[i], '@'); strlcpy(buf, argv[i], sizeof(buf)); /* reconstruct the first mailbox/pattern */ mboxlist_findall(&recon_namespace, buf, 1, 0, 0, do_reconstruct, fflag ? &discovered : NULL); if (rflag) { /* build a pattern for submailboxes */ char *p = strchr(buf, '@'); if (p) *p = '\0'; strlcat(buf, ".*", sizeof(buf)); /* append the domain */ if (domain) strlcat(buf, domain, sizeof(buf)); /* reconstruct the submailboxes */ mboxlist_findall(&recon_namespace, buf, 1, 0, 0, do_reconstruct, fflag ? &discovered : NULL); } } /* examine our list to see if we discovered anything */ while (discovered.count) { char *name = strarray_shift(&discovered); int r = 0; /* create p (database only) and reconstruct it */ /* partition is defined by the parent mailbox */ /* don't notify mailbox creation here */ r = mboxlist_createmailbox(name, 0, NULL, 1, "cyrus", NULL, 0, 0, !xflag, 0, NULL); if (r) { fprintf(stderr, "createmailbox %s: %s\n", name, error_message(r)); } else { mboxlist_findone(&recon_namespace, name, 1, 0, 0, do_reconstruct, &discovered); } /* may have added more things into our list */ free(name); } free_hash_table(&unqid_table, free); sync_log_done(); mboxlist_close(); mboxlist_done(); quotadb_close(); quotadb_done(); partlist_local_done(); #ifdef WITH_DAV webdav_done(); carddav_done(); caldav_done(); #endif cyrus_done(); strarray_fini(&discovered); return 0; }
int main(int argc, char **argv) { save_argv0(argv[0]); const char *alt_config = NULL; const char *input_file = NULL; const char *backup_name = NULL; const char *servername = NULL; enum restore_mode mode = RESTORE_MODE_UNSPECIFIED; int local_only = 0; int wait = 0; int do_nothing = 0; int do_all_mailboxes = 0; struct restore_options options = {0}; options.expunged_mode = RESTORE_EXPUNGED_OKAY; options.trim_deletedprefix = 1; struct backup *backup = NULL; mbname_t *mbname = NULL; struct backup_mailbox_list *mailbox_list = NULL; struct sync_folder_list *reserve_folder_list = NULL; struct sync_reserve_list *reserve_list = NULL; struct buf tagbuf = BUF_INITIALIZER; struct backend *backend = NULL; struct dlist *upload = NULL; int opt, r; while ((opt = getopt(argc, argv, ":A:C:DF:LM:P:UXaf:m:nru:vw:xz")) != EOF) { switch (opt) { case 'A': if (options.keep_uidvalidity) usage(); options.override_acl = optarg; break; case 'C': alt_config = optarg; break; case 'D': /* XXX does this clash with keep_uidvalidity? */ options.trim_deletedprefix = 0; break; case 'F': if (do_all_mailboxes) usage(); input_file = optarg; break; case 'L': local_only = 1; break; case 'M': if (options.keep_uidvalidity) usage(); options.override_mboxname = optarg; break; case 'P': if (options.keep_uidvalidity) usage(); options.override_partition = optarg; break; case 'U': if (options.override_acl || options.override_mboxname || options.override_partition) usage(); options.keep_uidvalidity = 1; break; case 'X': if (options.expunged_mode != RESTORE_EXPUNGED_OKAY) usage(); options.expunged_mode = RESTORE_EXPUNGED_EXCLUDE; break; case 'a': if (input_file) usage(); do_all_mailboxes = 1; break; case 'f': if (mode != RESTORE_MODE_UNSPECIFIED) usage(); mode = RESTORE_MODE_FILENAME; backup_name = optarg; break; case 'm': if (mode != RESTORE_MODE_UNSPECIFIED) usage(); mode = RESTORE_MODE_MBOXNAME; backup_name = optarg; break; case 'n': do_nothing = 1; break; case 'r': options.do_submailboxes = 1; break; case 'u': if (mode != RESTORE_MODE_UNSPECIFIED) usage(); mode = RESTORE_MODE_USERNAME; backup_name = optarg; break; case 'v': options.verbose++; break; case 'w': wait = atoi(optarg); if (wait < 0) usage(); break; case 'x': if (options.expunged_mode != RESTORE_EXPUNGED_OKAY) usage(); options.expunged_mode = RESTORE_EXPUNGED_ONLY; break; case 'z': options.require_compression = 1; break; case ':': if (optopt == 'A') options.override_acl = ""; else usage(); break; default: usage(); break; } } /* we need a server name */ if (optind == argc) usage(); servername = argv[optind++]; /* we need a source of backup data */ if (mode == RESTORE_MODE_UNSPECIFIED) { if (optind == argc) usage(); backup_name = argv[optind++]; mode = RESTORE_MODE_USERNAME; } /* we need either an input file or some objects to restore */ if (!do_all_mailboxes && !input_file && optind == argc) usage(); /* and we can't have both because i said */ if ((input_file || do_all_mailboxes) && optind < argc) usage(); /* okay, arguments seem sane, we are go */ cyrus_init(alt_config, "restore", 0, 0); /* load the SASL plugins */ global_sasl_init(1, 0, mysasl_cb); /* wait here for gdb attach */ if (wait) { fprintf(stderr, "Waiting for %d seconds for gdb attach...\n", wait); sleep(wait); } /* open backup */ switch (mode) { case RESTORE_MODE_FILENAME: r = backup_open_paths(&backup, backup_name, NULL, BACKUP_OPEN_NONBLOCK, BACKUP_OPEN_NOCREATE); break; case RESTORE_MODE_MBOXNAME: mbname = mbname_from_intname(backup_name); if (!mbname) usage(); r = backup_open(&backup, mbname, BACKUP_OPEN_NONBLOCK, BACKUP_OPEN_NOCREATE); mbname_free(&mbname); break; case RESTORE_MODE_USERNAME: mbname = mbname_from_userid(backup_name); if (!mbname) usage(); r = backup_open(&backup, mbname, BACKUP_OPEN_NONBLOCK, BACKUP_OPEN_NOCREATE); mbname_free(&mbname); break; default: usage(); break; } if (r) goto done; /* scan for objects to restore: * mailboxes will have all messages added, modulo expunged_mode * messages will be added individually with appropriate folder */ mailbox_list = xzmalloc(sizeof *mailbox_list); reserve_folder_list = restore_make_reserve_folder_list(backup); reserve_list = sync_reserve_list_create(SYNC_MSGID_LIST_HASH_SIZE); if (input_file) { char buf[MAX_MAILBOX_NAME + 2]; // \n\0 size_t len; FILE *f; if (0 != strcmp(input_file, "-")) { f = fopen(input_file, "r"); if (!f) { fprintf(stderr, "fopen %s: %s\n", input_file, strerror(errno)); goto done;// FIXME shut_down? } } else { f = stdin; } while (fgets(buf, sizeof(buf), f)) { len = strlen(buf); if (len > 0 && buf[len - 1] == '\n') buf[--len] = '\0'; if (len == 0 || buf[0] == '#') continue; r = restore_add_object(buf, &options, backup, mailbox_list, reserve_folder_list, reserve_list); // FIXME r } fclose(f); } else if (do_all_mailboxes) { struct backup_mailbox_list *all_mailboxes; struct backup_mailbox *mailbox; all_mailboxes = backup_get_mailboxes(backup, 0, BACKUP_MAILBOX_ALL_RECORDS); for (mailbox = all_mailboxes->head; mailbox; mailbox = mailbox->next) { restore_add_mailbox(mailbox, &options, mailbox_list, reserve_folder_list, reserve_list); } backup_mailbox_list_empty(all_mailboxes); free(all_mailboxes); } else { int i; for (i = optind; i < argc; i++) { r = restore_add_object(argv[i], &options, backup, mailbox_list, reserve_folder_list, reserve_list); // FIXME r } } if (do_nothing) { if (options.verbose) fprintf(stderr, "do nothing (-n) specified, exiting now\n"); goto done; } /* connect to destination */ backend = restore_connect(servername, &tagbuf, &options); if (!backend) { // FIXME r = -1; goto done; } /* do the restore */ struct sync_reserve *reserve; for (reserve = reserve_list->head; reserve; reserve = reserve->next) { /* send APPLY RESERVE and parse missing lists */ r = sync_reserve_partition(reserve->part, reserve_folder_list, reserve->list, backend); if (r) goto done; /* send APPLY MESSAGEs */ r = backup_prepare_message_upload(backup, reserve->part, reserve->list, &sync_msgid_lookup, &upload); if (r) goto done; /* upload in small(ish) blocks to avoid timeouts */ while (upload->head) { struct dlist *block = dlist_splice(upload, 1024); sync_send_apply(block, backend->out); r = sync_parse_response("MESSAGE", backend->in, NULL); dlist_unlink_files(block); dlist_free(&block); if (r) goto done; } } /* sync_prepare_dlists needs to upload messages per-mailbox, because * it needs the mailbox to find the filename for the message. but * we have no such limitation, so we can upload messages while * looping over the reserve_list instead, above. * * alternatively, if we do it on a per-mailbox basis then we can limit * the hit on the staging directory to only a mailbox worth of messages * at a time. but we don't have a logical grouping that would make * this coherent */ /* send RESTORE MAILBOXes */ struct backup_mailbox *mailbox; for (mailbox = mailbox_list->head; mailbox; mailbox = mailbox->next) { /* XXX filter the mailbox records based on reserve/missing/upload */ /* XXX does this sensibly handle mailbox objs with empty values? */ struct dlist *dl = backup_mailbox_to_dlist(mailbox); if (!dl) continue; if (local_only) { free(dl->name); dl->name = xstrdup("LOCAL_MAILBOX"); } sync_send_restore(dl, backend->out); r = sync_parse_response("MAILBOX", backend->in, NULL); dlist_free(&dl); if (r) goto done; } done: if (r) fprintf(stderr, "%s: %s\n", backup_name, error_message(r)); /* release lock asap */ if (backup) backup_close(&backup); if (backend) backend_disconnect(backend); if (upload) { dlist_unlink_files(upload); dlist_free(&upload); } if (mailbox_list) { backup_mailbox_list_empty(mailbox_list); free(mailbox_list); } if (reserve_folder_list) sync_folder_list_free(&reserve_folder_list); if (reserve_list) sync_reserve_list_free(&reserve_list); buf_free(&tagbuf); backup_cleanup_staging_path(); cyrus_done(); exit(r ? EX_TEMPFAIL : EX_OK); }