// IFSDirOpen.bcpl -- Open and close IFS files
// Copyright Xerox Corporation 1979, 1981, 1982

// Last modified July 22, 1982  3:39 PM by Taft

get "Ifs.decl"
get "IfsFiles.decl"
get "Streams.d"
get "IfsDirs.decl"
get "IfsRs.decl"


external
[
// Outgoing procedures
IFSOpenFile; OpenIFSStream; OpenIFSFile; CreateIFSStream
CloseIFSStream; CloseIFSFile
CreateIFSFile; StreamsFD; CreateDirectoryEntry
IFSCleanup; IFSErrors; UpdatePageUsage

// Incoming procedures -- other parts of IFSDirs
LookupIFSFile; DestroyFD; LockDirFD; UnlockDirFD; ModifyDirFD
LookupFD; GetDIFRec; DIFRecFromDR; UpdateDIFRec; GetBufferForFD
UserOwns; CheckAccess; CheckAllocation
TransferLeaderPage; EmptiestUnit; GetDiskFromFD; LockFile; UnlockFile

// Incoming procedures -- B-Tree
UpdateRecord

// Incoming procedures -- rest of IFS
IFSError; Min; DoubleIncrement; FreePointer
CopyString; ExtractSubstring; ByteBlt

// Incoming procedures -- operating system
DefaultArgs; SysAllocate; SysFree; Zero; MoveBlock; Noop; SysErr
CreateDiskStream; CreateDiskFile; FileLength; Closes; KsHintLastPageFa
ReturnFrom; CallersFrame

// Incoming statics
CtxRunning
]

manifest
[
// Private "open modes" used only during Close
modeDontUnlock = 376B  // don't unlock (or destroy) fd
modeDontDestroy = 377B  // don't destroy fd
]

//----------------------------------------------------------------------------
let IFSOpenFile(name, lvErrorCode, mode, itemSize, lc, fs,
     dirName; numargs na) = valof
//----------------------------------------------------------------------------
// Opens an IFS file, going directly from a name to a stream
// by application of the LookupIFSFile and OpenIFSStream procedures.
// This is the simplest case.
// Note: caller should not lock the directory, and the directory
// is unlocked when we return.
[
DefaultArgs(lv na, -1, lv na, modeRead, charItem, 0, 0, 0)
if lc eq 0 then lc = (mode eq modeRead? lcVHighest,
    mode eq modeWrite? lcCreate+lcVNext, lcCreate+lcVHighest)
let fd = LookupIFSFile(name, lc, lvErrorCode, fs, dirName)
let str = 0
if fd ne 0 then
   [
   str = OpenIFSStream(fd, lvErrorCode, mode, itemSize)
   if str eq 0 then DestroyFD(fd)
   ]
resultis str
]

//----------------------------------------------------------------------------
and OpenIFSStream(fd, lvErrorCode, mode, itemSize; numargs na) = valof
//----------------------------------------------------------------------------
// Opens a file and returns a stream given an fd.
// Combines the actions of OpenIFSFile and CreateIFSStream.
// Returns zero if unsuccessful.
// Note: caller should not lock the directory, and the directory
// is unlocked when we return.
[
DefaultArgs(lv na, -1, lv na, modeRead, charItem)
let str = 0
@lvErrorCode = OpenIFSFile(fd, mode)
if @lvErrorCode eq 0 then
   [
   str = CreateIFSStream(fd, itemSize)
   if str eq 0 then
      [ CloseIFSFile(fd); @lvErrorCode = ecCreateStreamFailed ]
   ]
resultis str
]

