/* c.fart
 *
 * Disktree copier. Better than far.
 * Written for Graham Toal by Tiggr.
 * Not optimizing space. I'm lazy.
 * (C) 1990 WayForward Technologies */

#include <kernel.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef unsigned int uint;
typedef int boolean;

#undef TRUE
#define TRUE (0==0)
#undef FALSE
#define FALSE (0!=0)

typedef struct dirsize
  {
    /* all sizes are rounded up to 1024 bytes (E-type sector size) */
    uint size;        /* size taken by files in this directory */
    uint rsize;       /* recusive size, including this and other directories */
  } *dirsize_t;

typedef struct dirinfo
  {
    char *fullname;
    struct entry *first;
    struct dirsize size;
    boolean part_done;
  } *dirinfo_t;

#define DIR_SIZE      (0x800)
#define FLOPPY_SIZE   (0xc7000)

typedef struct entry
  {
    char *name;  /* leaf name */
    dirinfo_t dir;
    uint size;
    boolean done;
    struct entry *next;
  } *entry_t;

struct dirinfo root;

typedef struct file
  {
    char *name;
    struct file *next;
  } *file_t;

file_t already_done = NULL;
char *logfn;
FILE *logfile;
long pos = 0;
boolean doing_recover = FALSE;

/********************************
  hmpf */

static void
hmpf (int i)
{
  fprintf (stderr, "Fatal error occured. Goodbye...\n");
  exit (i);
} /* hmpf */


/********************************
  hmpf_system */

static int
hmpf_system (char *s)
{
  printf ("%s\n", s);
  fflush (stdout);
  return (_kernel_system (s, 0));
} /* hmpf_system */


/********************************
  usage */

static void
usage (char *progname)
{
  printf ("This is FART version 1.10 (23-Jan-1990), written by Tiggr.\n\
This utility was written for a specific purpose: doing what `far' does\n\
not do very well: copying a large directory tree from an adfs harddisc\n\
onto floppies. Don't complain if it does not match other expectations.\n\
\n\
Usage: %s <source root> <logfile> [-f <filename>]\n\
\n\
The <source root> of the directory tree to be copied should be given as a\n\
leaf name relative to the current directory (@). If the -f option is not\n\
specified <logfile> is used to write the filename of each file as it is\n\
copied to a floppy. If -f option is specified, the <filename> given is\n\
searched for in <logfile> (which must already exist). All files which appear\n\
in <logfile> before and including the given <filename> are not copied.\n\
Copying then starts after <filename>.\n\
\n\
FART cannot cope with files which don't fit on a floppy. FART will exit on\n\
any error condition occuring (but will try to close the logfile properly).\n\
\n\
BUGS\n\
  If FART recovers from a logfile and it crashes before having written so\n\
much to the logfile that the file's length would extend, the logfile will\n\
contain erroneous information.\n\
\n\
Share and Enjoy! (but without warranty)\n", progname);
} /* usage */


/********************************
  xswi
    performs the swi call, exiting the program upon error */

static void
xswi (uint swine, _kernel_swi_regs *r)
{
  _kernel_oserror *e = _kernel_swi (swine, r, r);

  if (!e)
    return;
  fprintf (stderr, "Fatal error: 0x%x:\n", e->errnum);
  fprintf (stderr, "%s\n", e->errmess);
  exit (1);
} /* xswi */


/********************************
  free_space
    returns free space on floppy adfs::0 */

static uint
free_space ()
{
  _kernel_swi_regs r;

  r.r[0] = (int) ":0";
  xswi (0x40243, &r);   /* ADFS_FreeSpace */
  return (r.r[0]);
} /* free_space */


/********************************
  logit
    writes an entry into the logfile */

static void
logit (char *name)
{
  logfile = fopen (logfn, "r+");
  fseek (logfile, pos, SEEK_SET);
  fprintf (logfile, "%s\n", name);
  pos = ftell (logfile);
  fclose (logfile);
} /* logit */


