/************************************************************
**
** Application: SyncDiscs
**
** Title:       c.syncdiscs
**
*************************************************************/

/*
*
* Copyright (c) 2017, David Pilling and Chris Johnson
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*   * Redistributions of source code must retain the above copyright
*     notice, this list of conditions and the following disclaimer.
*   * Redistributions in binary form must reproduce the above
*     copyright notice, this list of conditions and the following
*     disclaimer in the documentation and/or other materials provided
*     with the distribution.
*   * Neither the name of the copyright holder nor the names of their
*     contributors may be used to endorse or promote products derived
*     from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/


/******************************************************************************
 *                                                                            *
 * Note that in some of David's original comments he refers to 'server' and   *
 * 'local'. This is terminology relating to Acorn's network computer. In      *
 * this context we can take this to be the equivalent of 'primary' and        *
 * 'secondary'.                                                               *
 *                                                                            *
 ******************************************************************************/


/* Last error log ID = 126 */

/* Include files */
/* from standard clib */
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <locale.h>
#include <time.h>
#include <ctype.h>

/* from TASKLIB */
#include "Tasklib:h.swis"
#include "Tasklib:h.kernel"
#include "Tasklib:h.os"
#include "Tasklib:h.wimp"
#include "Tasklib:h.wimpt"
#include "Tasklib:h.werr"
#include "Tasklib:h.flex"
#include "Tasklib:h.sprite"
#include "Tasklib:h.transform"
#include "Tasklib:h.bbc"
#include "Tasklib:h.wos"
#include "Tasklib:h.err"
#include "Tasklib:h.fsx"
#include "Tasklib:h.etc"
#include "Tasklib:h.swis"
#include "Tasklib:h.bf"
#include "Tasklib:h.crc"
#include "Tasklib:h.bits"
#include "Tasklib:h.scrap"
#include "Tasklib:h.timex"
#include "Tasklib:h.poll"
#include "Tasklib:h.conf"
#include "Tasklib:h.log"
#include "Tasklib:h.deb"


/* from application */
#include "h.debugDefs"

//#include "h.compswitch"

#include "h.constants"
#include "h.reslink"
#include "h.arcs"
#include "h.getdir"
#include "h.zstring"
#include "h.zone"
#include "h.dbox"
#include "h.dirlist"
#include "h.progress"
#include "h.taskmanage"
#include "h.syncdiscs"



/************************************************************/
/* global variables */

static char oldpath[FSMAXWIMPMSGPATH];
static char newpath[FSMAXWIMPMSGPATH];
static char scrappath[FSMAXWIMPMSGPATH];

static buffer *bfp;
static char logpath[FSMAXWIMPMSGPATH];

static int  doscrap;
static int  timeresolution;

static buffer *cfp;
static int  csh;


#define FA_COPYANDDELETE 6
#define FA_COPY 0
#define FA_DELETE 2
#define FA_FORCE 1<<2
#define FA_VERBOSE 1

#define ERR_LOG "<SyncDiscs$Dir>.errlog"


typedef struct sd_flags
{
  int         first_pass;
} sd_flags;


typedef struct cmp_op
{
  char       *list;
  int         offset;
  int         size;
} cmp_op;


#define COMPARE_TYPES  9

#define SEC_NEWER      0
#define SEC_OLDER      1
#define SEC_DIFFLEN    2
#define SEC_DIFFTYPE   3
#define SEC_EXTRA      4
#define PRI_EXTRA      5
#define FILEDIR_CLASH  6
#define IGNORE         7
#define IGNORE_SEC     8


static char *filetype_str[3] = {"file", "directory", "image file"};

/**************************************************************************/
/* exported variables */
int         sync_running;
int         sync_pause;
int         sync_stop;
int         endtaskhandle;

fileraction_count farunning;


/**************************************************************************/
/* global variables */
static sd_flags flg;

static syncsettings sync;

static int  primary_length;
static int  secondary_length;

static buffer *errbfp;
static int  just_started = 1;
static int  sync_error;
static int  compare_output;

static cmp_op compare_result[COMPARE_TYPES];

static arctime this_time;
static arctime next_time;

static char *primary;
static char *secondary;
static char *scrap;

static int check_target_access;

/* task kill flags */
//static int sparkfs_killed;
//static int trap_delete_killed;

/**************************************************************************/
/* For various checks on paths and drives */

#define PRIM   0
#define SEC    1
#define SCRAP  2

typedef struct path_details
{
  char filesystem[20];
  char device[32];
  char rootdir[64];
}path_details;

static path_details paths[3];


/* macros */
#define LOG_ERR(x) err = x; if (err) make_errlog_entry (err->errmess);


/**************************************************************************/
/* Function prototypes */
static os_error *logfile_manage (syncsettings *sync, int *open);
static os_error *copy_dir_file_by_file (char *primary, char *secondary, int move);
static void      synczero (int handle);
static os_error *store_cmp_list_item_2 (char *objpath, int index);
static os_error *store_cmp_list_item (int acn, int n, int index);
static os_error *output_cmp_list (int index, char* desc);




/**************************************************************************/

static void do_polls (int polls)

{
  int i;

  for (i = 0; i < polls; i++) poll();
  return;
}



/**************************************************************************/

/* flags for copy action */
#define FS_RECURSE 1
#define FS_FORCE   (1 << 1)
#define FS_DELETESOURCE (1 << 7)

os_error * fs_copyobjects(char * from, char * to, int action)

{
 os_regset  rx;

 rx.r[0] = 26;
 rx.r[1] = (int)from;
 rx.r[2] = (int)to;
 rx.r[3] = action;
 rx.r[4] = 0;
 rx.r[5] = 0;
 rx.r[6] = 0;
 rx.r[7] = 0;
 rx.r[8] = 0;

 return (os_swix (OS_FSControl, &rx));
}




/***************************************************************************
 * Wimp poll routine used during the syncing process that will check       *
 * whether the pause button (in the status window) has been pressed. If so *
 * it will 'mark time' until either the continue button (or the stop       *
 * button) is pressed. It also controls the number of concurrent           *
 * Filer_Action processes.                                                 *
 ***************************************************************************/

static int poll_pause_or_stop (void)
{
  char        temp[64];

  if (!sync.multitask)
    return (0);

  poll ();

  /**************************************************************************
   * The following is part of the controlling of number of concurrent       *
   * Filer_Action tasks running. If too many are running concurrently, then *
   * the responsiveness of the desktop becomes worse than treacle.          *
  ***************************************************************************/

  if (farunning.count >= sync.num_fa)
  {
    do
    {
      poll ();
    }
    while (!sync_pause && !sync_stop &&
           (farunning.count > sync.num_fa_resume));
  }

  /* The following implements the pause/continue and stop facility when
   * multitasking
   */
  if (sync_pause)
  {
    /* We don't need null events while paused */
    remzeroevent (synczero, 0);
    do
    {
      poll ();
      if (sync.multiple_fa && (farunning.count > 0))
      {
        sprintf (temp, "Scan paused - %d filer_actions to complete",
                 farunning.count);
        progress_update_status_field (temp);
      }
      else
      {
        progress_update_status_field ("Scan paused - click Continue");
      }
    }
    while (sync_pause && !sync_stop);

    /* Now reregister zero event */
    addzeroevent (synczero, 0);
  }
  return (sync_stop);
}




/************************************************************/

static os_error *comparewrite (char *format, ...)
{
  os_error   *err;
  va_list     args;
  char       *v;

  err = flex_alloc ((flex_ptr)&v, 1024);
  if (!err)
  {
    va_start (args, format);
    vsprintf (v, format, args);

    va_end (args);

    err = bf_printf (cfp, v);
    flex_free ((flex_ptr)&v);
  }
  return (err);
}


#if 0

/************************************************************/

static os_error *comparewritef (int acn, int n, char *format)
{
  os_error   *err;
  char        s[NAMELEN];

  s[0] = 0;

  filepath1 (s, acn, n);

  err = bf_printf (cfp, format, s);

  return (err);
}

#endif


/************************************************************/

/* to separate the path and leaf of a filename - returns a
 * pointer to the start of the leaf
 */

static char *leaf (char *fn)
{
  char       *p;

  p = strrchr (fn, (int) '.');
  if (p)
    p++;
  return (p);
}


/***************************************************************
 *                                                             *
 * Function to check whether next update of status window      *
 * is due - if so adds 100 cs to the current time to give the  *
 * next time of update and returns (1), else returns (0)       *
 *                                                             *
 ***************************************************************/

static int next_update_due (void)
{
  int  update_now;

  getcurtime (&this_time);

  /* cmptimex returns values of 1 or -1 depending on which of the
   * two times is the larger
   */
  update_now = cmptimex (&this_time, &next_time);
  if (update_now == 1)
  {
    /* this_time is the larger */
    next_time = this_time;
    addtimex (&next_time, 100, 1);
    return (1);
  }
  else
  {
    return (0);
  }
}




/***************************************************************************
 *                                                                         *
 * The following function uses the buffer library to open the err log      *
 * file, add the log entry, and then close the file again. By closing the  *
 * file, entries are not lost if SyncDiscs exits ignominiously.            *
 ***************************************************************************/

os_error   *make_errlog_entry (int err_id, char *log_report_format, ...)
{
  os_error   *err;
  va_list     args;
  char       *v;

  fstat       f;
  buffer      errbf;
  char        st[64];

  /* pepare the string for the report according to the format string and
   * arguments */

  err = flex_alloc ((flex_ptr)&v, 1024);
  if (!err)
  {

    va_start (args, log_report_format);
    vsprintf (v, log_report_format, args);
    va_end (args);

    /* Now open the buffered file, output the report and then close the file
     * again */

    fs_stat (ERR_LOG, &f);
    if (just_started == 1)
    {
      /* First error since SyncDiscs was started - clear the existing file */
      err = bf_open (ERR_LOG, 'w', DEFBUFFSIZE, &errbf);
      /* Reset the flag */
      just_started = 0;
    }
    else
    {
      /* mode = 'u', update */
      err = bf_open (ERR_LOG, 'u', DEFBUFFSIZE, &errbf);
      if (f.object)
        err = bf_seek (&errbf, f.length);
    }
    if (!err)
    {
      errbfp = &errbf;
      if (sync_error == 0)
      {
        /* This is first error logged for this job. Write a header with time and date */
        writesystimedate (st);
        bf_printf (errbfp, "-------------------------------------------\n");
        bf_printf (errbfp, "SyncDiscs error log at %s\n", st);
        bf_printf (errbfp, "-------------------------------------------\n\n");
        sync_error = 1;
      }
      if (err_id > 0)
      {
        bf_printf (errbfp, "Code %d: %s\n", err_id, v);
      }
      else
      {
        bf_printf (errbfp, "%s\n", v);
      }

      err = bf_closec (errbfp, err, ERR_LOG, TEXT);
    }
    flex_free ((flex_ptr)&v);
  }
  return (err);
}



/************************************************************/

