// IfsVMemFileSwap.bcpl -- VMem access to IFS files -- swappable
// Copyright Xerox Corporation 1979, 1981

// Last modified November 17, 1981  9:19 PM by Taft

get "Ifs.decl"
get "IfsVMem.decl"
get "IfsFiles.decl"
get "Disks.d"
get "IfsDirs.decl"

external
[
// outgoing procedures
OpenVFile; OpenVFileFromFP; CloseVFile; MarkDirtyVFiles

// incoming procedures
VFileDOPAGEIO; VFilePageType; VFileWritePage
VFilePageGroupBase; VFilePageGroupAlign; VFilePageGroupSize
LookupIFSFile; DestroyFD; IFSError; ReadCalendar
InitFmap; AllocateVMem; FreeVMem; PurgeVMem; Lock; Unlock
DefaultArgs; SysAllocateZero; FreePointer; Noop

// outgoing statics
vFileVMI

// incoming statics
sysZone; primaryIFS; vmdt
]

static vFileVMI = 0

manifest ecInitFmap = 37

structure FVMI:
[
@VMI
lock @Lock	// lock for open/close of VFiles
timer word
]
manifest lenFVMI = size FVMI/16

//----------------------------------------------------------------------------
let OpenVFile(name, pages, lenFileMap, writeFileMap, fs; numargs na) = valof
//----------------------------------------------------------------------------
// Initiates VMEM access to an existing file given its IFS name.
// See OpenVFileFromFP (below) for details on other arguments.
// Returns zero if the file cannot be opened.
// Note: at the moment, this procedure does NOT lock the file.
[
DefaultArgs(lv na, -1, 0, 0, 0, primaryIFS)
let fd = LookupIFSFile(name, lcVHighest, 0, fs)
if fd eq 0 resultis 0
let fp = lv fd>>FD.dr>>DR.fp
let vmd = OpenVFileFromFP(fp, fs>>IFS.lpdt↑(fp>>IFP.unit), pages,
 lenFileMap, writeFileMap)
DestroyFD(fd)
resultis vmd
]

//----------------------------------------------------------------------------
and OpenVFileFromFP(fp, disk, pages, lenFileMap, writeFileMap;
    numargs na) = valof
//----------------------------------------------------------------------------
// Initiates VMEM access to a file given its FP.
// Pages is max number of file pages that will be accessible (default 1000).
// lenFileMap is the length to allocate for the ISF file map (minimum ~25,
// default 50).  Page 1 of the file is used to store the file map if
// writeFileMap is true (default false).
// Returns a VMD to be passed to VFileReadPage and VFileWritePage.
[
DefaultArgs(lv na, -2, 1000, 50, false)
if vFileVMI eq 0 then
   [
   vFileVMI = SysAllocateZero(lenFVMI)
   vFileVMI>>VMI.DOPAGEIO = VFileDOPAGEIO
   vFileVMI>>VMI.CleanupLocks = Noop
   vFileVMI>>VMI.PageType = VFilePageType
   vFileVMI>>VMI.PageGroupBase = VFilePageGroupBase
   vFileVMI>>VMI.PageGroupAlign = VFilePageGroupAlign
   vFileVMI>>VMI.PageGroupSize = VFilePageGroupSize
   vFileVMI>>VMI.type = vmiTypeVFile
   ]
Lock(lv vFileVMI>>FVMI.lock, true)
let vmd = SysAllocateZero(lenFVMD)
vmd>>FVMD.fileMap = SysAllocateZero(lenFileMap)
vmd>>FVMD.logVPagesPerDiskPage = disk>>DSK.lnPageSize-logVMPageLength
vmd>>FVMD.logPageGroupSize = vmd>>FVMD.logVPagesPerDiskPage
AllocateVMem(vmd, vFileVMI, pages lshift vmd>>FVMD.logVPagesPerDiskPage)
unless InitFmap(vmd>>FVMD.fileMap, lenFileMap, fp, writeFileMap, 0,
 sysZone, disk) do IFSError(ecInitFmap)
Unlock(lv vFileVMI>>FVMI.lock)
resultis vmd
]

//----------------------------------------------------------------------------
and CloseVFile(vmd) be
//----------------------------------------------------------------------------
[
PurgeVMem(vmd)
Lock(lv vFileVMI>>FVMI.lock, true)
MarkVFileIfDirty(vmd)
FreeVMem(vmd)
FreePointer(lv vmd>>FVMD.fileMap, lv vmd)
Unlock(lv vFileVMI>>FVMI.lock)
]

//----------------------------------------------------------------------------
and MarkDirtyVFiles() be
//----------------------------------------------------------------------------
// called every 15 seconds to update the write dates of open VFiles that have
// been dirtied.
[
vFileVMI>>FVMI.timer = (vFileVMI>>FVMI.timer+1) rem 60
if vFileVMI>>FVMI.timer ne 0 return  // do this only every 15 minutes

Lock(lv vFileVMI>>FVMI.lock)

// "manual" for-loop so VMDT.length is evaluated on every iteration
let i = 1
while i le vmdt>>VMDT.length do
   [
   let vmd = vmdt>>VMDT.vmd↑i
   if vmd>>VMD.vmi>>VMI.type eq vmiTypeVFile then MarkVFileIfDirty(vmd)
   i = i+1
   ]

Unlock(lv vFileVMI>>FVMI.lock)
]

//----------------------------------------------------------------------------
and MarkVFileIfDirty(vmd) be
//----------------------------------------------------------------------------
   if vmd>>FVMD.dirty then
      [
      let ld = VFileWritePage(vmd, 0)
      ReadCalendar(lv ld>>ILD.created)
      ReadCalendar(lv ld>>ILD.written)
      // this works because writing the leader page doesn't count as dirtying
      vmd>>FVMD.dirty = false
      ]