

#define fixme0  0



/* mkpatch.c: ++diff -r -u 
 *
 ****************************************************************
 * Copyright (C) 2002  Tom Lord
 * 
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "config-options.h"
#include "hackerlab/os/sys/types.h"
#include "hackerlab/os/sys/wait.h"
#include "hackerlab/os/signal.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/cmd/main.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/fs/tmp-files.h"
#include "hackerlab/sort/qsort.h"
#include "file-utils/inv.h"



static t_uchar * program_name = "mkpatch";
static t_uchar * usage = "mkpatch [options] [--] orig mod";
static t_uchar * version_string = (cfg__std__package " from regexps.com\n"
				   "\n"
				   "Copyright 2002 Tom Lord\n"
				   "\n"
				   "This is free software; see the source for copying conditions.\n"
				   "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n"
				   "PARTICULAR PURPOSE.\n"
				   "\n"
				   "Report bugs to " cfg__file_utils_bug_mail ".\n"
				   "\n");

#define OPTS(OP, OP2) \
  OP (opt_help_msg, "h", "help", 0, \
      "display help") \
  OP (opt_long_help, "H", 0, 0, \
      "Display a verbose help message and exit.") \
  OP (opt_version, "V", "version", 0, \
      "display version info") \

static t_uchar long_help[] = ("make a patch set\n"
			      "Make a complete patch set between two trees.\n"
			      "\n");

enum options
{
  OPTS (OPT_ENUM, OPT_IGN)  
};

struct opt_desc opts[] = 
{
  OPTS (OPT_DESC, OPT_DESC)
    {-1, 0, 0, 0, 0}
};



struct mkpatch_inventory 
{
  char * root;

  char ** file_locs;
  char ** file_paths;
  char ** file_tags;
  struct stat * file_stats;

  /* derived */
  int * tag_index;

  int * new_dirs;
  int * new_files;
  int * new_symlinks;
};

struct mkpatch_map
{
  /* 0 == orig, 1 == mod
   */
  int * common_dirs[2];
  int * common_symlinks[2];
  int * common_files[2];
  int * changed_type[2];
};



/* __STDC__ prototypes for static functions */
static void get_naming_conventions (struct inv_options * options, char * path);
static void inventory (struct inv_options * options, char * path, struct mkpatch_inventory * data);
static int mkpatch_inv_callback (int * errn,
				 struct alloc_limits * limits,
				 t_uchar * path,
				 struct stat * stat_buf,
				 enum inv_category category,
				 t_uchar * tag,
				 void * closure);
static void derive_map (struct mkpatch_map * map, struct mkpatch_inventory * orig_data, struct mkpatch_inventory * mod_data);
static int tag_cmp (void * va, void * vb, void * vdata);
static void record_common_tag (struct mkpatch_map * map,
			       struct mkpatch_inventory * orig_data, int orig_index,
			       struct mkpatch_inventory * mod_data, int mod_index);
static void record_new_file (struct mkpatch_inventory * data, int index);
static void report_new_symlinks (struct mkpatch_inventory * data, char * op);
static void report_new_files (struct mkpatch_inventory * data, char * op);
static void report_new_dirs (struct mkpatch_inventory * data, char * op);
static void report_changed_dirs (struct mkpatch_inventory * orig_data,
				 struct mkpatch_inventory * mod_data,
				 struct mkpatch_map * map);
static void report_changed_files (struct mkpatch_inventory * orig_data,
				  struct mkpatch_inventory * mod_data,
				  struct mkpatch_map * map);
static void report_changed_symlinks (struct mkpatch_inventory * orig_data,
				     struct mkpatch_inventory * mod_data,
				     struct mkpatch_map * map);
static void report_file_diffs (struct mkpatch_inventory * orig_data,
			       struct mkpatch_inventory * mod_data,
			       struct mkpatch_map * map);
static int invoke_diff (int output_fd, char * orig_path, char * orig_loc, char * mod_path, char * mod_loc);
static char * link_target (char * path);



