#include <exec/types.h>
#include <exec/io.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>
#include <intuition/intuition.h>
#include <workbench/startup.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/intuition.h>

#include <stdio.h>
#include <string.h>

#define error(s) { if (cli) Write(_Backstdout, (s), strlen(s)); }

#define USAGE "blocks <filename>\n"
/* border sizes */
#define XLEFT 2
#define XRIGHT 2
#define YTOP 10
#define YBOTTOM 1
/* Min & Max size for window */
#define MAXHEIGHT (200 - YTOP - YBOTTOM)
#define MAXWIDTH   (640 - XLEFT - XRIGHT)
#define MINWIDTH 200
#define MINHEIGHT 80

/* Spawn info */
long _stack = 2000;
char *_procname = "blocks";
long _priority = 0;
long _BackGroundIO = TRUE;
extern long _Backstdout;

typedef struct FileInfoBlock FIB;
typedef struct InfoData INFO;

extern struct IntuitionBase *IntuitionBase;
extern struct GfxBase *GfxBase;

/* For io to device */
struct MsgPort *port;
struct IOStdReq *io;
int DevOpen;
/* Device info */
long blk_size, blk_offset, root_blk, *secbuf;
struct Window *win;
/* size of window, of each block, etc */
int xdiv, rectw, recth, width, height;
int cli; /* Called from cli ? */

struct NewWindow newwin = {
    0, 0,
    0, 0,
    -1, -1,
    CLOSEWINDOW,
    WINDOWDRAG | WINDOWDEPTH | WINDOWCLOSE | SMART_REFRESH | NOCAREREFRESH | RM
BTRAP,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    0, 0, 0, 0,
    WBENCHSCREEN
};

/* Calculate coords on window for block 'block' on disk */
void cvt_point(x, y, block)
long *x, *y, block;
{
    *x = rectw * (block / (xdiv * height)) + XLEFT;
    *y = recth * (block % height) + YTOP;
}

/* Calc size, & open window */
int prepare_window(fib, dev)
struct FileInfoBlock *fib;
struct DeviceNode *dev;
{
    /* Get disk characteristics */
    struct FileSysStartupMsg *msg = (struct FileSysStartupMsg *)BADDR(dev->dn_S
tartup);
    ULONG *env = (ULONG *)BADDR(msg->fssm_Environ);
    long blkscyl = env[DE_NUMHEADS] * env[DE_BLKSPERTRACK];
    long numcyls = env[DE_UPPERCYL] - env[DE_LOWCYL] + 1;
    long blks;
    static char title[80];

    if (blkscyl <= MAXHEIGHT) /* Do a "nice" presentation, 1 cylinder per vert
line */
    {
        height = blkscyl;
        xdiv = (numcyls / MAXWIDTH + 1); /* Nb of cylinders per vertical line *
/
        width = numcyls / xdiv;
    }
    else /* Just squash em in */
    {
        blks = numcyls * blkscyl;
        height = MAXHEIGHT;
        xdiv = (blks / MAXHEIGHT + 1) / MAXWIDTH + 1;
        width = (blks / MAXHEIGHT + 1) / xdiv;
    }
    /* Size of rect for 1 block */
    rectw = MINWIDTH / width + 1;
    recth = MINHEIGHT / height + 1;

    /* Open window */
    sprintf(title, "File: %s, %ld blocks", fib->fib_FileName, fib->fib_NumBlock
s);
    newwin.Title = title;
    newwin.Width = rectw * width + XLEFT + XRIGHT;
    newwin.Height = recth * height + YTOP + YBOTTOM;

    if (win = OpenWindow(&newwin))
    {
        SetAPen(win->RPort, 2);
        RectFill(win->RPort, XLEFT, YTOP, win->Width - XRIGHT - 1, win->Height
- YBOTTOM - 1);
        SetAPen(win->RPort, 3);
        return TRUE;
    }
    return FALSE;
}

/* bstr -> cstr */
char *btoc_str(to, from)
char *to;
BSTR from;
{
    char *cstr = (char *)BADDR(from);

    strncpy(to, cstr + 1, *cstr);
    to[*cstr] = '\0';

    return to;
}

/* Reads sector at offset 'sector' from disk ('sector' must be a multiple of 51
2) */
BYTE *ReadSector(sector, buf, len)
long sector;
BYTE *buf;
long len;
{
    io->io_Command = CMD_READ;
    io->io_Length = len;
    io->io_Data = (APTR)buf;
    io->io_Offset = sector;
    DoIO((struct IORequest *)io);
    return (io->io_Error == 0) ? buf : NULL;
}

/* Find device by task */
struct DeviceNode *TaskDevice(task)
struct MsgPort *task;
{
    struct DeviceNode *devlist;

    Forbid();
    devlist = (struct DeviceNode *)BADDR(((struct DosInfo *)BADDR(((struct Root
Node *)(DOSBase->dl_Root))->rn_Info))->di_DevInfo);
    for (;devlist; devlist = (struct DeviceNode *)BADDR(devlist->dn_Next))
        if (devlist->dn_Type == DLT_DEVICE)
        {
            if (task == devlist->dn_Task) { Permit(); return devlist; }
        }

