/* 
 * Assign Command - C Language Equivalent. 
 *
 *     This command looks and feels like the original AmigaDOS Assign command
 *  except that it is written in C and thus available for "forking." Also 
 *  since it is written in C it is a somewhat larger than it's BCPL counterpart
 *  although a good assembly hack could probably fix that. 
 *
 *  (c) Copyright 1986 Charles McManis, All rights reserved.
 *  This code may be copied for private use only. It may not be
 *  included as part of any commercial package in whole or in
 *  part without the express written permission of the Author.
 *  
 */

#include <exec/types.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>
#include <stdio.h>

extern struct DosLibrary *DOSBase;

#define PATHSIZE        128     /* This is max numbers in a path (Arbitrary) */
struct DosInfo          *di; 
struct FileInfoBlock    *fi;    /* Our file info (on directory assigns) */
char                    *pathstr, *tmpstr;

/* 
 * Function - MyExit(ConditionCode)
 *   This is the final exit routine, if we can't find something we need 
 * like memory we go here and free up what we have. Exit code is 'cc'.
 */
void MyExit(cc)

int     cc;

{
  if (fi != NULL)      FreeMem(fi,sizeof(struct FileInfoBlock));
  if (pathstr != NULL) FreeMem(pathstr,PATHSIZE);
  if (tmpstr != NULL)  FreeMem(tmpstr,PATHSIZE);
  exit(cc);
}

/*
 * Function - CvtBstr(str)
 *    This function takes a BPTR to a BSTR and returns a pointer to an 
 * equivalent C string that may be printed or copied.
 */

#define STRSIZE 81

char *
CvtBstr(bstr)

char    *bstr;

{
  int   i,mx;
  char  *foo;
  static char   tmpstr[STRSIZE]; /* Maximum size */
  
  foo = (char *)(BADDR(bstr));
  mx = (foo[0] < STRSIZE) ? foo[0] : STRSIZE; /* Safety net */
  for (i=0; i<mx; i++) tmpstr[i] = foo[i+1];
  tmpstr[i] = '\0';
  return(tmpstr);
}

/* 
 * Function - DeleteDevice(Name)
 *     This function will search the device list for a directory entry
 * matching the one passed, and remove it from the device list.
 *
 * Returns 0 if successful.
 */

int
DeleteDevice(nm)

char    *nm;            /* String pointer to the Name */

{
  struct DeviceList     *prevdev,*thisdev;
  char                  *str;
  int                   i,found;

  prevdev = NULL;
  thisdev = (struct DeviceList *)BADDR(di->di_DevInfo);
  found = -1;
  while (thisdev != NULL) {
    if (thisdev->dl_Type == DLT_DIRECTORY) {
      str = CvtBstr(thisdev->dl_Name);
      if (stricmp(str,nm) == 0) {
        found = 0;
        if (prevdev == NULL) {                  /* If first device      */
          Forbid();                             /* Turn off tasking     */
          di->di_DevInfo = thisdev->dl_Next;    /* Delete from list     */
          Permit();                             /* Turn tasking back on */
        }
        else { 
          Forbid();                             /* Turn off tasking     */
          prevdev->dl_Next = thisdev->dl_Next;  /* Delete from list     */
          Permit();                             /* Back to multitasking */
        }
        /* Now free the Lock, the String, and the DevList structure     */
        UnLock(thisdev->dl_Lock);                /* free the lock       */
        str = (char *)(BADDR(thisdev->dl_Name)); /* easier to read      */
        i = str[0];
        FreeMem(str,i);                         /* Free the string      */
        FreeMem(thisdev,sizeof(struct DeviceList)); /* Free struct      */
        break;                                  /* Exit the while loop  */
      } /* if matched devices           */
    } /* if it was a directory entry    */
    prevdev = thisdev;                  /* If not found, walk the list  */
    thisdev = (struct DeviceList *)BADDR(thisdev->dl_Next);
  } /* While */
  return(found);
}

/*
 * Main code, options of the Assign command are name, directory, or List.
 * the default is to list the current assignments, Volumes, and devices.
 *
 */

void main(argc,argv)