int
main (int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;

  option = 0;

  while (1)
    {
      o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv,
			program_name, usage, version_string, long_help,
			opt_help_msg, opt_long_help, opt_version);
      if (o == opt_none)
	break;
      switch (o)
	{
	default:
	  safe_printfmt (2, "unhandled option `%s'\n", option->opt_string);
	  panic ("internal error parsing arguments");

	usage_error:
	  opt_usage (2, argv[0], program_name, usage, 1);
	  exit (1);

	/* bogus_arg: */
	  safe_printfmt (2, "ill-formed argument for `%s' (`%s')\n", option->opt_string, option->arg_string);
	  goto usage_error;
	}
    }

  if (argc != 3)
    goto usage_error;

  {
    char * orig;
    char * mod;
    struct inv_options orig_options;
    struct inv_options mod_options;
    struct mkpatch_inventory orig_data;
    struct mkpatch_inventory mod_data;
    struct mkpatch_map map;

    orig = argv[1];
    mod = argv[2];

    mem_set0 ((t_uchar *)&orig_options, sizeof (orig_options));
    mem_set0 ((t_uchar *)&mod_options, sizeof (mod_options));
    mem_set0 ((t_uchar *)&orig_data, sizeof (orig_data));
    mem_set0 ((t_uchar *)&mod_data, sizeof (mod_data));
    mem_set0 ((t_uchar *)&map, sizeof (map));

    orig_options.categories = inv_source;
    orig_options.want_tags = 1;
    orig_options.include_untagged = 1;
    orig_options.method = ftag_names; /* default only, may be set by `get_naming_conventions' */
    orig_options.nested = 0;
    orig_options.control = 1;
    orig_options.include_excluded = 1;
    orig_options.control_dir_name = "{arch}";

    mod_options = orig_options;

    get_naming_conventions (&orig_options, orig);
    get_naming_conventions (&mod_options, mod);

    orig_data.root = str_save (0, orig);
    mod_data.root = str_save (0, mod);

    {
      int here;

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

      safe_chdir (orig);
      inventory (&orig_options, ".", &orig_data);

      safe_fchdir (here);
      safe_chdir (mod);
      inventory (&mod_options, ".", &mod_data);

      safe_fchdir (here);
      safe_close (here);
    }

    derive_map (&map, &orig_data, &mod_data);

    /* report stuff */
    {
      /* Some aspects of the order in which changes are reported is 
       * significant in order to support single-pass dopatch.
       */

      /* It says "report new", but that really means "report removed".
       */
      report_new_symlinks (&orig_data, "rmv");
      report_new_files (&orig_data, "rmv");
      report_new_dirs (&orig_data, "rmv");

      /* Report renames, permission changes, and symlink target changes
       */
      report_changed_dirs (&orig_data, &mod_data, &map);
      report_changed_files (&orig_data, &mod_data, &map);
      report_changed_symlinks (&orig_data, &mod_data, &map);

      /* New directories and files.
       */
      report_new_dirs (&mod_data, "add");
      report_new_files (&mod_data, "add");

      /* individual file diffs
       */
      report_file_diffs (&orig_data, &mod_data, &map);
      /* report_changed_type_diffs (&orig_data, &mod_data, &map); */
    }
  }
  return 0;
}



static void
get_naming_conventions (struct inv_options * options, char * path)
{
  int errn;
  int re_error;

  errn = 0;
  re_error = 0;

  if (0 > inv_get_naming_conventions (&errn, &re_error, 0, options, path))
    {
      safe_printfmt (2, "\n");
      safe_printfmt (2, "mkpatch: error while computing naming conventions\n");
      safe_printfmt (2, "  dir: %s\n", path);
      if (errn)
	safe_printfmt (2, "  %s\n", errno_to_string (errn));
      if (re_error)
	safe_printfmt (2, "  bogus regular expression (not to mention poor error reporting in \"inventory\")\n");
      safe_printfmt (2, "\n");
      exit (2);
    }
}




static void
inventory (struct inv_options * options, char * path, struct mkpatch_inventory * data)
{
  int errn;
  char * bad_file;
    
  bad_file = 0;
  if (0 > inv_traversal (&errn, &bad_file, 0, options, path, mkpatch_inv_callback, (void *)data))
    {
      safe_printfmt (2, "\n");
      safe_printfmt (2, "mkpatch: error while computing inventory\n");
      safe_printfmt (2, "  dir: %s\n", path);
      if (bad_file)
	safe_printfmt (2, "  file: %s\n", bad_file);
      safe_printfmt (2, "  %s\n", errno_to_string (errn));
      safe_printfmt (2, "\n");
      exit (2);
    }
}



