-- File: Locker.mesa,  Last Edit: HGM  December 18, 1980  10:48 PM

DIRECTORY
  Storage USING [CopyString, Free, FreeString, Node],
  String USING [EquivalentString],
  Lock USING [Lock, LockObject, ReadWrite];

Locker: MONITOR IMPORTS Storage, String EXPORTS Lock =
  BEGIN OPEN Lock;

  first: Lock ← NIL;

  EnumerateLocks: PUBLIC ENTRY PROCEDURE [proc: PROCEDURE [Lock]] =
    BEGIN
    FOR lock: Lock ← first, lock.next UNTIL lock = NIL DO proc[lock]; ENDLOOP;
    END;

  GetLockLocation: PUBLIC PROCEDURE RETURNS [POINTER TO Lock] =
    BEGIN RETURN[@first]; END;

  wholeDiskBusy: BOOLEAN ← FALSE;
  lockFree: CONDITION;
  DiskNotBusy: ERROR = CODE;

  LockDiskAndWait: PUBLIC ENTRY PROCEDURE [fileName: STRING, why: ReadWrite] =
    BEGIN
    ok: BOOLEAN ← why # write;
    UNTIL ok DO
      FOR lock: Lock ← first, lock.next UNTIL lock = NIL DO
	IF String.EquivalentString[lock.name, fileName] THEN
	  BEGIN WAIT lockFree; EXIT; END;
	REPEAT FINISHED => ok ← TRUE;
	-- not in list yet, nobody else reading or writing

	ENDLOOP;
      ENDLOOP;
    FOR lock: Lock ← first, lock.next UNTIL lock = NIL DO
      IF String.EquivalentString[lock.name, fileName] THEN
	BEGIN
	lock.useCount ← lock.useCount + 1;
	UNTIL ~lock.write DO WAIT lockFree; ENDLOOP;
	IF why = write THEN lock.write ← TRUE;
	EXIT;
	END;
      REPEAT
	FINISHED =>
	  BEGIN
	  new: Lock ← Storage.Node[SIZE[LockObject]];
	  new↑ ← [first, Storage.CopyString[fileName], 1, why = write];
	  first ← new;
	  END;
      ENDLOOP;
    END;

  LockDisk: PUBLIC ENTRY PROCEDURE [
    fileName: STRING, why: ReadWrite, fast: BOOLEAN] RETURNS [ok: BOOLEAN] =
    BEGIN
    IF fast AND wholeDiskBusy THEN RETURN[FALSE];
    FOR lock: Lock ← first, lock.next UNTIL lock = NIL DO
      IF String.EquivalentString[lock.name, fileName] THEN
	BEGIN
	IF lock.write OR why = write THEN RETURN[FALSE];
	lock.useCount ← lock.useCount + 1;
	EXIT;
	END;
      REPEAT
	FINISHED =>
	  BEGIN
	  new: Lock ← Storage.Node[SIZE[LockObject]];
	  new↑ ← [first, Storage.CopyString[fileName], 1, why = write];
	  first ← new;
	  END;
      ENDLOOP;
    IF fast THEN wholeDiskBusy ← TRUE;
    RETURN[TRUE];
    END;

  UnlockDisk: PUBLIC ENTRY PROCEDURE [fileName: STRING, fast: BOOLEAN] =
    BEGIN
    where: POINTER TO Lock ← @first;
    FOR lock: Lock ← first, lock.next UNTIL lock = NIL DO
      IF String.EquivalentString[lock.name, fileName] THEN
	BEGIN
	lock.useCount ← lock.useCount - 1;
	lock.write ← FALSE;
	IF lock.useCount = 0 THEN
	  BEGIN
	  where↑ ← lock.next;
	  Storage.FreeString[lock.name];
	  Storage.Free[lock];
	  END;
	EXIT;
	END;
      where ← @lock.next;
      REPEAT FINISHED => ERROR DiskNotBusy;
      ENDLOOP;
    IF fast THEN wholeDiskBusy ← FALSE;
    BROADCAST lockFree;
    END;

  END.