/* inode-sig.c: * **************************************************************** * Copyright (C) 2003 Tom Lord * * See the file "COPYING" for further information about * the copyright and warranty status of this work. */ #include "hackerlab/bugs/panic.h" #include "hackerlab/mem/mem.h" #include "hackerlab/char/str.h" #include "hackerlab/fs/file-names.h" #include "hackerlab/vu/safe.h" #include "libfsutils/ensure-dir.h" #include "libfsutils/dir-listing.h" #include "libfsutils/string-files.h" #include "libawk/relational.h" #include "libawk/relassoc.h" #include "libarch/arch.h" #include "libarch/debug.h" #include "libarch/namespace.h" #include "libarch/invent.h" #include "libarch/inode-sig.h" #define MAX_INODE_SIG_FILES 5 static t_uchar const * INODE_IDS = "{arch}/,,inode-sigs"; static t_uchar const * INODE_PATHS = "{arch}/,,inode-sig-paths"; /* __STDC__ prototypes for static functions */ static void arch_update_inode_sig (rel_table sig, assoc_table newsig, assoc_table id_list); static void inode_sig_callback (void * closure, invent_callback_data_t const * const data); static void inode_sig_callback_not_reference (void * closure, invent_callback_data_t const * const data); static void arch_update_inode_sig (rel_table sig, assoc_table newsig, assoc_table id_list); static t_uchar * arch_inode_sig_dir (t_uchar const * const tree_root, t_uchar const * sig_type); static t_uchar * arch_inode_sig_file (t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision, t_uchar const * sig_type); static t_uchar * arch_inode_sig_tmp_file (t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision, t_uchar const * sig_type); static int arch_creat_inode_sig_file (t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision, t_uchar const * sig_type); static void arch_finish_inode_sig_file (t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision, t_uchar const * sig_type, int fd); static void arch_prune_inode_sig_dir (t_uchar const * const tree_root, t_uchar const * const current_inode_sig_file, t_uchar const * sig_type, int max_sigs); static void prune_old_ctime_cruft (rel_table the_table); static void prune_old_dev_cruft (rel_table the_table); static void arch_read_inode_sig (inode_sig * sig, t_uchar * tree_root, t_uchar * archive, t_uchar * revision); static void arch_read_inode_sig_paths (rel_table * as_table, assoc_table * as_assoc, t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision); static void arch_write_inode_sig_file (inode_sig sig, t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision); static int arch_inode_sig_equal (int current_result, rel_table existing_sig, rel_table current_sig, int chatter_fd); static void arch_snap_common (arch_project_tree_t * tree, t_uchar const * const archive, t_uchar const * const revision, inode_sig sig); static void arch_snap_common_files (arch_project_tree_t * tree, t_uchar * archive, t_uchar * prev_revision, t_uchar * revision, rel_table file_list, inode_sig sig); static inode_sig arch_tree_inode_sig_not_reference (arch_project_tree_t * tree); static inode_sig arch_tree_inode_sig_common (arch_project_tree_t * tree, void (*sig_callback )(void * closure, invent_callback_data_t const * const data)); t_uchar * arch_statb_inode_sig (struct stat * statb) { int fd = make_output_to_string_fd (); safe_printfmt (fd, "ino=%lu:mtime=%lu:size=%lu", (t_ulong)statb->st_ino, (t_ulong)statb->st_mtime, (t_ulong)statb->st_size); return string_fd_close (fd); } inode_sig arch_tree_inode_sig (arch_project_tree_t * tree) { return arch_tree_inode_sig_common (tree, inode_sig_callback); } inode_sig arch_tree_inode_sig_not_reference (arch_project_tree_t * tree) { return arch_tree_inode_sig_common (tree, inode_sig_callback_not_reference); } inode_sig arch_tree_inode_sig_common (arch_project_tree_t * tree, void (*sig_callback )(void * closure, invent_callback_data_t const * const data)) { struct arch_inventory_options options = {0, }; int here_fd; inode_sig answer; answer.ids = 0; answer.paths = 0; answer.start_time = time(NULL); if (answer.start_time == (time_t)-1) panic ("Could not get current time"); here_fd = safe_open (".", O_RDONLY, 0); safe_chdir (tree->root); options.categories = arch_inventory_source; options.want_ids = 1; options.include_excluded = 1; options.treat_unrecognized_source_as_source = 1; arch_get_inventory_naming_conventions (&options, tree); arch_project_tree_changeset_inventory_traversal (tree, &options, sig_callback, &answer); arch_free_inventory_naming_conventions (&options); rel_sort_table_by_field (0, answer.ids, 0); safe_fchdir (here_fd); safe_close (here_fd); return answer; } void arch_snap_inode_sig (arch_project_tree_t * tree, arch_patch_id * patch_id) { inode_sig sig = arch_tree_inode_sig (tree); arch_snap_common (tree, arch_patch_id_archive (patch_id), arch_patch_id_revision (patch_id), sig); } void arch_snap_inode_sig_not_reference (arch_project_tree_t * tree, t_uchar const * const archive, t_uchar const * const revision) { inode_sig sig = arch_tree_inode_sig_not_reference (tree); arch_snap_common (tree, archive, revision, sig); } void arch_snap_common (arch_project_tree_t * tree, t_uchar const * const archive, t_uchar const * const revision, inode_sig sig) { debug (dbg_invent, 6, "snapped inode sig for %s\n", tree->root); arch_write_inode_sig_file (sig, tree->root, archive, revision); arch_inode_sig_free (sig); } static void arch_write_inode_sig_file (inode_sig sig, t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision) { int fd; fd = arch_creat_inode_sig_file (tree_root, archive, revision, INODE_IDS); rel_print_pika_escape_iso8859_1_table (fd, arch_escape_classes, sig.ids); arch_finish_inode_sig_file (tree_root, archive, revision, INODE_IDS, fd); fd = arch_creat_inode_sig_file (tree_root, archive, revision, INODE_PATHS); rel_print_pika_escape_iso8859_1_table (fd, arch_escape_classes, sig.paths); arch_finish_inode_sig_file (tree_root, archive, revision, INODE_PATHS, fd); } void arch_snap_inode_sig_files (arch_project_tree_t * tree, t_uchar * archive, t_uchar * prev_revision, t_uchar * revision, rel_table file_list) { inode_sig newsig = arch_tree_inode_sig (tree); arch_snap_common_files (tree, archive, prev_revision, revision, file_list, newsig); } void arch_snap_inode_sig_not_reference_files (arch_project_tree_t * tree, t_uchar * archive, t_uchar * prev_revision, t_uchar * revision, rel_table file_list) { inode_sig newsig = arch_tree_inode_sig_not_reference (tree); arch_snap_common_files (tree, archive, prev_revision, revision, file_list, newsig); } void arch_snap_common_files (arch_project_tree_t * tree, t_uchar * archive, t_uchar * prev_revision, t_uchar * revision, rel_table file_list, inode_sig newsig) { inode_sig oldsig; assoc_table newsig_as; assoc_table newsig_paths; assoc_table id_list; arch_read_inode_sig (&oldsig, tree->root, archive, prev_revision); newsig_as = rel_to_assoc (newsig.ids, 0, 1); newsig_paths = rel_to_assoc (newsig.paths, 0, 1); id_list = arch_filenames_ids ( &file_list, tree); arch_update_inode_sig (oldsig.ids, newsig_as, id_list); arch_update_inode_sig (oldsig.paths, newsig_paths, id_list); arch_write_inode_sig_file (oldsig, tree->root, archive, revision); arch_inode_sig_free (oldsig); arch_inode_sig_free (newsig); free_assoc_table (newsig_as); free_assoc_table (newsig_paths); free_assoc_table (id_list); } static void arch_update_inode_sig (rel_table sig, assoc_table newsig, assoc_table id_list) { int sig_size = rel_n_records (sig); int i; for (i = 0; i != sig_size; ++ i) { if (assoc_ref (id_list, sig [i] [0] )) { t_uchar *vals = assoc_ref (newsig, sig [i] [0]); if (!vals) { /* stripped by not_reference logic to avoid * race conditions */ } else { rel_replace_record (sig, i, rel_make_record (sig [i] [0], vals, 0)); } } } } int arch_valid_inode_sig (arch_project_tree_t * tree, t_uchar const * const archive, t_uchar const * const revision) { int result = 1; inode_sig current_sig = arch_tree_inode_sig (tree); t_uchar *sig_file; sig_file = arch_inode_sig_file(tree->root, archive, revision, INODE_IDS); if (safe_access (sig_file, F_OK)) { safe_printfmt (2, "Missing inode signature. Cannot verify reference tree integrity: you should remove and recreate this reference tree.\n tree: %s\n archive: %s\n revision: %s\n", tree->root, archive, revision); } else { rel_table existing_sig = 0; arch_read_inode_sig_ids (&existing_sig, NULL, tree->root, archive, revision); result = arch_inode_sig_equal (result, existing_sig, current_sig.ids, 2); rel_free_table (existing_sig); } lim_free (0, sig_file); sig_file = arch_inode_sig_file(tree->root, archive, revision, INODE_PATHS); if (safe_access (sig_file, F_OK)) { /* don't spew warnings, most folk have perfectly ok revlibs. this is a * correctness problem that using baz will just address over time. */ } else { rel_table existing_paths = 0; arch_read_inode_sig_paths (&existing_paths, NULL, tree->root, archive, revision); result = arch_inode_sig_equal (result, existing_paths, current_sig.paths, 2); rel_free_table (existing_paths); } lim_free (0, sig_file); arch_inode_sig_free (current_sig); return result; } static int arch_inode_sig_equal (int current_result, rel_table existing_sig, rel_table current_sig, int chatter_fd) { /* Is there a convenience function (a rel_join magic foo?) for this */ if (rel_n_records (existing_sig) == rel_n_records (current_sig)) { int counter,inner; for (counter = 0; counter < rel_n_records (existing_sig); ++counter) for (inner = 0; inner < 2; ++inner) if (str_cmp(existing_sig[counter][inner],current_sig[counter][inner])) { current_result = 0; if (chatter_fd > -1) safe_printfmt (chatter_fd, "File signature changed: from '%s' to '%s'\n", existing_sig[counter][inner],current_sig[counter][inner]); } } else current_result = 0; return current_result; } static void arch_read_inode_sig (inode_sig * sig, t_uchar * tree_root, t_uchar * archive, t_uchar * revision) { arch_read_inode_sig_ids (&sig->ids, 0, tree_root, archive, revision); arch_read_inode_sig_paths (&sig->paths, 0, tree_root, archive, revision); } static void arch_read_inode_sig_paths (rel_table * as_table, assoc_table * as_assoc, t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision) { t_uchar * sig_file = arch_inode_sig_file (tree_root, archive, revision, INODE_PATHS); if (as_table) *as_table = 0; if (as_assoc) *as_assoc = 0; if (!safe_access (sig_file, F_OK)) { int fd = safe_open (sig_file, O_RDONLY, 0); rel_table the_table; the_table = rel_read_pika_unescape_iso8859_1_table (fd, 2, "arch", sig_file); if (as_assoc) *as_assoc = rel_to_assoc (the_table, 0, 1); if (as_table) *as_table = the_table; else rel_free_table (the_table); safe_close (fd); } lim_free (0, sig_file); } void arch_read_inode_sig_ids (rel_table * as_table, assoc_table * as_assoc, t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision) { t_uchar * sig_file = arch_inode_sig_file (tree_root, archive, revision, INODE_IDS); if (as_table) *as_table = 0; if (as_assoc) *as_assoc = 0; if (!safe_access (sig_file, F_OK)) { int fd = safe_open (sig_file, O_RDONLY, 0); rel_table the_table; the_table = rel_read_pika_unescape_iso8859_1_table (fd, 2, "arch", sig_file); prune_old_ctime_cruft (the_table); prune_old_dev_cruft (the_table); if (as_assoc) *as_assoc = rel_to_assoc (the_table, 0, 1); if (as_table) *as_table = the_table; else rel_free_table (the_table); safe_close (fd); } lim_free (0, sig_file); } void arch_read_id_shortcut (assoc_table * ids_shortcut, t_uchar * tree_root) { t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root, INODE_IDS); rel_table dir_listing = maybe_directory_files (inode_sig_dir); assoc_table answer = 0; assoc_table rejected = 0; int x, y; time_t newest_time = 0; rel_table sig_table = 0; for (x = 0; x < rel_n_records (dir_listing); ++x) { t_uchar * path = file_name_in_vicinity (0, inode_sig_dir, dir_listing[x][0]); t_uchar * pct = str_chr_index (dir_listing[x][0], '%'); if (pct && str_cmp (".", dir_listing[x][0]) && str_cmp ("..", dir_listing[x][0])) { t_uchar * maybe_archive = str_save_n (0, dir_listing[x][0], pct - dir_listing[x][0]); t_uchar * maybe_revision = str_save (0, pct + 1); if (arch_valid_archive_name (maybe_archive) && arch_valid_package_name (maybe_revision, arch_no_archive, arch_req_patch_level, 0)) { struct stat statb; safe_lstat (path, &statb); if (statb.st_mtime > newest_time) { newest_time = statb.st_mtime; rel_free_table (sig_table); debug (dbg_invent, 6, "reading inode sig for %s/%s in %s\n", maybe_archive, maybe_revision, tree_root); arch_read_inode_sig_ids (&sig_table, 0, tree_root, maybe_archive, maybe_revision); } } lim_free (0, maybe_archive); lim_free (0, maybe_revision); } lim_free (0, path); } for (y = 0; y < rel_n_records (sig_table); ++y) { t_uchar * this_id = sig_table[y][0]; t_uchar * this_sig = sig_table[y][1]; { t_uchar * is_rejected = assoc_ref (rejected, this_sig); t_uchar * already_have = assoc_ref (answer, this_sig); if (!is_rejected) { if (already_have) { if (!str_cmp (this_id, already_have)) { assoc_del (answer, this_sig); assoc_set (&rejected, this_sig, "yes"); } } else { assoc_set (&answer, this_sig, this_id); } } } } rel_free_table (sig_table); lim_free (0, inode_sig_dir); rel_free_table (dir_listing); free_assoc_table (rejected); *ids_shortcut = answer; } void inode_sig_callback (void * closure, invent_callback_data_t const * const data) { inode_sig * answer = (inode_sig *)closure; t_uchar * signature = arch_statb_inode_sig ((struct stat *)&data->stat_buf); if (!S_ISDIR (data->stat_buf.st_mode) && !S_ISLNK (data->stat_buf.st_mode)) { rel_add_records (&answer->ids, rel_make_record (data->id, signature, 0), 0); rel_add_records (&answer->paths, rel_make_record (data->id, data->path, 0), 0); } lim_free(0, signature); } void inode_sig_callback_not_reference (void * closure, invent_callback_data_t const * const data) { inode_sig * answer = (inode_sig *)closure; if (data->stat_buf.st_mtime < answer->start_time && data->stat_buf.st_atime < answer->start_time) inode_sig_callback (closure, data); } static t_uchar * arch_inode_sig_dir (t_uchar const * const tree_root, t_uchar const * sig_type) { /* FIXME: file_name_in_vicinity should be checked for const correctness */ return file_name_in_vicinity (0, tree_root, (t_uchar *)sig_type); } static t_uchar * arch_inode_sig_file (t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision, t_uchar const * sig_type) { t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root, sig_type); t_uchar * basename = str_alloc_cat_many (0, archive, "%", revision, str_end); t_uchar * answer = file_name_in_vicinity (0, inode_sig_dir, basename); lim_free (0, inode_sig_dir); lim_free (0, basename); return answer; } static t_uchar * arch_inode_sig_tmp_file (t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision, t_uchar const * sig_type) { t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root, sig_type); t_uchar * basename = str_alloc_cat_many (0, ",,", archive, "%", revision, str_end); t_uchar * answer = file_name_in_vicinity (0, inode_sig_dir, basename); lim_free (0, inode_sig_dir); lim_free (0, basename); return answer; } static int arch_creat_inode_sig_file (t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision, t_uchar const * sig_type) { t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root, sig_type); t_uchar * inode_sig_tmp_file = arch_inode_sig_tmp_file (tree_root, archive, revision, sig_type); int ign; int answer; ensure_directory_exists (inode_sig_dir); vu_unlink (&ign, inode_sig_tmp_file); answer = safe_open (inode_sig_tmp_file, (O_WRONLY | O_CREAT | O_EXCL), 0444); lim_free (0, inode_sig_dir); lim_free (0, inode_sig_tmp_file); return answer; } static void arch_finish_inode_sig_file (t_uchar const * const tree_root, t_uchar const * const archive, t_uchar const * const revision, t_uchar const * sig_type, int fd) { t_uchar * inode_sig_tmp_file = arch_inode_sig_tmp_file (tree_root, archive, revision, sig_type); t_uchar * inode_sig_file = arch_inode_sig_file (tree_root, archive, revision, sig_type); safe_close (fd); safe_rename (inode_sig_tmp_file, inode_sig_file); arch_prune_inode_sig_dir (tree_root, inode_sig_file, sig_type, MAX_INODE_SIG_FILES); lim_free (0, inode_sig_tmp_file); lim_free (0, inode_sig_file); } static void arch_prune_inode_sig_dir (t_uchar const * const tree_root, t_uchar const * const current_inode_sig_file, t_uchar const * sig_type, int max_sigs) { t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root, sig_type); rel_table dir_listing = maybe_directory_files (inode_sig_dir); int dead_one = -1; time_t oldest_time = 0; /* nothing is older to start with */ int n_inode_sig_files; int x; do { n_inode_sig_files = 0; for (x = 0; x < rel_n_records (dir_listing); ++x) { t_uchar * path = file_name_in_vicinity (0, inode_sig_dir, dir_listing[x][0]); t_uchar * pct = str_chr_index (dir_listing[x][0], '%'); if (pct && str_cmp (".", dir_listing[x][0]) && str_cmp ("..", dir_listing[x][0])) { t_uchar * maybe_archive = str_save_n (0, dir_listing[x][0], pct - dir_listing[x][0]); t_uchar * maybe_revision = str_save (0, pct + 1); if (arch_valid_archive_name (maybe_archive) && arch_valid_package_name (maybe_revision, arch_no_archive, arch_req_patch_level, 0)) { struct stat statb; ++n_inode_sig_files; safe_lstat (path, &statb); if (str_cmp (current_inode_sig_file, path) && ((dead_one < 0) || (statb.st_mtime < oldest_time))) { dead_one = x; oldest_time = statb.st_mtime; } } lim_free (0, maybe_archive); lim_free (0, maybe_revision); } lim_free (0, path); } if (n_inode_sig_files > max_sigs) { t_uchar * dead_path = file_name_in_vicinity (0, inode_sig_dir, dir_listing[dead_one][0]); safe_unlink (dead_path); --n_inode_sig_files; lim_free (0, dead_path); rel_remove_records (&dir_listing, dead_one, dead_one); dead_one = -1; } } while (n_inode_sig_files > max_sigs); /* usually only one iteration expected */ lim_free (0, inode_sig_dir); rel_free_table (dir_listing); } static void prune_old_ctime_cruft (rel_table the_table) { int x; #undef MIN #define MIN(A,B) (((A) < (B)) ? (A) : (B)) for (x = 0; x < rel_n_records (the_table); ++x) { t_uchar * sig = the_table[x][1]; t_uchar * third_colon; t_uchar * fourth_colon; third_colon = str_chr_index (sig, ':'); if (!third_colon) return; third_colon = str_chr_index (third_colon + 1, ':'); if (!third_colon) return; third_colon = str_chr_index (third_colon + 1, ':'); if (!third_colon) return; if (str_cmp_n (third_colon + 1, MIN (5, str_length (third_colon + 1)), "ctime", (size_t)5)) return; fourth_colon = str_chr_index (third_colon + 1, ':'); if (!fourth_colon) return; mem_move (third_colon, fourth_colon, 1 + str_length (fourth_colon)); } } void arch_inode_sig_free (inode_sig sig) { rel_free_table (sig.ids); rel_free_table (sig.paths); } static void prune_old_dev_cruft (rel_table the_table) { int x; #undef MIN #define MIN(A,B) (((A) < (B)) ? (A) : (B)) for (x = 0; x < rel_n_records (the_table); ++x) { t_uchar * sig = the_table[x][1]; t_uchar * first_colon; if (str_cmp_n (sig, MIN (3, str_length (sig)), "dev", (size_t)3)) return; first_colon = str_chr_index (sig, ':'); if (!first_colon) return; mem_move (sig, first_colon + 1, str_length (first_colon)); } } /** * \brief remove all inode sigs in tree_root * * this is extremely useful if dealing with full copy trees that will have inappropriate * inode sigs stored in them * \param tree_root the tree root. */ void arch_clear_inode_sigs (t_uchar const * const tree_root) { arch_prune_inode_sig_dir (tree_root, NULL, INODE_IDS, 0); arch_prune_inode_sig_dir (tree_root, NULL, INODE_PATHS, 0); } /* tag: Tom Lord Fri Sep 12 09:56:44 2003 (inode-sig.c) */