File Access from a Cedar 5 Workstation CEDAR 5.0 File Access from a Cedar 5 Workstation The FS, FSBackdoor and FSExtras interfaces Release as [Indigo]Documentation>FSDoc.Tioga Written by M. D. Schroeder Last edited December 16, 1983 3:09 pm. XEROX Xerox Corporation Palo Alto Research Center 3333 Coyote Hill Road Palo Alto, California 94304 For Internal Xerox Use Only 1. Introduction Purpose of document This document describes the standard facilities available for file access from a Cedar 5 workstation. These facilities are available through the interfaces FS, FSBackdoor, and FSExtras. FS contains facilities of interest to all users. FSBackdoor contains facilities of interest to experts. FSExtras contains items that will be added to FS or FSBackdoor the next time they can be recompiled. In cases where the descriptions of functions in the interface files differ from the descriptions in this document, this document should be viewed as the truth. Location of interfaces All interfaces are available through >Top>FS.df. The implementation of FS is contained in the Cedar 5 boot file. Overview of Functions FS is a file system for use on a Cedar workstation. It provides access both to remote file servers and to the local disk. Remote files accessible from FS must reside on a file server that supports the FTP protocol, in particular IFS's and Alpine servers. These file servers may be accessed from many Cedar instances on different workstations at the same time. Local files are accessed through an abstraction called the local server. The local server is the set of logical volumes on the local disk of a Cedar instance. One of these logical volumes may be designated the system volume. FS provides a directory for each volume, and a cache for remote files on the system volume. FS also defines a generic abstraction, the FS.OpenFile, of which the workstation file system is only one class. The generic operations provide access to the data pages and properties of files. Other packages may create their own classes of FS.OpenFile's, upon which these generic FS operations also may be performed. For example, direct page-at-a-time access to Alpine servers is provided through this mechanism. FS also contains facilities for creating IO.STREAM's on files, for binding local names to remote files, for finding the version of a file that was created at a particular time, and for limiting the number of extant versions of local files. History FS is a successor to the Cedar Interim File System (CIFS) that was developed by Dave Gifford with help from Larry Stewart. The design of FS benefited from advice offered by Andrew Birrell, Mark Brown, Butler Lampson, Roy Levin, Roger Needham, Eric Schmidt, Larry Stewart, Paul Rovner, and Ed Taft. The main designer and implementor of FS was Michael Schroeder. 2. Everything you really wanted to know about file access Simple file stream creation Most clients of FS simply want to create a file stream with which to read or write a file. If that's all you want to do then this section contains all you need to know about FS. Suppose that r is a ROPE containing a file name. If your program needs to read characters from the file named by r (local or global file name, probably with no version part) from a STREAM "s", it should call s _ FS.StreamOpen[r]; If your program will provide all new contents for the file named r (it ignores any existing versions of the file and creates a new version), it should call s _ FS.StreamOpen[r, $create]; If your program is logging output to the file named by r (it updates the file only by adding new characters to the end, and creates a new file of that name if none exists), it should call s _ FS.StreamOpen[r, $append]; Finally, if your program will both read and write the file named by r (it treats the file as an extendable, random-access sequence of bytes that it updates "in place"), it should call s _ FS.StreamOpen[r, $write]; It is an unusual program that requires $write mode, while $create mode is used frequently. The last three forms only work for local files. Simple error handling If you do not wish to land in the debugger when the file name r is misspelled or a file server is down, your calls on FS.StreamOpen should be protected with a catch phrase something like ! FS.Error => IF error.group # bug THEN { YourProcedureToShowRopeToUser[error.explanation]; CONTINUE } The moral The moral of this tale: doing simple things is simple. You do not need to understand the multitude of parameters to FS.StreamOpen and FS.StreamFromOpenFile, since they default correctly for most purposes. You do not need to use any procedure from this interface except FS.StreamOpen unless you are doing something special. There is only one signal to catch. Having created a file stream, you may have questions about the semantics of the generic IO operations when applied to this stream: will IO.SetIndex extend the file, and what does IO.Reset do? To answer these questions, read section 11, "FS file streams". It is also possible that you are not interested in streams at all, but want to do something else like enumerate a directory. Read on, the answer is here somewhere. 3. Error handling Error TYPE's Clients of FS should only have to catch the single ERROR FS.Error. In particular, errors from the File package and the STP package are mapped into FS.Error's. Other packages that hand out FS.OpenFile's (see section 10) are expected to raise FS.Error. FS.Error: ERROR [error: FS.ErrorDesc]; FS.ErrorDesc: TYPE = RECORD [group: FS.ErrorGroup, code: ATOM, explanation: ROPE] _ [ok, NIL, NIL]; FS.ErrorGroup: TYPE = { ok, -- initial group for a new FS.ErrorDesc bug, -- caused by an internal bug environment, -- something's wrong in the environment; human intervention required lock, -- conflict over locks client, -- illegal operation, probably due to bug in client program user -- illegal operation, probably due to user action }; FS error codes are partitioned into five groups: bugs, environment errors, lock conflicts, client errors, and user errors. A bug usually indicates that something unexpected has gone wrong inside the implementation of FS or lower packages. An environment error means that something in the external environment has changed that requires human intervention. A lock conflict means that some other client has the locks that this client needs to get its job done. A client error is an attempted illegal operation that probably is caused by a bug in a client program. A user error is an attempted illegal operation that probably is caused by a human user's actions. The distinction between the last two groups is somewhat arbitrary. An error description reports the group, as well as a specific code ATOM indicating exactly what went wrong. In the descriptions of procedures that follow, all codes from the error groups "client" and "user" that can be raised are specifically listed. Errors in the other groups should be expected as appropriate. The "explanation" is intended to be a description of the error suitable for human consumption; it frequently contains the name of the file being operated upon. Most clients should catch all FS.Error's with error.group # bug and report the error.explanation to the human user. The FS implementation is careful never to raise FS.Error while monitor locks are held. Any FS procedure can be called from a catch phrase for FS.Error. Error code ATOM's Error codes are ATOM's so that packages implementing FS.OpenFile's via FSBackdoor.CreateProcsOpenFile can extend the set of codes. Following is a complete list of the codes generated by the FS implementation. The list is partitioned by the FS.ErrorGroup. Bugs $inconsistent -- a local volume's permanent data structures are inconsistent $software -- label check encountered while reading or writing pages on a local volume $badFP -- File.FP from directory/cache doesn't correspond to a file on a local volume Environment Errors $wentOffline -- local volume is no longer accessible $hardware -- hard error while reading or writing pages on a local volume $volumeFull -- no more free pages on a local volume $fragmented -- file required too many non-contiguous areas on a local volume $noMoreVersions -- next version number would be too big $serverInaccessible -- couldn't get a response from a remote server $connectionRejected -- remote server rejected the connection attempt $connectionTimedOut -- connection to a remote server timed out $badCredentials -- remote server rejected the user name or password $accessDenied -- remote server access controls succeeded $quotaExceeded -- request refused by server, usually because disk space quota exceeded $invalidPropertyPage -- unrecognized property page format for a file on a local volume $badBTree -- directory/cache BTree is malformed and cannot be opened Lock Errors $lockConflict -- required lock cannot be obtained $fileBusy -- remote file currently being used in an incompatible way Client Errors $noCache -- couldn't operate on global file because there is no system volume and no cache $wrongLock -- operation requires a write lock and only a read lock is held $globalWriteLock -- tried to open a GName with a write lock $zeroKeep -- keep of 0 was specified $badByteCount -- tried to set a byte count that is beyond end of file or negative $unknownPage -- tried to read or write a page that is beyond end of the file $invalidOpenFile -- FS.OpenFile is closed, is FS.nullOpenFile, or is an unrecognized TYPE $notImplemented -- called a procedure that is not provided for this FS.OpenFile User Errors $nonCedarVolume -- named volume is not a cedar volume $unknownServer -- couldn't get a network address for the named server $unknownVolume -- named local volume cannot be found $unknownFile -- FName not in the implied directory $unknownCreatedTime -- no version of the given FName had the specified created-time $illegalName -- file name, directory name or pattern has illegal syntax or characters, or is too long, $patternNotAllowed -- name presented contained a "*" and procedure doesn't allow patterns $versionSpecified -- tried to create an FName with a specific version $globalCreation -- tried to create a GName $badWorkingDir -- default working directory specified is global or has no volume part $noKeeps -- tried to set the keep on a server that does not implement keeps $cantUpdateTiogaFile -- tried open a file stream to write on a Tioga-format file 4. Naming files Name structure Names for FS files are called "FNames". An FName consists of (in order) a server, a root directory, zero or more subdirectories, a simple name, and a version. An FName may not exceed 120 characters excluding the version part. The version part will always fit in 6 characters, so the maximum space an FName can occupy is 126 characters. Any name discovered that contains more than 120 characters before the version part will cause FS.Error[user, $illegalName] to be raised. FS.maxFNameLength: CARDINAL = 126; Both FTP bracket syntax and CIFS slash syntax for FNames are accepted, although bracket syntax is used internally, in all FNames returned from FS procedures, and in FS.Error explanations. The following constructs are equivalent: [server]subDirectory>simpleName!version /server/rootDirectory/subDirectory/simpleName!version The structural characters that delimit the server part of an FName are "[" and "]", or "/". Non-structural characters allowed in the server part are all alphameric characters plus ".", "$", "-", "+" and "#". The structural characters allowed in the rest of an FName are "<" and ">", or "/". The non-structural characters are the same as for the server part with the exception of "#". The additional structural character "!" is used as the version part prefix. The rest of the version part can be a number in the range [1 .. 65534], or one of the letters "L" or "H". Case is not significant in FNames. An empty server part means the local server. Otherwise the server part is presumed to name a remote file server. The names of files on remote servers are called "GNames", for global names. The detailed requirements on the structure of a GName are defined by the type of server named. The most common remote servers (IFS and Alpine) require the presence of a root directory part and include version parts in all file names. The names of files on the local server are called "LNames", for local names. In an LName, the root directory part is the name of a volume; an empty root directory part refers to the "system" volume (the one from which Cedar was booted). All LNames have version parts. For example, the following are complete LNames: []<>FS.mesa!3 ///FS.mesa!3 []Temp>DebugTool.bcd!12 //Debugger/Temp/DebugTool.bcd!12 The working directory mechanism described below eliminates the need for the server and root directory part on most LNames presented to FS procedures. Fine points: 1. Certain remote servers, e.g., Gateways and Grapevine servers, do not have a directory structure or versions. Such servers require GNames of the form "[server]simpleName" or the form "/server/simpleName". 2. The root directory in an LName may also be identified by its VolumeID. The syntax for the root directory part in this case is "#" followed by a 20-digit hexadecimal string. 3. The Cedar debugger interface makes a remote debuggee's disk available to a debugger as if its disks were attached to the debugger's machine. This is essentially transparent to FS; volumes of the debuggee's disk will appear to be part of the local server, i.e., files will be LNames, not GNames. This is entirely different from the situation in which the remote machine is operating normally and is running, say, an FTP server. Version numbers The local server and most remote servers include version numbers in file names. Version numbers allow for multiple instances of a file to exist at one time. FS enforces the policy that the version on any newly created file is one larger than the largest existing version, the so-called "next" or !N version. It is not possible to create any other version with FS. When accessing an existing file the version part of an FName may be a number, may be a variable, or may be omitted. The variables allowed are !L, meaning the lowest existing version number, and !H, meaning the highest existing version number. When omitted, the version part defaults to !L or !H, depending on the operation being performed. Working directory An FName that does not start with a server part is called a partial name and will be interpreted relative to a working directory. The working directory may be specified as the argument named "wDir" to FS procedures, or may be defaulted. To expand the arguments "name" and "wDir" of an FS procedure into a full FName, FS checks the first character of "name". If it is "[" ("/") then "name" is the full FName. If not then FS looks for a working directory name in three places in order: 1) the "wDir" argument; 2) the value of the $WorkingDirectory property of the process property list; 3) the default working directory (set with FS.SetDefaultWDir). The first ROPE found whose length is not 0 is preprended to "name" to form the full FName. A working directory obtained from 1 or 2 can be local or global. If it does not end with "]" or ">" ("/") then a ">" ("/") is appended. The default working directory must be local. The default working directory can be set with the procedure: FS.SetDefaultWDir: PROC [dir: ROPE _ NIL]; If Rope.Length[dir] is 0 then the default working directory is set to "[]<>", which also is the initial value for the default working directory. If "dir" does not end with ">" ("/") then a ">" ("/") is appended. Client errors: none User errors: $illegalName, $patternNotAllowed, $badWorkingDir The default working directory can be read with the procedure: FS.GetDefaultWDir: PROC RETURNS [ROPE]; Client errors: none User errors: none Patterns Enumerations are specified with a "pattern" argument, which is an FName in which the character "*", meaning "match anything", may appear zero or more times. "*" may not appear in the server part, or in the root directory of an LName. The presence or absence of a server part in "pattern" controls the use of working directories as usual. The version part of a "pattern" may be !H, !L, !*, or ! followed by digits; or may be omitted. It cannot contain a mixture of "*" and digits. Parsing and constructing FNames and FName patterns FS contains utilities for parsing and constructing FNames. FS.Position: TYPE = RECORD [start, length: CARDINAL]; FS.ComponentPositions: TYPE = RECORD [server, dir, subDirs, base, ext, ver: FS.Position] FS.ExpandName: PROC[name: ROPE, wDir: ROPE _ NIL] RETURNS [fullFName: ROPE, cp: FS.ComponentPositions, dirOmitted: BOOLEAN]; The full FName or pattern determined by "name" and "wDir" is converted to brackets syntax and syntax checked. The converted FName is returned as "fullFName". The positions of the components in "fullFName" are returned in "cp", expressed as starting index and length. No check is made to see if a file named "fullFName" actually exists. A variable or missing version part is not bound. The "server" is the server part without surrounding "[ ... ]". The "dir" is the root directory or volume part without the surounding "< ... >". The "subDirs" is the all the subdirectory part without without surounding "> ... >" but including interior ">"s. The "base" is the portion of the simple name up to but not including the final "." The "ext" is the portion of the simple name following the final "." If there are no "."s in the simple name then it is all considered to be "base". The "ver" is everything after the "!". The character "*", when it appears in a legal position, is treated as having no syntactic significance. The component positions are calculated as if each "*" were, say, the character "a". Since the "*" may in fact span component boundaries, some components following the "*" may be incorrectly delimited. If the "fullFName" is a GName of the form "[server]simpleName", i.e., it has no directory part (not just the empty directory part "<>"), then "dirOmitted" returns TRUE; otherwise it returns FALSE. If "fullFName" is an LName then "components.server.length" will be 0; if "fullFName" is an GName then "components.server.length" will be greater than 0. For a Position "p" from a ComponentPositions "c", Rope.Substr[fullFName, c.p.start, c.p.length] is a ROPE containing that component. An empty or missing component is reported as [start,0], where "start" is the index where the component would have appeared if present. Client errors: none User errors: $illegalName FS.ComponentRopes: TYPE = RECORD [server, dir, subDirs, base, ext, ver: ROPE _ NIL]; FS.ConstructFName: PROC [cr: FS.ComponentRopes, omitDir: BOOLEAN _ FALSE] RETURNS [ROPE]; Constructs an FName from the component ROPE's in "cr". No syntax checking is done. The only structural character that should appear in components is ">" within the subdirectory component. The construction algorithm is as follows: name _ Rope.Cat[ "[", cr.server, "]" ]; IF NOT omitDir THEN name _ Rope.Cat[ name, "<", cr.dir, ">" ]; IF Rope.Length[cr.subDirs] > 0 THEN name _ Rope.Cat[ name, cr.subDirs, ">" ]; name _ Rope.Cat[ name, cr.base ]; IF Rope.Length[cr.ext] > 0 THEN name _ Rope.Cat[ name, ".", cr.ext ]; IF Rope.Length[cr.ver] > 0 THEN name _ Rope.Cat[ name, "!", cr.ver ]; RETURN [name]; Client errors: none User errors: none 5. File properties Properties supported File properties provided by FS are page count, byte count, created-time, and keep. The page count is the number of pages allocated to the file. The byte count is the number of meaningful bytes in a file; the byte count is kept up-to-date by explicit client calls on FS procedures. The created-time is the date and time when the contents of the file were created (or changed). The keep specifies how many versions of a file to keep around. Converting page counts to byte or word counts Procedures are provided for converting between pages counts and byte counts or word counts: FS.WordsForPages: PROC[pages: INT] RETURNS [words: INT]; FS.BytesForPages: PROC[pages: INT] RETURNS [bytes: INT]; FS.PagesForWords: PROC[words: INT] RETURNS [pages: INT]; FS.PagesForBytes: PROC[bytes: INT] RETURNS [pages: INT]; Keeps A property for each LName is the "keep", a CARDINAL that specifies the number of versions of an LName that should be kept around. Whenever an LName is created, its "keep" is inherited from the existing !H version or set from an argument to the procedure doing the creation. The keep of the existing !H version can be changed with FS.SetKeep. Keep processing occurs when creating a new version of an LName. In this case, FS will enumerate existing version in decreasing order from !H. After "keep - 1" versions are encountered in this enumeration, additional versions will be deleted if not open. Thus, the keep is the steady state count of versions to be retained. The local volume file of a deleted version will be reused as the disk file for the new version being created. For example, if the only existing version of a file is named Example.bcd!4, it has a keep of 1, and no client has it open, then FS.Create[Example.bcd] will cause Example.bcd!4 to be deleted and its disk file to be reused for Example.bcd!5. Calling FS.SetKeep will have the side effect of deleting files that are obsolete relative to the new keep, even when the keep is not changed. Note that setting a keep to LAST[CARDINAL] will result in all versions being kept, since there can never be that many versions. A keep of 0 is illegal. No remote server implements keeps right now. 6. Name binding Naming with version variables For certain FS procedures the identification of which global file to operate on can include the version variables !L or !H, or can be defaulted to the variables !L or !H (depending on the operation). Binding a version variable to a particular version would be straightforward if the remote server were always accessible and if the client were always willing to incur the cost of accessing it. In some circumstances, however, a client may wish to complete the binding based on the possibly incomplete information available from the cache. The "remoteCheck" argument to FS.FileInfo, FS.Open and FS.Copy allows the client to control the scope of the version search. (FS.Rename and FS.Delete do not include a "remoteCheck" argument because renaming and deletion occur synchronously on the remote server, so there is no option.) The "remoteCheck" argument is evaluated only when a GName is specified. When "remoteCheck" is TRUE then FS binds a version variable by searching the versions on the remote server. The operation fails if the remote server is inaccessible. When "remoteCheck" is FALSE then FS binds the version variable by searching the versions in the cache; only if no cached version is found is the remote server interrogated. Naming with created-times All of the FS procedures that accept an FName argument also include a "wantedCreatedTime" argument. This time provides an alternative way to name a file. If "wantedCreatedTime" is not equal to BasicTime.nullGMT, then the file to be operated upon is determined by a search for the version with the specified created-time. Any version part included in the name is treated as a hint. The created-time of a file is treated as a unique identifier of the content. If a version of a global file with the wanted created-time is found in the cache, then FS will not bother to check with the remote server (even if "remoteCheck" is TRUE) to see if the same created-time to version binding exists there. Attachments FS includes facilities for attaching an LName to a GName and created-time. An attachment is a name binding. Attachment is a way to give an LName to a global file, and is useful as a cheap way to copy global files into local files. "BringOver" and "SModel" are the primary clients of the attachment mechanism. Attachments are created using certain modes of FS.Copy. For most FS procedures, an attached LName behaves as though the global file were in fact copied into the local file when FS.Copy was invoked. There are four exceptions. First, the various procedures for obtaining information about files inform the client when an LName is attached and provide the GName of the attachment. Second, opening an attached LName requires opening the global file; if the global file is not in the cache and the remote server is down or the file has been deleted on the remote server, then the open will fail. Third, for a "read" open both the local and global names are read locked. Fourth, for a "write" open, the attachment is broken and the contents of the global file are copied into a local file; the local file assumes the LName and the GName is forgotten. 7. Credentials and locks Name and password for server access For access to remote servers FS uses the credentials obtained from the UserCredentials interface. If these do not work then FS.Error[environment, $badCredentials] is raised. FS currently has no facilities for setting or using secondary credentials. Thus, access to files in password protected directories on servers is not possible. Locking There are two types of locks that can be set on files: "read" and "write". These locks are local to a Cedar instance. Even when a GName is locked, the lock will be effective only within the same workstation. Whenever the required lock cannot be obtained, FS raises FS.Error[lock, $lockConflict]. FS.Lock: TYPE = {read, write}; A "read" lock provides shared access to a file. Holders of "read" locks for a file may only read its data or properties. A "read" lock can be set anytime no "write" lock for that name is held. Thus, multiple "read" locks may be held. While a "read" lock is held no other client can delete the file or change its name. A "write" lock provides exclusive access to a file. The holder of a "write" lock may alter the file's contents and properties. An implicit "write" lock is required to rename or delete a file. While a "write" lock is held no other client can open the file, delete it, or alter its name. A "write" lock can be set only when no other locks are held. Limited use experience with the FS locking suggested that it is not well suited to the demands placed by the interaction of Tioga, the compiler and binder, and the DF package. The locking will be rethought for future versions of FS. 8. Getting information about files Info on a single file FS.FileInfo: PROC [name: ROPE, wantedCreatedTime: BasicTime.GMT _ BasicTime.nullGMT, remoteCheck: BOOLEAN _ TRUE, wDir: ROPE _ NIL] RETURNS [fullFName, attachedTo: ROPE, keep: CARDINAL, bytes: INT, created: BasicTime.GMT]; Returns information about the file specified by "name", "wantedCreatedTime", and "wDir". A missing version part defaults to !H. If "wantedCreatedTime" is not BasicTime.nullGMT then any version number in "name" is treated as a hint; the information returned is for the file with the wanted created-time, found by searching all versions of the named file as necessary. If the matching file is a local file not attached to a global file, then the full FName including version part is returned as "fullFName". The keep, byte count, and created-time also are returned. "attachedTo" will be NIL and the "remoteCheck" argument is ignored. If the matching file is a local file that is attached to a global file, then the full FName of the local file including version part is returned as "fullFName". The keep of the local file is returned. The full FName of the attached global file is returned as "attachedTo". The byte count and created-time of the global file also are returned. In this case, if the "remoteCheck" argument is FALSE then the byte count will be returned as -1, thus eliminating the need to open the global file from the cache or check with the remote server to determine this information. If "remoteCheck" is TRUE then the byte count of the global file is determined from the cache or remote server as necessary and returned. Any FS.Error's encountered when trying to determine the byte count of an attachment are suppressed and "bytes" is reported as -1. Whenever a valid byte count for an attachment is reported then the version part in the "attachedTo" name is the true version number that corresponds to the created-time for the attachment; otherwise this version part will be whatever was recorded by FS.Copy when the attachment was made. If the matching file is a global file its full FName including version part is returned as "fullFName". The keep returns as 0. The byte count and created-time of the global file are returned. "attachedTo" returns as NIL. If "name" ends with a version variable and no wanted created-time is specified, then "remoteCheck" controls access to the remote server. If "remoteCheck" is TRUE, then the remote server is accessed for the file information. If "remoteCheck" is FALSE, then the version variable is bound relative to the set of versions in the cache; the remote server is interrogated only if no version appears in the cache. Client errors: none User errors: $nonCedarVolume, $unknownServer, $unknownVolume, $unknownFile, $unknownCreatedTime, $illegalName, $patternNotAllowed Enumeration facilities Enumerations are specified with a "pattern" argument. When the version part of a "pattern" is omitted it defaults to !*. The order of the enumeration may be different for different sorts of servers. For the local server and IFS's, the enumeration is in lexical order of LNames expressed in bracket syntax (lower cases letters are mapped to upper case) but without the version part, and then in numeric order by version number. Monitor locks are not held during an enumeration. Thus, the client may call FS.Delete from an FS.InfoProc or an FS.NameProc. Fine point: deleting or storing (by FS.Copy) ahead of the current position in a remote enumeration can produce unexpected results; the change may not be reflected in the enumeration. FS.InfoProc: TYPE = PROC [fullFName, attachedTo: ROPE, created: BasicTime.GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOLEAN]; FS.EnumerateForInfo: PROC [pattern: ROPE, proc: FS.InfoProc, wDir: ROPE _ NIL]; "proc" is called for each FName selected by the pattern. Returning "continue" as FALSE stops the enumeration. "fullFName" reports the complete FName including version part of each name encountered. The keep, byte length, and created date of a local or global file are reported. When an LName attached to a GName is encountered, "fullFName" is the LName and "attachedTo" is the GName. In this case, the "keep" reported is that of the LName but the "bytes" and "created" are that of the GName. If any FS.Error's are encountered when trying to determine the byte count of an attachment, they are suppressed and "bytes" is reported as -1. Whenever a valid byte count for an attachment is reported then the version part in an "attachedTo" name is the true version number that corresponds to the created-time for the attachment; otherwise this version part will be whatever was recorded by FS.Copy when the attachment was made. Client errors: none User errors: $nonCedarVolume, $unknownServer, $unknownVolume, $illegalName FS.NameProc: TYPE = PROC [fullFName: ROPE] RETURNS [continue: BOOLEAN]; FS.EnumerateForNames: PROC [pattern: ROPE, proc: FS.NameProc, wDir: ROPE _ NIL]; Because only names are provided to "proc", this enumeration can go faster than the previous one, especially for GNames. Client errors: none User errors: $nonCedarVolume, $unknownServer, $unknownVolume, $illegalName 9. Opening files Introduction An FS.OpenFile may be presented to other procedures to get access to the file's contents and properties. All global files opened using the FS procedures defined below are accessed via the FTP protocol and the cache. FS.OpenFile: TYPE = RECORD [REF]; FS.nullOpenFile: FS.OpenFile = [NIL]; Other packages can include procedures that return FS.OpenFile's. In such cases, the arguments to the opening procedures and the detailed semantics of operations on the client-provided FS.OpenFile's are defined by that package. (The operations are defined by providing procedures to FSBackdoor.OpenFileFromProcs). For example, the FS.OpenFile's obtained by calling the Alpine package directly do permit use of Alpine's special facilities for transaction, locking, and page access. Generation of such FS.OpenFile's is via Alpine procedures in another package and completely bypasses FS's locking and cache. Opening an existing file An existing file may be opened using the following procedure: FS.Open: PROC [name: ROPE, lock: FS.Lock _ $read, wantedCreatedTime: BasicTime.GMT _ BasicTime.nullGMT, remoteCheck: BOOLEAN _ TRUE, wDir: ROPE _ NIL] RETURNS [FS.OpenFile]; An FS.FileInfo[name, wantedCreatedTime, remoteCheck, wDir] is done. If an FS.Error is not raised, then the file described by the result of FS.FileInfo is opened. If an "attachedTo" is returned then that global file is the one opened. "lock" specifies the lock to be set. Opening a file with an "write" lock causes the created-time to be set to now. When a local file that is attached to a global file is opened for writing, the attachment is broken and the contents of the global file are copied into a local file, which local file assumes the LName. Multiple calls on FS.Open produce different FS.OpenFile's. Client errors: $noCache, $globalWriteLock User errors: $nonCedarVolume, $unknownServer, $unknownVolume, $unknownFile, $unknownCreatedTime, $illegalName, $patternNotAllowed Creating a new file There are two important restrictions on file creation. First, no version may be specified; FS always creates the !N version. Second, global files may not be created; only FS.Copy and FS.Rename can cause new global file to come into existence. FS.Create: PROC [name: ROPE, setPages: BOOLEAN _ TRUE, pages: INT _ 0, setKeep: BOOLEAN _ FALSE, keep: CARDINAL _ 1, wDir: ROPE _ NIL] RETURNS [FS.OpenFile]; A new local file with the specified name is created and opened with a "write" lock. The created-time is set to now. No version part may be specified. FS will assign the version number that is one larger than the existing !H version, or !1 if no versions exist. If !1 is being created or "setKeep" is TRUE then the keep is set to "keep"; otherwise the keep for the new file is that from the existing !H version. If "setPages" is TRUE then the number of pages in the created file is set to "pages". If "setPages" is FALSE then the number of pages in the new file is the same as the reused file if any, otherwise it set to "pages". Client errors: $noCache, $globalWriteLock User errors: $nonCedarVolume, $unknownServer, $unknownVolume, $unknownFile, $unknownCreatedTime, $illegalName, $patternNotAllowed The following procedure is provided as a convenience for clients that wish to open an existing file for write, but create one if none exists. FS.OpenOrCreate: PROC [name: ROPE, keep: CARDINAL _ 1, pages: INT _ 5, wDir: ROPE _ NIL] RETURNS [FS.OpenFile]; Performs FS.Open[name: name, lock: write, wDir: wDir]. If FS.Error[$unknownFile] is raised then FS.Create[name: name, pages: pages, keep: keep, wDir: wDir] is called. Note that "name" may contain no version part in cases where FS.Create is called. Client errors: $noCache, $globalWriteLock, $zeroKeep User errors: $nonCedarVolume, $unknownServer, $unknownVolume, $unknownCreatedTime, $illegalName, $patternNotAllowed, $versionSpecified, $globalCreation 10. Generic operations on open files Obtaining information FS.GetClass: PROC [file: FS.OpenFile] RETURNS [ATOM]; Returns an ATOM that identifies the implementation for operations on "file". If "file" was obtained by calling FS.Open, FS.Create, or FS.OpenOrCreate, then "$FS" is returned. If "file" was obtained by calling some other package that manufactures FS.OpenFiles via FSBackdoor.CreateProcsOpenFile, then the ATOM returned is that provided by that package. Client errors: $invalidOpenFile, $notImplemented User errors: none FS.SameFile: PROC [file1, file2: FS.OpenFile] RETURNS [BOOLEAN]; Returns TRUE iff "file1" and "file2" are for the same files. Two FS.OpenFile's obtained from an FS procedure are the same if they refer to the same File.FP. Client errors: $invalidOpenFile, $notImplemented User errors: none FS.GetName: PROC [file: FS.OpenFile] RETURNS [fullFName, attachedTo: ROPE]; The full name of the opened file, in brackets syntax with any version number, is returned as "fullFName". If the name that was opened was an LName attached to a GName, then the name of the attached global file is returned as "attachedTo", which otherwise is NIL. Client errors: $invalidOpenFile, $notImplemented User errors: none FS.GetInfo: PROC [file: FS.OpenFile] RETURNS [keep: CARDINAL, pages, bytes: INT, created: BasicTime.GMT, lock: FS.Lock]; The keep (if not defined for the server in question then 0 is returned), number of pages, byte count, created-time, and the lock held are returned. If the name that was opened was an LName attached to a GName, then the pages, byte count, created-time, and lock are for the global file. Client errors: $invalidOpenFile, $notImplemented User errors: none Setting properties FS.SetPageCount: PROC [file: FS.OpenFile, pages: INT]; Extends or contracts the file until the number of pages is as specified. Newly allocated pages are zeroed. Contents of existing pages are undisturbed. Client errors: $wrongLock, $unknownPage, $invalidOpenFile, $notImplemented User errors: none FS.SetByteCountAndCreatedTime: PROC [file: FS.OpenFile, bytes: INT _ -1, created: BasicTime.GMT _ BasicTime.nullGMT]; Sets the byte count and created-time properties of "file". If "created" is BasicTime.nullGMT then the created-time of the file is not altered. If "bytes" is -1 then the byte count is not altered. Client errors: $wrongLock, $badByteCount, $invalidOpenFile, $notImplemented User errors: none Reading and writing data FS.Read: UNSAFE PROC [file: FS.OpenFile, from, nPages: INT, to: LONG POINTER]; Copies "nPages" pages from the file starting at "from" to the virtual memory interval starting at "to". "to" must be page-aligned. Note that ERROR's caused by "to" being an invalid address are not mapped to FS.Error's. Client errors: $unknownPage, $invalidOpenFile, $notImplemented User errors: none FS.Write: PROC [file: FS.OpenFile, to: INT, nPages: INT, from: LONG POINTER]; Copies "nPages" pages from the virtual memory interval starting at "from" to the file starting at page number "to". "from" must be page-aligned. Note that ERROR's caused by "from" being an invalid address are not mapped to FS.Error's. Client errors: $wrongLock, $unknownPage, $invalidOpenFile, $notImplemented User errors: none Closing open files FS.Close: PROC [file: FS.OpenFile]; Releases the locks associated with the open "file". Any further operations on copies of this FS.OpenFile will produce an FS.Error[client, $invalidOpenFile]. The locks will also be released by finalization when all copies of the FS.OpenFile disappear, but this occurs at an unspecified future time. Client errors: $invalidOpenFile, $notImplemented User errors: none 11. FS file streams Stream model A FS file is a sequence [0..fileLen) of mutable bytes; the length (fileLen) of a file can be changed. The state of an FS file stream is a file with length fileLen, an index streamIndex IN [0..fileLen], plus a number of readonly flags (accessRights, streamOptions) and a BOOL variable closed which jointly determine the effect of stream procs. For any of the procedures below, IO.Error[$StreamClosed,self] will be raised when the call is made after the stream has been closed. When IO.Error[Failure, self] is raised (possible for those calls maked with a *), then FileStream.ErrorFromStream[self] will return the most recent FS-level error involving this stream. Here is what the generic stream operations do when applied to an FS file stream: IO.GetChar: PROC [self: STREAM] RETURNS [CHAR] Raises ERROR IO.EndOfStream[self] if the input sequence is empty; otherwise consumes and returns the next byte in the input sequence. (*) IO.CharsAvail: PROC [self: STREAM, wait: BOOL] RETURNS [INT] Always returns INT.LAST to indicate that all operations are done very quickly. IO.EndOf: PROC [self: STREAM] RETURNS [BOOL] Returns TRUE if and only if calling self.GetChar[] would raise ERROR IO.EndOfStream[self]. This means that streamIndex = fileLen. No characters are consumed. Note that if a for a read stream created from a write stream on the same file by FSExtras.StreamFromOpenStream, successive calls to IO.EndOf may return different results since the write stream may have appended or truncated the file. IO.PutChar: PROC [self: STREAM, char: CHAR] If used with a stream with accessRights = $read, PutChar raises IO.Error[$NotImplementedForThisStream, self]. Otherwise, if self.EndOf[] would return TRUE, then append the given byte to the output sequence modifying streamIndex and fileLen.. When self.EndOf[] would return FALSE, replace the byte at the current position, streamIndex, with the given character and advance the position. (*) IO.Flush: PROC [self: STREAM] If used with a stream with accessRights = $read, no action is performed on self. For write streams, all buffers containing changed data (if any) are written to the file system. Flush does not return until all writes complete. (*) IO.Reset: PROC [self: STREAM] streamIndex is set to fileLen. Equivalent to self.SetIndex[self.GetLength[]]. (*) IO.Close: PROC [self: STREAM, abort: BOOL _ FALSE] Make the stream unusable for further procedures. A self.Flush[] is performed, and many stream resources, such as buffer space, are released. If StreamOptions[closeFSOpenFileOnClose] was true when the stream was created, then the underlaying OpenFile is normally closed. If, however, other streams have been created with FSExtras.StreamFromOpenStream, then the close will be delayed until all streams that have been derived from the original StreamOpen, including the original stream, have been closed. If StreamOptions[truncatePagesOnClose] was true when the stream was created, extra pages in the file are truncated when the close of the OpenFile occurs. (*) IO.GetIndex: PROC [self: STREAM] RETURNS [INT] Returns the stream index. IO.SetIndex: PROC [self: STREAM, index: INT] Sets the stream index. Raises IO.EndOfStream if index > number of bytes in the file; does not extend the file. Raises IO.BadIndex if index is < 0. (*) IO.GetLength: PROC [self: STREAM] RETURNS [INT] Returns the number of bytes in the file. IO.SetLength: PROC [self: STREAM, length: INT] Sets the number of bytes in the file, fileLen, to length. SetLength then sets streamIndex to be MIN[streamIndex, fileLen]. The contents of the file bytes IN [previous file length .. length) are undefined. If used with a stream with accessRights = $read, PutChar raises IO.Error[$NotImplementedForThisStream, self]. If length is too large (nearly INT.LAST), then it raises IO.BadIndex. (*) Concurrency File streams provide no interlocks of their own to control concurrent access to files. Instead, they rely on the underlying file system for concurrency control. FS uses locking to provide concurrency control. Individual file streams are independent objects, so there is never any need to synchronize calls to their procedures. If two processes are to share a single file stream, they must synchronize their accesses to that stream at a level above the file stream calls; individual file stream calls (PutChar, PutBlock, etc.) are not guaranteed to be atomic. Pragmatics An FS file is represented as a sequence of disk pages, each containing FS.BytesForPages[1] bytes. There is a single leader page (overhead) that is used to hold file properties such as the byte count and create time. There is no functional relationship between the byte count of a file and the allocated size of that file in pages. Allocating disk pages to a file is an expensive operation, especially when done incrementally (one page at a time). Extending a file many times can result in a fragmented file. If a client program can estimate the eventual length of a file it is creating, it should use the "createByteCount" parameter to FS.StreamOpen to allocate enough space to hold the entire length all at once; if a client program is updating an existing file then it should create a stream and then extend the file using IO.SetLength. If the file stream implementation must extend a file it will always extend it by several pages; if asked to shorten a file it will simply adjust the byte count field in the leader page without freeing any pages. If the streamOption "truncatePagesOnClose" is specified (the default), then extra pages are freed when the stream is closed. Creating file streams The following TYPE's and constants are used when creating STREAM's for FS files. FS.StreamOptions: TYPE = PACKED ARRAY StreamOption OF FalseBool; FS.FalseBool: TYPE = BOOL _ FALSE; FS.defaultStreamOptions: FS.StreamOptions = ALL[TRUE]; FS.StreamOption: TYPE = { tiogaRead, -- if accessOptions = $read and file is in Tioga format, read only plain text portion of the file (ignore "looks" and nodes with the comment property). If accessOptions # $read and file is in Tioga format, raise FS.Error[$cantUpdateTiogaFile]. commitAndReopenTransOnFlush, -- obsolete, will be removed when FS is recompiled truncatePagesOnClose, -- IO.Close call causes extra pages of the file to be freed. finishTransOnClose, -- obsolete, will be removed when FS is recompiled closeFSOpenFileOnClose -- each IO.Close call performs FS.Close on the underlying FS.OpenFile. }; FS.StreamBufferParms: TYPE = RECORD [vmPagesPerBuffer: INT [1 .. 128], nBuffers: INT [1 .. 4]]; FS.defaultStreamBufferParms: FS.StreamBufferParms = [vmPagesPerBuffer: 16, nBuffers: 2]; FS.minimumStreamBufferParms: FS.StreamBufferParms = [vmPagesPerBuffer: 1, nBuffers: 1]; Control the buffering strategy used by a stream. FS.ByteCount: TYPE = INT; FS.ExtendFileProc: TYPE = PROC [--current-- FS.ByteCount] RETURNS [--new-- FS.ByteCount]; Called by a stream when a file is to be extended. Returns a suggested new allocated byte count for the file; the stream may extend file by more than the requested amount. FS.AccessOptions: TYPE = {read, create, append, write}; STREAM's on FS files can be created from a file name, an FS.OpenFile, or another file stream. FS.StreamOpen: PROC [fileName: ROPE, accessOptions: AccessOptions _ $read, streamOptions: FS.StreamOptions _ defaultStreamOptions, keep: CARDINAL _ 1, createByteCount: FS.ByteCount _ 2560, streamBufferParms: FS.StreamBufferParms _ defaultStreamBufferParms, extendFileProc: FS.ExtendFileProc _ NIL] RETURNS [IO.STREAM]; Create a new stream by (1) generating an FS.OpenFile, then (2) creating a stream on this FS.OpenFile. (1) The FS procedure called to generate the open file depends on the "accessOptions". Any FS.Error raised by the open call will propagate through FS.StreamOpen. $read => FS.Open[name: fileName]; $create => FS.Create[name: fileName, keep: keep, pages: FS.PagesForBytes[createByteCount]]; $append => FS.OpenOrCreate[name: fileName, keep: keep, pages: FS.PagesForBytes[createByteCount]]; $write => FS.Open[name: fileName, lock: $write]; (2) Perform FS.StreamFromOpenFile[ ... , streamOptions, streamBufferParms, extendFileProc] on the resulting FS.OpenFile. The value of accessOptions determines the set of operations allowed on the stream (disallowed operations raise IO.Error[$NotImplementedForThisStream, stream] when called), and the initial streamIndex, as follows: read: PutChar, PutBlock, UnsafePutBlock, and SetLength are disallowed, and the initial streamIndex is 0. create: all operations are allowed, the length is set to zero at stream creation time, and the initial streamIndex is 0. append: all operations are allowed, and the initial streamIndex is the file's byte count. write: all operations are allowed, and the initial streamIndex is 0. Client errors: $noCache, $globalWriteLock, $zeroKeep User errors: $nonCedarVolume, $unknownServer, $unknownVolume, $unknownFile, $illegalName, $patternNotAllowed, $versionSpecified, $globalCreation, $cantUpdateTiogaFile InitialPosition: TYPE = {start, end}; FS.StreamFromOpenFile: PROC [openFile: OpenFile, accessRights: Lock _ $read, initialPosition: FS.InitialPosition _ $start, streamOptions: FS.StreamOptions _ defaultStreamOptions, streamBufferParms: FS.StreamBufferParms _ defaultStreamBufferParms, extendFileProc: FS.ExtendFileProc _ NIL] RETURNS [IO.STREAM]; Create a new stream on the open file. If AccessRights = $read then PutChar, PutBlock, UnsafePutBlock, and SetLength are disallowed on the resulting stream. InitialPosition specifies the initial value of streamIndex: start means 0, end means the file's byte count. Client errors: $wrongLock User errors: $cantUpdateTiogaFile FSExtras.StreamFromOpenStream: PROC [self: IO.STREAM] RETURNS [IO.STREAM]; Given an existing file stream open for writing (accessRights = $write), creates a new stream on the same file with accessRights = $read. The two streams are then independently positionable, but are internally linked together so that the reader sees data written by the writer. At most one read stream may be linked in this way with any given write stream. Will raise IO.Error[$NotImplementedForThisStream, self] if self is not a stream on an FS.OpenFile. Client errors: $notImplemented User errors: none It is also possible to get the FS.OpenFile back from a file STREAM. FS.OpenFileFromStream: PROC [self: IO.STREAM] RETURNS [FS.OpenFile]; Will raise IO.Error[$NotImplementedForThisStream, self] if self is not a stream on an FS.OpenFile. Client errors: none User errors: none Finding out about errors from generic STREAM procedures is a two step process. First IO.Error[$Failure, self] is raised and is caught by the client. The client then calls FS.ErrorFromStream to obtain more detailed information about the ERROR. FS.ErrorFromStream: PROC [self: IO.STREAM] RETURNS [FS.ErrorDesc]; Will raise IO.Error[$NotImplementedForThisStream, self] if self is not a stream on an FS.OpenFile. Client errors: none User errors: none 12. General file manipulation Copying files, including creating attachments When copying files, only the !N version of the target file can be created. Attachments are created by setting the "attach" argument of FS.Copy to TRUE. Copying from one remote server to another is implemented by retrieving the "from" file into the cache on the system volume. FSExtras.NewCopy: PROC [from, to: ROPE, setKeep: BOOLEAN _ FALSE, keep: CARDINAL _ 1, wantedCreatedTime: BasicTime.GMT _ BasicTime.nullGMT, remoteCheck: BOOLEAN _ TRUE, attach: BOOLEAN _ FALSE, wDir: ROPE _ NIL] RETURNS [toFName: Rope.ROPE]; Same as FS.Copy (next) except returns the full FName of the "to" file created by the copy. Will replace FS.Copy eventually. FS.Copy: PROC [from, to: ROPE, setKeep: BOOLEAN _ FALSE, keep: CARDINAL _ 1, wantedCreatedTime: BasicTime.GMT _ BasicTime.nullGMT, remoteCheck: BOOLEAN _ TRUE, attach: BOOLEAN _ FALSE, wDir: ROPE _ NIL]; The "to" name cannot contain a version part. The version of the "to" file created is one larger than the existing !H version, unless "to" is a GName on a server that does not support version numbers, in which case an existing global file will be overwritten. Case 1: "to" is a GName and "attach" is FALSE  FS does an FS.Open[from, read, wantedCreatedTime, remoteCheck, wdir] and stores the contents and properties of the opened file directly in the new "to" file on the remote server. Case 2: "to" is an LName and "attach" is FALSE  FS does an FS.Open[from, read, wantedCreatedTime, remoteCheck, wDir] to generate a "from" open file, and FS.Create[to, setKeep, keep, wDir] to generate a "to" OpenFile. The contents and properties are copied from the "from" OpenFile to the "to" OpenFile, and both are closed. (In the case of copying from an uncached global file, the global file is not added to the cache. The only pages allocated on the local volume are those needed to hold the target local file.) Case 3: "to" is a GName, "attach" is TRUE, and "from" is a LName  Things proceed as in case 1. Once the transfer is completed, the LName is attached to the GName. Case 3 is used by "SModel". Case 4: "to" is an LName, "attach" is TRUE, and "from" is a GName  Like case 2 except that instead of an actual transfer of contents and properties, the LName is attached to the GName / created-time. If no "wantedCreatedTime" is specified, or if "remoteCheck" is TRUE, then FS.FileInfo[to, wantedCreatedTime, TRUE] is performed first to determine/check the version number and created-time for the GName and any resulting FS.Error is passed on to the client. When "remoteCheck" is FALSE (but a "wantedCreatedTime" is specified) then the attachment is made to the "from" and "wantedCreatedTime" arguments without checking either the remote server or the cache. Case 4 is used by "BringOver". Case 5: both "from" and "to" are LNames, or both are GNames, and "attach" is TRUE  FS proceeds as though "attach" were FALSE (see cases 1 and 2 above). Client errors: $zeroKeep User errors: $nonCedarVolume, $unknownServer, $unknownVolume, $unknownFile, $unknownCreatedTime, $illegalName, $patternNotAllowed, $versionSpecified (on "to" name) Deleting files FS.Delete: PROC [name: ROPE, wantedCreatedTime: BasicTime.GMT _ BasicTime.nullGMT, wDir: ROPE _ NIL]; A missing version part defaults to !L. If "name" is an GName then the file is deleted directly from the remote server (and removed from the cache). An FS.Error[lock, $lockConflict] occurs if the specified file is currently open. If "name" is an LName that is attached to some GName, then just the LName is deleted; the global file is not deleted. Note that deleting the !H version may cause a previous version to become the new !H version, and therefore cause the keep on the previous version to become the controlling keep for the set of versions. Client errors: none User errors: $nonCedarVolume, $unknownServer, $unknownVolume, $unknownFile, $unknownCreatedTime, $illegalName, $patternNotAllowed Renaming files Only the !N version of a file may be created by FS.Rename. Renaming from one server to another is allowed. In the case of the "from" and "to" names being on the same remote server or the same local volume, remaining occurs without copying. Otherwise, FS.Rename is like FS.Copy followed by FS.Delete. FS.Rename: PROC [from, to: ROPE, setKeep: BOOLEAN _ FALSE, keep: CARDINAL _ 1, wantedCreatedTime: BasicTime.GMT _ BasicTime.nullGMT, wDir: ROPE _ NIL]; The "to" name cannot contain a version part. A missing version part defaults to !H for the "from" name. An FS.Error[lock, $lockConflict] occurs if the specified file is currently open. Renaming (and implied copying or deletion) on global servers occurs synchronously. Client errors: $zeroKeep User errors: $nonCedarVolume, $unknownServer, $unknownVolume, $unknownFile, $unknownCreatedTime, $illegalName, $patternNotAllowed, $versionSpecified (on "to" name) Setting keeps on existing files Only the keep of the !H version may be set. The "name" argument to FS.SetKeep cannot contain a version part. FS.SetKeep: PROC [name: ROPE, keep: CARDINAL _ 1, wDir: ROPE _ NIL]; "name" cannot contain a version part. Setting the keep causes any unopen versions that are beyond the new keep to be deleted. Setting the keep to "0" leaves the current keep, but does the keep processing. Client errors: none User errors: $nonCedarVolume, $unknownServer, $unknownVolume, $unknownFile, $unknownCreatedTime, $illegalName, $patternNotAllowed, $versionSpecified, $noKeeps 13. Functions for experts Generic open files FS provides a way for clients to implement objects with the TYPE FS.OpenFile. The client provides procedures that implement the operations on an FS.OpenFile. Refer to section 7 above for the semantics of these procedures. Note that no locking is done by FS on client-provided FS.OpenFile's. FS produces FS.Error[client, $notImplemented] when an attempt is make to use any client procedures that are NIL. FSBackdoor.FileProcs: TYPE; FSBackdoor.CreateFileProcs: PROC [ GetClass: PROC [clientFile: REF] RETURNS [ATOM] _ NIL, SameFile: PROC [clientFile1, clientFile2: REF] RETURNS [BOOLEAN] _ NIL, GetName: PROC [clientFile: REF] RETURNS [fullFName, attachedTo: ROPE] _ NIL, GetInfo: PROC [clientFile: REF] RETURNS [keep: CARDINAL, pages, bytes: INT, created: BasicTime.GMT, lock: FS.Lock] _ NIL, SetPageCount: PROC [clientFile: REF, pages: INT] _ NIL, SetByteCountAndCreatedTime: PROC [clientFile: REF, bytes: INT, created: BasicTime.GMT] _ NIL, Read: UNSAFE PROC [clientFile: REF, from, nPages: INT, to: LONG POINTER] _ NIL, Write: PROC [clientFile: REF, to: INT, nPages: INT, from: LONG POINTER] _ NIL, Close: PROC [clientFile: REF] _ NIL ] RETURNS [REF FSBackdoor.FileProcs]; SameFile will only get called for two clientFile's with equal "REF FileProcs". Client errors: none User errors: none FSBackdoor.CreateProcsOpenFile: PROC [clientFile: REF, fileProcs: REF FSBackdoor.FileProcs] RETURNS [FS.OpenFile]; Whenever any of the procedures above are called for the FS.OpenFile returned by FSBackdoor.CreateProcsOpenFile, the "clientFile" argument to CreateProcsOpenFile is passed on as the identification of the open file that is to be operated upon. Client errors: none User errors: none FSBackdoor.GetClientFileAndProcs: PROC [file: FS.OpenFile] RETURNS [clientFile: REF, fileProcs: REF FSBackdoor.FileProcs]; If "file" is a client-provided FS.OpenFile then the "clientFile" and "fileProcs" are returned, otherwise NIL is returned for both results. Client errors: $invalidOpenFile User error: none The client procedures should raise FS.Error as appropriate. Client procedures should generate the code ATOM's used by FS where meaningful, but can produce additional code ATOM's if needed. FS.Error's with existing code ATOM's should be generated using FSBackdoor.ProduceError (see below). For any new code ATOM's, the client is responsible for providing the appropriate FS.ErrorGroup. FSBackdoor.ErrorCode: TYPE = {ok -- used internally-- , inconsistent, software, badFP, wentOffline, hardware, volumeFull, fragmented, noMoreVersions, serverInaccessible, connectionRejected, connectionTimedOut, badCredentials, accessDenied, quotaExceeded, invalidPropertyPage, badBTree, lockConflict, fileBusy, noCache, wrongLock, globalWriteLock, zeroKeep, badByteCount, unknownPage, invalidOpenFile, notImplemented, nonCedarVolume, unknownServer, unknownVolume, unknownFile, unknownCreatedTime, illegalName, patternNotAllowed, versionSpecified, globalCreation, badWorkingDir, noKeeps, cantUpdateTiogaFile}; FSBackdoor.ProduceError: PROC [code: FSBackdoor.ErrorCode, explanation: ROPE]; Raises FS.Error for the code ATOM that corresponds lexically to the "code". The FS.ErrorGroup in the error is properly set. The error will contain "explanation". Clients should use this procedure to generate FS.Error's for existing codes to avoid spelling the ATOM's wrong and to avoid getting the wrong FS.ErrorGroup. Volume space management FS provides access to the File package procedures for setting the volume freeboard on the system volume and reading the size, free and freeboard counts from a named volume. FSBackdoor.SetFreeboard: PROC [freeboard: INT]; Client errors: none User errors: $unknownVolume FSBackdoor.VolumePages: PROC [volName: ROPE _ NIL] RETURNS [size, free, freeboard: INT]; "volName" = NIL means the system volume. Client errors: none User errors: $unknownVolume Manual cache management FSBackdoor.InfoProc: TYPE = PROC [fullGName: ROPE, created: BasicTime.GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOLEAN]; FSBackdoor.EnumerateCacheForInfo: PROC [proc: FSBackdoor.InfoProc, volName, pattern: ROPE _ NIL]; FSBackdoor.NameProc: TYPE = PROC [fullGName: ROPE] RETURNS [continue: BOOLEAN]; FSBackdoor.EnumerateCacheForNames: PROC [proc: FSBackdoor.NameProc, volName, pattern: ROPE _ NIL]; Like FS.EnumerateForInfo and FS.EnumerateForNames, except that only cached GNames in the named volume are enumerated. "volName" = NIL means the system volume. "pattern" = NIL means to match all names. Client errors: $noCache User errors: $nonCedarVolume, $unknownVolume, $illegalName FSBackdoor.Flush: PROC [fullGName: ROPE, volName: ROPE _ NIL]; The specified GName is removed from the directory/cache on the named volume and any associated file on that volume is deleted. "volName" = NIL means the system volume. Locking is done only for the system volume. In this case FS.Error[lock, $lockConflict] is generated when a "write" lock cannot be obtained. Flushing a cached file from other than the system volume is done independent of any locks that may be set, so BE SURE YOU KNOW WHAT YOU ARE DOING. No FS.Error occurs if the "fullGName" is not in the cache on the indicated volume Client errors: none User errors: $nonCedarVolume, $unknownVolume Direct access to the directory and cache on any volume The directory of LNames and the cache for GName for a volume are implemented together as a BTree. The following TYPE's represent an entry in this BTree. FSBackdoor.EntryType: TYPE = {local, attached, cached, notFound}; -- notFound will not occur in an FSBackdoor.Entry FSBackdoor.EntryPtr: TYPE = LONG BASE POINTER TO FSBackdoor.Entry; FSBackdoor.TextRP: TYPE = FSBackdoor.EntryPtr RELATIVE POINTER TO FSBackdoor.TextRep; FSBackdoor.TextRep: TYPE = MACHINE DEPENDENT RECORD [PACKED SEQUENCE length: CARDINAL OF CHAR]; FSBackdoor.Version: TYPE = RECORD [CARDINAL]; FSBackdoor.noVersion: FSBackdoor.Version = [LAST[CARDINAL]]; -- the version for a GName that includes no version, e.g. the GName "[Cabernet]gv.log" would have FSBackdoor.noVersion as the "version" part of it's FSBackdoor.Entry ************************************************************* WARNING: You should treat an FSBackdoor.Entry as READONLY ************************************************************* FSBackdoor.Entry: TYPE = MACHINE DEPENDENT RECORD [ size(0): CARDINAL, -- size in words of entire entry, including TextRep's at end version(1): FSBackdoor.Version, -- version part of FName nameBody(2): FSBackdoor.TextRP, -- FName w/o version rest(3): SELECT type(3): FSBackdoor.EntryType FROM local => [ keep(4): CARDINAL, fp(5): File.FP ], attached => [ keep(4): CARDINAL, created(5): BasicTime.GMT, attachedTo(5+SIZE[BasicTime.GMT]): FSBackdoor.TextRP -- full FName including version part ], cached => [ used(4): BasicTime.GMT, fp(4+SIZE[BasicTime.GMT]): File.FP ] ENDCASE -- here follows the TextRep for nameBody and possibly for attachedTo ]; The "nameBody" in an FSBackdoor.Entry is an FName without a version part, expressed in brackets syntax. LNames appear without the "[]<>" prefix. Thus the "nameBody" for the LName "[]<>foo.mesa!5" will be "foo.mesa" and for the GName "[indigo]Top>FS.df!47" will be "[indigo]Top>FS.df". The version part is recorded separately as a scalar. An entry in the directory/cache can be gotten at directly via the following TYPE's and procedures. FSBackdoor.highestVersion: FSBackdoor.Version = FSBackdoor.noVersion; -- representation for !H; will not appear in an FSBackdoor.Entry FSBackdoor.lowestVersion: FSBackdoor.Version = [0]; -- representation for !L; will not appear in an FSBackdoor.Entry FSBackdoor.Enumerate: PROCEDURE [ volName: Rope.ROPE, nameBodyPattern: Rope.Text, localOnly, allVersions: BOOLEAN, version: FSBackdoor.Version, matchProc: UNSAFE PROC [entry: FSBackdoor.EntryPtr] RETURNS [accept, stop: BOOLEAN], acceptProc: PROC RETURNS [stop: BOOLEAN] ]; "volName" is the name of a disk volume. A NIL "volName" means the system volume. The directory/cache of the volume is enumerated. The order of enumeration is lexical by nameBody (lower case letters are mapped to upper case) then numerical by version. Because LNames in the directory/cache do not have the "[]<>" prefix, and because of the constraints on the characters allowed in FNames, LNames appear in the directory/cache earlier in the enumeration order than GNames. The scope of the enumeration is constrained by the "localOnly" argument: if TRUE then the enumeration stops when the first GName is encountered; otherwise the enumeration can continue through the GNames. The "nameBodyPattern" is applied without consideration of any working directories and without systax checking. Conversion from slash syntax to brackets syntax is performed. Malformed patterns will not raise FS.Error; instead nothing will match. If you want to match only GNames, then be sure the "nameBodyPattern" starts with "[". A NIL "nameBodyPattern" defaults to "*". If "allVersions" is TRUE then all versions of entries selected by "nameBodyPattern" will match. If "allVersions" is FALSE then the versions that will match is specified by "version"; FSBackdoor.highestVersion and FSBackdoor.lowestVersion specify matching just the !H or !L versions, respectively; any other value specifies matching just that version. "matchProc" is called for each matching entry. If "stop" is returned as TRUE then the enumeration terminates. Otherwise, if "accept" is returned as TRUE then "acceptProc" is called. The "acceptProc" can also stop the enumeration. The directory/cache is read locked while "matchProc" is called, so other operations on the directory/cache and tasks involving indefinite waits should not be done from "matchProc". Do these from the "acceptProc" instead. Client errors: none User errors: $nonCedarVolume, $unknownVolume FSBackdoor.TextFromTextRep: PROCEDURE [nameBody: LONG POINTER TO FSBackdoor.TextRep] RETURNS [Rope.Text]; If "e" is an "EntryPtr", and "e.t" is a contained "TextRP", then "@e[e.t]" is a "LONG POINTER TO TextRep". Client errors: none User errors: none FSBackdoor.MakeFName: PROCEDURE [nameBody: Rope.ROPE, version: FSBackdoor.Version _ noVersion, prefix: Rope.ROPE _ NIL] RETURNS [Rope.ROPE]; Makes an FName out of a "nameBody" and "version", and a "prefix". Does not do a syntax check. The prefix is prepended if the nameBody does not start with the character "[". If the nameBody comes from the directory of some named volume other than the system volume, then a prefix of "[]" should be supplied. If a prefix is needed and NIL is supplied, then "[]<>" is used. Note that FSBackdoor.MakeFName need not be applied to an "attachedTo" name from an FSBackdoor.Entry, since it is stored as a full FName. Client errors: none User errors: none Directory/cache reconstruction FSBackdoor.ScavengeDirectoryAndCache: PROC [ volName: ROPE _ NIL ]; The directory/cache BTree for the named volume is rebuilt by enumerating the leader pages of all files on the volume and merging in any attached LNames that can be recovered from an existing BTree. "volName" = NIL means the system volume. While scavenging is occurring all FS operations that access the directory will wait. DO NOT call FSBackdoor.CloseVolume first. Client errors: none User errors: $nonCedarVolume, $unknownVolume Reporting activity FS provides procedures for reporting the occurance of events. FSExtras.NextRemoteEvent reports all actions on remote files, and is intended for use by clients like the "WatchTool". FSExtras.NextCreateEvent reports all creations of new LName - to - data bindings, and is intended for use by clients like "Tioga" that may want to know when new versions of local files come into existence. FSExtras.RemoteOp: TYPE = {startRetrieving, endRetrieving, startStoring, endStoring, startDeleting, endDeleting, startRenaming, endRenaming, startFlushing, endFlushing}; FSExtras.RemoteEvent: TYPE = RECORD [ op: FSExtras.RemoteOp, -- remote operation that has occurred fName: Rope.ROPE, -- full GName of the remote file operated upon chain: REF FSExtras.RemoteEvent -- used internally to chain events together ]; FSExtras.NextRemoteEvent: PROC [REF READONLY FSExtras.RemoteEvent _ NIL] RETURNS [REF READONLY FSExtras.RemoteEvent]; A remote event is the start or conclusion of retrieving, storing, deleting, renaming or flushing (from the cache) a global file. NextRemoteEvent returns the next event that occurs after the argument RemoteEvent. When the argument RemoteEvent is NIL then NextRemoteEvent returns the first event to occur after the time of the call. NextRemoteEvent will WAIT if another event has not occurred yet. Storing, retrieving and flushing of several files can be in progress simultaneously. Client errors: none User errors: none FSExtras.CreateOp: TYPE = {writeClose, renameTo, copyTo}; FSExtras.CreateEvent: TYPE = RECORD [ op: FSExtras.CreateOp, -- creation operation that has occurred fName: Rope.ROPE, -- full LName of the local file that has been created chain: REF FSExtras.CreateEvent -- used internally to chain events together ]; FSExtras.NextCreateEvent: PROC [REF READONLY FSExtras.CreateEvent _ NIL] RETURNS [REF READONLY FSExtras.CreateEvent]; A creation event is the closing of a local file that was open for write (was just created or overwritten), the completion of an FS.Rename call where "to" is an LName, or the completion of an FS.Copy call where "to" is an LName. NextCreateEvent returns the next event that occurs after the argument CreateEvent. When the argument CreateEvent is NIL then NextCreateEvent returns the first event to occur after the time of the call. NextCreateEvent will WAIT if another event has not occurred yet. Client errors: none User errors: none The following is the obsolete facility for reporting on remote events. FSBackdoor.EventHandle: TYPE = REF FSBackdoor.EventObject; FSBackdoor.EventObject: TYPE; FSBackdoor.EventOp TYPE = {startRetrieving, endRetrieving, startStoring, endStoring, startFlushing, endFlushing}; FSBackdoor.NextEvent: PROC [last: FSBackdoor.EventHandle _ NIL] RETURNS [fName: ROPE, op: FSBackdoor.EventOp, this: FSBackdoor.EventHandle]; An event is the start or conclusion of retrieving, storing, or flushing (from the cache) a global file. Given a "last" event, a call on FSBackdoor.NextEvent returns in "this" the next event along with the FName of the remote file operated upon and the operation. When "last" is NIL then the next event in time is returned. Will WAIT if necessary for another event to occur. Storing, retrieving and flushing of several files can be in progress simultaneously. Client errors: none User errors: none Other functions FSBackdoor.CloseVolume: PROC [v: File.Volume]; For experts only!!! Tells FS to close any directory/cache BTree it has open for the volume. May cause FS operations in progress to raise various strange BTree and File ERROR's. After CloseVolume returns, new FS operations will try to reopen a directory/cache BTree for the volume. Should be called AFTER a volume is erased. Client errors: none User errors: none FSBackdoor.GetFileHandle: PROC [file: FS.OpenFile] RETURNS [File.Handle]; For experts only!!! Returns the File.Handle that goes with an FS.OpenFile. Client errors: $invalidOpenFile, $notImplemented (for client-provided FS.OpenFile's) User errors: none FSBackdoor.FNameFromHandle: PROC [file: File.Handle] RETURNS [ROPE]; Returns the full FName of the named "file". Will raise FS.Error[environment, $invalidPropertyPage] if the handle is not for an FS file. Client errors: none User errors: none Ê ¤– "cedar" style˜Iblock•Mark centerHeaderšœ&˜&K– centerFooteršÏk ˜ Ititle˜&Isubtitlešœ*˜*Iabstractš œ* œ œ˜uK˜K˜K˜I boilerplateš ÏqœÏoœŸœŸœŸ Ñbox˜’head˜˜Kšœ®˜®—˜Kšœq˜q—˜Kšœ«˜«Kšœ+œÅœ­˜¡Kšœ)œœ½˜ï—˜Kšœê˜ê——˜9˜šœÈœžœ˜„Iunitšœœ˜—šœ›˜›Qšœœ˜—šœ»˜»Qšœœ˜—šœ·˜·Qšœœ˜—Kšœ‹˜‹—˜šœvœB˜ºQšœœ œ˜"Jšœ5 œ˜C——šœ ˜ Kšœuœœ†œW˜èKšœXœ/œ)œJ˜€Kšœ¤˜¤——˜˜ š œ3œœYœ(œ3œ˜ýQšÐbkÏbœœ œ ˜&Qš¡¢ œœœ œœœ œœ˜c š¡¢ œœ˜JšœÏcÐck£ ˜+Jšœ£˜!Jšœ £D˜QJšœ£˜Jšœ£;˜CJšœ£1˜6Jšœ˜——KšœÝ˜ÝKšœCœ”˜ÛKšœœS˜sKšœ0œ]œ˜˜—˜Kšœœ!œ»œ ˜€˜JšœÐciÏi¥*˜LJšœ ¦K˜UJšœ¦Ðik¦D˜U—˜Jšœ£$˜4Jšœ ¦>˜HJšœ ¦'˜3Jšœ ¦¥8˜LJšœ¦'˜7Jšœ¦/˜CJšœ¦¥˜DJšœ¦*˜>Jšœ¦3˜CJšœ¦*˜8Jšœ¦G˜VJšœ¦¥0˜VJšœ ¦¥6˜D—˜ Jšœ¦¥ ˜1Jšœ ¦:˜D—˜ Jšœ ¦¥N˜ZJšœ ¦¥<˜JJšœ¦¥'˜;Jšœ ¦¥˜$Jšœ¦¥@˜QJšœ ¦¥<˜LJšœ¦Ñcik¥¨¥)˜YJšœ¦¥1¨¥ ˜O—˜ Jšœ£"˜5Jšœ£3˜EJšœ£"˜4Jšœ£"˜2Jšœ£=˜TJšœ£W˜gJšœ¦F˜YJšœ£0˜EJšœ£˜*Jšœ£C˜UJšœ ¦B˜KJšœ£8˜P———˜˜šœ²œ(˜ÜQš¡¢œœ˜"—šœœ…œ>˜åIdisplayšœl˜l—KšœÞ˜ÞKšœq˜qKšœ·˜·šœ¾˜¾Ršœ]˜]—Kšœ•˜•˜ RšœÏ˜ÏR˜°Ršœ¯˜¯——˜KšœÅ˜Å—˜Kšœí˜íšœø˜øRšœœ˜£—Kšœ œ„˜’˜< š ¡Ïnœœœœ˜*Iindentš¦Ô˜ÔSš¦˜Sš¦¥¦¥¦˜>——˜= š ¡©œœœœ˜'Sš¦˜Sš¦˜———˜Kšœã˜ã—˜2šœ:˜:Qš ¡¢œœœœ˜5Qš ¡¢œœœ(œ ˜X š¡© œœœœœœ œœ œ˜|Sš¦„˜„Sš¦”˜”Sš¦²˜²Sš¦£§¦§¦˜ÄSš¦˜˜˜Sš¦e¦˜„Sš¦†˜†Sš¦˜Sš¦˜—Qš ¡¢œœœ(œœ˜T š¡©œœœœœœœ˜YSš¦'¦½˜èSš¦(§¦§¦ §¦,§¦§¦M§¦§¦'§¦§¦'§¦˜ñSš¦˜Sš¦˜————˜˜Kšœº˜º—˜-šœ[˜[Qš ¡© œœœœ œ˜8Qš ¡© œœœœ œ˜8Qš ¡© œœœœ œ˜8Qš ¡© œœœœ œ˜8——˜Kš œ+œ™œaœœ ˜×Kšœ¶œm˜¥Kšœœƒ˜Kšœœœo˜˜K˜,——˜˜Kšœ»œ œ œFœ œðœ¤œ‘˜Ú —˜KšœôœB˜º—˜ Kšœ·˜·Kšœ/œœž˜Ò——˜˜#Kšœ}œÐ˜Ï—˜šœŒœ˜ªQšÐkn©œœ˜—KšœÁ˜ÁKšœÞ˜ÞK˜é——˜"šœ˜ šª©œœœœ#œœœœœœœ œœ˜ÞSš¦ð˜ðSš¦Û§¦+˜‰Sš ¦Š§¦Ã§¦v§¦÷§¦#˜êSš¦Ü§¦¡§¦T§¦˜úSš¦˜Sš ¦¥¦¥¦¥¦ ¥¦¥¦ ¥¦˜‚——˜KšœãœÇ˜­Kš œMœœœ0œ˜µQšª©œœœœœ œœœ œ˜‡ š ª©œœ œœœœ˜OSš¦R§¦¢§¦€§¦#˜ Sš¦˜Sš¦¥¦¥¦¥¦ ˜K—Qš ª©œœœ œœ œ˜G š ª©œœ œœœœ˜PSš¦w˜wSš¦˜Sš¦¥¦¥¦¥¦ ˜K———˜šœ ˜ šœœÓ˜ØQšª¢œœ œ˜!Qš¡¢ œœ œ˜%—Kš œ2œ…œ’œ¨œg˜à—˜šœ=˜= šª©œœœœ+œ#œœœœœœ ˜­Sš ¦§¦F§¦?§¦±§¦§¦ ˜çSš¦¥¦˜*Sš ¦¥¦¥¦¥¦ ¥¦¥¦ ¥¦˜‚———˜šœ­œ œ9˜ô šª©œœœ œœ œœœœ œœœœ ˜Sš¦²§¦}§¦S§¦m˜üSš¦¥¦˜*Sš ¦¥¦¥¦¥¦ ¥¦¥¦ ¥¦˜‚——šœ˜ šª© œœœœ œ œœœœ ˜oSš ¦ §¦0§¦$§¦‚§¦˜ùSš¦¥¦¥¦ ˜5Sš ¦¥¦¥¦¥¦¥¦ ¥¦%¥¦˜˜————˜$˜ š ª©œœœ œœ˜5Sš ¦ §¦a§¦§¦ §¦o§¦8§¦+˜áSš¦¥¦˜0Sš¦˜— š ª©œœœ œœ˜@Sš¦§¦6§¦V§¦˜Sš¦ ¥¦˜1Sš¦˜— š ª©œœœ œœ˜KSš¦ƒ§¦˜‡Sš¦ ¥¦˜1Sš¦˜— šª©œœœ œœœœœ˜xSš¦ž˜žSš¦ ¥¦˜1Sš¦˜——˜ š ª© œœœœ˜6Sš¦˜˜˜Sš¦¥¦ ¥¦¥¦˜KSš¦˜— š ª©œœœœœ˜uSš¦Å˜ÅSš¦¥¦ ¥¦¥¦˜LSš¦˜——˜ šª©œœœœœœœ˜NSš¦§¦=§¦ ˜ÜSš¦¥¦¥¦˜?Sš¦˜— šª©œœœœ œœœ˜MSš¦œ§¦?§¦ ˜ëSš¦¥¦ ¥¦¥¦˜KSš¦˜——š©˜ šª©œœœ ˜#Sš¦^§¦§¦j§¦C˜«Sš¦ ¥¦˜1Sš¦˜———˜˜ Kšœe˜eKš œGÏe œœ0« œ« œœ «œ4˜ðKš œ!œpÏrœ¬œ1 œX˜¿˜PQš ÑknzÐnzœœœœœ˜.Sš¬Ðkr¬¯¬xœ˜ŠQš ­® œœœœœœ˜œœœ œ˜¾Sš¦)§¦.§¦ ˜e š¦[§¦6§¦ ˜¡Sš¦ §¦˜!Sš¦ §¦+§¦!˜[Sš¦ §¦T˜aSš¦ §¦$˜0— š¦é§¦c˜ÎSš¦i˜iSš¦x˜xSš¦Y˜YSš¦D˜D—Sš¦¥¦¥¦ ˜5Sš ¦¥¦¥¦¥¦ ¥¦ ¥¦%¥¦%˜§—Qš¢œœ˜% šª©œœCœ)œ9œ>œœœ œ˜´Sš¦Š˜ŠSš¦˜Sš¦"˜"— š ª ¢œœ œœ œ˜KSš¦ï§¦I§¦ ˜ÆSš¦˜Sš¦˜——šœœœ˜C š ª©œœ œœœ ˜DSš¦ §¦I§¦ ˜bSš¦˜Sš¦˜——š œ&œ*œUœ?œ˜ô š ª©œœ œœœ ˜BSš¦ §¦I§¦ ˜bSš¦˜Sš¦˜————˜˜-šœˆœ œ~˜• š¡ ©œœ œ œœœ#œ#œœ œœœœœœ˜ñSš¦§¦_§¦˜|— š¡©œœ œ œœœ#œ#œœ œœœœ˜ËSš¦„˜„Sš¦(§¦§¦¥˜âSš¦)§¦§¦\§¦ê˜†Sš¦%§¦˜˜ÁSš ¦'§¦ß§¦§¦!§¦l§¦:§¦Í˜¶Sš¦M§¦'§¦˜˜Sš¦˜Sš ¦¥¦¥¦¥¦ ¥¦¥¦ ¥¦4˜¤———˜ š ª©œœœœœœ˜eSš¦™§¦Ž˜©Sš¦˜Sš ¦¥¦¥¦¥¦ ¥¦¥¦ ¥¦˜‚——˜š œ0œÌœœœ˜®Qšª©œœ œ œœœ#œœœ˜—Sš¦R§¦§¦Ÿ˜ŽSš¦˜Sš ¦¥¦¥¦¥¦ ¥¦¥¦ ¥¦4˜¤——˜šœœ-œ'˜mQš ª©œœœœ œœ˜DSš¦Î˜ÎSš¦˜Sš ¦¥¦¥¦¥¦ ¥¦¥¦ ¥¦/˜Ÿ———˜˜š œ<œœOœƒœœ^œ˜—Qšª ©¢ œœ˜ šª ©œœ˜"Jš ©œœœœœœ˜6Jš ©œœœœœœ˜GJš ©œœœœœœ˜LJš©œœœœœœœœ œ˜yJš © œœœ œœ˜7Jš ©œœœ œœœ˜]Jš©œœœœœœœœ˜OJš©œœœœ œœœœ˜NJš©œœœ˜#Jšœœœ˜%Sš¦?§¦ ˜NSš¦˜Sš¦˜— š ª ©œœœ œœœ ˜rSš¦8§¦·˜ñSš¦˜Sš¦˜— š ª ©œœœ œœ œ˜zSš¦§¦H§¦˜ŠSš¦ ˜ Sš¦˜——šœ#œCœ@œœœTœ<œ ˜ƒQšª ¢ œœ£œ©˜ß šª © œœ+œ˜NSš ¦§¦§¦0§¦€§¦2§¦(§¦ ˜Á———˜šœ¬˜¬ šª © œœ œ˜/Sš¦˜Sš¦˜— š ª © œœ œœœ˜XSš¦ §¦˜(Sš¦˜Sš¦˜———˜Qšª © œœœ œœ œœœ œ˜ƒQš ª ©œœ/œœ˜aQš ª © œœœ œœ œ˜O š ª ©œœ/œœ˜bSš ¦§¦§¦d§¦'§¦˜ÊSš¦˜Sš¦¥¦¥¦ ˜;— š ª ©œœ œ œœ˜>Sš¦Œ§¦U§¦é§¦L˜Sš¦˜Sš¦¥¦˜-——˜6šœqœ$˜™Qš¡ ¢ œœ(£1˜sQš ¡ ¢ œœœœœœ˜BQš ¡ ¢œœœœœ˜UQš¡ ¢œœœ œœœœ œœœ˜_Qš ¡ ¢œœœœ˜- š¡ ¢ œœœ˜Jšœ œ£5˜GJšœœ£+˜KJšœ˜—Qš¡¢©œœœœœœœœ˜uSš ¦€§¦=§¦™§¦i§¦(˜òSš¦˜Sš¦˜—šœH˜HQš¡ ¢ œœœ˜:Qš¡ ¢ œœ˜Qš¡ ¢œœZ˜q š ª © œœ!œœ œ8˜ŒSš¦˜§¦0§¦€˜ÏSš¦˜Sš¦˜———˜ šª © œœ˜.Sš¦ª§¦™˜ÈSš¦˜Sš¦˜— š ª ©œœœ œ˜ISš¦?§¦ ˜KSš¦ ¥¦%§¦ ˜USš¦˜— š ª ©œœœœ˜DSš¦8§¦O˜‰Sš¦˜Sš¦˜—K˜—˜ ˜J˜————…—D8î