/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* |_o_o|\\ Copyright (c) 1987 The Software Distillery.  All Rights Reserved */
/* |. o.| || This program may not be distributed without the permission of   */
/* | .  | || the authors:                                          BBS:      */
/* | o  | ||   John Toebes     Dave Baker    John Mainwaring                 */
/* |  . |//                                                                  */
/* ======                                                                    */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Change History                                                            */
/*   10-JUN-88 JAT - Corrected code which handles file extension blocks      */
/*                   the AmigaDos technical reference manual is incorrect in */
/*                   its description of the HighSeq and DataBlockCount fields*/
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* AmigaDos 1.2 support routines */

#include "handler.h"

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: IsSameName                                                     */
/* Purpose: Compare a BSTR to a string/length for a directory match        */
/*          NOTE: Case is insensitive                                      */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int IsSameName(bstr, name, len)
register char * bstr;
register char * name;
register int len;
{
   register char c1, c2;

   /* Make sure they are the same length */
   if (len != *bstr++)
      {
      BUG(("Length mismatch B=%ld n=%ld\n", *--bstr, len));
      return(0);
      }

   /* Compare all the characters (ignoring case) */
   while(len--)
      {
      /* Get the next characters to compare */
      c1 = *name++; c2 = *bstr++;
      /* Convert to lower case */
      if ((c1 >= 'A') && (c1 <= 'Z')) c1 += ('a'-'A');
      if ((c2 >= 'A') && (c2 <= 'Z')) c2 += ('a'-'A');
      /* and make sure they match */
      if (c1 != c2) return(0);
      }

   /* Seems to work so let it pass... */
   return(1);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: NextKey                                                        */
/* Purpose: Find next key in directory rootkey starting at position key.   */
/* Note:    Key == 0 searches for first key.  Traverses all hash chains.   */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
KEY NextKey(global, key, info, rootkey)
GLOBAL global;
struct FileInfoBlock *info;
KEY key, rootkey;
{
   KEY limit;
   int hashval;
   struct DirBlock *block;

   limit = 0;

   if (key == 0)
      hashval = 0;
   else
      {
      /* Note that we at least can count on the directory entries being in  */
      /* ascending order.  This has two effects.  First it means that we are*/
      /* not running wild with back and forth seeks.  Secondly it means that*/
      /* if something is deleted, we can figure out what entries we already */
      /* gave them by just a simple key comparison                          */

      /* Already doing a directory, might as well continue on */
      /* first locate the last block they looked at           */
      if ((block = (struct DirBlock *)GetBlock(global, key)) == NULL)
         return(0);

      hashval = hash(info->fib_FileName+1, info->fib_FileName[0]);

      /* Now make sure it is the same block that we examined last time  */
      /* Make sure it is a directory entry belonging to the same parent */
      /* it isn't safe to compare the name as case might have changed   */
      /* although we could compare the hash for the name.               */
      if ((block->db_Type != T_SHORT)   ||
          (block->db_Parent != rootkey) ||
          (hash(block->db_Name+1, block->db_Name[0]) != hashval))
         {
         /* Well, something is certainly wrong here.  We have to go back */
         /* and start at the beginning of the current hash chain         */
         limit = key;
         key = 0;
         }
      else
         {
         /* If there is nothing on the chain then tell them to look at the */
         /* next hash value chain                                          */
         key = block->db_HashChain;
         hashval++;
         }
      }

   /* did we find something or will we have to look again */
   if (key == 0)
      {
      /* OK, now we need to go to the root and search the hash chains */
      if ((block = (struct DirBlock *)GetBlock(global,rootkey)) == NULL)
         /* Funny, couldn't find the parent directory so just fail the   */
         /* search for the next key                                      */
         return(0);

      while(hashval < HASHTABLESIZE)
         {
         if (key = block->db_HashTable[hashval++])
            {
            if (key > limit)
               /* Had a hit on the key so return it.                     */
               break;

            /* Walk down the list until we hit an entry greater than the */
            /* limit or the end of the list                              */
            while((key != 0) && (key < limit))
               {
               if ((block = (struct DirBlock *)GetBlock(global, key)) == NULL)
                  return(0);
               key = block->db_HashChain;
               }

            /* Relocate the parent block */
            limit = 0;
            if ((block = (struct DirBlock *)GetBlock(global,rootkey)) == NULL)
               /* Funny, couldn't find the parent directory so just fail the */
               /* search for the next key                                    */
               return(0);
            }
         }
      }
   /* if we didn't get anything useful, key is 0 */
   return (key);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: ParentKey                                                      */
/* Purpose: Find key of parent directory of input key                      */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
KEY ParentKey(global, key)
GLOBAL global;
KEY key;
{
   struct DirBlock *block;

   if (key == 0 ||
      (block = (struct DirBlock *)GetBlock(global,key)) == NULL)
      return(0);

   return(block->db_Parent);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: hash                                                           */
/* Purpose: Compute the hash value of a name                               */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int hash(name, len)
unsigned char *name;   /* pointer to str to hash on */
int len;
{
int c;
int hashval;

   /* Now compute a hashvalue for the result */
   hashval = len;
   while(len--)
      {
      c = *name++;
      if (c >= 'a' && c <= 'z')
         c = c - 'a' + 'A';
        hashval = ((hashval * 13 + c ) & 0x7ff);
      }

   return(hashval % HASHTABLESIZE);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: Parse                                                          */
/* Purpose: Separate out the next node in a file name                      */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int parse(name, len, buf)
char **name;
int *len;
register char *buf;
{
register int left;
register int c;
register int curlen;

   curlen = 0;
   left = *len;

   memset(buf, 0, 32);

   while((--left >= 0) && ((c = *(*name)++) != '/') )
      {
      if (c == ':')
         {
         /* got a volume name terminator.  We need to throw everything away */
         curlen = 0;
         buf[++curlen] = c;
         break;
         }
      else
         buf[++curlen] = c;
      }

   /* update our parse length */
   if (left < 0)
      *len = 0;
   else
      *len = left;

   buf[0] = curlen;

   BUGLSTR("Parsed Name:", buf+1, curlen);

   /* Now compute a hashvalue for the result */
   return(hash(buf+1, curlen));
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: GetProtect                                                     */
/* Purpose: Return the protection bits associated with a file entry        */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int GetProtect(global,key)
GLOBAL global;
KEY key;
{
struct DirBlock *block;

   block = (struct DirBlock *)GetBlock(global,key);
   if (block == NULL)
      return(FIBF_WRITE|FIBF_READ|FIBF_DELETE|FIBF_EXECUTE);

   return(block->db_Protect);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: SetProtect                                                     */
/* Purpose: Set the protection bits for a file                             */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int SetProtect(global, key, prot)
GLOBAL global;
KEY key;
long prot;
{
struct DirBlock *block;

   if (key == 0 ||
       (block = (struct DirBlock *)ModBlock(global,key)) == NULL)
      return(DOS_FALSE);

   block->db_Protect = prot;
   return(DOS_TRUE);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: GetType                                                        */
/* Purpose: Determine the type of a file entry                             */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int GetType(global, key)
GLOBAL global;
KEY key;
{
struct DirBlock *block;

   block = (struct DirBlock *)GetBlock(global,key);
   if (block == NULL)
      return(0);  /* A truely invalid type */

   BUG(("GetType: addr %08lx for key %ld\n", block, key));
   return(block->db_SecondaryType);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: SetCommentStr                                                  */
/* Purpose: Set the comment string for a file                              */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int SetCommentStr(global, key, comment)
GLOBAL global;
KEY key;
char *comment;
{
   struct DirBlock *block;

   if (key == 0 ||
       (block = (struct DirBlock *)ModBlock(global,key)) == NULL)
      return(DOS_FALSE);

   memcpy((char *)block->db_Comment, comment, (*comment)+1 );
   return(DOS_TRUE);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: GetInfo                                                        */
/* Purpose: Copy the file information for a dos info block                 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int GetInfo(global, key, info)
GLOBAL global;
KEY key;
register struct FileInfoBlock *info;
{
   register struct DirBlock *block;
   char *p;

   /* get the block associated with the key */
   if ((key == 0) ||
       ((block = (struct DirBlock *)GetBlock(global,key)) == NULL))
      return(DOS_FALSE);

   /* It would be prudent here to check the type of block and see that it */
   /* Is a true file entry block                                          */

   /* Now copy over the information they want */
   info->fib_DiskKey        = key;
   info->fib_EntryType      =
   info->fib_DirEntryType   = block->db_SecondaryType;

   /* Set the entry type to 2 to make dir happy */
   if (info->fib_EntryType == 1)
      info->fib_EntryType      =
      info->fib_DirEntryType   = 2;

   p = (char *)block->db_Name;

   memcpy(info->fib_FileName, p, (*p)+1);
   info->fib_Protection     = block->db_Protect;
   info->fib_Size           = block->db_Size;
   info->fib_NumBlocks      = (block->db_Size + (BLOCKSIZE-1))/BLOCKSIZE;
   info->fib_Date.ds_Days   = block->db_Days;
   info->fib_Date.ds_Minute = block->db_Minutes;
   info->fib_Date.ds_Tick   = block->db_Ticks;

   p = (char *)block->db_Comment;
   memcpy(info->fib_Comment,  p, (*p)+1 );

#if 0
   BUG(("Done setting up info for key %ld\n",info->fib_DiskKey));
   BUG(("Entry=%ld Protect=%ld Size=%ld Blocks=%ld\n", info->fib_DirEntryType, info->fib_Protection, info->fib_Size, info->fib_NumBlocks));
   BUG(("Day=%ld Min=%ld Tick=%ld\n", info->fib_Date.ds_Days, info->fib_Date.ds_Minute, info->fib_Date.ds_Tick));
   BUGBSTR("Name :", info->fib_FileName);
   BUGBSTR("Comment :", info->fib_Comment);
#endif

   return(DOS_TRUE);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: FindDir                                                        */
/* Purpose: Find a Directory associated with a file and parse out the name */
/*          of the file from the file string                               */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
KEY FindDir(global, key, namep, lenp)
GLOBAL global;
KEY key;
char **namep;
int *lenp;
{
int len;
char *p, *t;
register struct DirBlock *block;
char namebuf[32];
int hashval;

   len = *lenp;
   p = *namep + len - 1;

   /* Skip to the last colon or / in the name */
   while ((*p != ':') && (*p != '/') && len) { p--; len--; }
   *lenp -= len;

   t = p;
   if (len) t++;
   /* Are they attempting to access '.' or '..' ? */
   if (*t == '.' &&
       ((*lenp == 1) ||
        ((*lenp == 2) && (*(t+1) == '.'))))
      {
      /* Yes, so treat it as part of the directory */
      len += *lenp;
      *lenp = 0;
      }

   BUGLSTR("Locate: Path is :", *namep, len);

   /* Now run through the string searching the directory */
   while(len && key)
      {
      /* first go to the root of the directory they asked for */
      block = (struct DirBlock *)GetBlock(global,key);

      if (block == NULL)
         {
         BUG(("FindDir: Error accessing block %ld\n", key));
         return(0);
         }

      if ((block->db_SecondaryType != ST_ROOT) &&
          (block->db_SecondaryType != ST_USERDIR))
         {
         /* they want to traverse a file as a directory - give them hell */
         global->pkt->dp_Res2 = ERROR_OBJECT_WRONG_TYPE;
         return(0);
         }

      /* break down the next subcomponent of the name */
      hashval = parse(namep, &len, namebuf);

      BUGBSTR("Parsed Name :", namebuf);
      BUG(("Hashval = %ld\n", hashval));

      /* Did they specify the root directory ? */
      if ((namebuf[0] == 1) && (namebuf[1] == ':'))
         {
         key = global->Root;
         BUG(("Moving to root: %ld\n", key));
         }

      else if ((namebuf[0] == 0) ||
               ((namebuf[0] == 2) && (namebuf[1]=='.') && (namebuf[2] == '.')))
         {
         key = block->db_Parent;
         BUG(("Moving to paremt: %ld\n", key));
         }

      else if ((namebuf[0] != 1) || (namebuf[1] != '.'))
         {
         /* Search the current block for the given name */
         for( key = block->db_HashTable[hashval]; key != 0;
              key = block->db_HashChain)
            {
            block = (struct DirBlock *)GetBlock(global,key);
            if (block == NULL)
               {
               BUG(("FindDir: Can't get block %ld\n", key));
               return(0);
               }

            BUGBSTR("Block name:", block->db_Name);
            if (IsSameName(block->db_Name, (char *)namebuf+1, namebuf[0]))
               break;
            }
         }
      }

   if (key == 0)
      global->pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND;

   BUG(("locate: key is %ld\n", key));
   return(key);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: FindEntry                                                      */
/* Purpose: Locate an entry in a directory                                 */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
KEY FindEntry(global, key, name, len)
GLOBAL global;
KEY key;
char *name;
int len;
{
int hashval;
struct DirBlock *block;

   BUGLSTR("FindEntry: Name is ", name, len);

   if (len == 0)
      {
      BUG(("Length is zero, returning key=%ld\n", key));
      return(key);
      }

   hashval = hash(name, len);
   /* first go to the root of the directory they asked for */
   if ((block = (struct DirBlock *)GetBlock(global,key)) == NULL)
      return(0);

   BUG(("Starting search for hash=%ld from key=%ld\n", hashval, key));
 
   if ((block->db_SecondaryType != ST_ROOT) &&
       (block->db_SecondaryType != ST_USERDIR))
      {
      BUG(("Secondary type %ld bad for key %ld\n", block->db_SecondaryType, key));
      /* they want to traverse a file as a directory - give them hell */
      global->pkt->dp_Res2 = ERROR_OBJECT_WRONG_TYPE;
      return(0);
      }

   global->prevkey = 0;

   /* Search the current block for the given name */
   for( key = block->db_HashTable[hashval]; key != 0;
        key = block->db_HashChain)
      {
      if ((block = (struct DirBlock *)GetBlock(global,key)) == NULL)
         return(0);

      BUGBSTR("Block name:", block->db_Name);
      if (IsSameName(block->db_Name, name, len))
         break;

      global->prevkey = key;
      }

   if (!key)
      global->pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND;

   return(key);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: MakeEntry                                                      */
/* Purpose: Create a new entry for a file                                  */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
KEY MakeEntry(global, key, name, len, type)
GLOBAL global;
KEY key;
char *name;
int len;
int type;
{
int hashval;
KEY newkey, nextkey;
struct DirBlock *block;
LONG *zeroptr;
zeroptr = 0;

   BUGLSTR("MakeEntry: name ", name, len);

   hashval = hash(name, len);

   /* first go to the root of the directory they asked for */
   block = (struct DirBlock *)GetBlock(global,key);

   /* When all else fails pop up to let it be handled */
   if (block == NULL) return(0);

   if ((block->db_SecondaryType != ST_ROOT) &&
       (block->db_SecondaryType != ST_USERDIR))
      {
      /* they want to traverse a file as a directory - give them hell */
      global->pkt->dp_Res2 = ERROR_OBJECT_WRONG_TYPE;
      return(0);
      }

   /* Get us a block to put the newly created item */
   if (!(newkey = AllocateBlock(global, key, type)))
      {
      BUG(("MakeEntry:Unable to allocate a block %ld\n", newkey));
      global->pkt->dp_Res2 = ERROR_DISK_FULL;
      return(0);
      }

   /* try to put block into right place in parent directory */
   if ((nextkey = LinkDir(global, key, newkey, hashval)) == -1)
      {
      FreeBlock(global, newkey);
      return(0); /* invalid key - admission of failure */
      }
   BUG(("MakeEntry: new entry %d points to successor %d\n", newkey, nextkey)); 

   /* Find the newly created block and fill it in */
   block = (struct DirBlock *)ModBlock(global, newkey);
   if (block == NULL)
      {
      /* Actually getting an error here is quite fatal because we already   */
      /* modified the directory entry.  We need to figure out a way to back */
      /* out of the creation.  The only situation that should cause this is */
      /* if the allocated block gets flushed out to disk and we are unable  */
      /* to read it in.  In this case it is quite likely that the media is  */
      /* not usable.  However we eventually may have to handle this case    */
      FreeBlock(global, newkey);
      return(0);
      }
   memset((char *)block, 0, sizeof(struct DirBlock));

   BUG(("MakeEntry: New entry %ld at %08lx\n", newkey, block));
   block->db_HashChain = nextkey;

   /* Fill in the remainder of the information */
   block->db_Type              = T_SHORT;
   block->db_OwnKey            = newkey;
   block->db_SecondaryType     = type;
   memcpy(((char *)block->db_Name)+1, name, len);
   *(char *)&block->db_Name[0] = len;
   block->db_Parent            = key;
   SetDateInfo(global, block);
   return(newkey);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: LocateEntry                                                    */
/* Purpose: Find an file entry                                             */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
KEY LocateEntry(global, key, name)
GLOBAL global;
KEY key;
char *name;
{
int len;

len = *name++;

BUGLSTR("LocateEntry: Full Name is:", name, len);
if (!(key = FindDir(global, key, &name, &len)))
   return(0);

BUGLSTR("LocateEntry: name ", name, len);

return(FindEntry(global, key, name, len));
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: CreateEntry                                                    */
/* Purpose: Create a new file entry                                        */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
KEY CreateEntry(global, key, name, mode, type)
GLOBAL global;
KEY key;
char *name;
int mode;
int type;
{
int len;
KEY newkey;
struct DirBlock *block;

len = *name++;
BUGLSTR("CreateEntry: Name ", name, len);

if (!(key = FindDir(global, key, &name, &len)))
   return(0);

BUGLSTR("CreateEntry: Parsed Name ", name, len);
if (newkey = FindEntry(global, key, name, len))
   {
   /* Entry already exists, may need to blow it away */
   if (mode == NEWFILE)
      {
      global->pkt->dp_Res2 = ERROR_OBJECT_EXISTS;
      return(0);
      }
#if 0
   /* Although logical course of action, the current filing system seems */
   /* to not exactly work this way.                                      */
   if (GetProtect(global, newkey) & FIBF_DELETE)
      {
      global->pkt->dp_Res2 = ERROR_DELETE_PROTECTED;
      return(0);
      }
#endif
   if (GetProtect(global, newkey) & FIBF_WRITE)
      {
      global->pkt->dp_Res2 = ERROR_WRITE_PROTECTED;
      return(0);
      }
   if ((GetType(global, newkey) == ST_USERDIR) || (type == ST_USERDIR))
      {
      global->pkt->dp_Res2 = ERROR_OBJECT_WRONG_TYPE;
      return(0);
      }
   FreeDataChain(global, newkey);

   /* Also we need to update the name information in the file            */
   /* This allows them to change the case of the name.  Note that by     */
   /* doing this in place we preserve all the comment information and    */
   /* attributes of the old entry.  Also since the entry doesn't move on */
   /* disk we can keep it in the same plae on the hash chain.            */
   block = (struct DirBlock *)ModBlock(global, newkey);
   if (block != NULL)
      {
      memcpy(((char *)block->db_Name)+1, name, len);
      *(char *)&block->db_Name[0] = len;

      /* Clear the archive bit so backup programs know it has been modified */
      block->db_Protect &= ~FIBF_ARCHIVE;
      }
   }
else
   {
   /* We have a brand new file here, so make us an entry for it */
   newkey = MakeEntry(global, key, name, len, type);
   BUG(("CreateEntry: newkey is %ld\n", newkey));
   }
return(newkey);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: FreeDataChain                                                  */
/* Purpose: Truncate the data chain for a file                             */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int FreeDataChain(global, basekey)
GLOBAL global;
KEY basekey;
{
   KEY key, savekey;
   long i;
   struct FileBlock *block;

   key = basekey;
   while(key)
      {
      if ((block = (struct FileBlock *)GetBlock(global, key)) == NULL)
         return(0);

      for (i = 0; i < block->fb_HighSeq; i++)
          FreeBlock(global, block->fb_DataBlock[(BLOCKSPERENTRY-1)-i]);
      savekey = block->fb_Extension;
      BUG(("FreeDataChain: Base:%ld Key:%ld next:%ld\n", basekey, key, savekey));
      if (key != basekey)
         FreeBlock(global, key);
      key = savekey;
      }

   if ((block = (struct FileBlock *)ModBlock(global, basekey)) == NULL)
      {
      return(0);
      }

   block->fb_FirstData = block->fb_DataBlockCount = block->fb_HighSeq = 0;
   block->fb_Extension = 0;
   block->fb_Size = 0;
   return(1);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: SeekDataChain                                                  */
/* Purpose: Locate a block in a file data chain                            */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
KEY SeekDataChain(global, efh, num)
GLOBAL global;
struct EFileHandle *efh;
long num;
{
   struct FileBlock *block;
   KEY key;
   long off;
   long entryblock;

   if (num-- <= 0)
      {
      BUG(("Attempt to seek to %ld on %08lx\n", num+1, efh));
      global->pkt->dp_Res2 = ERROR_SEEK_ERROR;
      return(0);
      }

   entryblock = num/BLOCKSPERENTRY;
   BUG(("SeekDataChain: num=%ld entry=%ld cur=%ld\n",num,entryblock,efh->efh_CurExtBlock));

   if (entryblock != efh->efh_CurExtBlock)
      {
      /* Are we seeking forward? */
      BUG(("Forward seek\n"));
      if (entryblock > efh->efh_CurExtBlock)
         {
         entryblock -= efh->efh_CurExtBlock;
         key = efh->efh_ExtBlockKey;
         efh->efh_CurExtBlock += entryblock;
         }
      else
         {
         BUG(("Must seek from beginning\n"));
         /* Need to seek from beginning to target */
         efh->efh_CurExtBlock = entryblock;
         key = efh->efh_Lock->fl_Key;
         }
      while(key && entryblock--)
         {
         if ((block = (struct FileBlock *)GetBlock(global, key)) == NULL)
            return(0);
         key = block->fb_Extension;
         }
      efh->efh_ExtBlockKey = key;
      }
   else
      key = efh->efh_ExtBlockKey;

   if (key)
      {
      off = num - (efh->efh_CurExtBlock * BLOCKSPERENTRY);

      if ((block = (struct FileBlock *)GetBlock(global, key)) == NULL)
         return(0);

      if (off >= block->fb_HighSeq)
         {
         BUG(("SeekDataChain: %ld Out of range %ld for %ld\n", off, block->fb_HighSeq, off));
         key = 0;
         }
      else
         key = block->fb_DataBlock[(BLOCKSPERENTRY-1) - off];
      }

   BUG(("SeekDataChain: Returning block %ld\n", key));
   return(key);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: AppendDataChain                                                */
/* Purpose: Allocate a new block to extend a file data chain               */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
KEY AppendDataChain(global, efh, highseq)
GLOBAL global;
struct EFileHandle *efh;
   long highseq;
{
   KEY key, blockkey, newkey;
   struct FileBlock *block;
   struct DataBlock *data;
   KEY firstdata, filekey;

   filekey = efh->efh_Lock->fl_Key;
   if ((block = (struct FileBlock *)GetBlock(global, filekey)) == NULL)
      {
      BUG(("Couldn't locate the base node for key %ld\n", filekey))
      return(0);
      }

   firstdata = block->fb_FirstData;
   if (highseq)
      {
      key = SeekDataChain(global, efh, highseq);
      if (key == 0)
         {
         /* Too far past end of file, issue an error */
         BUG(("Append Error: efh:%08lx seq=%ld\n", efh, highseq));
         global->pkt->dp_Res1 = DOS_FALSE;
         global->pkt->dp_Res2 = ERROR_SEEK_ERROR;
         return(0);
         }
      }
   else
      key = filekey;

   /* Get the block to put the data into */
   if ((newkey = AllocateBlock(global, key, T_DATA)) == 0)
      return(0);

   BUG(("Append: New data block #%ld at %ld Ext = %ld\n", highseq, newkey, efh->efh_ExtBlockKey));
   if ((block = (struct FileBlock *)
                ModBlock(global, efh->efh_ExtBlockKey)) == NULL)
      goto appenderr;

   if (block->fb_HighSeq != BLOCKSPERENTRY)
      {
      /* Will fit in the current file chain so put it there and go on */
      block->fb_DataBlock[(BLOCKSPERENTRY-1)-block->fb_HighSeq] = newkey;
      block->fb_HighSeq++;
      }
   else
      {
      /* need to allocate another extension chain */
      BUG(("Need to allocate extension\n"));
      if ((blockkey =
           AllocateBlock(global, efh->efh_ExtBlockKey, T_SHORT)) == 0)
         goto appenderr;

      /* Succeeded so initialize the new extension chain */
      if ((block = (struct FileBlock *)
                   ModBlock(global, efh->efh_ExtBlockKey)) == NULL)
         goto initerr;

      block->fb_Extension = blockkey;

      if ((block = (struct FileBlock *)ModBlock(global, blockkey)) == NULL)
         {
         /* odds of this situation are extremely low since allocateblock */
         /* was supposed to set up the sitution, but let's be safe       */
initerr:
         FreeBlock(global, blockkey);
appenderr:
         FreeBlock(global, newkey);
         return(0);
         }

      memset((char *)block, 0, sizeof(struct FileBlock));
      block->fb_Type           = T_LIST;
      block->fb_OwnKey         = blockkey;
      block->fb_DataBlockCount = 0;
      block->fb_HighSeq        = 1;
      block->fb_FirstData      = firstdata;
      block->fb_Parent         = filekey;
      block->fb_Extension      = 0;
      block->fb_DataBlock[BLOCKSPERENTRY-1] = newkey;
      block->fb_SecondaryType  = ST_FILE;

      efh->efh_CurExtBlock++;
      efh->efh_ExtBlockKey = blockkey;
      }

   /* Go back and put the forward chain links in */
   if ((block = (struct FileBlock *)ModBlock(global, key)) == NULL)
      {
      BUG(("Unable to mod previous block %ld\n", key));
      return(0);
      }

   block->fb_FirstData = newkey;

   /* Now lastly we need to set up the new data block                 */
   if ((data = (struct DataBlock *)ModBlock(global, newkey)) == NULL)
      {
      BUG(("Unable to locate the new data block\n"));
      return(0);
      }

   memset((char *)data, 0, sizeof(struct DataBlock));
   data->db_Type = T_DATA;
   data->db_Header = filekey;
   data->db_SeqNum = highseq+1;
   data->db_DataSize = 0;  /* Since there is nothing in it yet */
   data->db_NextData = 0;  /* This is the last block in the file */
   return(newkey);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: PUTDATA                                                        */
/* Purpose: Stuff file data into a data block                              */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void PUTDATA(block, pos, data, len)
char *block;
long pos;
char *data;
long len;
{
struct DataBlock *db = (struct DataBlock *)block;
memcpy(((char *)db->db_Data) + pos, data, len);
if ((len+pos) > db->db_DataSize)
   db->db_DataSize = len+pos;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: GETDATA                                                        */
/* Purpose: Extract file data from a data block                            */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void GETDATA(data, len, block, pos)
char *data;
long len;
char *block;
long pos;
{
struct DataBlock *db = (struct DataBlock *)block;
memcpy(data, ((char *)db->db_Data) + pos, len);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: GetFileSize                                                    */
/* Purpose: Determine the current length of a file                         */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
long GetFileSize(global, efh)
GLOBAL global;
EFH efh;
{
struct DirBlock *db;

if ((db = (struct DirBlock *)GetBlock(global, efh->efh_Lock->fl_Key)) == NULL)
   /* Couldn't read the directory entry so let them know */
   return(-1);

return(db->db_Size);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: UpdateFile                                                     */
/* Purpose: Update the directory entry to indicate the current file length */
/*          The date time stamp is also updated                            */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int UpdateFile(global, efh, blknum, blkpos)
GLOBAL global;
EFH efh;
long blknum;
long blkpos;
{
struct DirBlock *db;

BUG(("UpdateFile: %ld to %ld/%ld\n", efh->efh_Lock->fl_Key, blknum, blkpos));
if ((db = (struct DirBlock *)ModBlock(global, efh->efh_Lock->fl_Key)) == NULL)
   /* Couldn't read the directory entry so let them know */
   return(0);

db->db_Size     = ((blknum-1)*BLOCKSIZE) + blkpos;

/* Clear the archive bit so the backup programs know it has been modified */
db->db_Protect &= ~FIBF_ARCHIVE;

SetDateInfo(global, db);

return(1);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: BlockKey                                                       */
/* Purpose: Return the key associated with a specific block in a file.     */
/*          If the block is one past the end of the file it will extend    */
/*          the file by one block                                          */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
KEY BlockKey(global, efh, seq, writemode)
GLOBAL global;
EFH efh;
long seq;
int writemode;
{
KEY key;

key = SeekDataChain(global, efh, seq);

/* If we didn't find an already existing block when we are attempting to   */
/* write to the block we want to append to the end of the file.  Note that */
/* the test of seq is just an additional paranoid protection               */
if (writemode && (key == 0) && seq)
   {
   BUG(("BlockKey: Extending file to %ld\n", seq));
   key = AppendDataChain(global, efh, seq-1);
   }

BUG(("BlockKey: Returning %ld\n", key));
return(key);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: SetDateInfo                                                    */
/* Purpose: Set the modified date/time stamp on the current entry          */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void SetDateInfo(global, db)
GLOBAL global;
struct DirBlock *db;
{
/* Note that this presumes DOS is around to do the data stamping */
#if 0
DateStamp((long *)&db->db_Days);
#else
GetDateStamp(global, (struct DateStamp *)&db->db_Days);  /* Just to experiment */
#endif
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: RenameDisk                                                     */
/* Purpose: Change the current disk label                                  */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int RenameDisk(global, name)
GLOBAL global;
char *name;
{
struct RootBlock *root;
int len;

   if ((global->volume == NULL) ||
       ((root = (struct RootBlock *)global->Root) == NULL))
      return(DOS_FALSE);

   /* Make sure we don't get a name to big */
   len = *name++;
   if (len >= NAMESIZE)
      len = NAMESIZE - 1;

   root->rb_Name[0] = len;
   memcpy(root->rb_Name+1, name, len);
   
   return(DOS_TRUE);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: DeleteEntry                                                    */
/* Purpose: Remove an directory entry                                      */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int DeleteEntry(global, key, name)
GLOBAL global;
KEY key;
char *name;
{
int len;
int i;
KEY parent, nextkey;
struct DirBlock *block;

len = *name++;

/* First see if the entry really exists */
if (!(parent = FindDir(global, key, &name, &len)))
   return(DOS_FALSE);

/* Ok, we got the directory, is the file or entry there ? */
if (!(key = FindEntry(global, parent, name, len)))
   return(DOS_FALSE);

/* Make sure they haven't protected it against deletion */
if (GetProtect(global, key) & FIBF_DELETE)
   {
   global->pkt->dp_Res2 = ERROR_DELETE_PROTECTED;
   return(DOS_FALSE);
   }

/* Gee, it is there, now we need to go through and delete it */
/* First we get rid of the data chain for the file */
switch(GetType(global, key)) {
   case ST_USERDIR:
      if ((block = (struct DirBlock *)GetBlock(global, key)) == NULL)
         return(DOS_FALSE);

      /* Make sure the directory is empty first */
      for (i= 0; i < HASHTABLESIZE; i++)
         if (block->db_HashTable[i] != 0)
            {
            global->pkt->dp_Res2 = ERROR_DIRECTORY_NOT_EMPTY;
            return(DOS_FALSE);
            }
      break;

   case ST_FILE:
      if (!FreeDataChain(global, key))
         {
         BUG(("Unable to release the data chain for the file %ld\n", key));
         return(DOS_FALSE);
         }
      break;

   default:
      BUG(("What type of a thing is this they are deleting\n"));
      global->pkt->dp_Res2 = ERROR_OBJECT_WRONG_TYPE;
      return(DOS_FALSE);
/*    break;   compiler doesn't really like this */
   }

/* Now we want to remove the entry from the directory */
/* to do this we want to find either the parent or the previous entry */
/* When we called FindEntry, it filled in global->prevkey with the    */
/* previous hash chain entry if we were in the list OR it is 0 which  */
/* means that we need to unlink it from our parent                    */
/* First we need to get the next entry in the hash chain */
if ((block = (struct DirBlock *)ModBlock(global, key)) == NULL)
   return(DOS_FALSE);

/* Just to be nice, mark it as deleted */
block->db_Type |= T_DELETED;
nextkey = block->db_HashChain;

if (global->prevkey)
   {
   if ((block = (struct DirBlock *)ModBlock(global, global->prevkey)) == NULL)
      return(DOS_FALSE);
   block->db_HashChain = nextkey;
   if ((block = (struct DirBlock *)ModBlock(global, parent)) == NULL)
      return(DOS_FALSE);
   }
else
   {
   if ((block = (struct DirBlock *)ModBlock(global, parent)) == NULL)
      return(DOS_FALSE);
   block->db_HashTable[hash(name, len)] = nextkey;
   }

/* We are now at the parent block, so update the date time information on it */
SetDateInfo(global, block);

/* Also, mark the directory block for the file as empty */
FreeBlock(global, key);

return(DOS_TRUE);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: RenameEntry                                                    */
/* Purpose: Implement most of ActRenameEntry.  Check for valid parameters  */
/*          then relink entry block to new directory location.             */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int RenameEntry(global, fromkey, tokey, fromname, toname)
GLOBAL global;
KEY fromkey, tokey;
char *fromname, *toname;
{
struct DirBlock *block;
KEY parentkey, prevkey, nextkey, dirkey, ancestor;
int hashval, len;

   if (!(fromkey = LocateEntry(global, fromkey, fromname)))
      return(DOS_FALSE);
   /* get some useful info while it's available, we're sure to need it 	*/
   prevkey = global->prevkey;
   if ((block = (struct DirBlock *)GetBlock(global, fromkey)) == NULL)
      return(DOS_FALSE);
   parentkey = block->db_Parent;
   hashval = hash(&block->db_Name[1], block->db_Name[0]);
   nextkey = block->db_HashChain;


   /* Now see if we can find the 'to' file.  It shouldn't exist, except	*/
   /* if we are only using rename to change the case of the name.	*/
   len = *toname++;
   if (!(dirkey = FindDir(global, tokey, &toname, &len)))
      {
      global->pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND;  /* or something..	*/
      return(DOS_FALSE);
      }
   if(tokey = FindEntry(global, dirkey, toname, len))
      /* check this is not same file name with just case changed	*/
      if (tokey != fromkey)
	 {
	 global->pkt->dp_Res2 = ERROR_OBJECT_EXISTS;
   	 return(DOS_FALSE);
	 }

   /* Check to see if we're trying to rename an ancestor to a		*/
   /* descendant, which has the unfortunate effect of orphaning it.	*/
   /* Can save some thrashing if source & dest are in same directory	*/
   if (dirkey != parentkey)
      {
      ancestor = dirkey;
      BUG(("RenameEntry: Ancestor = %d\n", ancestor));
      while (ancestor)
         {
         if (ancestor == fromkey)
            {
	    global->pkt->dp_Res2 = ERROR_OBJECT_IN_USE;  /* Better ideas?  */
	    return(DOS_FALSE);
	    }
         if ((block = (struct DirBlock *)GetBlock(global, ancestor)) == NULL)
            /* haven't located anything oedipal, nothing can be wrong, can */
	    /* be wrong, can be wrong...				   */
            break;
         ancestor = block->db_Parent;
         }
      BUG(("RenameEntry: Root = %d\n", ancestor));
      }

   /* Now ready to unlink the 'from' entry from its directory.		   */

   BUG(("RenameEntry: parentkey=%d, prevkey=%d, nextkey=%d\n",
           parentkey, prevkey, nextkey));

   if (prevkey)
      {
      if ((block = (struct DirBlock *)ModBlock(global, prevkey))
                 == NULL)
         return(DOS_FALSE);
      block->db_HashChain = nextkey;
      }
   else
      {
      if ((block = (struct DirBlock *)ModBlock(global, parentkey)) == NULL)
         return(DOS_FALSE);
      BUG(("  hashval = %d\n", hashval));
      block->db_HashTable[hashval] = nextkey;
      }

   /* The disk seems to be writable - let's hope it stays that way	*/

   /* Now link into the new directory under the new name		*/
   nextkey = LinkDir(global, dirkey, fromkey, hash(toname, len));
   if (nextkey == -1)
      {
      /* Relink from file back into from directory and return.		*/
      if ((nextkey = LinkDir(global, parentkey, fromkey, hashval)) == -1)
      /* How embarrasing!  Still, with the checks we did earlier, this	*/
      /* is definitely not likely to happen (I think...)		*/
         return(DOS_FALSE);
      if ((block = (struct DirBlock *)ModBlock(global, fromkey)) == NULL)
         return(DOS_FALSE);
      block->db_HashChain = nextkey;
         return(DOS_FALSE);  /* but we maintain our dignity		*/
      }

   /* if this next bit fails, we've screwed up the new directory...	*/
   if ((block = (struct DirBlock *)ModBlock(global, fromkey)) == NULL)
      return(DOS_FALSE);  /* hope ModBlock set a good error in Res2     */
   block->db_HashChain = nextkey;  /* complete fwd link in directory	*/
   block->db_Parent = dirkey;
   memcpy(&block->db_Name[1], toname, len);
   block->db_Name[0] = len;
   BUG(("RenameEntry: Entry %d linked to directory %d\n", fromkey, dirkey));
   return(DOS_TRUE);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Routine: LinkDir                                                        */
/* Purpose: link a block into a directory (almost - for logistic reasons,  */
/*          caller must fill return value into his own block->db_HashChain)*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
KEY LinkDir(global, parentkey, filekey, hashval)
GLOBAL global;
KEY parentkey, filekey;
int hashval;
{
struct DirBlock *block;
KEY nextkey;

   block = (struct DirBlock *)ModBlock(global, parentkey);
   if (block == NULL)
      return(-1);  /* not a nice key */

   SetDateInfo(global, block);    /* timestamp the change in the parent dir */
   BUG(("LinkDir: Mod block %ld at %08lx newkey = %ld\n",
           parentkey, block, filekey));

   /* Enter the key into the list in a sorted fashion.  The lowest key must */
   /* appear first.                                                         */
   nextkey = block->db_HashTable[hashval];
   if (nextkey == 0 || filekey < nextkey)
      {
      /* it goes at the head of the chain, so we can simply put it there    */
      block->db_HashTable[hashval] = filekey;
      }
   else
      {
      while(1)
        {
        /* Not at the head of the chain.  We need to find where it goes on */
        /* the chain.  First we get the next block in the chain and see    */
        /* what it links to.                                               */
        if ((block = (struct DirBlock *)GetBlock(global, nextkey)) == NULL)
           return(-1);  /* the bad news key */
        /* Should we go after this block ?                                 */
        if (block->db_HashChain == 0 ||
            block->db_HashChain > filekey)
           {
           /* Yes, make sure we can modify this block                      */
           if ((block = (struct DirBlock *)ModBlock(global, nextkey)) == NULL)
              return(-1);  /* this'll slay them */
           /* Insert us in the chain and stop the search                   */
           nextkey = block->db_HashChain;
           block->db_HashChain = filekey;
           break;
           }
        /* advance search 						   */
        nextkey = block->db_HashChain;
        }
      }
   /* if we got here we done good */
   return(nextkey);  /* note 0 is a healthy value here: end of the line */
}
