/*
 *
 * HiLandR's new list command
 *
 */
/// defines
#define __USE_SYSBASE

#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <libraries/multiuser.h>
#include <utility/utility.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/multiuser.h>

#include <clib/alib_stdio_protos.h>

#include <dos.h>
#include <strings.h>
#include <stdlib.h>

#define DOSLIB "dos.library"
#ifndef DOSVER
#define DOSVER 39L    /*  we require AT LEAST release 3.0 of OS */
#else
#ifdef LIBPOOL
APTR __stdargs LibAllocPooled(APTR, unsigned long);
APTR __stdargs LibCreatePool(unsigned long, unsigned long, unsigned long);
void __stdargs LibDeletePool(APTR);
void __stdargs LibFreePooled(APTR, APTR, unsigned long);
#define AllocPooled LibAllocPooled
#define CreatePool LibCreatePool
#define DeletePool LibDeletePool
#define FreePooled LibFreePooled
#endif
#endif
///

/// prototypes
void _XCEXIT(long dummy);

LONG __regargs readmatchdir(STRPTR);
LONG __regargs printdir(struct FileInfoBlock **);
void __regargs readsoftlink(STRPTR, STRPTR, LONG);
void __regargs itok(ULONG, STRPTR);
UWORD __regargs max(UWORD, UWORD);
void __regargs dirinfo(ULONG, ULONG, ULONG, ULONG);
BOOL __regargs scanrecurse(STRPTR, LONG *, LONG *);

STRPTR Template = "Dir/M,ViewDir=VD/S,"
    "Icons=IC/S,Comments=CM/S,Sizes=SZ/S,Dates=DT/S,Secs=SC/S,Full=FL/S,NoLinks=NL/S,"
    "NoSort=NS/S,SortDate=SD/S,SortSize=SS/S,FirstDirs=FD/S,SortBack=SB/S";

#define OPT_DIR       0
#define OPT_VIEWDIRS  1

#define OPT_ICONS     2
#define OPT_COMMS     3
#define OPT_SIZES     4
#define OPT_DATES     5
#define OPT_SECS      6
#define OPT_FULL      7
#define OPT_NOLINKS   8

#define OPT_NOSORT    9
#define OPT_SORTDATE  10
#define OPT_SORTSIZE  11
#define OPT_FIRSTDIRS 12
#define OPT_SORTBACK  13

#define OPT_COUNT     14

struct ExecBase *SysBase;
struct DosLibrary *DOSBase = NULL;
struct Library *UtilityBase = NULL;
struct muBase *muBase = NULL;

LONG opts[OPT_COUNT];

UBYTE Verstag[ ] = "\0$VER: ls 1.01 (28.1.96) ©HiLandR, thanks to Thomas Zander!\n";

UBYTE buf[256];

struct muExtOwner *taskowner;
struct muUserInfo *userinfo = NULL;
struct muGroupInfo *groupinfo = NULL;
struct AnchorPath *ua;

ULONG total = 0, totalsize = 0, totalblocks = 0, totalfiles = 0, totaldirs = 0;

int (*cmp_funct)(struct FileInfoBlock *, struct FileInfoBlock *);

#ifndef DEBUG
#define DEBUG
#endif

#ifndef DEBUG
#define DFUNC(x)
#else
#define DFUNC(x) (x)
#endif
///

