/*
 * LockMon:     determine the number of locks associated with a filesystem
 *              task. I haven't figured out how to do this directly, so I
 *              cheat -- if a volume is offlined, then a linked list of locks
 *              is transferred to the volume's DeviceList entry. Note that
 *              this isn't a permanent solution -- it won't work with hard
 *              disks or RAM:, for instance.
 *
 * Warnings:    Uses Aztec C library internal DOSBase, which holds the
 *              DOS library base pointer. Lattice does the same.
 *              And ... this implementation is guesswork. Caveat emptor!
 *
 * Request:     Anybody know a better way?
 *
 * Perpetrator: Dewi Williams ..!ihnp4!druca!dewi.
 *              Extremely public domain.
 * 
 * Version:     1.0
 */

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

/* Defines */

/* Change typeless BCPL BPTR to typed C (for struct pointers). Don't
 * use this define on an APTR, that's only a badly disguised void *.
 */
#define BPTR_TO_C(strtag, var)  ((struct strtag *)(BADDR( (ULONG) var)))

/* And do the reverse */
#define C_TO_BPTR(var)          (((ULONG)var)>>2)

/* Externs */
extern struct DosLibrary *DOSBase;      /* dos library base pointer */
extern short             Enable_Abort;  /* controls ^C processing */

/* ARGSUSED */

main(argc, argv)
int     argc;
char    **argv;
{
        struct  RootNode        *rn;
        struct  DosInfo         *di;
        struct  DeviceList      *dl;
        struct  FileLock        *myFileLock;
        struct   Process        *myprocess;
        char                    buf[80];
        struct  FileInfoBlock   *fi;
        char                    *name;
        char                    *btocstr();
        void                    *malloc();
        void                    CountLocks();
        struct  Task            *FindTask();    

        Enable_Abort = 0;       /* Disable ^C processing */

        printf("Eject any volumes you wish to profile.\n");
        printf("You will be requested to re-insert them as needed.\n");
        printf("When ready, press RETURN to continue or 'q' to abort.\n");
        (void)gets(buf);        /* Well, q and RETURN really... */

        if (buf[0] == 'q') {    /* Last chance to change your mind! */
                printf("Aborted...\n");
                exit(0);
        }

        /* Wait for things to settle down (just in case) */
        (void)Delay(5 * TICKS_PER_SECOND);

        /* The FileInfoBlock with guaranteed alignment. */      
        if ((fi = (struct FileInfoBlock *)malloc(sizeof(*fi) )) == NULL) {
                printf("Memory allocation failure");
                exit(1);
        }

        /* Note that since lockmon itself has a lock on the current
         * directory, this has to be filtered out as an artefact.
         * We can get its value out of the CLI stuff. Our task control
         * block is the first element of the process control block,
         * hence the following casting. This code fragment courtesy of
         * the kind person at C-A who answered my query about finding
         * the current directory on RAM:
         */
        myprocess = (struct Process *)FindTask(NULL);

        myFileLock = BPTR_TO_C(FileLock, myprocess->pr_CurrentDir);

        /* Any system manipulation of AmigaDOS linked lists while we're
         * traversing could quite easily crash us, and the system. So
         * we righteously forbid it.
         */
        Forbid();               /* but is this really necessary? */

        /* Take the DOS library base pointer, deduce the DOS Root Node
         * address from that. Follow this to the Info substructure, which
         * gives us the start address of the DeviceList chain. Volumes are
         * DeviceList entries with a type of DLT_VOLUME. Note that these are
         * AmigaDOS linked lists *NOT* Exec ones, so all the stuff about List,
         * Node etc. doesn't apply. One box, 2 operating systems!
         */
        rn = (struct RootNode *)DOSBase->dl_Root;
        di = BPTR_TO_C(DosInfo, rn->rn_Info);
        dl = BPTR_TO_C(DeviceList, di->di_DevInfo);
        
        while (dl != NULL) {
                if (dl->dl_Type == DLT_VOLUME) {
                        if ((name = btocstr(dl->dl_Name)) == NULL) {
                                printf("Memory allocation failure\n");
                                break;
                        }
                        printf("Volume '%s': ", name);
                        if (dl->dl_LockList != NULL) {
                                CountLocks(BPTR_TO_C(FileLock,dl->dl_LockList),
                                           fi, myFileLock);
                        } else {
                                /* Either mounted or no locks... */
                                if (dl->dl_Task == NULL) 
                                        printf(": no locks\n");
                                else
                                        printf(": mounted\n");
                        }
                        free(name);                     
                }
                dl = BPTR_TO_C(DeviceList, dl->dl_Next);
        }
        
        Permit();               /* Multi-tasking now back on */
        free(fi);
        exit(0);
}

/*
 * Traverse the linked lists of locks, counting them and printing out their
 * corresponding filesystem names. 
 */

void
CountLocks(list, fi, mylock)
struct  FileLock        *list;          /* the linked list to run through */
struct  FileInfoBlock   *fi;            /* area for Examine */
struct  FileLock        *mylock;        /* this one's ours */
{
        register struct FileLock *lptr;
        register int             count = 0;
        short                    Examine();     
        
        for(lptr=list;lptr != NULL; lptr= BPTR_TO_C(FileLock,lptr->fl_Link)){
                if (lptr == mylock) continue;
                
                if (Examine(C_TO_BPTR(lptr), fi) == FALSE) {
                        /* Clicked Cancel on system requester ? */
                        printf("\n\tCannot deduce filename for lock");
                } else {
                        printf("\n\t%s", fi->fib_FileName);
                }
                count++;

        }
        if (count > 0)
                printf("\nTotal: %d lock(s)\n", count);
        else
                printf(": no locks\n");
}

/*
 * Convert a BCPL string to a C string. To avoid scrogging in-memory
 * stuff, it malloc's off a copy first.
 */

char *
btocstr(b)
ULONG   b;
{
        register char   *p, *s;
        void            *malloc();

        s = (char *)BADDR(b);   /* Shift & get length-prefixed str */

        if ((p = malloc(s[0])) != NULL) {
                (void)movmem(s +1, p, s[0]);    /* Aztec memcpy */
                p[s[0]] = '\0';
        }
        return p;
}
