[comp.sys.amiga] Info command replacement

cmcmanis@sun.uucp (Chuck McManis) (05/18/87)

The enclosed program is a C version of the AmigaDOS info command. I wrote it
as part of a program sponsered by Charlie Heath on BIX who has set
out to replace all of the AmigaDOS commands on the Workbench disk with
versions written in C and assembler. The purpose being that C commands
do not require the funky BCPL setup and can therefore be used in 
Execute calls and Lattice fork() calls. You also have the advantage of
getting source to the AmigaDOS commands. 
  This program was written to minimize the size of the executable, and
to do that I have eliminated nearly all of the references to the Lattice
library lc.lib, what remains are the calls for 32 bit multiply and divide.
I used Carolyn's TWStartup.asm file for the startup code, thus eliminating
any reference to the Lattice stdio package however that means compiling
with the -v option (disable stack checking). For compactness I also compiled
with the -r option which causes the use of PC relative branching rather than
long branching. Blink switches include NODEBUG, SMALLCODE and SMALLDATA. 
see the .lnk command file outline in the main comment. 
  Also I took some artistic license and switched the output of the Volumes
available and the Disk statistics. Since I am usually more interested
in how much space a given diskette has rather that the fact that it is
mounted or not. To make it *really* info compatible you will have to
switch the Pass 1 and Pass 2 code.

Watch out for the signature at the end of the message...
--Chuck 
-------------------------------------cut here--------------------------
/* 
 * 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 */
}
-----------------------also cut here---------------------------
-- 
--Chuck McManis
uucp: {anywhere}!sun!cmcmanis   BIX: cmcmanis  ARPAnet: cmcmanis@sun.com
These opinions are my own and no one elses. But you knew that, didn't you.