    Permit();
    return NULL;
}

/* Reads block n of *partition* */
void ReadBlock(n)
long n;
{
    ReadSector((blk_offset + n) * blk_size * 4, secbuf, blk_size * 4);
}

/* Get partition characteristics, open device */
int setup(dev)
struct DeviceNode *dev;
{
    char devname[32];
    struct FileSysStartupMsg *msg = (struct FileSysStartupMsg *)BADDR(dev->dn_S
tartup);
    ULONG *env = (ULONG *)BADDR(msg->fssm_Environ);
    long err;

    port = CreatePort(0, 0);
    if (!port)
    {
        error("No port!\n");
        return FALSE;
    }
    io = CreateStdIO(port);
    if (!io)
    {
        error("No IO request!\n");
        return FALSE;
    }
    err = OpenDevice(btoc_str(devname, msg->fssm_Device), msg->fssm_Unit, (stru
ct IORequest *)io, msg->fssm_Flags);
    if (err)
    {
        error("Device not opened\n");
        return FALSE;
    }
    DevOpen = TRUE;
    blk_size = env[DE_SIZEBLOCK];
    blk_offset = env[DE_LOWCYL] * env[DE_BLKSPERTRACK] * env[DE_NUMHEADS];
    root_blk = (env[DE_BLKSPERTRACK] * env[DE_NUMHEADS] * (env[DE_UPPERCYL] - e
nv[DE_LOWCYL] + 1) - 1 + env[DE_RESERVEDBLKS]) / 2;
    secbuf = (long *)AllocMem(4 * blk_size, env[DE_MEMBUFTYPE]);
    if (!secbuf)
    {
        error("No sector buffer!\n");
        return FALSE;
    }
    return TRUE;
}

/* Close device */
void release()
{
    if (secbuf) FreeMem((char *)secbuf, 4 * blk_size);
    if (DevOpen) CloseDevice((struct IORequest *)io);
    if (io) DeleteStdIO(io);
    if (port) DeletePort(port);
}

/* Displays blocks used by file described by fib */
/* For OFS/FFS !!! */
void get_blocks(fib, node, device)
FIB *fib;
struct DeviceList *node;
struct DeviceNode *device;
{
    long key = fib->fib_DiskKey, data1 = blk_size - 51, used, i, x, y;
    int quit;
    struct IntuiMessage *msg;

    if (prepare_window(fib, device))
    {
        do {
            Forbid();
            if (device->dn_Task != node->dl_Task)
            {
                error("Disk not in drive!\n");
                break;
            }
            ReadBlock(key); /* Read either File header or file list block */
            Permit();
            used = secbuf[2]; /* num blocks described here */
            /* Draw blocks */
            for (i = 0; i < used; i++)
            {
                cvt_point(&x, &y, secbuf[data1 - i]);
                RectFill(win->RPort, x, y, x + rectw - 1, y + recth - 1);
            }
            /* Extension blocks ? */
            key = secbuf[blk_size - 2];
        } while (key != 0);

        /* Wait for window close */
        quit = FALSE;
        while (!quit)
        {
            WaitPort(win->UserPort);
            while (msg = (struct IntuiMessage *)GetMsg(win->UserPort))
            {
                quit = msg->Class == CLOSEWINDOW;
                ReplyMsg((struct Message *)msg);
            }
        }
        CloseWindow(win);
    }
}

/* Display blocks used by file name */
void disp_blocks(name)
char *name;
{
    FIB *fib = (FIB *)AllocMem(sizeof(FIB), 0L);
    INFO *info = (INFO *)AllocMem(sizeof(INFO), 0L);
    BPTR lock = Lock(name, SHARED_LOCK);
    struct DeviceList *node;
    struct DeviceNode *device;

    if (fib && info)
        if (lock)
            if (Info(lock, info) && Examine(lock, fib))
                if (fib->fib_DirEntryType >= 0) error("Only for files!\n")
                else if (info->id_UnitNumber < 0) error("Only on OFS or FFS dis
ks\n") /* Test detects RAM: ... */
                else
                {
                    node = (struct DeviceList *)BADDR(info->id_VolumeNode);
                    if (node == 0) error("Disk not in drive\n")
                    else
                        if (device = TaskDevice(node->dl_Task))
                        {
                            if (setup(device)) get_blocks(fib, node, device);
                            release();
                        }
                        else error("Couldn't find disk's device!\n")
                }
            else error("Couldn't get info on file\n")
        else error("No such file\n")
    else error("No memory !\n");

    if (lock) UnLock(lock);
    if (fib) FreeMem((char *)fib, sizeof(FIB));
    if (info) FreeMem((char *)info, sizeof(INFO));
}

/* Display blocks used by files passed as parms
   Works from WB or CLI */
void main(argc, argv)
int argc;
char **argv;
{
    int i;

    cli = (argc != 0);

    if (IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library"
, 0L))
        if (GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0L))
            if (argc == 0) /* WB */
            {
                struct WBStartup *msg = (struct WBStartup *)argv;

                for (i = 1; i < msg->sm_NumArgs; i++)
                    if (msg->sm_ArgList[i].wa_Name[0] != '\0') /* A dir */
                    {
                        CurrentDir(msg->sm_ArgList[i].wa_Lock);
                        disp_blocks(msg->sm_ArgList[i].wa_Name);
                    }
            }
            else if (argc == 1) error(USAGE)
            else
                for (i = 1; i < argc; i++) disp_blocks(argv[i]);
        else error("No graphics library !\n")
    else error("No intuition library !\n");

    if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
    if (GfxBase) CloseLibrary((struct Library *)GfxBase);
    if (_Backstdout) Close(_Backstdout);
}
