// IfsCachedDIF.bcpl
// Copyright Xerox Corporation 1981
// Last modified January 4, 1982  3:31 PM by Taft

get "Ifs.decl"
get "IfsSystemInfo.decl"
get "IfsFiles.decl"
get "IfsRS.decl"

external
[
// outgoing procedures
InitCachedDIF; GetDIF; UpdateCachedDIF; InvalidateCachedDIF

// incoming procedures
ReadDIF; WriteDIF; WheelCall; MakeQualifiedRName; StripDefaultReg
VFileWritePage; LockCell
CopyString; StringCompare
Lock; Unlock; SysAllocateZero; SysFree; FreePointer
DefaultArgs; MoveBlock; IFSError; Block

// incoming statics
infoVMD; CtxRunning
]

static [ @dc = 0 ]  // -> in-core version of DC, if nonzero

// This module maintains a write-through cache of partial DIFs (Directory
// Information Files).  The DIF fields maintained are: connectProt,
// timeLastValid, miscDirAttributes, password, userGroups, and capabilities.
// GetDIF ordinarily returns a DIF record that is valid only for these
// fields; however, it can be forced to read the DIF file itself so as to
// ensure that all fields are valid.
// Updates affecting only these fields may be made by calling
// UpdateCachedDIF; these updates will write through to the underlying DIF.
// Updates of other fields must be made by calling WriteDIF.

// If Grapevine authentication is enabled (enableGrapevineAuth), then
// it is possible for the cache to contain DIF entries that do not
// correspond to a real DIF file.  In this case, of course, updates do not
// write through.

// Important note on registry qualifications:
// If Grapevine authentication is enabled, the cache is always keyed on
// fully-qualified names -- regardless of whether such qualification is
// meaningful (i.e., corresponds to Grapevine names); however, the cache
// also remembers whether or not the underlying DIF name is qualified.
// This implies that the cache must be flushed when Grapevine authentication
// is enabled or disabled.

// Procedures in this module bypass all access control.  Any required
// access control must be performed by the caller.

// Note: the DIF Cache is a temporary structure that does not survive
// restarts of IFS.  Therefore the data structures may be changed freely.

structure DC:	// DIF Cache: layout of difPage of <System>Info
[
end word		// offset of first free word
age word		// age of cache
lock @Lock		// interlock for cache activity
firstDCE word 0		// first DCE starts here
]

structure DCE:	// DIF Cache entry
[
length word
age word		// age of most recent access to this entry
name word		// relative -> directory name
owner word		// relative -> owner string
flags word =
   [
   unqualified bit	// true DIF name is not qualified by registry
   ]
// stuff from DIF -- see IfsFiles.decl
connectProt @Protection
timeLastValid word 2
hintNotUserGroups @Protection
// *** following stuff must be parallel to DIF -- from here ***
miscDirAttributes word =
   [
   filesOnly bit
   blank bit 13
   validGrapevineRName bit
   nonexistentDIF bit
   ]
password word size DIF.password/16
userGroups @Protection
capabilities @Capabilities
// *** to here. ***
// end of stuff from DIF
strings word 0
]
manifest lenDCE = size DCE/16  // fixed portion only
manifest nWordsParallelDIF =
 (offset DCE.strings - offset DCE.miscDirAttributes)/16

manifest
[
stdPageLength = 1 lshift logStdPageLength
ecDIFCache = 123
]

//----------------------------------------------------------------------------
let InitCachedDIF() be
//----------------------------------------------------------------------------
// Called to initialize or flush the cache.
[
if LockDIFCache() then
   [
   dc>>DC.end = offset DC.firstDCE/16
   LockCell(lv dc)  // no-op if already locked
   dc = 0
   ]
]