//----------------------------------------------------------------------------
and OpenIFSFile(fd, mode, buffer; numargs na) = valof
//----------------------------------------------------------------------------
// Attempts to open the file designated by fd.
// Mode is one of modeRead, modeWrite, modeReadWrite, or modeAppend.
// Returns zero if successful and an error code if unsuccessful.
// buffer is optional: if supplied, a copy of the file's leader page
// is returned in it.
// Note: caller should not lock the directory, and the directory
// is unlocked upon return.
[
let callerSuppliedBuffer = na ge 3 & buffer ne 0
unless callerSuppliedBuffer do buffer = GetBufferForFD(fd)

fd>>FD.mode = mode
let fs = fd>>FD.fs
fd>>FD.oldPageNumber = 0

let result = valof
   [
   let ec = LookupFD(fd, lockRead)
   let owner = UserOwns(fd)

   // If file exists, read its leader page for access check
   if fd>>FD.lookupStatus eq lsExists then TransferLeaderPage(fd, buffer)

   // Must unlock directory while doing access checks (below), because
   // they are potentially very time-consuming (Grapevine and all that).
   UnlockDirFD(fd)
   if ec ne 0 resultis ec

   // Handle cases depending on mode
   switchon mode into
      [
      case modeRead:
      case modeReadWrite:
      case modeReadWriteShared:
         // Check read protection
         if fd>>FD.lookupStatus eq lsExists then
            unless CheckAccess(lv buffer>>ILD.readProt, owner) do
               resultis ecAccessDenied
         if mode eq modeRead then endcase
         // Fall into write cases for modeReadWrite*

// OpenIFSFile (cont'd)

      case modeWrite:
      case modeAppend:
         test fd>>FD.lookupStatus eq lsExists
            ifso  // File already exists, check write/append protection
               unless CheckAccess((mode eq modeAppend?
                lv buffer>>ILD.appendProt, lv buffer>>ILD.writeProt),
                owner) resultis ecAccessDenied
            ifnot
               [ // File does not exist, must create it.
               // Make sure not trying to create new version of DIF
               if fd>>FD.dr>>DR.type eq drTypeDIF resultis ecIllegalDIFAccess
   
               // Check DIF for create permission and
               // disk utilization within allocation.
               let dfd = GetDIFRec(fd)  // Get DIF dir entry
               if dfd eq 0 resultis ecDirNotFound
               let difRec = DIFRecFromDR(dfd>>FD.dr)
               unless CheckAccess(lv difRec>>DIFRec.createProt, owner) do
                  [ DestroyFD(dfd); resultis ecAccessDenied ]
               unless CheckAllocation(difRec) do
                  [ DestroyFD(dfd); resultis ecAllocExceeded ]
   
               // Now get default properties from the previous
               // version if any, or from the DIF otherwise.
               ec = LookupFD(fd, lockWrite)
               if ec eq 0 then
                  [
                  test fd>>FD.lookupStatus eq lsOtherVersion
                     ifso
                        [ // Previous version exists, get its leader page
                        TransferLeaderPage(fd, buffer)
                        manifest oNIP = offset ILD.inhProps/16+lenInhProps
                        Zero(buffer+oNIP, fs>>IFS.pageLength-oNIP)
                        ]
                     ifnot
                        [ // No previous version, use DIF properties
                        Zero(buffer, fs>>IFS.pageLength)
                        MoveBlock(lv buffer>>ILD.inhProps,
                         lv difRec>>DIFRec.inhProps, lenInhProps)
                        ]
   
                  // Ensure uniform capitalization of directory name
                  ByteBlt(lv fd>>FD.dr>>DR.pathName, 2,
                   lv dfd>>FD.dr>>DR.pathName, 2, fd>>FD.lenDirString-2)
   
                  // Now time to actually create the file
                  if fd>>FD.lookupStatus ne lsExists then
                     ec = CreateIFSFile(fd, buffer)
                  ]

               UnlockDirFD(fd)
               DestroyFD(dfd)
               if ec ne 0 resultis ec
               ]
         endcase

      default:
         IFSError(ecUndefinedOpenMode)
      ]
   
   // Lock the file in the Open File Table
   ec = LookupFD(fd, lockRead)
   if ec eq 0 & fd>>FD.lookupStatus ne lsExists then ec = ecFileNotFound
   if ec eq 0 then unless LockFile(fd, mode) do ec = ecFileBusy
   UnlockDirFD(fd)
   resultis ec
   ]

unless callerSuppliedBuffer do SysFree(buffer)
resultis result
]

//----------------------------------------------------------------------------
and CreateIFSFile(fd, buffer) = valof
//----------------------------------------------------------------------------
// Creates a new IFS file whose name is described by the fd.
// The buffer is used to write the leader page.  it must
// already contain all properties besides those
// that can be derived from the fd and userInfo.
// Returns zero if successful and an error code if unsuccessful.
// Directory must be write-locked by caller and remains so upon return.
[
let fs = fd>>FD.fs
let dr = fd>>FD.dr
CopyString(lv buffer>>ILD.pathName, lv dr>>DR.pathName)
CopyString(lv buffer>>ILD.author,
 CtxRunning>>RSCtx.userInfo>>UserInfo.userName)

// Decide which unit to create the file on
let unit = EmptiestUnit(fs)
if unit ls 0 then resultis ecFileSystemFull

// Make a name to hand to TFS.
// It is the first 39 characters of the IFS filename
let tfsName = ExtractSubstring(lv dr>>DR.pathName,
 1, Min(dr>>DR.pathName.length, maxLengthFn))

// Create file
CreateDiskFile(fs>>IFS.lpdt↑unit, tfsName, lv dr>>DR.fp, 0, 0, 0, buffer)
dr>>DR.fp.unit = unit
SysFree(tfsName)

// Create directory entry
CreateDirectoryEntry(fd)
fd>>FD.lookupStatus = lsExists
fd>>FD.oldPageNumber = -1  // so leader page is counted in disk allocation
resultis 0
]