static int source_and_dest_on_same_device (char *s1, char *s2)
{
  os_error   *err;
  char       *st1;
  char       *st2;
  char       *ps;
  int        res = 0;

  err = flex_alloc ((flex_ptr)&st1, FSMAXWIMPMSGPATH);
  if (!err)
  {
    err = flex_alloc ((flex_ptr)&st2, FSMAXWIMPMSGPATH);
    if (!err)
    {
      /* make a local copy of the strings */
      strcpy (st1, s1);
      strcpy (st2, s2);
      /* Assume we are dealing with full paths. Look for the root directory $
       * If found the left part of the string will be the full file system
       * + device name
       */
      ps = strchr (st1, '$');
      if (ps)
      {
        ps--;
        ps[0] = 0;
      }
      ps = strchr (st2, '$');
      if (ps)
      {
        ps--;
        ps[0] = 0;
      }
      if (strcmp (st1, st2) == 0) res = 1;
      flex_free ((flex_ptr)&st2);
    }
    flex_free ((flex_ptr)&st1);
  }
  return (res);
}


/************************************************************/

static os_error *copy_file (char *from, char *to, int move)
{
  os_error   *err;
  os_error   *er1;
  os_regset   rx;
  int         handle;
  char       *s;
  char       *p;
  int         i;
  int         type;
  int         bufsize = (FSMAXWIMPMSGPATH << 1) + 32;
  int  fs_action = FS_RECURSE | FS_FORCE;

  if (move)
  {
    /* set the action flag here for copy/delete by fs_control */
    /* saves another if(move) later on */
    fs_action |= FS_DELETESOURCE;

    /***********************************************************************
     *
     * As the quickest move, try a simple rename first. This will only     *
     * work if the source and destination are on the same device and the   *
     * destination does not already exist. If there is no error, then the  *
     * rename must have worked, so we can just return to caller.           *
     * Otherwise we need to execute the full copy and delete to do the     *
     * move.                                                               *
     *
     ***********************************************************************/

    if (source_and_dest_on_same_device (from, to))
    {
      /* we can try a rename ... but first make sure the destination
       * does not exist. Is it safe to ignore errors?
       */
      er1 = fs_exists (to, &type);
      if (!er1 && type)
      {
        er1 = fs_wipe (to);
      }
      if (!er1)
      {
        er1 = fs_rename (from, to);
      }
      if (er1)
      {
        /* log the error and carry on */
        make_errlog_entry (100, "Moving file %s: %s\n",from, er1->errmess);
      }
      else
        return (NULL);
    }

  }

  err = flex_alloc ((flex_ptr)&s, bufsize);
  if (err) return (err);

  if (sync.multitask)
  {
    /* use filer_action to do multitasking */
    /* start up a filer_action operation */
    err = wos_startnewtask ("Filer_Action", &handle);

    if (!err && (handle != 0))
    {
      /***********************************************************************
       *                                                                     *
       * Store the task handle as part of method of controlling the number   *
       * of concurrent Filer_Action processes.                               *
       *                                                                     *
       ***********************************************************************/

      farunning.handles[farunning.count] = handle;
      farunning.count++;
      progress_update_fa_count_field();

      /* send source directory */
      strcpy (s, from);
      p = leaf (s);
      if (p)
        *(p - 1) = 0;

      rx.r[0] = handle;
      rx.r[1] = (int) s;        /* parent directory */
      os_swix (FilerAction_SendSelectedDirectory, &rx);

      /* send source file/dir name */
      rx.r[0] = handle;
      rx.r[1] = (int) p;        /* leaf */
      os_swix (FilerAction_SendSelectedFile, &rx);

      /* now start the operation */
      /* find 'destination' directory */
      strcpy (s, to);
      p = leaf (s);
      if (p)
        *(p - 1) = 0;

      rx.r[0] = handle;
      if (move)
      {
        rx.r[1] = FA_COPYANDDELETE;        /* move by copy and delete */
      }
      else
      {
        rx.r[1] = FA_COPY;        /* copy */
      }
      rx.r[2] = FA_FORCE;                /* force */
      if (sync.verbose_fileractions)
      {
        rx.r[2] |= FA_VERBOSE;
      }
      rx.r[3] = (int) s;
      rx.r[4] = strlen (s) + 1;
      os_swix (FilerAction_SendStartOperation, &rx);

      /* wait for operation to complete if process involves a copy and
       * delete, otherwise don't wait for thread to complete
       */
      if (move || !sync.multiple_fa)
      {
        endtaskhandle = 0;

        while (1)
        {
          poll ();
          if (handle == endtaskhandle)
            break;
          if (sync_stop)
            break;
        }

        /* has the user pressed Pause while FA has been completing? if so
         * poll until continue or stop
        */

        if (sync_pause)
        {
          do
          {
            poll ();
          }
          while (sync_pause && !sync_stop);
        }
      }
      else
      {
        /**********************************************************************
         *                                                                    *
         * we need several poll()s here to enable the filer_actions to get    *
         * going. If only one poll, or none at this point, the                *
         * filer_actions are initialised but queue 'pending'. With a lot of   *
         * copying to do there may be very many still to complete when        *
         * SyncDiscs has completed its scan.                                  *
         *                                                                    *
         **********************************************************************/

        for (i = 0; i < 5; i++)
        {
          if (poll_pause_or_stop ())
            break;
        }
      }
    }
    else
    {
      /* problem starting filer_action - do a command line copy */
      er1 = fs_copyobjects(from, to, fs_action);
      if (er1)
      {
        /* log the error and carry on */
        make_errlog_entry (101, "Copy file %s: %s\n",from, er1->errmess);
      }
    }
  }
  else
  {
    er1 = fs_copyobjects(from, to, fs_action);
    if (er1)
    {
      /* log the error and carry on */
      make_errlog_entry (102, "Copy file %s: %s\n",from, er1->errmess);
    }
  }

  flex_free ((flex_ptr)&s);

  return (err);
}




/************************************************************/

static os_error *set_access( char * name, int acc)
{
 os_filestr  fiblock;

 fiblock.action = 1;
 fiblock.name = name;
 fiblock.loadaddr = 0;
 fiblock.execaddr = 0;
 fiblock.start = 0;
 fiblock.end = acc;
 return(os_file(&fiblock));

}





/************************************************************/

static os_error *filecopy (char *from, char *to, int move)
{
  os_error  *err;
  fstat      f;
  fstat      f2;

  BOOL       open = FALSE;
  int        old_acc;
  int        acc;


  err = NULL;

  if (cstrcmp (logpath, from))
  {
    fs_create_dir_chain (to);

    fs_stat (from, &f);

    switch (f.object)
    {
      case 1:
      case 3:
        /****************************************************************
         *                                                              *
         * object is a file or an archive                               *
         * Do some checks - is it open?                                 *
         *  If it is open for read only, then the OS will still allow   *
         *  the file to be copied (but see below). If it is open        *
         *  for write as well (or only) then we cannot do the copy.     *
         *  If it is - log the details...                               *
         *  ...and return (don't try to copy)                           *
         *                                                              *
         ****************************************************************/

        err = fs_file_is_writable (from, &open);
        if (open)
        {
            err = make_errlog_entry (103, "%s could not be copied, file was open "
                                     "for writing\n\n", from);
        }
        else
        {
          /**********************************************************************
           *                                                                    *
           *  ADFS, for example, uses the 'force' flag when copying over        *
           *  an existing file. Some filing systems, eg Fat32fs, will refuse    *
           *  to copy over a file that has permissions 'read only', even if     *
           *  the force flag is set. If destination is on such an FS, then      *
           *  we check the permissions and force to write allowed if necessary. *
           *                                                                    *
           **********************************************************************/

          if (check_target_access)
          {
            fs_stat (to, &f2);
            old_acc = f2.acc;
            if ((old_acc & 2) == 0)
            {
              /* need to change access flags */
              acc = old_acc | 2;
              set_access (to, acc);
            }
          }
          err = copy_file (from, to, move);
        }
        break;

      case 2:
        /* object is a directory
         * first ensure target directory exists */
        err = fs_cdir (to);
        /* do we want to copy complete directories? */
        if (sync.copy_dir)
        {
            err = copy_file (from, to, move);
        }
        else
        {
            err = copy_dir_file_by_file (from, to, move);
        }
        break;

    }

  }
  else
  {
    /* file to be copied is log file - skip it but note in the log */
    if (bfp)
    {
      bf_printf (bfp, "\n!!!  Active logfile (%s) has not been copied\n\n", from);
    }
  }

  return (err);
}







/************************************************************/

static os_error *getnewdir (int acn, int dir, heads ** hdr, int *size)
{
  os_error   *err;

  arc[acn].path = newpath;
  err = getdir (acn, dir, hdr, size);
  arc[acn].path = oldpath;

  return (err);
}




/************************************************************/

static os_error *newcopy (int acn, int n, int update)
{
  os_error   *err;
  char       *subpath;
  char       *source;
  char       *dest;
  int         type;
  char        buf[64];


  err = flex_alloc ((flex_ptr)&subpath, FSMAXWIMPMSGPATH);
  if (!err)
  {
    err = flex_alloc ((flex_ptr)&source, FSMAXWIMPMSGPATH);
    if (!err)
    {
      err = flex_alloc ((flex_ptr)&dest, FSMAXWIMPMSGPATH);
      if (!err)
      {
        subpath[0] = 0;
        filepath1 (subpath, acn, n);

        if (doscrap && update)
        {
          sprintf (source, "%s.%s", oldpath, subpath);
          sprintf (dest, "%s.%s", scrappath, subpath);

          if (bfp)
          {
            err = bf_printf (bfp, "move:%s to %s\n", source, dest);
          }
          if (!err)
          {
            if (progress_handle)
            {
              err = fs_exists (source, &type);
              if (type && !err)
              {
                sprintf (buf, "Moving %s to scrap", filetype_str[type - 1]);
                progress_update (buf, source + secondary_length);
              }
            }

            if (!err) err = filecopy (source, dest, 1);
          }
        }

        /* has the process been stopped during file move */
        if (!sync_stop && !err)
        {
          sprintf (source, "%s.%s", newpath, subpath);
          sprintf (dest, "%s.%s", oldpath, subpath);

          if (bfp)
          {
            err = bf_printf (bfp, "copy:%s to %s\n", source, dest);
          }
          if (progress_handle && !err)
          {
            err = fs_exists (source, &type);
            if (type && !err)
            {
              sprintf (buf, "Copying %s", filetype_str[type - 1]);
              progress_update (buf, source + primary_length);
            }
          }
        }

        if (!err)
        {
          err = filecopy (source, dest, 0);
        }
        flex_free ((flex_ptr)&dest);
      }
      flex_free ((flex_ptr)&source);
    }
    flex_free ((flex_ptr)&subpath);
  }

  return (err);
}






/************************************************************/