//----------------------------------------------------------------------------
and GetDIF(name, bypassCache, lvNameDIF; numargs na) = valof
//----------------------------------------------------------------------------
// Returns a copy of the DIF corresponding to supplied directory name.
// Caller must free it to SysZone when done with it.
// Returns zero if no such DIF exists either in the cache or in the
// file system.
// If name is unqualified, the default registry is assumed.
// If name is omitted, the current context's userName is used.
// If bypassCache, the DIF is always read from the file system so that all
// fields of the result will be valid; and zero is returned if the DIF
// does not exist in the file system, even if one does exist in the cache.
// If lvNameDIF is supplied, GetDIF stores in @lvDIF a pointer to a new
// string containing the true DIF name, which may or may not be qualified
// and which may be capitalized differently.
[
if na eq 0 then name = 0  // DefaultArgs doesn't work for this
DefaultArgs(lv na, -1, false, 0)

let localName = MakeQualifiedRName((name ne 0? name,
 CtxRunning>>RSCtx.userInfo>>UserInfo.userName))

// Try to find entry for name in cache.  localName is fully qualified.
let dif = 0
let dce = LockDIFCache()? FindCachedDIF(localName), 0
if dce ne 0 then
   [ // Found name in cache.  Copy the true DIF name.
   CopyString(localName, dce + dce>>DCE.name)
   if dce>>DCE.unqualified then StripDefaultReg(localName)
   unless bypassCache do
      [ // construct DIF containing cached information
      dif = SysAllocateZero(lenDIF)
      CopyString(lv dif>>DIF.owner, dce + dce>>DCE.owner)
      MoveBlock(lv dif>>DIF.connectProt, lv dce>>DCE.connectProt,
       lenProtection)
      MoveBlock(lv dif>>DIF.hintNotUserGroups, lv dce>>DCE.hintNotUserGroups,
       lenProtection)
      MoveBlock(lv dif>>DIF.timeLastValid, lv dce>>DCE.timeLastValid, 2)
      MoveBlock(lv dif>>DIF.miscDirAttributes, lv dce>>DCE.miscDirAttributes,
       nWordsParallelDIF)
      ]
   ]

// Now localName matches DIF name if DIF was found in cache,
// else is fully qualified.  Stop here if the DIF was found in the cache
// and the cache knows the DIF is nonexistent in the file system.
if dif eq 0 & not (dce ne 0 & dce>>DCE.nonexistentDIF) then
   [
   // Not in cache, or forced read; must read DIF from file system.
   // Note that ReadDIF overwrites its name argument with the true DIF name.
   let unqualified = false
   dif = WheelCall(ReadDIF, localName)
   if dif eq 0 & StripDefaultReg(localName) then
      [ unqualified = true; dif = WheelCall(ReadDIF, localName) ]
   if dif ne 0 & dce eq 0 & dc ne 0 then
      [ // found, put DIF in cache
      let fullName = MakeQualifiedRName(localName)
      InsertCachedDIF(name, dif)>>DCE.unqualified = unqualified
      SysFree(fullName)
      ]
   ]

dc = 0
test dif ne 0 & lvNameDIF ne 0
   ifso @lvNameDIF = localName
   ifnot SysFree(localName)

resultis dif
]

//----------------------------------------------------------------------------
and InvalidateCachedDIF(name) be
//----------------------------------------------------------------------------
// Invalidates cache entry for name, if there is one.
// If name is unqualified, the default registry is assumed.
[
if LockDIFCache() then
   [
   let localName = MakeQualifiedRName(name)
   let dce = FindCachedDIF(localName)
   if dce ne 0 then DeleteCachedDIF(dce)
   SysFree(localName)
   dc = 0
   ]
]

//----------------------------------------------------------------------------
and UpdateCachedDIF(name, dif) be
//----------------------------------------------------------------------------
// Puts dif into the cache, replacing any existing cache entry for name.
// Additionally, if an underlying DIF exists, rewrites the underlying DIF
// with the new information.  Note that only the fields kept in the cache are
// rewritten to the underline DIF; remaining fields are unchanged.
// If name is unqualified, the default registry is assumed.
[
let fullName = MakeQualifiedRName(name)
let unqualified = false
let difName = 0

// This will fail if there is no underlying DIF, even if there is a
// cache entry for name.
let rdif = GetDIF(name, true, lv difName)
dif>>DIF.nonexistentDIF = rdif eq 0
if rdif ne 0 then
   [
   // Found underlying DIF.  Update valid fields from the new DIF
   // and rewrite it.
   CopyString(lv rdif>>DIF.owner, lv dif>>DIF.owner)
   MoveBlock(lv rdif>>DIF.connectProt, lv dif>>DIF.connectProt,
    lenProtection)
   MoveBlock(lv rdif>>DIF.hintNotUserGroups, lv dif>>DIF.hintNotUserGroups,
    lenProtection)
   MoveBlock(lv rdif>>DIF.timeLastValid, lv dif>>DIF.timeLastValid, 2)
   MoveBlock(lv rdif>>DIF.miscDirAttributes,
    lv dif>>DIF.miscDirAttributes, nWordsParallelDIF)
   WheelCall(WriteDIF, difName, rdif, 0, true)  // don't flush from cache
   unqualified = StringCompare(fullName, difName) ne 0
   ]

if LockDIFCache() then
   [
   // GetDIF should have brought DIF into the cache already, but
   // conceivably someone else has sneaked in and flushed it.
   let dce = FindCachedDIF(fullName)
   if dce ne 0 then
      [ unqualified = dce>>DCE.unqualified; DeleteCachedDIF(dce) ]
   InsertCachedDIF(fullName, dif)>>DCE.unqualified = unqualified
   dc = 0
   ]

FreePointer(lv fullName, lv difName, lv rdif)
]

