/* 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 "tla/libfsutils/ensure-dir.h"
#include "tla/libfsutils/dir-listing.h"
#include "tla/libfsutils/string-files.h"
#include "tla/libawk/relational.h"
#include "tla/libawk/relassoc.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/invent.h"
#include "tla/libarch/inode-sig.h"



#define MAX_INODE_SIG_FILES 5


/* __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 (t_uchar * path, struct stat * stat_buf,
                    enum arch_inventory_category category,
                    t_uchar * id, int has_source_name,
                    void * closure,
                    int escape_classes);
static t_uchar * arch_inode_sig_dir (t_uchar * tree_root);
static t_uchar * arch_inode_sig_file (t_uchar * tree_root, t_uchar * archive, t_uchar * revision);
static t_uchar * arch_inode_sig_tmp_file (t_uchar * tree_root, t_uchar * archive, t_uchar * revision);
static int arch_creat_inode_sig_file (t_uchar * tree_root, t_uchar * archive, t_uchar * revision);
static void arch_finish_inode_sig_file (t_uchar * tree_root, t_uchar * archive, t_uchar * revision, int fd);
static void arch_prune_inode_sig_dir (t_uchar * tree_root, t_uchar * current_inode_sig_file);
static void prune_old_ctime_cruft (rel_table the_table);
static void prune_old_dev_cruft (rel_table the_table);



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);
}


rel_table
arch_tree_inode_sig (t_uchar * tree_root)
{
  struct arch_inventory_options options = {0, };
  int here_fd;
  rel_table answer = 0;

  here_fd = safe_open (".", O_RDONLY, 0);
  safe_chdir (tree_root);

  options.categories = arch_inventory_source;
  options.want_ids = 1;
  options.include_excluded = 1;

  arch_get_inventory_naming_conventions (&options, ".");

  arch_inventory_traversal (&options, ".", inode_sig_callback, (void *)&answer, 0);

  arch_free_inventory_naming_conventions (&options);

  rel_sort_table_by_field (0, answer, 0);

  safe_fchdir (here_fd);
  safe_close (here_fd);

  return answer;
}

void
arch_snap_inode_sig (t_uchar * tree_root, t_uchar * archive, t_uchar * revision)
{
  rel_table sig = arch_tree_inode_sig (tree_root);
  int fd = arch_creat_inode_sig_file (tree_root, archive, revision);

  rel_print_table (fd, sig);
  arch_finish_inode_sig_file (tree_root, archive, revision, fd);

  rel_free_table (sig);
}

 
void
arch_snap_inode_sig_files (t_uchar * tree_root, t_uchar * archive, t_uchar * prev_revision, t_uchar * revision, rel_table file_list)
{
  rel_table oldsig;
  assoc_table newsig_as;
  rel_table newsig_rel;
  assoc_table id_list;
  int fd = -1;
  
  arch_read_inode_sig (&oldsig, 0, tree_root, archive, prev_revision);
  newsig_rel = arch_tree_inode_sig (tree_root);
  newsig_as = rel_to_assoc (newsig_rel, 0, 1);

  id_list = arch_filenames_ids ( &file_list, tree_root);

  arch_update_inode_sig (oldsig, newsig_as, id_list);

  fd = arch_creat_inode_sig_file (tree_root, archive, revision);
  rel_print_table (fd, oldsig);
  arch_finish_inode_sig_file (tree_root, archive, revision, fd);

  rel_free_table (oldsig);
  rel_free_table (newsig_rel);
  free_assoc_table (newsig_as);
  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] ))
	{
	  rel_record new_record = 0;
	  t_uchar *vals = assoc_ref (newsig, sig [i] [0]);
	  invariant (vals != 0);
	  new_record = rel_make_record (sig [i] [0], vals, 0);
	  rel_free_record (sig [i]);
	  sig [i] = new_record;
	}
    }
}


int
arch_valid_inode_sig (t_uchar * tree_root, t_uchar * archive, t_uchar * revision)
{
  int result = 1;
  
  if (safe_access (arch_inode_sig_file(tree_root, archive, revision), 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;
      rel_table current_sig = arch_tree_inode_sig (tree_root);

      arch_read_inode_sig (&existing_sig, NULL, tree_root, archive, revision);
      /* 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]))
                result = 0;
        }
      else result = 0;
      rel_free_table (existing_sig);
      rel_free_table (current_sig);
    }

  return result;
}

void
arch_read_inode_sig (rel_table * as_table, assoc_table * as_assoc,
                     t_uchar * tree_root, t_uchar * archive, t_uchar * revision)
{
  t_uchar * sig_file = arch_inode_sig_file (tree_root, archive, revision);

  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_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);
    }
}


void
arch_read_id_shortcut (assoc_table * ids_shortcut, t_uchar * tree_root)
{
  t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root);
  rel_table dir_listing = maybe_directory_files (inode_sig_dir);
  assoc_table answer = 0;
  assoc_table rejected = 0;
  int x;

  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))
            {
              rel_table sig_table = 0;
              int y;

              arch_read_inode_sig (&sig_table, 0, tree_root, maybe_archive, maybe_revision);

              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, maybe_archive);
          lim_free (0, maybe_revision);
        }

      lim_free (0, path);
    }

  lim_free (0, inode_sig_dir);
  rel_free_table (dir_listing);
  free_assoc_table (rejected);

  *ids_shortcut = answer;
}




static void
inode_sig_callback (t_uchar * path, struct stat * stat_buf,
                    enum arch_inventory_category category,
                    t_uchar * id, int has_source_name,
                    void * closure,
                    int escape_classes)
{
  rel_table * answer = (rel_table *)closure;
  t_uchar * signature = arch_statb_inode_sig (stat_buf);

  if (!S_ISDIR (stat_buf->st_mode) && !S_ISLNK (stat_buf->st_mode))
    rel_add_records (answer, rel_make_record (id, signature, 0), 0);
  lim_free(0, signature);
}


static t_uchar *
arch_inode_sig_dir (t_uchar * tree_root)
{
  return file_name_in_vicinity (0, tree_root, "{arch}/,,inode-sigs");
}

static t_uchar *
arch_inode_sig_file (t_uchar * tree_root, t_uchar * archive, t_uchar * revision)
{
  t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root);
  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 * tree_root, t_uchar * archive, t_uchar * revision)
{
  t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root);
  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 * tree_root, t_uchar * archive, t_uchar * revision)
{
  t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root);
  t_uchar * inode_sig_tmp_file = arch_inode_sig_tmp_file (tree_root, archive, revision);
  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 * tree_root, t_uchar * archive, t_uchar * revision, int fd)
{
  t_uchar * inode_sig_tmp_file = arch_inode_sig_tmp_file (tree_root, archive, revision);
  t_uchar * inode_sig_file = arch_inode_sig_file (tree_root, archive, revision);

  safe_close (fd);
  safe_rename (inode_sig_tmp_file, inode_sig_file);
  arch_prune_inode_sig_dir (tree_root, inode_sig_file);

  lim_free (0, inode_sig_tmp_file);
  lim_free (0, inode_sig_file);
}


static void
arch_prune_inode_sig_dir (t_uchar * tree_root, t_uchar * current_inode_sig_file)
{
  t_uchar * inode_sig_dir = arch_inode_sig_dir (tree_root);
  rel_table dir_listing = maybe_directory_files (inode_sig_dir);
  int dead_one = -1;
  time_t oldest_time;
  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_INODE_SIG_FILES)
        {
          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);
        }


    } while (n_inode_sig_files > MAX_INODE_SIG_FILES); /* 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));
    }
}


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));
    }
}





/* tag: Tom Lord Fri Sep 12 09:56:44 2003 (inode-sig.c)
 */
