/*///---------------------------------------------------------------------\\\*/
/*// Copyright (c)               (_)/=\~/_(_)         All rights reserved. \\*/
/*/  2005-2006                (_)  /=\  ~/_  (_)        Released under the  \*/
/*\  Jim Peters            (_)    /=\    ~/_    (_)             GNU GPL v2  /*/
/*\\ http://uazu.net    (_) ____ /=\ ____ ~/_ ____ (_)      http://fsf.org //*/
/*\\\---------------------------------------------------------------------///*/

//
//	Local-end
//

#ifdef HEADER

#else

#include "all.h"

// Static vars
static char *datetime;		// Date-time stamp for the session
static char *sessid;		// Session ID
static char *old_idts;		// ID/timestamp directory from previous session, or 0
static char *idts;		// ID/timestamp directory

// Prototypes
static void dump_clash_summary();

//
//      ^C handling
//

int last_ctrlc_1;       // Time of last two presses of ^C
int last_ctrlc_2;
int abort_now= 0;       // Flag to main loop to abort now

void
ctrlc_handler(int sig) {
   int now= time(0);
   if (now - last_ctrlc_1 < 10) {
      // Three ^C in 10 seconds
      error("USER ABORT without cleanup");
   }
   if (now - last_ctrlc_2 >= 10) {
      warn("*** USER ABORT ***  (Will terminate shortly; use ^C^C^C to force termination.)");
      abort_now= 1;
   }
   last_ctrlc_1= last_ctrlc_2;
   last_ctrlc_2= now;
   if (SIG_ERR == signal(SIGTERM, ctrlc_handler)) 
      error("Can't set SIGTERM signal handler");
   if (SIG_ERR == signal(SIGINT, ctrlc_handler)) 
      error("Can't set SIGINT signal handler");
}

#define CHECK_ABORT if (abort_now) goto abort


//
//	Handle local side
//

