libtar.pp 35 KB

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