/* changeset-utils.c:
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "hackerlab/os/errno.h"
#include "hackerlab/os/errno-to-string.h"
#include "hackerlab/fmt/cvt.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/char/char-class.h"
#include "hackerlab/char/str.h"
#include "hackerlab/char/pika-escaping-utils.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu/safe.h"
#include "tla/libfsutils/read-line.h"
#include "tla/libfsutils/safety.h"
#include "tla/libarch/invent.h"
#include "tla/libarch/changeset-utils.h"


/* __STDC__ prototypes for static functions */
static void changeset_inv_callback (const t_uchar * path,
                                    struct stat * stat_buf,
                                    enum arch_inventory_category category,
                                    const t_uchar * id,
                                    int has_source_name,
                                    void * closure,
                                    int escape_classes);



void
arch_changeset_inventory (struct arch_changeset_inventory * inv_out,
                          const t_uchar * tree_root,
                          const t_uchar * path,
                          enum arch_id_tagging_method method,
                          enum arch_inventory_category untagged_source_category,
                          int escape_classes)
{
  int here_fd;
  struct arch_inventory_options options;

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

  mem_set0 ((t_uchar *)&options, sizeof (options));
  options.categories = arch_inventory_source;
  options.want_ids = 1;
  options.treat_unrecognized_source_as_source = 1;

  if (method != arch_unspecified_id_tagging)
    {
      options.method = method;
      options.untagged_source_category = untagged_source_category;
      options.override_method = 1;
    }
  options.nested = 0;
  options.include_excluded = 1;

  arch_get_inventory_naming_conventions (&options, tree_root);

  inv_out->method = options.method;

  safe_chdir (path);
  arch_inventory_traversal (&options, ".", changeset_inv_callback, (void *)inv_out, escape_classes);
  arch_free_inventory_naming_conventions (&options);

  rel_sort_table_by_field (0, inv_out->dirs, 1);
  rel_sort_table_by_field (0, inv_out->files, 1);

  safe_fchdir (here_fd);

  safe_close (here_fd);
}


void
arch_free_changeset_inventory_data (struct arch_changeset_inventory * i)
{
  rel_free_table (i->dirs);     i->dirs = rel_table_nil;
  rel_free_table (i->files);    i->files = rel_table_nil;
}


rel_table
arch_read_changeset_index (const t_uchar * path)
{
  int in_fd;
  rel_table answer = rel_table_nil;
  t_uchar * line;
  long len;

  in_fd = safe_open (path, O_RDONLY, 0);

  while (1)
    {
      t_uchar * loc;
      t_uchar * id;
      t_uchar * start;

      line = 0;
      len = 0;
      safe_next_line (&line, &len, in_fd);

      if (!len)
        break;

      while (len && char_is_space (*line))
        {
          ++line;
          --len;
        }

      start = line;
      while (len && !char_is_space (*line))
        {
          ++line;
          --len;
        }

      if (line == start)
        {
        syntax_error:
          safe_printfmt (2, "illegally formed changeset index (%s)\n", path);
          exit (2);
        }

      loc = pika_save_unescape_iso8859_1_n (0, 0, start, line - start );

      while (len && char_is_space (*line))
        {
          ++line;
          --len;
        }

      start = line;

      while (len && !char_is_space (*line))
        {
          ++line;
          --len;
        }

      if (line == start)
        goto syntax_error;

      id = pika_save_unescape_iso8859_1_n (0, 0, start, line - start );

      while (len && char_is_space (*line))
        {
          ++line;
          --len;
        }

      if (len)
        goto syntax_error;

      if (!is_non_upwards_relative_path (loc))
        {
          safe_printfmt (2, "illegal path in changeset: %s\n", loc);
          exit (2);
        }

      rel_add_records (&answer, rel_make_record_2_taking (rel_make_field_str (loc), rel_make_field_str (id)), rel_record_null);
      lim_free (0, loc);
      lim_free (0, id);
    }

  safe_close (in_fd);
  return answer;
}


rel_table
arch_read_changeset_dir_metadata (const t_uchar * path)
{
  int errn;
  int in_fd;
  rel_table answer = rel_table_nil;

  in_fd = vu_open (&errn, path, O_RDONLY, 0);
  if (in_fd < 0)
    {
      if (errn == ENOENT)
        return rel_table_nil;
      else
        {
          safe_printfmt (2, "arch_read_changeset_dir_metadata: unable to open file (%s)\n", path);
          safe_printfmt (2, "  %s\n", errno_to_string (errn));
          exit (2);
        }
    }

  while (1)
    {
      t_uchar * line;
      long len;
      t_uchar * start;
      t_uchar * perms = 0;
      t_uchar * loc = 0;

      line = 0;
      len = 0;
      safe_next_line (&line, &len, in_fd);
      if (!len)
        break;

      while (len && char_is_space (*line))
        {
          ++line;
          --len;
        }

      if ((len < 13) || str_cmp_prefix ("--permissions", line))
        {
        syntax_error:
          safe_printfmt (2, "illegal dir metadata file: %s\n", path);
          exit (2);
        }
      len -= 13;
      line += 13;

      while (len && char_is_space (*line))
        {
          ++line;
          --len;
        }

      start = line;
      while (len && !char_is_space (*line))
        {
          ++line;
          --len;
        }

      if (start == line)
        goto syntax_error;

      perms = pika_save_unescape_iso8859_1_n (0, 0, start, line - start );

      while (len && char_is_space (*line))
        {
          ++line;
          --len;
        }

      start = line;
      while (len && !char_is_space (*line))
        {
          ++line;
          --len;
        }

      if (start == line)
        goto syntax_error;

      loc = pika_save_unescape_iso8859_1_n (0, 0, start, line - start );

      if (!is_non_upwards_relative_path (loc))
        {
          safe_printfmt (2, "illegal path in changeset: %s\n", loc);
          exit (2);
        }

      rel_add_records (&answer, rel_make_record_2_taking (rel_make_field_str (loc), rel_make_field_str (perms)), rel_record_null);

      lim_free (0, perms);
      lim_free (0, loc);
    }

  safe_close (in_fd);

  rel_sort_table_by_field (0, answer, 0);

  return answer;
}