void 
do_local(char **av, char *rem_opt) {
   time_t now= time(0);
   struct stat sb;
   struct tm *tm;
   char *p;
   int outcnt= 0;

   // Signal handling
   rem_pid= 0;
   if (SIG_ERR == signal(SIGTERM, ctrlc_handler)) error("Can't set SIGTERM signal handler");
   if (SIG_ERR == signal(SIGINT, ctrlc_handler)) error("Can't set SIGINT signal handler");

   // Connect up
   pipe_conn(av);

   // Look for initial response from remote symirr
   {
      char *cmd, *vers, *on, *mach;
      p= pipe_rdlin();
      if (!starts_with(p, "OK\t"))
	 error("Bad initial response from remote symirr:\n  %s", p);
      cmd= skipTSV(p);
      vers= skipTSV(cmd);
      on= skipTSV(vers);
      mach= skipTSV(on);
      if (!mach) 
	 error("Bad initial response from remote symirr:\n  %s %s %s %s", p, cmd, vers, on);
      if (o_verb) 
	 warn("Connected to remote symirr version %s on %s", vers, mach);
   }

   // Look for capability response from remote symirr
   {
      p= pipe_rdlin();
      if (str_ne(p, "OK") && !starts_with(p, "OK\t"))
	 error("Bad capability response from remote symirr:\n  %s", p);
      // @@@ handle capabilities
   }

   // Send options, if there are any
   if (*rem_opt) {
      pipe_wrlin("opt\t%s", rem_opt);	
      p= pipe_rdlin();
      if (str_ne(p, "OK"))
	 error("Remote symirr options error (%s):\n  %s", rem_opt, p);
   }

   // Create date-time stamp
   tm= gmtime(&now);	// MEMDEBUG-IGNORE
   datetime= StrDupF("%04d%02d%02d-%02d%02d%02d", 
		     tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,  
		     tm->tm_hour, tm->tm_min, tm->tm_sec);

   // Generate a random ID
 retry_session_id:
   free(sessid);
   random_id();
   sessid= StrDup(md5_out62);
#define SESSID_LEN 10
   sessid[SESSID_LEN]= 0;	// Truncate to length SESSID_LEN

   // Form and send the "conn" command, listing all the IDs we know
   // about; also create base directory if it doesn't exist
   {
      char *cmd= StrDupF("conn\t%s\t%s", datetime, sessid);
      char *nam= StrF("%s/_SYMIRR_BASE", base);
      DIR *dir= opendir(nam);
      if (!dir) {
	 if (errno != ENOENT) 
	    error("Can't access directory (%s): %s", strerror(errno), nam);
	 if (mkdir(nam, 0777))
	    error("Can't create directory (%s): %s", strerror(errno), nam);
      } else {
	 struct dirent *ent;
	 while ((ent= readdir(dir))) {
	    if (strlen(ent->d_name) >= SESSID_LEN) {
	       char *freeme= cmd;
	       cmd= StrDupF("%s\t%s", cmd, ent->d_name);
	       free(freeme);
	       // Check for timestamp clashes
	       if (0 == stat(StrF("%s/_SYMIRR_BASE/%s/%s", base, ent->d_name, datetime), &sb)) {
		  // Timestamp is no good; we synced within this last
		  // second; increment and retry
		  p= strchr(datetime, 0);
		  while (1) { if (p[-1] != '9') { (*--p)++; break; } *--p= '0'; }
		  free(cmd);
		  goto retry_session_id;
	       }
	    }
	 }
	 closedir(dir);
      }
      pipe_wrlin("%s", cmd);
   }
   CHECK_ABORT;

   // Wait for reply from "conn" command
   {
      char *id, *ts;
      p= pipe_rdlin();
      if (starts_with(p, "NO")) {	
	 // Remote rejected our unique session ID
	 goto retry_session_id;
      }
      if (!starts_with(p, "OK") ||
	  !(id= skipTSV(p)))
	 error("Remote error to 'conn' command:\n  %s", p);
      ts= skipTSV(id);

      if (ts) {
	 // We are picking up a previous session
	 if (str_eq(id, sessid)) DIE;
	 free(sessid);
	 sessid= StrDup(id);
	 if (!o_basic) {
	    old_idts= StrDupF("%s/_SYMIRR_BASE/%s/%s", base, sessid, ts);
	    if (0 != stat(old_idts, &sb)) 
	       error("Remote end claims we had a previous session, but I can't find it:\n  %s\n"
		     "Run again with -B to ignore this error and do a basic sync", old_idts);
	    if (0 != stat(StrF("%s/local-pc-tree.gz", old_idts), &sb) ||
		0 != stat(StrF("%s/local-pc-changes", old_idts), &sb))
	       error("Remote end claims we had a previous session, but it seems invalid this end:\n  %s\n"
		     "Run again with -B to ignore this error and do a basic sync", old_idts);
	 }
      } else {
	 // We are starting a new session
	 if (str_ne(id, sessid)) DIE;
	 if (mkdir(p= StrF("%s/_SYMIRR_BASE/%s", base, sessid), 0777))
	    error("Can't create directory (%s): %s", strerror(errno), p);
      }

      // Create new session directory
      idts= StrDupF("%s/_SYMIRR_BASE/%s/%s", base, sessid, datetime);
      if (mkdir(idts, 0777))
	 error("Can't create directory (%s): %s", strerror(errno), idts);
      tgz_pathname= StrDupF("%s/backups.tgz", idts);

   }

   // We now have setup: sessid, idts, old_idts; old_idts if set
   // indicates we're not doing a basic sync, and that there is a
   // previous session with valid local-pc-tree.gz and
   // local-pc-changes.
   CHECK_ABORT;

   // Clean up history
   status("Cleaning up old history data ...");
   clean_and_pack(idts);
   status(0);

   // Fetch the tree, either the diff or the whole thing
   if (!o_basic && old_idts) {
      // We can do an extended sync
      if (!no_xtreediff &&
	  0 == stat(StrF("%s/remote-tree.gz", old_idts), &sb)) {
	 // Fetch a diff to save time
	 outcnt= fetch_xtreediff();
      } else {
	 // We synced the other way around last time; diff not possible
	 outcnt= fetch_xtree();
      }
   } else {
      // We have to exchange the whole tree and do a basic sync
      if (o_auto == 1) error("Automated mode not permitted for first sync (-aa to override)");
      if (!o_basic) o_basic= 'B';
      outcnt= fetch_tree();
   }
   // The tree-writing routines above may return -1 on abort
   if (outcnt < 0) goto abort;
   CHECK_ABORT;

   // Create local-pc-changes to flag that local-pc-tree.gz is complete
   chlog_open(idts);

   // Okay, now the user has to OKAY the action-list
   if (outcnt && !o_auto) {
      while (1) {
	 SMLoad *ld;
	 int act= 0;

	 call_editor(StrF("%s/actions", idts));

	 ld= smload(StrF("%s/actions", idts));
	 while (smload_do(ld)) {
	    p= ld->out;
	    while (isspace(*p)) p++;
	    if (p[0] == '#' || !p[0]) { free(ld->out); ld->out= 0; continue; }
	    if (starts_with(p, "EDIT")) { act= 'E'; break; }
	    break;
	 }
	 smload_del(ld);

	 if (act == 'E') continue;
	 break;
      }
      // Cleanup a bit
      unlink(StrF("%s/actions~", idts));
   }

   // Now run the action-list
   if (outcnt) {
      SMLoad *ld= smload(StrF("%s/actions", idts));
      SMAct *act= smact(&ld->out, base, idts);
      int cnt= 0;
      int clash= 0;

      while (smload_do(ld)) {
	 CHECK_ABORT;
	 if (strstr(ld->out, ".CLASH-")) clash++;
	 if (ld->out[0] != '#') status("!%2d%%: %s", (cnt++)*100/outcnt, ld->out); 
	 smact_do(act);
      }
      status(0);
      smload_del(ld);
      smact_del(act);

      // Dump out a summary of all the clashes
      if (clash) dump_clash_summary();
   }
   
   // Tell user if there was nothing to do
   if (!outcnt && o_verb) warn("No actions required");

   // Got through okay ... finish up
   tgz_close();
   pipe_disconn();
   
   return;

 abort:
   pipe_disconn();
   error("*** ABORTED CLEANLY ***");
}

