/* 
 * Info Command - C Language Equivalent. 
 *
 *     This command looks and feels like the original AmigaDOS Info command
 *  except that it is written in C and thus available for "forking" with the
 *  lattice 3.10 compiler. 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.
 *
 *  Permission is granted to distribute this package as part of the AmigaDOS
 *  Replacement Project (ARP) or as part of the Fish Library.
 *  
 *  Compiling and Linking Information - This program was coded to minimize
 *  the resulting executable size. To that end 99% of all references to 
 *  Lattice's library were removed, what remains are references to _CXD33
 *  and _CXM33 (some math routines) so you still need lc.lib but it is a
 *  lot smaller than it normally would be. And Carolyn Scheppner's startup
 *  code 'TWstartup.asm' was used rather than Lattice's c.o (I assembled 
 *  TWStartup.asm into ac.o.) 
 *
 *  The compile command I used to compile the source was :
 *  LC -r -v info
 *
 *  and the Blink command file (info.lnk) was set up as follows :
 *  FROM ac.o+info.o
 *  TO info
 *  LIB LIB:amiga.lib+LIB:lc.lib
 *  SMALLCODE
 *  SMALLDATA
 *  NODEBUG
 *  MAP info.map
 *
 *  The blink command to link this file is 
 *  Blink with info.lnk 
 *
 *  After you are done you should end up with an executable that is about
 *  4336 bytes long. Which compares favorably to the 1708 bytes of the 
 *  assem/BCPL version. The advantage to having a C version are two fold.
 *  First, you can use the Lattice fork() call or the AmigaDOS Execute()
 *  call to run this version from your program, and second you get to 
 *  see the source to the info command. Something Commodore wouldn't let
 *  you do without paying big bucks. So here it is 'info' the C version.
 */

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

/* Note there is a bug in the Commodore supplied 'info' command. The
 * way it calculates the size of the disk is with the following formula
 *
 * size = ((NumberofBlocks + 2) * 512) / 1024
 *
 * The actual size is :
 *
 * size = (NumberofBlocks * NumBytesPerBlock)/ 1024;
 *
 * define INFO_COMPATIBLE to get it their way, leave it undefined to get the
 * correct way.
 */

#define INFO_COMPATIBLE

extern struct DosLibrary *DOSBase;

#ifdef DEBUG
  struct RootNode 	*rn;
  struct DeviceList 	*dl, *t, *t2;
  struct DeviceNode 	*dn;
  struct DosInfo	*di;
#endif
char	*TWspec = "";

/*
 * Function cvtnum(num,str)
 *   This function converts an integer to a string.  It returns the length of
 * the string created. Change BASE for different bases, make it a variable for
 * dynamic base calculations.
 */
#define BASE 10 

int
cvtnum(str,num)

char	*str;
long	num;

{
  short int   i,j,ndx;
  long   temp;
  char  digit;

  ndx = 0;
  if (num < 0) { *(str+ndx++) = '-'; num = - (num); }
  if (num == 0) {
    *(str) = '0';
    *(str+1) = '\0';
    return(1);
  }
  i = 0;
  temp = 1;
  while (temp <= num) {
    temp *= BASE;
    i++;
  }
  temp /= BASE;
  for (j=0; j<i; j++) {
    digit = ((num/temp) < 10) ? '0'+ (num/temp) : 'A' + ((num/temp)-10);
    *(str+ndx++) = digit;
    num %= temp;
    temp /= BASE;
  }
  *(str+ndx) = '\0';
  return((int)ndx);
}
/* function strlen(str) - calculate the length of a string
 */
int strlen(str)

char *str;
{
  int i;
  for (i=0; *(str+i) != 0; i++) ;
  return(i);
}

/*
 * Function - Pad (buf, size)
 * This function will pad a string (usually a number) to be right justifed in
 * 'size' spaces. 
 */
void
Pad(str,size)

char	*str;
int	size;

{
  short i,j;

  j = strlen(str);
  if (j >= size) return;
  for (i=size; i >= 0; i--) 
    *(str+i) = (j < 0) ? ' ' : *(str+j--);
}    
  
#define MyExit(cc)	Exit(cc)
/* These are some macros that help in dealing with BCPL pointers and strings
 * the first is macro converts a BPTR to a C pointer of type struct DeviceList *
 * The second two provide the length of a BSTR * and a pointer to it's text.
 */

#define DLPTR(x)	((struct DeviceList *)BADDR(x))
#define LENGTH(x)	(*(UBYTE *)BADDR(x))
#define STRING(x)	(((char *)BADDR(x))+1)

/* 
 * Function - FindDevice(ListPtr,DeviceType)
 * 
 * This function will find a device of the specified type in the DeviceInfo
 * list and return a pointer to it. If the device List pointer you pass it
 * points to a struct of type DeviceType it will simply return that device
 * pointer. If you pass it NULL it will return NULL, and if it fails to
 * find a matching entry it returns NULL.
 *
 * Note to find the next device in the list you must update the pointer you
 * pass to the next device in the list. See the code below for some examples.
 */

struct DeviceList *
FindDevice(dlp,dlt)

struct DeviceList *dlp;		/* Pointer to a device list structure */
long		  dlt;		/* A device type as defined in dos.h  */

{
  struct DeviceList	*t; /* A temporary pointer */

  for (t = dlp; ((t != NULL) && (t->dl_Type != dlt)); t = DLPTR(t->dl_Next));
  return(t);
}

/* This macro writes out data to the screen bypassing C's printf statement */

