/* 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)
*/
syntax highlighted by Code2HTML, v. 0.9.1