void 
action_list_pre(char **pp, SMSave *sv, int ext) {
   if (!o_auto) {
      *pp= StrDup("EDIT"); 
      smsave_do(sv);
      if (!ext) {
	 if (o_basic == 'M' || o_basic == 'S') {
	    *pp= StrDup("# *** NOTE: THIS IS A BASIC MASTER-SLAVE FIRST-TIME SYNC, SO CHECK ***");
	    smsave_do(sv);
	    *pp= StrDup("# *** OVER CAREFULLY, ESPECIALLY ANY WARNINGS OR DELETIONS WITH rm ***"); 
	    smsave_do(sv);
	 } else {
	    *pp= StrDup("# *** NOTE: THIS IS A BASIC, FIRST-TIME SYNC, SO CHECK OVER CAREFULLY ***"); 
	    smsave_do(sv);
	 }
      }
      *pp= StrDup("# ACTION LIST: please check over and edit if necessary."); 
      smsave_do(sv);
      *pp= StrDup("# Once checked, delete the EDIT keyword above, save and quit."); 
      smsave_do(sv);
      *pp= StrDup("# To abort, delete all the commands in this file, save and quit."); 
      smsave_do(sv);
      *pp= StrDup("# ----------"); 
      smsave_do(sv);
   }
}

static void 
dump_clash_summary() {
   SMLoad *ld;
   int cnt;
   FILE *out= fopen(StrF("%s/clashes", idts), "w");
   
   warn("=== Clashes generated from command-line:\n  %s", command);
   fprintf(out, "=== Clashes generated from command-line:\n  %s\n", command);

   cnt= 0;
   ld= smload(StrF("%s/actions", idts));
   while (smload_do(ld)) {
      if (strstr(ld->out, ".CLASH-") && starts_with(ld->out+2, "mv")) {
	 if (0 == cnt++) {
	    warn("=== New clashes generated this session:");
	    fprintf(out, "=== New clashes generated this session:\n");
	 }
	 warn("%s", ld->out);
	 fprintf(out, "%s\n", ld->out);
      }
      free(ld->out); ld->out= 0;
   }
   smload_del(ld);

   cnt= 0;
   ld= smload(StrF("%s/actions", idts));
   while (smload_do(ld)) {
      if (strstr(ld->out, ".CLASH-") && !starts_with(ld->out+2, "mv")) {
	 if (0 == cnt++) {
	    warn("=== Clashes transferred this session:");
	    fprintf(out, "=== Clashes transferred this session:\n");
	 }
	 warn("%s", ld->out);
	 fprintf(out, "%s\n", ld->out);
      }
      free(ld->out); ld->out= 0;
   }
   smload_del(ld);

   warn("=== (see %s/clashes.gz)", idts);

   if (fclose(out))
      error("Error writing 'clashes' file:\n  %s/clashes", idts);
}

