/*

    fsi - a comprehensive File System Information utility for the Amiga

    Usage: fsi [-x] [device|volume|assign|file|directory name] [...]
           Include -x if you want extended device information

*/


#include <stdio.h>
#include <string.h>
#include <dos.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>
#include <exec/tasks.h>
#include <exec/nodes.h>
#include <exec/ports.h>
#include <exec/memory.h>
#include <proto/exec.h>
#include <proto/dos.h>


#define toAPTR(b) (void *)((LONG)(b)<<2)
#define toBPTR(a) (BPTR)((LONG)(a)>>2)
#define ID_OFS_DISK 0x444F5300
#define ID_FFS_DISK 0x444F5301


byte extended = 0;  /* Captain! I see a flag in the distance! */


char *c;
char name[FMSIZE+1];
APTR old_pr_WindowPtr;


BPTR lock                 = NULL;
struct Process *process   = NULL;
struct InfoData *info     = NULL;
struct FileInfoBlock *fib = NULL;


void clean_exit(int xcode)
{
  if (lock) UnLock(lock);
  if (info) FreeMem(info,sizeof(struct InfoData));
  if (fib) FreeMem(fib,sizeof(struct FileInfoBlock));
  if (process)
    {
      Forbid();
      process->pr_WindowPtr = old_pr_WindowPtr;
      Permit();
    }
  printf("\n");
  exit(xcode);
}


int strsam(char *str1, char *str2)
{
  while( (*str1) && (*str2) )
    {
      if ( toupper(*str1) != toupper(*str2) ) return(0);
      str1++;
      str2++;
    }
  if ( (!*str1) && (!*str2) ) return(1);
  return(0);
}


int bstrsam(char *str1, BSTR bstr)
{
  register char *str2 = toAPTR(bstr);
  if (strlen(str1) != (int)*str2) return(0);
  str2++;

  while(*str1)
    {
      if ( toupper(*str1) != toupper(*str2) ) return(0);
      str1++;
      str2++;
    }
  return(1);
}


void bstrcpy(char *str, BSTR bstr)
{
  register char *c = toAPTR(bstr);
  register int len = (int)*c;
  stccpy(str, ++c, ++len);
}


void get_bcplname(char *str, BSTR bstr)
{
  register char *c = toAPTR(bstr);
  if ( (!c) || (!*c) )
    {
      strcpy(str,"›3munknown›0m");
    }
  else
    {
      bstrcpy(str,bstr);
    }
}


struct DeviceList *DevList(void)
{
  register struct DosInfo *dos;
  register struct RootNode *root;
  root = (struct RootNode *)DOSBase->dl_Root;
  dos = toAPTR(root->rn_Info);
  return(toAPTR(dos->di_DevInfo));
}


/*
**  this function will search for a node which has the specified task 
**  pointer and type -- although one trackdisk.device task can handle
**  two or more drives, each FileSystem handler can only handle one drive.
**  therefore, each drive in your system (DF0:,DF1:,DF2:,DF3:,etc.) gets
**  its own FileSystem task.  to put it another way, each DLT_DEVICE has a
**  unique dn_Task field.  When a volume is mounted in a drive, the
**  volume's DLT_VOLUME node contains a pointer in the dn_Task field which
**  identifies the handler task for the drive that the volume is mounted
**  on.  you can search the device list for that drive/device if you wish.
**  likewise, to find what volume is mounted on a particular device,
**  search the list for a DLT_VOLUME that has the same dn_Task pointer.
**  this function is meant to be used in both situations.  note that if a
**  DLT_VOLUME node has a NULL dn_Task field that means that the volume is
**  NOT currently mounted in any drive.  likewise, if you have a device
**  node but fail to find a volume node with the same dn_Task, there is no
**  volume currently mounted in that device.
*/
struct DeviceNode *find_obj(struct MsgPort *task, ULONG type)
{
  register struct DeviceNode *dn;
  if (!task) return(NULL);
  Forbid();
  for(dn = (struct DeviceNode *)DevList(); dn; dn = toAPTR(dn->dn_Next))
    {
      if ( (dn->dn_Type == type) && (dn->dn_Task == task) ) break;
    }
  Permit();
  return(dn);
}


