DIRECTORY Arpa, Ascii, Basics, Basics16, BasicTime USING [GetClockPulses, GMT, Now, nullGMT, Pack, Period, TimeNotKnown, Update], Convert, CStrings USING [CString], HostTime, IO, PFS, PFSBackdoor, PFSClass, PFSNames, RefText USING [ObtainScratch, ReleaseScratch], Rope USING [Compare, Cat, Concat, Equal, Fetch, Find, FindBackward, Flatten, FromChar, FromRefText, Index, IsEmpty, IsPrefix, Length, Match, ROPE, Substr], UnixErrno, UnixFSPrivate USING [ReportFailure, TranslateErrno], UnixSysCalls, UnixSysCallExtensions USING [SetGetBlocking, SetPutBlocking], UXStrings, UnixTypes USING [AccModTimes, FD, FileFlags, Fmt, Mode, RES, Stat, Time, TimeVal], VUXFiles ; VUXFileImpl: CEDAR MONITOR LOCKS lock.lock USING lock: REF VUXData IMPORTS Arpa, Ascii, Basics, Basics16, BasicTime, Convert, HostTime, IO, PFS, PFSBackdoor, PFSClass, PFSNames, RefText, Rope, UnixErrno, UnixFSPrivate, UnixSysCalls, UnixSysCallExtensions, UXStrings, VUXFiles EXPORTS PFS, VUXFiles ~ { OpenFileObject: PUBLIC TYPE ~ PFSClass.OpenFileObject; -- necessary to establish a type identity for the compiler FHandle: TYPE ~ VUXFiles.FHandle; GMT: TYPE ~ BasicTime.GMT; DirHandle: TYPE ~ VUXFiles.DirHandle; ROPE: TYPE ~ Rope.ROPE; Mode: TYPE ~ UnixTypes.Mode; Version: TYPE ~ PFSNames.Version; Component: TYPE ~ PFSNames.Component; PATH: TYPE ~ PFSNames.PATH; FileType: TYPE ~ PFS.FileType; UniqueID: TYPE ~ PFS.UniqueID; MTime: TYPE ~ INT; RES: TYPE ~ UnixTypes.RES; FSHandle: TYPE ~ PFSClass.FSHandle; CString: TYPE ~ CStrings.CString; unixEpoch: GMT ฌ BasicTime.Pack[ [year~1970, month~January, day~1, hour~0, minute~0, second~0, zone~0, dst~no] ]; defaultCreateMode: Mode ฌ [fmt~reg, owner~[true, true, false]]; defaultCaseFileMode: Mode ฌ [fmt~reg, owner~[true, false, false], group~[true, false, false], others~[true, false, false]]; lockRetries: CARDINAL ฌ 10; bufferBytes: CARDINAL ฌ 8192; vuxFlavor: ATOM ~ $VUX; LookupSimple: PROC [dH: DirHandle, nameOnServer: ROPE] RETURNS [fH: FHandle, fmt: UnixTypes.Fmt, bytes: INT, mTime: MTime, attachedTo: PATH ฌ NIL] ~ { reply: VUXFiles.DirOpRes; [reply, attachedTo] ฌ VUXFiles.Lookup[dH, nameOnServer]; SELECT reply.status FROM ok => { fH ฌ reply.file; fmt ฌ reply.attributes.mode.fmt; bytes ฌ LOOPHOLE[reply.attributes.size]; mTime ฌ reply.attributes.mtime; }; ENOENT => { fH ฌ NIL; fmt ฌ reg; bytes ฌ 0; mTime ฌ 0; }; ENDCASE => PFSBackdoor.ProduceError[UnixFSPrivate.TranslateErrno[reply.status], IO.PutFR1["Unix error in VUXFileImpl.LookupSimple for %g", [rope[nameOnServer]] ], NEW[UnixErrno.Errno ฌ reply.status]]; }; ux0Component: Component ~ PFSNames.Fetch[PFS.PathFromRope["-ux:/"], 0]; ResolveAttachment: PROC [attachment: PATH, attachedTo: PATH] RETURNS [PATH] ~ { { absolute: PATH ~ IF PFSNames.IsAbsolute[attachedTo] THEN attachedTo ELSE PFSNames.Cat[PFSNames.Directory[attachment], attachedTo]; RETURN[ PFSNames.ReplaceComponent[absolute, 0, ux0Component] ]; }; }; LookupVersionByMTime: PROC [dH: DirHandle, base: ROPE, wantedMTime: MTime] RETURNS [fH: FHandle, nameOnServer: ROPE, version: Version, fmt: UnixTypes.Fmt ฌ reg, bytes: INT ฌ 0, attachedTo: PATH] ~ { found: BOOL ฌ FALSE; EachDirEntry: VUXFiles.EachDirEntryProc -- [entry: DirEntry] RETURNS [continue: BOOL ฌ TRUE] -- ~ { version ฌ entry.version; SELECT Rope.Compare[entry.nameWithoutVersion, base, FALSE] FROM less => RETURN[TRUE]; -- must be the case file equal => IF (entry.version # [none]) THEN { mTime: MTime; nameOnServer ฌ EncodeVersionInName[entry.nameWithoutVersion, entry.version.version]; [fH~fH, fmt~fmt, bytes~bytes, mTime~mTime, attachedTo~attachedTo] ฌ LookupSimple[dH, nameOnServer]; IF (fH # NIL) AND (mTime = wantedMTime) THEN { found ฌ TRUE; RETURN[FALSE]; }; }; greater => { RETURN[FALSE]; }; ENDCASE => ERROR; -- implementation error; invariants violated }; VUXFiles.EnumerateDirectory[dH, EachDirEntry, base, FALSE]; IF NOT found THEN RETURN [NIL, base, [none], reg, 0, NIL]; }; FindHighestVersion: PROC [dH:DirHandle, base: ROPE, staleOK: BOOL] RETURNS [highestVersion: Version ฌ [none], nameOnServer: ROPE, found: BOOL ฌ FALSE] ~ { highestVersionNo: INT ฌ 0; foundCased: BOOL ฌ FALSE; foundUncased: BOOL ฌ FALSE; EachDirEntry: VUXFiles.EachDirEntryProc -- [entry: DirEntry] RETURNS [continue: BOOL ฌ TRUE] -- ~ { SELECT Rope.Compare[entry.nameWithoutVersion, base, FALSE] FROM less => RETURN[TRUE]; -- must be the case file equal => IF (entry.version # [none]) THEN { temp: INT ~ entry.version.version; -- for coercion between Version and INT IF temp > highestVersionNo THEN { foundCased ฌ TRUE; highestVersionNo ฌ temp; } } ELSE foundUncased ฌ TRUE; greater => RETURN[FALSE]; ENDCASE ; }; VUXFiles.EnumerateDirectory[dH, EachDirEntry, base, staleOK]; IF foundCased THEN { nameOnServer ฌ EncodeVersionInName[base, highestVersionNo]; highestVersion ฌ [numeric, highestVersionNo]; found ฌ TRUE; } ELSE IF foundUncased THEN { nameOnServer ฌ base; found ฌ TRUE; }; }; FindLowestVersion: PROC [dH: DirHandle, base: ROPE, staleOK: BOOL] RETURNS [lowestVersion: Version ฌ [none], nameOnServer: ROPE, found: BOOL ฌ FALSE] ~ { lowestVersionNo: INT ฌ INT.LAST; foundCased: BOOL ฌ FALSE; foundUncased: BOOL ฌ FALSE; EachDirEntry: VUXFiles.EachDirEntryProc -- [entry: DirEntry] RETURNS [continue: BOOL ฌ TRUE] -- ~ { SELECT Rope.Compare[entry.nameWithoutVersion, base, FALSE] FROM less => RETURN[TRUE]; -- must be the case file equal => IF (entry.version # [none]) THEN { temp: INT ~ entry.version.version; -- for coercion between Version and INT IF( temp > 0) AND (temp < lowestVersionNo) THEN { foundCased ฌ TRUE; lowestVersionNo ฌ temp; } } ELSE foundUncased ฌ TRUE; greater => RETURN[FALSE]; ENDCASE; }; VUXFiles.EnumerateDirectory[dH, EachDirEntry, base, staleOK]; IF foundCased THEN { nameOnServer ฌ EncodeVersionInName[base, lowestVersionNo]; lowestVersion ฌ [numeric, lowestVersionNo]; found ฌ TRUE; } ELSE IF foundUncased THEN { nameOnServer ฌ base; found ฌ TRUE; }; }; FindExistingFile: PROC [name: PATH, wantedUniqueID: UniqueID] RETURNS [dH: DirHandle, fH: FHandle, nameOnServer: ROPE, version: Version, fmt: UnixTypes.Fmt, bytes: INT, uniqueID: UniqueID, attachedTo: PATH] ~ { base: ROPE; { ENABLE UNWIND => { IF dH # NIL THEN VUXFiles.UnPinDirPath[dH]; }; isPattern: BOOL; found: BOOL; mTime: MTime; dH ฌ VUXFiles.FollowDirPath[path~PFSNames.Parent[name], case~FALSE, create~FALSE]; [base~base, version~version, isPattern~isPattern] ฌ ReadBaseComponent[path~name, case~FALSE]; IF isPattern THEN PFSBackdoor.ProduceError[patternNotAllowed, IO.PutFR1["Pattern (%g) not allowed in VUXFileImpl.FindExistingFile", [rope[PFS.RopeFromPath[name]]] ], name]; SELECT version.versionKind FROM none, highest => { [version, nameOnServer, found] ฌ FindHighestVersion[dH, base, FALSE]; IF NOT found THEN GOTO Out; }; lowest => { [version, nameOnServer, found] ฌ FindLowestVersion[dH, base, FALSE]; IF NOT found THEN GOTO Out; }; all => { IF (wantedUniqueID = PFS.nullUniqueID) THEN ProduceError[patternNotAllowed, "Version * not allowed in VUXFileImpl.FindExistingFile", name]; found ฌ FALSE; }; numeric => { nameOnServer ฌ EncodeVersionInName[base, version.version]; found ฌ TRUE; }; ENDCASE => ERROR -- can't happen -- ; IF found THEN [fH, fmt, bytes, mTime, attachedTo] ฌ LookupSimple[dH, nameOnServer]; uniqueID ฌ [egmt~[gmt~GMTFromMTime[mTime], usecs~0]]; IF (wantedUniqueID = PFS.nullUniqueID) THEN GOTO Out; IF (fH = NIL) OR ((wantedUniqueID # PFS.nullUniqueID) AND (wantedUniqueID # uniqueID)) THEN [fH, nameOnServer, version, fmt, bytes, attachedTo] ฌ LookupVersionByMTime[dH, base, MTimeFromGMT[wantedUniqueID.egmt.gmt]]; IF attachedTo # NIL THEN attachedTo ฌ ResolveAttachment[name, attachedTo]; uniqueID ฌ wantedUniqueID; -- okay even if lookup failed EXITS Out => NULL; }; VUXFiles.UnPinDirPath[dH]; IF fH = NIL THEN { PFSBackdoor.ProduceError[unknownFile, IO.PutFR1["%g not found in VUXFileImpl.FindExistingFile", [rope[PFS.RopeFromPath[name]]] ], name]; }; }; AtomicallyRename: PROC [dHFrom: DirHandle, fHFrom: FHandle, dHTo: DirHandle, baseTo: ROPE, desiredVersion: Version, deleteIfError: BOOL, attach: BOOL ฌ FALSE] RETURNS [version: Version] ~ { toNameOnServer: ROPE; toNameRefText: REF TEXT; { ENABLE UNWIND => { IF deleteIfError THEN SilentlyRemove[dHFrom, fHFrom]; }; SELECT desiredVersion.versionKind FROM lowest => PFSBackdoor.ProduceError[versionSpecified, IO.PutFR1["Lowest is not a valid version number for a new file (%g)", [rope[baseTo]] ], baseTo]; next, highest, none => { found: BOOL; vTemp: CARDINAL; res: RES; [highestVersion~version, found~found] ฌ FindHighestVersion[dH~dHTo, base~baseTo, staleOK~TRUE]; FOR try: CARDINAL ฌ 0, try+1 DO IF try > lockRetries THEN ERROR; -- ??? PFSBackdoor.ReportFSError[lockConflict, sH, baseTo, "Too many retries on create."]; IF (vTemp ฌ version.version) >= (CARDINAL.LAST - 1) THEN ProduceError[noMoreVersions, "No more versions", baseTo]; version ฌ [numeric, vTemp+1]; toNameOnServer ฌ Rope.Cat[dHTo.fHandle, "/", EncodeVersionInName[baseTo, version.version]]; { from: CString ~ UXO[fHFrom]; to: CString ~ UXO[toNameOnServer]; IF attach THEN res ฌ UnixSysCalls.SymLink[from, to] ELSE res ฌ UnixSysCalls.Link[from, to]; UXR[from]; UXR[to]; }; IF res # success THEN { errno: UnixErrno.Errno ฌ UnixErrno.GetErrno[]; IF errno= EEXIST THEN LOOP ELSE ReportFailure["AtomicallyRename", "Couldn't link next version", toNameOnServer, errno]; }; dHTo.contentKnownStale ฌ TRUE; EXIT; ENDLOOP; IF NOT attach THEN { from: CString ~ UXO[fHFrom]; res ฌ UnixSysCalls.Unlink[from]; UXR[from]; }; IF res # success THEN { errno: UnixErrno.Errno ~ UnixErrno.GetErrno[]; ReportFailure["AtomicallyRename", "Couldn't unlink file from temporary name", fHFrom, errno]; }; dHFrom.contentKnownStale ฌ TRUE; }; numeric => { err: UnixErrno.Errno ฌ ok; res: RES; version ฌ desiredVersion; toNameOnServer ฌ Rope.Cat[dHTo.fHandle, "/", EncodeVersionInName[baseTo, version.version]]; { from: CString ~ UXO[fHFrom]; to: CString ~ UXO[toNameOnServer]; IF attach THEN res ฌ UnixSysCalls.SymLink[from, to] ELSE res ฌ UnixSysCalls.Link[from, to]; UXR[from]; UXR[to]; }; IF res#success THEN { err ฌ UnixErrno.GetErrno[]; IF err= EEXIST THEN ReportFailure["AtomicallyRename", "Couldn't create requested version of file", toNameOnServer, err]; }; IF NOT attach THEN { from: CString ~ UXO[fHFrom]; res ฌ UnixSysCalls.Unlink[from]; UXR[from]; }; dHFrom.contentKnownStale ฌ dHTo.contentKnownStale ฌ TRUE; }; ENDCASE => ERROR; -- Can't happen }; }; SilentlyRemove: PROC [dH: DirHandle, base: ROPE] ~ { from: CString ~ UXO[Rope.Cat[dH.fHandle, "/", base]]; [] ฌ UnixSysCalls.Unlink[from]; UXR[from]; dH.contentKnownStale ฌ TRUE; }; tempNamePrefix: ROPE ฌ NIL; CreateTempName: PROC [base: ROPE] RETURNS [tempName: ROPE] ~ { u1, u2: CARD; IF tempNamePrefix = NIL THEN { a: Arpa.Address ~ Arpa.MyAddress[]; tempNamePrefix ฌ IO.PutFLR["fs.%g.%g.%g.%g", LIST[[cardinal[a.a]], [cardinal[a.b]], [cardinal[a.c]], [cardinal[a.d]]]]; }; u1 ฌ u2 ฌ BasicTime.GetClockPulses[]; u1 ฌ LOOPHOLE[BasicTime.Now[! BasicTime.TimeNotKnown => CONTINUE]]; tempName ฌ IO.PutFR["%g.%g.%g", [rope[tempNamePrefix]], [cardinal[u1]], [cardinal[u2]] ]; }; caseFileNamePrefix: PUBLIC ROPE ฌ ".~case~"; caseFileNamePrefixLen: PUBLIC INT ฌ Rope.Length[caseFileNamePrefix]; CreateCaseFile: PUBLIC PROC [dh: DirHandle, name: ROPE] ~ { caseFileName: ROPE ฌ Rope.Concat[caseFileNamePrefix, name]; file: CString ~ UXO[Rope.Cat[dh.fHandle, "/", caseFileName]]; fd: UnixTypes.FD ~ UnixSysCalls.Open[file, magicFlags[create], defaultCaseFileMode]; UXR[file]; IF fd#error THEN [] ฌ UnixSysCalls.Close[fd]; dh.contentKnownStale ฌ TRUE; }; CheckCaseFile: PROC [dH: DirHandle, base: ROPE] RETURNS [caseFileName: ROPE ฌ NIL] ~ { EachDirEntry: VUXFiles.EachDirEntryProc -- [entry: DirEntry] RETURNS [continue: BOOL ฌ TRUE] -- ~ { SELECT Rope.Compare[entry.nameWithoutVersion, base, FALSE] FROM less => ERROR; -- invariants violated equal => IF (entry.version = [numeric, 0]) THEN { caseFileName ฌ entry.nameWithoutVersion; RETURN[FALSE]; } ELSE RETURN[TRUE]; greater => RETURN[FALSE]; ENDCASE; }; VUXFiles.EnumerateDirectory[dH, EachDirEntry, base, FALSE]; }; CollectCaseFile: PUBLIC PROC [dH: DirHandle, base: ROPE] ~ { versionFound: BOOL ฌ FALSE; caseFileNames: LIST OF ROPE ฌ NIL; EachDirEntry: VUXFiles.EachDirEntryProc -- [entry: DirEntry] RETURNS [continue: BOOL ฌ TRUE] -- ~ { SELECT Rope.Compare[entry.nameWithoutVersion, base, FALSE] FROM less => RETURN[TRUE]; -- the case file equal => IF (entry.version # [none]) AND (entry.version # [numeric, 0])THEN { versionFound ฌ TRUE; RETURN[FALSE]; }; greater => { RETURN[FALSE]; }; ENDCASE; -- invariants violated }; EachCaseFileEntry: VUXFiles.EachDirEntryProc -- [entry: DirEntry] RETURNS [continue: BOOL ฌ TRUE] -- ~ { IF Rope.Equal[entry.nameWithoutVersion, base, FALSE] THEN { IF entry.version = [numeric, 0] THEN caseFileNames ฌ CONS[Rope.Concat[caseFileNamePrefix, entry.nameWithoutVersion], caseFileNames]; RETURN[TRUE]; }; RETURN[FALSE]; }; VUXFiles.EnumerateDirectory[dH, EachDirEntry, base, FALSE]; IF NOT versionFound THEN { VUXFiles.EnumerateDirectory[dH, EachCaseFileEntry, base, FALSE]; FOR each: LIST OF ROPE ฌ caseFileNames, each.rest WHILE each # NIL DO SilentlyRemove[dH, each.first]; ENDLOOP; }; }; FSData: TYPE ~ REF FSDataObject; FSDataObject: TYPE ~ RECORD [ fs: ROPE ]; vuxMaintenanceProcs: PFSClass.MaintenanceProcs ฌ NEW[PFSClass.MaintenanceProcsObject ฌ [ sweep: VUXSweep, validate: VUXValidate ]]; VUXSweep: PFSClass.SweepProc ~ { dH: DirHandle ฌ VUXFiles.GetDirRoot[]; VUXFiles.SweepDirCache[dH, seconds]; [] ฌ VUXFiles.UnPinDir[dH]; }; VUXGetHandle: PFSClass.GetHandleProc -- [fs: ROPE, flavorSpecified: BOOL] RETURNS [h: FSHandle, downMsg: ROPE] -- ~ { IF flavorSpecified THEN [h, downMsg] ฌ GetHandleInner[fs, FALSE] ELSE { h ฌ NIL; downMsg ฌ NIL }; }; VUXGetROHandle: PFSClass.GetHandleProc -- [fs: FS, flavorSpecified: BOOL] RETURNS [h: FSHandle, downMsg: ROPE ฌ NIL] -- ~ { IF flavorSpecified THEN [h, downMsg] ฌ GetHandleInner[fs, TRUE] ELSE { h ฌ NIL; downMsg ฌ NIL }; }; GetHandleInner: PROC [fs: ROPE, readOnly: BOOL] RETURNS [h: PFSClass.FSHandle, downMsg: ROPE ฌ NIL] ~ { data: FSData ฌ NEW[FSDataObject ฌ [fs]]; IF NOT Rope.IsEmpty[fs] THEN RETURN[NIL, NIL]; h ฌ NEW[PFSClass.FSObject ฌ [flavor: vuxFlavor, name: fs, maintenanceProcs: vuxMaintenanceProcs, procs: vuxProcs, data: data]]; }; VUXValidate: PFSClass.ValidateProc -- [h: FSHandle] RETURNS [obsolete: BOOL, downMsg: ROPE] -- ~ { data: FSData ฌ NARROW[h.data]; -- sanity check; RETURN[ FALSE, NIL ]; -- always available }; moduleName: ROPE ~ "VUXFileImpl."; ReportFailure: PROC [procName: ROPE, msg: ROPE, what: REF, errno: UnixErrno.Errno] ~ { procPrefix: ROPE ~ Rope.Cat[moduleName, procName, ": "]; filename: ROPE ฌ WITH what SELECT FROM file: PFSClass.OpenFile => PFS.RopeFromPath[file.fullFName], path: PATH => PFS.RopeFromPath[path], rope: ROPE => rope, ENDCASE => "???"; message: ROPE ~ Rope.Cat[procPrefix, msg, " for ", filename]; UnixFSPrivate.ReportFailure[errno, message]; }; ProduceError: PROC [code: PFSBackdoor.ErrorCode, explanation: Rope.ROPE, info: REF] ~ { filename: ROPE ฌ WITH info SELECT FROM file: PFSClass.OpenFile => PFS.RopeFromPath[file.fullFName], path: PATH => PFS.RopeFromPath[path], rope: ROPE => rope, ENDCASE => "???"; message: ROPE ~ Rope.Cat[explanation, " for ", filename]; PFSBackdoor.ProduceError[code, message, info]; }; vuxProcs: PFSClass.FileManipulationProcs ฌ NEW[PFSClass.FileManipulationProcsObject ฌ [ delete: VUXDelete, enumerateForInfo: VUXEnumerateForInfo, enumerateForNames: VUXEnumerateForNames, fileInfo: VUXFileInfo, lookupName: VUXLookupName, rename: VUXRename, copy: VUXCopy, setAttributes: VUXSetAttributes, setByteCountAndUniqueID: VUXSetByteCountAndUniqueID, setClientProperty: VUXSetClientProperty, getClientProperty: VUXGetClientProperty, enumerateClientProperties: VUXEnumerateClientProperties, read: VUXRead, write: VUXWrite, open: VUXOpen, close: VUXClose, store: VUXStore, retrieve: VUXRetrieve, attach: VUXAttach, getInfo: VUXGetInfo, pfsNameToUnixName: VUXPFSNameToUnixName, caseSensitive: VUXCaseSensitive ]]; VUXDelete: PUBLIC PFSClass.DeleteProc -- [h: FSHandle, file: NAME, wantedUniqueID: UniqueID, proc: PFS.NameConfirmProc] -- ~ { dH: DirHandle; version: Version; nameOnServer: ROPE; uniqueID: UniqueID; [dH~dH, nameOnServer~nameOnServer, version~version, uniqueID~uniqueID] ฌ FindExistingFile[file, wantedUniqueID]; IF (proc = NIL) OR proc[PFSNames.SetVersionNumber[file, version], uniqueID] THEN { ENABLE UNWIND => { VUXFiles.UnPinDirPath[dH]; }; unixFile: ROPE ~ Rope.Cat[dH.fHandle, "/", nameOnServer]; fileString: CString ~ UXO[unixFile]; res: RES ~ UnixSysCalls.Unlink[fileString]; UXR[fileString]; IF res#success THEN { errno: UnixErrno.Errno ~ UnixErrno.GetErrno[]; ReportFailure["VUXDelete", "Delete Failed", unixFile, errno]; }; dH.contentKnownStale ฌ TRUE; CollectCaseFile[dH, DecodeVersionFromName[nameOnServer].nameWithoutVersion]; }; }; VUXFileInfo: PUBLIC PFSClass.FileInfoProc -- [h: FSHandle, file: PATH, wantedUniqueID: UniqueID] RETURNS [version: Version, attachedTo: PATH, bytes: INT, uniqueID: UniqueID, mutability: PFS.Mutability, fileType: PFS.FileType] -- ~ { dH: DirHandle; fH: ROPE; fmt: UnixTypes.Fmt; shortNameComp: PFSNames.Component; [dH~dH, fH~fH, version~version, bytes~bytes, uniqueID~uniqueID, fmt~fmt, attachedTo~attachedTo] ฌ FindExistingFile[file, wantedUniqueID]; mutability ฌ mutable; fileType ฌ PFSFileTypeFromUnixFmt[fmt]; VUXFiles.UnPinDirPath[dH]; }; VUXGetInfo: PFSClass.GetInfoProc -- [h: FSHandle, file: OpenFile] RETURNS [fullFName, attachedTo: PATH, uniqueID: UniqueID, bytes: INT, mutability: PFS.Mutability, fileType: PFS.FileType] -- ~ TRUSTED { data: REF VUXData ~ NARROW[file.data]; fd: UnixTypes.FD ฌ data.fd; buf: UnixTypes.Stat; res: RES ~ UnixSysCalls.FStat[fd, @buf]; IF res # success THEN { errno: UnixErrno.Errno ~ UnixErrno.GetErrno[]; ReportFailure["VUXGetInfo", "Stat failed", file, errno]; }; RETURN[file.fullFName, file.attachedTo, file.uniqueID, buf.size, file.mutability, file.fileType]; }; VUXRename: PUBLIC PFSClass.RenameProc -- [h: FSHandle, fromFile: NAME, wantedUniqueID: UniqueID, toFile: NAME, keep: CARDINAL, proc: PFS.NameConfirmProc] RETURNS [done: BOOL ฌ FALSE] -- ~ { dHFrom, dHTo: DirHandle; { ENABLE UNWIND => { IF dHFrom # NIL THEN VUXFiles.UnPinDirPath[dHFrom]; IF dHTo # NIL THEN VUXFiles.UnPinDirPath[dHTo]; }; fromNameOnServer, fromNameWithoutVersion, baseTo: ROPE; isPattern, createCaseFile: BOOL; fHFrom: FHandle; uniqueID: UniqueID; version, specifiedVersion, createdVersion: Version; [dH~dHFrom, nameOnServer~fromNameOnServer, fH~fHFrom, version~version, uniqueID~uniqueID] ฌ FindExistingFile[fromFile, wantedUniqueID]; IF (proc = NIL) OR proc[PFSNames.SetVersionNumber[fromFile, version], uniqueID] THEN { dHTo ฌ VUXFiles.FollowDirPath[path~toFile, case~FALSE, create~TRUE]; [base~baseTo, version~specifiedVersion, isPattern~isPattern] ฌ ReadBaseComponent[path~toFile, case~FALSE]; IF isPattern THEN ProduceError[patternNotAllowed, "Pattern not allowed in VUXFileImpl.VUXRename", toFile]; SELECT version.versionKind FROM highest, none => specifiedVersion ฌ [next]; next, numeric => NULL; ENDCASE => ProduceError[versionSpecified, "Illegal version specified in Rename", toFile]; createdVersion ฌ AtomicallyRename[dHFrom, fHFrom, dHTo, baseTo, specifiedVersion, FALSE ! PFS.Error => GOTO failed]; fromNameWithoutVersion ฌ DecodeVersionFromName[fromNameOnServer].nameWithoutVersion; CollectCaseFile[dHFrom, fromNameWithoutVersion]; SELECT version.versionKind FROM numeric => createCaseFile ฌ (CheckCaseFile[dHTo, baseTo] = NIL); ENDCASE => createCaseFile ฌ (createdVersion = [numeric, 1]); IF createCaseFile THEN { [base~baseTo] ฌ ReadBaseComponent[path~toFile, case~TRUE]; CreateCaseFile[dHTo, baseTo ! PFS.Error => CONTINUE ]; }; VUXFiles.UnPinDirPath[dHTo]; dHTo ฌ NIL; }; VUXFiles.UnPinDirPath[dHFrom]; dHFrom ฌ NIL; RETURN[TRUE]; EXITS failed => { VUXFiles.UnPinDirPath[dHTo]; VUXFiles.UnPinDirPath[dHFrom] } }; }; VUXRetrieve: PUBLIC PFSClass.RetrieveProc -- [h: FSHandle, file: NAME, wantedUniqueID: UniqueID, proc: PFS.RetrieveConfirmProc, checkFileType: BOOL ฌ FALSE] -- ~ { fH: FHandle; bytesExpected: CARD; str: IO.STREAM; uniqueID: UniqueID; fd: UnixTypes.FD; openedVersion: Version; buffer: REF TEXT; attachedTo: PATH; offset: CARD ฌ 0; [fH~fH, version~openedVersion, bytes~bytesExpected, uniqueID~uniqueID, attachedTo~attachedTo] ฌ FindExistingFile[file, wantedUniqueID]; str ฌ proc[PFSNames.SetVersionNumber[file, openedVersion], bytesExpected, uniqueID]; IF str = NIL THEN RETURN; buffer ฌ RefText.ObtainScratch[MIN[bytesExpected, bufferBytes]]; { name: CString ~ UXO[fH]; fd ฌ OpenSetBlocking[name, magicFlags[read], []]; UXR[name]; }; IF fd = error THEN { errno: UnixErrno.Errno ~ UnixErrno.GetErrno[]; ReportFailure["Retrieve", "Open failed", fH, errno]; }; TRUSTED { ENABLE UNWIND => { RefText.ReleaseScratch[buffer]; }; DO bytesRead: INT; ptr: CString ~ LOOPHOLE[LOOPHOLE[buffer, CString] +SIZE[TEXT[0]]]; bytesRead ฌ UnixSysCalls.Read[fd, ptr, buffer.maxLength]; IF bytesRead < 0 THEN { errno: UnixErrno.Errno ~ UnixErrno.GetErrno[]; ReportFailure["Retrieve", "Read failed", fH, errno]; }; buffer.length ฌ bytesRead; IO.PutBlock[str, buffer]; IF buffer.length < buffer.maxLength THEN EXIT; ENDLOOP; }; RefText.ReleaseScratch[buffer]; }; OpenSetBlocking: PROC [path: CString, flags: UnixTypes.FileFlags, mode: UnixTypes.Mode] RETURNS [fd: UnixTypes.FD] ~ { fd ฌ UnixSysCalls.Open[path, flags, mode]; IF fd = error THEN RETURN; { res: RES ~ IF flags.access = RDONLY THEN UnixSysCallExtensions.SetGetBlocking[fd, allData] ELSE UnixSysCallExtensions.SetPutBlocking[fd, allData]; IF res # success THEN fdฌerror ; }; }; AtomicallyCreate: PROC [file: PATH, checkFileType: BOOL ฌ FALSE, fileType: PFS.FileType ฌ PFS.tUnspecified, createOptions: PFS.CreateOptions, openFlags: UnixTypes.FileFlags, action: PROC[UnixTypes.FD, CStrings.CString]] RETURNS [fd: UnixTypes.FD ฌ error, version: Version] ~ { tempNameOnServer, realBase: ROPE; tempNameString: CString; specifiedVersion: Version; BeginCreate: PROC ~ { isPattern: BOOL; dH ฌ VUXFiles.FollowDirPath[path~file, case~FALSE, create~TRUE]; [base~realBase, version~specifiedVersion, isPattern~isPattern] ฌ ReadBaseComponent[path~file, case~FALSE]; IF isPattern THEN ProduceError[patternNotAllowed, "Pattern not allowed in VUXFileImpl.VUXStore", file]; SELECT specifiedVersion.versionKind FROM none, highest => specifiedVersion ฌ [next]; next, numeric => NULL; ENDCASE => ProduceError[versionSpecified, "Illegal version specified in Store", file]; tempNameOnServer ฌ Rope.Cat[dH.fHandle, "/", CreateTempName[realBase]]; tempNameString ฌ UXO[tempNameOnServer]; fd ฌ OpenSetBlocking[tempNameString, magicFlags[create], []]; IF fd = error THEN { errno: UnixErrno.Errno ~ UnixErrno.GetErrno[]; ReportFailure["AtomicallyCreate", "Open failed", tempNameOnServer, errno]; }; { desiredMode: Mode ~ VUXFiles.GetCreateMode[dH~dH, forDirectory~FALSE]; IF UnixSysCalls.FChMod[fd, desiredMode] # success THEN { errno: UnixErrno.Errno ~ UnixErrno.GetErrno[]; ReportFailure["AtomicallyCreate", "ChMod failed", tempNameOnServer, errno]; }; }; dH.contentKnownStale ฌ TRUE; }; EndCreate: PROC ~ TRUSTED { createCaseFile: BOOL; res: RES ฌ UnixSysCalls.FSync[fd]; errno: UnixErrno.Errno; IF res#success AND (errno ฌ UnixErrno.GetErrno[]) # EBADF THEN { ReportFailure["AtomicallyCreate.EndCreate", "Server full? FSync failed", file, errno]; }; version ฌ AtomicallyRename[dH, tempNameOnServer, dH, realBase, specifiedVersion, TRUE]; SELECT version.versionKind FROM numeric => createCaseFile ฌ (CheckCaseFile[dH, realBase] = NIL); ENDCASE => createCaseFile ฌ (version = [numeric, 1]); -- ??? IF createCaseFile THEN { [base~realBase] ฌ ReadBaseComponent[path~file, case~TRUE]; CreateCaseFile[dH, realBase]; }; VUXFiles.UnPinDirPath[dH]; dH ฌ NIL; }; dH: DirHandle; TRUSTED { ENABLE UNWIND => { IF dH # NIL THEN VUXFiles.UnPinDirPath[dH]; }; BeginCreate[]; IF action#NIL THEN action[fd, tempNameString]; EndCreate[]; UXR[tempNameString]; }; }; VUXStore: PUBLIC PFSClass.StoreProc -- [h: FSHandle, file: NAME, wantedUniqueID: UniqueID, str: IO.STREAM, proc: PFS.StoreConfirmProc, checkFileType: BOOL ฌ FALSE, fileType: PFS.FileType ฌ PFS.tUnspecified, createOptions: PFS.CreateOptions] -- ~ { dH: DirHandle; DoStore: PROC [fd: UnixTypes.FD, stringName: CString] ~ TRUSTED { buffer: REF TEXT; TRUSTED { ENABLE UNWIND => { IF buffer # NIL THEN RefText.ReleaseScratch[buffer]; }; buffer ฌ RefText.ObtainScratch[bufferBytes]; DO bytesWritten: INT; ptr: CString ~ LOOPHOLE[LOOPHOLE[buffer, CString] +SIZE[TEXT[0]]]; buffer.length ฌ IO.GetBlock[str, buffer, 0, buffer.maxLength]; IF buffer.length = 0 THEN EXIT; bytesWritten ฌ UnixSysCalls.Write[fd, ptr, buffer.length]; IF bytesWritten # buffer.length THEN { IF bytesWritten < 0 THEN { errno: UnixErrno.Errno ~ UnixErrno.GetErrno[]; ReportFailure["Store", "Write failed", file, errno]; } ELSE ProduceError[inconsistent, "Invalid assumption about UnixSysCalls.Write in VUXFileImpl.Store", file]; }; ENDLOOP; { ENABLE PFS.Error => CONTINUE; oldMode: ROPE ฌ PFS.GetClientProperty[PFS.OpenFileFromStream[str], "UnixMode"]; buf: UnixTypes.Stat; res: RES ~ UnixSysCalls.FStat[fd, @buf]; newModeBits: CARD16 ฌ LOOPHOLE[buf.mode]; IF res#success THEN { errno: UnixErrno.Errno ~ UnixErrno.GetErrno[]; ReportFailure["DoStore", "Stat failed", file, errno]; }; IF oldMode # NIL THEN { oldModeBits: CARD16 ~ Convert.CardFromRope[oldMode]; newerModeBits: CARD16 ~ Basics16.BITOR[newModeBits, Basics16.BITAND[oldModeBits, 111B]]; [] ฌ UnixSysCalls.FChMod[fd, LOOPHOLE[newerModeBits]]; }; }; FixUID[stringName, wantedUniqueID]; RefText.ReleaseScratch[buffer]; buffer ฌ NIL; }; }; fd: UnixTypes.FD; version: Version; [fd, version] ฌ AtomicallyCreate[file~file, createOptions~createOptions, openFlags~[access: WRONLY], action~DoStore]; [] ฌ UnixSysCalls.Close[fd]; IF proc # NIL THEN [] ฌ proc[PFSNames.SetVersionNumber[file, version]]; }; PFSFileTypeFromUnixFmt: PROC [ ufs: UnixTypes.Fmt ] RETURNS [ pfs: PFS.FileType ฌ PFS.tUnspecified ] ~ { pfs ฌ SELECT ufs FROM dir => PFS.tDirectory, ENDCASE => PFS.tUnspecified; }; VUXEnumerateForInfo: PFSClass.EnumerateForInfoProc -- [h: FSHandle, pattern: NAME, proc: InfoProc, lbound: NAME, hbound: NAME] -- ~ { data: FSData ~ NARROW[h.data]; -- sanity check; Enumerate[h, pattern, NIL, proc]; }; VUXEnumerateForNames: PFSClass.EnumerateForNamesProc -- [h: FSHandle, pattern: NAME, proc: PFS.NameProc, lbound: NAME, hbound: NAME] -- ~ { data: FSData ฌ NARROW[h.data]; -- sanity check; Enumerate[h, pattern, proc, NIL]; }; VUXLookupName: PFSClass.LookupNameProc -- [h: FSHandle, file: NAME] RETURNS[NAME] -- ~ { data: FSData ฌ NARROW[h.data]; -- sanity check; dH: DirHandle; fH: FHandle; version: Version; [dH~dH, fH~fH, version~version] ฌ FindExistingFile[file, PFS.nullUniqueID ! PFS.Error => IF error.code = $unknownFile THEN CONTINUE ELSE REJECT]; VUXFiles.UnPinDirPath[dH]; IF fH # NIL THEN RETURN[PFSNames.SetVersionNumber[file, version]] ELSE RETURN [NIL]; }; VUXCopy: PFSClass.CopyProc -- [h: FSHandle, fromFile: NAME, wantedUniqueID: UniqueID, toFile: NAME, keep: CARDINAL, proc: PFS.NameConfirmProc] RETURNS [done: BOOL ฌ FALSE] -- ~ { data: FSData ฌ NARROW[h.data]; -- sanity check; }; VUXSetAttributes: PFSClass.SetAttributesProc -- [h: FSHandle, file: OpenFile, attributes: PFS.CreateOptions] -- ~ { data: FSData ฌ NARROW[h.data]; -- sanity check; NotYetImpl; }; VUXSetByteCountAndUniqueID: PFSClass.SetByteCountAndUniqueIDProc -- [h: FSHandle, file: OpenFile, bytes: INT, uniqueID: PFS.UniqueID] -- ~ { data: FSData ฌ NARROW[h.data]; -- sanity check; fd: UnixTypes.FD ฌ NARROW[file.data, REF VUXData].fd; IF bytes>=0 THEN { res: RES ~ UnixSysCalls.FTruncate[fd, bytes]; IF res#success THEN { errno: UnixErrno.Errno ~ UnixErrno.GetErrno[]; ReportFailure["VUXSetByteCountAndUniqueID", "FTruncate failed to change byteCount", file, errno]; }; }; }; VUXGetClientProperty: PFSClass.GetClientPropertyProc -- [h: FSHandle, file: OpenFile, propertyName: ROPE] RETURNS [propertyValue: ROPE] -- ~ TRUSTED { data: FSData ฌ NARROW[h.data]; -- sanity check; fd: UnixTypes.FD ฌ NARROW[file.data, REF VUXData].fd; buf: UnixTypes.Stat; res: RES ~ UnixSysCalls.FStat[fd, @buf]; IF res#success THEN { errno: UnixErrno.Errno ~ UnixErrno.GetErrno[]; ReportFailure["VUXGetClientProperty", "Stat failed", file, errno]; }; SELECT TRUE FROM Rope.Equal[propertyName, "UnixMode"] => { RETURN[Convert.RopeFromCard[LOOPHOLE[buf.mode, CARD16], 8]]; }; Rope.Equal[propertyName, "UnixOwner"] => { RETURN[Convert.RopeFromCard[buf.uid]]; }; Rope.Equal[propertyName, "UnixGroup"] => { RETURN[Convert.RopeFromCard[buf.gid]]; }; ENDCASE => RETURN[NIL]; }; VUXEnumerateClientProperties: PFSClass.EnumerateClientPropertiesProc -- [h: FSHandle, file: OpenFile, proc: PFS.PropProc] -- ~ TRUSTED { data: FSData ฌ NARROW[h.data]; -- sanity check; fd: UnixTypes.FD ฌ NARROW[file.data, REF VUXData].fd; buf: UnixTypes.Stat; res: RES ~ UnixSysCalls.FStat[fd, @buf]; IF NOT proc["UnixGroup", Convert.RopeFromCard[buf.gid]] THEN RETURN; IF NOT proc["UnixMode", Convert.RopeFromCard[LOOPHOLE[buf.mode, CARD16], 8]] THEN RETURN; [] ฌ proc["UnixOwner", Convert.RopeFromCard[buf.uid]]; }; VUXRead: UNSAFE PROC [h: FSHandle, file: PFSClass.OpenFile, filePosition, nBytes: CARD, toPtr: LONG POINTER, toStart: CARD] RETURNS [bytesRead: INT] ~ UNCHECKED { fs: FSData ~ NARROW[h.data]; -- sanity check data: REF VUXData ~ NARROW[file.data]; fd: UnixTypes.FD ฌ data.fd; failure: ROPE; errno: UnixErrno.Errno ฌ ok; DoRead: ENTRY PROC [lock: REF VUXData] RETURNS [failure: ROPE ฌ NIL] ~ TRUSTED { ENABLE UNWIND => NULL; fdIndex: PFSBackdoor.FDIndex ~ data.fdIndex; data.fdIndex ฌ PFSBackdoor.unreliableIndex; IF filePosition # fdIndex THEN { res: INT ~ UnixSysCalls.LSeek[fd, filePosition, set]; IF res < 0 THEN { errno ฌ UnixErrno.GetErrno[]; RETURN["LSeek failed"]; }; }; bytesRead ฌ UnixSysCalls.Read[d~fd, buf~LOOPHOLE[toPtr+toStart], nBytes~nBytes]; IF bytesRead < 0 THEN { errno ฌ UnixErrno.GetErrno[]; RETURN["Read failed"]; }; IF fdIndex # PFSBackdoor.unreliableIndex THEN data.fdIndex ฌ filePosition+bytesRead; }; bytesRead ฌ 0; IF (failure ฌ DoRead[data]) # NIL THEN ReportFailure["VUXRead", failure, file, errno]; }; VUXData: TYPE ~ PFSBackdoor.VUXData; VUXWrite: PROC [h: FSHandle, file: PFSClass.OpenFile, filePosition, nBytes: CARD, fromPtr: LONG POINTER, fromStart: CARD] RETURNS [bytesWritten: INT] ~ { fs: FSData ฌ NARROW[h.data]; -- sanity check data: REF VUXData ~ NARROW[file.data]; fd: UnixTypes.FD ฌ data.fd; failure: ROPE; errno: UnixErrno.Errno ฌ ok; DoWrite: ENTRY PROC [lock: REF VUXData] RETURNS [failure: ROPE ฌ NIL] ~ { ENABLE UNWIND => NULL; fdIndex: PFSBackdoor.FDIndex ~ data.fdIndex; data.fdIndex ฌ PFSBackdoor.unreliableIndex; IF filePosition # fdIndex THEN { res: INT ~ UnixSysCalls.LSeek[fd, filePosition, set]; IF res < 0 THEN { errno ฌ UnixErrno.GetErrno[]; RETURN["LSeek failed"]; }; }; bytesWritten ฌ UnixSysCalls.Write[d~fd, buf~LOOPHOLE[fromPtr+fromStart], nBytes~nBytes]; IF bytesWritten < 0 THEN { errno ฌ UnixErrno.GetErrno[]; RETURN["Write failed"]; }; IF fdIndex # PFSBackdoor.unreliableIndex THEN data.fdIndex ฌ filePosition+bytesWritten; }; bytesWritten ฌ 0; IF (failure ฌ DoWrite[data]) # NIL THEN ReportFailure["VUXWrite", failure, file, errno]; }; VUXOpen: PFSClass.OpenProc -- [h: FSHandle, file: NAME, wantedUniqueID: UniqueID, access: PFS.AccessOptions, checkFileType: BOOL, fileType: PFS.FileType, createOptions: PFS.CreateOptions] RETURNS [OpenFile] -- ~ { data: FSData ฌ NARROW[h.data]; -- sanity check; openFile: PFSClass.OpenFile ฌ NEW[PFSClass.OpenFileObject ฌ [fs: h, uniqueID: PFS.nullUniqueID, state: open, bytes: 0, mutability: mutable, fileType: PFS.tUnspecified, access: access]]; fd: UnixTypes.FD; version: Version; fullUName: ROPE; attachedTo: PATH; SELECT access FROM read => { fmt: UnixTypes.Fmt; [fH~fullUName, uniqueID~openFile.uniqueID, bytes~openFile.bytes, version~version, fmt~fmt, attachedTo~attachedTo] ฌ FindExistingFile[file, wantedUniqueID]; { fileString: CString ~ UXO[fullUName]; fd ฌ OpenSetBlocking[path: fileString, flags: [access: RDONLY], mode: [] ]; UXR[fileString]; }; openFile.fileType ฌ PFSFileTypeFromUnixFmt[fmt]; openFile.fullFName ฌ file.SetVersionNumber[version]; openFile.attachedTo ฌ attachedTo; openFile.data ฌ NEW[VUXData ฌ [fd]]; RETURN [openFile]; }; create => { [fd, version] ฌ AtomicallyCreate[file, checkFileType, fileType, createOptions, [access: WRONLY], NIL]; openFile.fullFName ฌ file.SetVersionNumber[version]; [fH~fullUName, uniqueID~openFile.uniqueID] ฌ FindExistingFile[openFile.fullFName, PFS.nullUniqueID ! PFS.Error => {fullUName ฌ NIL; CONTINUE}]; IF wantedUniqueID#PFS.nullUniqueID THEN openFile.uniqueID ฌ wantedUniqueID; openFile.data ฌ NEW[VUXData ฌ [fd, UXS[fullUName]]]; RETURN [openFile]; }; write => { version ฌ PFSNames.ShortName[file].version; IF version.versionKind=next OR version.versionKind=none THEN { [fd, version] ฌ AtomicallyCreate[file, checkFileType, fileType, createOptions, [access: RDWR], NIL]; openFile.fullFName ฌ file.SetVersionNumber[version]; [fH~fullUName, uniqueID~openFile.uniqueID] ฌ FindExistingFile[openFile.fullFName, PFS.nullUniqueID ! PFS.Error => {fullUName ฌ NIL; CONTINUE}]; IF wantedUniqueID#PFS.nullUniqueID THEN openFile.uniqueID ฌ wantedUniqueID; openFile.data ฌ NEW[VUXData ฌ [fd, UXS[fullUName]]]; } ELSE { [fH~fullUName, uniqueID~openFile.uniqueID, bytes~openFile.bytes, version~version, attachedTo~attachedTo] ฌ FindExistingFile[file, wantedUniqueID ! PFS.Error => {fullUName ฌ NIL; CONTINUE}]; IF attachedTo#NIL THEN { PFSBackdoor.ProduceError[accessDenied, Rope.Concat[fullUName, " is an attachment and cannot be opened for writing"]]; }; { fileString: CString ~ UXO[fullUName]; fd ฌ OpenSetBlocking[path: fileString, flags: [access: RDWR], mode: [] ]; UXR[fileString]; }; openFile.fullFName ฌ file.SetVersionNumber[version]; openFile.data ฌ NEW[VUXData ฌ [fd]]; }; openFile.fullFName ฌ file.SetVersionNumber[version]; RETURN [openFile]; }; append => { [fH~fullUName, uniqueID~openFile.uniqueID, bytes~openFile.bytes, version~version, attachedTo~attachedTo] ฌ FindExistingFile[file, wantedUniqueID]; IF attachedTo#NIL THEN { PFSBackdoor.ProduceError[accessDenied, Rope.Concat[fullUName, " is an attachment and cannot be opened for appending"]]; }; { fileString: CString ~ UXO[fullUName]; fd ฌ OpenSetBlocking[path: fileString, flags: [trunc: false, creat: false, access: RDWR], mode: [] ]; UXR[fileString]; }; openFile.fullFName ฌ file.SetVersionNumber[version]; openFile.data ฌ NEW[VUXData ฌ [fd]]; RETURN [openFile]; }; ENDCASE => ERROR; }; VUXAttach: PFSClass.AttachProc -- [h: FSHandle, file: NAME, to: NAME, keep: CARDINAL, wantedUniqueID: UniqueID, remoteCheck: BOOL ฌ TRUE] RETURNS [toFName: NAME] -- ~ { dH: DirHandle; realBase: ROPE; specifiedVersion: Version; version: Version; BeginCreate: PROC ~ { isPattern: BOOL; dH ฌ VUXFiles.FollowDirPath[path~file, case~FALSE, create~TRUE]; [base~realBase, version~specifiedVersion, isPattern~isPattern] ฌ ReadBaseComponent[path~file, case~FALSE]; IF isPattern THEN ProduceError[patternNotAllowed, "Pattern not allowed in VUXFileImpl.VUXStore", file]; SELECT specifiedVersion.versionKind FROM none, highest => specifiedVersion ฌ [next]; next, numeric => NULL; ENDCASE => ProduceError[versionSpecified, "Illegal version specified in Attach", file]; { toFileName: ROPE ~ PFS.PFSNameToUnixName[to]; IF toFileName=NIL THEN PFSBackdoor.ProduceError[unknownFile, Rope.Cat["Couldn't deduce a unix name for ", PFS.RopeFromPath[to], " in UXAttach."]]; version ฌ AtomicallyRename[dH, toFileName, dH, realBase, specifiedVersion, FALSE, TRUE]; -- the proceure is misnamed; it is only atomically making a link. }; dH.contentKnownStale ฌ TRUE; }; EndCreate: PROC ~ TRUSTED { createCaseFile: BOOL; SELECT version.versionKind FROM numeric => createCaseFile ฌ (CheckCaseFile[dH, realBase] = NIL); ENDCASE => createCaseFile ฌ (version = [numeric, 1]); -- ??? IF createCaseFile THEN { [base~realBase] ฌ ReadBaseComponent[path~file, case~TRUE]; CreateCaseFile[dH, realBase]; }; VUXFiles.UnPinDirPath[dH]; dH ฌ NIL; }; TRUSTED { ENABLE UNWIND => { IF dH # NIL THEN VUXFiles.UnPinDirPath[dH]; }; BeginCreate[]; EndCreate[]; toFName ฌ PFSNames.SetVersionNumber[file, version]; }; }; VUXClose: PFSClass.CloseProc -- [h: FSHandle, file: OpenFile, abort: BOOL] -- ~ { fsData: FSData ~ NARROW[h.data]; -- sanity check; data: REF VUXData ~ NARROW[file.data, REF VUXData]; fd: UnixTypes.FD ~ data.fd; failure: ROPE; errno: UnixErrno.Errno ฌ ok; DoClose: ENTRY PROC [lock: REF VUXData] RETURNS [failure: ROPE ฌ NIL] ~ { ENABLE UNWIND => NULL; res: RES; data.fdIndex ฌ PFSBackdoor.unreliableIndex; IF file.access#read THEN { res ฌ UnixSysCalls.FSync[fd]; IF res#success AND UnixErrno.GetErrno[] # EBADF THEN { errno ฌ UnixErrno.GetErrno[]; RETURN["Server full? FSync failed"]; }; }; IF data.setUniqueIDAtClose#NIL THEN FixUID[data.setUniqueIDAtClose, file.uniqueID]; res ฌ UnixSysCalls.Close[fd]; IF res#success THEN { errno ฌ UnixErrno.GetErrno[]; RETURN["Close failed"]; }; }; IF (failure ฌ DoClose[data]) # NIL THEN ReportFailure["VUXClose", failure, file, errno]; }; FixUID: PROC [stringName: CString, wantedUniqueID: UniqueID] ~ TRUSTED { mTime: MTime ~ MTimeFromGMT[wantedUniqueID.egmt.gmt]; modTimeVal: UnixTypes.AccModTimes ฌ [accessTime: [sec: mTime, usec: 0], modTime: [sec: mTime, usec: 0]]; IF wantedUniqueID.egmt.gmt # BasicTime.nullGMT THEN IF UnixSysCalls.UTimes[stringName, @modTimeVal] # success THEN { errno: UnixErrno.Errno ~ UnixErrno.GetErrno[]; ReportFailure["FixUID", "UTimes failed", UXStrings.ToRope[stringName], errno]; }; }; VUXSetClientProperty: PUBLIC PFSClass.SetClientPropertyProc -- [h: FSHandle, file: OpenFile, propertyName: ROPE, propertyValue: ROPE] -- = { data: FSData ฌ NARROW[h.data]; -- sanity check; fd: UnixTypes.FD ฌ NARROW[file.data, REF VUXData].fd; SELECT TRUE FROM Rope.Equal[propertyName, "UnixMode"] => { cardMode: CARD16 ~ Convert.CardFromRope[propertyValue, 8]; res: RES ~ UnixSysCalls.FChMod[fd, LOOPHOLE[cardMode]]; }; Rope.Equal[propertyName, "UnixOwner"] => { ProduceError[notImplemented, "The UnixOwner cannot be changed in the VUX view", file]; }; Rope.Equal[propertyName, "UnixGroup"] => { ProduceError[notImplemented, "The UnixGroup cannot be changed in the VUX view", file]; }; ENDCASE => ProduceError[notImplemented, Rope.Concat[propertyName, " is not an implemented property in the VUX view"], file]; }; VUXCaseSensitive: PUBLIC PFSClass.CaseSensitiveProc -- [h: FSHandle, file: PATH] RETURNS [ BOOL ] -- = { RETURN[FALSE]; }; VUXPFSNameToUnixName: PUBLIC PFSClass.PFSNameToUnixNameProc -- [h: FSHandle, file: PATH] RETURNS [ ROPE ] -- = { lastComponent: Component ฌ PFSNames.ShortName[file]; IF lastComponent.version.versionKind#numeric THEN { RETURN[FindExistingFile[file, PFS.nullUniqueID].fH] } ELSE { lastRope: ROPE ฌ PFSNames.ComponentRope[lastComponent]; newLastRope: ROPE ฌ EncodeVersionInName[lastRope, lastComponent.version.version]; newName: PATH ฌ PFSNames.ReplaceShortName[file, MakeComponent[newLastRope, [none]]]; resultExceptCase: ROPE ฌ PFS.RopeFromPath[newName, slashes]; RETURN[LowerCase[resultExceptCase]]; }; }; NotYetImpl: ERROR ~ CODE; oneEltPath: PATH ฌ PFSNames.ConstructName[components~LIST[ MakeComponent["*", [none]] ] ]; oneEltDirPath: PATH ฌ PFSNames.ConstructName[components~LIST[ MakeComponent["*", [none]] ], directory~TRUE ]; MakeComponent: PROC [r: ROPE, v: Version] RETURNS [Component] ~ { RETURN[[name:[r, 0, Rope.Length[r]], version: v]]; }; magicFlags: ARRAY {read, create, append, write} OF UnixTypes.FileFlags ~ [ read: [access: RDONLY], -- read only create: [trunc: true, creat: true, access: WRONLY], -- truncate, create, write only append: [creat: true, append: true, access: WRONLY], -- create, append, write only write: [trunc: true, creat: true, access: WRONLY] -- truncate, create, write only ]; ReadBaseComponent: PROC [path: PATH, case: BOOL] RETURNS [base: ROPE, version: Version, isPattern: BOOL ฌ FALSE] ~ { shortName: Component ~ PFSNames.ShortName[path]; base ฌ PFSNames.ComponentRope[shortName]; version ฌ shortName.version; IF NOT case THEN { base ฌ LowerCase[base]; }; }; LowerCase: PROC [r: ROPE] RETURNS[ROPE] ~ { len: INT ฌ r.Length[]; buf: REF TEXT ฌ RefText.ObtainScratch[len]; FOR i: INT IN [0..len) DO buf[i] ฌ Ascii.Lower[r.Fetch[i]]; ENDLOOP; buf.length ฌ len; RETURN[Rope.FromRefText[buf]]; }; GMTFromMTime: PUBLIC PROC [unixTime: UnixTypes.Time] RETURNS [gmt: GMT] ~ { RETURN [BasicTime.Update[unixEpoch, INT[unixTime]]]; }; MTimeFromGMT: PUBLIC PROC [gmt: GMT] RETURNS [unixTime: UnixTypes.Time] ~ { RETURN [BasicTime.Period[from~unixEpoch, to~gmt]]; }; CompareSunTimes: PUBLIC PROC [t1, t2: UnixTypes.TimeVal] RETURNS [Basics.Comparison] ~ { RETURN [SELECT t1.sec FROM < t2.sec => less, > t2.sec => greater, ENDCASE => Basics.CompareCard[t1.usec, t2.usec]]; }; EncodeVersionInName: PROC [name: ROPE, version: CARD] RETURNS [nameWithVersion: ROPE] ~ { versionNumber: CARDINAL ฌ version; nameWithVersion ฌ Rope.Cat[ name, ".~", Convert.RopeFromCard[versionNumber, 10, FALSE], "~" ]; }; versionPartLeftBracket: ROPE ฌ ".~"; versionPartRightBracket: CHAR ฌ '~; versionPartLeftBracketLen: INT ฌ Rope.Length[versionPartLeftBracket]; versionPartRightBracketLen: INT ฌ 1; versionPartBracketsLen: INT ฌ versionPartLeftBracketLen + versionPartRightBracketLen; DecodeVersionFromName: PROC [name: ROPE] RETURNS [dirEntry: VUXFiles.DirEntry] ~ { nameLen, bangPos: INT; versionNumber: CARD; versionNumberRope: ROPE; dirEntry ฌ [name, [none], FALSE]; nameLen ฌ Rope.Length[name]; IF Rope.IsPrefix[caseFileNamePrefix, name, FALSE] THEN RETURN; -- accelerator bangPos ฌ Rope.FindBackward[name, versionPartLeftBracket]; IF bangPos < 0 THEN bangPos ฌ nameLen; dirEntry.nameWithoutVersion ฌ Rope.Flatten[name, 0, bangPos]; IF bangPos > (nameLen-versionPartBracketsLen) THEN RETURN; IF Rope.Fetch[name, nameLen-1] = versionPartRightBracket THEN versionNumberRope ฌ Rope.Substr[name, bangPos+versionPartLeftBracketLen, nameLen-bangPos-versionPartBracketsLen] ELSE versionNumberRope ฌ Rope.Substr[name, bangPos+versionPartLeftBracketLen]; versionNumber ฌ Convert.CardFromRope[versionNumberRope, 10 ! Convert.Error => {versionNumber ฌ 0; CONTINUE }]; IF versionNumber # 0 THEN dirEntry.version ฌ [numeric, versionNumber]; }; UXS: PROC[r: REF] RETURNS [CString] ~ INLINE{ RETURN[UXStrings.Create[r]]; }; UXO: PROC[r: REF] RETURNS [CString] ~ INLINE{ RETURN[UXStrings.CreateScratch[r]]; }; UXR: PROC[s: CString] ~ INLINE{ UXStrings.ReleaseScratch[s]; }; initialInfoSetSize: CARDINAL ฌ 16; InfoSet: TYPE ~ REF InfoSetObject; InfoSetObject: TYPE ~ RECORD [ size: CARDINAL, info: SEQUENCE maxSize: CARDINAL OF Info ]; infoSetSize: CARDINAL ฌ SIZE[InfoSetObject[0]]; -- avoid compiler bug maxMaxSize: CARDINAL ฌ (CARDINAL.LAST - infoSetSize - 10) / SIZE[Info]; -- the '10' is just for luck InfoKind: TYPE ~ { caseHint, file, subdirectory }; Info: TYPE ~ RECORD [ name: ROPE, -- not including version part kind: InfoKind, version: Version ]; AddInfoToSet: PROC [set: InfoSet, info: Info] RETURNS [newSet: InfoSet] ~ { IF set = NIL THEN { set ฌ NEW[InfoSetObject[initialInfoSetSize]]; set.size ฌ 0 }; IF set.size < set.maxSize THEN { newSet ฌ set } ELSE { newSize: CARDINAL ~ MIN[2*set.maxSize, maxMaxSize]; newSet ฌ NEW[InfoSetObject[newSize]]; FOR i: CARDINAL IN [0 .. set.size) DO newSet.info[i] ฌ set.info[i]; ENDLOOP; newSet.size ฌ set.size; }; newSet.info[newSet.size] ฌ info; newSet.size ฌ newSet.size + 1; }; lastCharRope: ROPE ~ Rope.FromChar[CHAR.LAST]; Enumerate: PROC [h: FSHandle, pattern: PATH, nameProc: PFS.NameProc, infoProc: PFS.InfoProc] ~ { casedDirPath: PATH; dH: VUXFiles.DirHandle; match: PATH; matchCount: INT; version: PFSNames.Version ฌ PFSNames.ShortName[pattern].version; Matched: PROC[dH: VUXFiles.DirHandle, nameSoFar: PATH, casedName: ROPE, uncasedName: ROPE, version: PFSNames.Version, isDir: BOOL] RETURNS [continue: BOOL ฌ TRUE] ~ { name: PATH ฌ PFSNames.ReplaceShortName[PFSNames.Cat[nameSoFar, IF isDir THEN oneEltDirPath ELSE oneEltPath], MakeComponent[casedName, version]]; IF nameProc # NIL THEN continue ฌ nameProc[name] ELSE { bytes: INT; fileType: PFS.FileType; fmt: UnixTypes.Fmt; attachedTo: PATH ฌ NIL; mTime: MTime; uniqueID: PFS.UniqueID; mutability: PFS.Mutability ฌ mutable; lowName: ROPE ฌ uncasedName; fH: FHandle; IF version.versionKind = numeric THEN lowName ฌ EncodeVersionInName[uncasedName, version.version]; [fH~fH, fmt~fmt, bytes~bytes, mTime~mTime, attachedTo~attachedTo] ฌ LookupSimple[dH, lowName]; uniqueID ฌ [egmt~[gmt~GMTFromMTime[mTime], usecs~0]]; fileType ฌ PFSFileTypeFromUnixFmt[fmt]; IF attachedTo#NIL THEN attachedTo ฌ ResolveAttachment[name, attachedTo]; continue ฌ infoProc[name, attachedTo, uniqueID, LOOPHOLE[bytes], mutability, fileType]; }; }; NameInfo: TYPE ~ RECORD[ name: ROPE, version: Version, casedName: ROPE]; noName: NameInfo ~ [NIL, [none], NIL]; EnumerateInner: PROC[level: INT, dH: VUXFiles.DirHandle, nameSoFar: PATH] RETURNS [continue: BOOL ฌ TRUE] ~ { component: ROPE ฌ VUXFiles.GetDirComponent[path~pattern, pos~level, smashCase~FALSE]; starPos: INT ~ Rope.Index[component, 0, "*"]; lowestPossible: ROPE ฌ Rope.Substr[VUXFiles.GetDirComponent[path~pattern, pos~level, smashCase~TRUE], 0, starPos]; highestPossible: ROPE ฌ Rope.Concat[lowestPossible, lastCharRope]; set: InfoSet ฌ NIL; latestCaseHint, thisName, casedName: ROPE ฌ NIL; lag: NameInfo ฌ noName; EachName: VUXFiles.EachDirEntryProc ~ { thisName ฌ entry.nameWithoutVersion; casedName ฌ thisName; IF Rope.Compare[thisName, highestPossible] = greater THEN RETURN[FALSE]; SELECT TRUE FROM entry.version.versionKind = numeric AND entry.version.version=0 => { latestCaseHint ฌ thisName; }; entry.version.versionKind = none => { IF Rope.Equal[thisName, "."] OR Rope.Equal[thisName, ".."] THEN RETURN[TRUE]; IF latestCaseHint # NIL THEN SELECT Rope.Compare[latestCaseHint, thisName, FALSE] FROM less => latestCaseHint ฌ NIL; equal => casedName ฌ latestCaseHint; ENDCASE; IF Rope.Match[component, thisName, FALSE] THEN { IF level = PFSNames.ComponentCount[pattern] - 1 THEN { RETURN Matched[dH, nameSoFar, casedName, thisName, [none], TRUE]; } ELSE { continue: BOOL ฌ TRUE; subDH: VUXFiles.DirHandle ฌ NIL; [dHChild~subDH] ฌ VUXFiles.GetDirChild[dH, thisName, FALSE ! PFS.Error => IF error.code = $unknownFile THEN {subDH ฌ NIL; CONTINUE}]; IF subDH # NIL THEN { continue ฌ EnumerateInner[level + 1, subDH, PFSNames.ReplaceShortName[PFSNames.Cat[nameSoFar, oneEltPath], MakeComponent[casedName, [none]]]]; [] ฌ VUXFiles.UnPinDir[subDH]; }; RETURN [continue]; }; }; }; entry.version.versionKind = numeric => { IF level # PFSNames.ComponentCount[pattern] - 1 THEN RETURN[TRUE]; IF latestCaseHint # NIL THEN SELECT Rope.Compare[latestCaseHint, thisName, FALSE] FROM less => latestCaseHint ฌ NIL; equal => casedName ฌ latestCaseHint; ENDCASE; IF Rope.Match[component, thisName, FALSE] THEN { it: NameInfo ฌ noName; SELECT version.versionKind FROM all, none => { it ฌ [thisName, entry.version, casedName]; }; numeric => { IF entry.version = version THEN it ฌ [thisName, entry.version, casedName]; }; lowest => { IF NOT Rope.Equal[lag.name, thisName, TRUE] THEN { it ฌ [thisName, entry.version, casedName]; }; lag ฌ [thisName, entry.version, casedName]; }; highest => { IF NOT Rope.Equal[lag.name, thisName, TRUE] THEN { it ฌ lag; }; lag ฌ [thisName, entry.version, casedName]; }; ENDCASE => ERROR; IF it.name # NIL THEN { RETURN[ Matched[dH, nameSoFar, it.casedName, it.name, it.version, FALSE] ] ; }; }; }; ENDCASE => ERROR; }; IF level >= PFSNames.ComponentCount[pattern] THEN RETURN; IF starPos >= 0 THEN lowestPossible ฌ lowestPossible.Substr[0, starPos]; VUXFiles.EnumerateDirectory[dH, EachName, lowestPossible, FALSE]; IF lag.name#NIL AND version.versionKind=highest THEN RETURN[ Matched[dH, nameSoFar, lag.casedName, lag.name, lag.version, FALSE] ]; }; { ENABLE { UNWIND => { IF dH # NIL THEN VUXFiles.UnPinDirPath[dH]; dH ฌ NIL; }; }; IF PFSNames.IsADirectory[pattern] THEN -- Perhaps dubious pattern ฌ PFSNames.Cat[pattern, oneEltPath]; [dH, match, matchCount] ฌ FollowDirPathForEnumeration[PFSNames.StripVersionNumber[pattern]]; IF dH # NIL THEN [] ฌ EnumerateInner[matchCount, dH, match]; }; IF dH # NIL THEN VUXFiles.UnPinDirPath[dH]; }; FollowDirPathForEnumeration: PROC [pattern: PATH] RETURNS [dH: DirHandle, match: PATH, matchCount: INT ฌ 0] ~ { ENABLE UNWIND => { IF dH # NIL THEN VUXFiles.UnPinDirPath[dH]; }; dir: PATH ฌ PFSNames.Directory[pattern]; dirPath: PATH; caseFileName: ROPE; component, casedComponent: ROPE; i: INT ฌ 0; EachDirEntry: VUXFiles.EachDirEntryProc -- [entry: VUXFiles.DirEntry] RETURNS [continue: BOOL ฌ TRUE] -- ~ { SELECT TRUE FROM Rope.Compare[entry.nameWithoutVersion, component, FALSE] = greater => { continue ฌ FALSE; }; entry.version.versionKind = numeric AND entry.version.version=0 => { casedComponent ฌ entry.nameWithoutVersion; continue ฌ FALSE; }; ENDCASE => NULL; }; dH ฌ VUXFiles.GetDirRoot[]; dirPath ฌ PFSNames.SubName[name~dir, start~0, count~1, absolute~TRUE, directory~TRUE]; FOR i ฌ 1, i + 1 WHILE i < PFSNames.ComponentCount[pattern] - 1 DO component ฌ casedComponent ฌ VUXFiles.GetDirComponent[path~pattern, pos~i, smashCase~TRUE]; IF Rope.Find[component, "*"] >= 0 THEN EXIT; caseFileName ฌ Rope.Concat[caseFileNamePrefix, component]; VUXFiles.EnumerateDirectory[dH, EachDirEntry, component, FALSE]; dirPath ฌ PFSNames.ReplaceShortName[PFSNames.Cat[dirPath, oneEltPath], MakeComponent[casedComponent, [none]] ]; dH ฌ VUXFiles.GetDirChild[dH~dH, childName~component, create~FALSE ! PFS.Error => { IF error.code = $unknownFile THEN {VUXFiles.UnPinDirPath[dH]; dH ฌ NIL; CONTINUE }}].dHChild; IF dH = NIL THEN RETURN; ENDLOOP; match ฌ dirPath; matchCount ฌ i; }; UIDFromMTime: PROC [mTime: MTime] RETURNS [UniqueID] ~ { RETURN[[egmt: [gmt: HostTime.ExtendedGMTFromHostTime[[a~LOOPHOLE[mTime]]].gmt, usecs: 0], host: [0, 0]]] }; PFSClass.ClearCachedServer["-vux"]; PFSClass.Register[vuxFlavor, VUXGetHandle]; }... 8 VUXFileImpl.mesa Copyright ำ 1987, 1988, 1989, 1990, 1991, 1992 by Xerox Corporation. All rights reserved. Demers, February 19, 1988 4:54:19 pm PST Xerox3, January 15, 1988 12:37:12 pm PST JKF November 16, 1988 4:51:21 pm PST Tim Diebert: May 18, 1988 8:46:48 am PDT Carl Hauser, September 21, 1989 10:05:55 am PDT Doug Wyatt, February 2, 1990 3:56:14 pm PST Nichols, March 29, 1990 3:38 pm PST Willie-s, July 9, 1992 4:56 pm PDT Chauser, February 3, 1993 3:11 pm PST Michael Plass, January 27, 1992 5:34 pm PST Wade, May 3, 1991 5:27 pm PDT TODO: The EachDirProcs need to terminate enumeration (sorted assumption). TODO: Ensure that the FileInfoProc does the right thing about attachedTo [DKW]. Copied Types Parameters File lookup and creation Return info for named file if it's present and we can stat it successfully. Return fH=NIL if named file is nonexistent. Raise PFS.Error otherwise. All attachments supported in this view must be -ux file names and should be identified as such to clients. Return info for file if it's present and we can stat it successfully. Return fH=NIL if no file exists with given name and created time. Raise PFS.Error for other errors. Determine a version number for to file; rename the from file to that version. If "deleteIfError" is TRUE, the "from" file will disappear even if the rename operation fails. ignore failures Registered As a PFS Class Returns [NIL, NIL] if fs can't be located. at the moment, we only support the local Unix name space. Error reporting Procs to provide versioned view fullFName ฌ PFSNames.SetVersionNumber[file, version]; chmod done separately from open to override umask. Sync the file, because otherwise writes to full NFS servers all appear to work, but the bits don't appear there. Preserve "Execute" bits in Unix-based views This is not pleasant... IF wantedUniqueID.egmt.time # BasicTime.nullGMT THEN TRUSTED { bah! there's no string name available for the UTimes. mTime: MTime ~ MTimeFromGMT[wantedUniqueID.egmt.time]; modTimeVal: UnixTypes.AccModTimes _ [accessTime: [sec: mTime, usec: 0], modTime: [sec: mTime, usec: 0]]; res: RES ~ UnixSysCalls.UTimes[stringName, @modTimeVal]; }; Sync the file, because otherwise writes to full NFS servers all appear to work, but the bits don't appear there. have to find it; may raise PFS.Error[FileNotFound] produce the unix name by syntactic transformation Utility Time Conversion Version Encoding Enumerations Parameters Sets of Info Not used right now. EnumerationAborted: PRIVATE ERROR ~ CODE; Enumerate all the files that match the pattern, calling either nameProc or infoProc (whichever is non-NIL) on each. Called on each file name matched, it constructs the final name and calls either the name proc or the info proc. The recursive tree walk. I suppose all the continue flag stuff could be done with exceptions. Pass to VUXFiles.EnumerateDirectory to fill "set" with a directory's file entries. See if we have a case hint for this guy. Check for match. Match if we're processing the last component, else recur. Don't match files until the last pattern component. See if we have a case hint for this guy. Now try for match. If versions match, we're in. Match everything. Versions must agree. Main for EnumerateInner. We enumerate the directory and build a set that has all the files, dirs, and case hints in it. The enumeratioin should place each case hint immediately before the files it pertains to. Main code for Emuerate. Match the non-wildcard part of the pattern. We could let EnumerateInner do this, but we don't, for both historical and speed reasons. Follows a pattern down the file tree through all components with no '* characters. The case information for each directory traversed is remembered. When done, we return a directory handle for the matched part, its name, and the length of the name. assert: Rope.Equal [entry.nameWithoutVersion, component] Component 0 is empty by convention, and we don't process the last one. At this point, i has either the index of the component with the first *, or the index of the last component. the LOOPHOLE is to guard again bogus unix file times Initialization ส7•NewlineDelimiter –(cedarcode) style•NewLineDelimiter ™codešœ™Kšœ ฯeœO™ZK™(K™(K™$K™(K™/K™+K™#K™"K™%K™+K™K™K™I™OK˜——šฯk ˜ K˜K˜K˜K˜ Kšœ žœžœ4˜XK˜Kšœ žœ ˜K˜ Kšžœ˜Kšžœ˜K˜ K˜ K˜ Kšœžœ!˜.Kšœžœƒžœ ˜›K˜ Kšœžœ!˜4K˜ Kšœžœ"˜=K˜ Kšœ žœžœžœ˜RK˜Kšœ˜K˜—š ฯn œžœžœžœ žœžœ˜BKšžœ>žœžœ„˜ะKšžœžœ ˜K˜head™ Kšœžœžœฯc:˜qK˜Kšœ žœ˜!Kšžœžœ žœ˜Kšœ žœ˜%Kšžœžœžœ˜Kšœžœ˜Kšœ žœ˜!Kšœ žœ˜%Kšžœžœ žœ˜Kšœ žœžœ ˜Kšœ žœžœ ˜Kšœžœžœ˜Kšžœžœ žœ˜Kšœ žœ˜#Kšœ žœ˜!—™ šœ žœ˜ K˜MK˜—Kšœ?˜?Kšœ{˜{Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜—™šŸ œžœžœ˜6Kšžœ*žœžœžœ˜[Kšœ˜K™KK™+K™K˜Kšœ˜Kšœ8˜8šžœž˜˜K˜Kšœ ˜ Kšœžœ˜(Kšœ˜K˜—šžœ˜ Kšœžœ˜ Kšœ ˜ K˜ Kšœ ˜ K˜—KšžœIžœQžœ"˜ศ—K˜K˜—Kšœ)žœ˜GK™jš Ÿœžœžœžœžœžœ˜Ošœ˜Kš œ žœžœ!žœ žœ:˜‚Kšžœ9˜?Kšœ˜—K˜K˜—šŸœžœžœ˜JKšžœžœ5žœžœ˜wK˜K™EK™AK™!K˜Kšœžœžœ˜šŸ œ 7œ˜cK˜šžœ.žœž˜?Kšœžœžœ ˜.šœ žœžœ˜+K˜ KšœT˜TKšœc˜cšžœžœžœžœ˜.Kšœžœ˜ Kšžœžœ˜K˜—K˜—˜ Kšžœžœ˜K˜—Kšžœžœ ,˜>—K˜—Kšœ4žœ˜;Kš žœžœžœžœžœžœ˜:K˜K˜—šŸœžœžœ žœ˜BKšžœ2žœ žœžœ˜WKšœžœ˜Kšœ žœžœ˜Kšœžœžœ˜šŸ œ 7œ˜cšžœ.žœž˜?Kšœžœžœ ˜.šœ žœžœ˜+Kšœžœ '˜Jšžœžœ˜!Kšœ žœ˜Kšœ˜K˜—K˜šžœ˜Kšœžœ˜——Kšœ žœžœ˜Kšžœ˜ —K˜—Kšœ=˜=šžœ žœ˜Kšœ;˜;Kšœ-˜-Kšœžœ˜ K˜—šžœžœžœ˜Kšœ˜Kšœžœ˜ Kšœ˜—K˜K˜—šŸœžœžœ žœ˜BKšžœ1žœ žœžœ˜VKšœžœžœžœ˜ Kšœ žœžœ˜Kšœžœžœ˜šŸ œ 7œ˜cšžœ.žœž˜?Kšœžœžœ ˜.šœ žœžœ˜+Kšœžœ '˜Jšžœ žœžœ˜1Kšœ žœ˜Kšœ˜K˜—K˜šžœ˜Kšœžœ˜——Kšœ žœžœ˜Kšžœ˜ —K˜—Kšœ=˜=šžœ žœ˜Kšœ:˜:Kšœ+˜+Kšœžœ˜ K˜—šžœžœžœ˜Kšœ˜Kšœžœ˜ Kšœ˜—K˜K˜—šŸœžœžœžœ,žœ/žœ"žœ˜าKšœžœ˜ ˜šžœžœ˜Kšžœžœžœ˜+K˜—Kšœ žœ˜Kšœžœ˜ Kšœ ˜ K˜Kšœ=žœ žœ˜RKšœVžœ˜]Kšžœ žœ-žœJžœ˜ฌšžœž˜˜Kšœ>žœ˜EKšžœžœžœžœ˜K˜—˜ Kšœ=žœ˜DKšžœžœžœžœ˜K˜—˜Kšžœžœžœa˜ŒKšœžœ˜K˜—˜ Kšœ:˜:Kšœžœ˜ K˜—Kšžœžœ œ˜%—KšžœžœF˜SKšœ5˜5Kšžœžœžœžœ˜5š žœžœžœžœžœ˜WKšžœ}˜—Kšžœžœžœ2˜JKšœ ˜8šž˜Kšœžœ˜ —K˜—Kšœ˜šžœžœžœ˜Kšœ&žœ>žœ˜ˆK˜—K˜K˜—š Ÿœžœ?žœ*žœ žœžœ˜žKšžœ˜K™MK™^Kšœžœ˜Kšœžœžœ˜˜šžœžœ˜Kšžœžœ ˜5K˜—šžœž˜&Kšœ5žœ^˜•šœ˜Kšœžœ˜ Kšœžœ˜Kšœžœ˜ KšœYžœ˜_šžœžœ ž˜Kšžœžœžœ Z˜{Kšžœžœžœžœ:˜rK˜Kšœ[˜[šœ˜Kšœžœ ˜Kšœžœ˜"šžœ˜ Kšžœ&˜*Kšžœ#˜'—Kšžœžœ˜Kšœ˜—šžœžœ˜Kšœ.˜.Kš žœžœžœžœžœX˜wKšœ˜—Kšœžœ˜Kšžœ˜Kšžœ˜—šžœžœžœ˜Kšœžœ ˜Kšœ ˜ Kšžœ˜ Kšœ˜—šžœžœ˜Kšœ.ฯi˜/K˜]Kšœ˜—Kšœžœ˜ K˜—šœ ˜ K˜Kšœžœ˜ Kšœ˜Kšœ[˜[šœ˜Kšœžœ ˜Kšœžœ˜"šžœ˜ Kšžœ&˜*Kšžœ#˜'—Kšžœžœ˜Kšœ˜—šžœ žœ˜Kšœ˜Kšžœžœžœe˜xKšœ˜—šžœžœžœ˜Kšœžœ ˜Kšœ ˜ Kšžœ˜ Kšœ˜—Kšœ4žœ˜9K˜—Kšžœžœ ˜!—K˜—K˜K˜—šŸœžœžœ˜4Kšœžœ"˜5Kšœ˜Kšžœ˜ Kšœžœ˜K˜K˜—Kšœžœžœ˜K˜š Ÿœžœžœžœ žœ˜>Kšœžœ˜ šžœžœžœ˜K˜#KšœžœžœF˜wK˜—Kšœ%˜%Kšœžœ+žœ˜CKšœ žœL˜YK˜K˜—Kšœžœžœ ˜,Kšœžœžœ#˜DK˜šŸœžœžœžœ˜;Kšœžœ)˜;Kšœžœ*˜=KšœžœD˜TKšžœ˜ K™Kšžœ žœ˜-Kšœžœ˜K˜K˜—š Ÿ œžœžœžœžœžœ˜VšŸ œ 7œ˜cšžœ.žœž˜?Kšœžœ ˜%šœ ˜ šžœ žœ˜(Kšœ(˜(Kšžœžœ˜K˜—Kšžœžœžœ˜—Kšœ žœžœ˜Kšžœ˜ —K˜—Kšœ4žœ˜;K˜K˜—šŸœžœžœžœ˜žœ˜VKšœ0žœ žœ˜DKšœcžœ˜jKšžœ žœY˜jšžœž˜Kšœ+˜+Kšœžœ˜KšžœR˜Y—KšœRžœžœ žœ ˜tKšœT˜TKšœ0˜0šžœž˜Kšœ;žœ˜@Kšžœ5˜<—šžœžœ˜Kšœ4žœ˜:Kšœžœ žœ˜6K˜—Kšœ$žœ˜(K˜—Kšœ(žœ˜,Kšžœžœ˜ šž˜KšœH˜H—K˜—K˜K˜—š Ÿ œžœ ขg ข œ˜ฃK˜ Kšœžœ˜Kšœžœžœ˜Kšœ˜Kšœžœ˜K˜Kšœžœžœ˜Kšœ žœ˜Kšœžœ˜K˜Kšœ‡˜‡KšœT˜TKšžœžœžœžœ˜Kšœžœ˜@šœ˜Kšœžœ˜Kšœ1˜1Kšžœ˜ Kšœ˜—šžœ žœ˜K˜.K˜4Kšœ˜—šžœ˜ šžœžœ˜Kšœ˜K˜—šž˜Kšœ žœ˜Kš œžœžœžœžœ˜BKšœ9˜9šžœžœ˜K˜.K˜4Kšœ˜—Kšœ˜Kšžœ˜Kšžœ"žœžœ˜.Kšžœ˜—K˜—K˜K˜K˜—šŸœžœCžœžœ˜vKšœ*˜*Kšžœ žœžœ˜šœ˜š œžœžœžœžœ2˜ZKšžœ3˜7—Kšžœžœ ˜ K˜—K˜K˜—šŸœžœžœžœžœ žœ žœžœ9žœ žœžœžœ˜•Kšœžœ˜!K˜K˜šŸ œžœ˜Kšœ žœ˜Kšœ,žœ žœ˜@Kšœcžœ˜jKšžœ žœV˜gšžœž˜(Kšœ+˜+Kšœžœ˜KšžœP˜W—KšœG˜GKšœžœ˜'Kšœ=˜=šžœ žœ˜K˜.K˜JKšœ˜—šœ˜K™2Kšœ?žœ˜Fšžœ0žœ˜9K˜.K˜KKšœ˜—Kšœ˜—Kšœžœ˜K˜K˜—šŸ œžœžœ˜Kšœžœ˜K™pKšœžœ˜"K˜šžœ žœ"žœžœ˜@K˜VKšœ˜—KšœQžœ˜Wšžœž˜Kšœ;žœ˜@Kšžœ/ ˜<—šžœžœ˜Kšœ4žœ˜:Kšœ˜K˜—Kšœ žœ˜$K˜K˜—Kšœ˜K˜šžœ˜ šžœžœ˜Kšžœžœžœ˜+K˜—K˜Kšžœžœžœ˜.K˜ Kšžœ˜K˜—K˜K˜—K˜š Ÿœžœ ขt ข ข4 œ˜๗Kšœ˜K˜šŸœžœžœžœ˜AKšœžœžœ˜šžœ˜ šžœžœ˜Kšžœ žœžœ ˜4K˜—Kšœ,˜,šž˜Kšœžœ˜Kš œžœžœžœžœ˜BKšœžœ,˜>Kšžœžœžœ˜Kšœ:˜:šžœžœ˜&šžœžœ˜Kšœ.ก˜/K˜4Kšœ˜—Kšžœf˜jKšœ˜—Kšžœ˜—˜K™+K™Kšžœžœ žœ˜Kšœ žœžœžœ&˜OKšœ˜Kšœžœ ˜(Kšœ žœžœ ˜)šžœ žœ˜K˜.K˜5K˜—šžœ žœžœ˜Kšœ žœ!˜4Kšœžœ žœžœ˜XKšœžœ˜6K˜—K˜—Kšœ#˜#Kšœ)žœ˜-K˜—K˜K˜—Kšœžœ˜Kšœ˜Kšœ\žœ˜uK˜Kšžœžœžœ5˜GK˜K˜—š Ÿœžœžœžœ žœ˜hšœžœž˜Kšœžœ ˜Kšžœžœ˜—Kšœ˜K˜—šŸœ  Nœ˜…Kšœžœ  ˜/Kšœžœ˜!Kšœ˜K˜—šŸœ! Rœ˜‹Kšœžœ  ˜/Kšœžœ˜!Kšœ˜K˜—šŸ œ -œ˜XKšœžœ  ˜/K˜K˜ Kšœ˜Kšœ9žœžœ žœžœžœžœžœ˜‘Kšœ˜Kšžœžœžœžœ*˜AKšžœžœžœ˜Kšœ˜K˜—šŸœ “œ˜ฒKšœžœ  ˜/Kšœ˜K˜—šŸœ Bœ˜sKšœžœ  ˜/K˜ Kšœ˜K˜—šŸœ' Gœ˜ŒKšœžœ  ˜/Kšœžœžœ žœ ˜5šžœ žœ˜Kšœžœ%˜-šžœ žœ˜K˜.K˜aK˜—Kšœ˜—šžœ.žœžœ™>K™5Kšœ6™6Kšœh™hKšœžœ0™8Kšœ™—Kšœ˜K˜—šŸœ! Uœžœ˜–Kšœžœ  ˜/Kšœžœžœ žœ ˜5Kšœ˜Kšœžœ ˜(šžœ žœ˜K˜.K˜BK˜—šžœžœžœ˜šœ)˜)Kšžœžœ žœ˜žœ žœžœ žœžœ žœž œ˜ฃKšœ žœ  ˜,Kšœžœ žœ ˜&Kšœžœ ˜Kšœ žœ˜K˜šŸœžœžœžœ žœ žœžœžœ˜PKšžœžœžœ˜Kšœ,˜,Kšœ+˜+šžœžœ˜ Kšœžœ-˜5šžœ žœ˜K˜Kšžœ˜Kšœ˜—Kšœ˜—Kšœ(žœ ˜Pšžœžœ˜K˜Kšžœ˜Kšœ˜—Kšžœ'žœ'˜TK˜—K˜Kšžœžœžœ0˜VK˜K˜—šœ žœ˜%K˜—šŸœžœ>žœ žœžœ žœžœžœ˜™Kšœ žœ  ˜,Kšœžœ žœ ˜&Kšœžœ ˜Kšœ žœ˜K˜šŸœžœžœžœ žœ žœžœ˜IKšžœžœžœ˜Kšœ,˜,Kšœ+˜+šžœžœ˜ Kšœžœ-˜5šžœ žœ˜K˜Kšžœ˜Kšœ˜—Kšœ˜—Kšœ,žœ$˜Xšžœžœ˜K˜Kšžœ˜Kšœ˜—Kšžœ'žœ*˜WK˜—Kšœ˜Kšžœžœžœ1˜XKšœ˜K˜—šŸœ ถœ˜ีKšœžœ  ˜/Kšœžœ.žœEžœ ˜บKšœžœ˜K˜Kšœ žœ˜Kšœ žœ˜šžœž˜šœ ˜ Kšœ˜Kšœ›˜›šœ˜Kšœžœ ˜%Kšœ8žœ˜MKšžœ ˜Kšœ˜—Kšœ0˜0Kšœ4˜4K˜!Kšœžœ˜$Kšžœ ˜K˜—šœ ˜ KšœXžœžœ˜fKšœ4˜4Kš œRžœžœžœžœ˜Kšžœžœžœ$˜KKšœžœžœ˜4Kšžœ ˜Kšœ˜—šœ ˜ Kšœ+˜+šžœžœžœ˜>KšœXžœžœ˜dKšœ4˜4Kš œRžœžœžœžœ˜Kšžœžœžœ$˜KKšœžœžœ˜4Kšœ˜—šžœ˜Kšœ“žœžœžœ˜ฝšžœ žœžœ˜Kšœv˜vKšœ˜—šœ˜Kšœžœ ˜%Kšœ8žœ˜KKšžœ ˜Kšœ˜—Kšœ4˜4Kšœžœ˜$Kšœ˜—Kšœ4˜4Kšžœ ˜Kšœ˜—šœ ˜ Kšœ’˜’šžœ žœžœ˜Kšœx˜xKšœ˜—šœ˜Kšœžœ ˜%KšœTžœ˜gKšžœ ˜Kšœ˜—Kšœ4˜4Kšœžœ˜$Kšžœ ˜K˜—Kšžœžœ˜—Kšœ˜K˜—šŸ œ …œ˜จKšœ˜K˜Kšœ žœ˜K˜K˜šŸ œžœ˜Kšœ žœ˜Kšœ,žœ žœ˜@Kšœcžœ˜jKšžœ žœV˜gšžœž˜(Kšœ+˜+Kšœžœ˜KšžœQ˜X—K˜šœ˜Kšœ žœžœ˜-Kšžœ žœžœTžœ%˜’KšœKžœžœ A˜šKšœ˜—Kšœžœ˜K˜K˜—šŸ œžœžœ˜Kšœžœ˜šžœž˜Kšœ;žœ˜@Kšžœ/ ˜<—šžœžœ˜Kšœ4žœ˜:Kšœ˜K˜—Kšœ žœ˜$K˜K˜—K˜šžœ˜ šžœžœ˜Kšžœžœžœ˜+K˜—K˜K˜ Kšœ3˜3K˜—K˜Kšœ˜K˜—šŸœ 0œ˜QKšœžœ  ˜1Kšœžœ žœ žœ ˜3Kšœžœ ˜Kšœ žœ˜K˜šŸœžœžœžœ žœ žœžœ˜IKšžœžœžœ˜Kšœžœ˜ Kšœ+˜+K™pšžœžœ˜K˜šžœ žœžœžœ˜6K˜Kšžœ˜%Kšœ˜—K˜—Kšžœžœžœ0˜SKšœ˜šžœ žœ˜K˜Kšžœ˜Kšœ˜—K˜—Kšžœžœžœ1˜XKšœ˜K˜—šŸœžœ3žœ˜HKšœ5˜5Kšœh˜hšžœ-žœžœ8žœ˜uK˜.K˜NKšœ˜—Kšœ˜K˜—šŸœžœ  ขF œ˜ŒKšœžœ  ˜/Kšœžœžœ žœ ˜5šžœžœžœ˜šœ)˜)Kšœ žœ*˜:Kšœžœžœ ˜7Kšœ˜—šœ*˜*KšœV˜VKšœ˜—šœ*˜*KšœV˜VKšœ˜—Kšžœu˜|—Kšœ˜K˜—šŸœžœ ข* œ˜hKšžœžœ˜Kšœ˜K˜—šŸœžœ  ข* œ˜pK˜4šžœ+žœ˜3K™2Kšžœžœ˜3K˜—šžœ˜K™1Kšœ žœ)˜7Kšœ žœ@˜QKšœ žœG˜TKšœžœžœ ˜