static int
mkpatch_inv_callback (int * errn,
		      struct alloc_limits * limits,
		      t_uchar * path,
		      struct stat * stat_buf,
		      enum inv_category category,
		      t_uchar * tag,
		      void * closure)
{
  struct mkpatch_inventory * data;
  int n;

  data = (struct mkpatch_inventory *)closure;

  n = ar_size ((void *)data->file_locs, 0, sizeof (char *));
  *(char **)ar_push ((void **)&data->file_locs, 0, sizeof (char *)) = str_save (0, path);
  *(char **)ar_push ((void **)&data->file_tags, 0, sizeof (char *)) = str_save (0, tag);
  *(struct stat *)ar_push ((void *)&data->file_stats, 0, sizeof (struct stat)) = *stat_buf;

  *(char **)ar_push ((void **)&data->file_paths, 0, sizeof (char *))
    = file_name_in_vicinity (0, data->root, path);

  return 0;
}

static void
derive_map (struct mkpatch_map * map, struct mkpatch_inventory * orig_data, struct mkpatch_inventory * mod_data)
{
  int x;
  int y;
  int n_orig;
  int n_mod;

  n_orig = ar_size ((void *)orig_data->file_tags, 0, sizeof (char *));
  n_mod = ar_size ((void *)mod_data->file_tags, 0, sizeof (char *));

  ar_setsize ((void **)&orig_data->tag_index, 0, n_orig, sizeof (int));
  for (x = 0; x < n_orig; ++x)
    orig_data->tag_index[x] = x;

  ar_setsize ((void **)&mod_data->tag_index, 0, n_mod, sizeof (int));
  for (x = 0; x < n_mod; ++x)
    mod_data->tag_index[x] = x;

  quicksort ((void *)orig_data->tag_index, n_orig, sizeof (int), tag_cmp, (void *)orig_data);
  quicksort ((void *)mod_data->tag_index, n_mod, sizeof (int), tag_cmp, (void *)mod_data);

  /* check for duplicate tags
   */

  if (fixme0)
    panic ("fuck");



  /* catalog new, removed, and common tags
   */
  x = 0;
  y = 0;

  while ((x < n_orig) || (y < n_mod))
    {
      int cmp;

      cmp = ((y == n_mod)
	     ? -1
	     : ((x == n_orig)
		? 1
		: str_cmp (orig_data->file_tags[orig_data->tag_index[x]], mod_data->file_tags[mod_data->tag_index[y]])));
      
      if (!cmp)
	{
	  record_common_tag (map, orig_data, orig_data->tag_index[x], mod_data, mod_data->tag_index[y]);
	  ++x;
	  ++y;
	}
      else if (cmp < 0)
	{
	  record_new_file (orig_data, orig_data->tag_index[x]);
	  ++x;
	}
      else
	{
	  record_new_file (mod_data, mod_data->tag_index[y]);
	  ++y;
	}
    }

}


static int
tag_cmp (void * va, void * vb, void * vdata)
{
  int a;
  int b;
  struct mkpatch_inventory * data;

  a = *(int *)va;
  b = *(int *)vb;
  data = (struct mkpatch_inventory *)vdata;

  return str_cmp (data->file_tags[a], data->file_tags[b]);
}


static void
record_common_tag (struct mkpatch_map * map,
		   struct mkpatch_inventory * orig_data, int orig_index,
		   struct mkpatch_inventory * mod_data, int mod_index)
{
  if (S_ISDIR (orig_data->file_stats[orig_index].st_mode))
    {
      if (S_ISDIR (mod_data->file_stats[mod_index].st_mode))
	{
	  *(int *)ar_push ((void **)&map->common_dirs[0], 0, sizeof (int)) = orig_index;
	  *(int *)ar_push ((void **)&map->common_dirs[1], 0, sizeof (int)) = mod_index;
	}
      else
	{
	changed_type:
	  *(int *)ar_push ((void **)&map->changed_type[0], 0, sizeof (int)) = orig_index;
	  *(int *)ar_push ((void **)&map->changed_type[1], 0, sizeof (int)) = mod_index;
	}
    }
  else if (S_ISLNK (orig_data->file_stats[orig_index].st_mode))
    {
      if (S_ISLNK (mod_data->file_stats[mod_index].st_mode))
	{
	  *(int *)ar_push ((void **)&map->common_symlinks[0], 0, sizeof (int)) = orig_index;
	  *(int *)ar_push ((void **)&map->common_symlinks[1], 0, sizeof (int)) = mod_index;
	}
      else
	goto changed_type;
    }
  else
    {
      if (!S_ISDIR (mod_data->file_stats[mod_index].st_mode) && !S_ISLNK (mod_data->file_stats[mod_index].st_mode))
	{
	  *(int *)ar_push ((void **)&map->common_files[0], 0, sizeof (int)) = orig_index;
	  *(int *)ar_push ((void **)&map->common_files[1], 0, sizeof (int)) = mod_index;
	}
      else
	goto changed_type;
    }
}