/// saveds
int __saveds cmd_ls(void)
    {
    struct RDArgs *rdargs;
    static STRPTR defaultname[ ] = {"", NULL};
    STRPTR *argptr, curarg;
    LONG err;

    SysBase = *((struct ExecBase **)4);
    if ((DOSBase = (struct DosLibrary *)OpenLibrary(DOSLIB, DOSVER)) &&
     (UtilityBase = OpenLibrary(UTILITYNAME, 0)))
        {
       muBase = (struct muBase *)OpenLibrary(MULTIUSERNAME, MULTIUSERVERSION);
       if (muBase)
            taskowner = muGetTaskExtOwner(NULL);
        if (rdargs = ReadArgs(Template, opts, NULL))
            {
            if (!muBase || ((userinfo = muAllocUserInfo()) &&
               (groupinfo = muAllocGroupInfo())))
                {
                if (opts[OPT_DIR])
                    argptr = (STRPTR *)opts[OPT_DIR];
                else
                    argptr = defaultname;
                if (ua = (struct AnchorPath *)AllocMem(sizeof(struct AnchorPath), MEMF_ANY))
                    {
                    err = 0;
                    while ((err == 0) && (curarg = *argptr++))
                        {
                        memset(ua, 0, sizeof(struct AnchorPath));
                        ua->ap_BreakBits = SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D;
                        err = readmatchdir(curarg);
                        }
                    if (err == ERROR_BREAK)
                        Printf("***Break\n");
                    else if (err)
                        PrintFault(err, "***ls");
                    else if (total > 1)
                        {
                        Printf("\nTotal: ");
                        dirinfo(totalsize, totalblocks, totalfiles, totaldirs);
                        }
                    FreeMem(ua, sizeof(struct AnchorPath));
                    }
                }
            if (muBase)
                {
                muFreeUserInfo(userinfo);
                muFreeGroupInfo(groupinfo);
                }
            FreeArgs(rdargs);
            }
        else
            PrintFault(IoErr(), NULL);
        if (muBase)
            muFreeExtOwner(taskowner);
        }
    else
        ((struct Process *)(SysBase->ThisTask))->pr_Result2 = ERROR_INVALID_RESIDENT_LIBRARY;
    CloseLibrary((struct Library *)muBase);
    CloseLibrary(UtilityBase);
    CloseLibrary((struct Library *)DOSBase);

    return 0;
    }
///


#define DIRLEN 64

/// readmatchdir
LONG __regargs readmatchdir(STRPTR name)
    {
    APTR pool = NULL;
    struct FileInfoBlock **fibs = NULL, **fibptr;
    LONG temprc, rc, dirpt = 0, dirlen = 0;
    BPTR lock, oldlock;

    MatchFirst(name, ua);
    if (temprc = IoErr())
        {
        if (temprc == ERROR_NO_MORE_ENTRIES)
            {
            Printf("%sNo match.\n", total ? "\n" : NULL);
            total++;
            return 0;
            }
        return temprc;
        }
    if (!(ua->ap_Flags & APF_ITSWILD) && (ua->ap_Info.fib_DirEntryType >= 0))
        {
        ua->ap_Flags |= APF_DODIR;
        MatchNext(ua);
        if (ua->ap_Flags & APF_DIDDIR)
            {
            if (NameFromLock(ua->ap_Last->an_Lock, buf, 256))
                Printf("%sDirectory %s is empty\n", total ? "\n" : NULL, buf);
            total++;
            temprc = ERROR_NO_MORE_ENTRIES;
            }
        }
    lock = DupLock(ua->ap_Last->an_Lock);
    while (temprc == 0)
        {
         /* re-allocate larger buffer is current one is full
            or if no buffer allocated yet */
        if (dirpt+2 >= dirlen)
            {
            if (((pool == NULL) &&
             ((pool = CreatePool(MEMF_ANY, 1000, 1000)) == NULL)) ||
             ((fibptr = (struct FileInfoBlock **)
             AllocPooled(pool, 4*(dirlen+DIRLEN))) == NULL))
                {
                temprc = ERROR_NO_FREE_STORE;
                break;
                }
            if (fibs)
                {
                CopyMemQuick((ULONG *)fibs, (ULONG *)fibptr, 4*dirlen);
                FreePooled(pool, fibs, 4*dirlen);
                }
            dirlen += DIRLEN;
            fibs = fibptr;
            fibptr = fibs+dirpt;
            }

         /* show entry */
        if ((*fibptr = AllocPooled(pool, sizeof(struct FileInfoBlock))) == NULL)
            {
            temprc = ERROR_NO_FREE_STORE;
            break;
            }
        CopyMemQuick(&ua->ap_Info, *fibptr++, sizeof(struct FileInfoBlock));
        dirpt++;

         /* read next entry */
        MatchNext(ua);
        temprc = IoErr();
        if (temprc && temprc != ERROR_NO_MORE_ENTRIES)
            {
            if(pool) DeletePool(pool),pool=NULL; 
                // if() added to prevend a crash when ^C is pressed 
                // while scanning the dir. 
                // added: 25-1-96 by Thomas Zander
            break;
            }
        if (ua->ap_Flags & APF_DIDDIR)
            temprc = ERROR_NO_MORE_ENTRIES;

         /* dir changed? */
        if (temprc == ERROR_NO_MORE_ENTRIES || ua->ap_Flags & APF_DirChanged)
            {
            *fibptr++ = NULL;

            oldlock = CurrentDir(lock);
            rc = printdir(fibs);
            CurrentDir(oldlock);

            if (!temprc)
                {
                UnLock(lock);
                lock = DupLock(ua->ap_Last->an_Lock);
                }

            DeletePool(pool);

            pool = fibs = NULL;

            dirpt = dirlen = 0;

            if (temprc || (temprc = rc))
                break;
            }
        }
    if (temprc == ERROR_NO_MORE_ENTRIES)
        {
         /* the dir was entry */
        }
    if (pool) DeletePool(pool);
        // if added to prevend a crash when ^C is pressed 
        // while scanning the dir. 
        // added: 25-1-96 by Thomas Zander
    UnLock(lock);
    MatchEnd(ua);

    if (temprc == ERROR_NO_MORE_ENTRIES)
        return 0;
    return temprc;
    }
