static void ct_version_tree_free_version(struct ct_vertree_ver *entry) { struct ct_vertree_dir *dir; struct ct_vertree_spec *spec; struct ct_vertree_link *flink; struct ct_vertree_file *file; if (C_ISDIR(entry->cvv_type)) { dir = (struct ct_vertree_dir *)entry; e_free(&dir); } else if (C_ISBLK(entry->cvv_type) || C_ISCHR(entry->cvv_type)) { spec = (struct ct_vertree_spec *)entry; e_free(&spec); } else if (C_ISLINK(entry->cvv_type)) { flink = (struct ct_vertree_link *)entry; if (flink->cvl_linkname != NULL) e_free(&flink->cvl_linkname); e_free(&flink); } else if (C_ISREG(entry->cvv_type)) { file = (struct ct_vertree_file *)entry; e_free(&file); } return; }
/* * So that we can provide correct statistics we have to go through all ctfiles * being extracted and sum the sizes to be extracted. This is kinda expensive, * but not really avoidable if we want to provide the statistics. * * Failure means we have called ct fatal. */ int ct_extract_calculate_total(struct ct_global_state *state, struct ct_extract_args *cea, struct ct_match *inc_match, struct ct_match *ex_match) { struct ct_extract_head extract_head; struct ctfile_parse_state xdr_ctx; struct ct_match *rb_match = NULL; struct fnode *fnode; int allfiles; int fillrb = 0, haverb = 0; int doextract = 0; int tr_state; int ret; int retval = 1; TAILQ_INIT(&extract_head); if ((ret = ct_extract_setup(&extract_head, &xdr_ctx, cea->cea_local_ctfile, cea->cea_ctfile_basedir, &allfiles)) != 0) { ct_fatal(state, "can't setup extract queue", ret); goto done; } if (allfiles) { char *nothing = NULL; if ((ret = ct_match_compile(&rb_match, CT_MATCH_RB, ¬hing)) != 0) { ct_fatal(state, "Couldn't create match tree", ret); goto done; } fillrb = 1; } while (1) { switch ((ret = ctfile_parse(&xdr_ctx))) { case XS_RET_FILE: if (fillrb == 0 && xdr_ctx.xs_hdr.cmh_nr_shas == -1) { continue; } fnode = ct_alloc_fnode(); /* XXX need the fnode for the correct paths */ ct_populate_fnode(state->extract_state, &xdr_ctx, fnode, &tr_state, allfiles, cea->cea_strip_slash); /* we don't care about individual shas */ if (C_ISREG(fnode->fn_type)) { ctfile_parse_seek(&xdr_ctx); } doextract = !ct_match(inc_match, fnode->fn_fullname); if (doextract && ex_match != NULL && !ct_match(ex_match, fnode->fn_fullname)) doextract = 0; /* * 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 (doextract == 1 && fillrb && xdr_ctx.xs_hdr.cmh_nr_shas == -1) { ct_match_insert_rb(rb_match, fnode->fn_fullname); doextract = 0; } ct_free_fnode(fnode); break; case XS_RET_FILE_END: if (doextract == 0) continue; /* update statistics */ state->ct_stats->st_bytes_tot += xdr_ctx.xs_trl.cmt_orig_size; break; case XS_RET_EOF: ctfile_parse_close(&xdr_ctx); /* if rb tree and rb is empty, goto end state */ if ((haverb && ct_match_rb_is_empty(inc_match)) || (fillrb && ct_match_rb_is_empty(rb_match))) { retval = 0; goto done; } if (!TAILQ_EMPTY(&extract_head)) { /* * if allfiles and this was the first pass. * free the current match lists * switch to rb tree mode */ if (fillrb) { ex_match = NULL; inc_match = rb_match; rb_match = NULL; haverb = 1; fillrb = 0; } /* reinits xdr_ctx */ if ((ret = ct_extract_open_next(&extract_head, &xdr_ctx)) != 0) { ct_fatal(state, "Can't open next ctfile", ret); goto done; } continue; } retval = 0; goto done; break; case XS_RET_FAIL: ct_fatal(state, "Failed to parse ctfile", xdr_ctx.xs_errno); goto done; break; } } done: /* empty unless we quit early */ ct_extract_cleanup_queue(&extract_head); /* only have control of the rb tree we made */ if (haverb) ct_match_unwind(inc_match); if (rb_match != NULL) ct_match_unwind(rb_match); return (retval); }
/* * Main guts of ctd_build_version_tree. Factored out to avoid deep nesting. * Insert or update an entry in the tree with the information received from * the ctfile. */ static int ct_vertree_add(struct ct_vertree_dnode_cache *dnode_cache, struct ct_vertree_entry *head, struct ctfile_parse_state *parse_state, struct ct_vertree_ctfile *ctfile, off_t fileoffset, int allfiles) { struct ctfile_header *hdr = &parse_state->xs_hdr; struct ctfile_header *hdrlnk= &parse_state->xs_lnkhdr; struct dnode *dnode; struct ct_vertree_dnode *fb_dnode; struct ct_vertree_entry *parent = NULL, sentry, *entry; struct ct_vertree_ver *lastver, *ver; struct ct_vertree_file *file; struct ct_vertree_spec *spec; struct ct_vertree_link *linkver; size_t sz; bool root_dnode = false; entry = NULL; /* First find parent directory if any */ if (hdr->cmh_parent_dir != -1 && hdr->cmh_parent_dir != -2) { if ((dnode = ctfile_parse_finddir(parse_state, hdr->cmh_parent_dir)) == NULL) { CNDBG(CT_LOG_VERTREE, "can't find dir %" PRId64, hdr->cmh_parent_dir); return (CTE_CTFILE_CORRUPT); } fb_dnode = (struct ct_vertree_dnode *)dnode; if (fb_dnode == dnode_cache->root_dnode) { // If we have the root dnode, store in head. parent = head; } else { parent = fb_dnode->cvd_dir; } } else { parent = head; } if (parent == head && strcmp(hdr->cmh_filename, CT_PATHSEP_STR) == 0) { root_dnode = true; } /* * Have parent node, search children to see if we already exist. * Else make a new one and insert. */ sentry.cve_name = hdr->cmh_filename; if ((entry = RB_FIND(ct_vertree_entries, &parent->cve_children, &sentry)) == NULL) { /* new name, insert node */ entry = e_calloc(1, sizeof(*entry)); TAILQ_INIT(&entry->cve_versions); RB_INIT(&entry->cve_children); entry->cve_parent = parent; entry->cve_name = e_strdup(sentry.cve_name); /* don't insert root dnodes, just do dnode dance */ if (root_dnode) { goto rootdir; } if (RB_INSERT(ct_vertree_entries, &parent->cve_children, entry) != NULL) { CNDBG(CT_LOG_VERTREE, "entry %s already exists", sentry.cve_name); e_free(&sentry.cve_name); goto err; } } /* * then check version tags -> head/tail if mtime and type match, we're * good else prepare version entry. */ if (allfiles) { lastver = TAILQ_FIRST(&entry->cve_versions); } else { lastver = TAILQ_LAST(&entry->cve_versions, ct_vertree_vers); } /* Don't check atime, it doesn't matter */ if (lastver != NULL && lastver->cvv_type == hdr->cmh_type && lastver->cvv_mtime == hdr->cmh_mtime && lastver->cvv_uid == hdr->cmh_uid && lastver->cvv_gid == hdr->cmh_gid && lastver->cvv_mode == hdr->cmh_mode) { ver = lastver; } else { /* something changed. make a new one */ if (C_ISDIR(hdr->cmh_type)) { sz = sizeof(struct ct_vertree_dir); } else if (C_ISBLK(hdr->cmh_type) || C_ISCHR(hdr->cmh_type)) { sz = sizeof(struct ct_vertree_spec); } else if (C_ISLINK(hdr->cmh_type)) { sz = sizeof(struct ct_vertree_link); } else if (C_ISREG(hdr->cmh_type)) { sz = sizeof(struct ct_vertree_file); } else { CNDBG(CT_LOG_VERTREE, "invalid type %d", hdr->cmh_type); goto err; } ver = e_calloc(1, sz); ver->cvv_type = hdr->cmh_type; ver->cvv_mtime = hdr->cmh_mtime; ver->cvv_atime = hdr->cmh_atime; ver->cvv_uid = hdr->cmh_uid; ver->cvv_gid = hdr->cmh_gid; ver->cvv_mode = hdr->cmh_mode; /* dir handled below */ if (C_ISBLK(hdr->cmh_type) || C_ISCHR(hdr->cmh_type)) { spec = (struct ct_vertree_spec *)ver; spec->cvs_rdev = hdr->cmh_rdev; } else if (C_ISLINK(hdr->cmh_type)) { /* hardlink/symlink */ linkver = (struct ct_vertree_link *)ver; linkver->cvl_linkname = e_strdup(hdrlnk->cmh_filename); linkver->cvl_hardlink = !C_ISLINK(hdrlnk->cmh_type); } else if (C_ISREG(hdr->cmh_type)) { file = (struct ct_vertree_file *)ver; file->cvf_nr_shas = -1; } if (allfiles) { TAILQ_INSERT_HEAD(&entry->cve_versions, ver, cvv_link); } else { TAILQ_INSERT_TAIL(&entry->cve_versions, ver, cvv_link); } } /* * Each ctfile only has each directory referenced once, so put it * in the cache regardless of whether it was known of before, that * will be a previous run and the cache will have been wiped since * then. */ if (C_ISDIR(hdr->cmh_type)) { rootdir: fb_dnode = e_calloc(1, sizeof(*fb_dnode)); fb_dnode->cvd_dnode.d_name = e_strdup(entry->cve_name); /* * in the root_dnode case this will be a bad pointer but it * will never be derefed. */ fb_dnode->cvd_dir = entry; if ((dnode = ctfile_parse_insertdir(parse_state, &fb_dnode->cvd_dnode)) != NULL) CABORTX("duplicate dentry"); TAILQ_INSERT_TAIL(&dnode_cache->cache, fb_dnode, cvd_link); if (root_dnode) { dnode_cache->root_dnode = fb_dnode; } } else if (C_ISREG(hdr->cmh_type)) { /* * Allfiles ctfiles may have shas == -1, so in some cases we * may wish to update an existing file when we find the actual * shas. It is an error to have a file node with -1 for shas * after all metadata have been parsed. it means one was * missing. */ file = (struct ct_vertree_file *)ver; /* * bugs in previous editions with incremental selection and * off_t on linux mean that there are ctfiles in the wild which * provide a list of shas in a later level when the file is * defined in an earlier level file, also. For example for the * same filename and date we have level 0: 3 shas, level 1: -1 * shas (i.e. in a previous level), level 2: 3 shas (same as * level * 0). In that case we just assume that if we already * have sha data for a file * then it is correct and we skip * previous versions. */ if (file->cvf_nr_shas != -1) { goto out; } /* * previous linux off_t bugs with files over 2gb mean that there * are sign extended ctfiles in the wild, so we count those as * zero length for purposes of the version tree. */ if (hdr->cmh_nr_shas < -1) { hdr->cmh_nr_shas = 0; } if (hdr->cmh_nr_shas != -1) { file->cvf_nr_shas = hdr->cmh_nr_shas; file->cvf_sha_offs = fileoffset; file->cvf_file = ctfile; if (ctfile_parse_seek(parse_state)) { CNDBG(CT_LOG_VERTREE, "failed to skip shas in %s", ctfile->cvc_path); goto err; } } if (ctfile_parse(parse_state) != XS_RET_FILE_END) { CNDBG(CT_LOG_VERTREE, "no file trailer found"); goto err; } file->cvf_file_size = parse_state->xs_trl.cmt_orig_size; } out: /* * If we're an explicit "/" entry then we don't want to be added to * the tree. all our children will be added to the root entry. */ if (root_dnode) { e_free(&entry); } return (0); err: if (entry != NULL) e_free(&entry); return (CTE_CTFILE_CORRUPT); }
/* Printing functions */ void ct_pr_fmt_file(void *state, struct fnode *fnode) { int *verbose = state; char *loginname; struct group *group; char *link_ty, *pchr; char filemode[11], uid[11], gid[11], lctime[26]; time_t ltime; if (*verbose == 0) return; if (*verbose > 1) { switch(fnode->fn_type & C_TY_MASK) { case C_TY_DIR: filemode[0] = 'd'; break; case C_TY_CHR: filemode[0] = 'c'; break; case C_TY_BLK: filemode[0] = 'b'; break; case C_TY_REG: filemode[0] = '-'; break; case C_TY_FIFO: filemode[0] = 'f'; break; case C_TY_LINK: filemode[0] = 'l'; break; case C_TY_SOCK: filemode[0] = 's'; break; default: filemode[0] = '?'; } filemode[1] = (fnode->fn_mode & 0400) ? 'r' : '-'; filemode[2] = (fnode->fn_mode & 0100) ? 'w' : '-'; filemode[3] = (fnode->fn_mode & 0200) ? 'x' : '-'; filemode[4] = (fnode->fn_mode & 0040) ? 'r' : '-'; filemode[5] = (fnode->fn_mode & 0020) ? 'w' : '-'; filemode[6] = (fnode->fn_mode & 0010) ? 'x' : '-'; filemode[7] = (fnode->fn_mode & 0004) ? 'r' : '-'; filemode[8] = (fnode->fn_mode & 0002) ? 'w' : '-'; filemode[9] = (fnode->fn_mode & 0001) ? 'x' : '-'; filemode[10] = '\0'; loginname = ct_getloginbyuid(fnode->fn_uid); if (loginname && (strlen(loginname) < sizeof(uid))) snprintf(uid, sizeof(uid), "%10s", loginname); else snprintf(uid, sizeof(uid), "%-10d", fnode->fn_uid); group = getgrgid(fnode->fn_gid); if (group && (strlen(group->gr_name) < sizeof(gid))) snprintf(gid, sizeof(gid), "%10s", group->gr_name); else snprintf(gid, sizeof(gid), "%-10d", fnode->fn_gid); ltime = fnode->fn_mtime; ctime_r(<ime, lctime); pchr = strchr(lctime, '\n'); if (pchr != NULL) *pchr = '\0'; /* stupid newline on ctime */ printf("%s %s %s %s ", filemode, uid, gid, lctime); } if (fnode->fn_skip_file) { if (*verbose > 1) printf("%s does not need rearchive", fnode->fn_fullname); } else { printf("%s", fnode->fn_fullname); } if (*verbose > 1) { /* XXX - translate to guid name */ if (C_ISLINK(fnode->fn_type)) { if (fnode->fn_hardlink) { link_ty = "=="; } else { link_ty = "->"; } printf(" %s %s", link_ty, fnode->fn_hlname); } else if (C_ISREG(fnode->fn_type)) { } } fflush(stdout); }
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); }