/* 
FILES.C -- list all files in system file table
Version No. 3 (23 March 1991)
Author: Andrew Schulman (CIS 76320,302)

Microsoft C 6.0:
    cl files.c
    cl -DFREEUP -Fefreeup.exe files.c

Turbo C:
    tcc files.c
    tcc -DFREEUP -efreeup.exe files.c
        
This version is different from that published in BYTE, March 1991,
and from code in the book UNDOCUMENTED DOS, pp. 197-201, 209-210.
    -- includes DOS 3.0 file structure (USHORT dir_entry, not BYTE)
    -- allows PSP +- 1 (some DOS versions store PSPowner incorrectly!)
    -- fixes bug in for loops
    -- points out typo printed in BYTE version (int 21h, not int 21)
    -- fixes test for AUX, CON, PRN (e.g. check for "AUX " not "AUX")
    -- split filename[11] into filename[8], ext[3]
    -- DOS 2.x, 3.0, 3.1+ structs combined into union, with access
       macros. This points up the relative inflexibility of structures:
       struct->field just turns into an offset, but at compile-time,
       with no control over changing field offsets at run-time.
    -- points out a possible problem with the AUX-CON "sanity check."
            
    Future versions of FILES should also print out open/sharing modes,
and drive number (inside dev_info field in DOS 3+).
*/

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

typedef unsigned char BYTE;
typedef unsigned USHORT;
typedef unsigned long ULONG;
typedef BYTE far *FP;

#pragma pack(1)

typedef struct file2 {   
    BYTE num_handles, open_mode;
    BYTE fattr;     // might not be accurate??
    BYTE drive;
    BYTE filename[8], ext[3];
    USHORT unknown1, unknown2;
    ULONG fsize;
    // ...
    // NOTE! no owner_psp!
    } file2; // for DOS 2.x, see UNDOCUMENTED DOS, p. 526
    
typedef struct file30 {   
    USHORT num_handles, open_mode;
    BYTE fattr;
    USHORT dev_info;    // includes drive number
    FP ptr;
    USHORT start_cluster, time, date;
    ULONG fsize, offset;
    USHORT rel_cluster, abs_cluster, dir_sector;
    USHORT dir_entry;    // only difference from file31: USHORT, not BYTE
    BYTE filename[8], ext[3];
    ULONG share_prev_sft;
    USHORT share_net_machine;
    USHORT owner_psp;
    // ...
    } file30; // for DOS 3.0 only
    
typedef struct file31 {   
    USHORT num_handles, open_mode;
    BYTE fattr;
    USHORT dev_info;    // includes drive number
    FP ptr;
    USHORT start_cluster, time, date;
    ULONG fsize, offset;
    USHORT rel_cluster, abs_cluster, dir_sector;
    BYTE dir_entry;
    BYTE filename[8], ext[3];
    ULONG share_prev_sft;
    USHORT share_net_machine;
    USHORT owner_psp;
    // ...
    } file31; // for DOS 3.1+, 4.x, see UNDOCUMENTED DOS, pp. 527-528
        
typedef union file {
    file2 f2;
    file30 f30;
    file31 f31;
    } file;
    
#define DOSVER_FIELD(pf,x) \
    (_osmajor == 2) ?                   pf->f2.x : \
    (_osmajor == 3 && _osminor == 0) ?  pf->f30.x : \
    /* DOS 3.1+ */                      pf->f31.x

// access macros
#define FILENAME(pf)    DOSVER_FIELD(pf,filename)
#define EXT(pf)         DOSVER_FIELD(pf,ext)
#define FSIZE(pf)       DOSVER_FIELD(pf,fsize)
#define FATTR(pf)       DOSVER_FIELD(pf,fattr)
#define NUM_HANDLES(pf) DOSVER_FIELD(pf,num_handles)

#define NUM_HANDLES_LVALUE(pf)  *((USHORT far *) pf)

// treat OWNER_PSP separately in DOS2, because no owner_psp field
#define OWNER_PSP(pf)   \
    (_osmajor == 2) ?                   -1 : \
    (_osmajor == 3 && _osminor == 0) ?  pf->f30.owner_psp : \
    /* DOS 3.1+ */                      pf->f31.owner_psp
        
typedef struct sysftab {
    struct sysftab far *next;
    USHORT num_files;
    file f[1];
    } SYS_FTAB;     // see UNDOCUMENTED DOS, pp. 526-527
    
typedef struct {
    BYTE type;      
    USHORT owner;    /* PSP of the owner */
    USHORT size;
    BYTE unused[3];
    BYTE dos4[8];
    } MCB;      // see UNDOCUMENTED DOS, p. 520

void fail(char *s) { puts(s); exit(1); }    

#ifdef __TURBOC__
#define GETVECT(intno)  getvect(intno)
#define ASM             asm
#else
#define GETVECT(intno)  _dos_getvect(intno)
#define ASM             _asm
#endif

#ifndef MK_FP
#define MK_FP(seg,ofs)  ((FP)(((ULONG)(seg) << 16) | (ofs)))
#endif

#define NEXT(mcb)       (MK_FP(FP_SEG(mcb) + (mcb)->size + 1, 0))

int belongs(FP vec, USHORT start, USHORT size)
{
    USHORT seg = FP_SEG(vec) + (FP_OFF(vec) >> 4);
    return (seg >= start) && (seg <= (start + size));
}