static void 
dump(char *nam, char *out, int act) {
   static int prev_act= 0;
   char flag= (act - ((act < prev_act) ? 0 : prev_act)) ? '*' : ' ';
   prev_act= act;

   if (out && !isEOF(out)) 
      warn("%12.12s %c== %s", nam, flag, out);
   else if (out) 
      warn("%12.12s %c||", nam, flag);
   else 
      warn("%12.12s %c--", nam, flag);
}
#define DUMP(xx) DEBUG dump(#xx, xx, act)
#define DUMP_NL() DEBUG warn("")

int 
fetch_xtreediff() {
   // We can fetch a diff to save time
   int end= 0;
   int act;
   char *inp= 0;
   int out= 0, inpcnt= 0;
   
   SMLoad *ld0= smload(StrF("%s/remote-tree.gz", old_idts));
   SMPatch *pt= smpatch(&ld0->out, &inp);
   SMTsave *sv2= smtsave(StrF("%s/remote-tree.gz", idts), &pt->out, 1);
   SMLoad *ld1= smload(StrF("%s/local-pc-tree.gz", old_idts));
   SMLoad *ld2= smload_sorted(StrF("%s/local-pc-changes", old_idts));
   SMMapch *map= smmapch(&ld2->out);
   SMMerge *mrg= smmerge(&ld1->out, &map->out);
   SMTsave *sv0= smtsave(StrF("%s/local-pc-tree.gz", idts), &mrg->out, 0);
   SMTree *tr= smtree(base);
   SMXtree *xtr= smxtree(&sv0->out, &tr->out);
   SMXcomp *xc= smxcomp(&xtr->out, &sv2->out);
   SMSave *sv1= smsave(StrF("%s/actions", idts), &xc->out);
   
   // Force a few initial strings into the actions file
   action_list_pre(&xc->out, sv1, 1);
   
   pipe_wrlin("xtreediff");
   while (!abort_now) {
      if (!inp && !end) {
	 inp= StrDup(pipe_rdlin());
	 if (starts_with(inp, "END")) end= 1;
	 if (++inpcnt % 10 == 0) status("!Fetching xtreediff: %d", inpcnt);
      }

      act= 0;			DUMP(inp);
      act += smload_do(ld0); 	DUMP(ld0->out);
      act += smpatch_do(pt); 	DUMP(pt->out);
      act += smtsave_do(sv2);	DUMP(sv2->out);
      act += smload_do(ld1); 	DUMP(ld1->out);
      act += smload_do(ld2); 	DUMP(ld2->out);
      act += smmapch_do(map); 	DUMP(map->out);
      act += smmerge_do(mrg); 	DUMP(mrg->out);
      act += smtree_do(tr); 	DUMP(tr->out);
      act += smtsave_do(sv0); 	DUMP(sv0->out);
      act += smxtree_do(xtr); 	DUMP(xtr->out);
      act += smxcomp_do(xc); 	DUMP(xc->out);
      out += (xc->out && !isEOF(xc->out) && xc->out[0] != '#');
      act += smsave_do(sv1);    DUMP_NL();

      if (end && !act) break;
   }
   
   smload_del(ld0);
   smpatch_del(pt);
   smtsave_del(sv2);
   smload_del(ld1);
   smload_del(ld2);
   smmapch_del(map);
   smmerge_del(mrg);
   smtree_del(tr);
   smtsave_del(sv0);
   smxtree_del(xtr);
   smxcomp_del(xc);
   smsave_del(sv1);

   status(0);
   return abort_now ? -1 : out;
}

