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:
BOOL ←
FALSE] = {
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 FSName
ParseClientName:
PUBLIC
PROC [clientName, wDir:
ROPE, defaultVersionHigh, pattern:
BOOL]
RETURNS [pn: FSName.ParsedFName, vI: FSName.VersionInfo] = {
vName: ROPE ← NIL;
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: BOOL ← FALSE;
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.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:
BOOL ←
FALSE]
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: CARDINAL ← IF 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;
};
}.