libtar.pp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  1. (**
  2. Copyright (c) 2000-2010 by Stefan Heymann
  3. See the file COPYING.FPC, included in this distribution,
  4. for details about the copyright.
  5. This program is distributed in the hope that it will be useful,
  6. but WITHOUT ANY WARRANTY; without even the implied warranty of
  7. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  8. ===============================================================================================
  9. Name : LibTar
  10. ===============================================================================================
  11. Subject : Handling of "tar" files
  12. ===============================================================================================
  13. Author : Stefan Heymann
  14. Eschenweg 3
  15. 72076 Tübingen
  16. GERMANY
  17. E-Mail: [email protected]
  18. Web: www.destructor.de
  19. ===============================================================================================
  20. TTarArchive Usage
  21. -----------------
  22. - Choose a constructor
  23. - Make an instance of TTarArchive TA := TTarArchive.Create (Filename);
  24. - Scan through the archive TA.Reset;
  25. WHILE TA.FindNext (DirRec) DO BEGIN
  26. - Evaluate the DirRec for each file ListBox.Items.Add (DirRec.Name);
  27. - Read out the current file TA.ReadFile (DestFilename);
  28. (You can ommit this if you want to
  29. read in the directory only) END;
  30. - You're done TA.Free;
  31. TTarWriter Usage
  32. ----------------
  33. - Choose a constructor
  34. - Make an instance of TTarWriter TW := TTarWriter.Create ('my.tar');
  35. - Add a file to the tar archive TW.AddFile ('foobar.txt');
  36. - Add a string as a file TW.AddString (SL.Text, 'joe.txt', Now);
  37. - Destroy TarWriter instance TW.Free;
  38. - Now your tar file is ready.
  39. Source
  40. --------------------------
  41. The official site to get this code is http://www.destructor.de/
  42. Donateware
  43. ----------
  44. If you like this code, you are free to donate
  45. http://www.destructor.de/donateware.htm
  46. ===============================================================================================
  47. !!! All parts of this code which are not finished or known to be buggy
  48. are marked with three exclamation marks
  49. ===============================================================================================
  50. Date Author Changes
  51. -----------------------------------------------------------------------------------------------
  52. 2001-04-26 HeySt 0.0.1 Start
  53. 2001-04-28 HeySt 1.0.0 First Release
  54. 2001-06-19 HeySt 2.0.0 Finished TTarWriter
  55. 2001-09-06 HeySt 2.0.1 Bugfix in TTarArchive.FindNext: FBytesToGo must sometimes be 0
  56. 2001-10-25 HeySt 2.0.2 Introduced the ClearDirRec procedure
  57. 2001-11-13 HeySt 2.0.3 Bugfix: Take out ClearDirRec call from WriteTarHeader
  58. Bug Reported by Tony BenBrahim
  59. 2001-12-25 HeySt 2.0.4 WriteTarHeader: Fill Rec with zero bytes before filling it
  60. 2002-05-18 HeySt 2.0.5 Kylix awareness: Thanks to Kerry L. Davison for the canges
  61. 2005-09-03 HeySt 2.0.6 TTarArchive.FindNext: Don't access SourceStream.Size
  62. (for compressed streams, which don't know their .Size)
  63. 2006-03-13 HeySt 2.0.7 Bugfix in ReadFile (Buffer : POINTER)
  64. 2006-09-20 MvdV 2.0.7.1 Small fixes for FPC.
  65. 2007-05-16 HeySt 2.0.8 Bugfix in TTarWriter.AddFile (Convertfilename in the ELSE branch)
  66. Bug Reported by Chris Rorden
  67. 2010-11-29 HeySt 2.1.0 WriteTarHeader: Mode values for ftNormal/ftLink/ftSymbolicLink/ftDirectory
  68. Thanks to Iouri Kharon for the fix.
  69. Still no support for filenames > 100 bytes. Sorry.
  70. Support for Unicode Delphi versions (2009, 2010, XE, etc.)
  71. MvdV 2.1.0 notes : not all of the Unicode changes have been made, decisions on this subject still pending on the FPC side.
  72. Mostly rawbytestring and a couple of more hary typecasts.
  73. *)
  74. UNIT libtar;
  75. INTERFACE
  76. {$IFDEF FPC}
  77. {$MODE Delphi}
  78. {$ELSE}
  79. {$IFDEF LINUX}
  80. {$DEFINE Kylix}
  81. {$DEFINE LIBCUNIT}
  82. {$ENDIF}
  83. {$ENDIF}
  84. USES
  85. {$IFDEF LIBCUNIT}
  86. Libc, // MvdV: Nothing is used from this???
  87. {$ENDIF}
  88. {$ifdef Unix}
  89. UnixType, BaseUnix, Unix,
  90. {$endif}
  91. (*$IFDEF MSWINDOWS *)
  92. Windows,
  93. (*$ENDIF *)
  94. SysUtils, Classes;
  95. TYPE
  96. // --- File Access Permissions
  97. TTarPermission = (tpReadByOwner, tpWriteByOwner, tpExecuteByOwner,
  98. tpReadByGroup, tpWriteByGroup, tpExecuteByGroup,
  99. tpReadByOther, tpWriteByOther, tpExecuteByOther);
  100. TTarPermissions = SET OF TTarPermission;
  101. // --- Type of File
  102. TFileType = (ftNormal, // Regular file
  103. ftLink, // Link to another, previously archived, file (LinkName)
  104. ftSymbolicLink, // Symbolic link to another file (LinkName)
  105. ftCharacter, // Character special files
  106. ftBlock, // Block special files
  107. ftDirectory, // Directory entry. Size is zero (unlimited) or max. number of bytes
  108. ftFifo, // FIFO special file. No data stored in the archive.
  109. ftContiguous, // Contiguous file, if supported by OS
  110. ftDumpDir, // List of files
  111. ftMultiVolume, // Multi-volume file part
  112. ftVolumeHeader); // Volume header. Can appear only as first record in the archive
  113. // --- Mode
  114. TTarMode = (tmSetUid, tmSetGid, tmSaveText);
  115. TTarModes = SET OF TTarMode;
  116. // --- Record for a Directory Entry
  117. // Adjust the ClearDirRec procedure when this record changes!
  118. TTarDirRec = RECORD
  119. Name : AnsiString; // File path and name
  120. Size : INT64; // File size in Bytes
  121. DateTime : TDateTime; // Last modification date and time
  122. Permissions : TTarPermissions; // Access permissions
  123. FileType : TFileType; // Type of file
  124. LinkName : AnsiString; // Name of linked file (for ftLink, ftSymbolicLink)
  125. UID : INTEGER; // User ID
  126. GID : INTEGER; // Group ID
  127. UserName : AnsiString; // User name
  128. GroupName : AnsiString; // Group name
  129. ChecksumOK : BOOLEAN; // Checksum was OK
  130. Mode : TTarModes; // Mode
  131. Magic : AnsiString; // Contents of the "Magic" field
  132. MajorDevNo : INTEGER; // Major Device No. for ftCharacter and ftBlock
  133. MinorDevNo : INTEGER; // Minor Device No. for ftCharacter and ftBlock
  134. FilePos : INT64; // Position in TAR file
  135. END;
  136. // --- The TAR Archive CLASS
  137. TTarArchive = CLASS
  138. PROTECTED
  139. FStream : TStream; // Internal Stream
  140. FOwnsStream : BOOLEAN; // True if FStream is owned by the TTarArchive instance
  141. FBytesToGo : INT64; // Bytes until the next Header Record
  142. PUBLIC
  143. CONSTRUCTOR Create (Stream : TStream); OVERLOAD;
  144. CONSTRUCTOR Create (Filename : STRING;
  145. FileMode : WORD = fmOpenRead OR fmShareDenyWrite); OVERLOAD;
  146. DESTRUCTOR Destroy; OVERRIDE;
  147. PROCEDURE Reset; // Reset File Pointer
  148. FUNCTION FindNext (VAR DirRec : TTarDirRec) : BOOLEAN; // Reads next Directory Info Record. FALSE if EOF reached
  149. PROCEDURE ReadFile (Buffer : POINTER); OVERLOAD; // Reads file data for last Directory Record
  150. PROCEDURE ReadFile (Stream : TStream); OVERLOAD; // -;-
  151. PROCEDURE ReadFile (Filename : STRING); OVERLOAD; // -;-
  152. FUNCTION ReadFile : STRING; OVERLOAD; // -;- RawByteString in D2009+. Not active due to FPC unicode architecture not being finalized
  153. PROCEDURE GetFilePos (VAR Current, Size : INT64); // Current File Position
  154. PROCEDURE SetFilePos (NewPos : INT64); // Set new Current File Position
  155. END;
  156. // --- The TAR Archive Writer CLASS
  157. TTarWriter = CLASS
  158. PROTECTED
  159. FStream : TStream;
  160. FOwnsStream : BOOLEAN;
  161. FFinalized : BOOLEAN;
  162. // --- Used at the next "Add" method call: ---
  163. FPermissions : TTarPermissions; // Access permissions
  164. FUID : INTEGER; // User ID
  165. FGID : INTEGER; // Group ID
  166. FUserName : AnsiString; // User name
  167. FGroupName : AnsiString; // Group name
  168. FMode : TTarModes; // Mode
  169. FMagic : AnsiString; // Contents of the "Magic" field
  170. CONSTRUCTOR CreateEmpty;
  171. PUBLIC
  172. CONSTRUCTOR Create (TargetStream : TStream); OVERLOAD;
  173. CONSTRUCTOR Create (TargetFilename : STRING; Mode : INTEGER = fmCreate); OVERLOAD;
  174. DESTRUCTOR Destroy; OVERRIDE; // Writes End-Of-File Tag
  175. PROCEDURE AddFile (Filename : STRING; TarFilename : AnsiString = '');
  176. PROCEDURE AddStream (Stream : TStream; TarFilename : AnsiString; FileDateGmt : TDateTime);
  177. PROCEDURE AddString (Contents : Ansistring; TarFilename : AnsiString; FileDateGmt : TDateTime); // RawByteString
  178. PROCEDURE AddDir (Dirname : AnsiString; DateGmt : TDateTime; MaxDirSize : INT64 = 0);
  179. PROCEDURE AddSymbolicLink (Filename, Linkname : AnsiString; DateGmt : TDateTime);
  180. PROCEDURE AddLink (Filename, Linkname : AnsiString; DateGmt : TDateTime);
  181. PROCEDURE AddVolumeHeader (VolumeId : AnsiString; DateGmt : TDateTime);
  182. PROCEDURE Finalize;
  183. PROPERTY Permissions : TTarPermissions READ FPermissions WRITE FPermissions; // Access permissions
  184. PROPERTY UID : INTEGER READ FUID WRITE FUID; // User ID
  185. PROPERTY GID : INTEGER READ FGID WRITE FGID; // Group ID
  186. PROPERTY UserName : AnsiString READ FUserName WRITE FUserName; // User name
  187. PROPERTY GroupName : AnsiString READ FGroupName WRITE FGroupName; // Group name
  188. PROPERTY Mode : TTarModes READ FMode WRITE FMode; // Mode
  189. PROPERTY Magic : AnsiString READ FMagic WRITE FMagic; // Contents of the "Magic" field
  190. END;
  191. // --- Some useful constants
  192. CONST
  193. FILETYPE_NAME : ARRAY [TFileType] OF STRING =
  194. ('Regular', 'Link', 'Symbolic Link', 'Char File', 'Block File',
  195. 'Directory', 'FIFO File', 'Contiguous', 'Dir Dump', 'Multivol', 'Volume Header');
  196. ALL_PERMISSIONS = [tpReadByOwner, tpWriteByOwner, tpExecuteByOwner,
  197. tpReadByGroup, tpWriteByGroup, tpExecuteByGroup,
  198. tpReadByOther, tpWriteByOther, tpExecuteByOther];
  199. READ_PERMISSIONS = [tpReadByOwner, tpReadByGroup, tpReadByOther];
  200. WRITE_PERMISSIONS = [tpWriteByOwner, tpWriteByGroup, tpWriteByOther];
  201. EXECUTE_PERMISSIONS = [tpExecuteByOwner, tpExecuteByGroup, tpExecuteByOther];
  202. FUNCTION PermissionString (Permissions : TTarPermissions) : STRING;
  203. FUNCTION ConvertFilename (Filename : STRING) : STRING;
  204. FUNCTION FileTimeGMT (FileName : STRING) : TDateTime; OVERLOAD;
  205. FUNCTION FileTimeGMT (SearchRec : TSearchRec) : TDateTime; OVERLOAD;
  206. PROCEDURE ClearDirRec (VAR DirRec : TTarDirRec);
  207. (*
  208. ===============================================================================================
  209. IMPLEMENTATION
  210. ===============================================================================================
  211. *)
  212. IMPLEMENTATION
  213. FUNCTION PermissionString (Permissions : TTarPermissions) : STRING;
  214. BEGIN
  215. Result := '';
  216. IF tpReadByOwner IN Permissions THEN Result := Result + 'r' ELSE Result := Result + '-';
  217. IF tpWriteByOwner IN Permissions THEN Result := Result + 'w' ELSE Result := Result + '-';
  218. IF tpExecuteByOwner IN Permissions THEN Result := Result + 'x' ELSE Result := Result + '-';
  219. IF tpReadByGroup IN Permissions THEN Result := Result + 'r' ELSE Result := Result + '-';
  220. IF tpWriteByGroup IN Permissions THEN Result := Result + 'w' ELSE Result := Result + '-';
  221. IF tpExecuteByGroup IN Permissions THEN Result := Result + 'x' ELSE Result := Result + '-';
  222. IF tpReadByOther IN Permissions THEN Result := Result + 'r' ELSE Result := Result + '-';
  223. IF tpWriteByOther IN Permissions THEN Result := Result + 'w' ELSE Result := Result + '-';
  224. IF tpExecuteByOther IN Permissions THEN Result := Result + 'x' ELSE Result := Result + '-';
  225. END;
  226. FUNCTION ConvertFilename (Filename : STRING) : STRING;
  227. // Converts the filename to Unix conventions
  228. // could be empty and inlined away for FPC. FPC I/O should be
  229. // forward/backward slash safe.
  230. BEGIN
  231. (*$IFDEF Unix *)
  232. Result := Filename;
  233. (*$ELSE *)
  234. Result := StringReplace (Filename, '\', '/', [rfReplaceAll]);
  235. (*$ENDIF *)
  236. END;
  237. FUNCTION FileTimeGMT (FileName: STRING): TDateTime;
  238. // Returns the Date and Time of the last modification of the given File
  239. // The Result is zero if the file could not be found
  240. // The Result is given in UTC (GMT) time zone
  241. VAR
  242. SR : TSearchRec;
  243. BEGIN
  244. Result := 0.0;
  245. IF FindFirst (FileName, faAnyFile, SR) = 0 THEN
  246. Result := FileTimeGMT (SR);
  247. FindClose (SR);
  248. END;
  249. FUNCTION FileTimeGMT (SearchRec : TSearchRec) : TDateTime;
  250. (*$IFDEF MSWINDOWS *)
  251. VAR
  252. SystemFileTime: TSystemTime;
  253. (*$ENDIF *)
  254. (*$IFDEF Unix *)
  255. VAR
  256. TimeVal : TTimeVal;
  257. TimeZone : TTimeZone;
  258. (*$ENDIF *)
  259. BEGIN
  260. Result := 0.0;
  261. (*$IFDEF MSWINDOWS *) (*$WARNINGS OFF *)
  262. IF (SearchRec.FindData.dwFileAttributes AND faDirectory) = 0 THEN
  263. IF FileTimeToSystemTime (SearchRec.FindData.ftLastWriteTime, SystemFileTime) THEN
  264. Result := EncodeDate (SystemFileTime.wYear, SystemFileTime.wMonth, SystemFileTime.wDay)
  265. + EncodeTime (SystemFileTime.wHour, SystemFileTime.wMinute, SystemFileTime.wSecond, SystemFileTime.wMilliseconds);
  266. (*$ENDIF *) (*$WARNINGS ON *)
  267. (*$IFDEF Unix *)
  268. IF SearchRec.Attr AND faDirectory = 0 THEN BEGIN
  269. FillChar(TimeVal, SizeOf(TimeVal), #0);
  270. FillChar(TimeZone, SizeOf(TimeZone), #0);
  271. Result := FileDateToDateTime (SearchRec.Time);
  272. {$IFDEF Kylix}
  273. GetTimeOfDay (TimeVal, TimeZone);
  274. {$ELSE}
  275. fpGetTimeOfDay (@TimeVal, @TimeZone);
  276. {$ENDIF}
  277. Result := Result + TimeZone.tz_minuteswest / (60 * 24);
  278. END;
  279. (*$ENDIF *)
  280. end;
  281. PROCEDURE ClearDirRec (VAR DirRec : TTarDirRec);
  282. // This is included because a FillChar (DirRec, SizeOf (DirRec), 0)
  283. // will destroy the long string pointers, leading to strange bugs
  284. BEGIN
  285. WITH DirRec DO BEGIN
  286. Name := '';
  287. Size := 0;
  288. DateTime := 0.0;
  289. Permissions := [];
  290. FileType := TFileType (0);
  291. LinkName := '';
  292. UID := 0;
  293. GID := 0;
  294. UserName := '';
  295. GroupName := '';
  296. ChecksumOK := FALSE;
  297. Mode := [];
  298. Magic := '';
  299. MajorDevNo := 0;
  300. MinorDevNo := 0;
  301. FilePos := 0;
  302. END;
  303. END;
  304. (*
  305. ===============================================================================================
  306. TAR format
  307. ===============================================================================================
  308. *)
  309. CONST
  310. RECORDSIZE = 512;
  311. NAMSIZ = 100;
  312. TUNMLEN = 32;
  313. TGNMLEN = 32;
  314. CHKBLANKS = #32#32#32#32#32#32#32#32;
  315. TYPE
  316. TTarHeader = PACKED RECORD
  317. Name : ARRAY [0..NAMSIZ-1] OF AnsiChar;
  318. Mode : ARRAY [0..7] OF AnsiChar;
  319. UID : ARRAY [0..7] OF AnsiChar;
  320. GID : ARRAY [0..7] OF AnsiChar;
  321. Size : ARRAY [0..11] OF AnsiChar;
  322. MTime : ARRAY [0..11] OF AnsiChar;
  323. ChkSum : ARRAY [0..7] OF AnsiChar;
  324. LinkFlag : AnsiChar;
  325. LinkName : ARRAY [0..NAMSIZ-1] OF AnsiChar;
  326. Magic : ARRAY [0..7] OF AnsiChar;
  327. UName : ARRAY [0..TUNMLEN-1] OF AnsiChar;
  328. GName : ARRAY [0..TGNMLEN-1] OF AnsiChar;
  329. DevMajor : ARRAY [0..7] OF AnsiChar;
  330. DevMinor : ARRAY [0..7] OF AnsiChar;
  331. END;
  332. FUNCTION ExtractText (P : PAnsiChar) : AnsiString;
  333. BEGIN
  334. Result := AnsiString(P);
  335. END;
  336. FUNCTION ExtractNumber (P : PAnsiChar) : INTEGER; OVERLOAD;
  337. VAR
  338. Strg : AnsiString;
  339. BEGIN
  340. Strg := Trim (StrPas (P));
  341. P := PAnsiChar (Strg);
  342. Result := 0;
  343. WHILE (P^ <> #32) AND (P^ <> #0) DO BEGIN
  344. Result := (ORD (P^) - ORD ('0')) OR (Result SHL 3);
  345. INC (P);
  346. END;
  347. END;
  348. FUNCTION ExtractNumber64 (P : PAnsiChar) : INT64; OVERLOAD;
  349. VAR
  350. Strg : AnsiString;
  351. BEGIN
  352. Strg := Trim (StrPas (P));
  353. P := PAnsiChar (Strg);
  354. Result := 0;
  355. WHILE (P^ <> #32) AND (P^ <> #0) DO BEGIN
  356. Result := (ORD (P^) - ORD ('0')) OR (Result SHL 3);
  357. INC (P);
  358. END;
  359. END;
  360. FUNCTION ExtractNumber (P : PAnsiChar; MaxLen : INTEGER) : INTEGER; OVERLOAD;
  361. VAR
  362. S0 : ARRAY [0..255] OF AnsiChar;
  363. Strg : AnsiString;
  364. BEGIN
  365. StrLCopy (S0, P, MaxLen);
  366. Strg := Trim (StrPas (S0));
  367. P := PAnsiChar (Strg);
  368. Result := 0;
  369. WHILE (P^ <> #32) AND (P^ <> #0) DO BEGIN
  370. Result := (ORD (P^) - ORD ('0')) OR (Result SHL 3);
  371. INC (P);
  372. END;
  373. END;
  374. FUNCTION ExtractNumber64 (P : PAnsiChar; MaxLen : INTEGER) : INT64; OVERLOAD;
  375. VAR
  376. S0 : ARRAY [0..255] OF AnsiChar;
  377. Strg : AnsiString;
  378. BEGIN
  379. StrLCopy (S0, P, MaxLen);
  380. Strg := Trim (StrPas (S0));
  381. P := PAnsiChar (Strg);
  382. Result := 0;
  383. WHILE (P^ <> #32) AND (P^ <> #0) DO BEGIN
  384. Result := (ORD (P^) - ORD ('0')) OR (Result SHL 3);
  385. INC (P);
  386. END;
  387. END;
  388. FUNCTION Records (Bytes : INT64) : INT64;
  389. BEGIN
  390. Result := Bytes DIV RECORDSIZE;
  391. IF Bytes MOD RECORDSIZE > 0 THEN
  392. INC (Result);
  393. END;
  394. PROCEDURE Octal (N : INTEGER; P : PAnsiChar; Len : INTEGER);
  395. // Makes a string of octal digits
  396. // The string will always be "Len" characters long
  397. VAR
  398. I : INTEGER;
  399. BEGIN
  400. FOR I := Len-2 DOWNTO 0 DO BEGIN
  401. (P+I)^ := AnsiChar (ORD ('0') + ORD (N AND $07));
  402. N := N SHR 3;
  403. END;
  404. FOR I := 0 TO Len-3 DO
  405. IF (P+I)^ = '0'
  406. THEN (P+I)^ := #32
  407. ELSE BREAK;
  408. (P+Len-1)^ := #32;
  409. END;
  410. PROCEDURE Octal64 (N : INT64; P : PAnsiChar; Len : INTEGER);
  411. // Makes a string of octal digits
  412. // The string will always be "Len" characters long
  413. VAR
  414. I : INTEGER;
  415. BEGIN
  416. FOR I := Len-2 DOWNTO 0 DO BEGIN
  417. (P+I)^ := AnsiChar (ORD ('0') + ORD (N AND $07));
  418. N := N SHR 3;
  419. END;
  420. FOR I := 0 TO Len-3 DO
  421. IF (P+I)^ = '0'
  422. THEN (P+I)^ := #32
  423. ELSE BREAK;
  424. (P+Len-1)^ := #32;
  425. END;
  426. PROCEDURE OctalN (N : INTEGER; P : PAnsiChar; Len : INTEGER);
  427. BEGIN
  428. Octal (N, P, Len-1);
  429. (P+Len-1)^ := #0;
  430. END;
  431. PROCEDURE WriteTarHeader (Dest : TStream; DirRec : TTarDirRec);
  432. VAR
  433. Rec : ARRAY [0..RECORDSIZE-1] OF AnsiChar;
  434. TH : TTarHeader ABSOLUTE Rec;
  435. Mode : INTEGER;
  436. NullDate : TDateTime;
  437. Checksum : CARDINAL;
  438. I : INTEGER;
  439. BEGIN
  440. FillChar (Rec, RECORDSIZE, 0);
  441. StrLCopy (TH.Name, PAnsiChar (DirRec.Name), NAMSIZ);
  442. CASE DirRec.FileType OF
  443. ftNormal, ftLink : Mode := $08000;
  444. ftSymbolicLink : Mode := $0A000;
  445. ftDirectory : Mode := $04000;
  446. ELSE Mode := 0;
  447. END;
  448. IF tmSaveText IN DirRec.Mode THEN Mode := Mode OR $0200;
  449. IF tmSetGid IN DirRec.Mode THEN Mode := Mode OR $0400;
  450. IF tmSetUid IN DirRec.Mode THEN Mode := Mode OR $0800;
  451. IF tpReadByOwner IN DirRec.Permissions THEN Mode := Mode OR $0100;
  452. IF tpWriteByOwner IN DirRec.Permissions THEN Mode := Mode OR $0080;
  453. IF tpExecuteByOwner IN DirRec.Permissions THEN Mode := Mode OR $0040;
  454. IF tpReadByGroup IN DirRec.Permissions THEN Mode := Mode OR $0020;
  455. IF tpWriteByGroup IN DirRec.Permissions THEN Mode := Mode OR $0010;
  456. IF tpExecuteByGroup IN DirRec.Permissions THEN Mode := Mode OR $0008;
  457. IF tpReadByOther IN DirRec.Permissions THEN Mode := Mode OR $0004;
  458. IF tpWriteByOther IN DirRec.Permissions THEN Mode := Mode OR $0002;
  459. IF tpExecuteByOther IN DirRec.Permissions THEN Mode := Mode OR $0001;
  460. OctalN (Mode, @TH.Mode, 8);
  461. OctalN (DirRec.UID, @TH.UID, 8);
  462. OctalN (DirRec.GID, @TH.GID, 8);
  463. Octal64 (DirRec.Size, @TH.Size, 12);
  464. NullDate := EncodeDate (1970, 1, 1);
  465. IF DirRec.DateTime >= NullDate
  466. THEN Octal (Trunc ((DirRec.DateTime - NullDate) * 86400.0), @TH.MTime, 12)
  467. ELSE Octal (Trunc ( NullDate * 86400.0), @TH.MTime, 12);
  468. CASE DirRec.FileType OF
  469. ftNormal : TH.LinkFlag := '0';
  470. ftLink : TH.LinkFlag := '1';
  471. ftSymbolicLink : TH.LinkFlag := '2';
  472. ftCharacter : TH.LinkFlag := '3';
  473. ftBlock : TH.LinkFlag := '4';
  474. ftDirectory : TH.LinkFlag := '5';
  475. ftFifo : TH.LinkFlag := '6';
  476. ftContiguous : TH.LinkFlag := '7';
  477. ftDumpDir : TH.LinkFlag := 'D';
  478. ftMultiVolume : TH.LinkFlag := 'M';
  479. ftVolumeHeader : TH.LinkFlag := 'V';
  480. END;
  481. StrLCopy (TH.LinkName, PAnsiChar (DirRec.LinkName), NAMSIZ);
  482. StrLCopy (TH.Magic, PAnsiChar (DirRec.Magic + #32#32#32#32#32#32#32#32), 7);
  483. StrLCopy (TH.UName, PAnsiChar (DirRec.UserName), TUNMLEN);
  484. StrLCopy (TH.GName, PAnsiChar (DirRec.GroupName), TGNMLEN);
  485. OctalN (DirRec.MajorDevNo, @TH.DevMajor, 8);
  486. OctalN (DirRec.MinorDevNo, @TH.DevMinor, 8);
  487. StrMove (TH.ChkSum, CHKBLANKS, 8);
  488. CheckSum := 0;
  489. FOR I := 0 TO SizeOf (TTarHeader)-1 DO
  490. INC (CheckSum, INTEGER (ORD (Rec [I])));
  491. OctalN (CheckSum, @TH.ChkSum, 8);
  492. Dest.Write (TH, RECORDSIZE);
  493. END;
  494. (*
  495. ===============================================================================================
  496. TTarArchive
  497. ===============================================================================================
  498. *)
  499. CONSTRUCTOR TTarArchive.Create (Stream : TStream);
  500. BEGIN
  501. INHERITED Create;
  502. FStream := Stream;
  503. FOwnsStream := FALSE;
  504. Reset;
  505. END;
  506. CONSTRUCTOR TTarArchive.Create (Filename : STRING; FileMode : WORD);
  507. BEGIN
  508. INHERITED Create;
  509. FStream := TFileStream.Create (Filename, FileMode);
  510. FOwnsStream := TRUE;
  511. Reset;
  512. END;
  513. DESTRUCTOR TTarArchive.Destroy;
  514. BEGIN
  515. IF FOwnsStream THEN
  516. FStream.Free;
  517. INHERITED Destroy;
  518. END;
  519. PROCEDURE TTarArchive.Reset;
  520. // Reset File Pointer
  521. BEGIN
  522. FStream.Position := 0;
  523. FBytesToGo := 0;
  524. END;
  525. FUNCTION TTarArchive.FindNext (VAR DirRec : TTarDirRec) : BOOLEAN;
  526. // Reads next Directory Info Record
  527. // The Stream pointer must point to the first byte of the tar header
  528. VAR
  529. Rec : ARRAY [0..RECORDSIZE-1] OF CHAR;
  530. CurFilePos : INTEGER;
  531. Header : TTarHeader ABSOLUTE Rec;
  532. I : INTEGER;
  533. HeaderChkSum : WORD;
  534. Checksum : CARDINAL;
  535. BEGIN
  536. // --- Scan until next pointer
  537. IF FBytesToGo > 0 THEN
  538. FStream.Seek (Records (FBytesToGo) * RECORDSIZE, soFromCurrent);
  539. // --- EOF reached?
  540. Result := FALSE;
  541. CurFilePos := FStream.Position;
  542. TRY
  543. FStream.ReadBuffer (Rec, RECORDSIZE);
  544. if Rec [0] = #0 THEN EXIT; // EOF reached
  545. EXCEPT
  546. EXIT; // EOF reached, too
  547. END;
  548. Result := TRUE;
  549. ClearDirRec (DirRec);
  550. DirRec.FilePos := CurFilePos;
  551. DirRec.Name := ExtractText (Header.Name);
  552. DirRec.Size := ExtractNumber64 (@Header.Size, 12);
  553. DirRec.DateTime := EncodeDate (1970, 1, 1) + (ExtractNumber (@Header.MTime, 12) / 86400.0);
  554. I := ExtractNumber (@Header.Mode);
  555. IF I AND $0100 <> 0 THEN Include (DirRec.Permissions, tpReadByOwner);
  556. IF I AND $0080 <> 0 THEN Include (DirRec.Permissions, tpWriteByOwner);
  557. IF I AND $0040 <> 0 THEN Include (DirRec.Permissions, tpExecuteByOwner);
  558. IF I AND $0020 <> 0 THEN Include (DirRec.Permissions, tpReadByGroup);
  559. IF I AND $0010 <> 0 THEN Include (DirRec.Permissions, tpWriteByGroup);
  560. IF I AND $0008 <> 0 THEN Include (DirRec.Permissions, tpExecuteByGroup);
  561. IF I AND $0004 <> 0 THEN Include (DirRec.Permissions, tpReadByOther);
  562. IF I AND $0002 <> 0 THEN Include (DirRec.Permissions, tpWriteByOther);
  563. IF I AND $0001 <> 0 THEN Include (DirRec.Permissions, tpExecuteByOther);
  564. IF I AND $0200 <> 0 THEN Include (DirRec.Mode, tmSaveText);
  565. IF I AND $0400 <> 0 THEN Include (DirRec.Mode, tmSetGid);
  566. IF I AND $0800 <> 0 THEN Include (DirRec.Mode, tmSetUid);
  567. CASE Header.LinkFlag OF
  568. #0, '0' : DirRec.FileType := ftNormal;
  569. '1' : DirRec.FileType := ftLink;
  570. '2' : DirRec.FileType := ftSymbolicLink;
  571. '3' : DirRec.FileType := ftCharacter;
  572. '4' : DirRec.FileType := ftBlock;
  573. '5' : DirRec.FileType := ftDirectory;
  574. '6' : DirRec.FileType := ftFifo;
  575. '7' : DirRec.FileType := ftContiguous;
  576. 'D' : DirRec.FileType := ftDumpDir;
  577. 'M' : DirRec.FileType := ftMultiVolume;
  578. 'V' : DirRec.FileType := ftVolumeHeader;
  579. END;
  580. DirRec.LinkName := ExtractText (Header.LinkName);
  581. DirRec.UID := ExtractNumber (@Header.UID);
  582. DirRec.GID := ExtractNumber (@Header.GID);
  583. DirRec.UserName := ExtractText (Header.UName);
  584. DirRec.GroupName := ExtractText (Header.GName);
  585. DirRec.Magic := Trim (ExtractText (Header.Magic));
  586. DirRec.MajorDevNo := ExtractNumber (@Header.DevMajor);
  587. DirRec.MinorDevNo := ExtractNumber (@Header.DevMinor);
  588. HeaderChkSum := ExtractNumber (@Header.ChkSum); // Calc Checksum
  589. CheckSum := 0;
  590. StrMove (Header.ChkSum, CHKBLANKS, 8);
  591. FOR I := 0 TO SizeOf (TTarHeader)-1 DO
  592. INC (CheckSum, INTEGER (ORD (Rec [I])));
  593. DirRec.CheckSumOK := WORD (CheckSum) = WORD (HeaderChkSum);
  594. IF DirRec.FileType in [ftLink, ftSymbolicLink, ftDirectory, ftFifo, ftVolumeHeader]
  595. THEN FBytesToGo := 0
  596. ELSE FBytesToGo := DirRec.Size;
  597. END;
  598. PROCEDURE TTarArchive.ReadFile (Buffer : POINTER);
  599. // Reads file data for the last Directory Record. The entire file is read into the buffer.
  600. // The buffer must be large enough to take up the whole file.
  601. VAR
  602. RestBytes : INTEGER;
  603. BEGIN
  604. IF FBytesToGo = 0 THEN EXIT;
  605. RestBytes := Records (FBytesToGo) * RECORDSIZE - FBytesToGo;
  606. FStream.ReadBuffer (Buffer^, FBytesToGo);
  607. FStream.Seek (RestBytes, soFromCurrent);
  608. FBytesToGo := 0;
  609. END;
  610. PROCEDURE TTarArchive.ReadFile (Stream : TStream);
  611. // Reads file data for the last Directory Record.
  612. // The entire file is written out to the stream.
  613. // The stream is left at its current position prior to writing
  614. VAR
  615. RestBytes : INTEGER;
  616. BEGIN
  617. IF FBytesToGo = 0 THEN EXIT;
  618. RestBytes := Records (FBytesToGo) * RECORDSIZE - FBytesToGo;
  619. Stream.CopyFrom (FStream, FBytesToGo);
  620. FStream.Seek (RestBytes, soFromCurrent);
  621. FBytesToGo := 0;
  622. END;
  623. PROCEDURE TTarArchive.ReadFile (Filename : STRING);
  624. // Reads file data for the last Directory Record.
  625. // The entire file is saved in the given Filename
  626. VAR
  627. FS : TFileStream;
  628. BEGIN
  629. FS := TFileStream.Create (Filename, fmCreate);
  630. TRY
  631. ReadFile (FS);
  632. FINALLY
  633. FS.Free;
  634. END;
  635. END;
  636. FUNCTION TTarArchive.ReadFile : STRING;
  637. // Reads file data for the last Directory Record. The entire file is returned
  638. // as a large ANSI string.
  639. VAR
  640. RestBytes : INTEGER;
  641. BEGIN
  642. IF FBytesToGo = 0 THEN EXIT;
  643. RestBytes := Records (FBytesToGo) * RECORDSIZE - FBytesToGo;
  644. SetLength (Result, FBytesToGo);
  645. FStream.ReadBuffer (PAnsiChar (Result)^, FBytesToGo);
  646. FStream.Seek (RestBytes, soFromCurrent);
  647. FBytesToGo := 0;
  648. END;
  649. PROCEDURE TTarArchive.GetFilePos (VAR Current, Size : INT64);
  650. // Returns the Current Position in the TAR stream
  651. BEGIN
  652. Current := FStream.Position;
  653. Size := FStream.Size;
  654. END;
  655. PROCEDURE TTarArchive.SetFilePos (NewPos : INT64); // Set new Current File Position
  656. BEGIN
  657. IF NewPos < FStream.Size THEN
  658. FStream.Seek (NewPos, soFromBeginning);
  659. END;
  660. (*
  661. ===============================================================================================
  662. TTarWriter
  663. ===============================================================================================
  664. *)
  665. CONSTRUCTOR TTarWriter.CreateEmpty;
  666. VAR
  667. TP : TTarPermission;
  668. BEGIN
  669. INHERITED Create;
  670. FOwnsStream := FALSE;
  671. FFinalized := FALSE;
  672. FPermissions := [];
  673. FOR TP := Low (TP) TO High (TP) DO
  674. Include (FPermissions, TP);
  675. FUID := 0;
  676. FGID := 0;
  677. FUserName := '';
  678. FGroupName := '';
  679. FMode := [];
  680. FMagic := 'ustar';
  681. END;
  682. CONSTRUCTOR TTarWriter.Create (TargetStream : TStream);
  683. BEGIN
  684. CreateEmpty;
  685. FStream := TargetStream;
  686. FOwnsStream := FALSE;
  687. END;
  688. CONSTRUCTOR TTarWriter.Create (TargetFilename : STRING; Mode : INTEGER = fmCreate);
  689. BEGIN
  690. CreateEmpty;
  691. FStream := TFileStream.Create (TargetFilename, Mode);
  692. FOwnsStream := TRUE;
  693. END;
  694. DESTRUCTOR TTarWriter.Destroy;
  695. BEGIN
  696. IF NOT FFinalized THEN BEGIN
  697. Finalize;
  698. FFinalized := TRUE;
  699. END;
  700. IF FOwnsStream THEN
  701. FStream.Free;
  702. INHERITED Destroy;
  703. END;
  704. PROCEDURE TTarWriter.AddFile (Filename : STRING; TarFilename : AnsiString = '');
  705. VAR
  706. S : TFileStream;
  707. Date : TDateTime;
  708. BEGIN
  709. Date := FileTimeGMT (Filename);
  710. IF TarFilename = '' THEN
  711. TarFilename := ConvertFilename (Filename)
  712. ELSE TarFilename := ConvertFilename (TarFilename);
  713. S := TFileStream.Create (Filename, fmOpenRead OR fmShareDenyWrite);
  714. TRY
  715. AddStream (S, TarFilename, Date);
  716. FINALLY
  717. S.Free
  718. END;
  719. END;
  720. PROCEDURE TTarWriter.AddStream (Stream : TStream; TarFilename : AnsiString; FileDateGmt : TDateTime);
  721. VAR
  722. DirRec : TTarDirRec;
  723. Rec : ARRAY [0..RECORDSIZE-1] OF CHAR;
  724. BytesToRead : INT64; // Bytes to read from the Source Stream
  725. BlockSize : INT64; // Bytes to write out for the current record
  726. BEGIN
  727. ClearDirRec (DirRec);
  728. DirRec.Name := TarFilename;
  729. DirRec.Size := Stream.Size - Stream.Position;
  730. DirRec.DateTime := FileDateGmt;
  731. DirRec.Permissions := FPermissions;
  732. DirRec.FileType := ftNormal;
  733. DirRec.LinkName := '';
  734. DirRec.UID := FUID;
  735. DirRec.GID := FGID;
  736. DirRec.UserName := FUserName;
  737. DirRec.GroupName := FGroupName;
  738. DirRec.ChecksumOK := TRUE;
  739. DirRec.Mode := FMode;
  740. DirRec.Magic := FMagic;
  741. DirRec.MajorDevNo := 0;
  742. DirRec.MinorDevNo := 0;
  743. WriteTarHeader (FStream, DirRec);
  744. BytesToRead := DirRec.Size;
  745. WHILE BytesToRead > 0 DO BEGIN
  746. BlockSize := BytesToRead;
  747. IF BlockSize > RECORDSIZE THEN BlockSize := RECORDSIZE;
  748. FillChar (Rec, RECORDSIZE, 0);
  749. Stream.Read (Rec, BlockSize);
  750. FStream.Write (Rec, RECORDSIZE);
  751. DEC (BytesToRead, BlockSize);
  752. END;
  753. END;
  754. PROCEDURE TTarWriter.AddString (Contents : AnsiString; TarFilename : AnsiString; FileDateGmt : TDateTime); // rawbytestring
  755. VAR
  756. S : TStringStream;
  757. BEGIN
  758. S := TStringStream.Create (Contents);
  759. TRY
  760. AddStream (S, TarFilename, FileDateGmt);
  761. FINALLY
  762. S.Free
  763. END
  764. END;
  765. PROCEDURE TTarWriter.AddDir (Dirname : AnsiString; DateGmt : TDateTime; MaxDirSize : INT64 = 0);
  766. VAR
  767. DirRec : TTarDirRec;
  768. BEGIN
  769. ClearDirRec (DirRec);
  770. DirRec.Name := Dirname;
  771. DirRec.Size := MaxDirSize;
  772. DirRec.DateTime := DateGmt;
  773. DirRec.Permissions := FPermissions;
  774. DirRec.FileType := ftDirectory;
  775. DirRec.LinkName := '';
  776. DirRec.UID := FUID;
  777. DirRec.GID := FGID;
  778. DirRec.UserName := FUserName;
  779. DirRec.GroupName := FGroupName;
  780. DirRec.ChecksumOK := TRUE;
  781. DirRec.Mode := FMode;
  782. DirRec.Magic := FMagic;
  783. DirRec.MajorDevNo := 0;
  784. DirRec.MinorDevNo := 0;
  785. WriteTarHeader (FStream, DirRec);
  786. END;
  787. PROCEDURE TTarWriter.AddSymbolicLink (Filename, Linkname : AnsiString; DateGmt : TDateTime);
  788. VAR
  789. DirRec : TTarDirRec;
  790. BEGIN
  791. ClearDirRec (DirRec);
  792. DirRec.Name := Filename;
  793. DirRec.Size := 0;
  794. DirRec.DateTime := DateGmt;
  795. DirRec.Permissions := FPermissions;
  796. DirRec.FileType := ftSymbolicLink;
  797. DirRec.LinkName := Linkname;
  798. DirRec.UID := FUID;
  799. DirRec.GID := FGID;
  800. DirRec.UserName := FUserName;
  801. DirRec.GroupName := FGroupName;
  802. DirRec.ChecksumOK := TRUE;
  803. DirRec.Mode := FMode;
  804. DirRec.Magic := FMagic;
  805. DirRec.MajorDevNo := 0;
  806. DirRec.MinorDevNo := 0;
  807. WriteTarHeader (FStream, DirRec);
  808. END;
  809. PROCEDURE TTarWriter.AddLink (Filename, Linkname : AnsiString; DateGmt : TDateTime);
  810. VAR
  811. DirRec : TTarDirRec;
  812. BEGIN
  813. ClearDirRec (DirRec);
  814. DirRec.Name := Filename;
  815. DirRec.Size := 0;
  816. DirRec.DateTime := DateGmt;
  817. DirRec.Permissions := FPermissions;
  818. DirRec.FileType := ftLink;
  819. DirRec.LinkName := Linkname;
  820. DirRec.UID := FUID;
  821. DirRec.GID := FGID;
  822. DirRec.UserName := FUserName;
  823. DirRec.GroupName := FGroupName;
  824. DirRec.ChecksumOK := TRUE;
  825. DirRec.Mode := FMode;
  826. DirRec.Magic := FMagic;
  827. DirRec.MajorDevNo := 0;
  828. DirRec.MinorDevNo := 0;
  829. WriteTarHeader (FStream, DirRec);
  830. END;
  831. PROCEDURE TTarWriter.AddVolumeHeader (VolumeId : AnsiString; DateGmt : TDateTime);
  832. VAR
  833. DirRec : TTarDirRec;
  834. BEGIN
  835. ClearDirRec (DirRec);
  836. DirRec.Name := VolumeId;
  837. DirRec.Size := 0;
  838. DirRec.DateTime := DateGmt;
  839. DirRec.Permissions := FPermissions;
  840. DirRec.FileType := ftVolumeHeader;
  841. DirRec.LinkName := '';
  842. DirRec.UID := FUID;
  843. DirRec.GID := FGID;
  844. DirRec.UserName := FUserName;
  845. DirRec.GroupName := FGroupName;
  846. DirRec.ChecksumOK := TRUE;
  847. DirRec.Mode := FMode;
  848. DirRec.Magic := FMagic;
  849. DirRec.MajorDevNo := 0;
  850. DirRec.MinorDevNo := 0;
  851. WriteTarHeader (FStream, DirRec);
  852. END;
  853. PROCEDURE TTarWriter.Finalize;
  854. // Writes the End-Of-File Tag
  855. // Data after this tag will be ignored
  856. // The destructor calls this automatically if you didn't do it before
  857. VAR
  858. Rec : ARRAY [0..RECORDSIZE-1] OF CHAR;
  859. BEGIN
  860. FillChar (Rec, SizeOf (Rec), 0);
  861. FStream.Write (Rec, RECORDSIZE);
  862. {
  863. Avoid warning: 'tar: A lone zero block at *'
  864. The reason for this message is that GNU tar format has been changed
  865. to require TWO zero blocks marking the end of the archive.
  866. Thus write a second zero block.
  867. }
  868. FStream.Write (Rec, RECORDSIZE);
  869. FFinalized := TRUE;
  870. END;
  871. END.