/*-
 * $Id: hanlock.c,v 1.4 90/01/27 20:22:17 Rhialto Exp $
 * $Log:       hanlock.c,v $
 * Revision 1.4  90/01/27  20:22:17  Rhialto
 * Added extra check when freeing MSFileLocks.
 * 
 * Revision 1.3  90/01/23  00:36:57  Rhialto
 * Add an #ifndef READONLY.
 *
 * Revision 1.2  89/12/17  23:05:33  Rhialto
 * Add MSSetProtect
 *
 * Revision 1.1  89/12/17  20:03:01  Rhialto
 *
 * HANLOCK.C
 *
 * The code for the messydos file system handler
 *
 * The Lock department. Takes care of operations on locks, and consequently,
 * on directories.
 *
 * This code is (C) Copyright 1989 by Olaf Seibert. All rights reserved. May
 * not be used or copied without a licence.
-*/

#include "han.h"
#include "dos.h"

#ifdef DEBUG
#   define     debug(x)  dbprintf x
#else
#   define     debug(x)
#endif

struct LockList *LockList;     /* List of all locked files we have. Note
                                * this is not the same as all locks we
                                * have */
struct MSFileLock *RootLock;   /* Lock on root directory */
struct MSFileLock *EmptyFileLock;      /* 2nd result of MSLock() */

struct DirEntry FakeRootDirEntry = {
    {                          /* de_Msd */
       "Unnamed ",             /* msd_Name */
       "   ",                  /* msd_Ext */
       ATTR_VOLUMELABEL,       /* msd_Attributes */
       {0},                    /* msd_Pad1 */
       0,                      /* msd_Time */
       0x21,                   /* msd_Date, 1/1/80 */
       0,                      /* msd_Cluster */
       0                       /* msd_Filesize */
    },
    -1,                        /* de_Sector */
    0                          /* de_Offset */
};
byte           DotDot[1 + 8 + 3] = "..          ";

/*
 * This routine compares a name in a directory entry with a given name
 */

int
CompareNames(dir, name)
register struct MsDirEntry *dir;
register byte  *name;
{
#ifdef DEBUG
    if (name[0] == '\\') {
       extern short    DBEnable;

       DBEnable = name[1] & 0x0F;

       return CMP_END_OF_DIR;
    }
#endif

    if (dir->msd_Name[0] & DIR_DELETED_MASK)
       return CMP_FREE_SLOT;

    if (dir->msd_Name[0] == 0)
       return CMP_END_OF_DIR;  /* end of directory */

    if (dir->msd_Attributes & ATTR_VOLUMELABEL)
       return CMP_NOT_EQUAL;

    if (strncmp(dir->msd_Name, name, 8 + 3))
       return CMP_NOT_EQUAL;

    if (dir->msd_Attributes & ATTR_DIRECTORY)
       return CMP_OK_DIR;

    return CMP_OK_FILE;
}

void
NextDirEntry(sector, offset)
register word  *sector;
register word  *offset;
{
    if ((*offset += MS_DIRENTSIZE) >= Disk.bps) {
       *offset = 0;
       if (*sector >= Disk.datablock) {
           /* Must be subdirectory */
           *sector = NextClusteredSector(*sector);
           debug(("NextClusteredSector: %d\n", *sector));
       } else {
           if (++*sector >= Disk.datablock) {
               *sector = SEC_EOF;
           }
       }
    }
    /* else no more work needed */
}

/*
 * Get the directory entry following the given one. If requested, we make
 * the directory longer.
 */

struct DirEntry *
FindNext(previous, createit)
register struct DirEntry *previous;
int            createit;
{
    byte          *sector;
    word           prevsec = previous->de_Sector;

    NextDirEntry(&previous->de_Sector, &previous->de_Offset);

    if (previous->de_Sector == SEC_EOF) {
       error = ERROR_OBJECT_NOT_FOUND;
#ifndef READONLY
       if (createit) {
           if (prevsec < Disk.datablock - 1) { /* Should not be necessary */
               previous->de_Sector = prevsec + 1;
           } else if (prevsec >= Disk.datablock) {
               previous->de_Sector = FindFreeSector(prevsec);
           }
           if (previous->de_Sector != SEC_EOF) {
               sector = EmptySec(previous->de_Sector);
               setmem(sector, (int) Disk.bps, 0);
               MarkSecDirty(sector);
               FreeSec(sector);
               setmem(&previous->de_Msd, sizeof (previous->de_Msd), 0);

               return previous;
           }
       }
#endif
    } else if (sector = GetSec(previous->de_Sector)) {
       CopyMem(sector + previous->de_Offset, &previous->de_Msd,
               (long) MS_DIRENTSIZE);
       OtherEndianMsd(&previous->de_Msd);
       FreeSec(sector);

       return previous;
    }
    return NULL;
}