/*
**  this function will search the device list for a node with the given name
*/
struct DeviceNode *lookup_name(char *name)
{
  register struct DeviceNode *dn;
  register char *colon;

  if ( (!name) || (!*name) ) return(NULL);

  for(colon = name; *colon; colon++);
  colon--;
  if (*colon == ':') *colon = '\0'; /* it should... */

  Forbid();

  for(dn = (struct DeviceNode *)DevList(); dn; dn = toAPTR(dn->dn_Next))
    {
      if (bstrsam(name,dn->dn_Name)) break;
    }

  Permit();

  if (*colon == '\0') *colon = ':';

  return(dn);
}


void show_datestamp(struct DateStamp *ds)
{
  char time_date[26];
  ULONG secs;

/* calculate time & date in seconds */
  secs=(
        (ds->ds_Days*86400L)            /* days since 1978 * secs per day  */
        +(ds->ds_Minute*60)             /* minutes since midnite * 60 secs */
        +(ds->ds_Tick/TICKS_PER_SECOND) /* seconds since minute            */
        +252460800L /* number of seconds between 1970 &1978 (2 leap years) */
        +21600L     /* correction factor of six hours (for DST)...         */
                    /* obviously this will not work properly everywhere    */
                    /* (can you say K-L-U-D-G-E ??)                        */
       );
  strcpy(time_date,(char *)ctime(&secs));
  time_date[24]='\0';

  printf("Last modified: %s\n",time_date);
}


void show_protection(ULONG p)
{
  char protection[9];

/* build protection string */
  protection[8]='\0';
  protection[7]=(p&1L) ?'-':'d';
  protection[6]=(p&2L) ?'-':'e';
  protection[5]=(p&4L) ?'-':'w';
  protection[4]=(p&8L) ?'-':'r';
  protection[3]=(p&16L)?'a':'-';
  protection[2]=(p&32L)?'p':'-';
  protection[1]=(p&64L)?'s':'-';
  protection[0]='-';

  printf("Protection bits: %s\n",protection);
}


void extended_devinfo(struct DeviceNode *dev)
{
  struct FileSysStartupMsg *startup;
  struct DosEnvec *env;
  struct Task *task;
  char temp[FMSIZE+1];

  if (!extended) return;  /* user hasn't requested this stuff */

  if (!dev)
    {
      printf("\n›3mNo extended device information is available›0m\n");
      return;
    }

  printf("\n›3mExtended device information:›0m\n");
  printf("(note: the following information is not consistently\n");
  printf("    used by all AmigaDOS devices, and therefore may be\n");
  printf("    partially or wholly invalid for this particular device)\n\n");

  get_bcplname(temp,dev->dn_Handler);
  printf("FS handler:\t%s\n",temp);

  if (dev->dn_Task)
    {
      task = dev->dn_Task->mp_SigTask;
      if (task)
        {
          c = task->tc_Node.ln_Name;
          if ( (c) && (*c) )
            {
              strcpy(temp,c);
            }
          else
            {
              goto notaskname;
            }
        }
      else
        {
notaskname:
          strcpy(temp,"›3munknown›0m");
        }
      printf("Task name:\t%s\n",temp);
    }

  startup = toAPTR(dev->dn_Startup);
  if (startup)
    {
      get_bcplname(temp,startup->fssm_Device);
      printf("Device name:\t%s\n",temp);

      printf("Unit number:\t%lu\n",startup->fssm_Unit);

      env = toAPTR(startup->fssm_Environ);
      if (env)
        {
          switch(env->de_DosType)
            {
              case ID_OFS_DISK:
                {
                  strcpy(temp,"Old file system");
                  break;
                }
              case ID_FFS_DISK:
                {
                  strcpy(temp,"Fast file system");
                  break;
                }
              default:
                {
                  sprintf(temp,"›3munknown›0m (0x%08X)",env->de_DosType);
                  break;
                }
            }
          printf("File System:\t%s\n",temp);

          printf("Bytes/Block:\t%lu\n",(env->de_SizeBlock * sizeof(long)));
          printf("Surfaces:\t%lu\n",env->de_Surfaces);
          printf("Blocks/Track:\t%lu\n",env->de_BlocksPerTrack);
          printf("Low Cylinder:\t%lu\n",env->de_LowCyl);
          printf("High Cylinder:\t%lu\n",env->de_HighCyl);
          printf("Reserved:\t%lu\n",env->de_Reserved);
          printf("Interleave:\t%lu\n",env->de_Interleave);
          printf("Buffers:\t%lu\n",env->de_NumBuffers);
          printf("Boot Priority:\t%ld\n",env->de_BootPri);
        }
    }
}


