libtar.pp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  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. Result := FileDateToDateTime (SearchRec.Time);
  270. {$IFDEF Kylix}
  271. GetTimeOfDay (TimeVal, TimeZone);
  272. {$ELSE}
  273. fpGetTimeOfDay (@TimeVal, @TimeZone);
  274. {$ENDIF}
  275. Result := Result + TimeZone.tz_minuteswest / (60 * 24);
  276. END;
  277. (*$ENDIF *)
  278. end;
  279. PROCEDURE ClearDirRec (VAR DirRec : TTarDirRec);
  280. // This is included because a FillChar (DirRec, SizeOf (DirRec), 0)
  281. // will destroy the long string pointers, leading to strange bugs
  282. BEGIN
  283. WITH DirRec DO BEGIN
  284. Name := '';
  285. Size := 0;
  286. DateTime := 0.0;
  287. Permissions := [];
  288. FileType := TFileType (0);
  289. LinkName := '';
  290. UID := 0;
  291. GID := 0;
  292. UserName := '';
  293. GroupName := '';
  294. ChecksumOK := FALSE;
  295. Mode := [];
  296. Magic := '';
  297. MajorDevNo := 0;
  298. MinorDevNo := 0;
  299. FilePos := 0;
  300. END;
  301. END;
  302. (*
  303. ===============================================================================================
  304. TAR format
  305. ===============================================================================================
  306. *)
  307. CONST
  308. RECORDSIZE = 512;
  309. NAMSIZ = 100;
  310. TUNMLEN = 32;
  311. TGNMLEN = 32;
  312. CHKBLANKS = #32#32#32#32#32#32#32#32;
  313. TYPE
  314. TTarHeader = PACKED RECORD
  315. Name : ARRAY [0..NAMSIZ-1] OF AnsiChar;
  316. Mode : ARRAY [0..7] OF AnsiChar;
  317. UID : ARRAY [0..7] OF AnsiChar;
  318. GID : ARRAY [0..7] OF AnsiChar;
  319. Size : ARRAY [0..11] OF AnsiChar;
  320. MTime : ARRAY [0..11] OF AnsiChar;
  321. ChkSum : ARRAY [0..7] OF AnsiChar;
  322. LinkFlag : AnsiChar;
  323. LinkName : ARRAY [0..NAMSIZ-1] OF AnsiChar;
  324. Magic : ARRAY [0..7] OF AnsiChar;
  325. UName : ARRAY [0..TUNMLEN-1] OF AnsiChar;
  326. GName : ARRAY [0..TGNMLEN-1] OF AnsiChar;
  327. DevMajor : ARRAY [0..7] OF AnsiChar;
  328. DevMinor : ARRAY [0..7] OF AnsiChar;
  329. END;
  330. FUNCTION ExtractText (P : PAnsiChar) : AnsiString;
  331. BEGIN
  332. Result := AnsiString(P);
  333. END;
  334. FUNCTION ExtractNumber (P : PAnsiChar) : INTEGER; OVERLOAD;
  335. VAR
  336. Strg : AnsiString;
  337. BEGIN
  338. Strg := Trim (StrPas (P));
  339. P := PAnsiChar (Strg);
  340. Result := 0;
  341. WHILE (P^ <> #32) AND (P^ <> #0) DO BEGIN
  342. Result := (ORD (P^) - ORD ('0')) OR (Result SHL 3);
  343. INC (P);
  344. END;
  345. END;
  346. FUNCTION ExtractNumber64 (P : PAnsiChar) : INT64; OVERLOAD;
  347. VAR
  348. Strg : AnsiString;
  349. BEGIN
  350. Strg := Trim (StrPas (P));
  351. P := PAnsiChar (Strg);
  352. Result := 0;
  353. WHILE (P^ <> #32) AND (P^ <> #0) DO BEGIN
  354. Result := (ORD (P^) - ORD ('0')) OR (Result SHL 3);
  355. INC (P);
  356. END;
  357. END;
  358. FUNCTION ExtractNumber (P : PAnsiChar; MaxLen : INTEGER) : INTEGER; OVERLOAD;
  359. VAR
  360. S0 : ARRAY [0..255] OF AnsiChar;
  361. Strg : AnsiString;
  362. BEGIN
  363. StrLCopy (S0, P, MaxLen);
  364. Strg := Trim (StrPas (S0));
  365. P := PAnsiChar (Strg);
  366. Result := 0;
  367. WHILE (P^ <> #32) AND (P^ <> #0) DO BEGIN
  368. Result := (ORD (P^) - ORD ('0')) OR (Result SHL 3);
  369. INC (P);
  370. END;
  371. END;
  372. FUNCTION ExtractNumber64 (P : PAnsiChar; MaxLen : INTEGER) : INT64; OVERLOAD;
  373. VAR
  374. S0 : ARRAY [0..255] OF AnsiChar;
  375. Strg : AnsiString;
  376. BEGIN
  377. StrLCopy (S0, P, MaxLen);
  378. Strg := Trim (StrPas (S0));
  379. P := PAnsiChar (Strg);
  380. Result := 0;
  381. WHILE (P^ <> #32) AND (P^ <> #0) DO BEGIN
  382. Result := (ORD (P^) - ORD ('0')) OR (Result SHL 3);
  383. INC (P);
  384. END;
  385. END;
  386. FUNCTION Records (Bytes : INT64) : INT64;
  387. BEGIN
  388. Result := Bytes DIV RECORDSIZE;
  389. IF Bytes MOD RECORDSIZE > 0 THEN
  390. INC (Result);
  391. END;
  392. PROCEDURE Octal (N : INTEGER; P : PAnsiChar; Len : INTEGER);
  393. // Makes a string of octal digits
  394. // The string will always be "Len" characters long
  395. VAR
  396. I : INTEGER;
  397. BEGIN
  398. FOR I := Len-2 DOWNTO 0 DO BEGIN
  399. (P+I)^ := AnsiChar (ORD ('0') + ORD (N AND $07));
  400. N := N SHR 3;
  401. END;
  402. FOR I := 0 TO Len-3 DO
  403. IF (P+I)^ = '0'
  404. THEN (P+I)^ := #32
  405. ELSE BREAK;
  406. (P+Len-1)^ := #32;
  407. END;
  408. PROCEDURE Octal64 (N : INT64; P : PAnsiChar; Len : INTEGER);
  409. // Makes a string of octal digits
  410. // The string will always be "Len" characters long
  411. VAR
  412. I : INTEGER;
  413. BEGIN
  414. FOR I := Len-2 DOWNTO 0 DO BEGIN
  415. (P+I)^ := AnsiChar (ORD ('0') + ORD (N AND $07));
  416. N := N SHR 3;
  417. END;
  418. FOR I := 0 TO Len-3 DO
  419. IF (P+I)^ = '0'
  420. THEN (P+I)^ := #32
  421. ELSE BREAK;
  422. (P+Len-1)^ := #32;
  423. END;
  424. PROCEDURE OctalN (N : INTEGER; P : PAnsiChar; Len : INTEGER);
  425. BEGIN
  426. Octal (N, P, Len-1);
  427. (P+Len-1)^ := #0;
  428. END;
  429. PROCEDURE WriteTarHeader (Dest : TStream; DirRec : TTarDirRec);
  430. VAR
  431. Rec : ARRAY [0..RECORDSIZE-1] OF AnsiChar;
  432. TH : TTarHeader ABSOLUTE Rec;
  433. Mode : INTEGER;
  434. NullDate : TDateTime;
  435. Checksum : CARDINAL;
  436. I : INTEGER;
  437. BEGIN
  438. FillChar (Rec, RECORDSIZE, 0);
  439. StrLCopy (TH.Name, PAnsiChar (DirRec.Name), NAMSIZ);
  440. CASE DirRec.FileType OF
  441. ftNormal, ftLink : Mode := $08000;
  442. ftSymbolicLink : Mode := $0A000;
  443. ftDirectory : Mode := $04000;
  444. ELSE Mode := 0;
  445. END;
  446. IF tmSaveText IN DirRec.Mode THEN Mode := Mode OR $0200;
  447. IF tmSetGid IN DirRec.Mode THEN Mode := Mode OR $0400;
  448. IF tmSetUid IN DirRec.Mode THEN Mode := Mode OR $0800;
  449. IF tpReadByOwner IN DirRec.Permissions THEN Mode := Mode OR $0100;
  450. IF tpWriteByOwner IN DirRec.Permissions THEN Mode := Mode OR $0080;
  451. IF tpExecuteByOwner IN DirRec.Permissions THEN Mode := Mode OR $0040;
  452. IF tpReadByGroup IN DirRec.Permissions THEN Mode := Mode OR $0020;
  453. IF tpWriteByGroup IN DirRec.Permissions THEN Mode := Mode OR $0010;
  454. IF tpExecuteByGroup IN DirRec.Permissions THEN Mode := Mode OR $0008;
  455. IF tpReadByOther IN DirRec.Permissions THEN Mode := Mode OR $0004;
  456. IF tpWriteByOther IN DirRec.Permissions THEN Mode := Mode OR $0002;
  457. IF tpExecuteByOther IN DirRec.Permissions THEN Mode := Mode OR $0001;
  458. OctalN (Mode, @TH.Mode, 8);
  459. OctalN (DirRec.UID, @TH.UID, 8);
  460. OctalN (DirRec.GID, @TH.GID, 8);
  461. Octal64 (DirRec.Size, @TH.Size, 12);
  462. NullDate := EncodeDate (1970, 1, 1);
  463. IF DirRec.DateTime >= NullDate
  464. THEN Octal (Trunc ((DirRec.DateTime - NullDate) * 86400.0), @TH.MTime, 12)
  465. ELSE Octal (Trunc ( NullDate * 86400.0), @TH.MTime, 12);
  466. CASE DirRec.FileType OF
  467. ftNormal : TH.LinkFlag := '0';
  468. ftLink : TH.LinkFlag := '1';
  469. ftSymbolicLink : TH.LinkFlag := '2';
  470. ftCharacter : TH.LinkFlag := '3';
  471. ftBlock : TH.LinkFlag := '4';
  472. ftDirectory : TH.LinkFlag := '5';
  473. ftFifo : TH.LinkFlag := '6';
  474. ftContiguous : TH.LinkFlag := '7';
  475. ftDumpDir : TH.LinkFlag := 'D';
  476. ftMultiVolume : TH.LinkFlag := 'M';
  477. ftVolumeHeader : TH.LinkFlag := 'V';
  478. END;
  479. StrLCopy (TH.LinkName, PAnsiChar (DirRec.LinkName), NAMSIZ);
  480. StrLCopy (TH.Magic, PAnsiChar (DirRec.Magic + #32#32#32#32#32#32#32#32), 7);
  481. StrLCopy (TH.UName, PAnsiChar (DirRec.UserName), TUNMLEN);
  482. StrLCopy (TH.GName, PAnsiChar (DirRec.GroupName), TGNMLEN);
  483. OctalN (DirRec.MajorDevNo, @TH.DevMajor, 8);
  484. OctalN (DirRec.MinorDevNo, @TH.DevMinor, 8);
  485. StrMove (TH.ChkSum, CHKBLANKS, 8);
  486. CheckSum := 0;
  487. FOR I := 0 TO SizeOf (TTarHeader)-1 DO
  488. INC (CheckSum, INTEGER (ORD (Rec [I])));
  489. OctalN (CheckSum, @TH.ChkSum, 8);
  490. Dest.Write (TH, RECORDSIZE);
  491. END;
  492. (*
  493. ===============================================================================================
  494. TTarArchive
  495. ===============================================================================================
  496. *)
  497. CONSTRUCTOR TTarArchive.Create (Stream : TStream);
  498. BEGIN
  499. INHERITED Create;
  500. FStream := Stream;
  501. FOwnsStream := FALSE;
  502. Reset;
  503. END;
  504. CONSTRUCTOR TTarArchive.Create (Filename : STRING; FileMode : WORD);
  505. BEGIN
  506. INHERITED Create;
  507. FStream := TFileStream.Create (Filename, FileMode);
  508. FOwnsStream := TRUE;
  509. Reset;
  510. END;
  511. DESTRUCTOR TTarArchive.Destroy;
  512. BEGIN
  513. IF FOwnsStream THEN
  514. FStream.Free;
  515. INHERITED Destroy;
  516. END;
  517. PROCEDURE TTarArchive.Reset;
  518. // Reset File Pointer
  519. BEGIN
  520. FStream.Position := 0;
  521. FBytesToGo := 0;
  522. END;
  523. FUNCTION TTarArchive.FindNext (VAR DirRec : TTarDirRec) : BOOLEAN;
  524. // Reads next Directory Info Record
  525. // The Stream pointer must point to the first byte of the tar header
  526. VAR
  527. Rec : ARRAY [0..RECORDSIZE-1] OF CHAR;
  528. CurFilePos : INTEGER;
  529. Header : TTarHeader ABSOLUTE Rec;
  530. I : INTEGER;
  531. HeaderChkSum : WORD;
  532. Checksum : CARDINAL;
  533. BEGIN
  534. // --- Scan until next pointer
  535. IF FBytesToGo > 0 THEN
  536. FStream.Seek (Records (FBytesToGo) * RECORDSIZE, soFromCurrent);
  537. // --- EOF reached?
  538. Result := FALSE;
  539. CurFilePos := FStream.Position;
  540. TRY
  541. FStream.ReadBuffer (Rec, RECORDSIZE);
  542. if Rec [0] = #0 THEN EXIT; // EOF reached
  543. EXCEPT
  544. EXIT; // EOF reached, too
  545. END;
  546. Result := TRUE;
  547. ClearDirRec (DirRec);
  548. DirRec.FilePos := CurFilePos;
  549. DirRec.Name := ExtractText (Header.Name);
  550. DirRec.Size := ExtractNumber64 (@Header.Size, 12);
  551. DirRec.DateTime := EncodeDate (1970, 1, 1) + (ExtractNumber (@Header.MTime, 12) / 86400.0);
  552. I := ExtractNumber (@Header.Mode);
  553. IF I AND $0100 <> 0 THEN Include (DirRec.Permissions, tpReadByOwner);
  554. IF I AND $0080 <> 0 THEN Include (DirRec.Permissions, tpWriteByOwner);
  555. IF I AND $0040 <> 0 THEN Include (DirRec.Permissions, tpExecuteByOwner);
  556. IF I AND $0020 <> 0 THEN Include (DirRec.Permissions, tpReadByGroup);
  557. IF I AND $0010 <> 0 THEN Include (DirRec.Permissions, tpWriteByGroup);
  558. IF I AND $0008 <> 0 THEN Include (DirRec.Permissions, tpExecuteByGroup);
  559. IF I AND $0004 <> 0 THEN Include (DirRec.Permissions, tpReadByOther);
  560. IF I AND $0002 <> 0 THEN Include (DirRec.Permissions, tpWriteByOther);
  561. IF I AND $0001 <> 0 THEN Include (DirRec.Permissions, tpExecuteByOther);
  562. IF I AND $0200 <> 0 THEN Include (DirRec.Mode, tmSaveText);
  563. IF I AND $0400 <> 0 THEN Include (DirRec.Mode, tmSetGid);
  564. IF I AND $0800 <> 0 THEN Include (DirRec.Mode, tmSetUid);
  565. CASE Header.LinkFlag OF
  566. #0, '0' : DirRec.FileType := ftNormal;
  567. '1' : DirRec.FileType := ftLink;
  568. '2' : DirRec.FileType := ftSymbolicLink;
  569. '3' : DirRec.FileType := ftCharacter;
  570. '4' : DirRec.FileType := ftBlock;
  571. '5' : DirRec.FileType := ftDirectory;
  572. '6' : DirRec.FileType := ftFifo;
  573. '7' : DirRec.FileType := ftContiguous;
  574. 'D' : DirRec.FileType := ftDumpDir;
  575. 'M' : DirRec.FileType := ftMultiVolume;
  576. 'V' : DirRec.FileType := ftVolumeHeader;
  577. END;
  578. DirRec.LinkName := ExtractText (Header.LinkName);
  579. DirRec.UID := ExtractNumber (@Header.UID);
  580. DirRec.GID := ExtractNumber (@Header.GID);
  581. DirRec.UserName := ExtractText (Header.UName);
  582. DirRec.GroupName := ExtractText (Header.GName);
  583. DirRec.Magic := Trim (ExtractText (Header.Magic));
  584. DirRec.MajorDevNo := ExtractNumber (@Header.DevMajor);
  585. DirRec.MinorDevNo := ExtractNumber (@Header.DevMinor);
  586. HeaderChkSum := ExtractNumber (@Header.ChkSum); // Calc Checksum
  587. CheckSum := 0;
  588. StrMove (Header.ChkSum, CHKBLANKS, 8);
  589. FOR I := 0 TO SizeOf (TTarHeader)-1 DO
  590. INC (CheckSum, INTEGER (ORD (Rec [I])));
  591. DirRec.CheckSumOK := WORD (CheckSum) = WORD (HeaderChkSum);
  592. IF DirRec.FileType in [ftLink, ftSymbolicLink, ftDirectory, ftFifo, ftVolumeHeader]
  593. THEN FBytesToGo := 0
  594. ELSE FBytesToGo := DirRec.Size;
  595. END;
  596. PROCEDURE TTarArchive.ReadFile (Buffer : POINTER);
  597. // Reads file data for the last Directory Record. The entire file is read into the buffer.
  598. // The buffer must be large enough to take up the whole file.
  599. VAR
  600. RestBytes : INTEGER;
  601. BEGIN
  602. IF FBytesToGo = 0 THEN EXIT;
  603. RestBytes := Records (FBytesToGo) * RECORDSIZE - FBytesToGo;
  604. FStream.ReadBuffer (Buffer^, FBytesToGo);
  605. FStream.Seek (RestBytes, soFromCurrent);
  606. FBytesToGo := 0;
  607. END;
  608. PROCEDURE TTarArchive.ReadFile (Stream : TStream);
  609. // Reads file data for the last Directory Record.
  610. // The entire file is written out to the stream.
  611. // The stream is left at its current position prior to writing
  612. VAR
  613. RestBytes : INTEGER;
  614. BEGIN
  615. IF FBytesToGo = 0 THEN EXIT;
  616. RestBytes := Records (FBytesToGo) * RECORDSIZE - FBytesToGo;
  617. Stream.CopyFrom (FStream, FBytesToGo);
  618. FStream.Seek (RestBytes, soFromCurrent);
  619. FBytesToGo := 0;
  620. END;
  621. PROCEDURE TTarArchive.ReadFile (Filename : STRING);
  622. // Reads file data for the last Directory Record.
  623. // The entire file is saved in the given Filename
  624. VAR
  625. FS : TFileStream;
  626. BEGIN
  627. FS := TFileStream.Create (Filename, fmCreate);
  628. TRY
  629. ReadFile (FS);
  630. FINALLY
  631. FS.Free;
  632. END;
  633. END;
  634. FUNCTION TTarArchive.ReadFile : STRING;
  635. // Reads file data for the last Directory Record. The entire file is returned
  636. // as a large ANSI string.
  637. VAR
  638. RestBytes : INTEGER;
  639. BEGIN
  640. IF FBytesToGo = 0 THEN EXIT;
  641. RestBytes := Records (FBytesToGo) * RECORDSIZE - FBytesToGo;
  642. SetLength (Result, FBytesToGo);
  643. FStream.ReadBuffer (PAnsiChar (Result)^, FBytesToGo);
  644. FStream.Seek (RestBytes, soFromCurrent);
  645. FBytesToGo := 0;
  646. END;
  647. PROCEDURE TTarArchive.GetFilePos (VAR Current, Size : INT64);
  648. // Returns the Current Position in the TAR stream
  649. BEGIN
  650. Current := FStream.Position;
  651. Size := FStream.Size;
  652. END;
  653. PROCEDURE TTarArchive.SetFilePos (NewPos : INT64); // Set new Current File Position
  654. BEGIN
  655. IF NewPos < FStream.Size THEN
  656. FStream.Seek (NewPos, soFromBeginning);
  657. END;
  658. (*
  659. ===============================================================================================
  660. TTarWriter
  661. ===============================================================================================
  662. *)
  663. CONSTRUCTOR TTarWriter.CreateEmpty;
  664. VAR
  665. TP : TTarPermission;
  666. BEGIN
  667. INHERITED Create;
  668. FOwnsStream := FALSE;
  669. FFinalized := FALSE;
  670. FPermissions := [];
  671. FOR TP := Low (TP) TO High (TP) DO
  672. Include (FPermissions, TP);
  673. FUID := 0;
  674. FGID := 0;
  675. FUserName := '';
  676. FGroupName := '';
  677. FMode := [];
  678. FMagic := 'ustar';
  679. END;
  680. CONSTRUCTOR TTarWriter.Create (TargetStream : TStream);
  681. BEGIN
  682. CreateEmpty;
  683. FStream := TargetStream;
  684. FOwnsStream := FALSE;
  685. END;
  686. CONSTRUCTOR TTarWriter.Create (TargetFilename : STRING; Mode : INTEGER = fmCreate);
  687. BEGIN
  688. CreateEmpty;
  689. FStream := TFileStream.Create (TargetFilename, Mode);
  690. FOwnsStream := TRUE;
  691. END;
  692. DESTRUCTOR TTarWriter.Destroy;
  693. BEGIN
  694. IF NOT FFinalized THEN BEGIN
  695. Finalize;
  696. FFinalized := TRUE;
  697. END;
  698. IF FOwnsStream THEN
  699. FStream.Free;
  700. INHERITED Destroy;
  701. END;
  702. PROCEDURE TTarWriter.AddFile (Filename : STRING; TarFilename : AnsiString = '');
  703. VAR
  704. S : TFileStream;
  705. Date : TDateTime;
  706. BEGIN
  707. Date := FileTimeGMT (Filename);
  708. IF TarFilename = '' THEN
  709. TarFilename := ConvertFilename (Filename)
  710. ELSE TarFilename := ConvertFilename (TarFilename);
  711. S := TFileStream.Create (Filename, fmOpenRead OR fmShareDenyWrite);
  712. TRY
  713. AddStream (S, TarFilename, Date);
  714. FINALLY
  715. S.Free
  716. END;
  717. END;
  718. PROCEDURE TTarWriter.AddStream (Stream : TStream; TarFilename : AnsiString; FileDateGmt : TDateTime);
  719. VAR
  720. DirRec : TTarDirRec;
  721. Rec : ARRAY [0..RECORDSIZE-1] OF CHAR;
  722. BytesToRead : INT64; // Bytes to read from the Source Stream
  723. BlockSize : INT64; // Bytes to write out for the current record
  724. BEGIN
  725. ClearDirRec (DirRec);
  726. DirRec.Name := TarFilename;
  727. DirRec.Size := Stream.Size - Stream.Position;
  728. DirRec.DateTime := FileDateGmt;
  729. DirRec.Permissions := FPermissions;
  730. DirRec.FileType := ftNormal;
  731. DirRec.LinkName := '';
  732. DirRec.UID := FUID;
  733. DirRec.GID := FGID;
  734. DirRec.UserName := FUserName;
  735. DirRec.GroupName := FGroupName;
  736. DirRec.ChecksumOK := TRUE;
  737. DirRec.Mode := FMode;
  738. DirRec.Magic := FMagic;
  739. DirRec.MajorDevNo := 0;
  740. DirRec.MinorDevNo := 0;
  741. WriteTarHeader (FStream, DirRec);
  742. BytesToRead := DirRec.Size;
  743. WHILE BytesToRead > 0 DO BEGIN
  744. BlockSize := BytesToRead;
  745. IF BlockSize > RECORDSIZE THEN BlockSize := RECORDSIZE;
  746. FillChar (Rec, RECORDSIZE, 0);
  747. Stream.Read (Rec, BlockSize);
  748. FStream.Write (Rec, RECORDSIZE);
  749. DEC (BytesToRead, BlockSize);
  750. END;
  751. END;
  752. PROCEDURE TTarWriter.AddString (Contents : AnsiString; TarFilename : AnsiString; FileDateGmt : TDateTime); // rawbytestring
  753. VAR
  754. S : TStringStream;
  755. BEGIN
  756. S := TStringStream.Create (Contents);
  757. TRY
  758. AddStream (S, TarFilename, FileDateGmt);
  759. FINALLY
  760. S.Free
  761. END
  762. END;
  763. PROCEDURE TTarWriter.AddDir (Dirname : AnsiString; DateGmt : TDateTime; MaxDirSize : INT64 = 0);
  764. VAR
  765. DirRec : TTarDirRec;
  766. BEGIN
  767. ClearDirRec (DirRec);
  768. DirRec.Name := Dirname;
  769. DirRec.Size := MaxDirSize;
  770. DirRec.DateTime := DateGmt;
  771. DirRec.Permissions := FPermissions;
  772. DirRec.FileType := ftDirectory;
  773. DirRec.LinkName := '';
  774. DirRec.UID := FUID;
  775. DirRec.GID := FGID;
  776. DirRec.UserName := FUserName;
  777. DirRec.GroupName := FGroupName;
  778. DirRec.ChecksumOK := TRUE;
  779. DirRec.Mode := FMode;
  780. DirRec.Magic := FMagic;
  781. DirRec.MajorDevNo := 0;
  782. DirRec.MinorDevNo := 0;
  783. WriteTarHeader (FStream, DirRec);
  784. END;
  785. PROCEDURE TTarWriter.AddSymbolicLink (Filename, Linkname : AnsiString; DateGmt : TDateTime);
  786. VAR
  787. DirRec : TTarDirRec;
  788. BEGIN
  789. ClearDirRec (DirRec);
  790. DirRec.Name := Filename;
  791. DirRec.Size := 0;
  792. DirRec.DateTime := DateGmt;
  793. DirRec.Permissions := FPermissions;
  794. DirRec.FileType := ftSymbolicLink;
  795. DirRec.LinkName := Linkname;
  796. DirRec.UID := FUID;
  797. DirRec.GID := FGID;
  798. DirRec.UserName := FUserName;
  799. DirRec.GroupName := FGroupName;
  800. DirRec.ChecksumOK := TRUE;
  801. DirRec.Mode := FMode;
  802. DirRec.Magic := FMagic;
  803. DirRec.MajorDevNo := 0;
  804. DirRec.MinorDevNo := 0;
  805. WriteTarHeader (FStream, DirRec);
  806. END;
  807. PROCEDURE TTarWriter.AddLink (Filename, Linkname : AnsiString; DateGmt : TDateTime);
  808. VAR
  809. DirRec : TTarDirRec;
  810. BEGIN
  811. ClearDirRec (DirRec);
  812. DirRec.Name := Filename;
  813. DirRec.Size := 0;
  814. DirRec.DateTime := DateGmt;
  815. DirRec.Permissions := FPermissions;
  816. DirRec.FileType := ftLink;
  817. DirRec.LinkName := Linkname;
  818. DirRec.UID := FUID;
  819. DirRec.GID := FGID;
  820. DirRec.UserName := FUserName;
  821. DirRec.GroupName := FGroupName;
  822. DirRec.ChecksumOK := TRUE;
  823. DirRec.Mode := FMode;
  824. DirRec.Magic := FMagic;
  825. DirRec.MajorDevNo := 0;
  826. DirRec.MinorDevNo := 0;
  827. WriteTarHeader (FStream, DirRec);
  828. END;
  829. PROCEDURE TTarWriter.AddVolumeHeader (VolumeId : AnsiString; DateGmt : TDateTime);
  830. VAR
  831. DirRec : TTarDirRec;
  832. BEGIN
  833. ClearDirRec (DirRec);
  834. DirRec.Name := VolumeId;
  835. DirRec.Size := 0;
  836. DirRec.DateTime := DateGmt;
  837. DirRec.Permissions := FPermissions;
  838. DirRec.FileType := ftVolumeHeader;
  839. DirRec.LinkName := '';
  840. DirRec.UID := FUID;
  841. DirRec.GID := FGID;
  842. DirRec.UserName := FUserName;
  843. DirRec.GroupName := FGroupName;
  844. DirRec.ChecksumOK := TRUE;
  845. DirRec.Mode := FMode;
  846. DirRec.Magic := FMagic;
  847. DirRec.MajorDevNo := 0;
  848. DirRec.MinorDevNo := 0;
  849. WriteTarHeader (FStream, DirRec);
  850. END;
  851. PROCEDURE TTarWriter.Finalize;
  852. // Writes the End-Of-File Tag
  853. // Data after this tag will be ignored
  854. // The destructor calls this automatically if you didn't do it before
  855. VAR
  856. Rec : ARRAY [0..RECORDSIZE-1] OF CHAR;
  857. BEGIN
  858. FillChar (Rec, SizeOf (Rec), 0);
  859. FStream.Write (Rec, RECORDSIZE);
  860. {
  861. Avoid warning: 'tar: A lone zero block at *'
  862. The reason for this message is that GNU tar format has been changed
  863. to require TWO zero blocks marking the end of the archive.
  864. Thus write a second zero block.
  865. }
  866. FStream.Write (Rec, RECORDSIZE);
  867. FFinalized := TRUE;
  868. END;
  869. END.