/* xobj.c - 'wrapper' calls for directing requests through to
   the underlying 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 "dossupport.h"

LONG XObjClose(glb glob,struct CFSFH *fh)

{
 LONG Result;
  
 if (!(Result=xClose(glob, fh->xfh)))
  return Result;  /* Dare not free the file handle. */

 fh->xfh=NULL;
  
 /* Check whether we need to compress the file (signified by a non-NULL
  * fh->filename field). */

 if (fh->filename)
  {
   debug (("Attempting to compress file '%s'...",fh->filename));
#ifdef DEBUG
   Result=TransformFile(glob,fh->parent->xlock,fh->filename,PackFile2File,NULL);
   debug (("result: %ld (ioerr=%ld).\n",Result,glob->ioerr));
#else
   (void)TransformFile(glob,fh->parent->xlock,fh->filename,PackFile2File,NULL);
#endif

   /* Even if the compressing fails, we still have the file intact. */

   /* create xScan filenote if possible */
   xScan (glob,fh->parent->xlock,fh->filename);
  }

 XObjFreeFH (glob,fh);
 return DOSTRUE;
}


LONG XObjRead( glb glob, struct CFSFH *fh, UBYTE *buf, LONG len ){
   return xRead( glob, fh->xfh, buf, len );
}


LONG XObjWrite( glb glob, struct CFSFH *fh, UBYTE *buf, LONG len ){
   return xWrite( glob, fh->xfh, buf, len );
}


LONG XObjSeek( glb glob, struct CFSFH *fh, LONG pos, LONG offset ){
   return xSeek( glob, fh->xfh, pos, offset );
}


/* Allocate and partly initialise a struct CFSFH (should ONLY be called
 * from XObjCreateFH()!).
 */
static struct CFSFH *XObjAllocFH(glb glob, struct FileHandle *xfh, LONG mode){
   struct CFSFH *newfh;

   if( !dalloc(newfh) ){
      debug(("Error: XObjAllocFH(): No memory for file handle.\n"));
      OUTOFMEM;
      return NULL;
   }

   newfh->objtype = XOBJECT;
   newfh->mode = mode;
   newfh->xfh = xfh;
   newfh->f = &Xfunc;
   /* Other fields are NULL by default. */
   return newfh;
}


/* This function is needed to convert from XpkFH to CFSFH in Xpk_Write(). */
void XObjStealXpkFH(glb glob, struct CFSFH *fh){
  fh->objtype = XOBJECT;
  fh->f = &Xfunc;
}  


/* Stuff the filename / parentdir thingies into a CFSFH file handle. Must
 * be 'undone' with XObjUnStuffFH().
 */
BOOL XObjStuffFH(glb glob, struct CFSFH *fh, char *name,
		 struct CFSLock *parent){

  debug(("Setting up fields in fh for autocompressing at Close().\n"));
  if(!(fh->filename = copystr(name))){
    debug(("Error: XObjStuffFH(): No memory for file name.\n"));
    OUTOFMEM;
    return FALSE;
  }
  if(!(fh->parent = CFSDupLock(glob, parent))){
    /* Couldn't DupLock() it, so just hold a reference. */
    debug(("XObjStuffFH(): Couldn't DupLock() parent, adding reference.\n"));
    XObjAddReferenceToLock(glob, parent);
    fh->parent = parent;
    /* NOTE: We now have a dangerous double reference to the
     * lock. Notably, the lock will be CFSUnLock()'ed two
     * times.
     */
  }
  return TRUE;
}


void XObjUnStuffFH(glb glob, struct CFSFH *fh){
  if(fh->filename){
    freestr(fh->filename);
    CFSUnLock(glob, fh->parent);
    fh->filename = NULL;
    fh->parent = NULL;
  }
}
  

/* Free CFSFH allocated with XObjCreateFH() (will not close the file handle).*/
void XObjFreeFH(glb glob, struct CFSFH *fh){
  XObjUnStuffFH(glob, fh);
  dfree(fh);
}


/* Create a file handle to an xObj file. If 'name' is non-NULL,
 * and autocompression is ON, will prepare filehandle for autocompression
 * at Close() time (assuming 'parent' to be a lock on the parentdir of
 * the file).
 */
struct CFSFH *XObjCreateFH(glb glob, struct FileHandle *xfh, LONG mode,
                                 char *name,struct CFSLock *parent){
  struct CFSFH *newfh;

  if( !( newfh = XObjAllocFH(glob, xfh, mode) ) ){
    return NULL;
  }

  /* Prepare for compressing the file at Close() time if needed. */
  if(glob->autocompress && name){
    if(!XObjStuffFH(glob, newfh, name, parent)){
      SAVEIOERR;
      XObjFreeFH(glob, newfh);
      RESTIOERR;
      return NULL;
    }
  }
  return newfh;
}


/* Wraps a CFSLock around an xLock. */
struct CFSLock *XObjMakeLock( glb glob, struct FileLock *xlock, LONG mode ){
   struct CFSLock *newlock;