int 
fetch_xtree() {
   int end= 0;
   int act;
   char *inp= 0;
   int out= 0, inpcnt= 0;
   
   SMTsave *sv2= smtsave(StrF("%s/remote-tree.gz", idts), &inp, 1);
   SMLoad *ld1= smload(StrF("%s/local-pc-tree.gz", old_idts));
   SMLoad *ld2= smload_sorted(StrF("%s/local-pc-changes", old_idts));
   SMMapch *map= smmapch(&ld2->out);
   SMMerge *mrg= smmerge(&ld1->out, &map->out);
   SMTsave *sv0= smtsave(StrF("%s/local-pc-tree.gz", idts), &mrg->out, 0);
   SMTree *tr= smtree(base);
   SMXtree *xtr= smxtree(&sv0->out, &tr->out);
   SMXcomp *xc= smxcomp(&xtr->out, &sv2->out);
   SMSave *sv1= smsave(StrF("%s/actions", idts), &xc->out);
   
   // Force a few initial strings into the actions file
   action_list_pre(&xc->out, sv1, 1);
   
   pipe_wrlin("xtree");
   while (!abort_now) {
      if (!inp && !end) {
	 inp= StrDup(pipe_rdlin());
	 if (starts_with(inp, "END")) end= 1;
	 if (++inpcnt % 10 == 0) status("!Fetching xtree: %d", inpcnt);
      }

      act= 0;		       DUMP(inp);
      act += smtsave_do(sv2);  DUMP(sv2->out);
      act += smload_do(ld1);   DUMP(ld1->out);
      act += smload_do(ld2);   DUMP(ld2->out);
      act += smmapch_do(map);  DUMP(map->out);
      act += smmerge_do(mrg);  DUMP(mrg->out);
      act += smtree_do(tr);    DUMP(tr->out);
      act += smtsave_do(sv0);  DUMP(sv0->out);
      act += smxtree_do(xtr);  DUMP(xtr->out);
      act += smxcomp_do(xc);   DUMP(xc->out);
      out += (xc->out && !isEOF(xc->out) && xc->out[0] != '#');
      act += smsave_do(sv1);   DUMP_NL();

      if (end && !act) break;
   }
   
   smtsave_del(sv2);
   smload_del(ld1);
   smload_del(ld2);
   smmapch_del(map);
   smmerge_del(mrg);
   smtree_del(tr);
   smtsave_del(sv0);
   smxtree_del(xtr);
   smxcomp_del(xc);
   smsave_del(sv1);

   status(0);
   return abort_now ? -1 : out;
}

int 
fetch_tree() {
   // We have to fetch the whole tree and do a simple comparison
   int end= 0;
   int act;
   char *inp= 0;
   int out= 0, inpcnt= 0;
   
   SMTsave *sv2= smtsave(StrF("%s/remote-tree.gz", idts), &inp, 1);
   SMTree *tr= smtree(base);
   SMTsave *sv0= smtsave(StrF("%s/local-pc-tree.gz", idts), &tr->out, 0);
   SMComp *cmp= smcomp(&sv0->out, &sv2->out);
   SMSave *sv1= smsave(StrF("%s/actions", idts), &cmp->out);
   
   // Force a few initial strings into the actions file
   action_list_pre(&cmp->out, sv1, 0);
   
   pipe_wrlin("tree");
   while (!abort_now) {
      if (!inp && !end) {
	 inp= StrDup(pipe_rdlin());
	 if (starts_with(inp, "END")) end= 1;
	 if (++inpcnt % 10 == 0) status("!Fetching tree: %d", inpcnt);
      }

      act= 0;		       DUMP(inp);
      act += smtsave_do(sv2);  DUMP(sv2->out);
      act += smtree_do(tr);    DUMP(tr->out);
      act += smtsave_do(sv0);  DUMP(sv0->out);
      act += smcomp_do(cmp);   DUMP(cmp->out);
      out += (cmp->out && !isEOF(cmp->out) && cmp->out[0] != '#');
      act += smsave_do(sv1);   DUMP_NL();
      
      if (end && !act) break;
   }
   
   smtsave_del(sv2);
   smtree_del(tr);
   smtsave_del(sv0);
   smcomp_del(cmp);
   smsave_del(sv1);

   status(0);
   return abort_now ? -1 : out;
}


