FSTools64.Mod 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133
  1. (* Aos, Copyright 2001, Pieter Muller, ETH Zurich *)
  2. MODULE FSTools64; (** AUTHOR "be"; PURPOSE "Files Tools"; *)
  3. (**
  4. * Usage:
  5. *
  6. * FSTools64.Mount prefix alias [volpar] ["|" fspar] ~ Mount the specified volume.
  7. * FSTools64.Unmount prefix [\f] ~ Unmount the specified volume. Use /f to force unmounting.
  8. *
  9. * FSTools64.SetDefault prefix ~ Set the specified volume as default volume.
  10. * FSTools64.Watch ~ Diplays a list of all mounted file systems
  11. *
  12. * FSTools64.CopyFiles [-ioq] {sourcefile " => " destfile} ~ Copy the specified files to
  13. * FSTools64.RenameFiles [-i] {oldname " => " newname} ~ Rename files
  14. * FSTools64.DeleteFiles [-i] {file} ~ Delete the specified files
  15. * FSTools64.Directory [-ts] ~ Show Directory (t: show creation times, s: show file sizes)
  16. *
  17. * FSTools64.Safe ~ disallow pattern matching
  18. * FSTools64.Unsafe ~ allow pattern matching
  19. *
  20. * Options i, o and q:
  21. *
  22. * i: ignore errors, e.g. continue with deletion of files if a file could not be deleted
  23. * o: force overwriting existing files
  24. * q: quiet mode
  25. *
  26. * Examples:
  27. *
  28. * FSTools64.Mount FAT FatFS IDE0#4~
  29. * FSTools64.Unmount FAT~
  30. *
  31. * FSTools64.CopyFiles AOS:Configuration.XML => FAT:Configuration.XML AOS:Test.Mod => FAT:Test.Mod ~
  32. * FSTools64.RenameFiles Configuration.XML => Configuration.Bak ~
  33. * FSTools64.DeleteFiles Test.Mod Bimbo.Mod ~
  34. * FSTools64.Directory -s ~
  35. *
  36. * Pattern matching:
  37. *
  38. * Supported by: CopyFiles, RenameFiles, DeleteFiles and Directory
  39. *
  40. * WARNING: If no prefix is specified, the source mask if checked against all files on all mounted volumes, i.e. the command
  41. * FSTools64.DeleteFiles * ~ would DELETE ALL FILES ON ALL MOUNTED partitions.
  42. *
  43. * The source mask may contain an arbitrary number of '*' (matches any string) and '?' (matches any character) characters.
  44. * For operations that have a target, the target mask semantics is the following:
  45. *
  46. * - '?' characters are not allowed in the target mask
  47. * - '*' characters are not allowed in the prefix and path
  48. * - every occurence of the character '*' is replaced by ...
  49. * ... the source file name if there is no '.' character on the left side of the '*' character
  50. * ... the source file extension if there is at least one '.' character on the left side of the '*' character
  51. *
  52. * Notes:
  53. * - Files treats the right-most '.*' as file extension, e.g. the file extension of 'AosBimbo.Test.00.Bak.Mod' is '.Mod'
  54. *
  55. *)
  56. IMPORT Modules, Commands, Options, Streams, Files := Files64, Configuration, Dates, Strings;
  57. CONST
  58. MaxNameLen = 512; (* Maximum file name length including path and 0X-termination *)
  59. InitialFilelistSize = 1024;
  60. (* Layout for Directory operation *)
  61. Column1 = 30;
  62. FormatDateTime = "dd.mm.yyyy hh:nn:ss";
  63. Error = -1;
  64. CR = 0DX; LF = 0AX;
  65. TYPE
  66. String = Strings.String;
  67. FileList = POINTER TO ARRAY OF String;
  68. EnumProc = PROCEDURE(context : Commands.Context);
  69. VAR
  70. unsafeMode : BOOLEAN;
  71. PROCEDURE ExpandAlias(CONST alias : ARRAY OF CHAR; VAR genvol, genfs: ARRAY OF CHAR);
  72. VAR t: ARRAY 64 OF CHAR; i, j, res: LONGINT;
  73. BEGIN
  74. genvol[0] := 0X; genfs[0] := 0X;
  75. t := "Files.Alias.";
  76. i := 0; WHILE t[i] # 0X DO INC(i) END;
  77. j := 0; WHILE alias[j] # 0X DO t[i] := alias[j]; INC(i); INC(j) END;
  78. t[i] := 0X;
  79. Configuration.Get(t, t, res);
  80. i := 0;
  81. WHILE (t[i] # 0X) & (t[i] # ";") DO genvol[i] := t[i]; INC(i) END;
  82. genvol[i] := 0X;
  83. IF (t[i] = ";") THEN
  84. j := 0; INC(i);
  85. WHILE (t[i] # 0X) DO genfs[j] := t[i]; INC(j); INC(i) END;
  86. genfs[j] := 0X
  87. END
  88. END ExpandAlias;
  89. PROCEDURE GetFileSystemFactory(CONST name : ARRAY OF CHAR; error : Streams.Writer) : Files.FileSystemFactory;
  90. VAR
  91. factory : Files.FileSystemFactory;
  92. moduleName, procedureName : Modules.Name; msg : ARRAY 128 OF CHAR; res : LONGINT;
  93. BEGIN
  94. factory := NIL;
  95. Commands.Split(name, moduleName, procedureName, res, msg);
  96. IF (res = Commands.Ok) THEN
  97. GETPROCEDURE(moduleName, procedureName, factory);
  98. IF factory = NIL THEN
  99. error.String('failed to get file system factory with name "'); error.String(name); error.String('"!'); error.Ln;
  100. END;
  101. ELSE
  102. error.String(msg); error.Ln;
  103. END;
  104. RETURN factory;
  105. END GetFileSystemFactory;
  106. PROCEDURE Mount*(context : Commands.Context); (** prefix alias [volpar] ["|" fspar] ~ *)
  107. VAR
  108. factory : Files.FileSystemFactory;
  109. parvol, parfs: Files.Parameters; i, res: LONGINT;
  110. alias, genvol, genfs : ARRAY 64 OF CHAR; prefix: Files.Prefix;
  111. BEGIN
  112. IF context.arg.GetString(prefix) & context.arg.GetString(alias) THEN
  113. ExpandAlias(alias, genvol, genfs);
  114. IF (Files.This(prefix) # NIL) THEN
  115. context.error.String(prefix); context.error.String("; already used"); context.error.Ln;
  116. ELSIF (genvol = "") OR (genfs = "") THEN
  117. context.error.String(prefix); context.error.String(": unknown alias "); context.error.String(alias); context.error.Ln;
  118. ELSE
  119. IF genvol # "NIL" THEN
  120. NEW(parvol, context.in, context.arg, context.out, context.error, context.caller);
  121. parvol.vol := NIL; res := 0;
  122. COPY(prefix, parvol.prefix);
  123. factory := GetFileSystemFactory(genvol, context.error);
  124. IF (factory # NIL) THEN
  125. factory(parvol);
  126. END;
  127. IF (factory = NIL) OR (parvol.vol = NIL) THEN res := 1; END;
  128. ELSE
  129. i := 0
  130. END;
  131. IF (res = Commands.Ok) THEN
  132. NEW(parfs, context.in, context.arg, context.out, context.error, context.caller);
  133. IF (parvol # NIL) THEN parfs.vol := parvol.vol; ELSE parfs.vol := NIL; END;
  134. COPY(prefix, parfs.prefix);
  135. factory := GetFileSystemFactory(genfs, context.error);
  136. IF (factory # NIL) THEN
  137. factory(parfs);
  138. IF (Files.This(prefix) = NIL) THEN
  139. res := 1
  140. ELSE
  141. context.out.String(prefix); context.out.String(": mounted"); context.out.Ln;
  142. END;
  143. ELSE
  144. res := 1;
  145. END;
  146. IF (res # 0) & (parvol # NIL) & (parvol.vol # NIL) THEN
  147. parvol.vol.Finalize() (* unmount volume *)
  148. END
  149. ELSE
  150. (* skip *)
  151. END
  152. END;
  153. ELSE
  154. context.error.String('Expected parameters: prefix alias ([volpar] ["|" fspar]'); context.error.Ln;
  155. END;
  156. END Mount;
  157. PROCEDURE Unmount*(context : Commands.Context); (** prefix[\f] *)
  158. VAR prefix: Files.Prefix; fs: Files.FileSystem; i: LONGINT; force: BOOLEAN; option : ARRAY 8 OF CHAR; ch : CHAR;
  159. BEGIN
  160. context.arg.SkipWhitespace;
  161. i := 0; ch := context.arg.Peek();
  162. WHILE (i < LEN(prefix)-1) & (ch # ":") & (ch # "\") & (ch > " ") & (context.arg.res = Streams.Ok) DO
  163. context.arg.Char(ch); (* consume ch *)
  164. prefix[i] := ch;
  165. INC(i);
  166. ch := context.arg.Peek();
  167. END;
  168. prefix[i] := 0X;
  169. IF (ch = ":") THEN context.arg.Char(ch); (* consume ":" *) END;
  170. context.arg.SkipWhitespace; context.arg.String(option);
  171. force := option = "\F";
  172. fs := Files.This(prefix);
  173. IF fs # NIL THEN
  174. IF (fs.vol = NIL) OR force OR ~(Files.Boot IN fs.vol.flags) THEN
  175. Files.Remove(fs);
  176. context.out.String(prefix); context.out.Char(":");
  177. context.out.String(" unmounted"); context.out.Ln;
  178. ELSE
  179. context.error.String(prefix); context.error.Char(":");
  180. context.error.String(" can't unmount boot volume. Use \f parameter to force unmounting."); context.error.Ln;
  181. END
  182. ELSE
  183. context.error.String(prefix); context.error.Char(":"); context.error.String(" not found"); context.error.Ln;
  184. END
  185. END Unmount;
  186. PROCEDURE SetDefault*(context : Commands.Context); (** prefix *)
  187. VAR prefix: Files.Prefix; fs: Files.FileSystem; i: LONGINT; ft: Files.FileSystemTable;
  188. BEGIN
  189. context.arg.SkipWhitespace; context.arg.String(prefix);
  190. fs := Files.This(prefix);
  191. IF fs # NIL THEN
  192. Files.Promote(fs);
  193. Files.GetList(ft);
  194. IF ft # NIL THEN
  195. context.out.String("Path: ");
  196. FOR i := 0 TO LEN(ft)-1 DO
  197. context.out.String(ft[i].prefix); context.out.String(" "); context.out.Ln;
  198. END
  199. END
  200. ELSE
  201. context.error.String(prefix); context.error.String(": not found"); context.error.Ln;
  202. END;
  203. END SetDefault;
  204. (* using the NIST standard for Kibi, Mebi & Gibi: http://physics.nist.gov/cuu/Units/binary.html *)
  205. PROCEDURE WriteK( k: HUGEINT; out : Streams.Writer);
  206. VAR suffix: ARRAY 3 OF CHAR;
  207. BEGIN
  208. IF k < 10*1024 THEN COPY("Ki", suffix)
  209. ELSIF k < 10*1024*1024 THEN COPY("Mi", suffix); k := k DIV 1024
  210. ELSE COPY("Gi", suffix); k := k DIV (1024*1024)
  211. END;
  212. out.Int(k, 1); out.String(suffix); out.String("B");
  213. END WriteK;
  214. PROCEDURE Watch*(context : Commands.Context); (** ~ *)
  215. VAR prefix : Files.Prefix; free, total, i: LONGINT; fs: Files.FileSystem; ft: Files.FileSystemTable; found : BOOLEAN;
  216. BEGIN
  217. prefix := "";
  218. context.arg.SkipWhitespace; context.arg.String(prefix);
  219. found := FALSE;
  220. Files.GetList(ft);
  221. IF ft # NIL THEN
  222. FOR i := 0 TO LEN(ft)-1 DO
  223. fs := ft[i];
  224. IF (prefix = "") OR (prefix = fs.prefix) THEN
  225. found := TRUE;
  226. context.out.String(fs.prefix); context.out.String(": "); context.out.String(fs.desc);
  227. IF fs.vol # NIL THEN
  228. context.out.String(" on "); context.out.String(fs.vol.name);
  229. IF Files.ReadOnly IN fs.vol.flags THEN context.out.String(" (read-only)") END;
  230. IF Files.Removable IN fs.vol.flags THEN context.out.String(" (removable)") END;
  231. IF Files.Boot IN fs.vol.flags THEN context.out.String(" (boot)") END;
  232. context.out.Ln; context.out.String(" ");
  233. free := ENTIER(fs.vol.Available()/1024.0D0 * fs.vol.blockSize);
  234. total := ENTIER(fs.vol.size/1024.0D0 * fs.vol.blockSize);
  235. WriteK(free, context.out); context.out.String(" of ");
  236. WriteK(total, context.out); context.out.String(" free")
  237. END;
  238. context.out.Ln
  239. END;
  240. END;
  241. END;
  242. IF ~found THEN
  243. IF (prefix = "") THEN
  244. context.out.String("No file systems found."); context.out.Ln;
  245. ELSE
  246. context.out.String("File system "); context.out.String(prefix); context.out.String(" not found.");
  247. context.out.Ln;
  248. END;
  249. END;
  250. END Watch;
  251. (** File operations *)
  252. (* Simple text formatting (assuming the use of fixed fonts) *)
  253. PROCEDURE Align(out : Streams.Writer; CONST string : ARRAY OF CHAR);
  254. VAR spaces, i : LONGINT;
  255. BEGIN
  256. spaces := Column1 - Strings.Length(string); IF spaces < 0 THEN spaces := 0; END;
  257. FOR i := 0 TO spaces-1 DO out.Char(" "); END;
  258. END Align;
  259. PROCEDURE Directory*(context : Commands.Context); (** [Options] [pattern] *)
  260. VAR
  261. options : Options.Options;
  262. string, pattern : ARRAY 256 OF CHAR;
  263. enum : Files.Enumerator;
  264. flags, fileflags : SET;
  265. count: LONGINT; total : HUGEINT;
  266. time, date: LONGINT; size : HUGEINT;
  267. name : ARRAY MaxNameLen OF CHAR;
  268. dt : Dates.DateTime;
  269. BEGIN
  270. NEW(options);
  271. options.Add("s", "size", Options.Flag);
  272. options.Add("t", "time", Options.Flag);
  273. IF options.Parse(context.arg, context.error) THEN
  274. flags := {};
  275. IF options.GetFlag("size") THEN INCL(flags, Files.EnumSize); END;
  276. IF options.GetFlag("time") THEN INCL(flags, Files.EnumTime); END;
  277. IF ~context.arg.GetString(pattern) THEN
  278. pattern := "";
  279. END;
  280. NEW(enum); enum.Open(pattern, flags);
  281. count := 0; total := 0;
  282. WHILE enum.GetEntry(name, fileflags, time, date, size) DO
  283. INC(count);
  284. context.out.String(name);
  285. IF Files.EnumSize IN flags THEN
  286. Align(context.out, name); context.out.Int(size, 10); context.out.Char("B");
  287. INC(total, size)
  288. END;
  289. IF Files.EnumTime IN flags THEN
  290. IF Files.EnumSize IN flags THEN context.out.String(" "); ELSE Align(context.out, name); END;
  291. dt := Dates.OberonToDateTime(date, time);
  292. Strings.FormatDateTime(FormatDateTime, dt, string);
  293. context.out.String(string);
  294. END;
  295. context.out.Ln;
  296. END;
  297. enum.Close;
  298. IF count > 1 THEN
  299. context.out.Int(count, 0); context.out.String(" files ");
  300. IF Files.EnumSize IN flags THEN
  301. context.out.String("use "); WriteK((total+1023) DIV 1024, context.out);
  302. END
  303. END;
  304. context.out.Ln;
  305. END;
  306. END Directory;
  307. PROCEDURE EnumerateDirectory(
  308. enum : Files.Enumerator;
  309. enumProc : EnumProc;
  310. options : Options.Options;
  311. context : Commands.Context;
  312. CONST filemask : ARRAY OF CHAR;
  313. CONST arguments : ARRAY OF CHAR);
  314. VAR
  315. name : Files.FileName;
  316. flags : SET; time, date: LONGINT; size : HUGEINT;
  317. subDirEnum : Files.Enumerator;
  318. PROCEDURE PrepareContext(context : Commands.Context; CONST currentFile, arguments : ARRAY OF CHAR);
  319. CONST PlaceHolder = "<#filename#>";
  320. VAR thisArguments : Strings.String; position : LONGINT;
  321. BEGIN
  322. NEW(thisArguments, Strings.Length(arguments) + 1024);
  323. COPY(arguments, thisArguments^);
  324. (* replace PlaceHolder string by current file's name *)
  325. position := Strings.Pos(PlaceHolder, arguments);
  326. WHILE (position >= 0) DO
  327. Strings.Delete(thisArguments^, position, Strings.Length(PlaceHolder));
  328. Strings.Insert(name, thisArguments^, position);
  329. position := Strings.Pos(PlaceHolder, thisArguments^);
  330. END;
  331. context.arg(Streams.StringReader).InitStringReader(Strings.Length(thisArguments^));
  332. context.arg(Streams.StringReader).Set(thisArguments^);
  333. END PrepareContext;
  334. BEGIN
  335. ASSERT((enum # NIL) & (enumProc # NIL) & (options # NIL) & (context # NIL));
  336. WHILE enum.GetEntry(name, flags, time, date, size) DO
  337. IF ~(Files.Directory IN flags) & Strings.Match(filemask, name) THEN
  338. PrepareContext(context, name, arguments);
  339. enumProc(context);
  340. context.out.Update;
  341. context.error.Update;
  342. ELSIF options.GetFlag("subdirectories") THEN
  343. IF options.GetFlag("directories") THEN
  344. Strings.Append(name, Files.PathDelimiter);
  345. PrepareContext(context, name, arguments);
  346. enumProc(context);
  347. Strings.Append(name, filemask);
  348. END;
  349. NEW(subDirEnum);
  350. subDirEnum.Open(name, {});
  351. EnumerateDirectory(subDirEnum, enumProc, options, context, filemask, arguments);
  352. subDirEnum.Close;
  353. END;
  354. END;
  355. enum.Close;
  356. END EnumerateDirectory;
  357. PROCEDURE Enumerate*(context : Commands.Context); (** [Options] pattern commandProc ~ *)
  358. VAR
  359. options : Options.Options;
  360. pattern, path, filemask : Files.FileName;
  361. commandProcStr, msg : ARRAY 128 OF CHAR;
  362. arguments : Strings.String;
  363. enumProc : EnumProc;
  364. moduleName, procedureName : Modules.Name;
  365. enum : Files.Enumerator;
  366. enumContext : Commands.Context;
  367. arg : Streams.StringReader;
  368. res : LONGINT;
  369. BEGIN
  370. NEW(options);
  371. options.Add("s", "subdirectories", Options.Flag);
  372. options.Add("d", "directories", Options.Flag);
  373. IF options.Parse(context.arg, context.out) THEN
  374. IF context.arg.GetString(pattern) & context.arg.GetString(commandProcStr) THEN
  375. Commands.Split(commandProcStr, moduleName, procedureName, res, msg);
  376. IF (res = Commands.Ok) THEN
  377. GETPROCEDURE(moduleName, procedureName, enumProc);
  378. IF (enumProc # NIL) THEN
  379. Files.SplitPath(pattern, path, filemask);
  380. NEW(enum);
  381. enum.Open(path, {});
  382. NEW(arg, 4096);
  383. NEW(arguments, context.arg.Available()); Strings.Truncate(arguments^, 0);
  384. context.arg.Bytes(arguments^, 0, context.arg.Available(), res); (* ignore res *)
  385. NEW(enumContext, context.in, arg, context.out, context.error, context.caller);
  386. EnumerateDirectory(enum, enumProc, options, enumContext, filemask, arguments^);
  387. enum.Close;
  388. ELSE
  389. context.out.String("Procedure "); context.out.String(commandProcStr); context.out.String(" not found");
  390. context.out.Ln;
  391. END;
  392. ELSE
  393. context.out.String("Command procedure error, res: "); context.out.Int(res, 0);
  394. context.out.String(" ("); context.out.String(msg); context.out.String(")");
  395. context.out.Ln;
  396. END;
  397. ELSE
  398. context.out.String("FSTools64.Enumerate [Options] pattern ~"); context.out.Ln;
  399. END;
  400. END;
  401. END Enumerate;
  402. (** Create a new file and optionally fill it with content
  403. Option c: Transform <LF> into <CR><LF>
  404. Option r: Remove whitespace at beginning of line
  405. Option a: Append to file instead of creating new file
  406. *)
  407. PROCEDURE CreateFile*(context : Commands.Context); (** [Options] filename [content] ~ *)
  408. VAR
  409. options : Options.Options; cr, removeWhitespace : BOOLEAN;
  410. file : Files.File; filename : Files.FileName; writer : Files.Writer; ch : CHAR; pos: HUGEINT;
  411. BEGIN
  412. NEW(options);
  413. options.Add("c", "cr", Options.Flag);
  414. options.Add("r", "remove", Options.Flag);
  415. options.Add("a", "append", Options.Flag);
  416. IF options.Parse(context.arg, context.out) THEN
  417. IF context.arg.GetString(filename) THEN
  418. cr := options.GetFlag("cr");
  419. removeWhitespace := options.GetFlag("remove");
  420. file := NIL;
  421. IF options.GetFlag("append") THEN
  422. file := Files.Old(filename);
  423. END;
  424. IF file = NIL THEN
  425. file := Files.New(filename);
  426. pos := 0;
  427. ELSE
  428. pos := file.Length();
  429. END;
  430. Files.OpenWriter(writer, file, pos);
  431. IF removeWhitespace THEN context.arg.SkipWhitespace; END;
  432. WHILE (context.arg.res = Streams.Ok) DO
  433. ch := context.arg.Get();
  434. IF (ch = LF) THEN
  435. IF cr THEN writer.Char(CR); END;
  436. IF removeWhitespace THEN context.arg.SkipWhitespace; END;
  437. END;
  438. IF ch # 0X THEN
  439. writer.Char(ch);
  440. END;
  441. END;
  442. writer.Update;
  443. Files.Register(file);
  444. context.out.String("Created file "); context.out.String(filename); context.out.Ln;
  445. ELSE
  446. context.out.String("FSTools64.CreateFile filename [content] ~"); context.out.Ln;
  447. END;
  448. END;
  449. END CreateFile;
  450. PROCEDURE CopyTo*(context : Commands.Context); (** targetpath sourcepath {filename} ~ *)
  451. VAR targetPath, sourcePath, targetFullname, sourceFullname, filename : Files.FileName; overwrite : BOOLEAN; nofFilesCopied, nofErrors, res : LONGINT;
  452. BEGIN
  453. context.arg.SkipWhitespace; context.arg.String(targetPath);
  454. context.arg.SkipWhitespace; context.arg.String(sourcePath);
  455. nofFilesCopied := 0; nofErrors := 0;
  456. WHILE context.arg.GetString(filename) DO
  457. COPY(targetPath, targetFullname); Strings.Append(targetFullname, filename);
  458. COPY(sourcePath, sourceFullname); Strings.Append(sourceFullname, filename);
  459. overwrite := TRUE;
  460. Files.CopyFile(sourceFullname, targetFullname, overwrite, res);
  461. IF (res = Files.Ok) THEN
  462. INC(nofFilesCopied);
  463. ELSE
  464. INC(nofErrors);
  465. context.error.String("Error: Could not copy file "); context.error.String(sourceFullname);
  466. context.error.String(" to "); context.error.String(targetFullname); context.error.String(", res: ");
  467. context.error.Int(res, 0); context.error.Ln;
  468. RETURN;
  469. END;
  470. END;
  471. context.out.Int(nofFilesCopied, 0); context.out.String(" files copied");
  472. IF (nofErrors > 0) THEN
  473. context.out.String(" ("); context.out.Int(nofErrors, 0); context.out.String(" errors)");
  474. END;
  475. context.out.Ln;
  476. END CopyTo;
  477. (** Copy files *)
  478. PROCEDURE CopyFiles*(context : Commands.Context); (** [Options] {source => destination} ~ *)
  479. VAR
  480. source, destination : FileList;
  481. overwritten, error, ignoreErrors, quiet : BOOLEAN;
  482. nofFiles, res, n : LONGINT;
  483. options: Options.Options;
  484. BEGIN
  485. NEW(options);
  486. options.Add("o", "overwrite", Options.Flag); (* overwrite target file if it exists *)
  487. options.Add("i", "ignore", Options.Flag); (* continue on errors *)
  488. options.Add("n", "nolist", Options.Flag); (* only allow two arguments *)
  489. options.Add("q", "quiet", Options.Flag); (* do not print copied file names *)
  490. IF options.Parse(context.arg, context.error) THEN
  491. ignoreErrors := options.GetFlag("ignore");
  492. IF options.GetFlag("nolist") THEN (* source target *)
  493. nofFiles := GetSimpleFileLists(context, source, destination);
  494. ELSE (* {source => target} *)
  495. nofFiles := GetFileLists(context, source, destination);
  496. END;
  497. IF nofFiles # Error THEN
  498. quiet := options.GetFlag("quiet");
  499. IF ~quiet THEN context.out.String("Copying files..."); context.out.Ln; context.out.Update END;
  500. n := 0;
  501. WHILE(n < LEN(source)) & (source[n] # NIL) & (n < LEN(destination)) & (destination[n] # NIL) & (ignoreErrors OR ~error) DO
  502. IF ~quiet THEN
  503. context.out.String(" Copy "); context.out.String(source[n]^); context.out.String(" => ");
  504. context.out.String(destination[n]^); context.out.String(" ... ");
  505. context.out.Update;
  506. END;
  507. overwritten := options.GetFlag("overwrite");
  508. Files.CopyFile(source[n]^, destination[n]^, overwritten, res);
  509. IF res = Files.Ok THEN
  510. IF ~quiet THEN
  511. context.out.String("done");
  512. IF overwritten THEN context.out.String(" (overwritten)"); END;
  513. context.out.Char("."); context.out.Ln;
  514. context.out.Update;
  515. END;
  516. INC(n);
  517. ELSE
  518. IF quiet THEN
  519. context.out.String(" Copy "); context.out.String(source[n]^); context.out.String(" => ");
  520. context.out.String(destination[n]^);
  521. END;
  522. context.error.String("failed "); ShowRes(context.error, res); context.error.Ln;
  523. context.error.Update;
  524. error := TRUE;
  525. END;
  526. END;
  527. END;
  528. IF nofFiles # Error THEN
  529. context.out.Int(n, 0); context.out.String(" of "); context.out.Int(nofFiles, 0); context.out.String(" files copied."); context.out.Ln;
  530. ELSE
  531. context.out.String("No files copied."); context.out.Ln;
  532. END;
  533. END;
  534. END CopyFiles;
  535. PROCEDURE GenerateName(CONST prefix: ARRAY OF CHAR; index: LONGINT; VAR str: ARRAY OF CHAR);
  536. VAR startTime: Dates.DateTime; num: ARRAY 32 OF CHAR;
  537. BEGIN
  538. startTime := Dates.Now();
  539. Strings.FormatDateTime("_yyyymmdd__hhnnss",startTime,str);
  540. Strings.Concat(prefix,str,str);
  541. IF index # 0 THEN
  542. Strings.IntToStr(index,num);
  543. Strings.Append(str,"_");
  544. Strings.Concat(str,num,str);
  545. END;
  546. Strings.Concat(str,".bak",str);
  547. END GenerateName;
  548. PROCEDURE Backup*(context: Commands.Context);
  549. VAR index: LONGINT; fileList: FileList; nofFiles, n, res: LONGINT; str: Files.FileName; overwritten: BOOLEAN;
  550. BEGIN
  551. overwritten := FALSE;
  552. nofFiles := GetFileList(context, fileList);
  553. n := 0;
  554. WHILE (fileList[n] # NIL) DO
  555. index := -1;
  556. REPEAT
  557. INC(index);
  558. GenerateName(fileList[n]^, index, str);
  559. UNTIL Files.Old(str) = NIL;
  560. Files.CopyFile(fileList[n]^, str, overwritten, res);
  561. context.out.String("backed up "); context.out.String(fileList[n]^); context.out.String(" in "); context.out.String(str); context.out.Ln;
  562. ASSERT(~overwritten);
  563. INC(n);
  564. END;
  565. END Backup;
  566. (** Delete files *)
  567. PROCEDURE DeleteFiles*(context : Commands.Context); (** [Options] {file} ~ *)
  568. VAR
  569. filelist : FileList;
  570. error, ignoreErrors, silent : BOOLEAN;
  571. nofFiles, res, n, ndone : LONGINT;
  572. options : Options.Options;
  573. BEGIN
  574. NEW(options);
  575. options.Add("i", "ignore", Options.Flag);
  576. options.Add("s", "silent", Options.Flag);
  577. IF options.Parse(context.arg, context.error) THEN
  578. ignoreErrors := options.GetFlag("ignore");
  579. silent := options.GetFlag("silent");
  580. nofFiles := GetFileList(context, filelist);
  581. IF (nofFiles > 0) THEN
  582. context.out.String("Deleting files..."); context.out.Ln;
  583. n := 0; ndone := 0;
  584. WHILE(filelist[n] # NIL) & (ignoreErrors OR ~error) DO
  585. res := 0;
  586. IF ~silent THEN context.out.String(" Delete "); context.out.String(filelist[n]^); context.out.String(" ... "); context.out.Update; END;
  587. Files.Delete(filelist[n]^, res);
  588. IF res = Files.Ok THEN
  589. IF ~silent THEN context.out.String("done."); context.out.Ln; END;
  590. INC(ndone);
  591. ELSE
  592. IF silent THEN
  593. context.out.String(" Delete "); context.out.String(filelist[n]^); context.out.String(" ... "); context.out.Update;
  594. END;
  595. context.out.String("failed "); ShowRes(context.out, res); context.out.Ln;
  596. error := TRUE;
  597. END;
  598. INC(n);
  599. context.out.Update;
  600. END;
  601. context.out.Int(ndone, 0); context.out.String(" of "); context.out.Int(nofFiles, 0); context.out.String(" files deleted."); context.out.Ln;
  602. ELSIF (nofFiles = 0) THEN
  603. context.out.String("No files matching the mask found."); context.out.Ln;
  604. ELSE
  605. context.error.String("Syntax Error: No files deleted"); context.error.Ln;
  606. END;
  607. END;
  608. END DeleteFiles;
  609. (** Rename files. *)
  610. PROCEDURE RenameFiles*(context : Commands.Context); (** [Options] {source => destination} ~ *)
  611. VAR
  612. source, target : FileList;
  613. error, ignoreErrors : BOOLEAN;
  614. nofFiles, res, n : LONGINT;
  615. options : Options.Options;
  616. BEGIN
  617. NEW(options);
  618. options.Add("i", "ignore", Options.Flag); (* continue on errors *)
  619. options.Add("n", "nolist", Options.Flag);
  620. IF options.Parse(context.arg, context.error) THEN
  621. ignoreErrors := options.GetFlag("ignore");
  622. IF options.GetFlag("nolist") THEN
  623. nofFiles := GetSimpleFileLists(context, source, target);
  624. ELSE
  625. nofFiles := GetFileLists(context, source, target);
  626. END;
  627. IF nofFiles # Error THEN
  628. context.out.String("Renaming files..."); context.out.Ln;
  629. n := 0;
  630. WHILE(source[n] # NIL) & (target[n] # NIL) & (ignoreErrors OR ~error) DO
  631. res := 0;
  632. context.out.String(" Rename "); context.out.String(source[n]^); context.out.String(" => "); context.out.String(target[n]^); context.out.String(" ... ");
  633. Files.Rename(source[n]^, target[n]^, res);
  634. IF res # Files.Ok THEN
  635. context.error.String("failed "); ShowRes(context.error, res); context.error.Ln;
  636. error := TRUE;
  637. ELSE
  638. context.out.String("done."); context.out.Ln;
  639. INC(n);
  640. END;
  641. END;
  642. END;
  643. IF nofFiles # Error THEN
  644. context.out.Int(n, 0); context.out.String(" of "); context.out.Int(nofFiles, 0); context.out.String(" files renamed."); context.out.Ln;
  645. ELSE
  646. context.out.String("No files renamed."); context.out.Ln;
  647. END;
  648. END;
  649. END RenameFiles;
  650. PROCEDURE CreateDirectory*(context : Commands.Context); (* path ~ *)
  651. VAR path : Files.FileName; res : LONGINT;
  652. BEGIN
  653. IF context.arg.GetString(path) THEN
  654. Files.CreateDirectory(path, res);
  655. IF (res # Files.Ok) THEN
  656. context.out.String("Could not create directory '"); context.out.String(path); context.out.String("', res: ");
  657. ShowRes(context.out, res); context.out.Ln;
  658. END;
  659. ELSE
  660. context.out.String("Usage: FSTools64.CreateDirectory <path> ~"); context.out.Ln;
  661. END;
  662. END CreateDirectory;
  663. PROCEDURE DeleteDirectory*(context : Commands.Context); (* path ~ *)
  664. VAR path : Files.FileName; res : LONGINT;
  665. BEGIN
  666. IF context.arg.GetString(path) THEN
  667. Files.RemoveDirectory(path, FALSE, res);
  668. IF (res # Files.Ok) THEN
  669. context.out.String("Could not delete directory '"); context.out.String(path); context.out.String("', res: ");
  670. ShowRes(context.out, res); context.out.Ln;
  671. END;
  672. ELSE
  673. context.out.String("Usage: FSTools64.DeleteDirectory <path> ~"); context.out.Ln;
  674. END;
  675. END DeleteDirectory;
  676. (** Compare filenames of two directories and display files that are not present in both directories *)
  677. PROCEDURE CompareDirectories*(context : Commands.Context); (** directory1 directory2 ~ *)
  678. VAR
  679. fileList1, fileList2 : FileList;
  680. length1, length2 : LONGINT;
  681. dirname1, dirname2 : Files.FileName;
  682. index1, index2 : LONGINT;
  683. differences : LONGINT;
  684. PROCEDURE GetSortedFileList(CONST dirname : ARRAY OF CHAR; VAR index : LONGINT) : FileList;
  685. VAR mask : Files.FileName; fileList : FileList;
  686. BEGIN
  687. COPY(dirname, mask);
  688. Strings.Append(mask, Files.PathDelimiter);
  689. Strings.Append(mask, "*");
  690. NEW(fileList, 128);
  691. InsertFiles(mask, fileList, index);
  692. IF (index > 0) THEN SortFileList(fileList, index); END;
  693. ASSERT(fileList # NIL);
  694. RETURN fileList;
  695. END GetSortedFileList;
  696. PROCEDURE CompareEntries(CONST entry1, entry2 : ARRAY OF CHAR) : LONGINT;
  697. VAR result : LONGINT; prefix : Files.Prefix; filename1, filename2, pathname, path : Files.FileName;
  698. BEGIN
  699. Files.SplitName(entry1, prefix, pathname);
  700. Files.SplitPath(pathname, path, filename1);
  701. Files.SplitName(entry2, prefix, pathname);
  702. Files.SplitPath(pathname, path, filename2);
  703. IF (filename1 < filename2) THEN result := -1;
  704. ELSIF (filename1 > filename2) THEN result := 1;
  705. ELSE result := 0;
  706. END;
  707. RETURN result;
  708. END CompareEntries;
  709. BEGIN
  710. context.arg.SkipWhitespace; context.arg.String(dirname1);
  711. context.arg.SkipWhitespace; context.arg.String(dirname2);
  712. differences := 0;
  713. length1 := 0;
  714. fileList1 := GetSortedFileList(dirname1, length1);
  715. length2 := 0;
  716. fileList2 := GetSortedFileList(dirname2, length2);
  717. context.out.String(dirname1); context.out.String(": "); context.out.Int(length1, 0); context.out.String(" entries"); context.out.Ln;
  718. context.out.String(dirname2); context.out.String(": "); context.out.Int(length2, 0); context.out.String(" entries"); context.out.Ln;
  719. index1 := 0; index2 := 0;
  720. WHILE (index1 < length1) DO
  721. WHILE (index2 < length2) & (CompareEntries(fileList1[index1]^, fileList2[index2]^) > 0) DO
  722. context.out.String(fileList2[index2]^); context.out.Ln;
  723. INC(differences);
  724. INC(index2);
  725. END;
  726. IF (index2 < length2) & (CompareEntries(fileList1[index1]^, fileList2[index2]^) = 0)THEN
  727. INC(index2);
  728. ELSE
  729. INC(differences);
  730. context.out.String(fileList1[index1]^); context.out.Ln;
  731. END;
  732. INC(index1);
  733. END;
  734. WHILE (index2 < length2) DO
  735. context.out.String(fileList2[index2]^); context.out.Ln;
  736. INC(differences);
  737. INC(index2);
  738. END;
  739. IF (differences = 0) THEN
  740. context.out.String("Directories contain the same entries"); context.out.Ln;
  741. END;
  742. END CompareDirectories;
  743. (** Compare two files by byte-wise comparison of contents *)
  744. PROCEDURE CompareFiles*(context : Commands.Context); (* filename1 filename2 ~ *)
  745. VAR filename : Files.FileName; file1, file2 : Files.File; reader1, reader2 : Files.Reader; ch1, ch2 : CHAR;
  746. BEGIN
  747. context.arg.SkipWhitespace; context.arg.String(filename);
  748. file1 := Files.Old(filename);
  749. IF (file1# NIL) THEN
  750. context.arg.SkipWhitespace; context.arg.String(filename);
  751. file2 := Files.Old(filename);
  752. IF (file2 # NIL) THEN
  753. IF (file1.Length() = file2.Length()) THEN
  754. NEW(reader1, file1, 0);
  755. NEW(reader2, file2, 0);
  756. REPEAT
  757. reader1.Char(ch1);
  758. reader2.Char(ch2);
  759. UNTIL (ch1 # ch2) OR (reader1.res # Files.Ok) OR (reader2.res # Files.Ok);
  760. IF (ch1 = ch2) & (reader1.res = reader2.res) & (reader1.res = Streams.EOF) THEN
  761. context.out.String("Files are equal"); context.out.Ln;
  762. ELSE
  763. context.out.String("Content mismatch"); context.out.Ln;
  764. END;
  765. ELSE
  766. context.out.String("Length mismatch"); context.out.Ln;
  767. END;
  768. ELSE
  769. context.error.String("File "); context.error.String(filename); context.error.String(" not found");
  770. context.error.Ln;
  771. END;
  772. ELSE
  773. context.error.String("File "); context.error.String(filename); context.error.String(" not found");
  774. context.error.Ln;
  775. END;
  776. END CompareFiles;
  777. PROCEDURE SortFileList(filelist : FileList; length : LONGINT );
  778. VAR i, j : LONGINT; temp : Strings.String;
  779. BEGIN
  780. (* bubble sort *)
  781. FOR i := 0 TO length-1 DO
  782. FOR j := 0 TO length-2 DO
  783. IF filelist[j]^ > filelist[j+1]^ THEN
  784. temp := filelist[j+1];
  785. filelist[j+1] := filelist[j];
  786. filelist[j] := temp;
  787. END;
  788. END;
  789. END;
  790. END SortFileList;
  791. PROCEDURE ResizeFilelist(VAR filelist : FileList);
  792. VAR temp : FileList; i : LONGINT;
  793. BEGIN
  794. NEW(temp, 2 * LEN(filelist));
  795. FOR i := 0 TO LEN(filelist)-1 DO
  796. temp[i] := filelist[i];
  797. END;
  798. filelist := temp;
  799. END ResizeFilelist;
  800. (* Checks whether a file list entry contains mask characters and adds the corresponding files if it does *)
  801. PROCEDURE InsertFiles(CONST mask : ARRAY OF CHAR; VAR filelist : FileList; VAR index : LONGINT);
  802. VAR
  803. enum : Files.Enumerator;
  804. fileflags : SET;
  805. time, date: LONGINT; size : HUGEINT;
  806. name : ARRAY MaxNameLen OF CHAR;
  807. BEGIN
  808. NEW(enum); enum.Open(mask, {});
  809. WHILE enum.GetEntry(name, fileflags, time, date, size) DO
  810. IF (fileflags * {Files.Directory} = {}) THEN
  811. IF index >= LEN(filelist) THEN ResizeFilelist(filelist); END;
  812. filelist[index] := Strings.NewString(name);
  813. INC(index);
  814. END;
  815. END;
  816. enum.Close;
  817. END InsertFiles;
  818. (* Count the number of occurences of the character 'ch' in the string 'string'. Case-Sensitive! *)
  819. PROCEDURE CountCharacters(CONST string : ARRAY OF CHAR; ch : CHAR) : LONGINT;
  820. VAR count, i : LONGINT;
  821. BEGIN
  822. count := 0;
  823. FOR i := 0 TO LEN(string)-1 DO
  824. IF string[i] = ch THEN INC(count); END;
  825. END;
  826. RETURN count;
  827. END CountCharacters;
  828. (* Split full name into prefix, path, filename and file extension *)
  829. PROCEDURE SplitFullName(CONST fullname : ARRAY OF CHAR; VAR prefix, path, filename, extension : ARRAY OF CHAR);
  830. VAR pathname, name : ARRAY 1024 OF CHAR;
  831. BEGIN
  832. Files.SplitName(fullname, prefix, pathname);
  833. Files.SplitPath(pathname, path, name);
  834. Files.SplitExtension(name, filename, extension);
  835. END SplitFullName;
  836. PROCEDURE IsValidTargetMask(context : Commands.Context; CONST mask : ARRAY OF CHAR) : BOOLEAN;
  837. VAR
  838. prefix : ARRAY Files.PrefixLength OF CHAR;
  839. filename, extension : ARRAY Files.NameLength OF CHAR;
  840. path : ARRAY 512 OF CHAR;
  841. BEGIN
  842. SplitFullName(mask, prefix, path, filename, extension);
  843. IF (CountCharacters(mask, "?") > 0) THEN
  844. context.error.String("Syntax Error in "); context.error.String(mask); context.error.String(": '?' matching characters not implemented for target mask"); context.error.Ln;
  845. RETURN FALSE;
  846. END;
  847. IF (CountCharacters(prefix, "*") # 0) OR (CountCharacters(path, "*") # 0) THEN
  848. context.error.String("Syntax Error in "); context.error.String(mask); context.error.String(": Target prefix/path may not contain '*' characters"); context.error.Ln;
  849. RETURN FALSE;
  850. END;
  851. RETURN TRUE;
  852. END IsValidTargetMask;
  853. (* If the user does not specify a prefix or path for a mask, the mask will include all directories and subdirectories.
  854. Since this is too dangerous for file operations as delete, we only allow pattern operations if a prefix
  855. or path is specified within the pattern or the unsafe mode is set *)
  856. PROCEDURE AllowMaskInSafeMode(CONST mask : ARRAY OF CHAR) : BOOLEAN;
  857. VAR prefix : Files.Prefix; pathname, path, filename : Files.FileName;
  858. BEGIN
  859. Files.SplitName(mask, prefix, pathname);
  860. Files.SplitPath(pathname, path, filename);
  861. RETURN (prefix # "") OR ((path # "") & (path # Files.PathDelimiter));
  862. END AllowMaskInSafeMode;
  863. PROCEDURE GetTargetName(CONST sourceMask, targetMask, sourceName : ARRAY OF CHAR) : String;
  864. VAR
  865. targetName : ARRAY 1024 OF CHAR;
  866. srcPrefix, srcPath, srcFilename, srcExtension : ARRAY 512 OF CHAR;
  867. isExtension : BOOLEAN;
  868. i, j, index : LONGINT;
  869. BEGIN
  870. SplitFullName(sourceName, srcPrefix, srcPath, srcFilename, srcExtension);
  871. index := 0;
  872. FOR i := 0 TO LEN(targetMask)-1 DO
  873. IF targetMask[i] = "." THEN
  874. isExtension := TRUE;
  875. targetName[index] := targetMask[i];
  876. INC(index);
  877. ELSIF targetMask[i] = "*" THEN
  878. IF isExtension THEN
  879. j := 0; WHILE (j < LEN(srcExtension)) & (srcExtension[j] # 0X) DO targetName[index] := srcExtension[j]; INC(index); INC(j); END;
  880. ELSE
  881. j := 0; WHILE (j < LEN(srcFilename)) & (srcFilename[j] # 0X) DO targetName[index] := srcFilename[j]; INC(index); INC(j); END;
  882. END;
  883. ELSE
  884. targetName[index] := targetMask[i];
  885. INC(index);
  886. END;
  887. END;
  888. IF index < LEN(targetName) THEN targetName[index] := 0X; END;
  889. RETURN Strings.NewString(targetName);
  890. END GetTargetName;
  891. PROCEDURE InsertFilesAndFixDestination(context : Commands.Context; CONST sourceMask, targetMask : ARRAY OF CHAR; VAR source, target : FileList; VAR index : LONGINT) : BOOLEAN;
  892. VAR
  893. enum : Files.Enumerator;
  894. fileflags : SET;
  895. time, date: LONGINT; size : HUGEINT;
  896. name : ARRAY MaxNameLen OF CHAR;
  897. BEGIN
  898. IF ~IsValidTargetMask(context, targetMask) THEN RETURN FALSE; END;
  899. NEW(enum); enum.Open(sourceMask, {});
  900. WHILE enum.GetEntry(name, fileflags, time, date, size) DO
  901. IF (fileflags * {Files.Directory} = {}) THEN
  902. IF index >= LEN(source) THEN ResizeFilelist(source); ResizeFilelist(target); END;
  903. source[index] := Strings.NewString(name);
  904. target[index] := GetTargetName(sourceMask, targetMask, name);
  905. INC(index);
  906. END;
  907. END;
  908. enum.Close;
  909. RETURN TRUE;
  910. END InsertFilesAndFixDestination;
  911. PROCEDURE IsMask(CONST string : ARRAY OF CHAR) : BOOLEAN;
  912. BEGIN
  913. RETURN Strings.ContainsChar(string, "*", FALSE) OR Strings.ContainsChar(string, "?", FALSE);
  914. END IsMask;
  915. PROCEDURE GetFileList(context : Commands.Context; VAR filelist : FileList) : LONGINT;
  916. VAR filename : ARRAY MaxNameLen OF CHAR; done, error : BOOLEAN; count : LONGINT;
  917. BEGIN
  918. NEW(filelist, InitialFilelistSize);
  919. WHILE ~done & ~error DO
  920. IF context.arg.GetString(filename) THEN
  921. IF IsMask(filename) THEN
  922. IF ~(AllowMaskInSafeMode(filename) OR unsafeMode) THEN
  923. ShowUnsafeMessage(context.out); RETURN 0;
  924. END;
  925. InsertFiles(filename, filelist, count);
  926. ELSE
  927. IF count >= LEN(filelist) THEN ResizeFilelist(filelist); END;
  928. filelist[count] := Strings.NewString(filename);
  929. INC(count);
  930. END;
  931. ELSIF context.arg.res = Streams.EOF THEN
  932. done := TRUE;
  933. ELSE
  934. context.error.String("Command parsing error (res: "); context.error.Int(context.arg.res, 0); context.error.String(")");
  935. error := TRUE;
  936. END;
  937. END;
  938. IF error THEN count := Error; END;
  939. RETURN count;
  940. END GetFileList;
  941. PROCEDURE GetSimpleFileLists(context : Commands.Context; VAR source, target : FileList) : LONGINT;
  942. VAR sourceFilename, targetFilename : Files.FileName; count : LONGINT;
  943. BEGIN
  944. IF context.arg.GetString(sourceFilename) & context.arg.GetString(targetFilename) THEN
  945. count := 1;
  946. IF IsMask(sourceFilename) OR IsMask(targetFilename) THEN
  947. IF ~(AllowMaskInSafeMode(sourceFilename) OR unsafeMode) THEN ShowUnsafeMessage(context.out); RETURN 0; END;
  948. IF ~InsertFilesAndFixDestination(context, sourceFilename, targetFilename, source, target, count) THEN END;
  949. ELSE
  950. NEW(source, 1); NEW(target, 1);
  951. source[0] := Strings.NewString(sourceFilename);
  952. target[0] := Strings.NewString(targetFilename);
  953. END;
  954. ELSE
  955. count := Error;
  956. context.error.String("Expected two filenames as arguments"); context.error.Ln;
  957. END;
  958. RETURN count;
  959. END GetSimpleFileLists;
  960. PROCEDURE GetFileLists(context : Commands.Context; VAR source, target : FileList) : LONGINT;
  961. VAR
  962. filename : ARRAY MaxNameLen OF CHAR; done, error : BOOLEAN; count : LONGINT;
  963. sourceString, targetString : String;
  964. BEGIN
  965. NEW(source, InitialFilelistSize); NEW(target, InitialFilelistSize);
  966. WHILE ~done & ~error DO
  967. IF context.arg.GetString(filename) THEN
  968. sourceString := Strings.NewString(filename);
  969. IF context.arg.GetString(filename) & Strings.Match(filename, "=>") THEN
  970. IF context.arg.GetString(filename) THEN
  971. targetString := Strings.NewString(filename);
  972. IF IsMask(sourceString^) OR IsMask(targetString^) THEN
  973. IF ~(AllowMaskInSafeMode(sourceString^) OR unsafeMode) THEN ShowUnsafeMessage(context.out); RETURN 0; END;
  974. IF ~InsertFilesAndFixDestination(context, sourceString^, targetString^, source, target, count) THEN END;
  975. ELSE
  976. IF count >= LEN(source) THEN ResizeFilelist(source); ResizeFilelist(target); END;
  977. source[count] := sourceString;
  978. target[count] := targetString;
  979. INC(count);
  980. END;
  981. ELSE
  982. context.error.String("Command parsing error (res: "); context.error.Int(context.arg.res, 0); context.error.String(")");
  983. context.error.Ln;
  984. error := TRUE;
  985. END;
  986. ELSE
  987. context.error.String("Command parsing error: Exspected => token, found: "); context.error.String(filename);
  988. context.error.Ln;
  989. error := TRUE;
  990. END;
  991. ELSIF context.arg.res = Streams.EOF THEN
  992. done := TRUE;
  993. ELSE
  994. context.error.String("Command parsing error (res: "); context.error.Int(context.arg.res, 0); context.error.String(")");
  995. context.error.Ln;
  996. error := TRUE;
  997. END;
  998. END;
  999. IF error THEN count := Error; END;
  1000. RETURN count;
  1001. END GetFileLists;
  1002. PROCEDURE Safe*(context : Commands.Context);
  1003. BEGIN
  1004. unsafeMode := FALSE;
  1005. context.out.String("FSTools64: SAFE mode."); context.out.Ln;
  1006. END Safe;
  1007. PROCEDURE Unsafe*(context : Commands.Context);
  1008. BEGIN
  1009. unsafeMode := TRUE;
  1010. context.out.String("FSTools64: UNSAFE mode now. BE CAREFUL!"); context.out.Ln;
  1011. END Unsafe;
  1012. PROCEDURE ShowUnsafeMessage(out : Streams.Writer);
  1013. BEGIN
  1014. out.String("FSTools64: Pattern matching is disabled in SAFE mode. Press FSTools64.Unsafe ~ to enable pattern matching."); out.Ln;
  1015. END ShowUnsafeMessage;
  1016. PROCEDURE ShowRes(out : Streams.Writer; res : LONGINT);
  1017. BEGIN
  1018. out.String("(");
  1019. CASE res OF
  1020. Files.VolumeReadOnly: out.String("Target volume is read-only");
  1021. |Files.FsNotFound: out.String("File system not found");
  1022. |Files.FileAlreadyExists: out.String("File already exists");
  1023. |Files.BadFileName: out.String("Bad file name");
  1024. |Files.FileNotFound: out.String("File not found");
  1025. ELSE
  1026. out.String("res: "); out.Int(res, 0);
  1027. END;
  1028. out.String(")");
  1029. END ShowRes;
  1030. (** Close files -- paradox: open (old) file and call Close method. Intended for systems in a host environment to explicitely release a file handle. *)
  1031. PROCEDURE CloseFiles*(context : Commands.Context); (** [Options] {file} ~ *)
  1032. VAR
  1033. filelist : FileList;
  1034. nofFiles, res, n, ndone : LONGINT;
  1035. file: Files.File;
  1036. BEGIN
  1037. nofFiles := GetFileList(context, filelist);
  1038. n := 0; ndone := 0;
  1039. WHILE (n<nofFiles) & (filelist[n] # NIL) DO
  1040. file := Files.Old(filelist[n]^);
  1041. IF file # NIL THEN file.Close END;
  1042. INC(n);
  1043. END;
  1044. END CloseFiles;
  1045. (* returns if a file or directory exists. If yes, then fullname is set to filename *)
  1046. PROCEDURE Exists*(CONST name: ARRAY OF CHAR; VAR fullName: ARRAY OF CHAR; VAR flags: SET): BOOLEAN;
  1047. BEGIN
  1048. RETURN Files.Exists(name, fullName, flags);
  1049. END Exists;
  1050. END FSTools64.
  1051. SystemTools.Free FSTools64 ~
  1052. FSTools64.DeleteFiles ./*.Bak ~
  1053. FSTools64.DeleteFiles ./../../source/*.Bak ~
  1054. FSTools64.SplitFile BootManager.Bin 0200H BootManagerMBR.Bin BootManagerTail.Bin ~
  1055. FSTools64.Directory --size --time ./../../source/*.XML ~