void extra_volinfo(char *volname)
{
  BPTR vlock;
  int rc;
  LONG blocks, bytes;
  char temp[FMSIZE+1];

  vlock = Lock(name,ACCESS_READ);
  if (!vlock) return;
  rc = Info(vlock,info);
  UnLock(vlock);
  if (rc == DOSFALSE) return;

/* disk type */
  switch(info->id_DiskType)
    {
      case ID_UNREADABLE_DISK:
        {
          strcpy(temp,"Unreadable");
          break;
        }
      case ID_DOS_DISK:
        {
          strcpy(temp,"Normal DOS disk");
          break;
        }
      case ID_NOT_REALLY_DOS:
        {
          strcpy(temp,"Not DOS");
          break;
        }
      case ID_KICKSTART_DISK:
        {
          strcpy(temp,"Kickstart");
          break;
        }
      default:
        {
          strcpy(temp,"›3munknown›0m");
          break;
        }
    }
  printf("Disk type:\t%s\n",temp);

/* disk status */
  switch(info->id_DiskState)
    {
      case ID_WRITE_PROTECTED:
        {
          strcpy(temp,"Write protected (read only)");
          break;
        }
      case ID_VALIDATING:
        {
          strcpy(temp,"Not validated (currently unusable)");
          break;
        }
      case ID_VALIDATED:
        {
          strcpy(temp,"Read/Write (validated)");
          break;
        }
      default:
        {
          strcpy(temp,"›3munknown›0m");
          break;
        }
    }
  printf("Status:\t\t%s",temp);
  if (info->id_InUse == DOSTRUE) printf(" (disk is busy)");
  printf("\n");

/* disk capacity info */
  printf("Bytes/Block:\t%ld\n",info->id_BytesPerBlock);
  printf("Soft errors:\t%ld\n",info->id_NumSoftErrors);

  blocks = info->id_NumBlocks;
  bytes = (blocks * info->id_BytesPerBlock);
  printf("Capacity:\t%ld blocks;\t%ld bytes\n",blocks,bytes);

  blocks = info->id_NumBlocksUsed;
  bytes = (blocks * info->id_BytesPerBlock);
  printf("Space used:\t%ld blocks;\t%ld bytes\n",blocks,bytes);

  blocks = (info->id_NumBlocks - blocks);
  bytes = (blocks * info->id_BytesPerBlock);
  printf("Space free:\t%ld blocks;\t%ld bytes\n",blocks,bytes);
}


void show_volinfo(char *name, struct DeviceList *vol)
{
  register struct DeviceNode *dev;
  char temp[FMSIZE+1];

  printf("Volume \"%s\" is ",name);
  dev = find_obj(vol->dl_Task,DLT_DEVICE);
  if (!dev)
    {
      printf("not mounted\n");
      printf("Additional information is unavailable\n");
      return;
    }

  bstrcpy(temp,dev->dn_Name);
  strcat(temp,":");
  printf("mounted on device \"%s\"\n",temp);

  extra_volinfo(name);

  extended_devinfo(dev);
}