//
//	Bring up the editor
//

void 
call_editor(char *fnam) {
   pid_t pid= fork();
   if (pid < 0) error("Can't fork()");
   if (pid == 0) {
      char *p;
      if (o_editor) {
	 execlp(o_editor, o_editor, fnam, 0);
	 error("Can't execute editor: %s", o_editor);
      }
      // Just take the first of these that will work; if exec returns,
      // it failed
      if ((p= getenv("VISUAL"))) execlp(p, p, fnam, 0);
      if ((p= getenv("EDITOR"))) execlp(p, p, fnam, 0);
      execlp("editor", "editor", fnam, 0);
      execlp("emacs", "emacs", fnam, 0);
      execlp("vi", "vi", fnam, 0);
      error("Can't find a working editor on this system");
   }

   {   
      int stat;
      char *p;
      pid_t rv;
      int cnt= 0;
      
      while (0 == (rv= waitpid(pid, &stat, WNOHANG))) {
	 usleep(500000);	// 500ms
	 cnt += 500;
	 if (cnt >= 30000) {	// Every 30s
	    cnt= 0;
	    pipe_wrlin("ping");
	    p= pipe_rdlin();
	    if (!starts_with(p, "OK"))
	       error("Remote ping gave unexpected error:\n  %s", p);
	 }
      }
      if (rv < 0) 
	 error("Error waiting for editor (%s)", strerror(errno));
      if (!WIFEXITED(stat) || 0 != WEXITSTATUS(stat))
	 error("Editor failed; aborting");
   }
}