int     argc;           /* Number of arguments (max 3)  */
char    *argv[];        /* Text of the arguments        */

{
  /* The pointers here make it easier later */

  struct RootNode       *rn;
  struct DeviceList     *dl, *curdev, *xl;
  struct DeviceNode     *dn;
  struct FileLock       *fl;
  BPTR                  plock, thislock, tb;
  char                  Name[41],Path[81],*sptr;
  int                   i,listem;

  /* Get some memory for various buffers */
  fi = (struct FileInfoBlock *)AllocMem(sizeof(struct FileInfoBlock),0);
  if (fi == NULL) MyExit(RETURN_FAIL);
  pathstr = (char *)AllocMem(PATHSIZE,0);
  if (pathstr == NULL) MyExit(RETURN_FAIL);
  tmpstr = (char *)AllocMem(PATHSIZE,0);
  if (tmpstr == NULL) MyExit(RETURN_FAIL);

  printf("Assign command substitute v1.0\n");
  /* Then we track down the head of the Device list from the Root Node */
  rn = (struct RootNode *)DOSBase->dl_Root;
  di = (struct DosInfo *)BADDR(rn->rn_Info);
  /* dl becomes the anchor point that we always start from */
  dl = (struct DeviceList *)BADDR(di->di_DevInfo);  
  listem = FALSE; /* Initially no list output   */
  Name[0] = '\0'; /* No name parameter          */
  Path[0] = '\0'; /* And no path specifier      */

/*************************************************************************
 * Process the arguments passed. To be 100% compatible with the AmigaDOS *
 * assign command.                                                       *
 *************************************************************************/

  if (argc > 4) {
    printf("Too many arguments, usage is Assign [Name:] [Dir:] [List]\n"); 
    MyExit(RETURN_WARN);
  }
  for (i=1; i<argc; i++) 
    if (stricmp(argv[i],"LIST") == 0) listem = TRUE;
    else if (Name[0] == '\0') strcpy(Name,argv[i]);
    else if (Path[0] == '\0') strcpy(Path,argv[i]);
    else {
      printf("Bad Arguments, usage is Assign [Name:] [Dir:] [List]\n");
      MyExit(RETURN_WARN);
    }
  if (strcmp(Name,"?") == 0) {
    printf("Usage is Assign [Name:] [Dir:] [List]\n");
    MyExit(RETURN_OK);
  }
  if (argc > 1) {
    if ((sptr = (char *)strchr(Name,':')) == NULL) {
      printf("Improper device name format.\n");
      MyExit(RETURN_WARN);
    }
    *sptr = '\0'; /* Eliminate the trailing colon */
  }
  /* Ok, now we have the parameters they work like this, if a name was 
   * supplied but no path then it is a deassign operation, if both are
   * supplied it is an assign operation, if both are missing it is a 
   * list operation 
   */
  if ((Name[0] != '\0') && (Path[0] == '\0')) /* DEASSIGN 'Name' */
    if (DeleteDevice(Name) != 0) {
      printf("Device %s: Not found.\n");        
      MyExit(RETURN_WARN);
    }
  if ((Name[0] != '\0') && (Path[0] != '\0')) { /* ASSIGN 'Name' */
    (void) DeleteDevice(Name);          /* Delete in case it exists        */
    if ((thislock = Lock(Path,ACCESS_READ)) != 0) {
      dl = (struct DeviceList *)AllocMem(sizeof(struct DeviceList),0);
      sptr = (char *)AllocMem(strlen(Name)+1,0); 
      strcpy(sptr+1,Name); /* Create BSTR out of Name */
      *sptr = strlen(Name);
      dl->dl_Name = (BSTR *)(((long)sptr) >> 2); /* Convert to BSTR ptr    */
      dl->dl_Lock = thislock;           /* Put the BPTR value into Lock    */
      dl->dl_Type = DLT_DIRECTORY;      /* The type is Directory           */
      fl = (struct FileLock *)(BADDR(thislock)); /* for convience          */
      dl->dl_Task = fl->fl_Task;        /* Copy the message port task ptr  */
      tb = (BPTR)(((long)dl) >> 2);     /* Make a BPTR out of our pointer  */
      Forbid();                         /* Disable multitasking            */
      dl->dl_Next = di->di_DevInfo;     /* Insert it at the head           */
      di->di_DevInfo = tb;              /* Put a pointer to us in DevInfo  */
      Permit();                         /* Start up Multitasking again     */
    } /* if we got the lock */
    else {
      printf("Couldn't get a Lock on %s\n",Path);
      MyExit(RETURN_WARN);
    }
  } /* ASSIGN operation */
  if (((Name[0] != '\0') || (Path[0] != '\0')) && ! listem) MyExit(RETURN_OK);

  /* The remainder of the code lists out the current assignments, if
   * no arguments, or the argument "List" is included it will run this
   * section.
   *
   * Pass 1: Print out all of the known volumes, if they have a handler
   *         task present then they are mounted in a physical device
   */
  curdev = dl;
  printf("Volumes:\n");
  while (curdev != NULL) {
    if (curdev->dl_Type == DLT_VOLUME) {
      printf("%s ",CvtBstr(curdev->dl_Name));
      if (curdev->dl_Task != NULL) printf("[Mounted]");
      printf("\n");
    }
    curdev = (struct DeviceList *)BADDR(curdev -> dl_Next);
  }
  printf("\n");

  /* Pass 2 : Scan for directory redirections. If the volume that the 
   *          directory is redirected to is not mounted then we just
   *          print out the volume name. If it is mounted we print 
   *          out the full path. If we have been redirected to a device
   *          like 'RAM' then we just print the device name.
   */
  curdev = dl;
  printf("Directories:\n");
  while (curdev != NULL) {
    if (curdev->dl_Type == DLT_DIRECTORY) {
      strcpy(tmpstr,CvtBstr(curdev->dl_Name));
      printf("%s        ",tmpstr);
      if (strlen(tmpstr) < 8) printf("  ");
      fl = (struct FileLock *)(BADDR(curdev->dl_Lock));
      xl = (struct DeviceList *)(BADDR(fl->fl_Volume));
      if (xl->dl_Type == DLT_DEVICE) {
        dn = (struct DeviceNode *) xl;
        printf("%s:",CvtBstr(dn->dn_Name));
      }
      else {
        if (xl->dl_Task == NULL) /* Not Mounted if Task == NULL */
          printf("Volume: %s",CvtBstr(xl->dl_Name));
        else { /* This code tracks down the full path */
          printf("%s:",CvtBstr(xl->dl_Name));
          pathstr[0] = '\0';  /* initialize it to the null string */
          thislock = curdev->dl_Lock;
          plock = ParentDir(thislock);
          while (plock != 0) {
            if (Examine(thislock,fi)) {
              if (strlen(pathstr) == 0) strcpy(pathstr,fi->fib_FileName);
              else {
                strins(pathstr,"/");          /* insert directory separator */
                strins(pathstr,fi->fib_FileName); /* insert parent dir name */
              }
            }
            else printf("\nBad Lock!\n"); /* This should never print! */
            thislock = plock;
            plock = ParentDir(thislock);
          } /* while we haven't got to the top */
          printf("%s",pathstr);
        }
      } /* else it was a directory rather than a device */
      printf("\n");
    } /* If it was a directory redirection at all */
    curdev = (struct DeviceList *)BADDR(curdev -> dl_Next);
  }
  printf("\n");

  /* Pass 3 : Print out all of the devices, like the original we pretty much
   *          assume device names are three characters long. (They can be more 
   *          though.)
   */
  printf("Devices:");
  i = 0;
  curdev = dl;
  while (curdev != NULL) {
    if (curdev->dl_Type == DLT_DEVICE) {
      if ((i%5) == 0) printf("\n"); /* Every 5 devices print a newline */
      i++;
      dn = (struct DeviceNode *) curdev;
      printf("%s  ",CvtBstr(dn->dn_Name));
    }
    curdev = (struct DeviceList *)BADDR(curdev -> dl_Next);
  } /* While */
  printf("\n");
  MyExit(RETURN_OK); /* Exit with a status of zero */
}
