/* lock.c - Lock-related stuff for the cfs file system. 
   Copyright (C) 1991, 1992, 1993 Kristian Nielsen.

   This file is part of XFH, the compressing file system handler.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            */

#include "CFS.h"

#include <string.h>

#include "dossupport.h"


/*
 * RawCFSLock() is the low-lewel LOCATE_OBJECT function of the CFS file
 * system. It takes a simple filename (that is, no path specification),
 * a lock to the parent directory, and a 'mode' value as arguments. A
 * special case occurs for a null name, in which case it just returns
 * a copy of the passed lock.
 * NOTE: 'directory/' is a valid name if it's a directory.
 */

struct CFSLock *RawCFSLock(glb glob, struct CFSLock *lock,
                        char * name, LONG mode ){
   LONG saveioerr;
	
   if( name && *name ){

      /* Here is the place to check whether the lock refers to a
       * directory in the underlying file system, or if it is in fact
       * a directory in an archieve. This code is for the former case: */

      if( lock->objtype == XOBJECT ){
         struct FileLock *xlock;

         if( !(xlock=xLock(glob,lock->xlock,name,mode)) ){
            debug(("Error: RawCFSLock(): Could not obtain lock: %ld.\n",glob->ioerr));
            return NULL;
         }
         if( !xExamine( glob, xlock, &glob->fib1 ) ){
            debug(("Error: RawCFSLock(): Cannot examine() object: %ld\n",glob->ioerr));
            saveioerr = glob->ioerr;
            xUnLock( glob, xlock );
            glob->ioerr = saveioerr;
            return NULL;
         }
         if( glob->fib1.fib_DirEntryType > 0 ){
            struct CFSLock *newlock;

            /* A directory. */
            newlock = XObjMakeLock(glob, xlock, mode);
            if( !newlock ){
               saveioerr = glob->ioerr;
               xUnLock(glob, xlock);
               glob->ioerr = saveioerr;
            }
            return newlock;
         }else{
            struct FileHandle *xfh;
            LONG filetype;
            
            /* A plain file. Determine its type. */
            /* First open the file. If the lock is to be exclusive, */
            /* we need to xUnLock() it while examining the file (BAD!) */
            /* This means that there's an ever so slight change that */
            /* the file will go away inbetween the xClose() and xLock(). */
            /* It might even have come back in a new disguise! sigh... */
            
            if( mode == EXCLUSIVE_LOCK ){
               debug(("RawCFSLock(): Temporarely UnLock()'ing...\n"));
               xUnLock(glob, xlock);
               xlock = NULL;
               xfh = xOpen( glob, lock->xlock, name, MODE_OLDFILE );
            }else{
               xfh = xOpenFromCopyOfLock( glob, xlock );
            }
            if( !xfh ){
               debug(("Error: RawCFSLock(): Cannot Open() file: %ld.\n",glob->ioerr));
               if( xlock ){
                  saveioerr = glob->ioerr;
                  xUnLock(glob, xlock);
                  glob->ioerr = saveioerr;
               }
               return NULL;
            }
            filetype = xFileType(glob, xfh);
            xClose(glob, xfh);
            if(!xlock){
               if( !(xlock=xLock(glob,lock->xlock,name,mode)) ){
                  debug(("Error: RawCFSLock(): Could not re-Lock() the file: %ld.\n",glob->ioerr));
                  return NULL;
               }
            }
            /* ToDo: this shouldn't be implemented as an ugly switch(). */
            switch(filetype){
               case XOBJECT:{
                  struct CFSLock *newlock;
                  
                  newlock = XObjMakeLock(glob, xlock, mode);
                  if( !newlock ){
                     saveioerr = glob->ioerr;
                     xUnLock( glob, xlock );
                     glob->ioerr = saveioerr;
                  }
                  return newlock;
               }
               case XPKOBJECT:{
                  struct XpkLock *newlock;
                  
                  newlock = XpkMakeLock(glob, xlock, mode);
                  if( !newlock ){
                     saveioerr = glob->ioerr;
                     xUnLock(glob, xlock);
                     glob->ioerr = saveioerr;
                  }
                  return (struct CFSLock *)newlock;
               }
               default:
                  debug(("ERROR: RawCFSLock(): unsupported file type %ld.\n",filetype));
                  glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
                  return NULL;
            }
         }
      }else{
         debug(("** PANIC **: bad object type in lock: %ld\n",lock->objtype));
         glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
         return NULL;
      }
   }else{
      /* A Null name was passed. If the parent lock is of the right type,
       * just DupLock() it. Otherwise, signal an error.
       * (Duplock() will catch the case of an exclusive lock).
       */
      if( mode == lock-> mode ){
         return CFSDupLock( glob, lock );
      }else{
         glob->ioerr = ERROR_OBJECT_IN_USE;
         return NULL;
      }
   }
}


