NFSCrashTestImpl.mesa
Copyright Ó 1989 by Xerox Corporation. All rights reserved.
Bob Hagmann July 6, 1989 8:22:38 am PDT
DIRECTORY
Basics,
BasicTime,
Camelot,
Commander,
CommandTool,
Convert,
File,
FS,
IO,
Mach,
PBasics,
Process,
Random,
RefText,
Rope,
SunMount,
SunNFS,
YggDID,
YggDIDPrivate,
YggDIDMapPrivate,
YggdrasilInit,
YggLock,
YggFixedNames,
YggEnvironment,
YggFile,
YggFileStream,
YggIndexMaint,
YggInternal,
YggMonitoringLog,
YggNFS,
YggTransaction;
NFSCrashTestImpl: CEDAR PROGRAM
IMPORTS Basics, BasicTime, Commander, CommandTool, Convert, IO, Process, Random, RefText, Rope, YggDID, YggFile, YggNFS
EXPORTS YggDID, YggInternal
= BEGIN
ROPE: TYPE = Rope.ROPE;
junk
DID: PUBLIC TYPE ~ REF DIDRep;
DIDRep: PUBLIC TYPE ~ YggDIDPrivate.DIDRep;
Document: TYPE = REF DocumentRep;
DocumentRep: PUBLIC TYPE = YggDIDMapPrivate.DocumentRep;
Global data
sunEpoch: BasicTime.GMT ← BasicTime.Pack[
[year~1970, month~January, day~1, hour~0, minute~0, second~0, zone~0, dst~no]
];
randomStream: Random.RandomStream;
updateCode: TYPE = {createFile, deleteFile, mkdir, rmdir, changeAttributes, link, rename};
UpdateStuff: TYPE = RECORD [
updateType: updateCode,
directory: REF TEXT,
name: REF TEXT,
uid: CARD ← 0,
gid: CARD ← 0,
mode: CARD ← 0,
size: CARD ← 0,
seed: BYTE ← 0,
oldDirectory: REF TEXTNIL,
oldName: REF TEXTNIL
];
FixedSequenceSize: INT = 1;
FixedSequence: ARRAY [0..FixedSequenceSize) OF UpdateStuff ← [
[createFile, "", "fileone", 42, 7, 444B, 1055, 12]
];
KnownDirectoryINodes: LIST OF REF DirectoryINode ← NIL;
NumberOfKnownDirectoryINodess: INT ← 0;
NumberOfRMDirectories: INT ← 0;
FailedToRMDirCount: INT ← 0;
DirectoryStuff: TYPE = RECORD [
directoryName: REF TEXT,
dirINode: REF DirectoryINode
];
DirectoryINode: TYPE = RECORD [
fHandle: SunNFS.FHandle,
inDirectory: LIST OF REF DirectoryINode,
filesInDirectory: LIST OF FileStuff,
directoriesInDirectory: LIST OF DirectoryStuff,
isRoot: BOOLFALSE,
rmOnly: BOOLFALSE
];
KnownFiles: LIST OF FileStuff ← NIL;
NumberOfKnownFiles: INT ← 0;
FileStuff: TYPE = RECORD [
fileName: REF TEXT,
inode: REF INodeStuff
];
INodeStuff: TYPE = RECORD [
fHandle: SunNFS.FHandle,
inDirectory: LIST OF REF DirectoryINode,
contents: REF TEXT,
mode: CARD,
uid: CARD,
gid: CARD
];
MaxFileSize: INT ← 10000;
ReadBuffer: REF TEXT;
VolatilizeTest Command
NFSCrashTestProc: Commander.CommandProc = {
[cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...]
randomIndex: INT ← 0;
replyLookup: SunNFS.DirOpRes;
CheckAFile: PROC [lofs: LIST OF FileStuff] ~ {
reply: SunNFS.AttrStat;
gotAHit: BOOLFALSE;
FOR dirs: LIST OF REF DirectoryINode ← lofs.first.inode.inDirectory, dirs.rest UNTIL dirs = NIL DO
matchedOK: BOOL ← FALSE;
FOR files: LIST OF FileStuff ← dirs.first.filesInDirectory, files.rest UNTIL files = NIL DO
IF files.first.inode = lofs.first.inode AND RefText.Equal[files.first.fileName, lofs.first.fileName] THEN {matchedOK ← TRUE; EXIT;};
REPEAT FINISHED => ERROR;
ENDLOOP;
IF ~matchedOK THEN LOOP;
gotAHit ← TRUE;
replyLookup ← YggNFS.Lookup[[dir: dirs.first.fHandle, name: lofs.first.fileName]];
IF replyLookup.status # ok THEN ERROR;
IF ~RefText.Equal[replyLookup.file, lofs.first.inode.fHandle] THEN ERROR;
ENDLOOP;
IF ~gotAHit THEN ERROR;
reply ← YggNFS.Getattr[lofs.first.inode.fHandle];
IF reply.status # ok THEN ERROR;
IF reply.attributes.mode # lofs.first.inode.mode THEN ERROR;
IF reply.attributes.uid # lofs.first.inode.uid THEN ERROR;
IF reply.attributes.gid # lofs.first.inode.gid THEN ERROR;
IF reply.attributes.size # RefText.Length[lofs.first.inode.contents] THEN ERROR;
SELECT CARD[Basics.BITAND[reply.attributes.mode, YggNFS.typeBits]] FROM
YggNFS.regularModeBits => {
reply ← YggNFS.Read[file: lofs.first.inode.fHandle, offset: 0, count: reply.attributes.size, block: ReadBuffer];
IF reply.status # ok THEN ERROR;
IF ~RefText.Equal[ReadBuffer, lofs.first.inode.contents] THEN ERROR;
};
YggNFS.symbolicLinkModeBits => {
status: SunNFS.Stat;
path: SunNFS.Path;
[status: status, data: path] ← YggNFS.Readlink[file: lofs.first.inode.fHandle];
IF status # ok THEN ERROR;
IF ~RefText.Equal[path, lofs.first.inode.contents] THEN ERROR;
};
ENDCASE => ERROR;
reply ← YggNFS.Read[file: lofs.first.inode.fHandle, offset: 0, count: reply.attributes.size, block: ReadBuffer];
IF reply.status # ok THEN ERROR;
IF ~RefText.Equal[ReadBuffer, lofs.first.inode.contents] THEN ERROR;
};
CheckADirectory: PROC [lodin: LIST OF REF DirectoryINode] ~ {
cookie: SunNFS.Cookie ← NIL;
fileNameList: LIST OF REF TEXTNIL;
files: LIST OF Rope.ROPENIL;
sawPointPoint: BOOLFALSE;
allAtOnceFiles: LIST OF Rope.ROPENIL;
allAtOnceSawPointPoint: BOOLFALSE;
allAtOnceNoOfNFSItems: INT ← 0;
noOfFiles: INT ← 0;
noOfDirs: INT ← 0;
noOfNFSItems: INT ← 0;
FOR files: LIST OF FileStuff ← lodin.first.filesInDirectory, files.rest UNTIL files = NIL DO
CheckAFile[files];
noOfFiles ← noOfFiles + 1;
ENDLOOP;
FOR dirsInDir: LIST OF DirectoryStuff ← lodin.first.directoriesInDirectory, dirsInDir.rest UNTIL dirsInDir = NIL DO
noOfDirs ← noOfDirs + 1;
FOR parentDirs: LIST OF REF DirectoryINode ← dirsInDir.first.dirINode.inDirectory, parentDirs.rest UNTIL parentDirs = NIL DO
IF parentDirs.first = lodin.first THEN EXIT;
REPEAT FINISHED => ERROR;
ENDLOOP;
ENDLOOP;
{
XeachDir: YggNFS.EachDirEntryProc ~ {
PROC [fileid: CARD, filename: -- ephemeral -- FileName]
RETURNS [accept: BOOL, continue: BOOLTRUE];
IF RefText.Equal[s1: filename, s2: "..", case: FALSE] THEN {
allAtOnceSawPointPoint ← TRUE;
RETURN;
};
allAtOnceFiles ← CONS[Rope.FromRefText[filename], allAtOnceFiles];
allAtOnceNoOfNFSItems ← allAtOnceNoOfNFSItems + 1;
};
status: SunNFS.Stat;
eof: BOOLFALSE;
[status: status, eof: eof] ← YggNFS.Readdir[dir: lodin.first.fHandle, cookie: NIL, count: 1000, eachDirEntry: XeachDir];
IF status # ok THEN ERROR;
IF ~eof THEN ERROR;
};
DO
eachDir: YggNFS.EachDirEntryProc ~ {
PROC [fileid: CARD, filename: -- ephemeral -- FileName]
RETURNS [accept: BOOL, continue: BOOLTRUE];
IF RefText.Equal[s1: filename, s2: "..", case: FALSE] THEN {
sawPointPoint ← TRUE;
RETURN;
};
fileNameList ← CONS[filename, fileNameList];
files ← CONS[Rope.FromRefText[filename], files];
noOfNFSItems ← noOfNFSItems + 1;
};
status: SunNFS.Stat;
eof: BOOLFALSE;
[status: status, eof: eof, newCookie: cookie] ← YggNFS.Readdir[dir: lodin.first.fHandle, cookie: cookie, count: RandomChooseInt[randomStream, 1, 5], eachDirEntry: eachDir];
IF status # ok THEN ERROR;
IF eof THEN EXIT;
ENDLOOP;
IF noOfNFSItems # (noOfFiles + noOfDirs) THEN ERROR;
FOR fnl: LIST OF REF TEXT ← fileNameList, fnl.rest UNTIL fnl = NIL DO
foundIt: BOOLFALSE;
FOR files: LIST OF FileStuff ← lodin.first.filesInDirectory, files.rest UNTIL files = NIL DO
IF RefText.Equal[fnl.first, files.first.fileName] THEN {foundIt ← TRUE; EXIT};
ENDLOOP;
IF foundIt THEN LOOP;
FOR dirs: LIST OF DirectoryStuff ← lodin.first.directoriesInDirectory, dirs.rest UNTIL dirs = NIL DO
IF RefText.Equal[fnl.first, dirs.first.directoryName] THEN {foundIt ← TRUE; EXIT};
ENDLOOP;
IF ~foundIt THEN ERROR;
ENDLOOP;
FOR files: LIST OF FileStuff ← lodin.first.filesInDirectory, files.rest UNTIL files = NIL DO
CheckAFile[files];
ENDLOOP;
};
argv: CommandTool.ArgumentVector;
out: IO.STREAM;
noisy: BOOLFALSE;
fixedRandom: BOOLFALSE;
offset: INT ← 0;
noTests: INT;
mntStatus: SunMount.FHStatus;
mountINode: REF DirectoryINode;
startTime: BasicTime.GMT ← BasicTime.Now[];
out ← cmd.out;
argv ← CommandTool.Parse[cmd: cmd ! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
IF argv.argc < 2 THEN GOTO failed;
DO
IF Rope.InlineFetch[argv[offset+1], 0] = '- THEN {
c: CHAR;
IF argv[offset+1].Length[] # 2 THEN GOTO failed;
c ← argv[offset+1].InlineFetch[1] ;
SELECT c FROM
'n => noisy ← TRUE;
ENDCASE;
offset ← offset + 1;
}
ELSE EXIT;
ENDLOOP;
noTests ← Convert.IntFromRope[argv[1+offset] ! Convert.Error => GOTO failed];
IF noTests > FixedSequenceSize THEN GOTO failed;
KnownDirectoryINodes ← NIL;
NumberOfKnownDirectoryINodess ← 0;
NumberOfRMDirectories ← 0;
FailedToRMDirCount ← 0;
KnownFiles ← NIL;
NumberOfKnownFiles ← 0;
IF noTests <= 0 THEN RETURN;
YggdrasilInit.Initialize["bogus", "pooz"];
randomStream ← Random.Create[range: 16384, seed: -1] ;
IF YggNFS.MakeFileSystem[] THEN ERROR;
mntStatus ← YggNFS.Mnt["/"];
IF mntStatus.status # 0 THEN ERROR;
mountINode ← NEW[DirectoryINode ← [mntStatus.directory, NIL, NIL, NIL, TRUE]];
KnownDirectoryINodes ← LIST[mountINode];
FOR loopCount: INT IN [0..noTests) DO
Process.CheckForAbort[];
IF noisy AND (loopCount MOD 100) = 99 THEN {
freePages: INT ← YggFile.ServerInfo[].secondaryBlocksFree;
out.PutF["..."];
PrintTimeOnOut[startTime, out];
out.PutF["(loopCount %g, number of files %g, free pages %g, at %g)...\n", IO.int[loopCount], IO.int[NumberOfKnownFiles], IO.int[freePages], IO.time[]];
};
SELECT FixedSequence[loopCount].updateType FROM
createFile => { -- make new file with a contents
dirINode: REF DirectoryINode;
now: SunNFS.TimeVal;
symbolicLink: BOOLFALSE;
mode: CARD;
uid: CARD;
gid: CARD;
oldFree: INT;
newFileName: REF TEXT;
contents: REF TEXT;
reply: SunNFS.DirOpRes;
replyAttr: SunNFS.AttrStat;
status: SunNFS.Stat;
now ← SunTimeFromGMT[BasicTime.Now[]];
dirINodeFindDirectoryByName[FixedSequence[loopCount].directory];
newFileName ← FixedSequence[loopCount].name;
uid ← FixedSequence[loopCount].uid;
gid ← FixedSequence[loopCount].gid;
oldFree ← YggFile.ServerInfo[].secondaryBlocksFree;
IF RandomChooseInt[randomStream, 0, 6] = 3 THEN {
lookupStat: SunNFS.DirOpRes;
symbolicLink ← TRUE;
mode ← PickMode[] + YggNFS.symbolicLinkModeBits;
contents ← PickAName[NIL];
status ← YggNFS.Symlink[from: [dir: dirINode.fHandle, name: newFileName], to: contents, attributes: [mode: mode, uid: uid, gid: gid, size: RefText.Length[contents], atime: now, mtime: now]];
IF status # ok THEN ERROR;
lookupStat ← YggNFS.Lookup[which: [dir: dirINode.fHandle, name: newFileName]];
IF lookupStat.status # ok THEN ERROR;
reply.file ← lookupStat.file;
}
ELSE {
contents ← MakeUpContents[FixedSequence[loopCount].size, FixedSequence[loopCount].seed];
mode ← FixedSequence[loopCount].mode + YggNFS.regularModeBits;
IF cmd.procData.clientData = $crash THEN {
reply ← YggNFS.Create[where: [dir: dirINode.fHandle, name: newFileName], attributes: [mode: mode, uid: uid, gid: gid, size: RefText.Length[contents], atime: now, mtime: now]];
IF reply.status # ok THEN ERROR;
replyAttr ← YggNFS.Write[file: reply.file, offset: 0, count: RefText.Length[contents], block: contents];
IF replyAttr.status # ok THEN ERROR;
};
};
NumberOfKnownFiles ← NumberOfKnownFiles + 1;
dirINode.filesInDirectory ← CONS[[newFileName, NEW[INodeStuff ← [fHandle: reply.file, inDirectory: LIST[dirINode], contents: contents, mode: mode, uid: uid, gid: gid]]], dirINode.filesInDirectory];
KnownFiles ← CONS[dirINode.filesInDirectory.first, KnownFiles];
IF noisy THEN {
newFree: INT ← 0;
newDID: DID;
preferedPrintName: Rope.ROPE;
IF cmd.procData.clientData = $crash THEN {
newFree ← YggFile.ServerInfo[].secondaryBlocksFree;
newDID ← DIDFromFHandle[reply.file];
};
preferedPrintName ← MakePreferedPrintName[dirINode.filesInDirectory];
SELECT TRUE FROM
cmd.procData.clientData = $crash AND symbolicLink => {
out.PutF["Make new symbolic link: name= %g, did: %g, took %g pages\n", IO.rope[preferedPrintName], IO.card[newDID.didLow], IO.int[oldFree - newFree] ];
};
cmd.procData.clientData = $crash AND ~symbolicLink => {
out.PutF["Make new file: name= %g, did: %g, took %g pages\n", IO.rope[preferedPrintName], IO.card[newDID.didLow], IO.int[oldFree - newFree] ];
};
cmd.procData.clientData = $recoverAndCheck AND symbolicLink => {
out.PutF["Make new symbolic link: name= %g\n", IO.rope[preferedPrintName] ];
};
cmd.procData.clientData = $recoverAndCheck AND ~symbolicLink => {
out.PutF["Make new file: name= %g\n", IO.rope[preferedPrintName] ];
};
ENDCASE;
};
};
deleteFile => { -- delete a file
rand2: INT;
fileCount: INT ← 0;
lodin: LIST OF REF DirectoryINode;
lofiles: LIST OF FileStuff;
allKnownFiles: LIST OF FileStuff ← NIL;
prevAllKnownFiles: LIST OF FileStuff ← NIL;
prevFiles: LIST OF FileStuff ← NIL;
lordin: LIST OF REF DirectoryINode;
prevDir: LIST OF REF DirectoryINode;
dirINode: REF DirectoryINode;
status: SunNFS.Stat;
oldFree: INT;
IF NumberOfKnownFiles = 0 THEN LOOP;
rand2 ← RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess];
FOR lodin ← KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO
rand2 ← rand2 - 1;
IF rand2 <= 0 THEN EXIT;
ENDLOOP;
IF lodin = NIL THEN LOOP;
dirINode ← lodin.first;
FOR lofs: LIST OF FileStuff ← dirINode.filesInDirectory, lofs.rest UNTIL lofs = NIL DO
fileCount ← fileCount + 1;
ENDLOOP;
IF fileCount = 0 THEN LOOP;
rand2 ← RandomChooseInt[randomStream, 0, fileCount-1];
FOR lofiles ← dirINode.filesInDirectory, lofiles.rest UNTIL lofiles = NIL DO
rand2 ← rand2 -1;
IF rand2 <= 0 THEN EXIT;
prevFiles ← lofiles;
ENDLOOP;
IF lofiles = NIL THEN LOOP;
oldFree ← YggFile.ServerInfo[].secondaryBlocksFree;
status ← YggNFS.Remove[which: [dir: dirINode.fHandle, name: lofiles.first.fileName]];
IF status # ok THEN ERROR;
NumberOfKnownFiles ← NumberOfKnownFiles - 1;
FOR allKnownFiles ← KnownFiles, allKnownFiles.rest UNTIL allKnownFiles = NIL DO
IF RefText.Equal[lofiles.first.fileName, allKnownFiles.first.fileName] AND (lofiles.first.inode = allKnownFiles.first.inode) THEN EXIT;
prevAllKnownFiles ← allKnownFiles;
REPEAT FINISHED => ERROR;
ENDLOOP;
IF prevAllKnownFiles = NIL THEN KnownFiles ← allKnownFiles.rest
ELSE prevAllKnownFiles.rest ← allKnownFiles.rest;
IF prevFiles = NIL THEN dirINode.filesInDirectory ← lofiles.rest
ELSE prevFiles.rest ← lofiles.rest;
FOR lordin ← lofiles.first.inode.inDirectory, lordin.rest UNTIL lordin = NIL DO
IF lordin.first = dirINode THEN {
EXIT;
};
prevDir ← lordin;
REPEAT FINISHED => ERROR;
ENDLOOP;
IF prevDir = NIL THEN lofiles.first.inode.inDirectory ← lordin.rest
ELSE prevDir.rest ← lordin.rest;
IF lofiles.first.inode.inDirectory # NIL THEN { -- multi-hard link file has had one of it's links deleted. Does the other one still work?
FOR files: LIST OF FileStuff ← lofiles.first.inode.inDirectory.first.filesInDirectory, files.rest UNTIL files = NIL DO
IF files.first.inode = lofiles.first.inode THEN {
CheckAFile[files];
EXIT;
};
REPEAT FINISHED => ERROR;
ENDLOOP;
CheckADirectory[lofiles.first.inode.inDirectory]; -- check the directory too
};
IF noisy THEN {
newFree: INT ← YggFile.ServerInfo[].secondaryBlocksFree;
out.PutF["Delete file: name= %g in directory name= %g, got %g pages\n", IO.rope[RefText.TrustTextAsRope[lofiles.first.fileName]], IO.rope[MakePreferedDirPrintName[dirINode]], IO.int[newFree - oldFree] ];
};
};
mkdir => { -- make new directory
rand2: INT;
lodin: LIST OF REF DirectoryINode;
now: SunNFS.TimeVal;
mode: CARD;
uid: CARD;
gid: CARD;
oldFree: INT;
newDirName: REF TEXT;
reply: SunNFS.DirOpRes;
now ← SunTimeFromGMT[BasicTime.Now[]];
rand2 ← RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess];
FOR lodin ← KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO
rand2 ← rand2 - 1;
IF rand2 <= 0 AND ~lodin.first.rmOnly THEN EXIT;
ENDLOOP;
IF lodin = NIL THEN LOOP;
IF lodin.first.isRoot AND NumberOfKnownDirectoryINodess # 0 THEN LOOP; -- root directory, and there are others
newDirName ← PickAName[lodin];
mode ← PickMode[];
uid ← PickUID[];
gid ← PickGID[];
oldFree ← YggFile.ServerInfo[].secondaryBlocksFree;
reply ← YggNFS.Mkdir[where: [dir: lodin.first.fHandle, name: newDirName], attributes: [mode: mode, uid: uid, gid: gid, size: 0, atime: now, mtime: now]];
IF reply.status # ok THEN ERROR;
NumberOfKnownDirectoryINodess ← NumberOfKnownDirectoryINodess + 1;
lodin.first.directoriesInDirectory ← CONS[[newDirName, NEW[DirectoryINode ← [fHandle: reply.file, inDirectory: LIST[lodin.first], filesInDirectory: NIL, directoriesInDirectory: NIL, isRoot: FALSE]]], lodin.first.directoriesInDirectory];
KnownDirectoryINodes ← CONS[lodin.first.directoriesInDirectory.first.dirINode, KnownDirectoryINodes];
IF noisy THEN {
newFree: INT ← YggFile.ServerInfo[].secondaryBlocksFree;
newDID: DID ← DIDFromFHandle[reply.file];
preferedPrintName: Rope.ROPE;
preferedPrintName ← MakePreferedDirPrintName[lodin.first.directoriesInDirectory.first.dirINode];
out.PutF["Make new directory: name= %g, did: %g, took %g pages\n", IO.rope[preferedPrintName], IO.card[newDID.didLow], IO.int[oldFree - newFree] ];
};
};
rmdir => { -- remove a directory
lodin: LIST OF REF DirectoryINode;
See if dirs are empty
FOR lodin ← KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO
IF lodin.first.isRoot THEN LOOP;
IF ~lodin.first.rmOnly THEN LOOP;
IF lodin.first.filesInDirectory = NIL AND lodin.first.directoriesInDirectory = NIL THEN {
FailedToRMDirCount ← 0;
RemoveDirectory[lodin, noisy, out];
EXIT;
};
ENDLOOP;
IF lodin # NIL THEN {
LOOP;
};
If it looks like we need another
IF NumberOfRMDirectories * 5 < NumberOfKnownDirectoryINodess OR FailedToRMDirCount > 5 THEN {
FailedToRMDirCount ← 0;
FOR lodin ← KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO
IF lodin.first.isRoot THEN LOOP;
IF lodin.first.rmOnly THEN LOOP;
lodin.first.rmOnly ← TRUE;
IF noisy THEN {
preferedPrintName: Rope.ROPE;
preferedPrintName ← MakePreferedDirPrintName[lodin.first];
out.PutF["Flag directory for RmDir: name= %g\n", IO.rope[preferedPrintName] ];
};
NumberOfRMDirectories ← NumberOfRMDirectories + 1;
EXIT;
ENDLOOP;
}
ELSE {
FailedToRMDirCount ← FailedToRMDirCount + 1;
};
};
changeAttributes => { -- change a file's attrs
rand2: INT;
dirINode: REF DirectoryINode;
prevFiles: LIST OF FileStuff ← NIL;
fileCount: INT ← 0;
lofiles: LIST OF FileStuff;
lodin: LIST OF REF DirectoryINode;
mode: CARD;
uid: CARD;
gid: CARD;
oldFree: INT ← YggFile.ServerInfo[].secondaryBlocksFree;
reply: SunNFS.AttrStat;
IF NumberOfKnownFiles = 0 THEN LOOP;
rand2 ← RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess];
FOR lodin ← KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO
rand2 ← rand2 - 1;
IF rand2 <= 0 THEN EXIT;
ENDLOOP;
IF lodin = NIL THEN LOOP;
dirINode ← lodin.first;
FOR lofs: LIST OF FileStuff ← dirINode.filesInDirectory, lofs.rest UNTIL lofs = NIL DO
fileCount ← fileCount + 1;
ENDLOOP;
IF fileCount = 0 THEN LOOP;
rand2 ← RandomChooseInt[randomStream, 0, fileCount-1];
FOR lofiles ← dirINode.filesInDirectory, lofiles.rest UNTIL lofiles = NIL DO
rand2 ← rand2 -1;
IF rand2 <= 0 THEN EXIT;
prevFiles ← lofiles;
ENDLOOP;
IF lofiles = NIL THEN LOOP;
mode ← PickMode[] + CARD[Basics.BITAND[lofiles.first.inode.mode, YggNFS.typeBits]];
uid ← PickUID[];
gid ← PickGID[];
reply ← YggNFS.Setattr[file: lofiles.first.inode.fHandle, attributes: [mode: mode, uid: uid, gid: gid, size: CARD.LAST, atime: [CARD.LAST, CARD.LAST], mtime: [CARD.LAST, CARD.LAST]]];
IF reply.status # ok THEN ERROR;
lofiles.first.inode.mode ← mode;
lofiles.first.inode.uid ← uid;
lofiles.first.inode.gid ← gid;
IF noisy THEN {
newFree: INT ← YggFile.ServerInfo[].secondaryBlocksFree;
preferedPrintName: Rope.ROPE;
preferedPrintName ← MakePreferedPrintName[lofiles];
out.PutF["Changed attributes on file: name= %g\n", IO.rope[preferedPrintName] ];
IF newFree # oldFree THEN out.PutF[" ---- Change attributes took %g pages\n", IO.int[newFree - oldFree] ];
};
};
link => { -- make a hard link
rand2: INT;
lodin: LIST OF REF DirectoryINode;
loallFileStuff: LIST OF FileStuff;
mode: CARD;
uid: CARD;
gid: CARD;
oldFree: INT ← YggFile.ServerInfo[].secondaryBlocksFree;
newFileName: REF TEXT;
oldPreferedPrintName: Rope.ROPE;
fileFHandle: SunNFS.FHandle;
status: SunNFS.Stat;
IF NumberOfKnownFiles = 0 THEN LOOP;
rand2 ← RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess];
FOR lodin ← KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO
rand2 ← rand2 - 1;
IF rand2 <= 0 AND ~lodin.first.rmOnly THEN EXIT;
ENDLOOP;
IF lodin = NIL THEN LOOP;
IF lodin.first.isRoot AND NumberOfKnownDirectoryINodess # 0 THEN LOOP; -- root directory, and there are others
rand2 ← RandomChooseInt[randomStream, 0, NumberOfKnownFiles-1];
FOR loallFileStuff ← KnownFiles, loallFileStuff.rest UNTIL loallFileStuff = NIL DO
rand2 ← rand2 - 1;
IF rand2 <= 0 AND CARD[Basics.BITAND[loallFileStuff.first.inode.mode, YggNFS.typeBits]] # YggNFS.symbolicLinkModeBits THEN EXIT;
ENDLOOP;
IF loallFileStuff = NIL THEN LOOP;
oldPreferedPrintName ← MakePreferedPrintName[loallFileStuff];
fileFHandle ← loallFileStuff.first.inode.fHandle;
newFileName ← PickAName[lodin];
mode ← PickMode[];
uid ← PickUID[];
gid ← PickGID[];
status ← YggNFS.Link[to: fileFHandle, as: [dir: lodin.first.fHandle, name: newFileName]];
lodin.first.filesInDirectory ← CONS[[newFileName, loallFileStuff.first.inode], lodin.first.filesInDirectory];
loallFileStuff.first.inode.inDirectory ← CONS[lodin.first, loallFileStuff.first.inode.inDirectory];
KnownFiles ← CONS[lodin.first.filesInDirectory.first, KnownFiles];
NumberOfKnownFiles ← NumberOfKnownFiles + 1;
IF noisy THEN {
newFree: INT ← YggFile.ServerInfo[].secondaryBlocksFree;
preferedPrintName: Rope.ROPE;
preferedPrintName ← MakePreferedPrintName[lodin.first.filesInDirectory];
out.PutF["Make new hard link: name= %g (one other name is %g)\n", IO.rope[preferedPrintName], IO.rope[oldPreferedPrintName] ];
IF newFree # oldFree THEN out.PutF[" ---- New hard link took %g pages\n", IO.int[newFree - oldFree] ];
};
};
rename => { -- rename file
rand2: INT;
fileCount: INT ← 0;
fromDir: LIST OF REF DirectoryINode;
lofiles: LIST OF FileStuff;
allKnownFiles: LIST OF FileStuff ← NIL;
prevAllKnownFiles: LIST OF FileStuff ← NIL;
prevFiles: LIST OF FileStuff ← NIL;
lordin: LIST OF REF DirectoryINode;
prevDir: LIST OF REF DirectoryINode;
dirINode: REF DirectoryINode;
status: SunNFS.Stat;
toDir: LIST OF REF DirectoryINode;
now: SunNFS.TimeVal;
symbolicLink: BOOLFALSE;
newFileName: REF TEXT;
oldPreferedPrintName: Rope.ROPE;
oldFree: INT ← YggFile.ServerInfo[].secondaryBlocksFree;
IF NumberOfKnownFiles = 0 THEN LOOP;
rand2 ← RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess];
FOR fromDir ← KnownDirectoryINodes, fromDir.rest UNTIL fromDir = NIL DO
rand2 ← rand2 - 1;
IF rand2 <= 0 THEN EXIT;
ENDLOOP;
IF fromDir = NIL THEN LOOP;
dirINode ← fromDir.first;
FOR lofs: LIST OF FileStuff ← dirINode.filesInDirectory, lofs.rest UNTIL lofs = NIL DO
fileCount ← fileCount + 1;
ENDLOOP;
IF fileCount = 0 THEN LOOP;
rand2 ← RandomChooseInt[randomStream, 0, fileCount-1];
FOR lofiles ← dirINode.filesInDirectory, lofiles.rest UNTIL lofiles = NIL DO
rand2 ← rand2 -1;
IF rand2 <= 0 THEN EXIT;
prevFiles ← lofiles;
ENDLOOP;
IF lofiles = NIL THEN LOOP;
oldPreferedPrintName ← MakePreferedPrintName[lofiles];
lofiles.first is the file to rename
now ← SunTimeFromGMT[BasicTime.Now[]];
rand2 ← RandomChooseInt[randomStream, 0, NumberOfKnownDirectoryINodess];
FOR toDir ← KnownDirectoryINodes, toDir.rest UNTIL toDir = NIL DO
rand2 ← rand2 - 1;
IF rand2 <= 0 AND ~toDir.first.rmOnly THEN EXIT;
ENDLOOP;
IF toDir = NIL THEN LOOP;
IF toDir.first.isRoot AND NumberOfKnownDirectoryINodess # 0 THEN LOOP; -- root directory, and there are others
newFileName ← PickAName[toDir];
status ← YggNFS.Rename[from: [dir: fromDir.first.fHandle, name: lofiles.first.fileName], to: [dir: toDir.first.fHandle, name: newFileName]];
IF status # ok THEN ERROR;
IF prevFiles = NIL THEN dirINode.filesInDirectory ← lofiles.rest
ELSE prevFiles.rest ← lofiles.rest;
FOR lordin ← lofiles.first.inode.inDirectory, lordin.rest UNTIL lordin = NIL DO
IF lordin.first = dirINode THEN {
EXIT;
};
prevDir ← lordin;
REPEAT FINISHED => ERROR;
ENDLOOP;
IF prevDir = NIL THEN lofiles.first.inode.inDirectory ← lordin.rest
ELSE prevDir.rest ← lordin.rest;
toDir.first.filesInDirectory ← CONS[[newFileName, lofiles.first.inode], toDir.first.filesInDirectory];
lofiles.first.inode.inDirectory ← CONS[toDir.first, lofiles.first.inode.inDirectory];
FOR allKnownFiles ← KnownFiles, allKnownFiles.rest UNTIL allKnownFiles = NIL DO
IF RefText.Equal[lofiles.first.fileName, allKnownFiles.first.fileName] AND (lofiles.first.inode = allKnownFiles.first.inode) THEN {
allKnownFiles.first.fileName ← newFileName;
EXIT;
};
REPEAT FINISHED => ERROR;
ENDLOOP;
IF noisy THEN {
newFree: INT ← YggFile.ServerInfo[].secondaryBlocksFree;
newPreferedPrintName: Rope.ROPE;
newPreferedPrintName ← MakePreferedPrintName[toDir.first.filesInDirectory];
out.PutF["Rename: from = %g to = %g\n", IO.rope[oldPreferedPrintName], IO.rope[newPreferedPrintName] ];
IF newFree # oldFree THEN out.PutF[" ---- Rename took %g pages\n", IO.int[newFree - oldFree] ];
};
};
ENDCASE;
ENDLOOP;
IF cmd.procData.clientData = $recoverAndCheck THEN { -- check the computed stuff against the disk stuff
out.PutF["\nCheck everything\n"];
PrintTimeOnOut[startTime, out];
out.PutF["Start Look Up All Old files/directories\n"];
FOR lofs: LIST OF FileStuff ← KnownFiles, lofs.rest UNTIL lofs = NIL DO
CheckAFile[lofs];
ENDLOOP;
FOR lodin: LIST OF REF DirectoryINode ← KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO
CheckADirectory[lodin];
ENDLOOP;
PrintTimeOnOut[startTime, out];
out.PutF["Look Up All Old files/directories Done\n\n"];
}
ELSE out.PutF["\n\n Rollback now!!! \n" ];
EXITS
failed => {result ← $Failure};
};
Utilities
FindDirectoryByName: PROC [name: REF TEXT] RETURNS [REF DirectoryINode ← NIL] ~ {
lodin: LIST OF REF DirectoryINode ← NIL;
IF Rope.IsEmpty[RefText.TrustTextAsRope[name]] THEN {
FOR lodin ← KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO
IF lodin.first.isRoot THEN RETURN [lodin.first];
ENDLOOP;
}
ELSE {
FOR lodin ← KnownDirectoryINodes, lodin.rest UNTIL lodin = NIL DO
FOR dirInDir: LIST OF DirectoryStuff ← lodin.first.directoriesInDirectory, dirInDir.rest UNTIL dirInDir = NIL DO
IF Rope.Equal[RefText.TrustTextAsRope[name], RefText.TrustTextAsRope[dirInDir.first.directoryName]] THEN RETURN[dirInDir.first.dirINode];
ENDLOOP;
ENDLOOP;
};
ERROR;
};
PrintTimeOnOut: PROC [startTime: BasicTime.GMT, out: IO.STREAM] ~ {
now: BasicTime.GMT ← BasicTime.Now[];
delta: INT;
delta ← BasicTime.Period[from: startTime, to: now];
out.PutF["%g seconds: ", IO.int[delta]];
};
RemoveDirectory: PROC [lodin: LIST OF REF DirectoryINode, noisy: BOOL, out: IO.STREAM] ~ {
status: SunNFS.Stat;
oldFree: INT;
preferedPrintName: Rope.ROPE;
prevDir: LIST OF REF DirectoryINode ← NIL;
IF lodin.first.isRoot THEN ERROR;
FOR ll: LIST OF REF DirectoryINode ← KnownDirectoryINodes, ll.rest UNTIL ll = NIL DO
IF ll = lodin THEN EXIT;
prevDir ← ll;
REPEAT FINISHED => ERROR;
ENDLOOP;
oldFree ← YggFile.ServerInfo[].secondaryBlocksFree;
preferedPrintName ← MakePreferedDirPrintName[lodin.first];
IF prevDir = NIL THEN KnownDirectoryINodes ← lodin.rest
ELSE prevDir.rest ← lodin.rest;
FOR parentDirs: LIST OF REF DirectoryINode ← lodin.first.inDirectory, parentDirs.rest UNTIL parentDirs = NIL DO
prevDirInParent: LIST OF DirectoryStuff ← NIL;
FOR nowDirs: LIST OF DirectoryStuff ← parentDirs.first.directoriesInDirectory, nowDirs.rest UNTIL nowDirs = NIL DO
IF nowDirs.first.dirINode = lodin.first THEN {
status ← YggNFS.Rmdir[which: [dir: parentDirs.first.fHandle, name: nowDirs.first.directoryName]];
IF status # ok THEN ERROR;
IF prevDirInParent = NIL THEN parentDirs.first.directoriesInDirectory ← nowDirs.rest
ELSE prevDirInParent.rest ← nowDirs.rest;
EXIT;
};
prevDirInParent ← nowDirs;
REPEAT FINISHED => ERROR;
ENDLOOP;
ENDLOOP;
NumberOfKnownDirectoryINodess ← NumberOfKnownDirectoryINodess - 1;
IF noisy THEN {
newFree: INT ← YggFile.ServerInfo[].secondaryBlocksFree;
out.PutF["Directory RmDired: name= %g, got %g pages\n", IO.rope[preferedPrintName], IO.int[newFree - oldFree] ];
};
};
RandomChooseInt: PROC [rs: Random.RandomStream ← NIL, min: INT ← 0, max: INT] RETURNS [int: INT] ~ {
RETURN[Random.ChooseInt[rs, min, max]];
};
PickAName: PROC [lodin: LIST OF REF DirectoryINode] RETURNS [newFileName: REF TEXT] ~ {
DO
nameLength: INT;
itsADup: BOOLFALSE;
nameLength ← RandomChooseInt[randomStream, 3, 10];
newFileName ← RefText.New[nameLength];
FOR inx: INT IN [0..nameLength) DO
char: CHAR;
char ← VAL[BYTE[ORD['a] + RandomChooseInt[randomStream, 0, 25]]];
[] ← RefText.AppendChar[newFileName, char];
ENDLOOP;
IF lodin # NIL THEN {
FOR lof: LIST OF FileStuff ← lodin.first.filesInDirectory, lof.rest UNTIL lof = NIL DO
IF RefText.Equal[newFileName, lof.first.fileName] THEN {
itsADup ← TRUE;
EXIT;
};
ENDLOOP;
IF itsADup THEN LOOP;
FOR dirs: LIST OF DirectoryStuff ← lodin.first.directoriesInDirectory, dirs.rest UNTIL dirs = NIL DO
IF RefText.Equal[newFileName, dirs.first.directoryName] THEN {
itsADup ← TRUE;
EXIT;
};
ENDLOOP;
IF itsADup THEN LOOP;
};
EXIT;
ENDLOOP;
};
MakePreferedPrintName: PROC [filesInDirectory: LIST OF FileStuff] RETURNS [preferedPrintName: Rope.ROPE] ~ {
preferedPrintName ← Rope.Concat[MakePreferedDirPrintName[filesInDirectory.first.inode.inDirectory.first], RefText.TrustTextAsRope[filesInDirectory.first.fileName]];
};
MakePreferedDirPrintName: PROC [dirINode: REF DirectoryINode] RETURNS [preferedPrintName: Rope.ROPE ← NIL] ~ {
DO
dirStuff: REF DirectoryINode;
IF dirINode.isRoot THEN EXIT;
dirStuff ← dirINode.inDirectory.first;
FOR lodirs: LIST OF DirectoryStuff ← dirStuff.directoriesInDirectory, lodirs.rest UNTIL lodirs = NIL DO
IF lodirs.first.dirINode = dirINode THEN {
preferedPrintName ← Rope.Cat[RefText.TrustTextAsRope[lodirs.first.directoryName], "/", preferedPrintName];
EXIT;
};
REPEAT FINISHED => ERROR;
ENDLOOP;
dirINode ← dirStuff;
ENDLOOP;
preferedPrintName ← Rope.Concat["/", preferedPrintName];
};
MakeUpContents: PROC [size: CARD, seed: BYTE] RETURNS [contents: REF TEXT] ~ {
char: CHARVAL[seed];
contents ← RefText.New[size];
FOR inx: CARD IN [0..size) DO
byte: BYTE;
[] ← RefText.InlineAppendChar[contents, char];
byte ← ORD[char];
IF byte = 255 THEN byte ← 0 ELSE byte ← byte + 1;
char ← VAL[byte];
ENDLOOP;
};
PickUID: PROC RETURNS [uid: CARD] ~ {
uid ← RandomChooseInt[randomStream, 1, 321];
};
PickGID: PROC RETURNS [gid: CARD] ~ {
gid ← RandomChooseInt[randomStream, 1, 42];
};
PickMode: PROC RETURNS [theBits: CARD] ~ {
theBits ← 0;
FOR i: INT IN [0..4) DO
theBits ← theBits * 8;
theBits ← theBits + RandomChooseInt[randomStream, 0, 7];
ENDLOOP;
};
PointerFromRefText: UNSAFE PROC [block: REF READONLY TEXT] RETURNS [LONG POINTER] ~ TRUSTED INLINE {
RETURN[ LOOPHOLE[block, LONG POINTER] + UNITS[TEXT[0]] ] };
DIDFromFHandle: PROC [file: SunNFS.FHandle] RETURNS [did: YggDID.DID] ~ TRUSTED {
did ← YggDID.VolatilizeDID[PointerFromRefText[file]];
};
FHandleFromDID: PROC [did: YggDID.DID] RETURNS [file: SunNFS.FHandle] ~ TRUSTED {
file ← NEW[TEXT[SunNFS.fhSize]];
YggDID.StabilizeDID[did, PointerFromRefText[file]];
};
GMTFromSunTime: PROC [sunTime: SunNFS.TimeVal] RETURNS [gmt: BasicTime.GMT] ~ {
RETURN [BasicTime.Update[sunEpoch, INT[sunTime.seconds]]];
};
SunTimeFromGMT: PROC [gmt: BasicTime.GMT] RETURNS [sunTime: SunNFS.TimeVal] ~ {
RETURN [ [seconds~BasicTime.Period[from~sunEpoch, to~gmt], useconds~0] ];
};
CompareSunTimes: PROC [t1, t2: SunNFS.TimeVal] RETURNS [Basics.Comparison] ~ {
RETURN [SELECT t1.seconds FROM
< t2.seconds => less,
> t2.seconds => greater,
ENDCASE => Basics.CompareCard[t1.useconds, t2.useconds]];
};
Initialization
Init: PROC = {
Commander.Register["NFSCrashTest", NFSCrashTestProc, "NFSCrashTest noTests", $crash];
Commander.Register["NFSCrashTest", NFSCrashTestProc, "NFSCrashTest noTests", $recoverAndCheck];
ReadBuffer ← RefText.New[MaxFileSize];
};
Init[];
END.