FSTools.Mod 41 KB

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