/********************************
  findname
    returns TRUE iff the name can be found in the already_done list */

static boolean
findname (char *name)
{
  file_t f = already_done;
  char *s, *t;

  while (f)
    {
      s = name;
      t = f->name;
      while (*s && (*s == *t ||
             isalpha (*s) && isalpha (*t) && toupper (*s) == toupper (*t)))
        {
          s++;
          t++;
        }
      if (!*s && !*t)
        {
          printf ("found: '%s'\n", name);
          return (TRUE);      
        }
      f = f->next;
    }
  return (FALSE);
} /* findname */


/********************************
  build_list
    builds a list containing information on the objects. */

static void
build_list (dirinfo_t dir)
{
  _kernel_swi_regs r;
  entry_t cur = NULL, next;
  union
    {
      char c[256];
      uint i[64];
    } buf;
  char *name = &buf.c[20];
  uint size = 0, rsize = 0;

  r.r[0] = 10;
  r.r[1] = (int) dir->fullname;
  r.r[2] = (int) &buf.i[0];
  r.r[4] = 0;
  r.r[5] = 256;
  r.r[6] = 0;

  while (r.r[4] != -1)
    {
      r.r[3] = 1;
      xswi (0x0c, &r);   /* OS_GBPB */
      if (r.r[3] != 1)
        continue;
      if (buf.i[4] < 1 || buf.i[4] > 2)
        {
          printf ("Unrecognised object type %d for file `%s.%s'\n",
                  buf.i[4], dir->fullname, name);
          exit (3);
        }
      next = calloc (1, sizeof (struct entry));
      if (cur)
        cur->next = next;
      else
        dir->first = next;
      cur = next;
      cur->name = malloc (1 + strlen (name));
      strcpy (cur->name, name);
      cur->size = buf.i[2];
      if (buf.i[4] == 2)
        {
          /* directory */
          cur->dir = calloc (1, sizeof (struct dirinfo));
          cur->dir->fullname = malloc (2 + strlen (name) +
                                       strlen (dir->fullname));
          sprintf (cur->dir->fullname, "%s.%s", dir->fullname, name);
          build_list (cur->dir);
          rsize += cur->dir->size.rsize;
        }
      else
        size += (buf.i[2] + 1023) & ~1023;
    }
  dir->size.size = size;
  dir->size.rsize = rsize + size + DIR_SIZE;
} /* build_list */


/********************************
  do_one
    returns TRUE if it could copy the whole directory */

static boolean
do_one (dirinfo_t dir)
{
  entry_t cur;
  boolean ret = FALSE, do_recursive = TRUE, b,
          have_skipped = FALSE, dolog = FALSE;
  char src[1024], cmd[3072];
  int left;

  if (findname (dir->fullname))
    return (TRUE);
  sprintf (cmd, "cdir adfs::0.%s", dir->fullname);
  if (hmpf_system (cmd))
    hmpf (1);
  for (cur = dir->first; cur; cur = cur->next)
    {
      if (cur->done)
        continue;
      left = free_space ();
      ret = TRUE;
      if (cur->dir)
        {
          /* directory */
          if (!cur->dir->part_done && cur->dir->size.rsize <= left &&
              !doing_recover)
            {
              /* lucky; recursize directory fits! */
/*
              printf ("dir fits: sizeof (\"%s\") = %d\n",
                      cur->dir->fullname, cur->dir->size.rsize);
*/
              cur->done = TRUE;
              if (findname (cur->dir->fullname))
                continue;
              sprintf (cmd, "copy %s adfs::0.%s rf~cvq",
                       cur->dir->fullname, cur->dir->fullname);
              if (hmpf_system (cmd))
                hmpf (1);
              logit (cur->dir->fullname);
              dolog = TRUE;
            }
          else if (DIR_SIZE < left)
            {
              if (findname (cur->dir->fullname))
                {
                  cur->done = TRUE;
                  continue;
                }
              b = do_one (cur->dir);
              cur->dir->part_done = TRUE;
              cur->done = b;
              if (b)
                {
                  logit (cur->dir->fullname);
                  cur->done = TRUE;
                  dolog = TRUE;
                }
              do_recursive = do_recursive && b;
            }
          else
            {
              if (findname (cur->dir->fullname))
                {
                  cur->done = TRUE;
                  continue;
                }
              have_skipped = TRUE;
            }
        }
      else
        {
          if (cur->size <= left)
            {
/*
              printf ("file fits: sizeof (\"%s.%s\") = %d\n",
                      dir->fullname, cur->name, cur->size);
*/
              cur->done = TRUE;
              sprintf (src, "%s.%s", dir->fullname, cur->name);
              if (findname (src))
                {
                  continue;
                }
              logit (src);
              sprintf (cmd, "copy %s adfs::0.%s f~cvq", src, src);
              if (hmpf_system (cmd))
                hmpf (1);
            }
          else
            {
              sprintf (cmd, "%s.%s", dir->fullname, cur->name);
              if (findname (cmd))
                {
                  cur->done = TRUE;
                  continue;
                }
              have_skipped = TRUE;
            }
        }
    }
  if (do_recursive && !have_skipped && dolog)
    logit (dir->fullname);
  return (do_recursive && !have_skipped);
} /* do_one */