void show_fileinfo(char *name, BPTR lock, struct FileInfoBlock *fib)
{
  char temp[FMSIZE+1];

  getpath(lock,temp);
  if (!strchr(temp,':')) strcat(temp,":");
  printf("Full path name: \"%s\"\n",temp);

  printf("File size: %lu bytes\n",fib->fib_Size);

  show_datestamp(&fib->fib_Date);

  show_protection(fib->fib_Protection);

  if (fib->fib_Comment[0])
    {
      printf("Comment: %s\n",fib->fib_Comment);
    }

  printf("\n");
  c = strchr(temp,':');
  *++c = '\0';
  show_volinfo(temp,(struct DeviceList *)lookup_name(temp));
}


void show_dirinfo(char *name, BPTR lock, struct FileInfoBlock *fib)
{
  char temp[FMSIZE+1];

  getpath(lock,temp);
  if (!strchr(temp,':')) strcat(temp,":");
  for(c = temp; *c; c++);
  c--;
  if ( (*c != ':') && (*c != '/') ) strcat(temp,"/");
  printf("Full path name: \"%s\"\n",temp);

  show_datestamp(&fib->fib_Date);

  if (fib->fib_DirEntryType > 0) show_protection(fib->fib_Protection);

  printf("\n");
  c = strchr(temp,':');
  *++c = '\0';
  show_volinfo(temp,(struct DeviceList *)lookup_name(temp));
}


void show_devinfo(char *name, struct DeviceNode *dev)
{
  struct DeviceList *dl;
  char temp[FMSIZE+1];

  if (!dev->dn_Task)
    {
      printf("non-filing system device\n");
      get_bcplname(temp,dev->dn_Handler);
      printf("FS handler:\t%s\n",temp);
      return;
    }
  printf("filing system device\n");

  dl = (struct DeviceList *)find_obj(dev->dn_Task,DLT_VOLUME);
  if (!dl)
    {
      printf("There is no volume presently mounted in this device\n");
    }
  else
    {
      bstrcpy(temp,dl->dl_Name);
      strcat(temp,":");
      printf("\nVolume \"%s\" is currently mounted in this device\n",temp);
      extra_volinfo(temp);
    }

  extended_devinfo(dev);
}


void show_assigninfo(char *name, struct DeviceList *dev)
{
  BPTR alock;
  struct FileLock *fl;
  struct DeviceList *dl;
  char volname[FMSIZE+1];

/* get volume name */
  alock = dev->dl_Lock;
  if (!alock) return;
  fl = toAPTR(alock);
  dl = toAPTR(fl->fl_Volume);
  bstrcpy(volname,dl->dl_Name);

/* check if volume is mounted */
  dl = (struct DeviceList *)find_obj(fl->fl_Task,DLT_VOLUME);

/* no, just show name of volume */
  if (!dl)
    {
      printf("It identifies a directory or file on volume: \"%s:\"\n",volname);
      printf("The volume is not mounted\n");
      printf("Additional information is unavailable\n");
      return;
    }

/* yes, get all info for this object */
  Examine(alock,fib);
  if (fib->fib_DirEntryType > 0)  /* a directory */
    {
      if (strsam(fib->fib_FileName,volname))
        {
          printf("It identifies the root directory of volume: \"%s\"\n",fib->fib_FileName);
          fib->fib_DirEntryType = 0L; /* prevents show_dirinfo() from
                                      ** attempting to display protection
                                      ** bits for this volume name */
        }
      else
        {
          printf("It identifies a directory: \"%s\"\n",fib->fib_FileName);
        }
      show_dirinfo(fib->fib_FileName,alock,fib);
    }
  else
    {
      printf("It identifies a file: \"%s\"\n",fib->fib_FileName);
      show_fileinfo(fib->fib_FileName,alock,fib);
    }
}