// internal procedures

//----------------------------------------------------------------------------
and LockDIFCache() = valof
//----------------------------------------------------------------------------
// Waits until the DC is free, locks it into memory, dirties it, and
// stores a pointer to it in the static dc.
// Caller must unlock it simply by storing zero in dc.
// Returns true normally, false if the <System>Info file hasn't been
// opened yet (early system initialization).
[
while dc ne 0 do Block()
if infoVMD ne 0 then
   [
   dc = -1  // reserve lock while reading difPage from disk
   dc = VFileWritePage(infoVMD, difPage)
   ]
resultis dc ne 0
]

//----------------------------------------------------------------------------
and FindCachedDIF(name) = valof
//----------------------------------------------------------------------------
// Returns dce for DIF matching name, or zero if name not in cache.
// Updates age of matching entry if found.
// If Grapevine authentication is enabled, name must be fully qualified.
// dc must be locked and dirtied by caller.
[
let nameWord0 = name!0 & 177737B  // length and char↑1, ignoring case
let dce = lv dc>>DC.firstDCE
while dce ls dc + dc>>DC.end do
   [
   let dceName = dce + dce>>DCE.name
   if (dceName!0 & 177737B) eq nameWord0 &  // hack to speed search
    StringCompare(name, dceName) eq 0 then
      [ dc>>DC.age = dc>>DC.age+1; dce>>DCE.age = dc>>DC.age; resultis dce ]
   dce = dce + dce>>DCE.length
   ]
resultis 0
]

//----------------------------------------------------------------------------
and InsertCachedDIF(name, dif) = valof
//----------------------------------------------------------------------------
// Inserts cache entry for name using data from dif; returns pointer to dce.
// If Grapevine authentication is enabled, name must be fully qualified.
// name must not already exist in the cache.
// dc must be locked and dirtied by caller.
[
let dce = nil
let len = lenDCE + name>>String.length rshift 1 +
 dif>>DIF.owner.length rshift 1 + 2
until dc>>DC.end+len le stdPageLength do
   [ // Repeatedly delete oldest entry until new entry will fit
   let oldest = lv dc>>DC.firstDCE
   dce = oldest
   while dce ls dc+dc>>DC.end do
      [
      if dc>>DC.age-dce>>DCE.age ugr dc>>DC.age-oldest>>DCE.age then
         oldest = dce
      dce = dce + dce>>DCE.length
      ]
   if dce eq oldest then IFSError(ecDIFCache)
   DeleteCachedDIF(oldest)
   ]

// Make new entry and copy stuff from DIF into it
dce = dc + dc>>DC.end
dc>>DC.end = dc>>DC.end + len
dce>>DCE.length = len
dc>>DC.age = dc>>DC.age + 1
dce>>DCE.age = dc>>DC.age
dce>>DCE.name = offset DCE.strings/16
CopyString(dce + dce>>DCE.name, name)
dce>>DCE.owner = dce>>DCE.name + name>>String.length rshift 1 +1
CopyString(dce + dce>>DCE.owner, lv dif>>DIF.owner)
MoveBlock(lv dce>>DCE.connectProt, lv dif>>DIF.connectProt, lenProtection)
MoveBlock(lv dce>>DCE.hintNotUserGroups, lv dif>>DIF.hintNotUserGroups,
 lenProtection)
MoveBlock(lv dce>>DCE.timeLastValid, lv dif>>DIF.timeLastValid, 2)
MoveBlock(lv dce>>DCE.miscDirAttributes,
 lv dif>>DIF.miscDirAttributes, nWordsParallelDIF)
resultis dce
]

//----------------------------------------------------------------------------
and DeleteCachedDIF(dce) be
//----------------------------------------------------------------------------
// Deletes dce from cache.
// dc must be locked and dirtied by caller.
[
let len = dce>>DCE.length
MoveBlock(dce, dce+len, dc>>DC.end - (dce+len-dc))
dc>>DC.end = dc>>DC.end - len
]