static void
record_new_file (struct mkpatch_inventory * data, int index)
{
  if (S_ISDIR (data->file_stats[index].st_mode))
    {
      *(int *)ar_push ((void **)&data->new_dirs, 0, sizeof (int)) = index;
    }
  else if (S_ISLNK (data->file_stats[index].st_mode))
    {
      *(int *)ar_push ((void **)&data->new_symlinks, 0, sizeof (int)) = index;
    }
  else
    {
      *(int *)ar_push ((void **)&data->new_files, 0, sizeof (int)) = index;
    }
}

static void
report_new_symlinks (struct mkpatch_inventory * data, char * op)
{
  int n_links;
  int x;

  n_links = ar_size ((void *)data->new_symlinks, 0, sizeof (int));

  for (x = 0; x < n_links; ++x)
    {
      int index;
      char * target;
      char * target_tag;

      index = data->new_symlinks[x];

      target = link_target (data->file_paths[index]);
      target_tag = fixme0;

      safe_printfmt (1, "### %s-symlink %s \\\n", op, data->file_locs[index]);
      safe_printfmt (1, "###    %s \\\n", data->file_tags[index]);
      safe_printfmt (1, "###    %s %s\n", target, (target_tag ? "\\" : ""));
      if (target_tag)
        safe_printfmt (1, "###    %s\n", target_tag);
      safe_printfmt (1, "\n");
    }
}


static void
report_new_files (struct mkpatch_inventory * data, char * op)
{
  int n_files;
  int x;

  n_files = ar_size ((void *)data->new_files, 0, sizeof (int));

  for (x = 0; x < n_files; ++x)
    {
      int index;
      int is_binary;

      index = data->new_files[x];
      is_binary = fixme0;

      safe_printfmt (1, "### %s-file %s \\\n", op, data->file_locs[index]);
      safe_printfmt (1, "###    %s \\\n", data->file_tags[index]);
      safe_printfmt (1, "###    --permissions %lo \\\n",
		     (unsigned long)(data->file_stats[index].st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)));
      if (is_binary)
	{
	  panic ("binary file");
	}
      else
	{
	  char * orig_timestamp;
	  char * mod_timestamp;
	  char * file_contents;
	  size_t n_lines;
	  char ** lines;
	  size_t * line_lengths;

	  file_contents = lim_malloc (0, data->file_stats[index].st_size + 1);
	  {
	    int fd;

	    fd = safe_open (data->file_paths[index], O_RDONLY, 0);
	    if (data->file_stats[index].st_size != safe_read_retry (fd, file_contents, data->file_stats[index].st_size))
	      {
		safe_printfmt (2, "mkpatch: file changed size unexpectedly\n");
		safe_printfmt (2, "  file: %s\n", data->file_paths[index]);
		panic ("file changed size unexpectedly");
	      }
	    safe_close (fd);
	    file_contents[data->file_stats[index].st_size] = '\n';
	  }

	  {
	    char * pos;
	    char * eof;
	    
	    n_lines = 0;
	    lines = 0;
	    line_lengths = 0;
	    pos = file_contents;
	    eof = file_contents + data->file_stats[index].st_size;

	    while (pos < eof)
	      {
		char * next_line;
		
		next_line = str_chr_index (pos, '\n');
		if (next_line != eof)
		  ++next_line;
		*(char **)ar_push ((void **)&lines, 0, sizeof (char *)) = pos;
		*(size_t *)ar_push ((void **)&line_lengths, 0, sizeof (size_t)) = next_line - pos;
		++n_lines;
		pos = next_line;
	      }
	  }
	  

	  {
	    char * ct;

	    ct = ctime (&data->file_stats[index].st_mtime);
	    if (!ct)
	      ct = "?\n";


	    if (op[0] == 'r')
	      {
		mod_timestamp = "Wed Dec 31 16:00:00 1969\n";
		orig_timestamp = ct;
	      }
	    else
	      {
		orig_timestamp = "Wed Dec 31 16:00:00 1969\n";
		mod_timestamp = ct;
	      }
	    safe_printfmt (1, "diff -u --mkpatch orig/%s mod/%s\n", data->file_locs[index] + 2, data->file_locs[index] + 2);
	    safe_printfmt (1, "--- orig/%s\t%s", data->file_locs[index] + 2, orig_timestamp);
	    safe_printfmt (1, "+++ mod/%s\t%s", data->file_locs[index] + 2, mod_timestamp);
	  }

	  if (op[0] == 'r')
	    safe_printfmt (1, "@@ -1,%lu +0,0 @@\n", (t_ulong)n_lines);
	  else
	    safe_printfmt (1, "@@ -0,0 +1,%lu @@\n", (t_ulong)n_lines);
	  {
	    char prefix;
	    if (op[0] == 'r')
	      prefix = '-';
	    else
	      prefix = '+';
	    
	    for (x = 0; x < n_lines; ++x)
	      {
		safe_printfmt (1, "%c%.*s", prefix, (int)line_lengths[x], lines[x]);
	      }
	    if (n_lines && (lines[n_lines - 1][line_lengths[n_lines - 1] - 1] != '\n'))
	      safe_printfmt (1, "\n\\ No newline at end of file\n");
	  }
	  safe_printfmt (1, "\n\n");
	  lim_free (0, file_contents);
	  ar_free ((void **)&line_lengths, 0);
	  ar_free ((void **)&lines, 0);
	}
    }
}


