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

#include "config-options.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/os/sys/wait.h"
#include "hackerlab/os/signal.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/char/str.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/fs/file-names.h"
#include "tla/libarch/exec.h"
#include "tla/libarch/inode-sig.h"
#include "tla/libarch/diffs.h"



int
arch_binary_files_differ (const t_uchar * a, const t_uchar * b, const t_uchar * id, assoc_table inode_sig_shortcuts_of_b)
{
  struct stat a_stat;
  struct stat b_stat;

  int answer;

  safe_stat (a, &a_stat);
  safe_stat (b, &b_stat);

  answer = arch_file_stats_differ (&a_stat, &b_stat);
  if (answer == 0)
    return answer;
  answer = arch_inode_sigs_differ (&b_stat, id, inode_sig_shortcuts_of_b);
  if (answer == 0)
    return answer;

  return arch_filename_contents_differ (a, b);
}


int
arch_file_stats_differ (struct stat *a_stat, struct stat *b_stat)
{
  if (a_stat->st_size != b_stat->st_size)
    return 1;

  if (a_stat->st_ino == b_stat->st_ino && a_stat->st_dev == b_stat->st_dev ) 
    return 0;
  return -1;
}

int 
arch_inode_sigs_differ (struct stat *b_stat, const t_uchar * id, assoc_table inode_sig_shortcuts_of_b)
{
  if (id && inode_sig_shortcuts_of_b)
    {
      t_uchar * b_sig = arch_statb_inode_sig (b_stat);
      const t_uchar * b_goal = assoc_get_str_taking (inode_sig_shortcuts_of_b, rel_make_field_str (id));
      int shortcut_applies = !str_cmp (b_sig, b_goal);

      lim_free (0, b_sig);
      if (shortcut_applies)
        return 0;
    }

  return -1;
}

int
arch_filename_contents_differ (const t_uchar * a, const t_uchar * b)
{
  int a_fd;
  int b_fd;
  int answer;

  a_fd = safe_open (a, O_RDONLY, 0);
  b_fd = safe_open (b, O_RDONLY, 0);
  answer = arch_file_contents_differ (a_fd, b_fd);

  safe_close (a_fd);
  safe_close (b_fd);

  return answer;
}

int 
arch_file_contents_differ (int a_fd, int b_fd)
{
  static t_uchar a_buf[65536];
  static t_uchar b_buf[sizeof (a_buf)];  
  long a_amt;
  long b_amt;
  int answer = 0;

  while (1)
    {
      a_amt = safe_read_retry (a_fd, a_buf, sizeof (a_buf));
      b_amt = safe_read_retry (b_fd, b_buf, sizeof (b_buf));

      if ((a_amt != b_amt) || mem_cmp (a_buf, b_buf, a_amt))
        {
          answer = 1;
          break;
        }

      if (!a_amt)
        break;
    }

  return answer;
}


int
arch_invoke_diff (int output_fd,
                  const char * orig_path, const char * orig_loc,
                  const char * mod_path, const char * mod_loc,
                  t_uchar * id,
                  assoc_table inode_sig_shortcuts_of_mod)
{
  if (!arch_binary_files_differ (orig_path, mod_path, id, inode_sig_shortcuts_of_mod))
    return 0;
  return arch_really_invoke_diff (output_fd, orig_path, orig_loc, mod_path, mod_loc, NULL);
}

int
arch_really_invoke_diff (int output_fd,
                         const char * orig_path, const char * orig_loc,
                         const char * mod_path, const char * mod_loc, const char **extraopts)
{
  char * orig_label = 0;
  char * mod_label = 0;
  int pid;

  if (orig_loc)
    {
      orig_label = file_name_in_vicinity (0, "orig", orig_loc + 2);
    }
  if (mod_loc)
    {
      mod_label = file_name_in_vicinity (0, "mod", mod_loc + 2);
    }

  safe_flush (output_fd);
  
  pid = fork ();
  if (pid == -1)
    panic ("unable to fork for diff");

  if (pid)
    {
      int status;
      int wait_pid;

      wait_pid = waitpid (pid, &status, 0);
      if (wait_pid < 0)
        {
          panic_msg ("error waiting for subprocess");
          kill (0, SIGKILL);
          panic ("error waiting for subprocess");
        }
      if (WIFSIGNALED (status))
        {
          safe_printfmt (2, "\n");
          safe_printfmt (2, "diff subprocess killed by signal %d\n", WTERMSIG (status));
          safe_printfmt (2, "\n");
          exit (2);
        }
      else if (!WIFEXITED (status))
        {
          panic_msg ("waitpid returned for a non-exited process");
          kill (0, SIGKILL);
          panic ("waitpid returned for a non-exited process");
        }
      else
        {
          int exit_status;

          exit_status = WEXITSTATUS (status);

          if (exit_status && (exit_status != 1) && (exit_status != 2))
            {
              safe_printfmt (2, "\n");
              safe_printfmt (2, "encountered error diffing files\n");
              safe_printfmt (2, "  orig: %s\n", orig_path);
              safe_printfmt (2, "  mod: %s\n", mod_path);
              safe_printfmt (2, "  diff exit status: %d\n", exit_status);
              safe_printfmt (2, "\n");
              exit (2);
            }
          if (orig_label)
            {
              lim_free (0, orig_label);
            }
          if (mod_label)
            {
              lim_free (0, mod_label);
            }
          return exit_status;
        }
    }
  else
    {
      int errn;
      t_uchar ** argv = 0;

      if (0 > vu_move_fd (&errn, output_fd, 1))
        panic ("unable to redirect stdout for diff");


      /* `const' discarded before `exec'
       */

      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (t_uchar *)) = cfg__gnu_diff;
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (t_uchar *)) = "--binary";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (t_uchar *)) = "-u";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (t_uchar *)) = "-L";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (t_uchar *)) = orig_label ? (char *)orig_label : (char *)orig_path;
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (t_uchar *)) = "-L";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (t_uchar *)) = mod_label ? (char *)mod_label : (char *)mod_path;
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (t_uchar *)) = (char *)orig_path;
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (t_uchar *)) = (char *)mod_path;
      if (extraopts != NULL)
        {
          t_uchar ** opt;
          for (opt = extraopts; *opt != NULL; ++opt)
            {
              *(t_uchar **) ar_push ((void*) &argv, 0, sizeof(t_uchar*)) = *opt;

            }
        }

      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (t_uchar *)) = 0;

      arch_util_execvp (cfg__gnu_diff, argv);

      panic ("execvp for diff returned to caller");

      exit (2);
    }
  panic ("arch_invoke_diff: not reached");
  return 1;
}




/* tag: Tom Lord Mon May 19 18:00:23 2003 (diffs.c)
 */