   if( !dalloc(newlock) ){
      OUTOFMEM;
      return NULL;
   }
debug(("XObjMakeLock(): xlock=%lx, procid=%lx.\n",xlock,xlock->fl_Task));
   newlock->objtype = XOBJECT;
   newlock->mode = mode;
   newlock->xlock = xlock;
   newlock->f = &Xfunc;
   newlock->refcount = 0;
   return newlock;
}

struct CFSLock *XObjDupLock( glb glob, struct CFSLock *lock ){
   struct CFSLock *newlock;

   /* XOBJECT locks have their xLocks xDupLock'ed, and the rest of
    * the fields are just copied vanilla.
    * Special case for the refcount; it must be set to 0 in the new lock.
    */
   if( !dalloc(newlock) ){
      OUTOFMEM;
      return 0L;
   }
   *newlock = *lock;
   newlock->refcount = 0;   /* IMPORTANT!!! */
   if( !(newlock->xlock = xDupLock(glob, lock->xlock)) ){
      dfree(newlock);
      return 0L;
   }
   return newlock;   
}

/* This nasty function adds a reference to a CFSLock. FOR EACH CALL TO
 * THIS FUNCTION THERE MUST BE A CORRESPONDING CALL TO XObjUnLock(), as
 * well as a final one to match the original XObjLock(), XObjDupLock() or
 * whatever.
 */
void XObjAddReferenceToLock(glb glob, struct CFSLock *lock){
   lock->refcount++;
}


struct CFSLock *XObjParentDir( glb glob, struct CFSLock *lock ){
	struct FileLock *xlock;
	struct CFSLock *newlock;
	
	if( xSameLock(glob, glob->xrootlock, lock->xlock) == LOCK_SAME ){
		debug(("- ParentDir() of root lock. -"));
		glob->ioerr = 0L;
		return NULL;
	}
	if( !(xlock = xParentDir(glob, lock->xlock)) ){
		return 0L;
	}
	newlock = XObjMakeLock( glob, xlock, ACCESS_READ );
	if( !newlock ){
		LONG saveioerr = glob->ioerr;
		xUnLock(glob, xlock);
		glob->ioerr = saveioerr;
	}
	return newlock;
}


struct CFSLock *XObjParentFH( glb glob,  struct CFSFH *fh ){
	struct FileLock *xlock;
	struct CFSLock *newlock;
	
	if( !(xlock = xParentFH(glob, fh->xfh)) ){
		return 0L;
	}
	newlock = XObjMakeLock( glob, xlock, ACCESS_READ );
	if( !newlock ){
		LONG saveioerr = glob->ioerr;
		xUnLock(glob, xlock);
		glob->ioerr = saveioerr;
	}
	return newlock;
}


BOOL XObjUnLock( glb glob, struct CFSLock *lock ){
	BOOL err;

   /* Have to check if there are outstanding references to the lock. */
   if(lock->refcount){
      debug(("XObjUnLock: Just decrementing the refcount (%ld).\n",lock->refcount));
      lock->refcount--;
      return DOSTRUE;
   }
   
	err = xUnLock(glob, lock->xlock);
      
	/* Now, what if the UnLock fails? I guess it's best to return FALSE
	 * and let the lock live.
	 */
	if(!err) return DOSFALSE;
	dfree(lock);
	return DOSTRUE;
}


BOOL XObjSameLock(glb glob, struct CFSLock *l1, struct CFSLock *l2 ){
	return (BOOL) (xSameLock( glob, l1->xlock, l2->xlock ) == LOCK_SAME);
}

BOOL XObjMakeLink(glb glob,struct CFSLock *ParentLock,char *Name,
                  LONG Dest,LONG Type)

{
 if (Type)
  {
   debug (("XObjMakeLink: LINK_SOFT\n"));
   return xMakeLink(glob,ParentLock->xlock,Name,Dest,LINK_SOFT);
  }
 else
  {
   struct FileLock *DestLock;

   debug (("XObjMakeLink: LINK_HARD\n"));

   DestLock=(struct FileLock *)b2c(Dest);
   if (DestLock->fl_Task!=glob->dosport)
    {
     debug (("Error: not our lock !\n"));
     glob->ioerr=ERROR_OBJECT_WRONG_TYPE;
     return DOSFALSE;
    }
   DestLock=((struct CFSLock *)DestLock->fl_Key)->xlock;
   return xMakeLink(glob,ParentLock->xlock,Name,c2b(DestLock),LINK_HARD);
  }
}

BOOL XObjReadLink(glb glob,struct CFSLock *ParentLock,char *Name,
                  char *Buffer,ULONG Size)

{
 return xReadLink(glob,ParentLock->xlock,Name,Buffer,Size);
}

BOOL XObjExamine( glb glob, struct CFSLock *lock, struct FileInfoBlock *fib ){
   BOOL err, fix=FALSE;
   
   if(xSameLock(glob, glob->xrootlock, lock->xlock) == LOCK_SAME) fix=TRUE;
   err = xExamine(glob, lock->xlock, fib);
   debug(("XObjExamine(): %ld,'%s'\n",err,fib->fib_FileName));

   (void)XpkFastModifyFIB(fib);
   
   /* Check if Examine()'ing the root dir (if so, fix name). */
   if(fix){
      /* ARRGHH! Nasty BCPL stuff around... */
      bstr2c(glob->volnode->dl_Name, &fib->fib_FileName[0]);
      debug(("XObjExamine(): Changing name in FIB to '%s'.\n",
                                                 &fib->fib_FileName[0]));
   }
   return err;
}