static void
report_new_dirs (struct mkpatch_inventory * data, char * op)
{
  int n_dirs;
  int x;

  n_dirs = ar_size ((void *)data->new_dirs, 0, sizeof (int));

  for (x = 0; x < n_dirs; ++x)
    {
      int index;

      index = data->new_dirs[x];
      safe_printfmt (1, "### %s-dir %s \\\n", op, data->file_locs[index]);
      safe_printfmt (1, "###    %s \\\n", data->file_tags[index]);
      safe_printfmt (1, "###    --permissions %lo \\\n",
		     (unsigned long)(data->file_stats[index].st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)));
      safe_printfmt (1, "\n");
    }
}

static void
report_changed_dirs (struct mkpatch_inventory * orig_data,
			    struct mkpatch_inventory * mod_data,
			    struct mkpatch_map * map)
{
  int n_dirs;
  int x;

  n_dirs = ar_size ((void *)map->common_dirs[0], 0, sizeof (int));

  for (x = 0; x < n_dirs; ++x)
    {
      int orig_index;
      int mod_index;
      char * orig_loc;
      char * mod_loc;
      t_ulong orig_perms;
      t_ulong mod_perms;
      int loc_changed;
      int perms_changed;

      orig_index = map->common_dirs[0][x];
      mod_index = map->common_dirs[1][x];

      invariant (!str_cmp (orig_data->file_tags[orig_index], mod_data->file_tags[mod_index]));

      orig_loc = orig_data->file_locs[orig_index];
      mod_loc = mod_data->file_locs[mod_index];

      orig_perms = (orig_data->file_stats[orig_index].st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
      mod_perms = (mod_data->file_stats[mod_index].st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));

      loc_changed = !!str_cmp (orig_loc, mod_loc);
      perms_changed = (orig_perms != mod_perms);

      if (loc_changed || perms_changed)
	{
	  safe_printfmt (1, "### patch-dir %s \\\n", mod_loc);
	  if (loc_changed)
	    safe_printfmt (1, "###    was: %s \\\n", orig_loc);
	  safe_printfmt (1, "###    %s %s\n", orig_data->file_tags[orig_index], (perms_changed ? "\\" : ""));
	  if (perms_changed)
	    {
	      safe_printfmt (1, "###    had: --permissions %lo \\\n", orig_perms);
	      safe_printfmt (1, "###    has: --permissions %lo\n", mod_perms);
	    }
	  safe_printfmt (1, "\n");
	}
    }
}

