Copyright (C) 1994, Digital Equipment Corp.
LockOps.def
INTERFACELockOps ;
This is the package locking interface which the local lock server presents to its packagetool clients. This interface does not depend on the existence of packagetool siphons, but it does provide consistent semantics if one or more siphons do exist. Locks may either be managed by the local site, in which case the actual lock key is held in the local database, or managed remotely, in which case the local database contains a pointer to the managing site.
IMPORT Fingerprint, PackageObj, PkgErr, Time; TYPE T <: ROOT; (* lock server instance, NIL means the local one *) Owner = RECORD key: TEXT; site: SiteName; END; ShipArray = ARRAY OF PackageObj.Ship; (* NIL elements are ignored *) SiteName = TEXT; Auth = PackageObj.Auth; Dir = PackageObj.Dir; Instance = PackageObj.Instance; PN = PackageObj.PN; VN = PackageObj.VN; Version = PackageObj.Version; (* There is one database entry for each package at each site. Only one lock server manages the locks for any given package. This server's site is stored as "managedBy". If the package is locked, the owner's key and site are stored in "owner". Packages are managed by a version number scheme. Each instance of a package ship acquires a new version number. Version numbers are monotonically increasing and are stored with the actual package data at each replicas. Three version numbers are stored in the lock database for each package. The "curVN" describes the most recent version successfully shipped. "lastVN" describes the last version number to have be issued to a ship operation. Finally, a non-zero "pendVN" indicates that the replicas are in the process of rolling over to the indicated version. The actual file bits are moved to be replicas prior to initiation of "rollover". This serves to shrink the window during which the replicas are inconsistent to a single directory rename. If the lock server crashes with pendVN # 0, then the lock server queries the replicas as to their most recent version and sets curVN appropriately. When a client checks out a package, he gets the version number of the most recent version. Only replicas which hold that version (or possibly later versions) are eligible to act as a source for the package. Lock entries are removed from the database *very* infrequently. An entry is considered to be "deleted" when curVN is set to DeletedVN. At that point it can be recycled (for another create). A fingerprint of the package content is stored as "fp". The "fpValid" field indicates whether this fingerprint is valid for curVN. *) TYPE DirList = REF ARRAY OF Dir; EnumList = REF ARRAY OF EnumEntry; EnumEntry = RECORD e: Entry; arc: TEXT; fill: TEXT := NIL; END; RefEntry = REF Entry; Entry = RECORD lastModified: Time.T; owner: Owner; (* who owns the lock for this package *) (* package is not lock iff owner.key = NIL *) managedBy: SiteName; (* the siphon site managing this package lock *) fill: TEXT := NIL; (* for alignment *) fp: Fingerprint.T; (* for fast package content comparison *) (* Fingerprint.Zero if invalid *) instance: Instance; (* package create instance timestamp *) curVN: VN; (* package version number at local site *) lastVN: VN; (* most recent version number (unique) *) pendVN: VN; (* non-zero if Commit in progress *) (* note that the following fields are significant only at the managing site: owner, lastVN *) END; TYPE BreakRights = {OwnerOnly, (* caller must be the lock owner *)SiteOnly, (* caller must be at lock owning site *) AnySite(* anyone may unlock *)}; TYPE CommitEC = PkgErr.TL; (* iff non-NIL, an AtomList from PkgErr.i3 *) CommitFailures = REF ARRAY OF CommitEC; CONST NullVN = 0; (* the first version of a package *) InitialVN = 1; (* the first version of a package *) DeletedVN = LAST (VN); (* the version number for deleted packages *) (* Note that only the first and last versions of a package are empty. Any other Commit of null content is disallowed. *) EXCEPTION LockConflict (Owner); (* says who the real lock owner is *) CommitFailed (CommitFailures); SynchVersions (Version); (* occurs on Unlock when (ver < lastVer) *)procedures
PROCEDURE New (): T RAISES {PkgErr.E}; PROCEDURE SetServerT(t: T); (* register local server object *) PROCEDURE Create (auth: Auth; pn: PN; initialKey: TEXT; version: Version := Version{0, InitialVN}; remoteCheck: BOOLEAN := TRUE; t: T := NIL) RAISES {PkgErr.E}; (* Possible ECs: PackageNameInUse, NoSuchDir *) (* Attempts to create a package lock entry for the named package managed by the local lock server. The package is locked under the supplied "initialKey". Pointers to the new package lock entry will be installed in all domains that share the repository implied by "package". To maintain name consistency between siphon sites, package names are checked for remote existence prior to successful creation. The remote check can be overriden by calling with "remoteCheck" = FALSE. This can lead to database inconsistencies. Package creation at multiple sites is not an atomic transaction. The order of operations is as follows: -- the package is checked for uniqueness locally -- a local in-core lock is acquired on the package name -- all remote sites are checked that package doesn't exist -- the local entry is created and the lock released This all takes place synchronously. The actual remote package entry is created the first time the package is shipped. (There might be a ship for the initial null version.) *) (* raises NoSuchDir if parent directory doesn't exist *) PROCEDURE Remove(auth: Auth; pn: PN; key: TEXT; reship: BOOLEAN := TRUE; t: T := NIL) RAISES {PkgErr.E, CommitFailed, LockConflict}; (* This call removes the named package lock entry after checking lock ownership. If the lock is managed by a remote site, Error(PackageManagedRemotely) is raised. This call also removes the package from all known replicas and a null ship (version.vn = DeletedVN) of the package is queued for transmission to all remote siphons. Package deletion involves a Commit. If the deletion fails because the Commit failed. Then CommitFailed can arise. *) PROCEDURE Lock(auth: Auth; pn: PN; version: Version; key: TEXT; keySite: SiteName := NIL; t: T := NIL): Version RAISES {PkgErr.E, LockConflict}; (* attempts to acquire the named lock *) (* if the lock is managed by a remote site, that site is contacted synchronously through the siphon as part of this call *) (* resultant version describes the most recent version of the package *) (* see comments for PackageDB.Lock *) (* version should be NullVersion() when called locally. the siphon software will supply a non-null version and keySite. A non-null keySite is sufficient to terminate recursion in the case that the callee doesn't manage the package *) PROCEDURE Unlock(auth: Auth; pn: PN; version: Version; key: TEXT; keySite: SiteName := NIL; breakRights: BreakRights := BreakRights.OwnerOnly; forceVersion: BOOLEAN := FALSE; t: T := NIL) RAISES {PkgErr.E, CommitFailed, LockConflict, SynchVersions}; (* Unlocks the named package *) (* If the lock is managed by a remote site, then Unlock is called synchronously accross the siphon *) (* "breakRights" describes how aggressive the system should be in attempting to break existing locks. *) (* (breakRights = AnySite) can only be used at the managing site. *) (* the system asserts that the local (curVN,instance) is equal to (lastVN,instance) at the managing site. this might force a "commit" of null content to take place, if "forceVersion" is false, then Error(OutstandingVersion) is RAISED instead. *) (* See comments for PackageDB.Unlock. *) (* Unlock with forceVersion involves a Commit. If this fails then CommitFailed can arise. *) (* version should be NullVersion() when called locally. the siphon software will supply a non-null version and keySite. A non-null keySite is sufficient to terminate recursion in the case that the callee doesn't manage the package *) (* SynchVersions only shows through if keySite # NIL *) PROCEDURE AssignVersion( auth: Auth; pn: PN; key: TEXT; keySite: SiteName := NIL; t: T := NIL): Version RAISES {PkgErr.E, LockConflict}; (* This procedure is called by the packagetool to initiate a package ship. *) (* Returns a new version number to identify the ship operation. *) (* If the caller holds the lock, then a unique version number is issued. *) (* For foreign packages, the remote siphon is called synchronously. *) (* A non-null keySite is sufficient to terminate recursion in the case that the callee doesn't manage the package *) PROCEDURE Commit (auth: Auth; pn: PN; version: Version; VAR ships: ShipArray; reship: BOOLEAN := TRUE; t: T := NIL): CommitFailures RAISES {CommitFailed, PkgErr.E}; (* Caller should have "prepared" ships (PackageObj.Ship.prepare) to all replicas in "ships". First the lock server checks the validity of the supplied version number and iff valid, marks the entry as unstable. Next the argument replicas are contacted and instructed to commit the ship in progress ... this must succeed for at least one of the named replicas. If successful, the (curVN,instance for the package is set to "version". *) (* The definition of a "valid" version number is as follows: version.instance = entry.instance AND Managing site: lastVN >= version > localCurVersion Foreign site: version > localCurVersion "localCurVersion" is the value of curVN at the local site *) (* This procedure guarantees mutual exclusion over all ships of the argument package. *) (* This operation also makes an entry in the siphon queue if it is appropriate for this package. *) (* If this procedure returns NIL (the normal case) then all replicas have successfully committed the new version. If it returns non-NIL, then the return value indicates which replica(s) (in the same order as the replicas argument) failed and for which reason. If all replicas fail, then the CommitResult is passed back as an argument to the CommitFailed error. *) (* IFF "reship" then package will be enqueued at all siphons *)lock enumeration
PROCEDURE Enumerate (dir: Dir; site: SiteName := NIL; locksOnly: BOOLEAN := TRUE; localOnly: BOOLEAN := TRUE; pendingOnly: BOOLEAN := FALSE; t: T := NIL) : EnumList RAISES {PkgErr.E}; (* Enumerate returns information about entries in the specified locking database. "locksOnly" requests data about only those packages which are currently checked out. *) (* package entries are returned in alphanumeric sort order *) (* If site = NIL then the local locking database is enumerated. *) (* IF site # NIL then the specified remote site is enumerated. *) (* IFF localOnly, then only entries managed at "site" are returned *) (* IFF pendingOnly, then only entries with pendVN # NullVN are returned *) (* if dir # NIL then only entries for which dir = entry.dir are returned *) PROCEDURE GetEntry( pn: PN; goRemote: BOOLEAN := FALSE; t: T := NIL) : RefEntry RAISES {PkgErr.E}; (* Get just one entry from database. "goRemote" requests that data be fetched directly from the managing site of each package. Otherwise, the local lock database is examined. If "goRemote" is requested and a remote server is inaccesible, then Error(RemoteLockServerDown) will be raised. *)the following two procedures are administrative tools they apply only to the database at the target machine
PROCEDURE SetEntry(auth: Auth; pn: PN; entry: RefEntry; t: T := NIL) RAISES {PkgErr.E}; (* This overrides everything. An administrative tool. *) PROCEDURE SetFingerprint( auth: Auth; pn: PN; version: Version; fp: Fingerprint.T; t: T := NIL) RAISES {PkgErr.E}; (* Sets the fingerprint for this package. It also notes whether the supplied fingerprint is valid for the current version, (ie. fpValid := (version = curVN)). *)
the following two procedures are called only through siphon ops
they apply only to the database at the target machine
PROCEDURE CreateCheck(pn: PN; t: T := NIL) RAISES {PkgErr.E}; (* Possible ECs: PackageNameInUse *) (* Checks if a package entry can be created under the given name. *) (* RAISES Error(PackageNameInUse) if a package entry already exists or if another Create operation is in progress. *) PROCEDURE CreateForeign( auth: Auth; pn: PN; owningSite: SiteName; instance: Instance; t: T := NIL) RAISES {PkgErr.E}; (* Possible ECs: PackageNameInUse, NoSuchDir *) (* create a pointer to a remote package, managed by "owningSite" *) (* raises NoSuchDir if parent directory doesn't exist *) PROCEDURE RemoveForeign(auth: Auth; pn: PN; t: T := NIL) RAISES {PkgErr.E, CommitFailed};*********************** directory operations ***********************
PROCEDURE CreateDir (auth: Auth; dir: Dir; t: T := NIL) RAISES {PkgErr.E}; (* DirNameInUse, ParentDirExists *) (* raises ParentDirExists if any parent directory exists *) PROCEDURE RemoveDir (auth: Auth; dir: Dir; t: T := NIL) RAISES {PkgErr.E}; (* NoSuchDir, DirNotEmpty *) (* raises DirNotEmpty if directory contains package entries *) PROCEDURE CheckDir (name: Dir; t: T := NIL) RAISES {PkgErr.E}; (* NoSuchDir *) PROCEDURE EnumerateDirs (site: SiteName := NIL; t: T := NIL): DirList RAISES {PkgErr.E}; END LockOps.