static os_error *newreversecopy (int acn, int n, int update)
{
  os_error   *err;
  char       *subpath;
  char       *source;
  char       *dest;
  int         type;
  char        buf[64];


  err = flex_alloc ((flex_ptr)&subpath, FSMAXWIMPMSGPATH);
  if (!err)
  {
    err = flex_alloc ((flex_ptr)&source, FSMAXWIMPMSGPATH);
    if (!err)
    {
      err = flex_alloc ((flex_ptr)&dest, FSMAXWIMPMSGPATH);
      if (!err)
      {
        subpath[0] = 0;
        filepath1 (subpath, acn, n);

        if (doscrap && update)
        {
          sprintf (source, "%s.%s", newpath, subpath);
          sprintf (dest, "%s.%s", scrappath, subpath);

          if (bfp)
          {
            err = bf_printf (bfp, "move:%s to %s\n", source, dest);
          }

          if (progress_handle && !err)
          {
            err = fs_exists (source, &type);
            if (type && !err)
            {
              sprintf (buf, "Moving %s to scrap", filetype_str[type - 1]);
              progress_update (buf, source + primary_length);
            }
          }

          if (!err)
          {
            err = filecopy (source, dest, 1);
          }
        }
        /* has the process been stopped during file move */
        if (!sync_stop && !err)
        {
          sprintf (source, "%s.%s", oldpath, subpath);
          sprintf (dest, "%s.%s", newpath, subpath);

          if (bfp)
          {
            err = bf_printf (bfp, "copy:%s to %s\n", source, dest);
          }

          if (progress_handle && !err)
          {
            err = fs_exists (source, &type);
            if (type && !err)
            {
              sprintf (buf, "Reverse copying %s", filetype_str[type - 1]);
              progress_update (buf, source + secondary_length);
            }
          }

          if (!err)  err = filecopy (source, dest, 0);
        }

        flex_free ((flex_ptr)&dest);
      }
      flex_free ((flex_ptr)&source);
    }
    flex_free ((flex_ptr)&subpath);
  }
  return (err);
}



/************************************************************/

static os_error *wipe_file (char *object)
{
  os_error   *err;
  os_error   *er1;
  os_regset   rx;
  int         handle;
  char       *p;
  int         i;

  err = NULL;

  if (sync.multitask)
  {
    err = wos_startnewtask ("Filer_Action", &handle);

    if (err || (handle == 0))
    {
      /* just do a non-multitasking wipe */
      er1 = fs_wipe (object);
      if (er1)
        make_errlog_entry (104, "Delete file %s: %s\n",object, er1->errmess);
    }
    else
    {
      /**********************************************************************
       *                                                                    *
       * store the task handle as part of method of controlling the number  *
       * of concurrent Filer_Action processes.                              *
       *                                                                    *
       **********************************************************************/

      farunning.handles[farunning.count] = handle;
      farunning.count++;
      progress_update_fa_count_field();

      /* send source directory */
      p = leaf (object);
      if (p)
        *(p - 1) = 0;

      rx.r[0] = handle;
      rx.r[1] = (int) object;        /* parent directory */
      os_swix (FilerAction_SendSelectedDirectory, &rx);

      /* send source file/dir name */
      rx.r[0] = handle;
      rx.r[1] = (int) p;        /* leaf */
      os_swix (FilerAction_SendSelectedFile, &rx);

      /* now start the operation */

      rx.r[0] = handle;
      rx.r[1] = FA_DELETE;        /* delete */
      rx.r[2] = FA_FORCE;    /* force */
      if (sync.verbose_fileractions)
      {
        rx.r[2] |= FA_VERBOSE;
      }
      rx.r[3] = 0;
      rx.r[4] = 0;
      os_swix (FilerAction_SendStartOperation, &rx);

      /* wait for operation to complete only if we are limited to one
       * concurrent filer_action */
      if (!sync.multiple_fa)
      {
        endtaskhandle = 0;

        while (1)
        {
          poll ();
          if (handle == endtaskhandle) break;
          if (sync_stop) break;
        }

        /* has the user pressed Pause while FA has been completing? if so
         * poll until continue or stop */

        if (sync_pause)
        {
          do
          {
            poll ();
          }
          while (sync_pause && !sync_stop);
        }
      }
      else
      {
        /**********************************************************************
         *                                                                    *
         * we need several poll()s here to enable the filer_actions to get    *
         * going. If only one poll, or none at this point, the                *
         * filer_actions are initialised but queue 'pending'. With a lot of   *
         * copying to do there may be very many still to complete when        *
         * SyncDiscs has completed its scan.                                  *
         *                                                                    *
         **********************************************************************/

        for (i = 0; i < 5; i++)
        {
          if (poll_pause_or_stop ()) break;
        }
      }
    }
  }
  else
  {
    er1 = fs_wipe (object);
    if (er1)
      make_errlog_entry (105, "Delete file %s: %s\n",object, er1->errmess);
  }

  return (err);
}




/************************************************************/

static os_error *newremove (int acn, int n)
{
  os_error   *err;
  os_error   *er1;
  char       *path;
  char       *source;
  char       *dest;
  int         type;
  char        buf[64];


  err = flex_alloc ((flex_ptr)&path, FSMAXWIMPMSGPATH);
  if (!err)
  {
    err = flex_alloc ((flex_ptr)&source, FSMAXWIMPMSGPATH);
    if (!err)
    {
      err = flex_alloc ((flex_ptr)&dest, FSMAXWIMPMSGPATH);
      if (!err)
      {
        path[0] = 0;
        filepath1 (path, acn, n);

        if (doscrap)
        {
          sprintf (source, "%s.%s", oldpath, path);
          sprintf (dest, "%s.%s", scrappath, path);

          if (bfp)
          {
            err = bf_printf (bfp, "move:%s to %s\n", source, dest);
          }

          if (progress_handle && !err)
          {
            err = fs_exists (source, &type);
            if (type && !err)
            {
              sprintf (buf, "Moving %s to scrap", filetype_str[type - 1]);
              progress_update (buf, source + secondary_length);
            }
          }

          if (!err) err = filecopy (source, dest, 1);
        }
        else
        {
          sprintf (source, "%s.%s", oldpath, path);

          if (bfp)
          {
            err = bf_printf (bfp, "remove:%s\n", source);
          }

          if (sync.multitask && !err)
          {
            /*************************************************
             *                                               *
             * in case a large directory is to be deleted    *
             * use filer_action to do multitasking           *
             * start up a filer_action operation             *
             *                                               *
             *************************************************/

            if (progress_handle)
            {
              err = fs_exists (source, &type);
              if (type && !err)
              {
                sprintf (buf, "Removing %s", filetype_str[type - 1]);
                progress_update (buf, source + secondary_length);
              }
            }
          }

          if (!err)
          {
            er1 = wipe_file (source);
            if (er1)
              make_errlog_entry (106, "Delete file %s: %s\n",source, er1->errmess);
          }

        }

        flex_free ((flex_ptr)&dest);
      }
      flex_free ((flex_ptr)&source);
    }
    flex_free ((flex_ptr)&path);
  }
  return (err);
}




/************************************************************/

/* returns the path of the dir being processed */
static void get_scan_dir (int acn, int dir, char *path)
{
  strcpy (path, newpath);
  if (dir >= 0)
  {
    filepath1 (path, acn, dir);
  }
  return;
}



/************************************************************/

/* returns the path of the dir/file being processed */
static void get_scan_file (int acn, int dir, char *leaf, char *path)
{
  get_scan_dir (acn, dir, path);
  strcat (path, ".");
  strcat (path, leaf);
  return;
}





/************************************************************/

static int ignorefile_old (int acn, int dir, char *leaf)
{
  char       *path;
  int         inz = 0;
  os_error   *err;

  if (sync.use_zone_file)
  {
    err = flex_alloc ((flex_ptr)&path, FSMAXWIMPMSGPATH);
    if (!err)
    {
      strcpy (path, oldpath);

      if (dir >= 0)
      {
        filepath1 (path, acn, dir);
      }
      strcat (path, ".");
      strcat (path, leaf);

      inz = inzone (path, NULL);

      flex_free ((flex_ptr)&path);
    }
  }

  return (inz);
}






/************************************************************/

static int ignorefile (int acn, int dir, char *leaf)
{
  char       *path;
  int         inz = 0;
  int         type;
  char        buf[64];
  os_error   *err;


  if (sync.use_zone_file)
  {
    err = flex_alloc ((flex_ptr)&path, FSMAXWIMPMSGPATH);
    if (!err)
    {
      strcpy (path, newpath);
      if (dir >= 0)
      {
        filepath1 (path, acn, dir);
      }
      strcat (path, ".");
      strcat (path, leaf);

      inz = inzone (path, NULL);

      if (inz)
      {
        if (bfp)
        {
          bf_printf (bfp, "ignore:%s\n", path);
        }

        if (progress_handle)
        {
          err = fs_exists (path, &type);
          if (!err)
          {
            if (sync.compare) store_cmp_list_item_2 (path + primary_length, IGNORE);
            buf[0] = '\0';
            if (type)
            {
              sprintf (buf, "Ignoring %s", filetype_str[type - 1]);
              progress_update (buf, path + primary_length);
            }
          }
        }
      }
      flex_free ((flex_ptr)&path);
    }
  }

  return (inz);
}



/************************************************************/

/* we have an old structure acn,dir and a new one */
/* make the old one into the new one */

/* Primary is indexed by j */
/* Secondary by n */