int is_psp(USHORT seg)
{
    return ((((MCB far *) MK_FP(seg-1,0))->owner == seg) &&
            (*((USHORT far *) MK_FP(seg,0)) == 0x20CD));
}
                
/*
    Look for "orphaned" file handles: e.g., TSR>FOO.BAR or TSR>NUL
    will leave FOO.BAR or NUL entry in SFT, consuming file handle. If
    the PSP of the file's owner is COMMAND.COM, and if there's only
    one owner, then we decide it's an orphaned handle.
*/
int orphan(file far *ff)
{
    static command_com_psp = 0;
    if (! NUM_HANDLES(ff))
        return 0;
    if (! command_com_psp)  /* do just one time */
    {
        FP int2e = (FP) GETVECT(0x2E); // won't work under MKS Toolkit SH.EXE!
        MCB far *mcb;
        ASM mov ah, 52h
        ASM int 21h         // the 'h' was left out in BYTE!
        ASM mov ax, es:[bx-2]
        ASM mov word ptr mcb+2, ax
        ASM mov word ptr mcb, 0
        while (mcb->type != 'Z')
            if (belongs(int2e, FP_SEG(mcb), mcb->size))
            {
                command_com_psp = mcb->owner;
                break;
            }
            else
                mcb = (MCB far *) NEXT(mcb);
    }
    return ((OWNER_PSP(ff) == command_com_psp) &&
            (NUM_HANDLES(ff) == 1));
}

/* changed from the March 1991 BYTE article and from UNDOCUMENTED 
DOS, p. 199 */
#define IS_AUX(s) (s[0]=='A' && s[1]=='U' && s[2]=='X' && s[3] == ' ')
#define IS_CON(s) (s[0]=='C' && s[1]=='O' && s[2]=='N' && s[3] == ' ')
#define IS_PRN(s) (s[0]=='P' && s[1]=='R' && s[2]=='N' && s[3] == ' ')

main(void)
{
    SYS_FTAB far *sys_filetab;
    file far *ff;
    int size;
    int i;

    ASM mov ah, 52h
    ASM int 21h         // the 'h' was left out in BYTE!
    ASM les bx, dword ptr es:[bx+4] /* ptr to list of DOS file tables */
    ASM mov word ptr sys_filetab, bx
    ASM mov word ptr sys_filetab+2, es

    /* DOS box of OS/2 1.x doesn't provide system file tbl */
    if (sys_filetab == (SYS_FTAB far *) -1L)
        fail("system file table not supported");

    switch (_osmajor)
    {
        case 2:             size = 0x28; break;
        case 3:             size = 0x35; break;
        default:            size = 0x3b; break;
    }

    /* Perform sanity check: determine size of file structure
       empirically from difference between strings "CON" and
       "AUX." If this equals size computed via _osmajor, everything
       is fine. Otherwise, we reset size. */
    /* NOTE: But maybe this "sanity check" ain't so sane, since
       it depends on the placement of AUX before CON in the SFTs,
       which is by no means a requirement of the undocumented DOS
       "spec." */
    {
        FP p, q;
        int i;
        /* i=1000: set upper limit on string search in memory */
        for (p=(FP)sys_filetab->f, i=1000; p++, i--; )
            if (IS_AUX(p))
                break;
        if (! i) 
            return 1;
        for (q=p, i=1000; q++, i--; )
            if (IS_CON(q))
                break;
        if (! i) 
            return 1;
        /* size of file structure must equal span from AUX to CON */
        if (size != (q - p))
        {
            puts("warning: size based on _osmajor looks wrong");
            size = q - p;
        }
    }

    printf("Filename              Size      Attr Handles    Owner\n");
    printf("--------              ----      ---- -------    -----\n");

    do { /* FOR EACH SFT */
        
        /* FOR EACH ENTRY IN THIS SFT */
        for (i=sys_filetab->num_files, ff=sys_filetab->f; 
             i--; 
             ((FP) ff) += size)
        if (NUM_HANDLES(ff))
        {
            printf("%.8Fs.",   FILENAME(ff));
            printf("%.3Fs\t",  EXT(ff));
            printf("%10lu\t",  FSIZE(ff));
            printf("%04X\t",   FATTR(ff));
            printf("%d\t",     NUM_HANDLES(ff));
            if (_osmajor > 2)
            {
                USHORT owner = OWNER_PSP(ff);   // not in DOS2
                char far *name;
                printf("%04X\t", owner);
                if ((! is_psp(owner)) &&    // allow off-by-one
                    (! is_psp(owner - 1)) &&
                    (! is_psp(owner + 1)))
                        printf("[NOT PSP]");
                if (orphan(ff))
                    printf("[ORPHAN]");
#ifdef FREEUP
                // only DOS 3+
                name = FILENAME(ff);
                if (! IS_AUX(name)) 
                if (! IS_CON(name))
                if (! IS_PRN(name))
                if (orphan(ff) || 
                   ((! is_psp(owner)) &&        // allow off-by-one
                    (! is_psp(owner - 1)) &&
                    (! is_psp(owner + 1))))
                {
                    if (! (-- NUM_HANDLES_LVALUE(ff))) // decrement owners
                        printf(" [FREED]");
                    else
                        printf(" [NOW %d]", NUM_HANDLES(ff));
                }
#endif
            }
            printf("\n");
        }
        sys_filetab = sys_filetab->next; /* FOLLOWED LINKED LIST... */
    } while (FP_SEG(sys_filetab) && 
             FP_OFF(sys_filetab) != (unsigned) -1); /* ...UNTIL END */
    return 0;
}