mode_t
arch_read_permissions_patch (long * uid, long * gid, const t_uchar * file)
{
  int errn;
  t_uchar * line = 0;
  t_uchar * s;
  t_uchar * e;
  t_ulong answer;

  line = read_line_from_file (file);

  s = line;

  while (char_is_space (*s))
    ++s;
  if (str_cmp_prefix ("--permissions", s))
    {
    syntax_error:
      safe_printfmt (2, "illegal metadata patch file: %s\n", file);
      exit (2);
    }
  s += sizeof ("--permissions") - 1;
  while (char_is_space (*s))
    ++s;

  for (e = s; char_is_odigit (*e); ++e)
    ;

  if (e == s)
    goto syntax_error;

  if (cvt_octal_to_ulong (&errn, &answer, s, e - s))
    goto syntax_error;

  if (*e != ',')
    {
      if (uid)
        *uid = -1;
      if (uid)
        *uid = -1;
    }
  else
    {
      s = e + 1;
      for (e = s; char_is_digit (*e); ++e)
        ;

      if (e == s)
        goto syntax_error;

      if (uid && cvt_decimal_to_long (&errn, uid, s, e - s))
        goto syntax_error;

      if (*e != ',')
        {
          if (uid)
            *uid = -1;
          if (uid)
            *uid = -1;
        }
      else
        {
          s = e + 1;
          for (e = s; char_is_digit (*e); ++e)
            ;
          
          if (e == s)
            goto syntax_error;
          
          if (gid && cvt_decimal_to_long (&errn, gid, s, e - s))
            goto syntax_error;
        }
    }

  lim_free (0, line);
  return (mode_t)answer;
}


mode_t
arch_parse_permissions_params (long * uid, long * gid, const t_uchar * const line)
{
  int errn;
  const t_uchar * s;
  const t_uchar * e;
  t_ulong answer;

  s = line;

  while (char_is_space (*s))
    ++s;

  for (e = s; char_is_odigit (*e); ++e)
    ;

  if (e == s)
    {
    syntax_error:
      safe_printfmt (2, "illegal metadata patch param: %s\n", line);
      exit (2);
    }

  if (cvt_octal_to_ulong (&errn, &answer, s, e - s))
    goto syntax_error;

  if (*e != ',')
    {
      if (uid)
        *uid = -1;
      if (uid)
        *uid = -1;
    }
  else
    {
      s = e + 1;
      for (e = s; char_is_digit (*e); ++e)
        ;

      if (e == s)
        goto syntax_error;

      if (uid && cvt_decimal_to_long (&errn, uid, s, e - s))
        goto syntax_error;

      if (*e != ',')
        {
          if (uid)
            *uid = -1;
          if (uid)
            *uid = -1;
        }
      else
        {
          s = e + 1;
          for (e = s; char_is_digit (*e); ++e)
            ;
          
          if (e == s)
            goto syntax_error;
          
          if (gid && cvt_decimal_to_long (&errn, gid, s, e - s))
            goto syntax_error;
        }
    }

  return (mode_t)answer;
}





static void
changeset_inv_callback (const t_uchar * path,
                        struct stat * stat_buf,
                        enum arch_inventory_category category,
                        const t_uchar * id,
                        int has_source_name,
                        void * closure,
                        int escape_classes)
{
  struct arch_changeset_inventory * index;

  index = (struct arch_changeset_inventory *)closure;

  if (!id)
    {
      t_uchar * dir = 0;

      dir = file_name_directory_file (0, path);
      if (!arch_is_dont_care_explicit_dflt_dir (dir))
        {
          t_uchar * e_path = 0;

          e_path = pika_save_escape_iso8859_1 (0, 0, escape_classes, path);
          safe_printfmt (2, "missing explicit id for file (try tree-lint)\n   file:%s\n", e_path);
          exit (2);
        }
      lim_free (0, dir);

      return;
    }


  if (S_ISDIR (stat_buf->st_mode))
    rel_add_records (&index->dirs, rel_make_record_2_taking (rel_make_field_str (path), rel_make_field_str (id)), rel_record_null);
  else
    rel_add_records (&index->files, rel_make_record_2_taking (rel_make_field_str (path), rel_make_field_str (id)), rel_record_null);
}


/* tag: Tom Lord Thu May 15 13:00:33 2003 (changeset-utils.c)
 */