//----------------------------------------------------------------------------
and CreateIFSStream(fd, itemSize) = valof
//----------------------------------------------------------------------------
// Creates and returns a stream for an open (locked) file described by fd.
// Returns zero if unsuccessful.
[
let ksType = selecton fd>>FD.mode into
   [  // Select correct mode for streams package
   case modeRead: ksTypeReadOnly
   case modeWrite:
   case modeAppend: ksTypeWriteOnly
   case modeReadWrite: ksTypeReadWrite
   // This catches modeReadWriteShared, which is illegal for stream access
   default: IFSError(ecUndefinedOpenMode)
   ]
let str = CreateDiskStream(lv fd>>FD.dr>>DR.fp, ksType, itemSize,
 IFSCleanup, IFSErrors, 0, nil, GetDiskFromFD(fd))
if str ne 0 then
   [
   str>>ST.par1 = fd  //remember fd for this stream

   // If writing, remember the page number of the last page
   // so we can update the page utilization when the file is closed.
   // If we just created the file, CreateIFSFile has already set it to -1.
   if fd>>FD.oldPageNumber eq 0 then
      fd>>FD.oldPageNumber = KsHintLastPageFa(str)>>FA.pageNumber

   // If appending, position the stream to end-of-file
   if fd>>FD.mode eq modeAppend then FileLength(str)
   ]
resultis str
]

//----------------------------------------------------------------------------
and StreamsFD(str) = str>>ST.par1
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and CreateDirectoryEntry(fd) be
//----------------------------------------------------------------------------
// Directory must be write-locked by caller.
[
ModifyDirFD(fd)  // Mark directory as having been modified
UpdateRecord(fd>>FD.fs>>IFS.dirBTree, fd, DirEntryGenerator, fd, 0,
 fd>>FD.pathStk, true)
]

//----------------------------------------------------------------------------
and DirEntryGenerator(record, fd) = valof
//----------------------------------------------------------------------------
// Procedure called from UpdateRecord to supply the directory
// entry being inserted.  The entry should not already exist
// except in the case of a DIF being updated.
[
let dr = fd>>FD.dr
test record eq 0
   ifso
      record = SysAllocate(dr>>DR.length)
   ifnot
      unless dr>>DR.type eq drTypeDIF do
         IFSError(ecDirEntryAlreadyExists)
MoveBlock(record, dr, dr>>DR.length)
resultis record
]

//----------------------------------------------------------------------------
and IFSCleanup(str, buffer) be
//----------------------------------------------------------------------------
// Cleanup procedure called from the streams package when a stream is closed.
// fd>>FD.mode may be set by the caller to one of the private "open modes"
// to indicate special handling:
//   modeDontUnlock: leave the file locked and don't destroy the FD
//   modeDontDestroy: don't destroy the FD
// Ordinarily the file is unlocked and the FD destroyed.
[
let fd = str>>ST.par1
if fd eq 0 return  // Stream never really got created

// Compute change in length of file.  The end hint is up-to-date now since
// TruncateDiskStream has been done by this point if it is to be done at all.
CloseIFSFile(fd, KsHintLastPageFa(str)>>FA.pageNumber - fd>>FD.oldPageNumber)
unless fd>>FD.mode ge modeDontUnlock do DestroyFD(fd)
]

//----------------------------------------------------------------------------
and CloseIFSStream(str, dontUnlock; numargs na) = valof
//----------------------------------------------------------------------------
// Closes an IFS file given a stream handle, and destroys the
// stream, but does not destroy the fd.  If dontUnlock is supplied and true,
// does not unlock the file.  Returns the fd.
[
let fd = str>>ST.par1
fd>>FD.mode = na gr 1 & dontUnlock? modeDontUnlock, modeDontDestroy
Closes(str)  // Destroy stream and close file
resultis fd
]

//----------------------------------------------------------------------------
and CloseIFSFile(fd, dPages; numargs na) be
//----------------------------------------------------------------------------
// Closes an IFS file given its fd, but does not destroy the fd.
// If dPages is supplied and nonzero, the page utilization for
// the directory is updated in the DIF.
[
if na ge 2 & dPages ne 0 then
   UpdateDIFRec(fd, UpdatePageUsage, dPages)

// Now unlock the file unless we were told not to by CloseIFSStream.
unless fd>>FD.mode eq modeDontUnlock do UnlockFile(fd)
]

//----------------------------------------------------------------------------
and UpdatePageUsage(record, dPages) = valof
//----------------------------------------------------------------------------
[
if record eq 0 then IFSError(ecCantFindDIF)
let difRec = DIFRecFromDR(record)
DoubleIncrement(lv difRec>>DIFRec.diskPageUsage, dPages)
if difRec>>DIFRec.diskPageUsage↑0 ls 0 then
   Zero(lv difRec>>DIFRec.diskPageUsage, 2)  // don't let it go negative
resultis record
]

//----------------------------------------------------------------------------
and IFSErrors(str, ec, par) be
//----------------------------------------------------------------------------
[
SysErr(str, ec, par)  // ***just punt for now
]