///

/// printdir
#define ICON_SKIP 1
#define ICON_ON   2
#define ICON_OFF  0
#define fib_Icon  fib_Reserved[0]

/*
 *    U (»*TZ*)
 * dichspa rwedrwedrwed hilandr/dev 368K Yesterday 18:07 Text/ -> Work:Text/ :scripts & docs
 * ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯ ¯¯¯¯ ¯¯¯¯¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯¯¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
 *       bits            user group size    date   time  name      link          comment
 *
 */

#define FIELDS  20
#define BITSLEN 40
#define DATELEN 9
#define TIMELEN 8

LONG __regargs __saveds printdir(struct FileInfoBlock **fibs)
    {
    int cmpfunct(const void *, const void *);
    int cmp_names(struct FileInfoBlock *, struct FileInfoBlock *);
    int cmp_dates(struct FileInfoBlock *, struct FileInfoBlock *);
    int cmp_sizes(struct FileInfoBlock *, struct FileInfoBlock *);

    UWORD owners = 0, hil, i, j;
    struct FileInfoBlock **fibptr, *curfib, **fibptricon;
    ULONG bits, owner, flags;
    APTR pool;
    STRPTR *dstream, *dsptr, *dptr, ptr, s;
    const static STRPTR hil_on = "\x9B" "1m", hil_off = "\x9B" "0m";
    struct DateTime dt;
    BPTR dirlock, lock;
    UWORD userwidth, groupwidth, sizewidth, datewidth;
    ULONG dirsize = 0, dirblocks = 1, dirfiles = 0, dirdirs = 0;
    LONG temprc = 0;

    dt.dat_Format = FORMAT_INT;
    dt.dat_Flags = opts[OPT_DATES] ? 0 : DTF_SUBST;
    dt.dat_StrDay = NULL;

     /* seek for owners, view dirs */
    for (fibptr = fibs; curfib = *fibptr; fibptr++)
        {
        owners |= curfib->fib_OwnerUID | curfib->fib_OwnerGID;

        if (curfib->fib_DirEntryType >= 0)
            {
            curfib->fib_Size = 0;
            curfib->fib_NumBlocks = 1;
            if (opts[OPT_VIEWDIRS])
                scanrecurse(curfib->fib_FileName, &curfib->fib_Size, &curfib->fib_NumBlocks);
            }

        curfib->fib_Icon = ICON_OFF;
        }

    if (!muBase)
         owners = FALSE;

     /* sort list if needed */
    if (!opts[OPT_NOSORT])
        {
        if (opts[OPT_SORTDATE])
            cmp_funct = cmp_dates;
        else if (opts[OPT_SORTSIZE])
            cmp_funct = cmp_sizes;
        else
            cmp_funct = cmp_names;
        qsort(fibs, fibptr-fibs, 4, cmpfunct);
        }

    if ((pool = CreatePool(MEMF_ANY, 1024, 1024)) == NULL)
        return ERROR_NO_FREE_STORE;
    if ((dptr = dstream = (STRPTR *)AllocPooled(pool, (fibptr-fibs)*FIELDS*4)) == NULL)
        {
        DeletePool(pool);
        return ERROR_NO_FREE_STORE;
        }

    if (!opts[OPT_ICONS])
        {
         /* remove icons from list */
        for (fibptr = fibs; curfib = *fibptr; fibptr++)
            {
            strcpy(buf, curfib->fib_FileName);
            if (buf[0] == '.')
                curfib->fib_Icon = ICON_SKIP;
            else if ((strlen(buf) >=5 ) && !strcmp(buf+strlen(buf)-5, ".info"))
                {
                 /* mark as icon */
                curfib->fib_Icon = ICON_SKIP;
                buf[strlen(buf)-5] = '\0';
                 /* search for its file */
                for (fibptricon = fibs; curfib = *fibptricon; fibptricon++)
                    {
                    if (strcmp(buf, curfib->fib_FileName) == 0)
                        {
                        curfib->fib_Icon = ICON_ON;
                        break;
                        }
                    }
                }
            }
        }

    dirlock = Lock("", ACCESS_READ);

     /* process all fibs */
    dsptr = dstream;
    for (fibptr = fibs; curfib = *fibptr; fibptr++)
        {
        if ((!opts[OPT_ICONS]) && curfib->fib_Icon == ICON_SKIP)
            continue;

        temprc = ERROR_NO_FREE_STORE;

         /* count dir */
        dirsize   += curfib->fib_Size;
        dirblocks += curfib->fib_NumBlocks;
        if (curfib->fib_DirEntryType < 0)
            dirfiles++;
        else
            dirdirs++;

        dptr = dsptr;
        dsptr += FIELDS;

         /* allocate puddle for bits */
        if ((ptr = *dptr++ = AllocPooled(pool,
         BITSLEN+20*(opts[OPT_FULL] ? 1 : 0))) == NULL)
            break;

         /* show type of entry */
        *ptr++ = (curfib->fib_DirEntryType < -5 || curfib->fib_DirEntryType > 4) ?
         '?' : "PL-???ddsl"[curfib->fib_DirEntryType+5];

        if (!opts[OPT_ICONS])
             /* indicate icon */
            *ptr++ = (curfib->fib_Icon == ICON_ON) ? 'i' : '-';

        if (!opts[OPT_COMMS])
             /* indicate comment */
             *ptr++ = curfib->fib_Comment[0] ? 'c' : '-';

         /* show protection bits (hspa) */ // Nope must be: Uspa Thomas at 25-1-96
        bits = curfib->fib_Protection;

// next lines added 25-1-96 by TZ
        if(bits & 0x80000000) *ptr++ = 'u';
        else *ptr++ ='-';
        bits<<=1;
// next 3 lines changed: 
        for (i=1; i<4; i++)
            *ptr++ = ((bits <<= 1) & 0x100) ? "uspa"[i] : '-';
        *ptr++ = ' ';

//      for (i=0; i<4; i++)
//          *ptr++ = ((bits <<= 1) & 0x100) ? "uspa"[i] : '-';
//      *ptr++ = ' ';

         /* show right bits (rwed) */
        bits = curfib->fib_Protection;
        if (owners)
            {
            owner = *(ULONG *)&curfib->fib_OwnerUID;
            flags = muGetRelationship(taskowner, owner, TAG_DONE);
            if (!opts[OPT_FULL])
                {
                 /* the user is owner of file? */
                if ((flags & muRelF_UID_MATCH) == 0)
                    {
                     /* the user belongs to file owner's group? */
                    if (flags & muRelF_GID_MATCH)
                        bits = (bits>>8)^0x0F;
                     /* other user */
                    else
                        bits = (bits>>12)^0x0F;
                    }
                 /* show right bits for current user */
                for (i=0; i<4; i++)
                    *ptr++ = ((bits <<= 1) & 0x10) ? '-' : "rwed"[i];
                }
            else
                {
                hil = 0;
                 /* the user is owner of file? */
                if ((flags & muRelF_UID_MATCH) == 0)
                    hil = (flags & muRelF_GID_MATCH) ? 1 : 2;
                for (j = 0; j < 3; j++)
                    {
                    bits = curfib->fib_Protection;
                    switch (j)
                        {
                        case 1:
                            bits = (bits>>8)^0x0F;
                            break;

                        case 2:
                            bits = (bits>>12)^0x0F;
                        }
                    if (hil == j)
                        for(s = hil_on; *ptr = *s++; ptr++);
                     /* show right bits for current user */
                    for (i=0; i<4; i++)
                        *ptr++ = ((bits <<= 1) & 0x10) ? '-' : "rwed"[i];
                    if (hil == j)
                        for(s = hil_off; *ptr = *s++; ptr++);
                    }
                }
            *ptr++ = ' ';
            *ptr++ = ' ';
            *ptr = '\0';

             /* show owner user/group */
            if (curfib->fib_OwnerUID == 0)
                {
                for (i=0; i<7; i++)
                    *dptr++ = NULL;
                dptr[-4] = " ";
                }
            else
                {
                *dptr++ = flags & muRelF_UID_MATCH ? hil_on : NULL;
                userinfo->uid = curfib->fib_OwnerUID;
                muGetUserInfo(userinfo, muKeyType_uid);
                if ((ptr = *dptr++ = AllocPooled(pool, strlen(userinfo->UserID)+1)) == NULL)
                    break;
                strcpy(ptr, userinfo->UserID);
                *dptr++ = flags & muRelF_UID_MATCH ? hil_off : NULL;
                *dptr++ = "/";
                *dptr++ = flags & muRelF_GID_MATCH ? hil_on : NULL;
                groupinfo->gid = curfib->fib_OwnerGID;
                muGetGroupInfo(groupinfo, muKeyType_gid);
                if ((ptr = *dptr++ = AllocPooled(pool, strlen(groupinfo->GroupID)+1)) == NULL)
                    break;
                strcpy(ptr, groupinfo->GroupID);
                *dptr++ = flags & muRelF_GID_MATCH ? hil_off : NULL;
                }
            }
        else
            {
             /* show standard right bits */
            for (i=0; i<4; i++)
                *ptr++ = ((bits <<= 1) & 0x10) ? '-' : "rwed"[i];
            *ptr = '\0';
             /* no owner information available */
            for (i=0; i<7; i++)
                *dptr++ = NULL;
            }

         /* show size */
        if (curfib->fib_DirEntryType >= 0 && (!opts[OPT_VIEWDIRS]))
            *dptr++ = "Dir";
        else
            {
            if (opts[OPT_SIZES])
                {
                if ((ptr = *dptr++ = AllocPooled(pool, 12)) == NULL)
                    break;
                sprintf(ptr, "%ld", curfib->fib_Size);
                }
            else
                {
                if ((ptr = *dptr++ = AllocPooled(pool, 6)) == NULL)
                    break;
                itok(curfib->fib_Size, ptr);
                }
            }

         /* show date/time */
        dt.dat_Stamp = curfib->fib_Date;
        if ((dt.dat_StrDate = *dptr++ = AllocPooled(pool, 10)) == NULL ||
            (dt.dat_StrTime = *dptr++ = AllocPooled(pool, 10)) == NULL)
            break;
        if (!DateToStr(&dt))
            {
            *dt.dat_StrTime = '\0';
            strcpy(dt.dat_StrDate, "Invalid");
            }
        else if(!opts[OPT_SECS])
            dt.dat_StrTime[5] = '\0';

         /* show filename */
        *dptr++ = curfib->fib_DirEntryType >= 0 ? hil_on : NULL;
        if ((ptr = *dptr++ = AllocPooled(pool, strlen(curfib->fib_FileName)+3)) == NULL)
            break;
        strcpy(ptr, curfib->fib_FileName);
        if (curfib->fib_DirEntryType >= 0)
            strcat(ptr, "/");
        *dptr++ = curfib->fib_DirEntryType >= 0 ? hil_off : NULL;

         /* show hardlink */
        if (!opts[OPT_NOLINKS])
            {
            if (curfib->fib_DirEntryType == ST_LINKDIR ||
                curfib->fib_DirEntryType == ST_LINKFILE)
                {
                if (lock = Lock(curfib->fib_FileName, ACCESS_READ))
                    {
                    if (NameFromLock(lock, buf, 250))
                        {
                        if ((ptr = *dptr++ = AllocPooled(pool, strlen(buf)+6)) == NULL)
                            break;
                        strcpy(ptr, " -> ");
                        strcat(ptr, buf);
                        if (curfib->fib_DirEntryType == ST_LINKDIR)
                            strcat(ptr, "/");
                        }
                    else
                        *dptr++ = NULL;
                    UnLock(lock);
                    }
                else
                    *dptr++ = NULL;
                }
            /* show softlink */
            else if (curfib->fib_DirEntryType == ST_SOFTLINK)
                {
                readsoftlink(curfib->fib_FileName, buf, 250);
                if ((ptr = *dptr++ = AllocPooled(pool, strlen(buf)+6)) == NULL)
                    break;
                strcpy(ptr, " -> ");
                strcat(ptr, buf);
                }
            else
                *dptr++ = NULL;
            }
        else
            *dptr++ = NULL;

        if (opts[OPT_COMMS] && *curfib->fib_Comment)
            {
            if ((ptr = *dptr = AllocPooled(pool, strlen(curfib->fib_Comment)+4)) == NULL)
                break;
            strcpy(ptr, "\n:");
            strcat(ptr, curfib->fib_Comment);
            }
        else
            *dptr = NULL;

        temprc = 0;
        }

    if (temprc)
        {
        DeletePool(pool);
        UnLock(dirlock);
        return temprc;
        }

     /* determine field width for owner user, owner group, file length and date */
    userwidth = groupwidth = sizewidth = datewidth = 0;
    for (dptr = dstream; dptr < dsptr; dptr += FIELDS)
        {
        userwidth = max(strlen(dptr[2]), userwidth);
        groupwidth = max(strlen(dptr[6]), groupwidth);
        sizewidth = max(strlen(dptr[8]), sizewidth);
        datewidth = max(strlen(dptr[9]), datewidth);
        }

     /* print header */
    if (NameFromLock(dirlock, buf, 255))
        Printf("%sDirectory of %s\n", total ? "\n" : NULL, buf);
    UnLock(dirlock);

     /* create format string */
    sprintf(buf, " %%s%%s%%%lds%%s%%s%%s%%-%lds%%s  %%%lds  %%%lds %%s  %%s%%s%%s%%s%%s\n",
     userwidth, groupwidth, sizewidth, datewidth);
     /* print entries */
    for (dptr = dstream; dptr < dsptr; dptr += FIELDS)
        {
        VPrintf(buf, dptr);
        if (SetSignal(0L, SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D) &
         (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D))
            {
            DeletePool(pool);
            return ERROR_BREAK;
            }
        }

     /* print dir information */
    dirinfo(dirsize, dirblocks, dirfiles, dirdirs);

     /* if listing more dirs, count total information */
    totalsize   += dirsize;
    totalblocks += dirblocks;
    totalfiles  += dirfiles;
    totaldirs   += dirdirs;
    total++;

     /* free all allocated memory */
    DeletePool(pool);

    return 0L;
    }