static void
report_changed_files (struct mkpatch_inventory * orig_data,
		      struct mkpatch_inventory * mod_data,
		      struct mkpatch_map * map)
{
  int n_files;
  int x;

  /* this reports renames and permissions changes, not diffs
   */

  n_files = ar_size ((void *)map->common_files[0], 0, sizeof (int));

  for (x = 0; x < n_files; ++x)
    {
      int orig_index;
      int mod_index;
      char * orig_loc;
      char * mod_loc;
      t_ulong orig_perms;
      t_ulong mod_perms;
      int loc_changed;
      int perms_changed;

      orig_index = map->common_files[0][x];
      mod_index = map->common_files[1][x];

      invariant (!str_cmp (orig_data->file_tags[orig_index], mod_data->file_tags[mod_index]));

      orig_loc = orig_data->file_locs[orig_index];
      mod_loc = mod_data->file_locs[mod_index];

      orig_perms = (orig_data->file_stats[orig_index].st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
      mod_perms = (mod_data->file_stats[mod_index].st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));

      loc_changed = !!str_cmp (orig_loc, mod_loc);
      perms_changed = (orig_perms != mod_perms);

      if (loc_changed || perms_changed)
	{
	  safe_printfmt (1, "### change-file %s \\\n", mod_loc);
	  if (loc_changed)
	    safe_printfmt (1, "###    was: %s \\\n", orig_loc);
	  safe_printfmt (1, "###    %s %s\n", orig_data->file_tags[orig_index], (perms_changed ? "\\" : ""));
	  if (perms_changed)
	    {
	      safe_printfmt (1, "###    had: --permissions %lo \\\n", orig_perms);
	      safe_printfmt (1, "###    has: --permissions %lo\n", mod_perms);
	    }
	  safe_printfmt (1, "\n");
	}
    }
}

static void
report_changed_symlinks (struct mkpatch_inventory * orig_data,
			 struct mkpatch_inventory * mod_data,
			 struct mkpatch_map * map)
{
  int n_symlinks;
  int x;

  n_symlinks = ar_size ((void *)map->common_symlinks[0], 0, sizeof (int));

  for (x = 0; x < n_symlinks; ++x)
    {
      int orig_index;
      int mod_index;
      char * orig_loc;
      char * mod_loc;
      t_ulong orig_perms;
      t_ulong mod_perms;
      char * orig_target;
      char * mod_target;
      int loc_changed;
      int perms_changed;
      int target_changed;

      orig_index = map->common_symlinks[0][x];
      mod_index = map->common_symlinks[1][x];

      invariant (!str_cmp (orig_data->file_tags[orig_index], mod_data->file_tags[mod_index]));

      orig_loc = orig_data->file_locs[orig_index];
      mod_loc = mod_data->file_locs[mod_index];

      orig_perms = (orig_data->file_stats[orig_index].st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
      mod_perms = (mod_data->file_stats[mod_index].st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));

      orig_target = link_target (orig_data->file_paths[orig_index]);
      mod_target = link_target (mod_data->file_paths[mod_index]);

      loc_changed = !!str_cmp (orig_loc, mod_loc);
      perms_changed = (orig_perms != mod_perms);
      target_changed = !!str_cmp (orig_target, mod_target);

      if (loc_changed || perms_changed)
	{
	  safe_printfmt (1, "### change-symlink %s \\\n", mod_loc);
	  if (loc_changed)
	    safe_printfmt (1, "###    was: %s \\\n", orig_loc);
	  safe_printfmt (1, "###    %s %s\n", orig_data->file_tags[orig_index], (perms_changed ? "\\" : ""));
	  if (perms_changed)
	    {
	      safe_printfmt (1, "###    had: --permissions %lo \\\n", orig_perms);
	      safe_printfmt (1, "###    has: --permissions %lo\n", mod_perms);
	    }
	  if (target_changed)
	    {
	      safe_printfmt (1, "###    was-to: %s\n", orig_target);
	      safe_printfmt (1, "###    is-to: %s\n", mod_target);
	    }
	  safe_printfmt (1, "\n");
	}
    }
}


static void
report_file_diffs (struct mkpatch_inventory * orig_data,
		   struct mkpatch_inventory * mod_data,
		   struct mkpatch_map * map)
{
  int n_files;
  int x;
  int diff_fd;
  int errn;
  
  n_files = ar_size ((void *)map->common_files[0], 0, sizeof (int));
  
