// IFSGroupName.bcpl -- group number to name mapping
// Copyright Xerox Corporation 1981, 1982

// Last modified May 26, 1982  5:18 PM by Taft

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

external
[
// outgoing procedures
InitGroupName; GetGroupName; SetGroupName

// incoming procedures
VFileReadPage; VFileWritePage
LockCell; Lock; Unlock
SysAllocateZero; Zero; MoveBlock; ExtractSubstring; StringCompare

// incoming statics
infoVMD
]

static [ @gns = 0 ]

//----------------------------------------------------------------------------
structure GNS:  // Group Name State -- permanent resident data structure
//----------------------------------------------------------------------------
[
gn word			// -> in-core copy of GN page; zero if none.
			// Note that there is only one of these, regardless of
			// the number of simultaneous clients; we depend on
			// VMem to maintain at most one copy of GN at a time.
lock @Lock		// for mutual exclusion
]
manifest
[
lenGNS = size GNS/16
nGroupNames = size Protection +1  // extra one is "world" group name
]

//----------------------------------------------------------------------------
structure GN:  // layout of gnPage of <System>Info
//----------------------------------------------------------------------------
[
seal word		// = gnSeal
blank word 2		// unused at present
free word		// offset of first free word
name↑0,nGroupNames-1 word  // offsets of name strings; 0 => none
blank word 9		// for future expansion
strings word 0		// rest of page holds name strings
]

manifest
[
gnSeal = 063512B  // change this whenever GN structure changes incompatibly
stdPageLength = 1 lshift logStdPageLength
ecGroupNameTableFull = 70
ecDuplicateGroupName = 71
ecIllegalGroupNumber = 72
ecIllegalGroupName = 73
]

//----------------------------------------------------------------------------
let InitGroupName() be
//----------------------------------------------------------------------------
// Initializes this module and initializes the table if its seal is bad.
[
if gns eq 0 then
   [ gns = SysAllocateZero(lenGNS); LockCell(lv gns>>GNS.gn) ]

let gn = LockGroupNameTable(true)
UnlockGroupNameTable()

unless gn>>GN.seal eq gnSeal do
   [
   // Playing fast and loose with the unlocked GN page here.......
   // However, UnlockGroupNameTable does not call Block, so we are safe until
   // the calls to SetGroupName.  Those calls must be made with the GN page
   // unlocked or else they will deadlock.
   Zero(gn, stdPageLength)
   gn>>GN.seal = gnSeal
   gn>>GN.free = offset GN.strings/16

   SetGroupName(offset Protection.owner, "Owner")
   SetGroupName(offset Protection.world, "World")
   ]
]

//----------------------------------------------------------------------------
and GetGroupName(groupNum) = valof
//----------------------------------------------------------------------------
// Returns a new string which is the group name corresponding to groupNum,
// or zero if groupNum has no name.
// groupNum = nGroupNames-1 (= size Protection) refers to the special group
// which holds the real name of the "world" group.
[
let gn = LockGroupNameTable(false)

let name = groupNum uge nGroupNames % gn>>GN.name↑groupNum eq 0? 0,
    ExtractSubstring(gn + gn>>GN.name↑groupNum)

UnlockGroupNameTable()
resultis name
]

//----------------------------------------------------------------------------
and SetGroupName(groupNum, name) = valof
//----------------------------------------------------------------------------
// Makes name be the new name for groupNum.  Returns true normally,
// or an error code if unsuccessful.  name=0 or name.length=0 deletes
// the existing name for groupNum.
[
let gn = LockGroupNameTable(true)

let res = valof
   [
   if name>>String.length eq 0 then name = 0
   if groupNum uge nGroupNames resultis ecIllegalGroupNumber
   let newLen = name eq 0? 0, name>>String.length rshift 1 +1
   let oldOffset = gn>>GN.name↑groupNum
   let oldLen = oldOffset eq 0? 0, (gn+oldOffset)>>String.length rshift 1 +1
   if gn>>GN.free+newLen-oldLen ugr stdPageLength then
      resultis ecGroupNameTableFull

   if name ne 0 then
      [
      for i = 0 to (size Protection)-1 do
         if StringCompare(name, gn+gn>>GN.name↑i) eq 0 then
	    resultis ecDuplicateGroupName
      if StringCompare(name, "None") eq 0 resultis ecIllegalGroupName
      let nonDigit = false
      for i = 1 to name>>String.length do
         if (name>>String.char↑i-$0) ugr 9 then [ nonDigit = true; break ]
      unless nonDigit resultis ecIllegalGroupName  // all digits
      ]

   if oldLen ne 0 then
      [ // squeeze out old name and fix pointers to ones that move
      MoveBlock(gn+oldOffset, gn+oldOffset+oldLen,
       gn>>GN.free-(oldOffset+oldLen))
      for i = 0 to nGroupNames-1 do
         if gn>>GN.name↑i gr oldOffset then
	    gn>>GN.name↑i = gn>>GN.name↑i - oldLen
      gn>>GN.free = gn>>GN.free - oldLen
      gn>>GN.name↑groupNum = 0
      ]

   if name ne 0 then
      [
      // add new name at end and put offset in table
      MoveBlock(gn+gn>>GN.free, name, newLen)
      gn>>GN.name↑groupNum = gn>>GN.free
      gn>>GN.free = gn>>GN.free+newLen
      ]

   resultis 0
   ]

UnlockGroupNameTable()
resultis res
]

// Internal procedures

//----------------------------------------------------------------------------
and LockGroupNameTable(write) = valof
//----------------------------------------------------------------------------
[
Lock(lv gns>>GNS.lock, write)
// If multiple reads are in progress, GNS.gn is already nonzero, but we
// overwrite it anyway.  This is ok because GNS.gn is a VMem lock word,
// and VMem is guaranteed always to return the same pointer to a locked page.
gns>>GNS.gn = (write? VFileWritePage, VFileReadPage)(infoVMD, gnPage)
resultis gns>>GNS.gn
]

//----------------------------------------------------------------------------
and UnlockGroupNameTable() be
//----------------------------------------------------------------------------
[
// If we are the last locker of the GNS then unlock the page for VMem.
// This order of operations is a bit flakey -- it depends on Unlock not
// calling Block.  But then the other order isn't really much of an
// improvement, and it's more complicated to program.
Unlock(lv gns>>GNS.lock)
if gns>>GNS.lock.count eq 0 then gns>>GNS.gn = 0
]