#ifdef DEBUG

void
PrintDirEntry(de)
struct DirEntry *de;
{
    debug(("%d,%d ", de->de_Sector, de->de_Offset));
    debug(("%.8s.%.3s attr:%x time:%x date:%x start:%x size:%lx\n",
          de->de_Msd.msd_Name,
          de->de_Msd.msd_Ext,
          de->de_Msd.msd_Attributes,
          de->de_Msd.msd_Time,
          de->de_Msd.msd_Date,
          de->de_Msd.msd_Cluster,
          de->de_Msd.msd_Filesize
          ));
}

#endif

/*
 * MakeLock makes a struct MSFileLock from a directory entry and the
 * parent directory MSFileLock pointer. It looks if it already has a Lock
 * on it. In that case, it simply increments its reference count, when
 * possible.
 */

struct MSFileLock *
MakeLock(parentdir, dir, mode)
struct MSFileLock *parentdir;
struct DirEntry *dir;
ulong          mode;
{
    register struct MSFileLock *fl;
    struct MSFileLock *nextfl;

    if (mode != EXCLUSIVE_LOCK || (dir->de_Msd.msd_Attributes & ATTR_DIR))
       mode = SHARED_LOCK;

#ifdef DEBUG
    debug(("MakeLock: "));
    PrintDirEntry(dir);
#endif

    /*
     * Look through our list to see if we already have it. The criteria
     * for this are: 1. the directory entries are the same or 2. they have
     * the same first cluster and are both directories (which can have
     * multiple directory entries). Sigh.
     */

    for (fl = (struct MSFileLock *) LockList->ll_List.mlh_Head;
        nextfl = (struct MSFileLock *) fl->msfl_Node.mln_Succ;
        fl = nextfl) {
#ifdef DEBUG
       debug(("> "));
       PrintDirEntry(&fl->msfl_Msd);
#endif
       if ((fl->msfl_DirSector == dir->de_Sector &&
            fl->msfl_DirOffset == dir->de_Offset) ||
           (fl->msfl_Msd.msd_Cluster == dir->de_Msd.msd_Cluster &&
            (dir->de_Msd.msd_Attributes & ATTR_DIR) &&
            (fl->msfl_Msd.msd_Attributes & ATTR_DIR))
           ) {
           /* Found existing lock on file */
           if (fl->msfl_Refcount < 0 || mode == EXCLUSIVE_LOCK) {
               error = ERROR_OBJECT_IN_USE;
               return NULL;
           }
           fl->msfl_Refcount++;
           return fl;
       }
    }

    fl = AllocMem((long) sizeof (*fl), MEMF_PUBLIC);
    if (fl == NULL) {
       error = ERROR_NO_FREE_STORE;
       return NULL;
    }
    fl->msfl_Parent = parentdir ? MSDupLock(parentdir) : NULL;

    fl->msfl_Refcount = (mode == EXCLUSIVE_LOCK) ? -1 : 1;
    fl->msfl_DirSector = dir->de_Sector;
    fl->msfl_DirOffset = dir->de_Offset;
    fl->msfl_Msd = dir->de_Msd;

    AddHead(&LockList->ll_List, fl);

    return fl;
}

/*
 * This routine Locks a file. It first searches it in the directory, then
 * lets the rest of the work be done by MakeLock(). If it encounters an
 * empty slot in the directory, it remembers where, in case we need it. If
 * you clear the MODE_CREATEFILE bit in the mode parameter, we fabricate a
 * new MSFileLock from the empty directory entry. It then becomes the
 * caller's responsibility to MSUnLock() it eventually.
 */

