int ct_compare_secrets(struct ct_global_state *state, struct ct_op *op) { struct ct_ctfileop_args *cca = op->op_args; FILE *f, *tf; char temp_path[PATH_MAX]; struct stat sb, tsb; char buf[1024], tbuf[1024]; size_t rsz; off_t sz; int ret = 0, s_errno = 0; /* cachedir is '/' terminated */ strlcpy(temp_path, cca->cca_tdir, sizeof(temp_path)); strlcat(temp_path, cca->cca_localname, sizeof(temp_path)); if (stat(state->ct_config->ct_crypto_secrets, &sb) != 0) { s_errno = errno; ret = CTE_ERRNO; CWARNX("\"%s\": %s", state->ct_config->ct_crypto_secrets, ct_strerror(ret)); goto free; } if (stat(temp_path, &tsb) != 0) { s_errno = errno; ret = CTE_ERRNO; CWARNX("\"%s\": %s", temp_path, ct_strerror(ret)); goto free; } /* Compare size first */ if (tsb.st_size != sb.st_size) { ret = CTE_SECRETS_FILE_SIZE_MISMATCH; CWARNX("%" PRId64 " vs %" PRId64 ": %s", (int64_t)tsb.st_size, (int64_t)sb.st_size, ct_strerror(ret)); goto free; } if ((f = ct_fopen(state->ct_config->ct_crypto_secrets, "rb")) == NULL) { s_errno = errno; ret = CTE_ERRNO; CWARNX("\"%s\": %s", state->ct_config->ct_crypto_secrets, ct_strerror(ret)); goto free; } if ((tf = ct_fopen(temp_path, "rb")) == NULL) { s_errno = errno; ret = CTE_ERRNO; CWARNX("temp_path: %s", ct_strerror(ret)); goto close_current; } /* read then throw away */ unlink(temp_path); while (sb.st_size > 0) { sz = sb.st_size; if (sz > 1024) sz = 1024; sb.st_size -= sz; CNDBG(CT_LOG_FILE, "sz = %" PRId64 " remaining = %" PRId64, (int64_t)sz, (int64_t)sb.st_size); if ((rsz = fread(buf, 1, sz, f)) != sz) { CNDBG(CT_LOG_CRYPTO, "short read on secrets file (%" PRId64 " %" PRId64 ")", (int64_t)sz, (int64_t)rsz); ret = CTE_SECRETS_FILE_SHORT_READ; CWARNX("%s: %s", state->ct_config->ct_crypto_secrets, ct_strerror(ret)); goto out; } if ((rsz = fread(tbuf, 1, sz, tf)) != sz) { CNDBG(CT_LOG_CRYPTO, "short read on temporary secrets " "file (%" PRId64 " %" PRId64 ")", (int64_t)sz, (int64_t)rsz); ret = CTE_SECRETS_FILE_SHORT_READ; CWARNX("%s: %s", temp_path, ct_strerror(ret)); goto out; } if (memcmp(buf, tbuf, sz) != 0) { ret = CTE_SECRETS_FILE_DIFFERS; goto out; } } out: fclose(f); close_current: fclose(tf); free: e_free(&cca); if (ret == CTE_ERRNO) errno = s_errno; return (ret); }
int ct_get_password(char *password, size_t passwordlen, char *prompt, int verify) { int rv = 1; char pw[CT_PASS_MAX + 1]; if (readpassphrase(prompt ? prompt : "New password: "******"invalid password"); goto done; } if (verify) { if (readpassphrase("Retype password: "******"invalid password"); goto done; } if (strcmp(password, pw)) { CWARNX("passwords do not match"); goto done; } } if (strlen(password) == 0) { CWARNX("password must not be empty"); goto done; } rv = 0; done: bzero(pw, sizeof pw); return (rv); }
uint64_t ct_get_debugmask(char *debugstring) { char *cur, *next; uint64_t debug_mask = 0; int i, neg; struct debuglvl { const char *name; uint64_t mask; } debuglevels[] = { { "socket", CT_LOG_SOCKET }, { "config", CT_LOG_CONFIG }, { "exude", CT_LOG_EXUDE }, { "net", CT_LOG_NET }, { "trans", CT_LOG_TRANS }, { "sha", CT_LOG_SHA }, { "ctfile", CT_LOG_CTFILE }, { "db", CT_LOG_DB }, { "crypto", CT_LOG_CRYPTO }, { "file", CT_LOG_FILE }, { "xml", CT_LOG_XML }, { "vertree", CT_LOG_VERTREE }, { "all", ~(0ULL) }, }; next = debugstring; while ((cur = next) != NULL) { neg = 0; if ((next = strchr(next + 1, ',')) != NULL) { *(next++) = '\0'; } if (*cur == '-') { neg = 1; cur++; } for (i = 0; i < nitems(debuglevels); i++) { if (strcasecmp(debuglevels[i].name, cur) == 0) break; } if (i == nitems(debuglevels)) { CWARNX("unrecognized debug option:" "\"%s\"", cur); continue; } if (neg) debug_mask &= ~debuglevels[i].mask; else debug_mask |= debuglevels[i].mask; } return (debug_mask); }
int ct_extract_complete_file_end(struct ct_global_state *state, struct ct_trans *trans) { if (trans->tr_fl_node->fn_skip_file == 0) { ct_sha1_final(trans->tr_csha, &trans->tr_fl_node->fn_shactx); if (memcmp(trans->tr_csha, trans->tr_sha, sizeof(trans->tr_sha)) != 0) CWARNX("extract sha mismatch on %s", trans->tr_fl_node->fn_fullname); ct_file_extract_close(state->extract_state, trans->tr_fl_node); state->ct_print_file_end(state->ct_print_state, trans->tr_fl_node, state->ct_max_block_size); } state->ct_stats->st_files_completed++; return (0); }
enum ctdb_lookup ctdb_lookup_sha(struct ctdb_state *state, uint8_t *sha_k, uint8_t *sha_v, uint8_t *iv, int32_t *old_genid) { char shat[SHA_DIGEST_STRING_LENGTH]; int rv, rc; int32_t genid; uint8_t *p; sqlite3_stmt *stmt; rv = CTDB_SHA_NEXISTS; *old_genid = -1; if (state == NULL || state->ctdb_db == NULL) return rv; stmt = state->ctdb_stmt_lookup; if (state->ctdb_in_transaction == 0) { if (ctdb_begin_transaction(state) != 0) return (rv); state->ctdb_trans_commit_rem = OPS_PER_TRANSACTION; } if (clog_mask_is_set(CT_LOG_DB)) { ct_sha1_encode(sha_k, shat); CNDBG(CT_LOG_DB, "looking for bin %s", shat); } if (sqlite3_bind_blob(stmt, 1, sha_k, SHA_DIGEST_LENGTH, SQLITE_STATIC)) { CNDBG(CT_LOG_DB, "could not sha"); return (CTDB_SHA_NEXISTS); } rc = sqlite3_step(stmt); if (rc == SQLITE_DONE) { CNDBG(CT_LOG_DB, "not found"); sqlite3_reset(stmt); return CTDB_SHA_NEXISTS; } else if (rc != SQLITE_ROW) { CNDBG(CT_LOG_DB, "could not step(%d) %d %d %s", __LINE__, rc, sqlite3_extended_errcode(state->ctdb_db), sqlite3_errmsg(state->ctdb_db)); sqlite3_reset(stmt); return CTDB_SHA_NEXISTS; } CNDBG(CT_LOG_DB, "found"); p = (uint8_t *)sqlite3_column_blob(stmt, 0); if (p) { if (sqlite3_column_bytes(stmt, 0) != SHA_DIGEST_LENGTH) { CNDBG(CT_LOG_DB, "invalid blob size"); sqlite3_reset(stmt); return rv; } if (clog_mask_is_set(CT_LOG_DB)) { ct_sha1_encode(p, shat); CNDBG(CT_LOG_DB, "found bin %s", shat); } rv = CTDB_SHA_EXISTS; bcopy (p, sha_v, SHA_DIGEST_LENGTH); } else { CNDBG(CT_LOG_DB, "no bin found"); } if (state->ctdb_crypt) { p = (uint8_t *)sqlite3_column_blob(stmt, 1); if (p) { if (sqlite3_column_bytes(stmt, 1) != CT_IV_LEN) { CNDBG(CT_LOG_DB, "invalid blob size"); sqlite3_reset(stmt); rv = CTDB_SHA_NEXISTS; return rv; } if (clog_mask_is_set(CT_LOG_DB)) { ct_sha1_encode(p, shat); CNDBG(CT_LOG_DB, "found iv (prefix) %s", shat); } bcopy (p, iv, CT_IV_LEN); } else { CNDBG(CT_LOG_DB, "no iv found"); rv = CTDB_SHA_NEXISTS; } genid = sqlite3_column_int(stmt, 2); } else { genid = sqlite3_column_int(stmt, 1); } sqlite3_reset(stmt); if (genid < state->ctdb_genid) { ct_sha1_encode(sha_k, shat); rv = CTDB_SHA_MAYBE_EXISTS; *old_genid = genid; } else if (genid > state->ctdb_genid) { /* XXX Abort? */ CWARNX("WARNING: sha with higher genid than database!"); } state->ctdb_trans_commit_rem--; if (state->ctdb_trans_commit_rem <= 0) { ctdb_end_transaction(state); } return rv; }
void ct_extract(struct ct_global_state *state, struct ct_op *op) { struct ct_extract_args *cea = op->op_args; const char *ctfile = cea->cea_local_ctfile; char **filelist = cea->cea_filelist; int match_mode = cea->cea_matchmode; struct ct_extract_priv *ex_priv = op->op_priv; int ret; struct ct_trans *trans; char shat[SHA_DIGEST_STRING_LENGTH]; /* if we were woken up due to fatal, just clean up local state */ if (state->ct_dying != 0) goto dying; CNDBG(CT_LOG_TRANS, "entry"); switch (ct_get_file_state(state)) { case CT_S_STARTING: if (ex_priv == NULL) { ex_priv = e_calloc(1, sizeof(*ex_priv)); TAILQ_INIT(&ex_priv->extract_head); if ((ret = ct_match_compile(&ex_priv->inc_match, match_mode, filelist)) != 0) { ct_fatal(state, "failed to compile include pattern", ret); goto dying; } if (cea->cea_excllist != NULL && (ret = ct_match_compile(&ex_priv->ex_match, match_mode, cea->cea_excllist)) != 0) { ct_fatal(state, "failed to compile exclude pattern", ret); goto dying; } op->op_priv = ex_priv; RB_INIT(&ex_priv->pending_tree); } if ((ret = ct_file_extract_init(&state->extract_state, cea->cea_tdir, cea->cea_attr, cea->cea_follow_symlinks, ex_priv->allfiles, cea->cea_log_state, cea->cea_log_chown_failed)) != 0) { ct_fatal(state, "Can not initialize extract state", ret); goto dying; } if (ct_extract_calculate_total(state, cea, ex_priv->inc_match, ex_priv->ex_match) != 0) { CWARNX("failed to calculate stats"); goto dying; } if ((ret = ct_extract_setup(&ex_priv->extract_head, &ex_priv->xdr_ctx, ctfile, cea->cea_ctfile_basedir, &ex_priv->allfiles)) != 0) { ct_fatal(state, "can't setup extract queue", ret); goto dying; } state->ct_print_ctfile_info(state->ct_print_state, ex_priv->xdr_ctx.xs_filename, &ex_priv->xdr_ctx.xs_gh); /* XXX we should handle this better */ if (state->ct_max_block_size < ex_priv->xdr_ctx.xs_gh.cmg_chunk_size) CABORTX("block size negotiated with server %d is " "smaller than file max block size %d", state->ct_max_block_size, ex_priv->xdr_ctx.xs_gh.cmg_chunk_size); /* create rb tree head, prepare to start inserting */ if (ex_priv->allfiles) { ex_priv->fillrb = 1; } break; case CT_S_FINISHED: return; default: break; } ct_set_file_state(state, CT_S_RUNNING); while (1) { trans = ct_trans_alloc(state); if (trans == NULL) { /* system busy, return */ CNDBG(CT_LOG_TRANS, "ran out of transactions, waiting"); ct_set_file_state(state, CT_S_WAITING_TRANS); return; } trans->tr_statemachine = ct_state_extract; switch ((ret = ctfile_parse(&ex_priv->xdr_ctx))) { case XS_RET_FILE: if (ex_priv->fillrb == 0 && ex_priv->xdr_ctx.xs_hdr.cmh_nr_shas == -1) { if (ex_priv->allfiles == 0) CINFO("file %s has negative shas " "and backup is not allfiles", ex_priv->xdr_ctx.xs_hdr.cmh_filename); ex_priv->doextract = 0; goto skip; /* skip ze file for now */ } trans = ct_trans_realloc_local(state, trans); trans->tr_fl_node = ex_priv->fl_ex_node = ct_alloc_fnode(); ct_populate_fnode(state->extract_state, &ex_priv->xdr_ctx, trans->tr_fl_node, &trans->tr_state, ex_priv->allfiles, cea->cea_strip_slash); if (trans->tr_state == TR_S_EX_SPECIAL) { trans->tr_complete = ct_extract_complete_special; } else { trans->tr_complete = ct_extract_complete_file_start; } trans->tr_cleanup = ct_extract_cleanup_fnode; if (ex_priv->haverb) { struct ct_pending_file *cpf; if ((cpf = ct_extract_find_entry( &ex_priv->pending_tree, trans->tr_fl_node->fn_fullname)) != NULL) { struct fnode *hardlink; /* copy permissions over */ trans->tr_fl_node->fn_uid = cpf->cpf_uid; trans->tr_fl_node->fn_gid = cpf->cpf_gid; trans->tr_fl_node->fn_mode = cpf->cpf_mode; trans->tr_fl_node->fn_mtime = cpf->cpf_mtime; trans->tr_fl_node->fn_atime = cpf->cpf_atime; /* copy list of pending links over */ while ((hardlink = TAILQ_FIRST(&cpf->cpf_links))) { TAILQ_REMOVE(&cpf->cpf_links, hardlink, fn_list); TAILQ_INSERT_TAIL( &trans->tr_fl_node->fn_hardlinks, hardlink, fn_list); } ex_priv->doextract = 1; ct_extract_free_entry( &ex_priv->pending_tree, cpf); } else { ex_priv->doextract = 0; } } else { ex_priv->doextract = !ct_match(ex_priv->inc_match, trans->tr_fl_node->fn_fullname); if (ex_priv->doextract && ex_priv->ex_match != NULL && !ct_match(ex_priv->ex_match, trans->tr_fl_node->fn_fullname)) { ex_priv->doextract = 0; } } if (ex_priv->doextract && trans->tr_fl_node->fn_hardlink) { struct ct_pending_file *file; if ((file = ct_extract_find_entry( &ex_priv->pending_tree, trans->tr_fl_node->fn_hlname)) != NULL) { CNDBG(CT_LOG_FILE, "adding pending link for %s to %s", file->cpf_name, trans->tr_fl_node->fn_fullname); /* our reference to node passed */ ct_pending_file_add_link(file, trans->tr_fl_node); ex_priv->doextract = 0; goto skip; } } /* * If we're on the first ctfile in an allfiles backup * put the matches with -1 on the rb tree so we'll * remember to extract it from older files. */ if (ex_priv->doextract == 1 && ex_priv->fillrb && ex_priv->xdr_ctx.xs_hdr.cmh_nr_shas == -1) { ct_extract_insert_entry(&ex_priv->pending_tree, trans->tr_fl_node); ex_priv->doextract = 0; /* XXX reconsider the freeing */ } if (ex_priv->doextract == 0) { ct_free_fnode(trans->tr_fl_node); skip: ex_priv->fl_ex_node = NULL; ct_trans_free(state, trans); continue; } CNDBG(CT_LOG_CTFILE, "file %s numshas %" PRId64, trans->tr_fl_node->fn_fullname, ex_priv->xdr_ctx.xs_hdr.cmh_nr_shas); /* * special files we give our refcount up * regular files we need a new one since we need to * keep ours. */ if (trans->tr_state != TR_S_EX_SPECIAL) { ct_ref_fnode(trans->tr_fl_node); } else { ex_priv->fl_ex_node = NULL; } ct_queue_first(state, trans); break; case XS_RET_SHA: if (ex_priv->doextract == 0 || ex_priv->fl_ex_node->fn_skip_file != 0) { if (ctfile_parse_seek(&ex_priv->xdr_ctx)) { ct_fatal(state, "Can't seek past shas", ex_priv->xdr_ctx.xs_errno); goto dying; } ct_trans_free(state, trans); continue; } /* use saved fnode */ trans->tr_fl_node = ex_priv->fl_ex_node; if (memcmp(zerosha, ex_priv->xdr_ctx.xs_sha, SHA_DIGEST_LENGTH) == 0) { CWARNX("\"%s\" truncated during backup", trans->tr_fl_node->fn_fullname); if (ctfile_parse_seek(&ex_priv->xdr_ctx)) { ct_fatal(state, "Can't seek past " "truncation shas", ex_priv->xdr_ctx.xs_errno); goto dying; } ct_trans_free(state, trans); continue; } if (ex_priv->xdr_ctx.xs_gh.cmg_flags & CT_MD_CRYPTO) { /* * yes csha and sha are reversed, we want * to download csha, but putting it in sha * simplifies the code */ bcopy(ex_priv->xdr_ctx.xs_sha, trans->tr_csha, sizeof(trans->tr_csha)); bcopy(ex_priv->xdr_ctx.xs_csha, trans->tr_sha, sizeof(trans->tr_sha)); bcopy(ex_priv->xdr_ctx.xs_iv, trans->tr_iv, sizeof(trans->tr_iv)); } else { bcopy(ex_priv->xdr_ctx.xs_sha, trans->tr_sha, sizeof(trans->tr_sha)); } if (clog_mask_is_set(CT_LOG_SHA)) { ct_sha1_encode(trans->tr_sha, shat); CNDBG(CT_LOG_SHA, "extracting sha %s", shat); } trans->tr_state = TR_S_EX_SHA; trans->tr_complete = ct_extract_complete_file_read; trans->tr_dataslot = 0; ct_ref_fnode(trans->tr_fl_node); trans->tr_cleanup = ct_extract_cleanup_fnode; ct_queue_first(state, trans); break; case XS_RET_FILE_END: trans = ct_trans_realloc_local(state, trans); if (ex_priv->doextract == 0 || ex_priv->fl_ex_node->fn_skip_file != 0) { /* release our reference done with file */ if (ex_priv->fl_ex_node) { ct_free_fnode(ex_priv->fl_ex_node); ex_priv->fl_ex_node = NULL; } ct_trans_free(state, trans); continue; } /* use saved fnode from state */ trans->tr_fl_node = ex_priv->fl_ex_node; bcopy(ex_priv->xdr_ctx.xs_trl.cmt_sha, trans->tr_sha, sizeof(trans->tr_sha)); trans->tr_state = TR_S_EX_FILE_END; trans->tr_complete = ct_extract_complete_file_end; trans->tr_cleanup = ct_extract_cleanup_fnode; trans->tr_fl_node->fn_size = ex_priv->xdr_ctx.xs_trl.cmt_orig_size; /* * no reference here since we give our reference to the * last transaction on that file. We are done with it. */ ex_priv->fl_ex_node = NULL; ct_queue_first(state, trans); break; case XS_RET_EOF: CNDBG(CT_LOG_CTFILE, "Hit end of ctfile"); ctfile_parse_close(&ex_priv->xdr_ctx); /* if rb tree and rb is empty, goto end state */ if ((ex_priv->haverb || ex_priv->fillrb) && ct_extract_rb_empty(&ex_priv->pending_tree)) { /* * Cleanup extract queue, in case we had files * left. */ ct_extract_cleanup_queue( &ex_priv->extract_head); goto we_re_done_here; } if (!TAILQ_EMPTY(&ex_priv->extract_head)) { /* * if allfiles and this was the first pass. * free the current match lists * switch to rb tree mode */ if (ex_priv->fillrb) { ct_match_unwind(ex_priv->inc_match); if (ex_priv->ex_match) ct_match_unwind( ex_priv->ex_match); ex_priv->ex_match = NULL; ex_priv->inc_match = NULL; ex_priv->haverb = 1; ex_priv->fillrb = 0; } ct_trans_free(state, trans); /* reinits ex_priv->xdr_ctx */ if ((ret = ct_extract_open_next(&ex_priv->extract_head, &ex_priv->xdr_ctx)) != 0) { ct_fatal(state, "Can't open next ctfile", ret); goto dying; } state->ct_print_ctfile_info( state->ct_print_state, ex_priv->xdr_ctx.xs_filename, &ex_priv->xdr_ctx.xs_gh); /* poke file into action */ ct_wakeup_file(state->event_state); } else { /* * If rb tree and it is still has entries, * bitch about it */ /* XXX print out missing files */ if ((ex_priv->haverb || ex_priv->fillrb) && ct_extract_rb_empty( &ex_priv->pending_tree)) { CWARNX("out of ctfiles but some " "files are not found"); } we_re_done_here: if (ex_priv->inc_match) ct_match_unwind(ex_priv->inc_match); if (ex_priv->ex_match) ct_match_unwind( ex_priv->ex_match); ct_extract_pending_cleanup( &ex_priv->pending_tree); e_free(&ex_priv); op->op_priv = NULL; trans->tr_state = TR_S_DONE; trans->tr_complete = ct_extract_complete_done; trans->tr_cleanup = ct_extract_cleanup_done; /* * Technically this should be a local * transaction. However, since we are done * it doesn't really matter either way. */ ct_queue_first(state, trans); CNDBG(CT_LOG_TRANS, "extract finished"); ct_set_file_state(state, CT_S_FINISHED); } return; break; case XS_RET_FAIL: ct_fatal(state, "Failed to parse ctfile", ex_priv->xdr_ctx.xs_errno); goto dying; break; } } return; dying: /* only if we hadn't sent the final transaction yet */ if (ex_priv != NULL) { ct_extract_cleanup_queue(&ex_priv->extract_head); if (ex_priv->inc_match) ct_match_unwind(ex_priv->inc_match); if (ex_priv->ex_match) ct_match_unwind(ex_priv->ex_match); if (!ct_extract_rb_empty(&ex_priv->pending_tree)) { ct_extract_pending_cleanup(&ex_priv->pending_tree); } if (ex_priv->fl_ex_node != NULL) { ct_free_fnode(ex_priv->fl_ex_node); } /* XXX what about ex_priv->xdr_ctx ? */ e_free(&ex_priv); op->op_priv = NULL; /* if ex_priv is gone then the trans will clean this up */ if (state->extract_state) ct_file_extract_cleanup(state->extract_state); } return; }
int ct_main(int argc, char **argv) { struct ct_extract_args cea; struct ct_archive_args caa; struct ct_ctfileop_args cca; struct ct_ctfile_list_args ccla; struct ct_ctfile_delete_args ccda; struct ct_global_state *state = NULL; struct ct_config *conf; char *ct_tdir = NULL; char *ct_basisbackup = NULL; char *ctfile = NULL; char *ct_includefile = NULL; char *ct_excludefile = NULL; char *configfile = NULL, *config_file = NULL; char *basisfile = NULL; char *debugstring = NULL; char **excludelist = NULL; char **includelist = NULL; uint64_t debug_mask = 0; uint32_t cflags = CLOG_F_ENABLE | CLOG_F_STDERR; int ct_metadata = 0; int ct_match_mode = CT_MATCH_GLOB; int c; int ret = 0; int level0 = 0; int freeincludes = 0; int no_cross_mounts = 0; int strip_slash = 1; int follow_root_symlink = 0; int follow_symlinks = 0; int attr = 0; int verbose_ratios = 0; int ct_flags = 0; while ((c = getopt(argc, argv, "AB:C:D:E:F:HI:PRVXacdef:hmprtvx0")) != -1) { switch (c) { case 'A': /* noop, deprecated */ break; case 'B': basisfile = optarg; break; case 'C': ct_tdir = optarg; break; case 'D': if (debugstring != NULL) CFATALX("only one -D argument is valid"); debugstring = optarg; break; case 'E': ct_excludefile = optarg; break; case 'F': configfile = optarg; break; case 'H': follow_root_symlink = 1; break; case 'I': ct_includefile = optarg; break; case 'P': strip_slash = 0; break; case 'R': verbose_ratios = 1; break; case 'V': show_version(); exit(0); break; case 'X': no_cross_mounts = 1; break; case 'a': /* noop, deprecated */ break; case 'c': if (ct_action) CFATALX("cannot mix operations, -c -e -t -x"); ct_action = CT_A_ARCHIVE; break; case 'e': if (ct_action) CFATALX("cannot mix operations, -c -e -t -x"); ct_action = CT_A_ERASE; break; case 'f': /* metadata file */ ctfile = optarg; break; case 'h': follow_symlinks = 1; break; case 'm': /* metadata processing - XXX temporary? */ ct_metadata = 1; break; case 'r': ct_match_mode = CT_MATCH_REGEX; break; case 'p': attr = 1; break; case 't': if (ct_action) CFATALX("cannot mix operations, -c -e -t -x"); ct_action = CT_A_LIST; break; case 'v': ct_verbose++; break; case 'x': if (ct_action) CFATALX("cannot mix operations, -c -e -t -x"); ct_action = CT_A_EXTRACT; break; case '0': level0 = 1; break; default: ct_usage(); /* NOTREACHED */ } } argc -= optind; argv += optind; if (debugstring) { cflags |= CLOG_F_DBGENABLE | CLOG_F_FILE | CLOG_F_FUNC | CLOG_F_LINE | CLOG_F_DTIME; exude_enable(CT_LOG_EXUDE); #if CT_ENABLE_THREADS exude_enable_threads(); #endif debug_mask |= ct_get_debugmask(debugstring); } /* please don't delete this line AGAIN! --mp */ if (clog_set_flags(cflags)) errx(1, "illegal clog flags"); clog_set_mask(debug_mask); /* We can allocate these now that we've decided if we need exude */ if (configfile) config_file = e_strdup(configfile); if (basisfile) ct_basisbackup = e_strdup(basisfile); if (ct_includefile != NULL) { int nentries; if ((ct_action == CT_A_LIST || ct_action == CT_A_EXTRACT) && argc != 0) CFATALX("-I is invalid when a pattern is " "provided on the command line"); includelist = ct_matchlist_fromfile(ct_includefile, &nentries); if (nentries == -1) CFATAL("can't get includelist from %s", ct_includefile); freeincludes = 1; } else if ((ct_action == CT_A_LIST || ct_action == CT_A_EXTRACT)) { includelist = argv; } if (ct_excludefile != NULL) { int nentries; excludelist = ct_matchlist_fromfile(ct_excludefile, &nentries); if (nentries == -1) CFATAL("can't get excludelsit from %s", ct_excludefile); } if ((ret = ct_load_config(&conf, &config_file)) != 0) { CFATALX("%s", ct_strerror(ret)); } if (!(ct_metadata && (ct_action == CT_A_LIST || ct_action == CT_A_ERASE))) { if (ctfile == NULL) { CWARNX("ctfile is required"); ct_usage(); } if (conf->ct_ctfile_mode == CT_MDMODE_REMOTE && ctfile_verify_name(ctfile)) CFATALX("invalid ctfile: %s", ctfile); } /* * !metadata extract with no args extracts everything. * and all lists show everything if not filtered */ if (((ct_metadata == 0 && ct_action == CT_A_EXTRACT) || ct_action == CT_A_LIST) && argc == 0) ct_match_mode = CT_MATCH_EVERYTHING; if (level0) conf->ct_auto_incremental = 0; /* force incremental off */ if (conf->ct_ctfile_mode == CT_MDMODE_REMOTE && ct_metadata == 0 && ct_basisbackup != NULL) CFATALX("incremental basis in remote mode"); /* Don't bother starting a connection if just listing local files. */ if (ct_action == CT_A_LIST && conf->ct_ctfile_mode == CT_MDMODE_LOCAL && ct_metadata == 0 ) { ret = ct_list(ctfile, includelist, excludelist, ct_match_mode, NULL, strip_slash, ct_verbose); goto out; } ct_prompt_for_login_password(conf); if (ct_action == CT_A_EXTRACT || ct_action == CT_A_ARCHIVE || (ct_action == CT_A_LIST && conf->ct_ctfile_mode == CT_MDMODE_REMOTE && ct_metadata == 0) || ct_action == CT_A_ERASE) ct_flags |= CT_NEED_SECRETS; if (ct_action == CT_A_ARCHIVE) ct_flags |= CT_NEED_DB; if ((ret = ct_init(&state, conf, ct_flags, ct_info_sig)) != 0) CFATALX("failed to initialise cyphertite: %s", ct_strerror(ret)); #if defined(CT_EXT_INIT) CT_EXT_INIT(state); #endif if (conf->ct_crypto_passphrase != NULL && conf->ct_secrets_upload != 0) { ct_add_operation(state, ctfile_list_start, ct_check_secrets_extract, conf->ct_crypto_secrets); } if (ct_action == CT_A_EXTRACT) ct_set_log_fns(state, &ct_verbose, ct_print_ctfile_info, ct_print_file_start, ct_print_file_end, ct_print_traverse_start, ct_print_traverse_end); else if (ct_action == CT_A_ARCHIVE) ct_set_log_fns(state, &ct_verbose, ct_print_ctfile_info, ct_pr_fmt_file, ct_pr_fmt_file_end, ct_print_traverse_start, ct_print_traverse_end); if (conf->ct_ctfile_mode == CT_MDMODE_REMOTE && ct_metadata == 0) { switch (ct_action) { case CT_A_EXTRACT: case CT_A_LIST: cea.cea_local_ctfile = NULL; /* to be found */ cea.cea_filelist = includelist; cea.cea_excllist = excludelist; cea.cea_matchmode = ct_match_mode; cea.cea_ctfile_basedir = conf->ct_ctfile_cachedir; cea.cea_tdir = ct_tdir; cea.cea_strip_slash = strip_slash; cea.cea_attr = attr; cea.cea_follow_symlinks = follow_symlinks; cea.cea_log_state = &ct_verbose; cea.cea_log_chown_failed = ct_print_extract_chown_failed; ctfile_find_for_operation(state, ctfile, ((ct_action == CT_A_EXTRACT) ? ctfile_nextop_extract : ctfile_nextop_list), &cea, 1, 0); break; case CT_A_ARCHIVE: ct_normalize_filelist(argv); caa.caa_filelist = argv; caa.caa_excllist = excludelist; caa.caa_matchmode = ct_match_mode; caa.caa_includelist = includelist; caa.caa_tdir = ct_tdir; caa.caa_tag = ctfile; caa.caa_ctfile_basedir = conf->ct_ctfile_cachedir; /* we want to encrypt as long as we have keys */ caa.caa_no_cross_mounts = no_cross_mounts; caa.caa_strip_slash = strip_slash; caa.caa_follow_root_symlink = follow_root_symlink; caa.caa_follow_symlinks = follow_symlinks; caa.caa_max_incrementals = conf->ct_max_incrementals; if (conf->ct_auto_incremental) /* * Need to work out basis filename and * download it if necessary */ ctfile_find_for_operation(state, ctfile, ctfile_nextop_archive, &caa, 0, 1); else { /* No basis, just start the op */ ctfile_nextop_archive(state, NULL, &caa); } break; default: CWARNX("invalid action"); ct_usage(); /* NOTREACHED */ break; } } else if (ct_metadata != 0) { if (ct_action == CT_A_ARCHIVE || ct_action == CT_A_EXTRACT) { cca.cca_localname = ctfile; cca.cca_remotename = NULL; cca.cca_tdir = ct_tdir; cca.cca_cleartext = 0; cca.cca_ctfile = 1; /* only matters for archive */ ct_add_operation(state, ((ct_action == CT_A_ARCHIVE) ? ctfile_archive : ctfile_extract), ctfile_op_cleanup, &cca); } else if (ct_action == CT_A_ERASE) { if (ctfile != NULL) CFATALX("-f is not permitted with -me operation"); if (argc == 0) CFATALX("no files specified"); ccda.ccda_pattern = argv; ccda.ccda_matchmode = ct_match_mode; ccda.ccda_callback = ct_print_delete; ct_add_operation(state, ctfile_list_start, ctfile_process_delete, &ccda); } else if (ct_action == CT_A_LIST) { ccla.ccla_search = includelist; ccla.ccla_exclude = excludelist; ccla.ccla_matchmode = ct_match_mode; ct_add_operation(state, ctfile_list_start, ctfile_list_print, &ccla); } else { CWARNX("must specify action"); ct_usage(); /* NOTREACHED */ } } else { /* list is handled above */ if (ct_action == CT_A_ARCHIVE) { caa.caa_local_ctfile = ctfile; ct_normalize_filelist(argv); caa.caa_filelist = argv; caa.caa_excllist = excludelist; caa.caa_matchmode = ct_match_mode; caa.caa_includelist = includelist; caa.caa_tdir = ct_tdir; caa.caa_tag = ctfile; caa.caa_ctfile_basedir = NULL; /* we want to encrypt as long as we have keys */ caa.caa_no_cross_mounts = no_cross_mounts; caa.caa_strip_slash = strip_slash; caa.caa_follow_root_symlink = follow_root_symlink; caa.caa_follow_symlinks = follow_symlinks; caa.caa_max_incrementals = 0; /* unlimited */ caa.caa_basis = ct_basisbackup; ct_add_operation(state, ct_archive, NULL, &caa); } else if (ct_action == CT_A_EXTRACT) { cea.cea_local_ctfile = ctfile; cea.cea_filelist = includelist; cea.cea_excllist = excludelist; cea.cea_matchmode = ct_match_mode; cea.cea_ctfile_basedir = NULL; cea.cea_tdir = ct_tdir; cea.cea_strip_slash = strip_slash; cea.cea_attr = attr; cea.cea_follow_symlinks = follow_symlinks; cea.cea_log_state = &ct_verbose; cea.cea_log_chown_failed = ct_print_extract_chown_failed; ct_add_operation(state, ct_extract, NULL, &cea); } else { CWARNX("must specify action"); ct_usage(); /* NOTREACHED */ } } ct_wakeup_file(state->event_state); if ((ret = ct_run_eventloop(state)) != 0) { if (state->ct_errmsg[0] != '\0') CWARNX("%s: %s", state->ct_errmsg, ct_strerror(ret)); else CWARNX("%s", ct_strerror(ret)); return (ret); } if (verbose_ratios) ct_dump_stats(state, stdout); ct_cleanup_login_cache(); ct_cleanup(state); out: if (includelist && freeincludes == 1) ct_matchlist_free(includelist); if (excludelist) ct_matchlist_free(excludelist); if (conf->ct_ctfile_mode == CT_MDMODE_REMOTE && ct_metadata == 0) ctfile_trim_cache(conf->ct_ctfile_cachedir, conf->ct_ctfile_max_cachesize); ct_unload_config(config_file, conf); #if CT_CHECK_MEMORY e_check_memory(); #endif exude_cleanup(); return (ret); }
int ct_list(const char *file, char **flist, char **excludelist, int match_mode, const char *ctfile_basedir, int strip_slash, int verbose) { struct ct_extract_state *ces; struct ctfile_parse_state xs_ctx; struct fnode fnodestore; uint64_t reduction; struct fnode *fnode = &fnodestore; struct ct_match *match, *ex_match = NULL; char *ct_next_filename; char *sign; int state; int doprint = 0; int ret; int s_errno = 0, ct_errno = 0; char shat[SHA_DIGEST_STRING_LENGTH]; char cshat[SHA_DIGEST_STRING_LENGTH]; char iv[CT_IV_LEN*2+1]; if ((ret = ct_file_extract_init(&ces, NULL, 1, 1, 0, NULL, NULL)) != 0) CFATALX("failed to initialise extract state: %s", ct_strerror(ret)); if ((ret = ct_match_compile(&match, match_mode, flist)) != 0) CFATALX("failed to compile match pattern: %s", ct_strerror(ret)); if (excludelist != NULL && (ret = ct_match_compile(&ex_match, match_mode, excludelist)) != 0) CFATALX("failed to compile exclude pattern: %s", ct_strerror(ret)); verbose++; /* by default print something. */ ct_next_filename = NULL; next_file: ret = ctfile_parse_init(&xs_ctx, file, ctfile_basedir); if (ret) CFATALX("failed to open %s: %s", file, ct_strerror(ret)); ct_print_ctfile_info(&verbose, file, &xs_ctx.xs_gh); if (ct_next_filename) e_free(&ct_next_filename); if (xs_ctx.xs_gh.cmg_prevlvl_filename) { CNDBG(CT_LOG_CTFILE, "previous backup file %s\n", xs_ctx.xs_gh.cmg_prevlvl_filename); ct_next_filename = e_strdup(xs_ctx.xs_gh.cmg_prevlvl_filename); } bzero(&fnodestore, sizeof(fnodestore)); do { ret = ctfile_parse(&xs_ctx); switch (ret) { case XS_RET_FILE: ct_populate_fnode(ces, &xs_ctx, fnode, &state, xs_ctx.xs_gh.cmg_flags & CT_MD_MLB_ALLFILES, strip_slash); doprint = !ct_match(match, fnode->fn_fullname); if (doprint && ex_match != NULL && !ct_match(ex_match, fnode->fn_fullname)) doprint = 0; if (doprint) { ct_pr_fmt_file(&verbose, fnode); if (!C_ISREG(xs_ctx.xs_hdr.cmh_type) || verbose > 2) printf("\n"); } if (fnode->fn_hlname) e_free(&fnode->fn_hlname); if (fnode->fn_fullname) e_free(&fnode->fn_fullname); break; case XS_RET_FILE_END: sign = " "; if (xs_ctx.xs_trl.cmt_comp_size == 0) reduction = 100; else { uint64_t orig, comp; orig = xs_ctx.xs_trl.cmt_orig_size; comp = xs_ctx.xs_trl.cmt_comp_size; if (comp <= orig) { reduction = 100 * (orig - comp) / orig; } else { reduction = 100 * (comp - orig) / orig; if (reduction != 0) sign = "-"; } } if (doprint && verbose > 1) printf(" sz: %" PRIu64 " shas: %" PRIu64 " reduction: %s%" PRIu64 "%%\n", xs_ctx.xs_trl.cmt_orig_size, xs_ctx.xs_hdr.cmh_nr_shas, sign, reduction); else if (doprint) printf("\n"); break; case XS_RET_SHA: if (!(doprint && verbose > 2)) { if (ctfile_parse_seek(&xs_ctx)) { CFATALX("seek failed"); } } else { int i; ct_sha1_encode(xs_ctx.xs_sha, shat); switch (xs_ctx.xs_gh.cmg_flags & CT_MD_CRYPTO) { case 0: printf(" sha %s\n", shat); break; case CT_MD_CRYPTO: ct_sha1_encode(xs_ctx.xs_csha, cshat); for (i = 0; i < CT_IV_LEN; i++) snprintf(&iv[i * 2], 3, "%02x", xs_ctx.xs_iv[i]); printf(" sha %s csha %s iv %s\n", shat, cshat, iv); } } break; case XS_RET_EOF: break; case XS_RET_FAIL: s_errno = errno; ct_errno = xs_ctx.xs_errno; ; } } while (ret != XS_RET_EOF && ret != XS_RET_FAIL); ctfile_parse_close(&xs_ctx); if (ret != XS_RET_EOF) { errno = s_errno; CWARNX("corrupt ctfile: %s", ct_strerror(ct_errno)); } else { if (ct_next_filename) { file = ct_next_filename; goto next_file; } } ct_match_unwind(match); ct_file_extract_cleanup(ces); return (0); }
int ct_assl_negotiate_poll(struct ct_global_state *state) { void *body; struct ct_header hdr; ssize_t sz; int rv = 1; int payload_sz; uint8_t buf[20]; /* send server request */ if ((rv = ct_create_neg(&hdr, &body, state->ct_max_trans, state->ct_max_block_size)) != 0) goto done; payload_sz = hdr.c_size; ct_wire_header(&hdr); if (ct_assl_io_write_poll(state->ct_assl_ctx, &hdr, sizeof hdr, ASSL_TIMEOUT) != sizeof hdr) { rv = CTE_SHORT_WRITE; goto done; } if (ct_assl_io_write_poll(state->ct_assl_ctx, body, payload_sz, ASSL_TIMEOUT) != payload_sz) { rv = CTE_SHORT_WRITE; goto done; } /* get server reply */ sz = ct_assl_io_read_poll(state->ct_assl_ctx, &hdr, sizeof hdr, ASSL_TIMEOUT); if (sz != sizeof hdr) { rv = CTE_SHORT_READ; CWARNX("invalid header size %ld", (long) sz); goto done; } ct_unwire_header(&hdr); /* negotiate reply is the same size as the request, so reuse the body */ if (hdr.c_size != payload_sz) { rv = CTE_INVALID_REPLY_LEN; CWARNX("invalid negotiate reply size %d", hdr.c_size); goto done; } if (ct_assl_io_read_poll(state->ct_assl_ctx, buf, hdr.c_size, ASSL_TIMEOUT) != hdr.c_size) { CWARNX("couldn't read neg parameters"); rv = CTE_SHORT_READ; goto done; } if ((rv = ct_parse_neg_reply(&hdr, buf, &state->ct_max_trans, &state->ct_max_block_size)) != 0) { goto done; } e_free(&body); CNDBG(CT_LOG_NET, "negotiated queue depth: %u max chunk size: %u", state->ct_max_trans, state->ct_max_block_size); if ((rv = ct_create_login(&hdr, &body, state->ct_config->ct_username, state->ct_config->ct_password)) != 0) goto done; payload_sz = hdr.c_size; ct_wire_header(&hdr); if (ct_assl_io_write_poll(state->ct_assl_ctx, &hdr, sizeof hdr, ASSL_TIMEOUT) != sizeof hdr) { rv = CTE_SHORT_WRITE; goto done; } if (ct_assl_io_write_poll(state->ct_assl_ctx, body, payload_sz, ASSL_TIMEOUT) != payload_sz) { rv = CTE_SHORT_WRITE; goto done; } /* get server reply */ sz = ct_assl_io_read_poll(state->ct_assl_ctx, &hdr, sizeof hdr, ASSL_TIMEOUT); if (sz != sizeof hdr) { rv = CTE_SHORT_READ; goto done; } e_free(&body); ct_unwire_header(&hdr); /* XXX need a way to get error crud out, right now the function warns for us. */ if ((rv = ct_parse_login_reply(&hdr, NULL)) != 0) goto done; if (ct_skip_xml_negotiate) { goto out; } if ((rv = ct_create_xml_negotiate(&hdr, &body, ctdb_get_genid(state->ct_db_state))) != 0) { goto done; } payload_sz = hdr.c_size; ct_wire_header(&hdr); if (ct_assl_io_write_poll(state->ct_assl_ctx, &hdr, sizeof hdr, ASSL_TIMEOUT) != sizeof hdr) { rv = CTE_SHORT_WRITE; goto done; } if (ct_assl_io_write_poll(state->ct_assl_ctx, body, payload_sz, ASSL_TIMEOUT) != payload_sz) { rv = CTE_SHORT_WRITE; CWARNX("could not write body"); goto done; } e_free(&body); /* get server reply */ sz = ct_assl_io_read_poll(state->ct_assl_ctx, &hdr, sizeof hdr, ASSL_TIMEOUT); if (sz != sizeof hdr) { rv = CTE_SHORT_READ; CWARNX("invalid header size %" PRId64, (int64_t)sz); goto done; } ct_unwire_header(&hdr); if (hdr.c_size == 0) { goto out; } /* get server reply body */ body = e_calloc(1, hdr.c_size); sz = ct_assl_io_read_poll(state->ct_assl_ctx, body, hdr.c_size, ASSL_TIMEOUT); if (sz != hdr.c_size) { rv = CTE_SHORT_READ; goto done; } /* XXX check xml data */ if ((rv = ct_parse_xml_negotiate_reply(&hdr, body, state->ct_db_state)) != 0) { e_free(&body); goto done; } e_free(&body); out: CNDBG(CT_LOG_NET, "login successful"); rv = 0; done: return (rv); }
int ctfile_nextop_archive(struct ct_global_state *state, char *basis, void *args) { struct ct_archive_args *caa = args; struct ct_ctfileop_args *cca; char *ctfile; char buf[TIMEDATA_LEN], *fullname, *cachename; time_t now; CNDBG(CT_LOG_CTFILE, "setting basisname %s", basis ? basis : "<none>"); caa->caa_basis = basis; /* * We now have the basis found for us, cook and prepare the tag * we wish to create then add the operation. */ if ((ctfile = ctfile_cook_name(caa->caa_tag)) == NULL) { CWARNX("%s: %s", caa->caa_tag, ct_strerror(CTE_INVALID_CTFILE_NAME)); return (CTE_INVALID_CTFILE_NAME); } if (ctfile_is_fullname(ctfile) != 0) { CWARNX("%s", ct_strerror(CTE_ARCHIVE_FULLNAME)); e_free(&ctfile); return (CTE_ARCHIVE_FULLNAME); } now = time(NULL); if (strftime(buf, TIMEDATA_LEN, "%Y%m%d-%H%M%S", localtime(&now)) == 0) CABORTX("can't format time"); e_asprintf(&fullname, "%s-%s", buf, ctfile); CNDBG(CT_LOG_CTFILE, "backup file is %s", fullname); /* check it isn't already in the cache */ cachename = ctfile_get_cachename(fullname, state->ct_config->ct_ctfile_cachedir); if (ctfile_in_cache(fullname, state->ct_config->ct_ctfile_cachedir)) { CWARNX("%s: %s", fullname, ct_strerror(CTE_BACKUP_ALREADY_EXISTS)); e_free(&ctfile); e_free(&fullname); e_free(&cachename); return (CTE_BACKUP_ALREADY_EXISTS); } e_free(&ctfile); e_free(&fullname); caa->caa_local_ctfile = cachename; ct_add_operation(state, ct_archive, NULL, caa); /* * set up an additional operation to upload the newly created * ctfile after the archive is completed. */ cca = e_calloc(1, sizeof(*cca)); cca->cca_localname = cachename; cca->cca_cleartext = 0; cca->cca_ctfile = 1; ct_add_operation(state, ctfile_archive, ctfile_nextop_archive_cleanup, cca); return (0); }
/* * Download all dependent ctfiles of the current ctfile. * (called repeatedly until all are fetched). */ int ctfile_download_next(struct ct_global_state *state, struct ct_op *op) { struct ct_ctfileop_args *cca = op->op_args, *nextcca; const char *ctfile = cca->cca_localname; const char *rfile = cca->cca_remotename; char *prevfile; char *cookedname; int ret = 0; again: CNDBG(CT_LOG_CTFILE, "ctfile %s", ctfile); if ((ret = ctfile_get_previous(ctfile, cca->cca_tdir, &prevfile)) != 0) { CWARNX("can not get previous filename for %s", ctfile); /* error output will happen when even loop returns */ goto out; } if (prevfile == NULL) /* done with this chain */ goto out; if (prevfile[0] != '\0') { if ((cookedname = ctfile_cook_name(prevfile)) == NULL) { CWARNX("%s: %s", prevfile, ct_strerror(CTE_INVALID_CTFILE_NAME)); ret = CTE_INVALID_CTFILE_NAME; e_free(&prevfile); goto out; } CNDBG(CT_LOG_CTFILE, "prev file %s cookedname %s", prevfile, cookedname); if (!ctfile_in_cache(cookedname, cca->cca_tdir)) { nextcca = e_calloc(1, sizeof(*nextcca)); nextcca->cca_localname = cookedname; nextcca->cca_remotename = e_strdup(cookedname); nextcca->cca_tdir = cca->cca_tdir; nextcca->cca_ctfile = 1; ct_add_operation_after(state, op, ctfile_extract, ctfile_download_next, nextcca); } else { if (ctfile) e_free(&ctfile); if (rfile) e_free(&rfile); e_free(&cookedname); ctfile = prevfile; goto again; } } else e_free(&prevfile); out: if (ctfile) e_free(&ctfile); if (rfile) e_free(&rfile); e_free(&cca); return (ret); }
/* * List has completed. * * Select the best filename for download, and download it if missing. */ int ctfile_find_for_extract_complete(struct ct_global_state *state, struct ct_op *op) { struct ct_ctfile_find_args *ccfa = op->op_args; struct ct_ctfile_find_fileop_args *ccffa; struct ct_op *list_fakeop = op->op_priv; struct ct_ctfile_list_args *ccla = list_fakeop->op_args; struct ctfile_list_tree result; struct ctfile_list_file *tmp; char *best = NULL; int ret = 0; RB_INIT(&result); ctfile_list_complete(&state->ctfile_list_files, ccla->ccla_matchmode, ccla->ccla_search, ccla->ccla_exclude, &result); e_free(ccla->ccla_search); e_free(&ccla->ccla_search); e_free(&ccla); e_free(&list_fakeop); /* * Prepare arguments for next operation. * either we'll download the next file, or skip straight to * the callback for after the download, either way we need the nextop */ ccffa = e_calloc(1, sizeof(*ccffa)); ccffa->ccffa_nextop = ccfa->ccfa_nextop; ccffa->ccffa_nextop_args = ccfa->ccfa_nextop_args; ccffa->ccffa_download_chain = ccfa->ccfa_download_chain; /* grab the newest one */ if ((tmp = RB_MAX(ctfile_list_tree, &result)) == NULL) { if (ccfa->ccfa_empty_ok) goto do_operation; else { CWARNX("%s: %s", ccfa->ccfa_tag, ct_strerror(CTE_NO_SUCH_BACKUP)); ret = CTE_NO_SUCH_BACKUP; e_free(&ccffa); goto out; } } /* pick the newest one */ best = e_strdup(tmp->mlf_name); CNDBG(CT_LOG_CTFILE, "backup file is %s", best); while ((tmp = RB_ROOT(&result)) != NULL) { RB_REMOVE(ctfile_list_tree, &result, tmp); e_free(&tmp); } /* * if the metadata file is not in the cache directory then we * need to download it first. if we need to recursively download * an incremental chain then that code will handle scheduling * those operations too. If we have it, we still need to check * that all others in the chain exist, however. */ if (!ctfile_in_cache(best, ccfa->ccfa_cachedir)) { ccffa->ccffa_base.cca_localname = best; ccffa->ccffa_base.cca_tdir = ccfa->ccfa_cachedir; ccffa->ccffa_base.cca_remotename = e_strdup(best); ccffa->ccffa_base.cca_ctfile = 1; ct_add_operation(state, ctfile_extract, ctfile_extract_nextop, ccffa); } else { do_operation: /* * No download needed, fake the next operation callback * to see if we need anymore. */ ccffa->ccffa_base.cca_localname = best; ccffa->ccffa_base.cca_tdir = ccfa->ccfa_cachedir; ccffa->ccffa_base.cca_ctfile = 1; op->op_args = ccffa; ctfile_extract_nextop(state, op); } out: e_free(&ccfa); return (ret); }