///

/// readsoftlink
/*
 * thanks for this to Carsten Heyl and AMK
 */
void __regargs readsoftlink(STRPTR path, STRPTR buf, LONG buflen)
    {
    BPTR lock;
    UBYTE *bname;
    struct DevProc *devproc;
    LONG pathlen;

    strcpy(buf, "???");
    if ((pathlen = strlen(path)) > 255)
        return;

     /* build BSTR path */
    if (bname = AllocMem(pathlen+2, MEMF_CLEAR|MEMF_PUBLIC))
        {
        bname[0] = pathlen;
        CopyMem(path, bname+1, pathlen);
        if (devproc = GetDeviceProc(path, NULL))
            {
            if (lock = DoPkt(devproc->dvp_Port, ACTION_LOCATE_OBJECT, devproc->dvp_Lock,
             MKBADDR(bname), ACCESS_READ, NULL, NULL))
                {
                if ((lock == NULL) && (IoErr() == ERROR_IS_SOFT_LINK))
                    ReadLink(devproc->dvp_Port, devproc->dvp_Lock, path, buf, buflen);
                UnLock(lock);
                }
            FreeDeviceProc(devproc);
            }
        FreeMem(bname, pathlen+2);
        }
    }
///

/// itok
void __regargs itok(ULONG num, STRPTR buf)
    {
    UBYTE *exp = " KMG";
    ULONG m = 1, m2, res, mod;

    if (num<1024)
        {
        sprintf(buf, "%ld", num);
        return;
        }
    while ((res = (num+m/2)/m) >= 1000)
        {
        m2 = m;
        m *= 1024;
        exp++;
        }
    if (res < 11)
        {
        res = num/m;
        if (res < 1)
            {
            sprintf(buf, "1.0%lc", *exp);
            return;
            }
        mod = ((num/m2)%1024*10+512)/1024;
        if (mod > 9)
            {
            res++;
            mod = 0;
            }
        if (res < 10)
            {
            sprintf(buf, "%ld.%ld%lc", res, mod, *exp);
            return;
            }
        }
    sprintf(buf, "%ld%lc", res, *exp);
    }