static os_error *syncdir (int acn, int dir)
{
  os_error   *err;
  heads      *hdr = NULL;
  int         size;
  int         i;
  int         j;
  int         n;
  int         code;
  int         incn;
  int         dsize;
  int         name;
  int         difflen;
  int         difftype;
  long long   difftime;
  long long   t1;
  long long   t2;

  char       *temppath;
  char       *pth;
  int         oldignore;



  err = flex_alloc ((flex_ptr)&temppath, FSMAXWIMPMSGPATH);
  if (!err)
  {
    /*************************************************************************
     * The current object in the scanning process will be stored in the      *
     * variable temppath. This will be the complete path of the object. pth  *
     * points to the start of the subpath excluding the primary directory    *
     * path                                                                  *
     *************************************************************************/

    if (dir >= 0 && arc[acn].hdr[dir].dirn == DFLAT)
    {
      err = opendir (acn, dir);
    }

    if (!err)
    {
      err = getnewdir (acn, dir, &hdr, &size);
    }

    if (!sync.compare)
    {
      get_scan_dir (acn, dir, temppath);
      if (sync.verbose_log && bfp)
        bf_printf (bfp, ">> Scanning directory:%s\n", temppath);
    }

    if (!err)
    {
      j = 0;
      n = dir + 1;

      for (i = 0; i < ((dir < 0) ? arc[acn].fmain : arc[acn].hdr[dir].dirn);
           i++)
      {
        incn = 1;

        if (poll_pause_or_stop ())
          break;

        /* Check whether primary object is in the zone file ignore list */
        while (j < size && ignorefile (acn, dir, zstringptr (hdr[j].name)))
        {
          /* It is - we will need to skip it */
          j++;
        }

        if (j < size)
        {
          code = cstrcmp (zstringptr (arc[acn].hdr[n].name),
                          zstringptr (hdr[j].name));
          /* path in primary dir */
          if (next_update_due ())
          {
            get_scan_file (acn, dir, zstringptr (hdr[j].name), temppath);
            pth = temppath + primary_length;
            progress_update ("Scanning", pth);
          }
        }
        else
          code = -1;

        if (code == 0)                /* same names - check time stamps */
        {
          if (arc[acn].hdr[n].dirn == DNFILE && hdr[j].dirn == DNFILE)
          {
            difflen = arc[acn].hdr[n].length - hdr[j].length;

            if (sync.ignore_filetype)
            {
              difftype = 0;
            }
            else
            {
              difftype = fs_filetype (arc[acn].hdr[n].load) - fs_filetype (hdr[j].load);
            }

            t1 = (((long long) (arc[acn].hdr[n].load & 0xFF)) << 32) |
                               (((long long) arc[acn].hdr[n].exec) & 0xFFFFFFFF);
            t2 = (((long long) (hdr[j].load & 0xFF)) << 32) |
                               (((long long) hdr[j].exec) & 0xFFFFFFFF);
            difftime = t1 - t2;


            if (difftime < (long long) timeresolution && difftime > 0)
              difftime = 0;
            if (difftime > -(long long) timeresolution && difftime < 0)
              difftime = 0;

           /*
           * >> commented out by DP
           *
           * code=(arc[acn].hdr[n].load & 0xFF)-(hdr[j].load & 0xFF);
           * if(code==0)
           * {
           *   code=((unsigned int)arc[acn].hdr[n].exec)>((unsigned int)hdr[j].exec);
           *   if(code==0)
           *     code=-(((unsigned int)arc[acn].hdr[n].exec)<((unsigned int)hdr[j].exec));                     * } */

            if (sync.compare)
            {
              if (difftime > 0)
                store_cmp_list_item (acn, n, SEC_NEWER);
              else if (difftime < 0)
                store_cmp_list_item (acn, n, SEC_OLDER);
              else if (difflen)
                store_cmp_list_item (acn, n, SEC_DIFFLEN);
              else if (difftype)
                store_cmp_list_item (acn, n, SEC_DIFFTYPE);
            }
            else
            {
              if (difftime > 0 && !sync.overwrite)        /* client is newer
                                                           * than server */
              {
                /* reverse update */

                if (sync.newer)
                {
                  err = newreversecopy (acn, n, 1);
                }
              }
              else
                if (difftime < 0 || (difftime > 0 && sync.overwrite)
                    || difflen || difftype)
                  /* client older than server */
                {
                  /* update file */
                  name = arc[acn].hdr[n].name;
                  arc[acn].hdr[n] = hdr[j];
                  arc[acn].hdr[n].name = name;
                  arc[acn].hdr[n].get = 1;
                  arc[acn].hdr[n].update = 1;
                }
                else
                {
                  /* client and server same times */
                }
            }
          }
          else /* types clash */
          if ((arc[acn].hdr[n].dirn == DNFILE && hdr[j].dirn != DNFILE) ||
              (arc[acn].hdr[n].dirn != DNFILE && hdr[j].dirn == DNFILE))
          {
            if (!sync.compare)
              err = newremove (acn, n);
            if (!err)
            {
              name = arc[acn].hdr[n].name;
              arc[acn].hdr[n] = hdr[j];
              arc[acn].hdr[n].name = name;
              arc[acn].hdr[n].get = 1;
              arc[acn].hdr[n].update = 0;
              if (arc[acn].hdr[n].dirn != DNFILE)
                arc[acn].hdr[n].dirn = 0;
              if (sync.compare)
              {
                /* file / dir name clash */
                store_cmp_list_item (acn, n, FILEDIR_CLASH);
              }
            }
          }

          j++;
        }
        else if (code > 0)
        {
          err = insentry (acn, n, dir, 1);
          if (err)
            break;
          arc[acn].hdr[n] = hdr[j];
          zdupstring (hdr[j].name, &name);
          arc[acn].hdr[n].name = name;
          arc[acn].hdr[n].get = 1;
          arc[acn].hdr[n].update = 0;
          if (arc[acn].hdr[n].dirn != DNFILE)
            arc[acn].hdr[n].dirn = 0;
          j++;
          if (sync.compare)
          {
            store_cmp_list_item (acn, n, PRI_EXTRA);
          }
        }
        else
        {
          /***************************************************************************
           *                                                                         *
           * This must be a local entry (i.e. in the secondary path) which is not on *
           * server                                                                  *
           *                                                                         *
           * If we are using the zone file, and an entry in the primary has been     *
           * ignored (skipped), we may arrive here with a corresponding file in the  *
           * secondary. The original behaviour was to simply delete such files. This *
           * has caused users confusion, and is not always what is wanted. Now that  *
           * there is an option to leave ignored files in the secondary, we need to  *
           * do some checking here if that option is set                             *
           *                                                                         *
           ***************************************************************************/

          oldignore = ignorefile_old (acn, dir, zstringptr (arc[acn].hdr[n].name));
          if (sync.compare)
          {
            if (oldignore)
              store_cmp_list_item (acn, n, IGNORE_SEC);
            else
              store_cmp_list_item (acn, n, SEC_EXTRA);

            if (arc[acn].hdr[n].dirn >= 0)
              dsize = noindir (acn, n) + 1;
            else
              dsize = 1;
            scrapentry (acn, n, dsize);
            rementry (&arc[acn], n, dsize);
            i--;
            incn = 0;
          }
          else
          {
            if (sync.extra)
            {
              /* We do not want to force a reverse copy of the ignored file */
              if (oldignore)
              {
                filepath1 (temppath, acn, n);
                if (bfp)
                  bf_printf (bfp, "Ignoring secondary object: %s\n", temppath);
              }
              else
              {
                err = newreversecopy (acn, n, 0);
                if (err)
                  break;
              }
            }
            else
            {
              /*********************************************************************
               *                                                                   *
               * Do a delete if the object is not an ignored object or if the      *
               * 'no-delete' flag is not set                                       *
               *                                                                   *
               *********************************************************************/

              if (oldignore && sync.no_delete_ignored)
              {
                filepath1 (temppath, acn, n);
                if (bfp)
                  bf_printf (bfp, "Ignoring secondary object: %s\n", temppath);
              }
              else
              {
                err = newremove (acn, n);
                if (err)
                  break;
              }

              if (arc[acn].hdr[n].dirn >= 0)
                dsize = noindir (acn, n) + 1;
              else
                dsize = 1;
              scrapentry (acn, n, dsize);
              rementry (&arc[acn], n, dsize);
              i--;
              incn = 0;
            }
          }
        }
        poll_pause_or_stop ();
        if (err || sync_stop)
          break;

        if (incn)
        {
          if (arc[acn].hdr[n].dirn >= 0)
            n += noindir (acn, n) + 1;
          else
            n++;
        }
      }

      poll_pause_or_stop ();


      if (!err && !sync_stop)
      {
        while (j < size)
        {
          if (ignorefile (acn, dir, zstringptr (hdr[j].name)))
          {
            j++;
          }
          else
          {
            /* fetch entry from server */
            err = zdupstring (hdr[j].name, &name);
            if (err)
              break;
            err = insentry (acn, n, dir, 1);
            if (err)
              break;
            arc[acn].hdr[n] = hdr[j];
            arc[acn].hdr[n].name = name;
            arc[acn].hdr[n].get = 1;
            arc[acn].hdr[n].update = 0;
            if (arc[acn].hdr[n].dirn != DNFILE)
              arc[acn].hdr[n].dirn = 0;

            if (sync.compare)
            {
              store_cmp_list_item (acn, n, PRI_EXTRA);
            }
            n++;
            j++;
          }
        }
      }
      poll_pause_or_stop ();

      if (!err && !sync.compare && !sync_stop)
      {
        n = dir + 1;

        for (i = 0; i < ((dir < 0) ? arc[acn].fmain : arc[acn].hdr[dir].dirn);
             i++)
        {
          if (arc[acn].hdr[n].get)
          {
            err = newcopy (acn, n, arc[acn].hdr[n].update);
            if (err || sync_stop)
              break;
          }

          if (arc[acn].hdr[n].dirn >= 0)
            n += noindir (acn, n) + 1;
          else
            n++;
        }
      }
    }

    if (hdr)
    {
      for (i = 0; i < size; i++)
      {
        zrmstring (hdr[i].name);
      }
      flex_free ((flex_ptr) & hdr);
    }

    if (!sync.compare)
    {
      get_scan_dir (acn, dir, temppath);
      if (sync.verbose_log && bfp)
        bf_printf (bfp, ">> Completed directory:%s\n\n", temppath);
    }

    flex_free ((flex_ptr) &temppath);
  }

  return (err);
}







/************************************************************/

static os_error *updatedir (int acn, int dir)
{
  os_error   *err;
  int         i;
  int         level;
  int         ln;
  int         in;
  int         pn;
  int         n;
  int         size;


  for (i = 0; i < arc[acn].nofiles; i++)
  {
    arc[acn].hdr[i].get = arc[acn].hdr[i].put = 0;
  }

  err = syncdir (acn, dir);
  if (!err && !sync_stop)
  {
    level = 1;
    in = 0;
    if (dir < 0)
      ln = arc[acn].fmain;
    else
      ln = arc[acn].hdr[dir].dirn;
    pn = dir;
    n = dir + 1;

    while (level > 0)
    {
      for (i = in; i < ln; i++)
      {
        if (arc[acn].hdr[n].dirn >= 0 || arc[acn].hdr[n].dirn == DFLAT)
        {
          if (!arc[acn].hdr[n].get)
          {
            err = syncdir (acn, n);
            if (err || sync_stop)
              break;

            posn2[level] = pn;
            lno2[level] = ln;
            fpos2[level] = i;
            pn = n;
            i = 0;
            in = 0;
            ln = arc[acn].hdr[n].dirn + 1;
            levelstackcheck (++level);
          }
        }
        n++;
      }

      if (err || sync_stop)
        break;

      /* can close dir at pn now */

      closedir (acn, pn, &size);
      n -= size;

      level--;

      if (level)
      {
        pn = posn2[level];
        ln = lno2[level];
        in = fpos2[level] + 1;
      }
    }
  }

  return (err);
}



/************************************************************/

static char *dir_type[3] = {"primary", "secondary", "scrap"};


/**************************************************************************/

#define FREEWAY_TYPE_DISC 0x1u
#define FREEWAY_FLAGS 0

#define Freeway_Enumerate 0x47a83

#define FREEWAY_BUFFER_SIZE 256

static BOOL Share_drive_is_available (char * share_name)

{
  os_error   *err;
  char obj_name[FREEWAY_BUFFER_SIZE];
  char obj_desc[FREEWAY_BUFFER_SIZE];
  os_regset   r;

  int enum_ref = 0;
  int obj_name_length;


  while (enum_ref != -1)
  {
    r.r[0] = FREEWAY_FLAGS;
    r.r[1] = FREEWAY_TYPE_DISC;
    r.r[2] = sizeof(obj_name);
    r.r[3] = (int)obj_name;
    r.r[4] = sizeof(obj_desc);
    r.r[5] = (int)obj_desc;
    r.r[6] = 0;
    r.r[7] = enum_ref;
    r.r[8] = 0;

    err = os_swix (Freeway_Enumerate, &r);

    obj_name_length = r.r[2];
    enum_ref = r.r[7];
    if (enum_ref != -1)
    {
      obj_name[obj_name_length] = 0x00;
      if ( !strcmp(share_name, obj_name))
        return (TRUE);
    }
  }
  /* No match */
  return (FALSE);

}




