FSNameImpl.Mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Russ Atkinson, November 7, 1984 7:19:26 pm PST
HGM, February 7, 1984 11:24:30 pm PST (Allow ' in names)
Schroeder, December 15, 1983 9:26 am
Levin, August 9, 1983 11:28 am
DIRECTORY
Basics USING [DIVMOD],
FS USING [ComponentPositions, ComponentRopes, maxFNameLength, Position],
FSBackdoor USING [highestVersion, lowestVersion, ProduceError, Version],
FSFileOps USING [GetVolumeDesc, VolumeDesc],
FSName USING [ParsedFName, VersionInfo],
List USING [Assoc],
ProcessProps USING [GetPropList],
Rope USING [Cat, Concat, Fetch, Flatten, Index, Length, Match, NewText, Replace, Substr, ROPE, Text];
FSNameImpl: CEDAR PROGRAM
IMPORTS Basics, FSBackdoor, FSFileOps, List, ProcessProps, Rope
EXPORTS FS, FSBackdoor, FSName
SHARES Rope
= {
ROPE: TYPE = Rope.ROPE;
Version: TYPE = FSBackdoor.Version;
Global State
initialDefaultWDir: ROPE = "[]<>";
validNameChars: ROPE ← "$-+'←";
The valid name characters are in {A..Z, a..z, 0..9}, or in this set. Since the normal formatting characters are examined first, putting them in this set has no effect. Note that the normal formatting characters are in the set {'[, '], '<, '>, '., '!, '#, '*}
defaultWDir: ROPE ← initialDefaultWDir;
RRA: This does not need to be protected by a monitor, since RC assignments must be atomic for storage safety reasons.
Exported to FS
SetDefaultWDir: PUBLIC PROC [dir: ROPE] = {
IF Rope.Length[dir] = 0
THEN dir ← initialDefaultWDir
ELSE {
places: Places;
[dir, places] ← ExpandAndFindPlaces[dir, GetDefaultWDir[], directory];
IF places.serverEnd # 1 THEN DirNotLocal[dir];
IF places.dirEnd = places.serverEnd THEN NoVolumePart[dir];
dir ← Rope.Flatten[dir];
};
defaultWDir ← dir;
};
GetDefaultWDir: PUBLIC PROC RETURNS [ROPE] = {
RETURN [defaultWDir];
};
ExpandName: PUBLIC PROC[name: ROPE, wDir: ROPE] RETURNS [fullFName: ROPE, cp: FS.ComponentPositions, dirOmitted: BOOLFALSE] = {
places: Places;
fNLen: INT;
[fullFName, places] ← ExpandAndFindPlaces[name, wDir, pattern];
fNLen ← Rope.Length[fullFName];
cp.server ← [1, places.serverEnd-1];
IF places.dirEnd >= places.serverEnd+2
THEN cp.dir ← [places.serverEnd+2, places.dirEnd-places.serverEnd-2]
ELSE { dirOmitted ← TRUE; cp.dir ← [places.serverEnd+1, 0] };
cp.subDirs ← IF places.subDirEnd > places.dirEnd
THEN [places.dirEnd+1, places.subDirEnd-places.dirEnd-1]
ELSE [places.dirEnd+1, 0];
cp.base ← [places.subDirEnd+1, places.rootEnd-places.subDirEnd-1];
cp.ext ← IF places.bang > places.rootEnd
THEN [places.rootEnd+1, places.bang-places.rootEnd-1]
ELSE [places.rootEnd, 0];
IF places.bang < fNLen
THEN {
[] ← ParseVersion[fullFName, places.bang+1, TRUE];
cp.ver ← [places.bang+1, fNLen-places.bang-1];
}
ELSE cp.ver ← [fNLen, 0];
};
ConstructFName: PUBLIC PROC [cr: FS.ComponentRopes, omitDir: BOOL] RETURNS [fName: ROPE] = {
fName ← Rope.Cat[ "[", cr.server, "]" ];
IF NOT omitDir THEN fName ← Rope.Cat[ fName, "<", cr.dir, ">" ];
IF Rope.Length[cr.subDirs] > 0 THEN fName ← Rope.Cat[ fName, cr.subDirs, ">" ];
fName ← Rope.Cat[ fName, cr.base ];
IF Rope.Length[cr.ext] > 0 THEN fName ← Rope.Cat[ fName, ".", cr.ext ];
IF Rope.Length[cr.ver] > 0 THEN fName ← Rope.Cat[ fName, "!", cr.ver ];
};
Exported to FSBackdoor
MakeFName: PUBLIC PROC [nameBody: ROPE, version: Version, prefix: ROPE] RETURNS [ROPE] = {
con: ROPE ← Rope.Concat [ nameBody, VersionPartFromVersion[version] ];
IF NOT Rope.Match["[*", nameBody] THEN {
IF prefix = NIL THEN prefix ← initialDefaultWDir;
con ← Rope.Concat [ prefix, con ];
};
RETURN [con];
};
Exported to FSName
ParseClientName: PUBLIC PROC [clientName, wDir: ROPE, defaultVersionHigh, pattern: BOOL] RETURNS [pn: FSName.ParsedFName, vI: FSName.VersionInfo] = {
vName: ROPENIL;
places: Places;
[pn.fullName, places] ← ExpandAndFindPlaces[clientName, wDir, IF pattern THEN pattern ELSE fName];
IF places.bang < Rope.Length[pn.fullName]
THEN [pn.version, vI] ← ParseVersion[pn.fullName, places.bang+1, pattern]
ELSE {
vI ← missing;
pn.version ← IF defaultVersionHigh THEN FSBackdoor.highestVersion ELSE FSBackdoor.lowestVersion;
};
IF IsLocal[pn.fullName]
THEN {
vL: CARDINAL ← places.dirEnd - places.serverEnd - 2;
IF vL # 0 THEN vName ← Rope.Substr[pn.fullName, places.serverEnd + 2, vL];
pn.nameBody ← Rope.Flatten[ Rope.Substr[pn.fullName, places.dirEnd + 1, places.bang - places.dirEnd - 1] ];
}
ELSE pn.nameBody ← Rope.Flatten[ Rope.Substr[pn.fullName, 0, places.bang] ];
pn.volDesc ← FSFileOps.GetVolumeDesc[vName];
};
ParseCacheName: PUBLIC PROC [volName, cacheName: ROPE, pattern: BOOL] RETURNS [pn: FSName.ParsedFName, vI: FSName.VersionInfo] = {
firstChar: CHARACTER;
length, bangIndex: CARDINAL;
nameUse: NameUse = IF pattern THEN pattern ELSE fName;
pn ← [NIL, NIL, FSBackdoor.highestVersion, FSFileOps.GetVolumeDesc[volName]];
vI ← missing;
IF Rope.Length[cacheName] = 0 THEN {
IF pattern
THEN { pn.nameBody ← "[*"; RETURN }
ELSE IllegalName[cacheName, nameUse];
};
FOR i: INT IN [0 .. Rope.Length[cacheName]) DO -- determine syntax convention
SELECT Rope.Fetch[cacheName, i] FROM
'[, '], '<, '> => EXIT;
'/ => { cacheName ← ConvertSlashName[cacheName]; EXIT };
ENDCASE;
ENDLOOP;
firstChar ← Rope.Fetch[cacheName, 0];
IF firstChar # '[ THEN {
IF firstChar = '*
THEN cacheName ← Rope.Concat["[", cacheName]
ELSE IllegalName[cacheName, nameUse];
};
length ← Rope.Length[cacheName];
bangIndex ← Rope.Index[cacheName, IF length > 6 THEN length - 6 ELSE 0, "!"];
IF bangIndex # length AND NOT (bangIndex = length-2 AND Rope.Fetch[cacheName, length-1] = '*) THEN
[pn.version, vI] ← ParseVersion[cacheName, bangIndex+1, pattern];
pn.nameBody ← Rope.Flatten[ Rope.Substr[cacheName, 0, bangIndex] ];
};
ConvertNamebodyPattern: PUBLIC PROC [nbP: ROPE] RETURNS [Rope.Text] = {
RETURN [ IF Rope.Length[nbP] = 0 THEN "*" ELSE ConvertSlashName[nbP] ] };
ParseName: PUBLIC PROC [volName, fName: ROPE] RETURNS [p: FSName.ParsedFName] = {
length: CARDINAL = Rope.Length[fName];
bangIndex: CARDINAL = Rope.Index[fName, IF length > 6 THEN length - 6 ELSE 0, "!"];
IF bangIndex = length
THEN p.version ← FSBackdoor.highestVersion
ELSE [p.version, ] ← ParseVersion[fName, bangIndex+1, FALSE];
p.nameBody ← Rope.Flatten[ Rope.Substr[fName, 0, bangIndex] ];
p.fullName ← fName;
p.volDesc ← FSFileOps.GetVolumeDesc[volName];
};
IsLocal: PUBLIC PROC [name: ROPE] RETURNS [BOOL] = {
RETURN[ Rope.Length[name]=0 OR Rope.Fetch[name, 0]#'[ OR Rope.Fetch[name, 1]='] ]};
ServerAndFileRopes: PUBLIC PROC [gName: ROPE] RETURNS [server, file: ROPE] = {
closingBracket: CARDINAL = Rope.Index[gName, 0, "]"];
server ← Rope.Substr[gName, 1, closingBracket-1];
file ← Rope.Substr[gName, closingBracket+1];
};
BangStarFile: PUBLIC PROC [file: ROPE] RETURNS [ROPE] = {
length: CARDINAL = Rope.Length[file];
bangIndex: CARDINAL = Rope.Index[file, IF length > 6 THEN length - 6 ELSE 0, "!"];
RETURN [ Rope.Replace [ file, bangIndex, length - bangIndex, "!*" ] ];
};
BangVersionFile: PUBLIC PROC [file: ROPE, version: Version] RETURNS [ROPE] = {
length: CARDINAL = Rope.Length[file];
bangIndex: CARDINAL = Rope.Index[file, IF length > 6 THEN length - 6 ELSE 0, "!"];
RETURN [ Rope.Replace [ file, bangIndex, length - bangIndex, VersionPartFromVersion[version] ] ];
};
VersionFromRope: PUBLIC PROC [r: ROPE] RETURNS [v: Version] = {
IF Rope.Length[r] = 0
THEN v ← FSBackdoor.highestVersion
ELSE [v, ] ← ParseVersion[r, 0, FALSE];
};
VersionPartFromVersion: PUBLIC PROC [version: Version] RETURNS [r: Rope.Text] = {
Decimate: PROC [num: CARDINAL] = {
q, r: CARDINAL;
[q, r] ← Basics.DIVMOD[num, 10];
IF q # 0 THEN Decimate[q];
AppendChar[t, r+'0];
};
t: REF TEXT;
SELECT version FROM
FSBackdoor.lowestVersion, FSBackdoor.highestVersion => r ← NIL;
ENDCASE => TRUSTED {
r ← Rope.NewText[6];
t ← LOOPHOLE[r];
t[0] ← '!;
t.length ← 1;
Decimate[version];
};
};
Internal procedures
NameUse: TYPE = {fName, pattern, directory};
Places: TYPE = RECORD[serverEnd, dirEnd, subDirEnd, bang, rootEnd, firstStar: CARDINAL ← 0];
ExpandAndFindPlaces: PROC [name, wDir: ROPE, nameUse: NameUse] RETURNS [fN: ROPE, p: Places] = {
If an FS.Error is not generated then the full FName is returned as "fN" and the parts of this FName are located by the indexes in "p" according to the following rules (first means left-most, last means right-most, and after means to-the-right-of):
p.serverEnd = index of the "]";
p.dirEnd = IF no ">" THEN p.serverEnd ELSE index of first ">";
p.subDirEnd = IF no ">" THEN p.dirEnd ELSE index of last ">";
p.bang = IF no "!" THEN Rope.Length[fN] ELSE index of first "!";
p.rootEnd = IF no "." after p.subDirEnd THEN p.bang ELSE index of last "." after p.subDirEnd;
p.firstStar = index of the first "*".
Note that all parsing stops when the first "!" is encountered. It is the client's responsibility to verify that the characters following the "!" constitute a valid version part.
i: INT;
leftPoint: BOOLFALSE;
fNLen: CARDINAL ← Rope.Length[name];
fN ← name;
IF fNLen # 0 THEN SELECT Rope.Fetch[fN, 0] FROM
'[ => NULL;
'/ => fN ← ConvertSlashName[fN];
ENDCASE => {
need a working directory
IF Rope.Length[wDir] = 0 THEN {
ref: REF ← List.Assoc[key: $WorkingDirectory, aList: ProcessProps.GetPropList[]];
WITH ref SELECT FROM
wd: ROPE => wDir ← wd;
ENDCASE;
};
IF Rope.Length[wDir] = 0
THEN wDir ← GetDefaultWDir[]
ELSE wDir ← ConvertWDir[wDir];
FOR i IN [1 .. fNLen) DO -- convert the name part if necessary
SELECT Rope.Fetch[fN, i] FROM
'], '<, '>, '! => NULL;
'/ => fN ← ConvertSlashName[fN];
ENDCASE => LOOP;
EXIT;
ENDLOOP;
fN ← Rope.Cat[wDir, fN];
}; -- of need a working directory
fNLen ← Rope.Length[fN];
i ← 0;
DO
c: CHAR ← 0C;
i ← i + 1;
IF i >= fNLen THEN EXIT;
c ← Rope.Fetch[fN, i];
SELECT c FROM
IN ['a .. 'z], IN ['A .. 'Z], IN ['0 .. '9] => {};
'] =>
IF p.serverEnd = 0
THEN p.subDirEnd ← p.dirEnd ← p.serverEnd ← i
ELSE IllegalName[fN, nameUse]; -- this is the second ']
'< =>
IF p.serverEnd = i - 1
THEN leftPoint ← TRUE
ELSE IllegalName[fN, nameUse]; -- '< does not immediately follow ']
'> =>
IF leftPoint AND ( i > p.subDirEnd + 1 )
THEN {
IF p.dirEnd = p.serverEnd
THEN p.dirEnd ← i; -- end of directory part
p.subDirEnd ← i; -- end of subdirectory part
}
ELSE IllegalName[fN, nameUse]; -- '> in the wrong place
'. =>
p.rootEnd ← i;
'! => {
p.bang ← i; EXIT};
'# =>
IF p.serverEnd # 0 THEN {
not in server part
IF p.serverEnd = 1 AND leftPoint AND i = 3
THEN DO -- LName with a max 20 digit hex number as a volume id
i ← i + 1;
IF i >= fNLen THEN EXIT;
IF i > 23 THEN IllegalName[fN, nameUse];
SELECT Rope.Fetch[fN, i] FROM
IN ['0 .. '9], IN ['A .. 'H] => NULL;
ENDCASE => IllegalName[fN, nameUse];
ENDLOOP
ELSE IllegalName[fN, nameUse]; -- sharpSign in the wrong place
};
'* =>
IF nameUse = pattern
THEN { IF p.firstStar = 0 THEN p.firstStar ← i }
ELSE NoPatterns[fN]; -- star and we're not parsing a pattern
ENDCASE => {
valid: Rope.Text = Rope.Flatten[validNameChars];
IF valid # NIL THEN
FOR j: NAT IN [0..valid.length) DO
IF valid[j] = c THEN GO TO legal;
ENDLOOP;
IllegalCharacter[fN];
EXITS legal => {};
};
ENDLOOP;
IF p.serverEnd = 0 THEN IllegalName[fN, nameUse]; -- server part didn't end
IF p.bang = 0 THEN p.bang ← fNLen;
IF p.bang > FS.maxFNameLength - 6 THEN TooLong[fN];
IF p.firstStar = 0
THEN {
not a pattern
IF p.serverEnd = 1 AND nameUse # directory AND p.subDirEnd+1 >= p.bang THEN
LName has no simple name part
IllegalName[fN, nameUse];
}
ELSE {
have a pattern
IF (p.serverEnd = 1 AND p.firstStar < p.dirEnd) OR (p.firstStar < p.serverEnd) THEN
first star is in volume part of LName or in server part
IllegalName[fN, nameUse];
};
IF p.rootEnd <= p.subDirEnd THEN p.rootEnd ← p.bang;
};
ParseVersion: PROC [name: ROPE, index: CARDINAL, pattern: BOOLFALSE] RETURNS [value: Version, vI: FSName.VersionInfo] = {
c: CHAR;
lastIndex: CARDINAL = Rope.Length[name] - 1;
IF lastIndex IN [index .. index+4] THEN SELECT c ← Rope.Fetch[name, index] FROM
IN ['0 .. '9] => {
num: LONG CARDINAL ← 0;
DO
num ← num*10 + (c - '0);
IF index = lastIndex THEN {
IF num NOT IN (FSBackdoor.lowestVersion .. FSBackdoor.highestVersion)
THEN EXIT;
value ← [num];
vI ← number;
RETURN;
};
index ← index + 1;
c ← Rope.Fetch[name, index];
IF c NOT IN ['0 .. '9]
THEN EXIT;
ENDLOOP;
};
'H, 'h =>
IF index = lastIndex THEN {
value ← FSBackdoor.highestVersion; vI ← bangH; RETURN };
'L, 'l =>
IF index = lastIndex THEN {
value ← FSBackdoor.lowestVersion; vI ← bangL; RETURN };
'* =>
IF index = lastIndex AND pattern THEN {
value ← FSBackdoor.highestVersion; vI ← bangStar; RETURN };
ENDCASE;
IllegalVersion[name];
};
QuotedName: PROC [n: ROPE] RETURNS [ROPE] = {
quoteRope: ROPE = "\"";
RETURN [ Rope.Cat[quoteRope, n, quoteRope] ];
};
DirNotLocal: PROC [n: ROPE] = {
FSBackdoor.ProduceError[badWorkingDir, Rope.Concat[QuotedName[n], " is not a local directory."] ] };
NoVolumePart: PROC [n: ROPE] = {
FSBackdoor.ProduceError[badWorkingDir, Rope.Concat[QuotedName[n], " needs a volume part."] ] };
TooLong: PROC [n: ROPE] = {
FSBackdoor.ProduceError[ illegalName, Rope.Concat[QuotedName[n], " has more than 120 characters."] ] };
IllegalCharacter: PROC [n: ROPE] = {
FSBackdoor.ProduceError[ illegalName, Rope.Concat[QuotedName[n], " contains an illegal character."] ] };
IllegalVersion: PROC [n: ROPE] = {
FSBackdoor.ProduceError[ illegalName, Rope.Concat[QuotedName[n], " has an illegal version part."] ] };
IllegalName: PROC [n: ROPE, nameUse: NameUse] = {
e: ROPE = SELECT nameUse FROM
fName => " is not a legal FName.",
pattern => " is not a legal pattern.",
directory => " is not a legal directory name",
ENDCASE => ERROR;
FSBackdoor.ProduceError [illegalName, Rope.Concat[QuotedName[n], e] ];
};
NoPatterns: PROC [n: ROPE] = {
FSBackdoor.ProduceError[ patternNotAllowed, Rope.Concat[QuotedName[n], " contains a \"*\", but patterns are not allow for this operation."] ] };
AppendChar: PROC [text: REF TEXT, c: CHAR] = INLINE {
text[text.length] ← c;
text.length ← text.length + 1;
};
ConvertWDir: PROC [wDir: ROPE] RETURNS [w: ROPE] = {
SELECT Rope.Fetch[wDir, 0] FROM
'[ => {
c: CHAR = Rope.Fetch[wDir, Rope.Length[wDir] - 1];
IF c # '> AND c # '] THEN wDir ← Rope.Cat[wDir, ">"];
w ← wDir;
};
'/ => {
c: CHAR = Rope.Fetch[wDir, Rope.Length[wDir] - 1];
IF c # '/ THEN wDir ← Rope.Cat[wDir, "/"];
w ← ConvertSlashName[wDir];
};
ENDCASE => IllegalName[wDir, directory];
};
ConvertSlashName: PROC [r: ROPE] RETURNS [t: Rope.Text] = TRUSTED {
rL: INT = Rope.Length[r];
text: REF TEXT;
slashCount: CARDINALIF Rope.Fetch[r, 0] = '/ THEN 0 ELSE 2;
t ← Rope.NewText[rL+1];
text ← LOOPHOLE[t];
text.length ← 0;
FOR i: INT IN [0 .. rL) DO
c: CHAR = Rope.Fetch[r, i];
SELECT c FROM
'/ => {
slashCount ← slashCount + 1;
SELECT slashCount FROM
1 => AppendChar[text, '[];
2 => { -- end of server part
AppendChar[text, ']];
FOR j: INT IN [i+1 .. rL) DO -- see if we also need a "<"
SELECT Rope.Fetch[r, j] FROM
'/, '*, '> => {AppendChar[text, '<]; EXIT};
'! => EXIT;
ENDCASE;
ENDLOOP;
};
ENDCASE => AppendChar[text, '>];
};
'* => { -- "*" can hide any number of slashes
IF slashCount < 2 THEN slashCount ← 2;
AppendChar[text, c];
};
ENDCASE => AppendChar[text, c];
ENDLOOP;
};
}.