///

/// max
UWORD __regargs max(UWORD a, UWORD b)
    {
    return (UWORD)((a>b) ? a : b);
    }

#define FIB_a (*(struct FileInfoBlock **)a)
#define FIB_b (*(struct FileInfoBlock **)b)

int cmpfunct(const void *a, const void *b)
    {
    int c;

    if (opts[OPT_FIRSTDIRS])
        {
        if (FIB_a->fib_DirEntryType < 0)
            c = FIB_b->fib_DirEntryType >= 0;
        else
            c = -(FIB_b->fib_DirEntryType < 0);
        if (c)
            return c;
        }
    c = cmp_funct(FIB_a, FIB_b);
    return opts[OPT_SORTBACK] ? -c : c;
    }

int cmp_names(struct FileInfoBlock *a, struct FileInfoBlock *b)
    {
    return stricmp(a->fib_FileName, b->fib_FileName);
    }

int cmp_dates(struct FileInfoBlock *a, struct FileInfoBlock *b)
    {
    return CompareDates(&a->fib_Date, &b->fib_Date);
    }

int cmp_sizes(struct FileInfoBlock *a, struct FileInfoBlock *b)
    {
    return b->fib_Size - a->fib_Size; /* dirs sorted first already */
    }

void __regargs dirinfo(ULONG dirsize, ULONG dirblocks, ULONG dirfiles, ULONG dirdirs)
    {
    static UBYTE buf[10];

     /* print dir information */
    if (!opts[OPT_VIEWDIRS])
        {
        if (dirdirs)
            Printf("%ld dir%s, ", dirdirs, dirdirs > 1 ? "s" : NULL);
        if (dirfiles)
            {
            itok(dirsize, buf);
            Printf("%s %sin %ld file%s, ", buf,
             dirsize<1000 ? "bytes " : NULL, dirfiles, dirfiles > 1 ? "s" : NULL);
            }
        }
    else
        {
        itok(dirsize, buf);
        Printf("%s %sin ", buf, dirsize<1000 ? "bytes" : NULL);
        if (dirdirs)
            Printf("%ld dir%s%s ", dirdirs, dirdirs > 1 ? "s" : NULL, dirfiles ? " and" : ",");
        if (dirfiles)
            Printf("%ld file%s, ", dirfiles, dirfiles > 1 ? "s" : NULL);
        }
    Printf("%ld blocks used\n", dirblocks);
    }
///

/// scanrecurse
BOOL __regargs scanrecurse(STRPTR name, LONG *dirsize, LONG *dirblocks)
    {
    struct AnchorPath *ap;
    LONG temprc;
    BOOL rc = TRUE;

    if (ap = (struct AnchorPath *)AllocMem(
     sizeof(struct AnchorPath), MEMF_ANY|MEMF_CLEAR))
        {
        MatchFirst(name, ap);
        while ((temprc = IoErr()) == 0)
            {
            if (ap->ap_Info.fib_DirEntryType > 0)
                {
                if (!(ap->ap_Flags & APF_DIDDIR))
                    {
                    ap->ap_Flags |= APF_DODIR;
                    ++*dirblocks;
                    }
                ap->ap_Flags &= ~APF_DIDDIR;
                }
            else
                {
                *dirsize   += ap->ap_Info.fib_Size;
                *dirblocks += ap->ap_Info.fib_NumBlocks;
                }
            if (temprc && (temprc != ERROR_NO_MORE_ENTRIES))
                {
                rc = FALSE;
                break;
                }
            MatchNext(ap);
            }
        MatchEnd(ap);
        FreeMem(ap, sizeof(struct AnchorPath));
        }
    else
        return FALSE;
    return rc;
    }
///
