SSFS.Mod 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025
  1. MODULE SSFS; (** AUTHOR "staubesv"; PURPOSE "Simple Silly File System"; *)
  2. (*
  3. The Simple Silly File System (SSFS) is a very simple file system the can be useful for educational purposes. It is not supposed to be used in a
  4. productive environment and has many limitations that make it impractical. Nevertheless, it's a good starting point for student exercises.
  5. Volume Layout:
  6. | SuperBlock | Root Directory | Free Block Bitmap | DNodes and INodes (mixed) |
  7. 0 superBlock.rootDirectory superBlock.freeBlockBitMap superBlock.firstDataBlock
  8. The SuperBlock contains information about the file system volume layout. The root directory is a DirectoryBlock, i.e. it contains the mapping of
  9. filenames to Inode numbers.
  10. Note that we mix Inodes and Dnodes in the data section of the file system.
  11. This filesystem is not quite what we teached you in the lecture in that it does not distinct in block sizes in the metadata part and the data section.
  12. It is implemented like that to make the interface much easier: note that for block accesses the filesystem only makes use of the provided Volume object.
  13. *)
  14. IMPORT
  15. SYSTEM,
  16. KernelLog, Commands, Plugins, Dates, Strings, Disks, Files;
  17. CONST
  18. Ok* = 0;
  19. InvalidBlockNumber* = 1000;
  20. InvalidFilePosition* = 1001;
  21. BlockSizeNotSupported* = 2000;
  22. NotFormatted* = 2001;
  23. WrongVersion* = 2002;
  24. VolumeFull* = 3000;
  25. DeviceNotFound* = 4000;
  26. DeviceError* = 4001;
  27. PartitionTooSmall* = 5000;
  28. (* Magic number used to identify our file system *)
  29. SSFS_MagicNumber = LONGINT(99887766H);
  30. SSFS_Version = 01H;
  31. BlockSize = 4096; (* must be multiple of device.blockSize *)
  32. DirectoryEntrySize = 256;
  33. DirectoryEntriesPerBlock = BlockSize DIV DirectoryEntrySize; (* {BlockSize MOD DirectoryEntrySize = 0} *)
  34. (* Offsets of some special blocks *)
  35. Offset_SuperBlock = 0;
  36. BlockNotAllocated = 0;
  37. MinVolumeSize = 5; (* Superblock, Root Directory Block, Free Bitmap (min. 1 Block), 1x I-Node + 1x D-Note *)
  38. BitsPerSET = SIZEOF(SET) * 8;
  39. Trace = FALSE;
  40. TYPE
  41. Block = ARRAY BlockSize OF CHAR;
  42. (* abstract interface to the volume: read and write blocks *)
  43. (* simplification in this simple file system: any blocks are file system blocks *)
  44. Volume = OBJECT
  45. VAR
  46. device : Disks.Device; partition : LONGINT;
  47. nofBlocks : LONGINT;
  48. sectorsPerBlock : LONGINT;
  49. (* read a block as array of character *)
  50. PROCEDURE ReadBlock(blockNumber : LONGINT; VAR block : Block; VAR res : WORD);
  51. BEGIN
  52. IF (0 <= blockNumber) & (blockNumber < nofBlocks) THEN
  53. device.Transfer(Disks.Read, device.table[partition].start + blockNumber * sectorsPerBlock, sectorsPerBlock, block, 0, res);
  54. ELSE
  55. res := InvalidBlockNumber;
  56. END;
  57. END ReadBlock;
  58. (* write a block to disk *)
  59. PROCEDURE WriteBlock(blockNumber : LONGINT; VAR block : Block; VAR res : WORD);
  60. BEGIN
  61. IF (0 <= blockNumber) & (blockNumber < nofBlocks) THEN
  62. device.Transfer(Disks.Write, device.table[partition].start + blockNumber * sectorsPerBlock, sectorsPerBlock, block, 0, res);
  63. ELSE
  64. res := InvalidBlockNumber;
  65. END;
  66. END WriteBlock;
  67. (* finalizer: called when the filesystem goes down. As we do not make use of caches, this only closes the device for the time being *)
  68. PROCEDURE Finalize;
  69. VAR ignore : WORD;
  70. BEGIN
  71. device.Close(ignore);
  72. END Finalize;
  73. (* constructor of Volume: set variables for device, partition and geometry plus some sanity checks *)
  74. PROCEDURE &Init*(device : Disks.Device; partition : LONGINT; VAR res : WORD);
  75. BEGIN
  76. ASSERT((device # NIL) & (device.table # NIL) & (partition < LEN(device.table)));
  77. SELF.device := device; SELF.partition := partition;
  78. IF (BlockSize MOD device.blockSize = 0) THEN
  79. nofBlocks := device.table[partition].size * device.blockSize DIV BlockSize; (* not fully used blocks are truncated *)
  80. IF (nofBlocks >= MinVolumeSize) THEN
  81. sectorsPerBlock := BlockSize DIV device.blockSize;
  82. res := Ok;
  83. IF Trace THEN
  84. KernelLog.String("Volume created on partition "); KernelLog.String(device.name); KernelLog.String("#"); KernelLog.Int(partition, 0);
  85. KernelLog.String(", size: "); KernelLog.Int(nofBlocks, 0); KernelLog.String(" blocks a "); KernelLog.Int(BlockSize, 0);
  86. KernelLog.String(" Bytes"); KernelLog.Ln;
  87. END;
  88. ELSE
  89. res := PartitionTooSmall;
  90. END;
  91. ELSE
  92. res := BlockSizeNotSupported;
  93. END;
  94. END Init;
  95. END Volume;
  96. TYPE
  97. (** This object is NOT thread-safe!!! *)
  98. (* handling of the freeblocks bitmap *)
  99. BlockBitmap = OBJECT
  100. VAR
  101. (* Block usage bitmap: bit set <-> block used *)
  102. bitmap : POINTER TO ARRAY OF SET;
  103. (* Block number of next free block. May be out-of-date *)
  104. hint : LONGINT;
  105. fileSystem : FileSystem;
  106. (** Mark the specified block as free *)
  107. PROCEDURE FreeBlock(blockNumber : LONGINT; VAR res : WORD);
  108. BEGIN {EXCLUSIVE}
  109. IF (0 <= blockNumber) & (blockNumber < fileSystem.volume.nofBlocks) THEN
  110. SetUsed(blockNumber, FALSE);
  111. (* immediately write this back to the bitmap on the volume, iinefficient but ok for this purpose *)
  112. WriteBack(blockNumber, res);
  113. ELSE
  114. res := InvalidBlockNumber;
  115. END;
  116. END FreeBlock;
  117. (** Get the address of the next free block and mark it as used *)
  118. PROCEDURE AllocateBlock(VAR res : WORD) : LONGINT;
  119. VAR blockNumber : LONGINT;
  120. BEGIN {EXCLUSIVE}
  121. blockNumber := FindFreeBlock(res, TRUE);
  122. IF (res = Ok) THEN
  123. SetUsed(blockNumber, TRUE);
  124. (* immediately write this back to the bitmap on the volume *)
  125. WriteBack(blockNumber, res);
  126. END;
  127. RETURN blockNumber;
  128. END AllocateBlock;
  129. (* Find a free block and return its block number. *)
  130. PROCEDURE FindFreeBlock(VAR res : WORD; useHint : BOOLEAN) : LONGINT; (* private *)
  131. VAR blockNumber : LONGINT;
  132. BEGIN
  133. IF useHint & (hint >= fileSystem.superBlock.firstDataBlock) THEN
  134. blockNumber := hint;
  135. ELSE
  136. blockNumber := fileSystem.superBlock.firstDataBlock;
  137. END;
  138. ASSERT(blockNumber >= fileSystem.superBlock.firstDataBlock); (* don't overwrite the file system metadata *)
  139. WHILE (blockNumber < fileSystem.volume.nofBlocks) & IsUsed(blockNumber) DO INC(blockNumber); END;
  140. IF (blockNumber < fileSystem.volume.nofBlocks) THEN
  141. hint := blockNumber + 1;
  142. res := Ok;
  143. ELSE
  144. IF useHint THEN
  145. blockNumber := FindFreeBlock(res, FALSE);
  146. ELSE
  147. res := VolumeFull;
  148. END;
  149. END;
  150. RETURN blockNumber;
  151. END FindFreeBlock;
  152. (** Is the block <blockNumber> in use? *)
  153. PROCEDURE IsUsed(blockNumber : LONGINT) : BOOLEAN; (* private *)
  154. BEGIN
  155. ASSERT((0 <= blockNumber) & (blockNumber < fileSystem.volume.nofBlocks));
  156. RETURN (blockNumber MOD BitsPerSET) IN bitmap[blockNumber DIV BitsPerSET];
  157. END IsUsed;
  158. (* in- or exclude a used bit in the block-bitmap *)
  159. PROCEDURE SetUsed(blockNumber : LONGINT; used : BOOLEAN); (* private *)
  160. BEGIN
  161. ASSERT((0 <= blockNumber) & (blockNumber < fileSystem.volume.nofBlocks));
  162. IF used THEN
  163. ASSERT(~IsUsed(blockNumber));
  164. INCL(bitmap[blockNumber DIV BitsPerSET], blockNumber MOD BitsPerSET);
  165. ELSE
  166. ASSERT(IsUsed(blockNumber));
  167. EXCL(bitmap[blockNumber DIV BitsPerSET], blockNumber MOD BitsPerSET);
  168. END;
  169. END SetUsed;
  170. (* Writes the block of the block bitmap that has changed back to disk *)
  171. PROCEDURE WriteBack(blockNumber : LONGINT; VAR res : WORD); (* private *)
  172. VAR data : Block; blockIdx, index : LONGINT;
  173. BEGIN
  174. ClearBlock(data);
  175. blockIdx := 0;
  176. (* determine the index of the SET that has changed...*)
  177. index := blockNumber DIV BitsPerSET;
  178. (* ... but we need the first index of bitmap of the block in the free block bitmap that has changed *)
  179. index := index - (index MOD (BlockSize DIV BitsPerSET));
  180. ASSERT((0 <= index) & (index < LEN(bitmap)));
  181. WHILE (blockIdx < BlockSize) & (index < LEN(bitmap)) DO
  182. SYSTEM.PUT32(ADDRESSOF(data[blockIdx]), bitmap[index]);
  183. INC(index); INC(blockIdx, SIZEOF(SET));
  184. END;
  185. ASSERT((blockNumber DIV BlockSize) < fileSystem.superBlock.freeBlockBitmapSize); (* write to free block bitmap!! *)
  186. fileSystem.volume.WriteBlock(fileSystem.superBlock.freeBlockBitmapFirst + (blockNumber DIV BlockSize), data, res);
  187. END WriteBack;
  188. (* load all bitmap-blocks from the disk *)
  189. PROCEDURE LoadFromDisk(VAR res : WORD); (* Called only once by FileSystem.Init *)
  190. VAR data : Block; blockNumber, blockIdx, index : LONGINT;
  191. BEGIN
  192. ASSERT(fileSystem.superBlock.freeBlockBitmapSize # 0); (* volume formatted? *)
  193. index := 0;
  194. FOR blockNumber := 0 TO fileSystem.superBlock.freeBlockBitmapSize-1 DO
  195. fileSystem.volume.ReadBlock(fileSystem.superBlock.freeBlockBitmapFirst + blockNumber, data, res);
  196. IF (res = Ok) THEN
  197. blockIdx := 0;
  198. WHILE (blockIdx < BlockSize) & (index < LEN(bitmap)) DO
  199. bitmap[index] := SYSTEM.VAL(SET, SYSTEM.GET32(ADDRESSOF(data[blockIdx])));
  200. INC(index); INC(blockIdx, SIZEOF(SET));
  201. END;
  202. ELSE
  203. RETURN;
  204. END;
  205. END;
  206. IF Trace THEN
  207. KernelLog.String("Loaded bitmap from disk: "); KernelLog.Ln;
  208. Show;
  209. END;
  210. END LoadFromDisk;
  211. (* Displays the bitmap's first 256 entries (debugging) *)
  212. PROCEDURE Show;
  213. VAR i : LONGINT;
  214. BEGIN
  215. FOR i := 0 TO 7 DO
  216. IF (i MOD 2 = 0) THEN KernelLog.Ln; END;
  217. KernelLog.Bits(bitmap[i], 0, 32); KernelLog.String(" ");
  218. END;
  219. KernelLog.Ln;
  220. END Show;
  221. PROCEDURE &Init*(fileSystem : FileSystem);
  222. VAR bitmapSize : LONGINT; i : LONGINT;
  223. BEGIN
  224. ASSERT((fileSystem # NIL) & (fileSystem.volume # NIL));
  225. ASSERT(BlockSize MOD BitsPerSET = 0); (* entries of bitmap must not cross block boundaries *)
  226. SELF.fileSystem := fileSystem;
  227. (* allocate one bit for each file system block (rounded up to BitsPerSET) *)
  228. bitmapSize := (fileSystem.volume.nofBlocks + BitsPerSET-1) DIV BitsPerSET;
  229. NEW(bitmap, bitmapSize);
  230. FOR i := 0 TO LEN(bitmap)-1 DO bitmap[i] := {}; END;
  231. IF Trace THEN
  232. KernelLog.String("Bitmap start: "); KernelLog.Int(fileSystem.superBlock.freeBlockBitmapFirst, 0); KernelLog.Ln;
  233. KernelLog.String("BlockBitmap created, size: "); KernelLog.Int(bitmapSize, 0); KernelLog.String(" entries");
  234. KernelLog.Ln;
  235. Show;
  236. END;
  237. END Init;
  238. END BlockBitmap;
  239. TYPE
  240. (* Our file system has four kinds of blocks... *)
  241. DirectoryEntry = RECORD
  242. name : ARRAY 252 OF CHAR;
  243. inode : LONGINT;
  244. END;
  245. DirectoryBlock = ARRAY DirectoryEntriesPerBlock OF DirectoryEntry;
  246. SuperBlock = RECORD
  247. magicNumber : LONGINT; (* magic number of the file system *)
  248. version : LONGINT; (* SSFS Version *)
  249. rootDirectory : LONGINT; (* cluster that is reserved for the root directory *)
  250. freeBlockBitmapFirst : LONGINT; (* first cluster that is reserved for the bitmap *)
  251. freeBlockBitmapSize : LONGINT; (* number of clusters that are reserved for the bitmap *)
  252. firstDataBlock : LONGINT; (* number of block where the data start *)
  253. filler : ARRAY BlockSize - 6 * 4 OF CHAR; (* unused remainder *)
  254. END;
  255. Inode = RECORD
  256. size : LONGINT; (* file size in bytes *)
  257. attributes : SET; (* attributes, to be defined *)
  258. date, time : LONGINT; (* time and date of modification *)
  259. unused : LONGINT; (* currently unused *)
  260. direct : ARRAY (BlockSize - 5 * 4) DIV 4 OF LONGINT; (* direct links to dnodes *)
  261. END;
  262. Dnode = Block;
  263. TYPE
  264. (* the concrete implementation of our filesystem is done by implementing the methods defined by Files.FileSystem *)
  265. FileSystem = OBJECT(Files.FileSystem)
  266. VAR
  267. volume : Volume; (* volume, the file system is operating on *)
  268. superBlock : SuperBlock; (* superblock meta information, read at startup *)
  269. rootDirectory : DirectoryBlock; (* rootDirectory *)
  270. bitmap : BlockBitmap; (* free blocks bitmap *)
  271. (* constructor: set variables and read super block, check if file system is formatted, res = Ok indicates success *)
  272. PROCEDURE &Init*(volume : Volume; VAR res : WORD);
  273. VAR block : Block;
  274. BEGIN
  275. ASSERT(volume # NIL);
  276. SELF.volume := volume;
  277. COPY("SSFS", desc);
  278. flags := {}; vol := NIL;
  279. volume.ReadBlock(Offset_SuperBlock, block, res);
  280. IF (res = Ok) THEN
  281. superBlock := SYSTEM.VAL(SuperBlock, block);
  282. IF (superBlock.magicNumber = SSFS_MagicNumber) THEN
  283. IF (superBlock.version = SSFS_Version) THEN
  284. volume.ReadBlock(superBlock.rootDirectory, block, res);
  285. IF (res = Ok) THEN
  286. rootDirectory := SYSTEM.VAL(DirectoryBlock, block);
  287. IF (superBlock.freeBlockBitmapFirst # BlockNotAllocated) & (superBlock.freeBlockBitmapSize <= volume.nofBlocks) THEN
  288. NEW(bitmap, SELF);
  289. bitmap.LoadFromDisk(res);
  290. END;
  291. END;
  292. ELSE
  293. res := WrongVersion;
  294. END;
  295. ELSE
  296. res := NotFormatted;
  297. END;
  298. END;
  299. END Init;
  300. (** Create a new file with the specified name. End users use Files.New instead. *)
  301. PROCEDURE New0*(name: ARRAY OF CHAR): Files.File;
  302. VAR
  303. file : File; filename : Files.FileName; fileExists : BOOLEAN;
  304. inodeAdr, index, i : LONGINT; res : WORD; dateTime : Dates.DateTime;
  305. inode : Inode; block : Block;
  306. BEGIN {EXCLUSIVE}
  307. IF Trace THEN KernelLog.String("New: "); KernelLog.String(name); KernelLog.Ln; END;
  308. file := NIL; fileExists := FALSE;
  309. IF GetFilename(name, filename) THEN
  310. index := FindEntry(filename);
  311. IF (index = -1) THEN (* file does not exist yet... find a free directory entry *)
  312. index := FindEntry("");
  313. ELSE
  314. fileExists := TRUE;
  315. END;
  316. IF (index >= 0) THEN
  317. (* first try to allocate a inode for the file *)
  318. IF fileExists THEN
  319. ASSERT(rootDirectory[index].inode # 0);
  320. inodeAdr := rootDirectory[index].inode;
  321. volume.ReadBlock(inodeAdr, block, res);
  322. inode := SYSTEM.VAL(Inode, block);
  323. DeleteDnodes(inode, res);
  324. FOR i := 0 TO LEN(inode.direct)-1 DO inode.direct[i] := BlockNotAllocated; END;
  325. ELSE
  326. inodeAdr := bitmap.AllocateBlock(res);
  327. ClearInode(inode);
  328. END;
  329. IF (res = Ok) THEN
  330. dateTime := Dates.Now();
  331. Dates.DateTimeToOberon(dateTime, inode.date, inode.time);
  332. volume.WriteBlock(inodeAdr, SYSTEM.VAL(Block, inode), res);
  333. IF (res = Ok) THEN
  334. (* create directory entry now *)
  335. COPY(filename, rootDirectory[index].name);
  336. rootDirectory[index].inode := inodeAdr;
  337. volume.WriteBlock(superBlock.rootDirectory, SYSTEM.VAL(Block, rootDirectory), res);
  338. IF (res = Ok) THEN
  339. NEW(file, filename, inode, inodeAdr, SELF);
  340. ELSE
  341. KernelLog.String("Could not write back root directory, res: "); KernelLog.Int(res, 0); KernelLog.Ln;
  342. END;
  343. ELSE
  344. KernelLog.String("Could not write Inode, res: "); KernelLog.Int(res, 0); KernelLog.Ln;
  345. END;
  346. ELSE
  347. KernelLog.String("Could not allocate Inode for file "); KernelLog.String(filename);
  348. KernelLog.String(", res: "); KernelLog.Int(res, 0); KernelLog.Ln;
  349. END;
  350. ELSE
  351. KernelLog.String("Cannot create file "); KernelLog.String(filename); KernelLog.String(": root directory is full.");
  352. KernelLog.Ln;
  353. END;
  354. ELSE
  355. KernelLog.String("Invalid filename: "); KernelLog.String(filename); KernelLog.Ln;
  356. END;
  357. RETURN file;
  358. END New0;
  359. (** Open an existing file. The same file descriptor is returned if a file is opened multiple times. End users use Files.Old instead. *)
  360. PROCEDURE Old0*(name: ARRAY OF CHAR): Files.File;
  361. VAR file : File; filename : Files.FileName; block : Block; inode : Inode; index : LONGINT; res : WORD;
  362. BEGIN {EXCLUSIVE}
  363. IF Trace THEN KernelLog.String("Old: "); KernelLog.String(name); KernelLog.Ln; END;
  364. file := NIL;
  365. IF GetFilename(name, filename) THEN
  366. index := FindEntry(filename);
  367. IF (index >= 0) THEN
  368. ASSERT(rootDirectory[index].inode # 0);
  369. volume.ReadBlock(rootDirectory[index].inode, block, res);
  370. IF (res = Ok) THEN
  371. inode := SYSTEM.VAL(Inode, block);
  372. NEW(file, filename, inode, rootDirectory[index].inode, SELF);
  373. ELSE
  374. KernelLog.String("Could not read Inode for file "); KernelLog.String(filename); KernelLog.String(", res: ");
  375. KernelLog.Int(res, 0); KernelLog.Ln;
  376. END;
  377. END;
  378. ELSE
  379. KernelLog.String("Invalid filename: "); KernelLog.String(filename); KernelLog.Ln;
  380. END;
  381. RETURN file;
  382. END Old0;
  383. (* Called by File.Update. We write back the Inode here since we want the file system to be locked while doing that *)
  384. PROCEDURE UpdateInode(inode : Inode; inodeAdr : LONGINT);
  385. VAR res : WORD;
  386. BEGIN {EXCLUSIVE}
  387. ASSERT(inodeAdr >= superBlock.firstDataBlock);
  388. volume.WriteBlock(inodeAdr, SYSTEM.VAL(Block, inode), res);
  389. IF (res # Ok) THEN
  390. KernelLog.String("Error when writing back Inode of file, res: "); KernelLog.Int(res, 0); KernelLog.Ln;
  391. END;
  392. END UpdateInode;
  393. (* For now, this just removes a path delimiter character at the beginning of the filename *)
  394. PROCEDURE GetFilename(name : ARRAY OF CHAR; VAR filename : ARRAY OF CHAR) : BOOLEAN;
  395. VAR ch : CHAR; i, j : LONGINT;
  396. BEGIN
  397. Strings.TrimWS(name);
  398. ch := name[0];
  399. i := 0; j := 0;
  400. IF (ch = Files.PathDelimiter) THEN
  401. INC(i);
  402. END;
  403. WHILE (i < LEN(name)) & (j < LEN(filename)-1) & (name[i] # 0X) DO
  404. filename[j] := name[i];
  405. INC(i); INC(j);
  406. END;
  407. filename[j] := 0X;
  408. RETURN (filename # "");
  409. END GetFilename;
  410. (** Returns the index of the filename entry in the root directory or -1 if not found *)
  411. PROCEDURE FindEntry(CONST name : ARRAY OF CHAR) : LONGINT;
  412. VAR index : LONGINT;
  413. BEGIN
  414. index := 0;
  415. WHILE (index < LEN(rootDirectory)) & (rootDirectory[index].name # name) DO INC(index); END;
  416. IF (index >= LEN(rootDirectory)) THEN (* file not found *) index := -1; END;
  417. ASSERT((index = -1) OR ((0 <= index) & (index < LEN(rootDirectory))));
  418. RETURN index;
  419. END FindEntry;
  420. (** Delete a file. res = 0 indicates success. End users use Files.Delete instead. *)
  421. PROCEDURE Delete0*(name: ARRAY OF CHAR; VAR key: LONGINT; VAR res: WORD);
  422. VAR filename : Files.FileName; index : LONGINT; block : Block;
  423. BEGIN {EXCLUSIVE}
  424. IF Trace THEN KernelLog.String("Delete: "); KernelLog.String(name); KernelLog.Ln; END;
  425. IF GetFilename(name, filename) THEN
  426. index := FindEntry(filename);
  427. IF (index >= 0) THEN
  428. ASSERT(rootDirectory[index].inode # 0);
  429. volume.ReadBlock(rootDirectory[index].inode, block, res);
  430. IF (res = Ok) THEN
  431. DeleteFile(SYSTEM.VAL(Inode, block), rootDirectory[index].inode, res);
  432. IF (res # Ok) THEN
  433. KernelLog.String("Could not delete Inode or Dnodes"); KernelLog.Ln;
  434. res := -99;
  435. END;
  436. rootDirectory[index].name := "";
  437. rootDirectory[index].inode := 0;
  438. volume.WriteBlock(superBlock.rootDirectory, SYSTEM.VAL(Block, rootDirectory), res);
  439. END;
  440. ELSE
  441. res := Files.FileNotFound;
  442. END;
  443. ELSE
  444. KernelLog.String("Invalid filename: "); KernelLog.String(name); KernelLog.Ln;
  445. res := Files.FileNotFound;
  446. END;
  447. END Delete0;
  448. PROCEDURE DeleteFile(inode : Inode; inodeAdr : LONGINT; VAR res : WORD);
  449. BEGIN
  450. DeleteDnodes(inode, res);
  451. bitmap.FreeBlock(inodeAdr, res);
  452. END DeleteFile;
  453. PROCEDURE DeleteDnodes(inode : Inode; VAR res : WORD);
  454. VAR i : LONGINT; finished : BOOLEAN;
  455. BEGIN
  456. finished := FALSE;
  457. (* mark direct linked blocks as free *)
  458. i := 0;
  459. WHILE ~finished & (i < LEN(inode.direct)) DO
  460. IF (inode.direct[i] # BlockNotAllocated) THEN
  461. bitmap.FreeBlock(inode.direct[i], res);
  462. IF (res # Ok) THEN RETURN; END;
  463. ELSE
  464. finished := TRUE;
  465. END;
  466. INC(i);
  467. END;
  468. END DeleteDnodes;
  469. (** Rename a file. res = 0 indicates success. End users use Files.Rename instead. *)
  470. PROCEDURE Rename0*(old, new: ARRAY OF CHAR; f: Files.File; VAR res: WORD);
  471. BEGIN {EXCLUSIVE}
  472. res := -1; (* not supported *)
  473. END Rename0;
  474. (** Enumerate canonical file names. mask may contain * wildcards. For internal use only. End users use Enumerator instead. *)
  475. PROCEDURE Enumerate0*(mask: ARRAY OF CHAR; flags: SET; enum: Files.Enumerator);
  476. VAR i : LONGINT; block : Block; inode : Inode; name : Files.FileName; attributes : SET; time, date, size: LONGINT; res : WORD;
  477. BEGIN {EXCLUSIVE}
  478. IF( mask # "") THEN
  479. FOR i := 0 TO LEN(rootDirectory)-1 DO
  480. IF Strings.Match(mask, rootDirectory[i].name) THEN
  481. IF (rootDirectory[i].inode # BlockNotAllocated) THEN
  482. IF (flags # {}) THEN (* enumerate attributes *)
  483. volume.ReadBlock(rootDirectory[i].inode, block, res);
  484. IF (res = Ok) THEN
  485. inode := SYSTEM.VAL(Inode, block);
  486. attributes := inode.attributes;
  487. time := inode.time; date := inode.date;
  488. size := inode.size;
  489. ELSE
  490. KernelLog.String("Enumerate0: Could not read block, res: "); KernelLog.Int(res, 0);
  491. KernelLog.Ln;
  492. END;
  493. END;
  494. Files.JoinName(prefix, rootDirectory[i].name, name);
  495. enum.PutEntry(name, attributes, time, date, size);
  496. ELSIF rootDirectory[i].name # "" THEN
  497. KernelLog.String("Enumerate0: entry for file "); KernelLog.String(rootDirectory[i].name);
  498. KernelLog.String(" but file seems to be unallocated."); KernelLog.Ln;
  499. END;
  500. END;
  501. END;
  502. END;
  503. END Enumerate0;
  504. (** Return the unique non-zero key of the named file, if it exists. *)
  505. PROCEDURE FileKey*(name: ARRAY OF CHAR): LONGINT;
  506. VAR key, index : LONGINT;
  507. BEGIN {EXCLUSIVE}
  508. key := 0;
  509. IF (name # "") THEN
  510. index := FindEntry(name);
  511. IF (index >= 0) THEN
  512. key := rootDirectory[index].inode;
  513. END;
  514. END;
  515. RETURN key;
  516. END FileKey;
  517. (** Create a new directory structure. May not be supported by the actual implementation.
  518. End users use Files.CreateDirectory instead.*)
  519. PROCEDURE CreateDirectory0*(name: ARRAY OF CHAR; VAR res: WORD);
  520. BEGIN {EXCLUSIVE}
  521. res := -1; (* not supported *)
  522. END CreateDirectory0;
  523. (** Remove a directory. If force=TRUE, any subdirectories and files should be automatically deleted.
  524. End users use Files.RemoveDirectory instead. *)
  525. PROCEDURE RemoveDirectory0*(name: ARRAY OF CHAR; force: BOOLEAN; VAR key: LONGINT; VAR res: WORD);
  526. BEGIN {EXCLUSIVE}
  527. res := -1; (* not supported *)
  528. END RemoveDirectory0;
  529. (* format a volume for using this file system *)
  530. PROCEDURE Format(VAR res : WORD);
  531. VAR block : Block; i : LONGINT;
  532. BEGIN {EXCLUSIVE}
  533. (* Volume layout - SuperBlock is block 0 *)
  534. superBlock.magicNumber := SSFS_MagicNumber;
  535. superBlock.version := SSFS_Version;
  536. superBlock.rootDirectory := 1;
  537. superBlock.freeBlockBitmapFirst := 2;
  538. (* number of file system blocks for the bitmap incl rounding *)
  539. superBlock.freeBlockBitmapSize := (volume.nofBlocks + BlockSize-1) DIV BlockSize;
  540. superBlock.firstDataBlock := superBlock.freeBlockBitmapFirst + superBlock.freeBlockBitmapSize;
  541. (* some initialization of the unused regions *)
  542. FOR i := 0 TO LEN(superBlock.filler)-1 DO superBlock.filler[i] := 0X; END;
  543. (* now write the superblock *)
  544. volume.WriteBlock(Offset_SuperBlock, SYSTEM.VAL(Block, superBlock), res);
  545. IF Trace THEN
  546. KernelLog.String("Fomat information: "); KernelLog.Ln;
  547. KernelLog.String("SSFS Version: "); KernelLog.Int(superBlock.version, 0); KernelLog.Ln;
  548. KernelLog.String("Root Directory Block: "); KernelLog.Int(superBlock.rootDirectory, 0); KernelLog.Ln;
  549. KernelLog.String("Free Block Bitmap Start: "); KernelLog.Int(superBlock.freeBlockBitmapFirst, 0); KernelLog.Ln;
  550. KernelLog.String("Free Block Bitmap Size: "); KernelLog.Int(superBlock.freeBlockBitmapSize, 0); KernelLog.Ln;
  551. KernelLog.String("First Data Block: "); KernelLog.Int(superBlock.firstDataBlock, 0); KernelLog.Ln;
  552. END;
  553. (* clear directory *)
  554. IF (res = Ok) THEN
  555. ClearBlock(block);
  556. volume.WriteBlock(superBlock.rootDirectory, block, res);
  557. (* Clear the free block bitmap *)
  558. FOR i := 0 TO superBlock.freeBlockBitmapSize-1 DO
  559. volume.WriteBlock(superBlock.freeBlockBitmapFirst + i, block, res);
  560. IF (res # Ok) THEN
  561. RETURN;
  562. END;
  563. END;
  564. END;
  565. END Format;
  566. (* finalizer: called when the filesystem goes down, could be used to close all open files and flush cashes. We do not use any caches in this silly file system *)
  567. PROCEDURE Finalize*;
  568. BEGIN
  569. Finalize^;
  570. volume.Finalize;
  571. END Finalize;
  572. END FileSystem;
  573. TYPE
  574. File = OBJECT(Files.File)
  575. VAR
  576. inode : Inode;
  577. inodeModified : BOOLEAN; (* inode modified? *)
  578. fileSystem : FileSystem;
  579. name : Files.FileName;
  580. PROCEDURE &Init*(CONST name : ARRAY OF CHAR; inode : Inode; inodeAddress : LONGINT; fileSystem : FileSystem);
  581. BEGIN
  582. ASSERT((name # "") & (fileSystem # NIL) & (fileSystem.volume # NIL));
  583. COPY(name, SELF.name);
  584. SELF.inode := inode;
  585. key := inodeAddress;
  586. SELF.fileSystem := fileSystem;
  587. SELF.fs := fileSystem;
  588. END Init;
  589. (** Position a Rider at a certain position in a file. Multiple Riders can be positioned at different locations in a file.
  590. A Rider cannot be positioned beyond the end of a file. *)
  591. PROCEDURE Set*(VAR r: Files.Rider; pos: LONGINT);
  592. BEGIN {EXCLUSIVE}
  593. r.res := Ok; r.eof := FALSE; r.fs := fs; r.file := SELF;
  594. IF (pos < 0) THEN
  595. pos := 0;
  596. ELSIF (pos < inode.size) THEN
  597. r.apos := pos MOD BlockSize; r.bpos := pos DIV BlockSize;
  598. ELSE (* position beyond end of file -> set to end of file *)
  599. r.apos := inode.size MOD BlockSize; r.bpos := inode.size DIV BlockSize;
  600. END;
  601. END Set; (* abstract *)
  602. (** Return the offset of a Rider positioned on a file. *)
  603. PROCEDURE Pos*(VAR r: Files.Rider): LONGINT;
  604. BEGIN {EXCLUSIVE}
  605. ASSERT(r.file = SELF);
  606. RETURN r.apos + BlockSize * r.bpos;
  607. END Pos; (* abstract *)
  608. (** Read a byte from a file, advancing the Rider one byte further. R.eof indicates if the end of the file has been passed. *)
  609. PROCEDURE Read*(VAR r: Files.Rider; VAR x: CHAR);
  610. VAR a: ARRAY 1 OF CHAR;
  611. BEGIN
  612. ReadBytes(r, a, 0, 1); x := a[0];
  613. END Read;
  614. (** Read a sequence of len bytes into the buffer x at offset ofs, advancing the Rider.
  615. Less bytes will be read when reading over the end of the file.
  616. r.res indicates the number of unread bytes. x must be big enough to hold all the bytes. *)
  617. PROCEDURE ReadBytes*(VAR r: Files.Rider; VAR x: ARRAY OF CHAR; ofs, len: LONGINT);
  618. VAR dnode : Dnode; dataLeft, nofBytes, pos : LONGINT; res: WORD; eof : BOOLEAN;
  619. BEGIN {EXCLUSIVE}
  620. ASSERT(r.file = SELF);
  621. ASSERT(LEN(x) >= ofs + len); (* buffer big enough *)
  622. eof := FALSE;
  623. LOOP
  624. IF (len = 0) THEN (* all data read *)
  625. EXIT;
  626. ELSIF (r.bpos < LEN(inode.direct)) & (inode.direct[r.bpos] # BlockNotAllocated) THEN
  627. fileSystem.volume.ReadBlock(inode.direct[r.bpos], dnode, res);
  628. IF (res = Ok) THEN
  629. (* determine the number of bytes to be read from this dnode *)
  630. dataLeft := BlockSize - r.apos; (* data in this dnode starting at offset r.apos *)
  631. IF (len < dataLeft) THEN
  632. nofBytes := len;
  633. ELSE
  634. nofBytes := dataLeft;
  635. END;
  636. (* check against file length *)
  637. pos := r.bpos * BlockSize + r.apos;
  638. IF (pos + nofBytes > inode.size) THEN
  639. nofBytes := inode.size - pos;
  640. IF (nofBytes < 0) THEN nofBytes := 0; END;
  641. eof := TRUE;
  642. END;
  643. SYSTEM.MOVE(ADDRESSOF(dnode[r.apos]), ADDRESSOF(x[ofs]), nofBytes);
  644. len := len - nofBytes;
  645. ofs := ofs + len;
  646. r.apos := (r.apos + nofBytes) MOD BlockSize;
  647. IF (nofBytes = dataLeft) THEN
  648. r.bpos := r.bpos + 1; (* rider positioned at next dnode now *)
  649. END;
  650. IF eof THEN
  651. r.eof := TRUE;
  652. EXIT;
  653. END;
  654. ELSE (* error: could not read dnode *)
  655. r.res := res;
  656. EXIT;
  657. END;
  658. ELSE (* no more dnodes -> end of file reached *)
  659. r.eof := TRUE;
  660. EXIT;
  661. END;
  662. END;
  663. r.res := len;
  664. END ReadBytes;
  665. (** Write a byte into the file at the Rider position, advancing the Rider by one. *)
  666. PROCEDURE Write*(VAR r: Files.Rider; x: CHAR);
  667. VAR a: ARRAY 1 OF CHAR;
  668. BEGIN
  669. a[0] := x; WriteBytes(r, a, 0, 1);
  670. END Write;
  671. (** Write the buffer x containing len bytes (starting at offset ofs) into a file at the Rider position. *)
  672. PROCEDURE WriteBytes*(VAR r: Files.Rider; CONST x: ARRAY OF CHAR; ofs, len: LONGINT);
  673. VAR dnode : Dnode; blockNumber, spaceLeft, nofBytes, pos : LONGINT; res : WORD;
  674. BEGIN {EXCLUSIVE}
  675. ASSERT(r.file = SELF);
  676. ASSERT(r.bpos * BlockSize + r.apos <= inode.size); (* rider not positioned beyond end of file *)
  677. LOOP
  678. IF (len = 0) THEN (* all data written *)
  679. EXIT;
  680. ELSE
  681. ASSERT(r.bpos < LEN(inode.direct)); (* file not getting bigger than maximum file size *)
  682. (* allocate or load dnode we want to write to *)
  683. IF (inode.direct[r.bpos] = BlockNotAllocated) THEN
  684. blockNumber := fileSystem.bitmap.AllocateBlock(res);
  685. IF (res = Ok) THEN
  686. ClearBlock(dnode);
  687. inode.direct[r.bpos] := blockNumber;
  688. inodeModified := TRUE;
  689. ELSE
  690. r.res := res;
  691. EXIT;
  692. END;
  693. ELSE
  694. blockNumber := inode.direct[r.bpos];
  695. fileSystem.volume.ReadBlock(blockNumber, dnode, res);
  696. IF (res # Ok) THEN
  697. r.res := res;
  698. EXIT;
  699. END;
  700. END;
  701. ASSERT(blockNumber >= fileSystem.superBlock.firstDataBlock);
  702. (* determine how much bytes we write to this dnode *)
  703. spaceLeft := BlockSize - r.apos;
  704. IF (len < spaceLeft) THEN
  705. nofBytes := len;
  706. ELSE
  707. nofBytes := spaceLeft;
  708. END;
  709. SYSTEM.MOVE(ADDRESSOF(x[ofs]), ADDRESSOF(dnode[r.apos]), nofBytes);
  710. fileSystem.volume.WriteBlock(blockNumber, dnode, res);
  711. IF (res = Ok) THEN
  712. len := len - nofBytes;
  713. ofs := ofs + nofBytes;
  714. r.apos := (r.apos + nofBytes) MOD BlockSize;
  715. IF (r.apos = 0) THEN
  716. INC(r.bpos);
  717. IF (r.bpos >= LEN(inode.direct)) THEN (* maximum file size reached *)
  718. DEC(r.bpos);
  719. r.eof := TRUE;
  720. KernelLog.String("Maximum file length reached."); KernelLog.Ln;
  721. EXIT;
  722. END;
  723. END;
  724. ELSE
  725. r.res := res;
  726. EXIT;
  727. END;
  728. END;
  729. END;
  730. pos := r.bpos * BlockSize + r.apos;
  731. IF (pos > inode.size) THEN
  732. inode.size := pos;
  733. inodeModified := TRUE;
  734. END;
  735. r.res := len;
  736. END WriteBytes;
  737. (** Return the current length of a file. *)
  738. PROCEDURE Length*(): LONGINT;
  739. BEGIN {EXCLUSIVE}
  740. RETURN inode.size;
  741. END Length;
  742. (** Return the time (t) and date (d) when a file was last modified. *)
  743. PROCEDURE GetDate*(VAR t, d: LONGINT);
  744. BEGIN {EXCLUSIVE}
  745. t := inode.time; d := inode.date;
  746. END GetDate;
  747. (** Set the modification time (t) and date (d) of a file. *)
  748. PROCEDURE SetDate*(t, d: LONGINT);
  749. BEGIN {EXCLUSIVE}
  750. inode.time := t; inode.date := d;
  751. inodeModified := TRUE;
  752. END SetDate;
  753. (** Return the canonical name of a file. *)
  754. PROCEDURE GetName*(VAR name: ARRAY OF CHAR);
  755. BEGIN {EXCLUSIVE}
  756. Files.JoinName(fileSystem.prefix, SELF.name, name)
  757. END GetName;
  758. (** Register a file created with New in the directory, replacing the previous file in the directory with the same name.
  759. The file is automatically updated. End users use Files.Register instead. *)
  760. PROCEDURE Register0*(VAR res: WORD);
  761. BEGIN
  762. Update;
  763. END Register0;
  764. (** Flush the changes made to a file from its buffers. Register0 will automatically update a file. *)
  765. PROCEDURE Update*;
  766. BEGIN {EXCLUSIVE}
  767. IF inodeModified THEN
  768. ASSERT(key >= fileSystem.superBlock.firstDataBlock);
  769. fileSystem.UpdateInode(inode, key);
  770. END;
  771. END Update;
  772. END File;
  773. PROCEDURE ClearBlock(VAR block : Block);
  774. VAR i : LONGINT;
  775. BEGIN
  776. FOR i := 0 TO LEN(block)-1 DO
  777. block[i] := 0X;
  778. END;
  779. END ClearBlock;
  780. PROCEDURE ClearInode(VAR inode : Inode);
  781. VAR i : LONGINT;
  782. BEGIN
  783. inode.size := 0;
  784. inode.attributes := {};
  785. inode.date := 0; inode.time := 0;
  786. FOR i := 0 TO LEN(inode.direct)-1 DO inode.direct[i] := 0; END;
  787. END ClearInode;
  788. PROCEDURE GetFileSystem(context : Commands.Context; VAR res : WORD) : FileSystem;
  789. VAR
  790. devPart, devicename : ARRAY 64 OF CHAR; partition : LONGINT;
  791. device : Disks.Device;
  792. plugin : Plugins.Plugin;
  793. volume : Volume;
  794. fileSystem : FileSystem;
  795. (* Splits up string device#partition into devicename string and partition number *)
  796. PROCEDURE ParseDevPart(CONST devPart : ARRAY OF CHAR; VAR devicename : ARRAY OF CHAR; VAR partition : LONGINT) : BOOLEAN;
  797. VAR stringArray : Strings.StringArray;
  798. BEGIN
  799. stringArray := Strings.Split(devPart, "#");
  800. IF (LEN(stringArray) = 2) THEN
  801. COPY(stringArray[0]^, devicename);
  802. Strings.StrToInt(stringArray[1]^, partition);
  803. RETURN TRUE;
  804. ELSE
  805. RETURN FALSE;
  806. END;
  807. END ParseDevPart;
  808. BEGIN
  809. fileSystem := NIL;
  810. IF context.arg.GetString(devPart) & ParseDevPart(devPart, devicename, partition) THEN
  811. plugin := Disks.registry.Get(devicename);
  812. IF (plugin # NIL) & (plugin IS Disks.Device) THEN
  813. device := plugin (Disks.Device);
  814. device.Open(res);
  815. IF (res = Disks.Ok) THEN
  816. IF (device.table # NIL) & (partition < LEN(device.table)) THEN
  817. IF ~(Disks.Mounted IN device.table[partition].flags) THEN
  818. NEW(volume, device, partition, res);
  819. IF (res = Ok) THEN
  820. NEW(fileSystem, volume, res);
  821. IF (res # Ok) & (res # NotFormatted) THEN
  822. fileSystem := NIL;
  823. context.error.String("Could not mount file system, res: "); context.error.Ln;
  824. END;
  825. ELSE
  826. (* res set by NEW(volume,...) *)
  827. context.error.String("Could not create volume, res: "); context.error.Int(res, 0); context.error.Ln;
  828. END;
  829. ELSE
  830. res := DeviceError;
  831. context.error.String("Partition is already mounted."); context.error.Ln;
  832. END;
  833. ELSE
  834. res := DeviceError;
  835. context.error.String("Partition "); context.error.Int(partition, 0); context.error.String(" not available on device ");
  836. context.error.String(devicename); context.error.Ln;
  837. END;
  838. ELSE
  839. (* res set by device.Open(res) *)
  840. context.error.String("Could not open device "); context.error.String(devicename); context.error.String(", res: ");
  841. context.error.Int(res, 0); context.error.Ln;
  842. END;
  843. ELSE
  844. res := DeviceNotFound;
  845. context.error.String("Device "); context.error.String(devicename); context.error.String(" not found.");
  846. context.error.Ln;
  847. END;
  848. ELSE
  849. res := DeviceNotFound;
  850. context.error.String("Expected device#partition argument."); context.error.Ln;
  851. END;
  852. RETURN fileSystem;
  853. END GetFileSystem;
  854. (** Format the specified disk or partition with the SSFS file system *)
  855. PROCEDURE Format*(context : Commands.Context); (** device#partition ~ *)
  856. VAR fileSystem : FileSystem; res : WORD;
  857. BEGIN
  858. fileSystem := GetFileSystem(context, res);
  859. IF (res = Ok) OR (res = NotFormatted) THEN
  860. fileSystem.Format(res);
  861. fileSystem.Finalize;
  862. IF (res = Ok) THEN
  863. context.out.String("Disk formatted."); context.out.Ln;
  864. ELSE
  865. context.error.String("Formatting disk failed, res: "); context.out.Int(res, 0); context.error.Ln;
  866. END;
  867. END;
  868. END Format;
  869. (** Mount the specified SSFS file system *)
  870. PROCEDURE Mount*(context : Commands.Context); (** prefix device#partition ~*)
  871. VAR prefix : Files.Prefix; fileSystem : FileSystem; res : WORD;
  872. BEGIN
  873. IF context.arg.GetString(prefix) THEN
  874. IF (Files.This(prefix) = NIL) THEN
  875. fileSystem := GetFileSystem(context, res);
  876. IF (res = Ok) THEN
  877. Files.Add(fileSystem, prefix);
  878. context.out.String(prefix); context.out.String(" mounted."); context.out.Ln;
  879. ELSE
  880. (* error message by GetFileSystem procedure *)
  881. END;
  882. ELSE
  883. context.error.String("Prefix "); context.error.String(prefix); context.error.String(" is already used.");
  884. context.error.Ln;
  885. END;
  886. ELSE
  887. context.error.String("Usage: SSFS.Mount prefix device#partition ~"); context.error.Ln;
  888. END;
  889. END Mount;
  890. (** Unmount the specified SSFS file system *)
  891. PROCEDURE Unmount*(context : Commands.Context); (** prefix ~ *)
  892. VAR prefix : Files.Prefix; filesystem : Files.FileSystem;
  893. BEGIN
  894. context.arg.SkipWhitespace; context.arg.String(prefix);
  895. filesystem := Files.This(prefix);
  896. IF (filesystem # NIL) THEN
  897. IF (filesystem IS FileSystem) THEN
  898. IF Trace THEN filesystem(FileSystem).bitmap.Show; END;
  899. Files.Remove(filesystem);
  900. context.out.String(prefix); context.out.String(" ummounted."); context.out.Ln;
  901. ELSE
  902. context.error.String(prefix); context.error.String(" is not a SSFS file system."); context.error.Ln;
  903. END;
  904. ELSE
  905. context.error.String(prefix); context.error.String(" not found"); context.error.Ln;
  906. END
  907. END Unmount;
  908. BEGIN
  909. ASSERT(BlockSize MOD DirectoryEntrySize = 0); (* we don't want that directory entries spawn blocks *)
  910. END SSFS.
  911. System.Free SSFS ~
  912. SSFS.Format Test0#0 ~
  913. SSFS.Mount SSFS Test0#0 ~
  914. SSFS.Unmount SSFS ~
  915. VirtualDisks.Create SSFS.Dsk 8000 512 ~
  916. FSTools.DeleteFiles SSFS.Dsk ~
  917. VirtualDisks.Install Test0 Test.Dsk ~
  918. VirtualDisks.Uninstall Test0 ~