struct MSFileLock *
MSLock(parentdir, name, mode)
struct MSFileLock *parentdir;
byte          *name;
ulong          mode;
{
    byte          *sector;
    struct MSFileLock *newlock;
    register struct DirEntry *de;
    struct DirEntry sde;
    byte          *nextpart;
    byte           component[8 + 3];   /* Note: not null-terminated */
    int            createit;
    word           freesec;
    word           freeoffset;

    de = &sde;
    newlock = NULL;

    /*
     * See if we have an absolute path name (starting at the root).
     */

    {
       register byte  *colon;

       if (colon = index(name, ':')) {
           name = colon + 1;
           parentdir = RootLock;
       }
    }

    /*
     * Get a copy of the parent dir lock, so we can walk it over the
     * directory tree.
     */
    parentdir = MSDupLock(parentdir);

    /*
     * Start with the directory entry of the parent dir.
     */

    sde.de_Msd = parentdir->msfl_Msd;
    sde.de_Sector = parentdir->msfl_DirSector;
    sde.de_Offset = parentdir->msfl_DirOffset;
#ifdef DEBUG
    debug(("pdir %08lx: ", parentdir));
    PrintDirEntry(&parentdir->msfl_Msd);
#endif

newdir:
    freesec = SEC_EOF;         /* Means none found yet */

    nextpart = ToMSName(component, name);
    debug(("Component: '%11s'\n", component));
    if (nextpart[0] != '/') {
       nextpart = NULL;
#ifndef READONLY
       /*
        * See if we are requested to get an empty spot in the directory
        * if the given name does not exist already. The value of mode is
        * not important until we actually create the filelock.
        */
       if (!(mode & MODE_CREATEFILE)) {
           mode ^= MODE_CREATEFILE;
           createit = 1;
       } else
           createit = 0;
#endif
    } else
       nextpart++;

    /*
     * Are we at the end of the name? Then we are finished now. This works
     * because sde contains the directory entry of parentdir.
     */

    if (name[0] == '\0')
       goto exit;

    /*
     * If there is more name, we enter the directory, and here we get the
     * first entry.
     */

    sde.de_Sector = DirClusterToSector(sde.de_Msd.msd_Cluster);
    sde.de_Offset = 0;

    if ((sector = GetSec(sde.de_Sector)) == NULL)
       goto error;

    CopyMem(sector, &sde.de_Msd, (long) sizeof (struct MsDirEntry));
    OtherEndianMsd(&sde.de_Msd);
    FreeSec(sector);

    while (de) {
       switch (CompareNames(&sde.de_Msd, component)) {
       case CMP_FREE_SLOT:
           if (freesec == SEC_EOF) {
               freesec = sde.de_Sector;
               freeoffset = sde.de_Offset;
           }
           /* Fall through */
       case CMP_NOT_EQUAL:
           de = FindNext(&sde, createit);      /* Try next directory
                                                * entry */
           continue;
       case CMP_OK_DIR:
           if (name = nextpart) {
               /*
                * We want to keep locks on all directories between each
                * bottom directory and file lock, so we can easily find
                * the parent of a lock. There just seems to be a problem
                * here when we enter the 'subdirectories' . or .. , but
                * that in not so: MakeLock will detect that it has
                * already a lock on those, and NOT make :one/two the
                * parent of :one/two/.. .
                */
               newlock = MakeLock(parentdir, de, SHARED_LOCK);
               MSUnLock(parentdir);
               parentdir = newlock;
               goto newdir;
           }
           goto exit;
       case CMP_OK_FILE:
           if (nextpart) {
               error = ERROR_OBJECT_WRONG_TYPE;
               de = NULL;
           }
           goto exit;
       case CMP_END_OF_DIR:    /* means we will never find it */
           error = ERROR_OBJECT_NOT_FOUND;
           if (freesec == SEC_EOF) {
               freesec = sde.de_Sector;
               freeoffset = sde.de_Offset;
           }
           de = NULL;
           goto exit;
       }
    }

exit:
    if (de) {
       newlock = MakeLock(parentdir, &sde, mode);
    } else {
       newlock = NULL;
#ifndef READONLY
       if (createit &&         /* Do we want to make it? */
           error == ERROR_OBJECT_NOT_FOUND &&  /* does it not exist yet? */
           nextpart == NULL) { /* do we have the last part of the name */
           if (freesec != SEC_EOF) {   /* is there any room? */
               if (IDDiskState == ID_VALIDATED) {
                   error = 0;
                   setmem(&sde.de_Msd, sizeof (sde.de_Msd), 0);
                   sde.de_Sector = freesec;
                   sde.de_Offset = freeoffset;
                   /* ToMSName(sde.de_Msd.msd_Name, name); */
                   strncpy(sde.de_Msd.msd_Name, component, 8 + 3);
                   EmptyFileLock = MakeLock(parentdir, &sde, mode);
                   WriteFileLock(EmptyFileLock);
               } else
                   error = ERROR_DISK_WRITE_PROTECTED;
           } else
               error = ERROR_DISK_FULL;
       }
#endif
    }

error:
    MSUnLock(parentdir);

    return newlock;
}

/*
 * This routine DupLocks a file. This simply means incrementing the
 * reference count, if it was not an exclusive Lock.
 */

struct MSFileLock *
MSDupLock(fl)
struct MSFileLock *fl;
{
    if (fl == NULL)
       fl = RootLock;
    if (fl->msfl_Refcount <= 0) {
       error = ERROR_OBJECT_IN_USE;
       return NULL;
    } else {
       fl->msfl_Refcount++;
    }