/* And DOS said: let there be devices...
 * So hardwire a CFSLock to serve as our root lock.
 */
struct CFSLock *makerootlockdayone(glb glob){
   return XObjMakeLock(glob, glob->xrootlock, ACCESS_READ);
}


/*
 * Create a struct FileLock around a CFSLock for returning to caller.
 */
struct FileLock * CreateFileLock( glb glob, struct CFSLock *lock ){
   struct FileLock *xlock;
   
   if( !dalloc(xlock) ){
      OUTOFMEM;
      return NULL;
   }
   
   /* Place any linking of filelocks into a list here. */
   xlock -> fl_Key = (LONG) lock;
   xlock -> fl_Access = lock -> mode;
   xlock -> fl_Task = glob -> dosport;
   xlock -> fl_Volume = c2b(glob -> volnode);
   
   return xlock;
}



/*
 * This is the function that does all the main parsing of path strings.
 * It's purpose is to lock the parent directory of a file or dir, and
 * return a pointer to the actual filename. A special case occurs for
 * NULL filenames (either "" or "name:") - here the actual lock is
 * returned, and the name set to "".
 *
 * In case of an error, the name is set to "", and a NULL lock is returned.
 *
 * NOTE: This will fail to correctly handle a path like '...//' - it
 * will lock the directory and return a NULL name. This will present
 * a problem if someone tries to obtain an exclusive lock on such a path.
 */

struct CFSLock *CFSLockParent( glb glob, struct CFSLock *parentlock,
                          char **nameptr ){
   char *p,*q,*path,*name;
   struct CFSLock *lock,*childlock;
   int len;
   
   
   /* Watch out for a NULL name. */
   if(!*nameptr) *nameptr = "";
   
   /* Allocate a copy of the path so that we can modify it. */
   len = strlen(*nameptr);
   if( !(name = dosalloc( len+1 )) ){
      OUTOFMEM;
      *nameptr += len;       /* Set to empty string. */
      return NULL;
   }
   strcpy( name, *nameptr);
   
   path = name;

   /* A NULL parent lock is equal to the rootlock. */
   if(!parentlock) parentlock = glob->rootlock;
   
   /* Now check for an absolute path, ie '...:...' */
   if( p=strchr(name,':') ){
      
      /* Got an absolute path. This is either an assign (or the device /
       * volume name, which is treated the same way), or a root
       * specification (like ':system/cli'). */

      if( p==name ){               /* get the root dir. */
         parentlock = glob->rootlock;
      }
#ifndef NONSTRICT
      else{
         /* The assign/device/volume name was used by dos.library to
          * find our process id and the parent lock. It is of no use
          * to the handler.*/
         
         /* 'Assign' allows silly names like '1/2:', but some other
          * commands have trouble with this. Flag these names as invalid. */
          
         if( q=strchr(name,'/') ){
            if( q<p ){
               debug(("Error: silly device name '%s'/n",name));
               glob->ioerr = ERROR_INVALID_COMPONENT_NAME;
               dosfree( name );
               *nameptr += len;
               return NULL;
            }
         }
      }
#endif

      path = p+1;     /* Point past ':'. */

#ifndef NONSTRICT
      /* Disallow more than one ':' char in name. */
      if( strchr(path,':') ){
         debug(("Error: multiple ':' char in name '%s'.\n",name));
         glob->ioerr = ERROR_INVALID_COMPONENT_NAME;
         dosfree( name);
         *nameptr += len;
         return NULL;
      }
#endif
   
   }

   /* Now look through the path specification. */
   
   if( !(lock = CFSDupLock( glob, parentlock )) ){
      /* Couldn't DupLock the parent. Hence we must use a reference. */
      debug(("CFSLockParent(): Cannot DupLock() the passed parent dir: %ld - using reference.\n",glob->ioerr));
      XObjAddReferenceToLock(glob, parentlock);
      lock = parentlock;
      /* NOTE: We now have a dangerous double reference to the
       * lock. Notably, the lock will be CFSUnLock()'ed two
       * times.
      */
   }
   
   for(;;){
      p = path;
      if( !*p ) break;  /* If p is the null string, then lock is ok now. */
      path = strchr( p, '/' );
      if( path ){
         *path++ = '\0';
         if( !*path && path != p+1 ){
            break;                         /* Last component is 'name/'. */
         }
      }else{
         break;                            /* Last component is 'name'. */
      }

      /* At this point, p holds a null-terminated path component. */
      
      childlock = p+1 == path ?
         CFSParentDir(glob,lock) :
         RawCFSLock(glob,lock,p,SHARED_LOCK);
      if( !childlock ){
         LONG saveioerr;
         
         /* Some kind of error happened while locking the path component.
          * Report this error back to caller. */
         
         saveioerr = glob->ioerr;    /* Save I/O error code. */
         CFSUnLock( glob, lock);
         if( p+1 == path && !glob->ioerr) /* Attempt to use '/' past root */
            glob->ioerr = ERROR_OBJECT_NOT_FOUND;
         else
            glob->ioerr = saveioerr;  /* Restore for IoErr(). */
         dosfree( name );
         *nameptr += len;
         return NULL;
      }
      CFSUnLock( glob, lock);
      lock=childlock;

      if( !CFSExamine(glob, childlock, &glob->fib1) ){
         /* Error examining component. */

         CFSUnLock(glob, lock);
         dosfree( name );
         *nameptr += len;
         return NULL;
      }
      
      if( glob->fib1.fib_DirEntryType < 0 ){
         /* this component in the path is not a directory, so return 
          * an error. */
         
         debug(("Error: CFSLockParent(): Plain file used in path.\n"));
         CFSUnLock(glob, lock);
         glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
         dosfree( name );
         *nameptr += len;
         return NULL;
      }
   }

   *nameptr += (p - name);    /* Point to equivalent of *p in 'name'. */
   dosfree( name );
   return lock;
}