void handle_switch(char *arg)
{
  switch(toupper(arg[1]))
    {
      case 'X': /* extended info on */
        {
          extended = 1;
          break;
        }
      default:
        {
          printf("\nUnrecognized switch: \"%s\"\n",arg);
          break;
        }
    }
}


void add_colon(char *name)
{
  printf("\nYou forgot to postfix a colon to this name.\n");
  printf("Remember that AmigaDOS will not recognize it\n");
  printf("without a colon attached.\n");
  strcat(name,":");
}


main(int argc, char *argv[])
{
  int arg;
  register struct DeviceNode *dn;

  if (argc == 0) exit(0); /* We're men here, we don't do Workbench! */

  printf("\n›3;33;40mFile System Info v1.0 ›0m- created by Ray Lambert\n");

  if (argc == 1)
    {
      printf("\nCLI usage: fsi [-x] <name>\n");
      printf("    where <name> is the name of any filing system object\n");
      printf("    and -x is an optional switch to request extended\n");
      printf("    device information\n\n");
      exit(0);
    }


/* get memory for a struct InfoData */
  info = AllocMem(sizeof(struct InfoData),MEMF_PUBLIC|MEMF_CLEAR);
  if (!info)
    {
nomem:
      printf("\nSorry, not enough memory\n\n");
      clean_exit(20);
    }


/* get memory for a struct FileInfoBlock */
  fib = AllocMem(sizeof(struct FileInfoBlock),MEMF_PUBLIC|MEMF_CLEAR);
  if (!fib) goto nomem;


/* disable DOS requesters */
  Forbid();
  process = (struct Process *)FindTask(NULL);
  old_pr_WindowPtr = process->pr_WindowPtr;
  process->pr_WindowPtr = (void *)-1L;
  Permit();


/* look at all args */
  for(arg = 1; arg < argc; arg++)
    {

      if (argv[arg][0] == '-')  /* a switch */
        {
          handle_switch(argv[arg]);
          continue;
        }

      strcpy(name,argv[arg]);

    /* is this a device/volume/assign name? */
      c = strchr(name,':');
      if (c) c++;
      if ( (!c) || (!*c) )
      /* apparently not, but it may still be... */
        {
          dn = lookup_name(name);
          if (!dn) goto tryalock; /* not in the device list, trying getting a lock */
          if ( (!c) || (*c) ) add_colon(name);
          switch(dn->dn_Type)
            {
              case DLT_DEVICE:
                {
                  printf("\n\"%s\" is a ",name);
                  show_devinfo(name,dn);
                  break;
                }
              case DLT_VOLUME:
                {
                  printf("\n\"%s\" is a volume name\n",name);
                  show_volinfo(name,(struct DeviceList *)dn);
                  break;
                }
              case DLT_DIRECTORY:
                {
                  printf("\n\"%s\" is a logical name\n",name);
                  show_assigninfo(name,(struct DeviceList *)dn);
                  break;
                }
              default:
                {
                  goto noinfo;
                }
            }
        }
      else
    /* it is (presumably) a file or directory */
        {
tryalock:
          lock = Lock(name,ACCESS_READ);

          if (!lock)
            {
noinfo:
              if (strsam("NIL",name)) add_colon(name);
              if (strsam("NIL:",name))
                {
                  printf("\n\"NIL:\" is a 'fake' filing system device which\n");
                  printf("is normally used to suppress unwanted output\n");
                  printf("from programs which write to the CLI...\n");
                  printf("Additional information is unavailable\n");
                  continue;
                }
              printf("\nSorry, can't get any information for \"%s\"\n",argv[arg]);
              continue;
            }

          Examine(lock,fib);
          if (fib->fib_DirEntryType > 0)  /* a directory */
            {
              printf("\n\"%s\" is a directory name\n",name);
              show_dirinfo(name,lock,fib);
            }
          else
            {
              printf("\n\"%s\" is a file name\n",name);
              show_fileinfo(name,lock,fib);
            }
          UnLock(lock);
          lock = NULL;
        }
    }

  clean_exit(0);
}
