/*-
 * $Id: hanlock.c,v 1.30 90/06/04 23:17:18 Rhialto Rel $
 * $Log:	hanlock.c,v $
 * Revision 1.30  90/06/04  23:17:18  Rhialto
 * Release 1 Patch 3
 * 
 * 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 "dos.h"
#include "han.h"

#ifdef HDEBUG
#   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 */
	DATE_MIN,		/* msd_Date, 1/1/80 */
	0,			/* msd_Cluster */
	0			/* msd_Filesize */
    },
    ROOT_SEC,			/* 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;
{
    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 HDEBUG

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 HDEBUG
    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 HDEBUG
	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;
	    /*
	     * MSH::Command or ::Command?
	     */
	    if (name[0] == ':') {
		HandleCommand(name);
		error = ERROR_OBJECT_NOT_FOUND;

		return NULL;
	    }
	}
    }


    /*
     * 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 HDEBUG
    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 HDEBUG
    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;
{
#ifdef HDEBUG
    debug(("+ "));
    PrintDirEntry(msd);
#endif
    /*
     * Special treatment when we examine the root directory
     */
    if (fib->fib_DiskKey == (long)ROOT_SEC << 16) {
	strncpy(&fib->fib_FileName[1], msd->msd_Name, 8 + 3);
	(void) ZapSpaces(&fib->fib_FileName[2], &fib->fib_FileName[1 + 8 + 3]);
    } else {
	register byte  *end,
		       *dot;

	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_EntryType =
    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;
}

/*
 * We remember what we should do when we call ExNext with a lock on
 * a directory (enter or step over it) by a flag in fib_EntryType.
 * Unfortunately, the Commodore (1.3) List and Dir commands expect
 * that fib_EntryType contains the information that the documentation
 * (libraries/dos.h) specifies to be in fib_DirEntryType. Therefore
 * we use the low bit in fib_DiskKey instead. Yech.
 */

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 +
		       1;	/* 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 (offset & 1) {
	if (fl->msfl_Msd.msd_Attributes & ATTR_DIR) {
	    /* Enter subdirectory */
	    sector = DirClusterToSector(fl->msfl_Msd.msd_Cluster);
	    offset = 0;
	} else {
	    offset--;		/* Remember, it was odd */
	    NextDirEntry(&sector, &offset);
	}
    } else {
skip:
	NextDirEntry(&sector, &offset);
    }

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

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

	    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;
    }
}