/*
 * This function handles the ACTION_LOCATE_OBJECT request.
 */

struct CFSLock * CFSLock(glb glob,struct CFSLock * parentlock,
                          char * name,LONG mode){
   struct CFSLock *lock,*parent;
   LONG ioerrsave;
   
   /* Lock the parent directory, and set 'name' to point to the actual
    * name. For a path like 'name:', lock the file/dir and point name to
    * "" (RawCFSLock() will handle this case).
    */
   if(!(parent = CFSLockParent(glob, parentlock, &name ))){
      return NULL;
   }
   debug(("CFSLock: parent=%lx, name='%s'\n",parent,name));
   /* Now Lock the file or dir. */
   lock = RawCFSLock( glob, parent, name, mode );
   ioerrsave = glob->ioerr;
   CFSUnLock( glob, parent );
   glob->ioerr = ioerrsave;
   return lock;
}


struct CFSLock *CFSDupLock( glb glob, struct CFSLock *lock ){
   
   if( !lock ) return 0L;
   return (*lock->f->DupLock) (glob,lock);
}


struct CFSLock *CFSParentDir( glb glob, struct CFSLock *lock){
   
   if( !lock ) return NULL;
   else if(!lock->f){
      debug(("** PANIC **: CFSParentDir(): NULL action function.\n"));
      glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
      return DOSFALSE;
   }else return (*lock->f->Parent)(glob, lock);
}


struct CFSLock *CFSParentFH( glb glob, struct CFSFH *fh){
   
   if( !fh ) return NULL;
   else if(!fh->f){
      debug(("** PANIC **: CFSParentDir(): NULL action function.\n"));
      glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
      return DOSFALSE;
   }else return (*fh->f->ParentOfFH)(glob, fh);
}


BOOL CFSUnLock( glb glob,struct CFSLock *lock){

   if( !lock ) return 0L;       /* No really sensible error possible. */
   else if(!lock->f){
      debug(("** PANIC **: CFSUnLock(): NULL action function.\n"));
      glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
      return DOSFALSE;
   }else return (*lock->f->UnLock)(glob, lock);
}


BOOL CFSExamine(glb glob, struct CFSLock *lock, struct FileInfoBlock *fib){

   if( !lock ){
      return DOSFALSE;       /* No really sensible error possible. */
   }else if( lock->f ){
      return (*lock->f->Examine)( glob, lock, fib );
   } else {
      debug(("** PANIC **: NULL object action func Examine() in lock.\n"));
      return DOSFALSE;
   }
}