/********************************
  cleanup */

void
cleanup (void)
{
  fflush (logfile);
  fclose (logfile);
  return;
} /* cleanup */


/********************************
  main */

int
main (int argc, char **argv)
{
  struct dirinfo root;
  char *s, *resfn = NULL;
  file_t cf, *nf;
  char fname[1024];
  int c, i, j;

  if (argc != 3 && argc != 5 || (argc == 5 && strcmp (argv[3], "-f")))
    {
      usage (argv[0]);
      exit (2);
    }

  for (s = argv[1]; *s; s++)
    if (strchr ("@^%&:#-", *s))
      {
        fprintf (stderr,
                 "Please specify directory to be copied relative to @.\n");
        exit (2);
      }

  root.fullname = argv[1];
  logfn = argv[2];

  if (argc == 5)
    resfn = argv[4];

  logfile = fopen (logfn, resfn ? "r+" : "w");

  if (logfile == NULL)
    {
      fprintf (stderr, "failed to open logfile '%s'\n", logfn);
      exit (1);
    }

  if (atexit (cleanup))
    {
      fprintf (stderr, "failed to register exit handler\n");
      exit (1);
    }

  if (resfn)
    {
      /* Build list of already-done filenames. */

      doing_recover = TRUE;
      nf = &already_done;
      while (! feof (logfile))
        {
          i = 0;
          while ((c = fgetc (logfile)) != '\n' && c != EOF)
            fname[i++] = c;
          fname[i] = 0;
          if (i)
            {
              for (j = 0; j < i; j++)
                if (fname[j] != resfn[j] &&
                    (isalpha (fname[j]) && isalpha (resfn[j]) &&
                     toupper (fname[j]) != toupper (resfn[j])))
                  break;
              *nf = cf = malloc (sizeof (struct file));
              nf = &cf->next;
              cf->name = malloc (i + 1);
              strcpy (cf->name, fname);
              if (j == i)
                {
                  fseek (logfile, ftell (logfile), SEEK_SET);
                  break;
                }
            }
        }
      *nf = NULL;
    }
  pos = ftell (logfile);
  fclose (logfile);
  build_list (&root);
  for (;;)
    {
      printf ("Please insert fresh floppy and press return");
      fflush (stdout);
      while ((c = fgetc (stdin)) != '\n')
        if (c == EOF)
          exit (1);
      printf ("\n");
      if (hmpf_system ("-adfs-mount :0"))
        hmpf (1);
      if (hmpf_system ("dir \\"))
        hmpf (1);
      if (do_one (&root))
        break;
      if (hmpf_system ("-adfs-dismount :0"))
        hmpf (1);
    }
  fclose (logfile);
  return (0);
} /* main */

/* EOF fart.c */