BOOL XObjExNext( glb glob, struct CFSLock *lock, struct FileInfoBlock *fib ){
   BOOL err, err2;
   
   err = xExamineNext(glob, lock->xlock, fib);
   debug(("CFSExamineNext(): %ld,'%s'\n",err,fib->fib_FileName));
   if( !err ) return err;
   err2 = ModifyFIB( glob, lock, fib );
   if(!glob->FailOnExNext){
      /* The error return from ModifyFIB is discarded. This way, a directory
       * scan won't fail in flaky programs that don't test IOERR for
       * ERROR_NO_MORE_ENTRIES.
       */
   }else{
      err = err2;
   }
   return err;
}


BOOL XObjExamineFH( glb glob, struct CFSFH *fh, struct FileInfoBlock *fib ){
   BOOL err;

   err = xExamineFH(glob, fh->xfh, fib);
   debug(("XObjExamineFH(): %ld,'%s'\n",err,fib->fib_FileName));

   (void)XpkFastModifyFIB(fib);

   return err;
}

BOOL ModifyFIB( glb glob, struct CFSLock *lock, struct FileInfoBlock *fib ){
   LONG saveioerr;
   
   if( fib->fib_DirEntryType < 0 ){
      LONG filetype;
      struct FileHandle *xfh;
      BOOL err;

      if ( XpkFastModifyFIB(fib) ) return DOSTRUE;

      /* A plain file. This needs a closer look.*/
      xfh = xOpen( glob, lock->xlock, fib->fib_FileName, MODE_OLDFILE );
      if( !xfh ){
         debug(("Error: ModifyFIB(): Couldn't open file: %ld\n",glob->ioerr));
         return DOSFALSE;
      }
      filetype = xFileType( glob, xfh );
      err = ModifyFIB_type( glob, filetype, lock, fib, xfh );
      if( !err ) saveioerr = glob->ioerr;
      xClose(glob, xfh);
      if( !err ) glob->ioerr = saveioerr;
      return err;
   }else{
      /* Do nothing for a directory.*/
      return DOSTRUE;
   }
}

/* XObjModifyFIB() is just a NO-OP.*/
BOOL XObjModifyFIB( glb glob, struct CFSLock *lock,struct FileInfoBlock *fib, struct FileHandle *xfh ){
   return DOSTRUE;
}


struct CFSLock *XObjCreateDir( glb glob, struct CFSLock *parentlock, char *name ){
   struct CFSLock *newlock;
   struct FileLock *xlock;
   
   if( !(xlock = xCreateDir(glob, parentlock->xlock, name)) ){
      return 0L;
   }
   newlock = XObjMakeLock( glob, xlock, xlock->fl_Access );
   if( !newlock ){
      LONG saveioerr = glob->ioerr;

      /* Oh No! We're left with a dangling directory...
       * Try to remove it before returning.
       */
      debug(("XObjCreateDir(): Trying to delete dangling directory..."));
      xUnLock(glob, xlock);
      if( CFSDeleteFile( glob, parentlock, name ) )
         debug(("Succes!\n"));
      else
         debug(("Failure...\n"));
      glob->ioerr = saveioerr;
   }
   return newlock;
}


BOOL XObjSetProtection(glb glob, struct CFSLock *parent, char *name, LONG mask){
   BOOL err;
   
   err = xSetProtection(glob, parent->xlock, name, mask);
   return err;
}

BOOL XObjSetComment(glb glob, struct CFSLock *parent, char *name, char *comment)

{
 BOOL Success;
   
 if (Success=xSetComment(glob,parent->xlock,name,comment))
  xScan (glob,parent->xlock,name);

 return Success;
}

BOOL XObjSetFileDate(glb glob,struct CFSLock *parent,char *name,struct DateStamp *ds)

{
 BOOL Success;
   
 if (Success=xSetFileDate(glob,parent->xlock,name,ds))
  xScan (glob,parent->xlock,name);

 return Success;
}


BOOL XObjDeleteFile(glb glob, struct CFSLock *parent, char *name){
   BOOL err;
   
   err = xDeleteFile(glob, parent->xlock, name);
   return err;
}


BOOL XObjRename(glb glob, struct CFSLock *p1, char *n1, struct CFSLock *p2, char *n2){
   BOOL err;
   
   if(p2->objtype != XOBJECT){
      debug(("XObjRename(): Error: rename to lock of type %ld\n",p2->objtype));
      glob->ioerr = ERROR_RENAME_ACROSS_DEVICES;
      return DOSFALSE;
   }else{
      err = xRename(glob, p1->xlock, n1, p2->xlock, n2);
      return err;
   }
}


/* End of xobj.c */