/**************************************************************************/

static os_error * find_drive (int index)

{
  os_error   *err = NULL;
  int type;
  char message[128];


  if (paths[index].rootdir[0] != 0x00)
  {
    if (!strcmp(paths[index].filesystem, "Share"))
    {
      /* We need to treat ShareFS drives differently */
      /* Enumerate the shares to prevent 'please insert disc x' messages */
      if (!Share_drive_is_available (paths[index].device))
      {
        sprintf (message, "Shared drive %s is not available", paths[index].device);
        make_errlog_entry (0, "%s\n", message);
        err = generror (0, message);
      }
    }
    else
    {
      /* Filing systems such as LanMan98 simply give a 'disc not found' error */
      /* Simply check for existence of the root directory */
      err = fs_exists (paths[index].rootdir, &type);
      if (err)
      {
        sprintf (message, "Finding %s root dir (%s): %s\n",
                              dir_type[index], paths[index].rootdir, err->errmess );
        make_errlog_entry (0, message);
      }
    }
  }

  return (err);
}




/**************************************************************************/

static os_error * filesystem_and_device_2 (char * path, int index)

{
  os_error   *err = NULL;
  char filepath[256];
  char *colon;
  char *dollar;

  filepath[0] = 0x00;
  if (!strstr(path, "::"))
    fs_canonicalpath (path, filepath, sizeof (filepath), NULL, NULL);
  else
    strcpy(filepath,path);

  colon = strchr(filepath, ':');
  if (colon)
  {
    *colon = 0x00;
    strcpy(paths[index].filesystem, filepath);
    *colon = ':';
    colon += 2;

    dollar = strchr(colon, '$');

    if(dollar)
    {
      dollar--;
      *dollar = 0x00;
      strcpy(paths[index].device, colon);
      *dollar = '$';
    }
    else
    {
      paths[index].device[0] = 0x00;
    }

  }
  else
  {
    paths[index].filesystem[0] = 0x00;
  }

  /* Now make root dir path */
  if ((paths[index].filesystem[0] != 0x00) && (paths[index].device[0] != 0x00))
  {
    sprintf (paths[index].rootdir, "%s::%s.$",paths[index].filesystem,paths[index].device);
  }
  else
  {
    paths[index].rootdir[0] = 0x00;
  }
  return (err);

}




/**************************************************************************/

static os_error *filesystem_and_device (void)

{
  os_error   *err;
  int i;

  /* check file systems */
  err = filesystem_and_device_2 (newpath, PRIM);
  if (!err) err = filesystem_and_device_2 (oldpath, SEC);
  if (!err) err = filesystem_and_device_2 (scrap, SCRAP);

  if (!err)
  {
    for (i=0; i<3; i++)
    {
      if (!err) err = find_drive (i);
      else break;
    }
  }
  return (err);
}




/**************************************************************************/

static BOOL match_paths (char *path1, char *path2, char *p1name, char *p2name)
{
  os_error   *err;
  int  f, s;
  char *msg;

  err = flex_alloc ((flex_ptr)&msg, 300);
  if (!err)
  {
    /* Using case-insensitive compares */
    /* RISC OS file paths are case insensitive; some other filing systems
       are not */
    while (1)
    {
      f = toupper(*path1++);
      s = toupper(*path2++);
      if (f != s || !f) break;
    }
    /* if f and s are both nul terminators then
       primary and secondary are the same */
    if (f == s)
    {
      sprintf (msg, "The %s and the %s directories are the same", p1name, p2name);
      messagebox (msg);
      return (TRUE);
    }
    /* now check if one path is inside the other */
    if ((f==0) && (s=='.'))
    {
      sprintf (msg, "You cannot have the %s directory"
                  " inside the %s directory", p2name, p1name);
      messagebox (msg);
      return (TRUE);
    }
    else
    {
      if ((s==0) && (f=='.'))
      {
        sprintf (msg, "You cannot have the %s directory"
                    " inside the %s directory", p1name, p2name);
        messagebox (msg);
        return (TRUE);
      }
    }
    flex_free ((flex_ptr)&msg);
  }

  return (FALSE);
}





/**************************************************************************/

static BOOL paths_have_conflict (char *primary, char *secondary, char *scrap)
{
  BOOL match = FALSE;


  /* Following is to try to prevent tears! */

  if (match_paths(primary, secondary, "Primary", "Secondary")) match = TRUE;
  if (*scrap != '\0')
  {
    if (match_paths(scrap, primary, "Scrap", "Primary")) match = TRUE;
    if (match_paths(scrap, secondary, "Scrap", "Secondary")) match = TRUE;
  }
  return (match);
}




/****************************************************************************
 *                                                                          *
 * Following function does nothing, but is required for registering as the  *
 * null event handler function. If we don't return null events, SyncDiscs   *
 * multitasking will be stalled until some other event is returned          *
 *                                                                          *
 ***************************************************************************/

static void synczero (int handle)
{

  USE (handle);
  return;
}





/************************************************************/

static void do_div (int num, char * st)

{
  int q;
  int cs;
  char * s;


  if (num > 0)
  {
    /* do hours */
    s = st;
    cs = num;
    q = cs / 360000;
    cs = cs % 360000;
    if (q > 0)
    {
      s += sprintf (s, "%d hr ", q);
    }
    if (cs > 0)
    {
      q = cs / 6000;
      cs = cs % 6000;
      if (q > 0)
      {
        s += sprintf (s, "%d min ", q);
      }
      if (cs > 0)
      {
        q = cs / 100;
        cs = cs % 100;
        if (q > 0)
        {
          s += sprintf (s, "%d s ", q);
        }
      }
    }
  }
  else
  {
    strcpy (st, "0");
  }
  return;
}





/************************************************************/