  diff_fd = tmp_open_anonymous (&errn, O_RDWR, 0);
  if (diff_fd < 0)
    {
      safe_printfmt (2, "mkpatch: unable to create anonymous temp file\n");
      safe_printfmt (2, "  error: %s (%d)\n", errno_to_string (errn), errn);
      panic ("unable to create anonymous temp file");
    }

  for (x = 0; x < n_files; ++x)
    {
      int orig_index;
      int mod_index;
      char * orig_path;
      char * mod_path;
      char * orig_loc;
      char * mod_loc;
      t_ulong orig_perms;
      t_ulong mod_perms;
      int perms_changed;
      int orig_is_binary;
      int mod_is_binary;
      int files_differ;

      
      orig_index = map->common_files[0][x];
      mod_index = map->common_files[1][x];
      
      invariant (!str_cmp (orig_data->file_tags[orig_index], mod_data->file_tags[mod_index]));
      
      orig_path = orig_data->file_paths[orig_index];
      mod_path = mod_data->file_paths[mod_index];

      orig_loc = orig_data->file_locs[orig_index];
      mod_loc = mod_data->file_locs[mod_index];
      
      orig_perms = (orig_data->file_stats[orig_index].st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
      mod_perms = (mod_data->file_stats[mod_index].st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
      
      perms_changed = (orig_perms != mod_perms);

      orig_is_binary = fixme0;
      mod_is_binary = fixme0;

      

      if (!orig_is_binary || !mod_is_binary)
	{
	  files_differ = invoke_diff (diff_fd, orig_path, orig_loc, mod_path, mod_loc);
	}
      else
	{
	  panic ("binary files not handled");
	}

      if (files_differ)
	{
	  safe_printfmt (1, "### patch-file %s\n", mod_loc);
	  safe_printfmt (1, "###    %s\n", orig_data->file_tags[orig_index]);
	  if (perms_changed)
	    {
	      safe_printfmt (1, "###    had: --permissions %lo \\\n", orig_perms);
	      safe_printfmt (1, "###    has: --permissions %lo\n", mod_perms);
	      safe_printfmt (1, "###\n");
	    }
	  safe_lseek (diff_fd, 0, SEEK_SET);
	  while (1)
	    {
	      char buffer[4096];
	      long amt;

	      amt = safe_read_retry (diff_fd, buffer, sizeof (buffer));
	      if (!amt)
		break;
	      safe_write_retry (1, buffer, amt);
	    }
	  safe_printfmt (1, "\n\n");
	}
      safe_lseek (diff_fd, 0, SEEK_SET);
      safe_ftruncate (diff_fd, 0);
    }

  safe_close (diff_fd);
}

static int
invoke_diff (int output_fd, char * orig_path, char * orig_loc, char * mod_path, char * mod_loc)
{
  char * orig_label;
  char * mod_label;
  int pid;

  orig_label = file_name_in_vicinity (0, "orig", orig_loc + 2);
  mod_label = file_name_in_vicinity (0, "mod", orig_loc + 2);

  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, "mkpatch: 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 != 1) && exit_status)
	    {
	      safe_printfmt (2, "\n");
	      safe_printfmt (2, "mkpatch: 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);
	    }
	  lim_free (0, orig_label);
	  lim_free (0, mod_label);
	  return exit_status;
	}
    }
  else
    {
      int errn;
      if (0 > vu_move_fd (&errn, output_fd, 1))
	panic ("mkpatch: unable to redirect stdout for diff");
      execlp ("diff", "diff", "-u", "--text", "-L", orig_label, "-L", mod_label, orig_path, mod_path, 0);
      panic ("execlp for diff returned to caller");
      exit (2);
    }
  panic ("invoke_diff: not reached");
  return 1;
}




static char *
link_target (char * path)
{
  char * link_buf;
  int link_buf_size;
  int link_size;

  link_buf = lim_malloc (0, 1024);
  link_buf_size = 1024;

  while (1)
    {
      link_size = safe_readlink (path, link_buf, link_buf_size);
      
      if (link_size < link_buf_size)
	break;
      
      link_buf = lim_realloc (0, link_buf, link_buf_size * 2);
      link_buf_size *= 2;
    }

  return link_buf;
}


/* tag: Tom Lord Fri Feb 22 06:14:44 2002 (mkpatch.c)
 */