BOOL CFSExamineNext(glb glob, struct CFSLock *lock, struct FileInfoBlock *fib){

   if( !lock ){
      return DOSFALSE;       /* No really sensible error possible. */
   }else if( lock->f ){
      return (*lock->f->ExNext)( glob, lock, fib );
   } else {
      debug(("** PANIC **: NULL object action func ExNext() in lock.\n"));
      return DOSFALSE;
   }
}


BOOL CFSExamineFH(glb glob, struct CFSFH *fh, struct FileInfoBlock *fib){

   if( !fh ){
      return DOSFALSE;       /* No really sensible error possible. */
   }else if( fh->f ){
      return (*fh->f->ExamineFH)( glob, fh, fib );
   } else {
      debug(("** PANIC **: NULL object action func ExamineFH() in handle.\n"));
      return DOSFALSE;
   }
}


struct CFSLock *CFSCreateDir( glb glob, struct CFSLock *parent, char *name ){
   struct CFSLock *lock;
   
   /* Lock the parent directory, and set 'name' to point to the actual
    * name. For a path like 'name:', lock the file/dir and point name to
    * "" (RawCFSLock() will handle this case).
    */
   if(!(parent = CFSLockParent(glob, parent, &name ))){
      return NULL;
   }else if( parent->f ){
      LONG saveioerr;
      
      lock = (*parent->f->CreateDir)( glob, parent, name );
      if( !lock ) saveioerr = glob->ioerr;
      CFSUnLock( glob, parent );
      if( !lock ) glob->ioerr = saveioerr;
      return lock;
   }else{
      debug(("** PANIC **: NULL object action func CreateDir() in lock.\n"));
      CFSUnLock( glob, parent );
      glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
      return NULL;
   }
}


/* CFSDeleteFile(): Delete a file or directory.
 *
 * Just passes control to the appropriate lock-specific call.
 * NOTE: It's up to the lock-specific functions to check that an attempt
 * to delete a non-empty directory is rejected.
 */
BOOL CFSDeleteFile( glb glob, struct CFSLock *parent, char *name ){
   
   if(!(parent = CFSLockParent(glob, parent, &name ))){
      return DOSFALSE;
   }else if( parent->f ){
      LONG saveioerr;
      BOOL err;
      
      err = (*parent->f->DeleteFile)( glob, parent, name );
      if( !err ) saveioerr = glob->ioerr;
      CFSUnLock( glob, parent );
      if( !err ) glob->ioerr = saveioerr;
      return err;
   }else{
      debug(("** PANIC **: NULL object action func DeleteFile() in lock.\n"));
      CFSUnLock( glob, parent );
      glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
      return DOSFALSE;
   }
}


/* CfsRename(): Rename files within the file system.
 *
 * A problem arises if someone tries to rename between two directory-
 * locks of different types (ie. a lharc-archieve and an UFS directory).
 * This function passes control to the FROM-locktype rename function. It's
 * up to this function to check the type of the destination directory
 * and take an appropriate action.
 */
BOOL CFSRename( glb glob, struct CFSLock *parent1, char *name1, struct CFSLock *parent2, char *name2 ){
   
   if(!(parent1 = CFSLockParent(glob, parent1, &name1 ))){
      return DOSFALSE;
   }
   if(!(parent2 = CFSLockParent(glob, parent2, &name2 ))){
      LONG saveioerr = glob->ioerr;
      CFSUnLock( glob, parent1 );
      glob->ioerr = saveioerr;
      return DOSFALSE;
   }

   if( parent1->f ){
      LONG saveioerr;
      BOOL err;
      
      err = (*parent1->f->Rename)( glob, parent1, name1, parent2, name2 );
      if( !err ) saveioerr = glob->ioerr;
      CFSUnLock( glob, parent1 );
      CFSUnLock( glob, parent2 );
      if( !err ) glob->ioerr = saveioerr;
      return err;
   }else{
      debug(("** PANIC **: NULL object action func Rename() in lock.\n"));
      CFSUnLock( glob, parent1 );
      CFSUnLock( glob, parent2 );
      glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
      return DOSFALSE;
   }
}