os_error   *syncdiscs (void)
{
  os_error   *err, *err1;
  int         oldarcn;
  buffer      bf;
  buffer      cbf;
  fstat       f;
  char       *p;
  int         c;

  char        st[64];
  BOOL        open = FALSE;
  int         type;
  int         i;
  char        temp[512];
  int         first_directory;
  int         answer;               /* for yes/no confirmation */
  char      *prevlogpath;
  BOOL       pathconflict = FALSE;
  BOOL       device_problem = FALSE;
  BOOL       reenable_trapdelete;
  BOOL       reenable_sparkfs;
  BOOL       first_job;
  BOOL       zonefile_notfound = FALSE;

  int        handle;


  err = flex_alloc ((flex_ptr)&prevlogpath, FSMAXWIMPMSGPATH);
  if (err) return (err);
  /* function now modified to process all the jobs in the queue */
  sync_error = 0;
  prevlogpath[0] = '\0';
  /*************************************************************************
   * first_job is a flag used to set up some things that require us to be  *
   * (null) polling in order to be actioned. They really need to be        *
   * before the start of the main loop, but the null poll event handler    *
   * is not enabled before we are in the main loop                         *
   *************************************************************************/
  first_job = TRUE;
  reenable_sparkfs = FALSE;
  sync_running = 1;

  do
  {
    /*************************************************************************
     * Start of main loop                                                    *
     * Synchronisation will repeat from here if there is more than one job   *
     * in the queue                                                          *
     *************************************************************************/

    /* Get the settings for the next job in the queue */
    sync = *jobs.settings[0];

    reenable_trapdelete = FALSE;

    /* copy pointers to file strings (we use the local pointers since the original
     * app only did one job at a time, and the pointers primary etc were used
     * throughout the code */
    primary = sync.primary;
    secondary = sync.secondary;
    scrap = sync.scrap;


    /* Initialise the zone file to be used */
    if (sync.use_zone_file)
    {
      /* check whether there is a path entered in the zone file writable field */
      if (sync.zonefile[0] == 0)
      {
        /* Use the default zone file. Copy the path in to the data struct */
        fs_exists (NEW_DEFAULT_ZONE_FILE, &type);
        if (type == 1)
        {
          strcpy (sync.zonefile, NEW_DEFAULT_ZONE_FILE);
        }
        else
        {
          /* Cancel use of zone file */
          sync.use_zone_file = 0;
        }
      }
    }

    if (sync.use_zone_file)
    {
      /* Check that file exists. The fn zoneinit() will check, but if not found,
      ** the zone checking will continue, but with an empty zone file, which
      ** will slow things up a bit, so do it here */
      fs_exists (sync.zonefile, &type);
      if (type == 1)
      {
        /* Load and initialise the appropriate zone file */
        err = zoneinit (sync.zonefile);
        /* If there is an error do not continue to use zone file, tidy up
        ** and reset err to null */
        if (err)
        {
          zone_finit ();
          sync.use_zone_file = 0;
          err = NULL;
        }
      }
      else
      {
        zonefile_notfound = TRUE;
        sync.use_zone_file = 0;
      }
    }



#ifdef KILL_TASKS
    /* Do we need to kill any tasks? */
    if (!sync.compare)
    {
      if (sync.killtasks) taskmanage_killtasks (sync.killtasks_filename);
    }
#endif

#ifdef KILL_TRAPDELETE
    /* Do we need to disable TrapDelete during the sync? */
    /* We do not need to be polling to issue this command */
    if (sync.disable_trapdelete)
    {
      /* Check whether TrapDelete is running */
      err = wos_tasknametohandle ("TrapDelete", &handle);
      if (handle != 0)
      {
        /* TrapDelete is running */
        /* Disable trapdelete */
        os_cli ("TrapDelete_TrapDeletions 0");
        reenable_trapdelete = TRUE;
      }
    }
#endif


    /* check that pri and sec paths are not NULL. Skip this job if either is */

    if ((sync.primary[0] != '\0') && (sync.secondary[0] != '\0'))
    {
      /*************************************************************************
       *                                                                       *
       * Now all the variables are passed in one structure there is the        *
       * possibility of conflicting settings between compare and logfile. We   *
       * now need to reset some settings here, rather than in the calling      *
       * function as was done originally.                                      *
       *                                                                       *
       *************************************************************************/

      if (sync.compare)
      {
        sync.log = 0;
        sync.logfile[0] = '\0';
      }

      if (sync.tstamp_eq)
      {
        timeresolution = sync.timeresolution;
      }
      else
      {
        timeresolution = 0;
      }
      /* enable task close monitoring for filer_actions when syncing */

      flg.first_pass = 1;
      farunning.count = 0;
      /* zero the update time */
      next_time.hi = 0;
      next_time.lo = 0;


      /* Set up global variables for multitasking status report */
      if (sync.multitask)
      {
        sync_pause = 0;
        sync_stop = 0;

        if (first_job)
        {
          addzeroevent (synczero, 0);
          syncdiscsclose (0, 0);
          progress_open ();
          progress_update_job_count_field ();
#ifdef KILL_SPARKFS
          /* Do we need to disable SparkFS? */
          if (sync.kill_sparkfs)
          {
            /* Is SparkFS filer running? */
            wos_tasknametohandle ("SparkFiler", &handle);
            if (handle == 0)
            {
              /* Front end is not running - check whether the SparkFS module is active */
////              os_cli("RMKill SparkFS");
            }
            else
            {
              /* Send close down message to SparkFS */
              wos_killtask_handle (handle);
              reenable_sparkfs = TRUE;
              /* Also kill the SparkFS module which would normally be active */
              os_cli("RMKill SparkFS");
            }
          }
#endif
          do_polls (4);
          first_job = FALSE;
        }

        progress_unshade_buttons ();
        do_polls (3);
        if (sync.compare)
        {
          err = progress_update ("Started comparing", "");
        }
        else
        {
          err = progress_update ("Started synchronising", "");
        }
        do_polls (3);

      }

      if (sync.compare)
      {
        char        string[NAMELEN];

        if (!csh)
          sc_create (&csh);
        cfp = &cbf;

        err = fs_file_close_if_open (sc_name (csh), &open);

        err = bf_open (sc_name (csh), 'w', DEFBUFFSIZE, &cbf);

        /* If an error is returned then we need to stop the compare process */

        if (err)
        {
          cfp = NULL;
          /* Need some sort of error message for user if not mtasking */
          /* Need to tidy up before return */
          if (sync.multitask)
          {
            sync_running = 0;
            sync_stop = 0;
            remzeroevent (synczero, 0);
            err = progress_update ("Compare failed - could not open output file", "");
            progress_shade_buttons ();
          }
          os_cli ("Set SyncDiscs$Result 1");
          return (err);
        }
        else
        {

          writesystimedate (string);
          comparewrite ("Compare started at %s\n\n", string);

          if (open)
          {
            comparewrite ("** Compare file was open from previous compare **\n\n");
          }

          if (sync.use_zone_file)
          {
            comparewrite ("Using zone file %s\n\n", sync.zonefile);
          }
          else
          {
            if (zonefile_notfound)
            {
              comparewrite ("Zone file (%s) is not in use because the zone file"
                                                " was not found\n\n", sync.zonefile);
            }
            else
            {
              comparewrite ("Zone file is not in use\n\n");
            }
          }
          if (sync.tstamp_eq)
          {
            do_div (sync.timeresolution, temp);
            comparewrite ("*** Timestamps will be assumed equal if less than %s "
                             "different\n\n", temp);
          }

          if (sync.ignore_filetype)
          {
            comparewrite ("*** Differences in filetypes will be ignored\n\n");
          }


        }
      }

      if (!err)
      {
        /******************************************************************
         *                                                                *
         * There may be more than one set of directories in this job      *
         * set a flag for the first pass                                  *
         *                                                                *
         ******************************************************************/

        first_directory = 1;
        while (1)
        {
          /******************************************************************
           *                                                                *
           * The sync process will repeat from here if there is more than   *
           * single directory entered in the primary and secondary fields   *
           *                                                                *
           ******************************************************************/

          p = oldpath;
          while (1)
          {
            c = *secondary;
            if (!c)
              break;
            secondary++;
            if (c == ',')
              break;
            if (c != ' ')
              *p++ = c;
          }
          *p = 0;


          p = newpath;
          while (1)
          {
            c = *primary;
            if (!c)
              break;
            primary++;
            if (c == ',')
              break;
            if (c != ' ')
              *p++ = c;
          }
          *p = 0;


          p = scrappath;
          while (1)
          {
            c = *scrap;
            if (!c)
              break;
            scrap++;
            if (c == ',')
              break;
            if (c != ' ')
              *p++ = c;
          }
          *p = 0;


          if (!oldpath[0] && !newpath[0])
            break;
          else if (!oldpath[0])
            err = generror (0, "Secondary path has less entries than primary");
          else if (!newpath[0])
            err = generror (0, "Primary path has less entries than secondary");

          if (err)
            break;

          /* Set a flag to check later on because logpath may be undefined
             at this point */
          pathconflict = paths_have_conflict (newpath, oldpath, scrappath);
          if (pathconflict)
            break;

          /* Now check for file systems and devices */
          err = filesystem_and_device ();

          if (err)
          {
            device_problem = TRUE;
            break;
          }
          else
          {
            /* need to know what fs the destination is on, used in copyfile */
            if (!cstrncmp(paths[SEC].filesystem, "Fat32Fs", 7)) check_target_access = 1;
            else check_target_access = 0;

            /* Add the directories to the dir list */
            dirlist_add_dir (newpath, PRIMARY_LIST);
            dirlist_add_dir (oldpath, SECONDARY_LIST);
            dirlist_add_dir (scrappath, SCRAP_LIST);

            /* copy primary directory into progress window */
            progress_update_primary_field (newpath);
            progress_update_secondary_field (oldpath);

            /* First check that 'secondary' directory actually exists */
            fs_exists (oldpath, &type);
            if (type == 0)
            {
              /* create any higher level directories */
              fs_create_dir_chain (oldpath);
              /* create the final dir */
              fs_cdir (oldpath);
            }
            /***********************************************************************
             *                                                                     *
             * Calculate pointers to chop off the primary/secondary root paths     *
             * from the full file paths of objects. Add 1 to length to account     *
             * for added dot to get to remaining part of the full path of files    *
             *                                                                     *
             ***********************************************************************/
            primary_length = strlen (newpath) + 1;
            secondary_length = strlen (oldpath) + 1;

            doscrap = strlen (scrappath);

            if (sync.compare)
            {
              comparewrite ("*** Comparing directories\n");
              comparewrite ("*** Primary %s\n", newpath);
              comparewrite ("*** Secondary %s\n\n", oldpath);
              compare_output = 0;     /* flag will be set after there is output during
                                       * compare */

              /* now claim memory & initialise the compare results lists */
              /* This will be done for each dir if there are more than one in the
                 primary path */
              for (i=0; i<COMPARE_TYPES; i++)
              {
                compare_result[i].size = 1000;
                compare_result[i].offset = 0;
                err = flex_alloc ((flex_ptr) &compare_result[i].list,
                                             compare_result[i].size);
                if (err) break;
                compare_result[i].list[0] = '\0';
              }

            }

            if (sync.log)
            {
              /***********************************************************
               * Are we to use the log filename entered in the writable  *
               * or save the logfile in the primary directory?           *
               * If the latter, and there is more than one directory in  *
               * the primary list, then we need to change the logfile    *
               * path for each one                                       *
               ***********************************************************/

              if (sync.save_log_in_primary)
              {
                sprintf (sync.logfile, "%s.synclogs.log", newpath);
                /* expand the logfile path if necessary */
                fs_canonicalpath (sync.logfile, logpath, sizeof (logpath), NULL, NULL);
                /* now sort out the current and old logfiles */
                err = logfile_manage (&sync, &open);
              }
              else
              {
                /**********************************************************
                 * Use the default logfile path for the log               *
                 * Only need to do the logfile management for the first   *
                 * directory if there are more than one in the set for    *
                 * this job                                               *
                 **********************************************************/
                if (first_directory)
                {
                  /* expand the logfile path if necessary */
                  fs_canonicalpath (sync.logfile, logpath, sizeof (logpath), NULL, NULL);

                  /*************************************************************
                   * If the logfile path hasn't changed from the previous one  *
                   * in the queue, just tack this log on to the end.           *
                   * If the log path has changed, then we need to do some      *
                   * log file management based on the various settings         *
                   *************************************************************/

                  if (cstrcmp (logpath, prevlogpath))
                  {
                    err = logfile_manage (&sync, &open);
                  }
                  else
                  {
                    open = 0;
                  }
                  /* unset the flag */
                  first_directory = 0;
                  strcpy (prevlogpath, logpath);
                }
              }

              fs_stat (logpath, &f);

              bfp = &bf;

              err = bf_open (logpath, 'u', DEFBUFFSIZE, &bf);
              if (err)
              {
                bfp = NULL;
              }
              else
              {
                char        string[NAMELEN];

                if (f.object)
                  err = bf_seek (bfp, f.length);

                if (flg.first_pass)
                {
                  writesystimedate (string);
                  bf_printf (bfp, "------------------------------------------\n", st);
                  bf_printf (bfp, "*** SyncDiscs started scanning at %s\n", string);
                  bf_printf (bfp, "------------------------------------------\n\n", st);

                  if (sync.use_zone_file)
                  {
                    bf_printf (bfp, "Using zone file %s\n\n", sync.zonefile);
                  }
                  else
                  {
                    if (zonefile_notfound)
                    {
                      bf_printf (bfp,
                        "Zone file is not in use because the zone file was not found\n\n");
                    }
                    else
                    {
                      bf_printf (bfp, "Zone file is not in use\n\n");
                    }
                  }

#ifdef KILL_TRAPDELETE
                  if (reenable_trapdelete)
                    bf_printf (bfp, "TrapDelete has been disabled\n\n");
#endif

#ifdef KILL_SPARKFS
                  if (reenable_sparkfs)
                    bf_printf (bfp, "SparkFS has been disabled\n\n");
#endif

                  if (open)
                    bf_printf (bfp, "*** Logfile was open from previous logging\n\n");
                  if (sync.tstamp_eq)
                  {
                    do_div (sync.timeresolution, temp);
                    bf_printf (bfp,
                               "*** Timestamps will be assumed equal if less than %s "
                               "different\n\n", temp);
                  }
                  if (sync.ignore_filetype)
                  {
                    bf_printf (bfp, "*** Differences in filetypes will be ignored\n\n");
                  }

                  flg.first_pass = 0;
                }

                bf_printf (bfp, "*** Primary %s\n", newpath);
                bf_printf (bfp, "*** Secondary %s\n", oldpath);
                if (doscrap)
                  bf_printf (bfp, "*** Scrap %s\n", scrappath);
                bf_printf (bfp, "------------------------------------------\n\n", st);
                bf_printf (bfp, "\n\n");

              }
            }
            else
            {
              logpath[0] = '\0';
              bfp = NULL;
            }


            /* This is where the syncing actually occurs */

            if (!err)
            {
              err = loaddir (oldpath, &oldarcn);
              if (err)  make_errlog_entry (120, err->errmess);
              if (!err)
              {
                err = updatedir (oldarcn, -1);
                if (err)  make_errlog_entry (121, err->errmess);
              }
              if (!err)
              {
                if (oldarcn >= 0)
                {
                  err = losearc (oldarcn);
                  if (err)  make_errlog_entry (122, err->errmess);
                }
              }
            }

            if (sync.compare)
            {
              if (compare_output)
              {
                /* check which lists we need to output */
                output_cmp_list (SEC_NEWER,
                                 "Objects in secondary directory newer than primary:");

                output_cmp_list (SEC_OLDER,
                                 "Objects in secondary directory older than primary:");

                output_cmp_list (SEC_DIFFLEN,
                       "Objects in secondary directory different length than primary:");

                output_cmp_list (SEC_DIFFTYPE,
                       "Objects in secondary directory different filetype than primary:");

                output_cmp_list (PRI_EXTRA,
                                 "Primary directory contains extra objects:");

                output_cmp_list (SEC_EXTRA,
                                 "Secondary directory contains extra objects:");

                output_cmp_list (FILEDIR_CLASH,
                                 "File + Directory name clash:");

                output_cmp_list (IGNORE,
                                 "Objects in primary directory that will be ignored:");
                output_cmp_list (IGNORE_SEC,
                              "Objects in secondary directory that match a zone ignore pattern:");

              }
              else
              {
                comparewrite ("The two directories are the same\n\n", st);
              }
              comparewrite ("*** Finished comparing directories\n");
              comparewrite ("*** Primary %s\n", newpath);
              comparewrite ("*** Secondary %s\n\n", oldpath);
              /* free the list buffers for this pass of a primary dir */
              for (i = 0; i < COMPARE_TYPES; i++)
              {
                flex_free ((flex_ptr) &compare_result[i].list);
              }

            }
            else
            {
              /* if there was a path conflict and sync was skipped, logpath
                 may be undefined */
              if (bfp && !pathconflict)
              {

                bf_printf (bfp, "\n\n");

                writesystimedate (st);
                if (sync_stop)
                  bf_printf (bfp, "*** Stopped scanning %s at %s\n", newpath, st);
                else
                  bf_printf (bfp, "*** Completed scanning %s at %s\n", newpath, st);
                if (sync.multiple_fa && (farunning.count > 0))
                  bf_printf (bfp, "*** %d filer actions to complete", farunning.count);

                bf_printf (bfp, "------------------------------------------\n\n", st);

                err = bf_closec (bfp, err, logpath, TEXT);
              }
            }

            if (err || sync_stop)
              break;
         }
          /* end of main loop for multi-directories in one job */
        }

        /* Tidy up multitasking related things, status window etc */
        if (sync.multitask)
        {
          if (sync_stop)
          {
            /* check that all filer_actions have completed */
            if (sync.multiple_fa)
            {
              do
              {
                poll ();
                sprintf (temp, "Scan stopped by user - %d filer_actions to complete",
                         farunning.count);
                err = progress_update_status_field (temp);
              }
              while (farunning.count > 0);
            }
            err = progress_update ("Processing stopped by user", "");
          }
          else
          {
            progress_shade_buttons ();
            if (sync.compare)
            {
              err = progress_update ("Finished comparing", "");
            }
            else
            {
              /* check that all filer_actions have completed */
              if (sync.multiple_fa)
              {
                do
                {
                  poll ();
                  sprintf (temp, "Scan ended - %d filer_actions to complete",
                           farunning.count);
                  err = progress_update (temp, "");
                }
                while (farunning.count > 0);
              }

              err = progress_update ("Finished synchronising", "");
            }
          }
        }


        if (sync.compare)
        {
          writesystimedate (st);
          if (sync_stop)
            comparewrite ("Compare stopped by user at %s\n", st);
          else
            comparewrite ("Compare finished at %s\n", st);

          err = bf_closec (cfp, err, sc_name (csh), TEXT);

          sprintf (temp, "Filer_Run %s", sc_name (csh));
          os_cli (temp);
        }
        else
        {
          /* if there was a path conflict and sync was skipped, logpath
             may be undefined */
          if (sync.log && !pathconflict && !device_problem)
          {
            fs_stat (logpath, &f);
            err = bf_open (logpath, 'u', DEFBUFFSIZE, &bf);
            if (!err)
            {
              if (f.object)
              {
                err = bf_seek (bfp, f.length);
              }
              writesystimedate (temp);
              bf_printf (bfp, "------------------------------------------\n", st);
              if (sync_stop)
              {
                bf_printf (bfp, "Synchronisation stopped by user at %s\n", temp);
              }
              else
              {
                bf_printf (bfp, "SyncDiscs completed at %s\n", temp);
              }
              bf_printf (bfp, "------------------------------------------\n\n\n", st);
              err = bf_closec (bfp, err, logpath, TEXT);

              /* Auto open logfile moved from here to outside the loop */
            }
          }
        }
      }
    }
    /* Have we arrived here after the user has stopped the sync process
     * with other jobs still in the queue?
    */
    if (sync_stop && (jobs.number > 1))
    {
      if (jobs.number == 2)
      {
        err = confirm (CONYN, &answer, "There is one more job in the queue. "
                                       "Do you wish to cancel that job too?");
      }
      else
      {
        err = confirm (CONYN, &answer, "There are %d more jobs in the queue. "
                       "Do you wish to cancel those jobs too?", jobs.number - 1);
      }

      if (answer)
      {
        for (i=0; i<jobs.number; i++)
        {
          /* free the memory for the all the jobs */
          err = flex_free ((flex_ptr) &jobs.settings[i]);
          jobs.settings[i] = NULL;
        }
        /* set jobs counter to zero */
        jobs.number = 0;
      }
      else
      {
        /* user wishes to complete rest of queue as normal */
        /* move any more jobs down in the queue */
        if (jobs.number > 1)
        {
          for (i=1; i<jobs.number; i++)
          {
            *jobs.settings[i-1] = *jobs.settings[i];
          }
        }
        jobs.number--;
        /* free the memory for the job just completed */
        err = flex_free ((flex_ptr) &jobs.settings[jobs.number]);
        jobs.settings[jobs.number] = NULL;
        /* We need to reset the flag sync_stop else the sync will be
         * aborted anyway
        */
        sync_stop = 0;
      }
    }
    else
    {
      /* move any more jobs down in the queue */
      if (jobs.number > 1)
      {
        for (i=1; i<jobs.number; i++)
        {
          *jobs.settings[i-1] = *jobs.settings[i];
        }
      }
      jobs.number--;
      /* free the memory for the job just completed */
      err = flex_free ((flex_ptr) &jobs.settings[jobs.number]);
      jobs.settings[jobs.number] = NULL;
    }

    progress_update_job_count_field ();

#ifdef KILL_TRAPDELETE
    /* Did we disable trapdelete for this job? */
    if (sync.disable_trapdelete && reenable_trapdelete)
    {
      os_cli ("TrapDelete_TrapDeletions 1");
    }
#endif

    /* Free the zone file memory */
    if (sync.use_zone_file) zone_finit ();


  } while ((jobs.number > 0) && !sync_stop);
  /*  End of main loop, now do all the tidying up */

  flex_free ((flex_ptr)&prevlogpath);

#ifdef KILL_SPARKFS
  /* Do we need to reenable SparkFS? */
  if (sync.kill_sparkfs && reenable_sparkfs)
  {
    /* Running SparkFS will reload the modules */
    os_cli ("WimpTask <SparkFS$Dir>");
  }
#endif

  do_polls (3);

  /********************************************************************
   * Auto-opening the log file has been moved here outside the job    *
   * running loop to eliminate problems with opening 'the same'       *
   * logfile multiple times, or lots of logfiles, which might occur   *
   * depending on various settings flags.                             *
   * Now only the last logfile will be opened when running multiple   *
   * queued jobs.                                                     *
   ********************************************************************/

  if (sync.log && sync.auto_open_log && !pathconflict && !device_problem)
  {
    err = show_log_file (logpath);
  }

  /* finalise and reset flags */
  sync_running = 0;
  sync_stop = 0;
  if (sync.multitask)
  {
    remzeroevent (synczero, 0);
  }

  if (err)
  {
    os_cli ("Set SyncDiscs$Result 1");
    err1 = make_errlog_entry (123, "Error during processing: %s", err->errmess);
  }
  else
  {
    os_cli ("Set SyncDiscs$Result 0");
  }

  if (sync_error)
  {
    err = make_errlog_entry (0, "\nSyncDiscs completed\n");
    err = make_errlog_entry (0, "-------------------------------------------\n\n\n");
    sprintf (temp, "Filer_Run %s", ERR_LOG);
    os_cli (temp);
  }

  return (err);
}