#define PutS(str)	Write(Output(),str,strlen(str))


/*
 * Main code, This is where the code actually implements the Info command.
 * it is pretty simple really, first we build a list of all the disk devices
 * and do an 'Info' on each one, then list out all of the volumes.
 */

void main()

/* NOARGS */

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

#ifndef DEBUG
  struct RootNode 	*rn;
  struct DeviceList 	*dl, *t, *t2;
  struct DeviceNode 	*dn;
  struct DosInfo	*di;
#endif
  struct InfoData	info;
  struct Process	*myp;
  APTR			oldwinptr;
  BPTR			l;
  char			buf[80];
  int			i,a,u,size;


  PutS("Info 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);  

  /* 
   * Ok, now we list out all of the disk devices ...
   * 
   * Pass 1: Print out all of the known volumes, if they have a handler
   *	     task present then they are mounted in a physical device
   */
  PutS("Volumes Available:\n");
  for (t = FindDevice(DLPTR(di->di_DevInfo),DLT_VOLUME); t != NULL;
       t = FindDevice(DLPTR(t->dl_Next),DLT_VOLUME)) {
    Write(Output(),STRING(t->dl_Name),LENGTH(t->dl_Name));
    if (t->dl_Task != NULL) PutS(" [Mounted]");
    PutS("\n");
  }
  PutS("\n");

  /* Pass 2 : Print out all of the disk devices, like the original we 
   * pretty much assume device names are three characters long. 
   * (They can be more though.)
   */
  /* Disable requesters if no disk present */
  myp = (struct Process *) FindTask(NULL);
  oldwinptr = myp->pr_WindowPtr;
  myp->pr_WindowPtr = (APTR) -1;

  PutS("Mounted Disks:\n");
  PutS("Unit Size    Used    Free Full Errs   Status   Name\n");
  i = 0;
  for (t = FindDevice(DLPTR(di->di_DevInfo),DLT_DEVICE); t != NULL;
       t = FindDevice(DLPTR(t->dl_Next),DLT_DEVICE)) {
    dn = (struct DeviceNode *) t;
    /* This is supposed to distinguish a disk device (a task pointer) */
    if (dn->dn_Task) { 
      Write(Output(),STRING(dn->dn_Name), LENGTH(dn->dn_Name));
      PutS(": ");
      for (i=0; i<LENGTH(dn->dn_Name); i++) buf[i] = *(STRING(dn->dn_Name)+i);
      buf[i++] = ':'; buf[i] = '\0';
      l = Lock(buf,ACCESS_READ);
      if (l == NULL) PutS("No disk present\n");
      else {
        Info(l,&info);
        /* Calculate the size in K bytes (add 2 to blocks for) 'reserved' 
         * blocks which is a *bug* in the original info but what the heck.
         */
        a = info.id_NumBlocks;
#ifdef INFO_COMPATIBLE
        size = ((a+2) * 512) >> 10;
#else
        size = (a * info.id_BytesPerBlock ) >> 10;
#endif
        i = cvtnum(buf,size);
	/* The following case statement formats the number properly */
        buf[3] = 'K'; /* defaults to K bytes */
	switch (i) {
	  case 1 : buf[2] = buf[0]; 
                   buf[1] = ' '; /* fill in with spaces */
                   buf[0] = ' '; /* fill in with spaces */
		   break;
          case 5 : buf[3] = 'M';
	  case 2 : buf[2] = buf[1];
                   buf[1] = buf[0];
                   buf[0] = ' ';
		   break;
          case 6 : buf[3] = 'M';
	  case 3 : break;
	  case 4 :
	  case 7 : buf[3] = (i == 4) ? 'M' : 'G';
	  	   buf[2] = buf[1];
           	   buf[1] = '.';
		   break;
	  default: buf[0] = 'H'; /* Bigger than 10 Gigabytes */
		   buf[1] = 'U';
		   buf[2] = 'G';
		   buf[3] = 'E';
		   break;
        } /* end switch */
        buf[4] = ' '; buf[5] = '\0';
        PutS(buf);
        u = info.id_NumBlocksUsed;
	/* Now build the stats line without sprintf */
        cvtnum(buf,u);
        Pad(buf,7);
        *(buf+7) = ' ';
	cvtnum(buf+8,a-u);
	Pad(buf+8,7);
	*(buf+15) = ' ';
	cvtnum(buf+16,((a-(a-u))*100)/a);
	Pad(buf+16,3);
	*(buf+19) = '%';
	*(buf+20) = ' ';
	cvtnum(buf+21,info.id_NumSoftErrors);
	Pad(buf+21,3);	
        PutS(buf);
        switch (info.id_DiskState) {
          case ID_WRITE_PROTECTED : PutS("  Read Only  ");
  			            break;
          case ID_VALIDATING      : PutS("  Validating ");
  				    break;
          case ID_VALIDATED       : PutS("  Read/Write ");
  				    break;
   	  default:		    PutS("  Strange    ");
  				    break;
          } /* state switch */
        t2 = DLPTR(info.id_VolumeNode);
        if (t2 != NULL) Write(Output(),STRING(t2->dl_Name), LENGTH(t2->dl_Name));
        PutS("\n");
        UnLock(l);
      } /* else had a disk in it */
    } /* If it was a disk device */
  } /* For loop */
  PutS("\n");
  myp->pr_WindowPtr = oldwinptr;
  MyExit(RETURN_OK); /* Exit with a status of zero */
}