BOOL CFSSetProtection( glb glob, struct CFSLock *parent, char *name, LONG bits ){
   
   if(!(parent = CFSLockParent(glob, parent, &name ))){
      return DOSFALSE;
   }else if( parent->f ){
      LONG saveioerr;
      BOOL err;
      
      err = (*parent->f->SetProtection)( glob, parent, name, bits );
      if( !err ) saveioerr = glob->ioerr;
      CFSUnLock( glob, parent );
      if( !err ) glob->ioerr = saveioerr;
      return err;
   }else{
      debug(("** PANIC **: NULL object action func SetProtection() in lock.\n"));
      CFSUnLock( glob, parent );
      glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
      return DOSFALSE;
   }
}


BOOL CFSSetComment( glb glob, struct CFSLock *parent, char *name, char *comment ){
   
   if(!(parent = CFSLockParent(glob, parent, &name ))){
      return DOSFALSE;
   }else if( parent->f ){
      LONG saveioerr;
      BOOL err;
      
      err = (*parent->f->SetComment)( glob, parent, name, comment );
      if( !err ) saveioerr = glob->ioerr;
      CFSUnLock( glob, parent );
      if( !err ) glob->ioerr = saveioerr;
      return err;
   }else{
      debug(("** PANIC **: NULL object action func SetComment() in lock.\n"));
      CFSUnLock( glob, parent );
      glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
      return DOSFALSE;
   }
}


BOOL CFSSetDate( glb glob, struct CFSLock *parent, char *name, struct DateStamp *ds ){
   
   if(!(parent = CFSLockParent(glob, parent, &name ))){
      return DOSFALSE;
   }else if( parent->f ){
      LONG saveioerr;
      BOOL err;
      
      err = (*parent->f->SetFileDate)( glob, parent, name, ds );
      if( !err ) saveioerr = glob->ioerr;
      CFSUnLock( glob, parent );
      if( !err ) glob->ioerr = saveioerr;
      return err;
   }else{
      debug(("** PANIC **: NULL object action func SetDate() in lock.\n"));
      CFSUnLock( glob, parent );
      glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
      return DOSFALSE;
   }
}

BOOL CFSMakeLink(glb glob,struct CFSLock *Parent,char *Name,LONG Dest,LONG Type)

{
 if ((Parent=CFSLockParent(glob,Parent,&Name))==NULL) return DOSFALSE;

 if (Parent->f)
  {
   BOOL Res;
   LONG saveioerr;

   Res=(*Parent->f->MakeLink)(glob,Parent,Name,Dest,Type);

   if (!Res) saveioerr=glob->ioerr;
   CFSUnLock (glob,Parent);
   if (!Res) glob->ioerr=saveioerr;

   return Res;
  }
 else
  {
   debug (("** PANIC **: NULL object action func MakeLink() in lock.\n"));
   CFSUnLock (glob,Parent);
   glob->ioerr=ERROR_OBJECT_WRONG_TYPE;
   return DOSFALSE;
  }
}

BOOL CFSReadLink(glb glob,struct CFSLock *Parent,char *Name,char *Buffer,ULONG Size)

{
 if ((Parent=CFSLockParent(glob,Parent,&Name))==NULL) return DOSFALSE;

 if (Parent->f)
  {
   BOOL Res;
   LONG saveioerr;

   Res=(*Parent->f->ReadLink)(glob,Parent,Name,Buffer,Size);

   if (!Res) saveioerr=glob->ioerr;
   CFSUnLock (glob,Parent);
   if (!Res) glob->ioerr=saveioerr;

   return Res;
  }
 else
  {
   debug (("** PANIC **: NULL object action func ReadLink() in lock.\n"));
   CFSUnLock (glob,Parent);
   glob->ioerr=ERROR_OBJECT_WRONG_TYPE;
   return DOSFALSE;
  }
}

BOOL CFSSameLock( glb glob, struct CFSLock *l1, struct CFSLock *l2 ){
   if(!l1 || !l2) return (BOOL) (l1==l2 ? TRUE : FALSE);
   if( l1->objtype != l2->objtype ) return FALSE;
   if( !l1->f ){
      debug(("** PANIC **: CFSSameLock(): Bad action func in lock.\n"));
      glob->ioerr = ERROR_OBJECT_WRONG_TYPE;
      return FALSE;
   }else return (*l1->f->SameLock)(glob, l1, l2);
}


/* End of lock.c */