/**************************************************
 * Object type                                    *
 * 0 - Not found                                  *
 * 1 - File found                                 *
 * 2 - Directory found                            *
 * 3 - Image file found (both file and directory) *
 *                                                *
 **************************************************/

typedef struct gbpb10data
{
  int   load_add;
  int   exec_add;
  int   length;
  int   attributes;
  int   obj_type;
  char  name;
} gbpb10data;



/********************************************************************
 *                                                                  *
 * Read the next object in the given directory                      *
 * Returns name of object                                           *
 * object offset for next read (-1 if finished)                     *
 * number of objects read (1 for object read, 0 if nothing read)    *
 *                                                                  *
 ********************************************************************/

static os_error *get_next_object (char *parentdir, int *obj_offset,
                                  char *buff, int size, int *read)
{
  os_error   *err;
  os_regset   rx;

  /* Use OS_GBPB 10 to read file info from a directory */

  rx.r[0] = 10;
  rx.r[1] = (int) parentdir;
  rx.r[2] = (int) buff;
  rx.r[3] = 1;                        /* ask for one object */
  rx.r[4] = *obj_offset;
  rx.r[5] = size;
  rx.r[6] = 0;                        /* match all files */

  err = os_swix (OS_GBPB, &rx);

  /* save back the object offset and number of objects read */
  *obj_offset = rx.r[4];
  *read = rx.r[3];
  return (err);
}






/************************************************************/