// Clean up _SYMIRR_BASE directories
void 
clean_and_pack(char *idts) {
   char *iddir= StrDup(idts);
   char *p, *q;
   DIR *dir;
   struct dirent *ent;
   List *dl= 0;
   int hist= 0;		// Count of directories with valid history
   struct stat sb;
   char xprev[16];	// Expected previous directory

   // Truncate final path-segment
   if (!(p= strrchr(iddir, '/'))) DIE;
   *p= 0;
   
   // Make a list of all the sub-directories
   if (!(dir= opendir(iddir))) 
      error("Can't read directory:\n  %s", iddir);
   while ((ent= readdir(dir))) {
      p= ent->d_name;
      for (q= "dddddddd-dddddd"; *q; q++, p++)
	 if (!((*q == 'd') ? isdigit(*p) : *p == *q))
	    break;
      if (*q || *p) continue;
      list_add(&dl, StrDup(ent->d_name));
   }
   closedir(dir);

   // Run through the sub-directories in reverse order, treating the
   // first three with a valid history differently.  Write old
   // actions+clashes to TGZ; move backups.tgz to parent.
   list_rsort(&dl);
   xprev[0]= 0;
   while (dl) {
      char *ts= list_pop(&dl);
      char *xx, *yy;
      int valid;
      int skip= str_eq(ts, xprev);

      // Predict the next one, assuming running sequence.  We have to
      // skip all directories running in sequence like this, because
      // they may be running far ahead of real time if there are many
      // syncs a second, as in the test suite, and deleting a
      // directory would therefore result in out-of-order timestamps
      // -- very confusing.
      strcpy(xprev, ts);
      for (p= xprev + 14; p >= xprev; p--) {
	 if ('0' != (*p)--) break;
	 *p= '9';
      }
      if (skip) { free(ts); continue; }

      valid= (0 == stat(StrF("%s/%s/local-pc-tree.gz", iddir, ts), &sb) &&
	      0 == stat(StrF("%s/%s/local-pc-changes", iddir, ts), &sb));
      if (valid) hist++;

      // I could clean up invalid recent sessions (!valid), but maybe
      // they are useful for debugging; they will get cleaned up when
      // they are beyond 3 good sessions in the past.

      if (hist >= 3) {
	 // Move 'actions' file to TGZ, delete
	 xx= StrDupF("%s/%s/actions", iddir, ts);
	 yy= StrDupF("%s-actions", ts);
	 if (0 == stat(xx, &sb)) {
	    if (!tgz_out) tgz_open(StrF("%s/actions_clashes.tgz", iddir));
	    tgz_append_as(xx, yy);
	    if (0 != unlink(xx))
	       error("Delete failed (%s):\n  %s", strerror(errno), xx);
	 }
	 free(xx); free(yy);

	 // Move 'clashes' file to TGZ, delete
	 xx= StrDupF("%s/%s/clashes", iddir, ts);
	 yy= StrDupF("%s-clashes", ts);
	 if (0 == stat(xx, &sb)) {
	    if (!tgz_out) tgz_open(StrF("%s/actions_clashes.tgz", iddir));
	    tgz_append_as(xx, yy);
	    if (0 != unlink(xx))
	       error("Delete failed (%s):\n  %s", strerror(errno), xx);
	 }
	 free(xx); free(yy);

	 // Move 'backups.tgz' file up to parent
	 xx= StrDupF("%s/%s/backups.tgz", iddir, ts);
	 yy= StrDupF("%s/%s-backups.tgz", iddir, ts);
	 if (0 == stat(xx, &sb) && 0 != rename(xx, yy))
	    error("Rename failed (%s):\n  %s\n  %s", strerror(errno), xx, yy);
	 free(xx); free(yy);

	 // Get rid of local-tree.gz and remote-tree.gz
	 xx= StrF("%s/%s/local-tree.gz", iddir, ts);
	 if (0 == stat(xx, &sb) && 0 != unlink(xx))
	    error("Delete failed (%s):\n  %s", strerror(errno), xx);
	 xx= StrF("%s/%s/remote-tree.gz", iddir, ts);
	 if (0 == stat(xx, &sb) && 0 != unlink(xx))
	    error("Delete failed (%s):\n  %s", strerror(errno), xx);

	 // Get rid of local-pc-changes and local-pc-tree.gz
	 xx= StrF("%s/%s/local-pc-changes", iddir, ts);
	 if (0 == stat(xx, &sb) && 0 != unlink(xx))
	    error("Delete failed (%s):\n  %s", strerror(errno), xx);
	 xx= StrF("%s/%s/local-pc-tree.gz", iddir, ts);
	 if (0 == stat(xx, &sb) && 0 != unlink(xx))
	    error("Delete failed (%s):\n  %s", strerror(errno), xx);

	 // Remove the directory itself
	 xx= StrF("%s/%s", iddir, ts);
	 if (0 != rmdir(xx))
	    warn("Can't remove old timestamped directory (%s)\n  %s", strerror(errno), xx);
      }

      free(ts);
   }

   free(iddir);
   if (tgz_out) tgz_close();
}
   
#endif

/*///---------------------------------------------------------------------\\\*/
/*// )`( )()/)(`/\`//\`          (_)/=\~/_(_)           '/\\'/\')(\()( )'( \\*/
/*/ ()(')()\)(/`/\\ `         (_)  /=\  ~/_  (_)          ' //\'\)(/()(`)() \*/
/*\  )`()( )(/'            (_)    /=\    ~/_    (_)             `\)( )()'(  /*/
/*\\  ()()(`)(/         (_) ____ /=\ ____ ~/_ ____ (_)          \)(')()()  //*/
/*\\\---------------------------------------------------------------------///*/