    return fl;
}

/*
 * This routine DupLocks the parent of a lock, if there is one.
 */

struct MSFileLock *
MSParentDir(fl)
register struct MSFileLock *fl;
{
    if (fl == NULL || fl == RootLock) {
       error = ERROR_OBJECT_NOT_FOUND;
    } else if (fl->msfl_Parent)
       return MSDupLock(fl->msfl_Parent);

    return NULL;
}

/*
 * This routine UnLocks a file.
 */

int
MSUnLock(fl)
struct MSFileLock *fl;
{
#ifdef DEBUG
    debug(("MSUnLock %08lx: ", fl));
    PrintDirEntry(&fl->msfl_Msd);
#endif

    if (fl) {
       if (--fl->msfl_Refcount <= 0) {
           struct LockList *list;

           list = (struct LockList *) fl->msfl_Node.mln_Pred;
           Remove(fl);
           debug(("Remove()d %08lx\n", fl));

           /*
            * We may need to get rid of the LockList if it is empty. This
            * is the current LockList iff we are called from
            * MSDiskRemoved(). Please note that we are not even sure that
            * 'list' really is the list header, therefore the careful
            * test if fl refers to a volume label (root lock) which is
            * finally UnLock()ed. Because of the recursion, we only try to
            * free the LockList iff there is no parent anymore, since
            * otherwise list may be invalid by the time we use it.
            */
           if (fl->msfl_Parent) {
               MSUnLock(fl->msfl_Parent);
           } else {
               if ((fl->msfl_Msd.msd_Attributes & ATTR_VOLUMELABEL) &&
                   ((void *) list->ll_List.mlh_Head ==
                    (void *) &list->ll_List.mlh_Tail)
                   ) {
                   FreeLockList(list);
               }
           }
           FreeMem(fl, (long) sizeof (*fl));
       }
    }
    return DOSTRUE;
}

/*
 * This is (among other things) the inverse of ToMSName().
 */

void
ExamineDirEntry(msd, fib)
struct MsDirEntry *msd;
register struct FileInfoBlock *fib;
{
    register byte  *end,
                  *dot;

#ifdef DEBUG
    debug(("+ "));
    PrintDirEntry(msd);
#endif
    strncpy(&fib->fib_FileName[1], msd->msd_Name, 8);
    /* Keep at least one character, even a space, before the dot */
    dot = ZapSpaces(&fib->fib_FileName[2], &fib->fib_FileName[1 + 8]);
    dot[0] = ' ';
    strncpy(dot + 1, msd->msd_Ext, 3);
    dot[4] = '\0';
    end = ZapSpaces(dot, dot + 4);
    if (end > dot)
       dot[0] = '.';
    fib->fib_FileName[0] = strlen(&fib->fib_FileName[1]);

    fib->fib_DirEntryType =
       (msd->msd_Attributes & ATTR_DIR) ? FILE_DIR : FILE_FILE;
    fib->fib_Protection = 0;
    if (!(msd->msd_Attributes & ATTR_ARCHIVED))
       fib->fib_Protection |= FIBF_ARCHIVE;
    if (msd->msd_Attributes & ATTR_READONLY)
       fib->fib_Protection |= (FIBF_WRITE | FIBF_DELETE);
    if (msd->msd_Attributes & (ATTR_HIDDEN|ATTR_SYSTEM))
       fib->fib_Protection |= FIBF_HIDDEN;
    fib->fib_Size = msd->msd_Filesize;
    fib->fib_NumBlocks = (msd->msd_Filesize + Disk.bps - 1) / Disk.bps;
    ToDateStamp(&fib->fib_Date, msd->msd_Date, msd->msd_Time);
    fib->fib_Comment[0] = 0;
}

int
MSExamine(fl, fib)
struct MSFileLock *fl;
register struct FileInfoBlock *fib;
{
    if (fl == NULL)
       fl = RootLock;

    fib->fib_DiskKey = ((ulong) fl->msfl_DirSector << 16) | fl->msfl_DirOffset;
    fib->fib_EntryType = 0;    /* No ExNext called yet */
    ExamineDirEntry(&fl->msfl_Msd, fib);

    return DOSTRUE;
}