static os_error *copy_dir_file_by_file (char *primary, char *secondary,
                                        int move)
{
  os_error   *err;

  char       *dirlist;          /* ptr to flex memory to hold the
                                 * directories found in the scan */
  char       *scandir;          /* pointer to directory to be scanned */
  char       *storedir;         /* pointer to free memory to store more
                                 * directories */
  int         scan_offset;      /* offset from start of dir tree for scan
                                 * dir */
  int         store_offset;     /* offset from start of dir tree for
                                 * storage of further directories awaiting
                                 * scanning */

  int         dtbuf_size = 8000;        /* initial size of buffer to alloc */

  char        dirname[FSMAXWIMPMSGPATH];
  char        newsubpath[FSMAXWIMPMSGPATH];
  char        newsecdir[FSMAXWIMPMSGPATH];
  char        this_dir[FSMAXWIMPMSGPATH];
  char       *from;
  char       *to;

  /* for scanning for directories with OS_GBPB */
  int         obj_offset;
  int         obj_type = 99;
  char        buff[sizeof (fxstat)];
  char       *newdir;
  char       *fname;
  int         read;

  BOOL        open = FALSE;

  int         pass = 0;         /* This is required to change the behaviour
                                 * on the first pass because the list of
                                 * dirs is empty at the start, but will
                                 * normally contain one or more after a
                                 * pass. We need the pointer to point to the
                                 * start of the list on the second pass,
                                 * and then be incremented */

  int  action;     /* Flag for use in checking whether to ignore a file */

  err = NULL;

  /* allocate some memory for the dir tree */
  err = flex_alloc ((flex_ptr) & dirlist, dtbuf_size);
  if (err) return (err);
  /* initialise the memory and pointers */
  dirlist[0] = 0;
  scan_offset = 0;
  store_offset = 0;
  scandir = dirlist + scan_offset;

  storedir = dirlist + store_offset;


  /* now set starting directory to scan */
  strcpy (dirname, primary);

  do
  {
    if (poll_pause_or_stop ()) break;
    /* Read contents of the next directory */
    strcpy (this_dir, scandir);
    obj_offset = 0;
    if (pass)
    {
      sprintf (dirname, "%s.%s", primary, this_dir);
    }


    while (obj_offset != -1)
    {
      if (poll_pause_or_stop ())
        break;
      /* get the next object in the directory */
      get_next_object (dirname, &obj_offset, buff, sizeof (fxstat), &read);
      if (read)
      {
        /* a name has been returned */
        obj_type = (*(int *) (buff + 0x10));
        switch (obj_type)
        {
        case 1:
        case 3:
          /* must be a file or an archive so just copy it */
          /* use newsubpath and newsecdir as buffers for the filenames */
          /* construct the from and to filenames */
          from = newsubpath;
          to = newsecdir;
          fname = buff + 0x14;
          sprintf (from, "%s.%s", dirname, fname);
          /* Check it is not our logfile */
          if (cstrcmp (logpath, from))
          {
            if (this_dir[0] == '\0')
            {
              sprintf (to, "%s.%s", secondary, fname);
            }
            else
            {
              sprintf (to, "%s.%s.%s", secondary, this_dir, fname);
            }
            /* now copy the file */
            /* Do some checks - is it open? */
            /* If it is - log the details */
            /* ...and move on (don't try to copy) */
            err = fs_file_is_writable (from, &open);
            if (open)
            {
              err = make_errlog_entry (126, "%s could not be copied, "
                                       "file was open for writing\n\n", from);
            }
            else
            {
              /* Do we need to ignore the object */
              action = 0;
              if (sync.use_zone_file)
              {
                if (inzone (from, NULL)) action = 1;
              }
              if (action == 1)
              {
                /* ignore the file and move on.... */
                if (bfp)
                {
                  bf_printf (bfp, "ignore:%s\n", from);
                }
                if (progress_handle)
                {
                  progress_update ("Ignoring", from + primary_length);
                }
                /* no need to check whether the destination needs to be
                 * removed because we are copying a _new_ directory file by
                 * file */
              }
              else
              {
                /* OK to copy...  */
                if (progress_handle)
                {
                  progress_update ("Copying file", from + primary_length);
                }
                err = copy_file (from, to, move);
              }
            }
          }
          break;

        case 2:
          /* obj is a dir */
          /* construct the new primary subdir name */
          newdir = buff + 0x14;
          if (this_dir[0] == '\0')
          {
            strcpy (newsubpath, newdir);
          }
          else
          {
            sprintf (newsubpath, "%s.%s", this_dir, newdir);
          }
          /* Does it match an ignore path? */
          /* Construct the primary dir name... */
          /* Use newsecdir buffer for storage */
          sprintf (newsecdir, "%s.%s", dirname, newsubpath);
          /* skip creation and storage if it is... */

          action = 0;
          if (sync.use_zone_file)
          {
            if (inzone (newsecdir, NULL)) action = 1;
          }

          if (action == 1)
          {
            if (bfp)
            {
              bf_printf (bfp, "ignore:%s\n", newsecdir);
            }
            if (progress_handle)
            {
              progress_update ("Ignoring", newsecdir + primary_length);
            }
          }
          else
          {
            /* store the name to use on later pass */
            storedir = dirlist + store_offset;
            strcpy (storedir, newsubpath);
            /* calc the new offset */
            store_offset += strlen (newsubpath) + 1;
            /* null the next entry */
            storedir = dirlist + store_offset;
            storedir[0] = 0;

            /* construct the secondary dir name */
            sprintf (newsecdir, "%s.%s", secondary, newsubpath);
            if (progress_handle)
            {
              progress_update ("Creating directory", newsecdir + secondary_length);
            }
            /* create the directory */
            fs_cdir (newsecdir);

            /* Check length of buffer and extend if necessary */
            if ((store_offset + 2 + NAMELEN) > dtbuf_size)
            {
              dtbuf_size += 2000;
              err = flex_extend ((flex_ptr) & dirlist, dtbuf_size);
              if (err)
              {
                flex_free ((flex_ptr) & dirlist);
                return (err);
              }
            }
          }
          break;
        }
      }
    }

    /* Now go to next dir to scan */

    scandir = dirlist + scan_offset;
    if (pass)
    {
      scan_offset += strlen (scandir) + 1;
    }
    pass = 1;
    scandir = dirlist + scan_offset;
  }
  while (scandir[0] != '\0');

  flex_free ((flex_ptr) & dirlist);

  /*************************************************************************
   *                                                                       *
   * If the 'move' flag is set, we cannot delete the primary directories   *
   * as we go since sub-directories would be removed before they had been  *
   * scanned. Therefore we can only remove the primary directories (now    *
   * empty) after the scan has finished, i.e. do it now.                   *
   *                                                                       *
   *************************************************************************/

  if (move)
  {
    if (progress_handle)
    {
      progress_update ("Wiping directory structure", primary);
    }
    err = wipe_file (primary);
  }

  return (err);
}









/**************************************************************************
 *                                                                        *
 * Functions to manage the keeping of previous log files. Deletion of one *
 * or more old logs. Renaming of the remaining logs to one higher in the  *
 * series                                                                 *
 **************************************************************************/

static os_error *sd_delete_old_logs (char *fname, int i)
{
  os_error   *err;
  char        fn[FSMAXWIMPMSGPATH];
  int         type;

  sprintf (fn, "%s%d", fname, i);
  err = fs_exists (fn, &type);
  if (type && !err)
    err = fs_wipe (fn);
  return (err);
}



/************************************************************/

static os_error *sd_rename_old_logs (char *fname, int i)
{
  os_error   *err, *e;
  char       *from;
  char       *to;
  int         type = 0;
  char       *leafname;
  int         len;
  char        ind[4];


  err = flex_alloc ((flex_ptr)&from, FSMAXWIMPMSGPATH);
  if (!err)
  {
    err = flex_alloc ((flex_ptr)&to, FSMAXWIMPMSGPATH);
    if (!err)
    {
      if (i == 0)
        strcpy (from, fname);
      else
        sprintf (from, "%s%d", fname, i);
      sprintf (to, "%s%d", fname, i + 1);
      /* check that file exists before trying to rename it */
      e = fs_exists (from, &type);
      if (!e && type)
      {
        e = fs_rename (from, to);
        if (e)
        {
          /* there was an error */
          /* is the leaf name of length 10 chars on a 10 char limited
           * file system? */
          leafname = leaf (fname);
          if (strlen (leafname) == 10)
          {
            /* try another rename by overwriting the last character
             * of the filename rather than appending */
            len = strlen (fname);
            strcpy (from, fname);
            if (i)
            {
              from[len-1] = 0;
              sprintf (ind,"%d",i);
              strcat (from, ind);
            }
            /* Does this file exist? */
            e = fs_exists (from, &type);
            if (!e && type)
            {
              /* OK to try the rename */
              strcpy (to, fname);
              to[len-1] = 0;
              sprintf (ind, "%d", i + 1);
              strcat (to, ind);
              err = fs_rename (from, to);
            }
          }
        }
      }
      flex_free ((flex_ptr)&to);
    }
    flex_free ((flex_ptr)&from);
  }
  return (err);
}





/************************************************************/

static os_error * logfile_manage (syncsettings *sync, BOOL *open)
{
  os_error  *err;
  int        type;
  int        i;


  /* Check if logfile exists */
  err = fs_exists (logpath, &type);
  if (err) return (err);
  if (!type)
  {
    /* doesn't exist - is the dir path in situ? If not create it */
    fs_create_dir_chain (logpath);
  }
  else
  {
    /* For just in case the log file has been left open due to some sort
     * of crash */
    err = fs_file_close_if_open (logpath, open);
  }
  /* if creating new log, make the backups of the old logs first. The
   * oldest log is deleted, and all the others are moved back one in the
   * chain
   */
  if (sync->new_log)
  {
    /* Max num of old logs is 9. The number of logs being kept might have
     * been changed downwards, so try to delete all excess ones as well
     * as the nominal oldest one
     */
    if (sync->num_old_logs == 0)
    {
      /* Don't want to keep any old logs. Just delete the current one */
      err = fs_exists (logpath, &type);
      if (type && !err)
        err = fs_wipe (logpath);
    }
    else
    {
      for (i = 9; i >= sync->num_old_logs; i--)
      {
        sd_delete_old_logs (logpath, i);
      }
      /* now rename all the old logs */
      for (i = sync->num_old_logs - 1; i >= 0; i--)
      {
        sd_rename_old_logs (logpath, i);
      }
    }
  }
  return (err);
}




/***************************************************************************
 *                                                                         *
 * Functions to build and write the lists of object differences in the     *
 * Compare output                                                          *
 * store_cmp_list_item()                                                   *
 * Simply copies the object path into the end of the list, increments      *
 * the offset to the start of free memory, and writes a terminator.        *
 *                                                                         *
 * Length of buffer still free is checked and more claimed as necessary    *
 *                                                                         *
 ***************************************************************************/

static os_error *store_cmp_list_item_2 (char *objpath, int index)
{
  os_error   *err;
  char       *ptr;

  err = NULL;

  /* add the new path to the list */
  ptr = compare_result[index].list + compare_result[index].offset;
  strcpy (ptr, objpath);
  /* update the offset */
  compare_result[index].offset += strlen (ptr) + 1;
  /* ... and null the next object */
  ptr = compare_result[index].list + compare_result[index].offset;
  ptr[0] = 0;
  /* now check remaining length of buffer */
  if ((compare_result[index].offset + NAMELEN) > compare_result[index].size)
  {
    /* need to extend the buffer */
    compare_result[index].size += 1000;
    err = flex_extend ((flex_ptr) &compare_result[index].list,
                                  compare_result[index].size);
  }
  compare_output = 1;                /* a flag to show that compare has output
                                        something */
  return (err);
}


/************************************************************/

static os_error *store_cmp_list_item (int acn, int n, int index)
{
  char        s[FSMAXWIMPMSGPATH];

  s[0] = 0;

  /* get path of object */
  filepath1 (s, acn, n);
  return (store_cmp_list_item_2 (s, index));
}


/*************************************************************************
 *                                                                       *
 * output_cmp_list_items(). Simply starts at the beginning of the list   *
 * and writes each object path to the compare file. The end of the list  *
 * is reached when the next object is an empty (null) string             *
 *                                                                       *
 *************************************************************************/

static os_error *output_cmp_list_items (int index)
{
  os_error   *err;
  char       *p;

  err = NULL;

  p = compare_result[index].list;
  while (p[0] != '\0')
  {
    comparewrite ("  %s\n", p);
    p += strlen (p) + 1;
  }
  comparewrite ("\n");
  return (err);
}




/************************************************************/

static os_error *output_cmp_list (int index, char* desc)
{
  os_error   *err;

  err = NULL;
  /* check which lists we need to output */
  if (compare_result[index].list[0] != '\0')
  {
    /* this list is populated */
    comparewrite ("%s\n", desc);
    output_cmp_list_items (index);
  }
  return (err);
}





/*************  End of c.syncdiscs  ***********************/