int
MSExNext(fl, fib)
struct MSFileLock *fl;
register struct FileInfoBlock *fib;
{
    word           sector = fib->fib_DiskKey >> 16;
    word           offset = (word) fib->fib_DiskKey;
    byte          *buf;

    if (fl == NULL)
       fl = RootLock;

    if (fib->fib_EntryType == 0) {
       fib->fib_EntryType = 1;
       if (fl->msfl_Msd.msd_Attributes & ATTR_DIR) {
           /* Enter subdirectory */
           sector = DirClusterToSector(fl->msfl_Msd.msd_Cluster);
           offset = 0;
       } else
           NextDirEntry(&sector, &offset);
    } else {
skip:
       NextDirEntry(&sector, &offset);
    }

    if (sector != SEC_EOF) {
       register struct MsDirEntry *msd;

       if (buf = GetSec(sector)) {
           msd = (struct MsDirEntry *) (buf + offset);
           if (msd->msd_Name[0] == '\0') {
               FreeSec(buf);
               goto end;
           }
           if (msd->msd_Name[0] & DIR_DELETED_MASK ||
               msd->msd_Name[0] == '.' ||      /* Hide "." and ".." */
               (msd->msd_Attributes & ATTR_VOLUMELABEL)) {
               FreeSec(buf);
               goto skip;
           }
           OtherEndianMsd(msd);/* Get correct endianness */
           fib->fib_DiskKey = ((ulong) sector << 16) | offset;
           ExamineDirEntry(msd, fib);
           OtherEndianMsd(msd);/* Get wrong endianness */
           FreeSec(buf);

           return DOSTRUE;
       }
    }
end:
    error = ERROR_NO_MORE_ENTRIES;
    return DOSFALSE;
}

/*
 * Convert AmigaDOS protection bits to messy attribute bits.
 */

long
MSSetProtect(parentdir, name, mask)
register struct MSFileLock *parentdir;
char      *name;
long      mask;
{
    register struct MSFileLock *lock;

    if (parentdir == NULL)
       parentdir = RootLock;

    lock = MSLock(parentdir, name, EXCLUSIVE_LOCK);
    if (lock) {
       /* Leave SYSTEM bit as-is */
       lock->msfl_Msd.msd_Attributes &= ATTR_SYSTEM;
       /* write or delete protected -> READONLY */
       if (mask & (FIBF_WRITE|FIBF_DELETE))
           lock->msfl_Msd.msd_Attributes |= (ATTR_READONLY);
       /* hidden -> hidden */
       if (mask & FIBF_HIDDEN)
           lock->msfl_Msd.msd_Attributes |= (ATTR_HIDDEN);
       /* archived=0 (default) -> archived=1 (default) */
       if (!(mask & FIBF_ARCHIVE))
           lock->msfl_Msd.msd_Attributes |= (ATTR_ARCHIVED);
       WriteFileLock(lock);
       MSUnLock(lock);
       return TRUE;
    }

    return FALSE;
}

int
CheckLock(lock)
register struct MSFileLock *lock;
{
    register struct MSFileLock *parent;

    if (lock) {
       while (parent = lock->msfl_Parent)
           lock = parent;
       if (lock != RootLock)
           error = ERROR_DEVICE_NOT_MOUNTED;
    }
    return error;
}

#ifndef READONLY

void
WriteFileLock(fl)
register struct MSFileLock *fl;
{
    debug(("WriteFileLock %08lx\n", fl));

    if (fl) {
       register byte  *block = GetSec(fl->msfl_DirSector);

       if (block) {
           CopyMem(&fl->msfl_Msd, block + fl->msfl_DirOffset,
                   (long) sizeof (fl->msfl_Msd));
           OtherEndianMsd(block + fl->msfl_DirOffset);
           MarkSecDirty(block);
           FreeSec(block);
       }
    }
}

void
UpdateFileLock(fl)
register struct MSFileLock *fl;
{
    struct DateStamp dateStamp;

    debug(("UpdateFileLock %08lx\n", fl));

    DateStamp(&dateStamp);
    ToMSDate(&fl->msfl_Msd.msd_Date, &fl->msfl_Msd.msd_Time, &dateStamp);
    WriteFileLock(fl);
}

#endif

struct LockList *
NewLockList(cookie)
void          *cookie;
{
    struct LockList *ll;

    if (ll = AllocMem((long) sizeof (*ll), MEMF_PUBLIC)) {
       NewList(&ll->ll_List);
       ll->ll_Cookie = cookie;
    } else
       error = ERROR_NO_FREE_STORE;

    return ll;
}

void
FreeLockList(ll)
struct LockList *ll;
{
    debug(("FreeLockList %08lx\n", ll));

    if (ll) {
       MayFreeVolNode(ll->ll_Cookie);  /* not too happy about this */
       FreeMem(ll, (long) sizeof (*ll));
       if (ll == LockList)     /* locks on current volume */
           LockList = NULL;
    }
}
