VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/misc/tar.cpp@ 32566

Last change on this file since 32566 was 32566, checked in by vboxsync, 15 years ago

Runtime: in-memory extraction support for tar

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 30.1 KB
Line 
1/* $Id: tar.cpp 32566 2010-09-16 14:42:36Z vboxsync $ */
2/** @file
3 * IPRT - Tar archive I/O.
4 */
5
6/*
7 * Copyright (C) 2009 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*******************************************************************************
29* Header Files *
30*******************************************************************************/
31#include "internal/iprt.h"
32#include <iprt/tar.h>
33
34#include <iprt/asm.h>
35#include <iprt/assert.h>
36#include <iprt/err.h>
37#include <iprt/file.h>
38#include <iprt/mem.h>
39#include <iprt/path.h>
40#include <iprt/string.h>
41
42/*******************************************************************************
43* Structures and Typedefs *
44*******************************************************************************/
45
46/** @name RTTARRECORD::h::linkflag
47 * @{ */
48#define LF_OLDNORMAL '\0' /**< Normal disk file, Unix compatible */
49#define LF_NORMAL '0' /**< Normal disk file */
50#define LF_LINK '1' /**< Link to previously dumped file */
51#define LF_SYMLINK '2' /**< Symbolic link */
52#define LF_CHR '3' /**< Character special file */
53#define LF_BLK '4' /**< Block special file */
54#define LF_DIR '5' /**< Directory */
55#define LF_FIFO '6' /**< FIFO special file */
56#define LF_CONTIG '7' /**< Contiguous file */
57/** @} */
58
59typedef union RTTARRECORD
60{
61 char d[512];
62 struct h
63 {
64 char name[100];
65 char mode[8];
66 char uid[8];
67 char gid[8];
68 char size[12];
69 char mtime[12];
70 char chksum[8];
71 char linkflag;
72 char linkname[100];
73 char magic[8];
74 char uname[32];
75 char gname[32];
76 char devmajor[8];
77 char devminor[8];
78 } h;
79} RTTARRECORD;
80typedef RTTARRECORD *PRTTARRECORD;
81AssertCompileSize(RTTARRECORD, 512);
82AssertCompileMemberOffset(RTTARRECORD, h.size, 100+8*3);
83
84#if 0 /* not currently used */
85typedef struct RTTARFILELIST
86{
87 char *pszFilename;
88 RTTARFILELIST *pNext;
89} RTTARFILELIST;
90typedef RTTARFILELIST *PRTTARFILELIST;
91#endif
92
93/*******************************************************************************
94* Internal Functions *
95*******************************************************************************/
96
97static int rtTarCalcChkSum(PRTTARRECORD pRecord, uint32_t *pChkSum)
98{
99 uint32_t check = 0;
100 uint32_t zero = 0;
101 for (size_t i = 0; i < sizeof(RTTARRECORD); ++i)
102 {
103 /* Calculate the sum of every byte from the header. The checksum field
104 * itself is counted as all blanks. */
105 if ( i < RT_UOFFSETOF(RTTARRECORD, h.chksum)
106 || i >= RT_UOFFSETOF(RTTARRECORD, h.linkflag))
107 check += pRecord->d[i];
108 else
109 check += ' ';
110 /* Additional check if all fields are zero, which indicate EOF. */
111 zero += pRecord->d[i];
112 }
113
114 /* EOF? */
115 if (!zero)
116 return VERR_EOF;
117
118 *pChkSum = check;
119 return VINF_SUCCESS;
120}
121
122static int rtTarCheckHeader(PRTTARRECORD pRecord)
123{
124 uint32_t check;
125 int rc = rtTarCalcChkSum(pRecord, &check);
126 /* EOF? */
127 if (RT_FAILURE(rc))
128 return rc;
129
130 /* Verify the checksum */
131 uint32_t sum;
132 rc = RTStrToUInt32Full(pRecord->h.chksum, 8, &sum);
133 if (RT_SUCCESS(rc) && sum == check)
134 return VINF_SUCCESS;
135 return VERR_TAR_CHKSUM_MISMATCH;
136}
137
138static int rtTarCopyFileFromToBuf(RTFILE hFile, void **ppvBuf, uint64_t *pcbSize, PRTTARRECORD pRecord, const uint64_t cbOverallSize, uint64_t &cbOverallWritten, PFNRTPROGRESS pfnProgressCallback, void *pvUser)
139{
140 int rc = VINF_SUCCESS;
141
142 uint64_t cbToCopy = RTStrToUInt64(pRecord->h.size);
143
144 *ppvBuf = RTMemAlloc(cbToCopy);
145 char* pcsTmp = (char*)*ppvBuf;
146 if (!pcsTmp)
147 return VERR_NO_MEMORY;
148
149 uint64_t cbAllWritten = 0;
150 RTTARRECORD record;
151 /* Copy the content from hFile over to the memory. This is done block
152 * wise in 512 byte steps. After this copying is finished hFile will be on
153 * a 512 byte boundary, regardless if the file copied is 512 byte size
154 * aligned. */
155 for (;;)
156 {
157 if (pfnProgressCallback)
158 pfnProgressCallback((unsigned)(100.0 / cbOverallSize * cbOverallWritten), pvUser);
159 /* Finished already? */
160 if (cbAllWritten == cbToCopy)
161 break;
162 /* Read one block */
163 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
164 if (RT_FAILURE(rc))
165 break;
166 uint64_t cbToWrite = sizeof(record);
167 /* Check for the last block which has not to be 512 bytes in size. */
168 if (cbAllWritten + cbToWrite > cbToCopy)
169 cbToWrite = cbToCopy - cbAllWritten;
170 /* Write the block */
171 memcpy(pcsTmp, &record, cbToWrite);
172 /* Count how many bytes are written already */
173 cbAllWritten += cbToWrite;
174 cbOverallWritten += cbToWrite;
175 pcsTmp += cbToWrite;
176 }
177
178 /* Make sure the called doesn't mix truncated tar files with the official
179 * end indicated by rtTarCalcChkSum. */
180 if (rc == VERR_EOF)
181 rc = VERR_FILE_IO_ERROR;
182
183 if (RT_FAILURE(rc))
184 RTMemFree(*ppvBuf);
185 else
186 *pcbSize = cbToCopy;
187
188 return rc;
189}
190
191static int rtTarCopyFileFrom(RTFILE hFile, const char *pszTargetName, PRTTARRECORD pRecord, const uint64_t cbOverallSize, uint64_t &cbOverallWritten, PFNRTPROGRESS pfnProgressCallback, void *pvUser)
192{
193 RTFILE hNewFile;
194 /* Open the target file */
195 int rc = RTFileOpen(&hNewFile, pszTargetName, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
196 if (RT_FAILURE(rc))
197 return rc;
198
199/**@todo r=bird: Use a bigger buffer here, see comment in rtTarCopyFileTo. */
200
201 uint64_t cbToCopy = RTStrToUInt64(pRecord->h.size);
202 size_t cbAllWritten = 0;
203 RTTARRECORD record;
204 /* Copy the content from hFile over to pszTargetName. This is done block
205 * wise in 512 byte steps. After this copying is finished hFile will be on
206 * a 512 byte boundary, regardless if the file copied is 512 byte size
207 * aligned. */
208 for (;;)
209 {
210 if (pfnProgressCallback)
211 pfnProgressCallback((unsigned)(100.0 / cbOverallSize * cbOverallWritten), pvUser);
212 /* Finished already? */
213 if (cbAllWritten == cbToCopy)
214 break;
215 /* Read one block */
216 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
217 if (RT_FAILURE(rc))
218 break;
219 size_t cbToWrite = sizeof(record);
220 /* Check for the last block which has not to be 512 bytes in size. */
221 if (cbAllWritten + cbToWrite > cbToCopy)
222 cbToWrite = cbToCopy - cbAllWritten;
223 /* Write the block */
224 rc = RTFileWrite(hNewFile, &record, cbToWrite, NULL);
225 if (RT_FAILURE(rc))
226 break;
227 /* Count how many bytes are written already */
228 cbAllWritten += cbToWrite;
229 cbOverallWritten += cbToWrite;
230 }
231
232 /* Now set all file attributes */
233 if (RT_SUCCESS(rc))
234 {
235 int32_t mode;
236 rc = RTStrToInt32Full(pRecord->h.mode, 8, &mode);
237 if (RT_SUCCESS(rc))
238 {
239 mode |= RTFS_TYPE_FILE; /* For now we support regular files only */
240 /* Set the mode */
241 rc = RTFileSetMode(hNewFile, mode);
242 }
243 }
244 /* Make sure the called doesn't mix truncated tar files with the official
245 * end indicated by rtTarCalcChkSum. */
246 else if (rc == VERR_EOF)
247 rc = VERR_FILE_IO_ERROR;
248
249 RTFileClose(hNewFile);
250
251 /* Delete the freshly created file in the case of an error */
252 if (RT_FAILURE(rc))
253 RTFileDelete(pszTargetName);
254
255 return rc;
256}
257
258static int rtTarCopyFileTo(RTFILE hFile, const char *pszSrcName, const uint64_t cbOverallSize, uint64_t &cbOverallWritten, PFNRTPROGRESS pfnProgressCallback, void *pvUser)
259{
260 RTFILE hOldFile;
261 /* Open the source file */
262 int rc = RTFileOpen(&hOldFile, pszSrcName, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
263 if (RT_FAILURE(rc))
264 return rc;
265
266 /* Get the size of the source file */
267 uint64_t cbSize;
268 rc = RTFileGetSize(hOldFile, &cbSize);
269 if (RT_FAILURE(rc))
270 {
271 RTFileClose(hOldFile);
272 return rc;
273 }
274 /* Get some info from the source file */
275 RTFSOBJINFO info;
276 RTUID uid = 0;
277 RTGID gid = 0;
278 RTFMODE fmode = 0600; /* Make some save default */
279 int64_t mtime = 0;
280 /* This isn't critical. Use the defaults if it fails. */
281 rc = RTFileQueryInfo(hOldFile, &info, RTFSOBJATTRADD_UNIX);
282 if (RT_SUCCESS(rc))
283 {
284 fmode = info.Attr.fMode & RTFS_UNIX_MASK;
285 uid = info.Attr.u.Unix.uid;
286 gid = info.Attr.u.Unix.gid;
287 mtime = RTTimeSpecGetSeconds(&info.ModificationTime);
288 }
289
290 /* Fill the header record */
291 RTTARRECORD record;
292 RT_ZERO(record);
293 RTStrPrintf(record.h.name, sizeof(record.h.name), "%s", RTPathFilename(pszSrcName));
294 RTStrPrintf(record.h.mode, sizeof(record.h.mode), "%0.7o", fmode);
295 RTStrPrintf(record.h.uid, sizeof(record.h.uid), "%0.7o", uid);
296 RTStrPrintf(record.h.gid, sizeof(record.h.gid), "%0.7o", gid);
297 RTStrPrintf(record.h.size, sizeof(record.h.size), "%0.11o", cbSize);
298 RTStrPrintf(record.h.mtime, sizeof(record.h.mtime), "%0.11o", mtime);
299 RTStrPrintf(record.h.magic, sizeof(record.h.magic), "ustar ");
300 RTStrPrintf(record.h.uname, sizeof(record.h.uname), "someone");
301 RTStrPrintf(record.h.gname, sizeof(record.h.gname), "someone");
302 record.h.linkflag = LF_NORMAL;
303
304 /* Create the checksum out of the new header */
305 uint32_t chksum;
306 rc = rtTarCalcChkSum(&record, &chksum);
307 if (RT_SUCCESS(rc))
308 {
309 RTStrPrintf(record.h.chksum, sizeof(record.h.chksum), "%0.7o", chksum);
310
311 /* Write the header first */
312 rc = RTFileWrite(hFile, &record, sizeof(record), NULL);
313 if (RT_SUCCESS(rc))
314 {
315/** @todo r=bird: using a 64KB buffer here instead of 0.5KB would probably be
316 * a good thing. */
317 uint64_t cbAllWritten = 0;
318 /* Copy the content from pszSrcName over to hFile. This is done block
319 * wise in 512 byte steps. After this copying is finished hFile will be
320 * on a 512 byte boundary, regardless if the file copied is 512 byte
321 * size aligned. */
322 for (;;)
323 {
324 if (pfnProgressCallback)
325 pfnProgressCallback((unsigned)(100.0 / cbOverallSize * cbOverallWritten), pvUser);
326 if (cbAllWritten >= cbSize)
327 break;
328 size_t cbToRead = sizeof(record);
329 /* Last record? */
330 if (cbAllWritten + cbToRead > cbSize)
331 {
332 /* Initialize with zeros */
333 RT_ZERO(record);
334 cbToRead = cbSize - cbAllWritten;
335 }
336 /* Read one block */
337 rc = RTFileRead(hOldFile, &record, cbToRead, NULL);
338 if (RT_FAILURE(rc))
339 break;
340 /* Write one block */
341 rc = RTFileWrite(hFile, &record, sizeof(record), NULL);
342 if (RT_FAILURE(rc))
343 break;
344 /* Count how many bytes are written already */
345 cbAllWritten += sizeof(record);
346 cbOverallWritten += sizeof(record);
347 }
348
349 /* Make sure the called doesn't mix truncated tar files with the
350 * official end indicated by rtTarCalcChkSum. */
351 if (rc == VERR_EOF)
352 rc = VERR_FILE_IO_ERROR;
353 }
354 }
355
356 RTFileClose(hOldFile);
357 return rc;
358}
359
360static int rtTarSkipData(RTFILE hFile, PRTTARRECORD pRecord)
361{
362 int rc = VINF_SUCCESS;
363 /* Seek over the data parts (512 bytes aligned) */
364 int64_t offSeek = RT_ALIGN(RTStrToInt64(pRecord->h.size), sizeof(RTTARRECORD));
365 if (offSeek > 0)
366 rc = RTFileSeek(hFile, offSeek, RTFILE_SEEK_CURRENT, NULL);
367 return rc;
368}
369
370static int rtTarGetFilesOverallSize(RTFILE hFile, const char * const *papszFiles, size_t cFiles, uint64_t *pcbOverallSize)
371{
372 int rc;
373 size_t cFound = 0;
374 RTTARRECORD record;
375 for (;;)
376 {
377/** @todo r=bird: the reading, validation and EOF check done here should be
378 * moved to a separate helper function. That would make it easiser to
379 * distinguish genuine-end-of-tar-file and VERR_EOF caused by a
380 * trunacted file. That said, rtTarSkipData won't return VERR_EOF, at
381 * least not on unix, since it's not a sin to seek beyond the end of a
382 * file. */
383 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
384 /* Check for error or EOF. */
385 if (RT_FAILURE(rc))
386 break;
387 /* Check for EOF & data integrity */
388 rc = rtTarCheckHeader(&record);
389 if (RT_FAILURE(rc))
390 break;
391 /* We support normal files only */
392 if ( record.h.linkflag == LF_OLDNORMAL
393 || record.h.linkflag == LF_NORMAL)
394 {
395 for (size_t i = 0; i < cFiles; ++i)
396 {
397 if (!RTStrCmp(record.h.name, papszFiles[i]))
398 {
399 uint64_t cbSize;
400 /* Get the file size */
401 rc = RTStrToUInt64Full(record.h.size, 8, &cbSize);
402 /* Sum up the overall size */
403 *pcbOverallSize += cbSize;
404 ++cFound;
405 break;
406 }
407 }
408 if ( cFound == cFiles
409 || RT_FAILURE(rc))
410 break;
411 }
412 rc = rtTarSkipData(hFile, &record);
413 if (RT_FAILURE(rc))
414 break;
415 }
416 /* Make sure the file pointer is at the begin of the file again. */
417 if (RT_SUCCESS(rc))
418 rc = RTFileSeek(hFile, 0, RTFILE_SEEK_BEGIN, 0);
419 return rc;
420}
421
422/*******************************************************************************
423* Public Functions *
424*******************************************************************************/
425
426RTR3DECL(int) RTTarQueryFileExists(const char *pszTarFile, const char *pszFile)
427{
428 /* Validate input */
429 AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
430 AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
431
432 /* Open the tar file */
433 RTFILE hFile;
434 int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
435 if (RT_FAILURE(rc))
436 return rc;
437
438 bool fFound = false;
439 RTTARRECORD record;
440 for (;;)
441 {
442/** @todo r=bird: the reading, validation and EOF check done here should be
443 * moved to a separate helper function. That would make it easiser to
444 * distinguish genuine-end-of-tar-file and VERR_EOF caused by a
445 * trunacted file. That said, rtTarSkipData won't return VERR_EOF, at
446 * least not on unix, since it's not a sin to seek beyond the end of a
447 * file. */
448 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
449 /* Check for error or EOF. */
450 if (RT_FAILURE(rc))
451 break;
452 /* Check for EOF & data integrity */
453 rc = rtTarCheckHeader(&record);
454 if (RT_FAILURE(rc))
455 break;
456 /* We support normal files only */
457 if ( record.h.linkflag == LF_OLDNORMAL
458 || record.h.linkflag == LF_NORMAL)
459 {
460 if (!RTStrCmp(record.h.name, pszFile))
461 {
462 fFound = true;
463 break;
464 }
465 }
466 rc = rtTarSkipData(hFile, &record);
467 if (RT_FAILURE(rc))
468 break;
469 }
470
471 RTFileClose(hFile);
472
473 if (rc == VERR_EOF)
474 rc = VINF_SUCCESS;
475
476 /* Something found? */
477 if ( RT_SUCCESS(rc)
478 && !fFound)
479 rc = VERR_FILE_NOT_FOUND;
480
481 return rc;
482}
483
484RTR3DECL(int) RTTarList(const char *pszTarFile, char ***ppapszFiles, size_t *pcFiles)
485{
486 /* Validate input */
487 AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
488 AssertPtrReturn(ppapszFiles, VERR_INVALID_POINTER);
489 AssertPtrReturn(pcFiles, VERR_INVALID_POINTER);
490
491 /* Open the tar file */
492 RTFILE hFile;
493 int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
494 if (RT_FAILURE(rc))
495 return rc;
496
497 /* Initialize the file name array with one slot */
498 size_t cFilesAlloc = 1;
499 char **papszFiles = (char**)RTMemAlloc(sizeof(char *));
500 if (!papszFiles)
501 {
502 RTFileClose(hFile);
503 return VERR_NO_MEMORY;
504 }
505
506 /* Iterate through the tar file record by record. Skip data records as we
507 * didn't need them. */
508 RTTARRECORD record;
509 size_t cFiles = 0;
510 for (;;)
511 {
512 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
513 /* Check for error or EOF. */
514 if (RT_FAILURE(rc))
515 break;
516 /* Check for EOF & data integrity */
517 rc = rtTarCheckHeader(&record);
518 if (RT_FAILURE(rc))
519 break;
520 /* We support normal files only */
521 if ( record.h.linkflag == LF_OLDNORMAL
522 || record.h.linkflag == LF_NORMAL)
523 {
524 if (cFiles >= cFilesAlloc)
525 {
526 /* Double the array size, make sure the size doesn't wrap. */
527 void *pvNew = NULL;
528 size_t cbNew = cFilesAlloc * sizeof(char *) * 2;
529 if (cbNew / sizeof(char *) / 2 == cFilesAlloc)
530 pvNew = RTMemRealloc(papszFiles, cbNew);
531 if (!pvNew)
532 {
533 rc = VERR_NO_MEMORY;
534 break;
535 }
536 papszFiles = (char **)pvNew;
537 cFilesAlloc *= 2;
538 }
539
540 /* Duplicate the name */
541 papszFiles[cFiles] = RTStrDup(record.h.name);
542 if (!papszFiles[cFiles])
543 {
544 rc = VERR_NO_MEMORY;
545 break;
546 }
547 cFiles++;
548 }
549 rc = rtTarSkipData(hFile, &record);
550 if (RT_FAILURE(rc))
551 break;
552 }
553
554 RTFileClose(hFile);
555
556 if (rc == VERR_EOF)
557 rc = VINF_SUCCESS;
558
559 /* Return the file array on success, dispose of it on failure. */
560 if (RT_SUCCESS(rc))
561 {
562 *pcFiles = cFiles;
563 *ppapszFiles = papszFiles;
564 }
565 else
566 {
567 while (cFiles-- > 0)
568 RTStrFree(papszFiles[cFiles]);
569 RTMemFree(papszFiles);
570 }
571 return rc;
572}
573
574RTR3DECL(int) RTTarExtractFileToBuf(const char *pszTarFile, void **ppvBuf, uint64_t *pcbSize, const char *pszFile, PFNRTPROGRESS pfnProgressCallback, void *pvUser)
575{
576 /* Validate input */
577 AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
578 AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
579
580 /* Open the tar file */
581 RTFILE hFile;
582 int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
583 if (RT_FAILURE(rc))
584 return rc;
585
586 /* Get the overall size of all files to extract out of the tar archive
587 headers. Only necessary if there is a progress callback. */
588 uint64_t cbOverallSize = 0;
589 if (pfnProgressCallback)
590 rc = rtTarGetFilesOverallSize(hFile, &pszFile, 1, &cbOverallSize);
591 if (RT_SUCCESS(rc))
592 {
593 /* Iterate through the tar file record by record. */
594 RTTARRECORD record;
595 bool fFound = false;
596 uint64_t cbOverallWritten = 0;
597 for (;;)
598 {
599 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
600 /* Check for error or EOF. */
601 if (RT_FAILURE(rc))
602 break;
603 /* Check for EOF & data integrity */
604 rc = rtTarCheckHeader(&record);
605 if (RT_FAILURE(rc))
606 break;
607 /* We support normal files only */
608 if ( record.h.linkflag == LF_OLDNORMAL
609 || record.h.linkflag == LF_NORMAL)
610 {
611 if (!RTStrCmp(record.h.name, pszFile))
612 {
613 fFound = true;
614 rc = rtTarCopyFileFromToBuf(hFile, ppvBuf, pcbSize, &record, cbOverallSize, cbOverallWritten, pfnProgressCallback, pvUser);
615 /* We are finished */
616 break;
617 }
618 else
619 {
620 rc = rtTarSkipData(hFile, &record);
621 if (RT_FAILURE(rc))
622 break;
623 }
624 }
625 }
626
627 if (rc == VERR_EOF)
628 rc = VINF_SUCCESS;
629
630 /* If we didn't found the file, indicate an error */
631 if (!fFound && RT_SUCCESS(rc))
632 rc = VERR_FILE_NOT_FOUND;
633 }
634
635 RTFileClose(hFile);
636 return rc;
637}
638
639RTR3DECL(int) RTTarExtractFiles(const char *pszTarFile, const char *pszOutputDir, const char * const *papszFiles, size_t cFiles, PFNRTPROGRESS pfnProgressCallback, void *pvUser)
640{
641 /* Validate input */
642 AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
643 AssertPtrReturn(pszOutputDir, VERR_INVALID_POINTER);
644 AssertPtrReturn(papszFiles, VERR_INVALID_POINTER);
645
646 /* Open the tar file */
647 RTFILE hFile;
648 int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
649 if (RT_FAILURE(rc))
650 return rc;
651
652 /* Get the overall size of all files to extract out of the tar archive
653 headers. Only necessary if there is a progress callback. */
654 uint64_t cbOverallSize = 0;
655 if (pfnProgressCallback)
656 rc = rtTarGetFilesOverallSize(hFile, papszFiles, cFiles, &cbOverallSize);
657 if (RT_SUCCESS(rc))
658 {
659 /* Iterate through the tar file record by record. */
660 RTTARRECORD record;
661 char **paExtracted = (char **)RTMemTmpAllocZ(sizeof(char *) * cFiles);
662 if (paExtracted)
663 {
664 size_t cExtracted = 0;
665 uint64_t cbOverallWritten = 0;
666 for (;;)
667 {
668 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
669 /* Check for error or EOF. */
670 if (RT_FAILURE(rc))
671 break;
672 /* Check for EOF & data integrity */
673 rc = rtTarCheckHeader(&record);
674 if (RT_FAILURE(rc))
675 break;
676 /* We support normal files only */
677 if ( record.h.linkflag == LF_OLDNORMAL
678 || record.h.linkflag == LF_NORMAL)
679 {
680 bool fFound = false;
681 for (size_t i = 0; i < cFiles; ++i)
682 {
683 if (!RTStrCmp(record.h.name, papszFiles[i]))
684 {
685 fFound = true;
686 if (cExtracted < cFiles)
687 {
688 char *pszTargetFile;
689 rc = RTStrAPrintf(&pszTargetFile, "%s/%s", pszOutputDir, papszFiles[i]);
690 if (rc > 0)
691 {
692 rc = rtTarCopyFileFrom(hFile, pszTargetFile, &record, cbOverallSize, cbOverallWritten, pfnProgressCallback, pvUser);
693 if (RT_SUCCESS(rc))
694 paExtracted[cExtracted++] = pszTargetFile;
695 else
696 RTStrFree(pszTargetFile);
697 }
698 else
699 rc = VERR_NO_MEMORY;
700 }
701 else
702 rc = VERR_ALREADY_EXISTS;
703 break;
704 }
705 }
706 if (RT_FAILURE(rc))
707 break;
708 /* If the current record isn't a file in the file list we have to
709 * skip the data */
710 if (!fFound)
711 {
712 rc = rtTarSkipData(hFile, &record);
713 if (RT_FAILURE(rc))
714 break;
715 }
716 }
717 }
718
719 if (rc == VERR_EOF)
720 rc = VINF_SUCCESS;
721
722 /* If we didn't found all files, indicate an error */
723 if (cExtracted != cFiles && RT_SUCCESS(rc))
724 rc = VERR_FILE_NOT_FOUND;
725
726 /* Cleanup the names of the extracted files, deleting them on failure. */
727 while (cExtracted-- > 0)
728 {
729 if (RT_FAILURE(rc))
730 RTFileDelete(paExtracted[cExtracted]);
731 RTStrFree(paExtracted[cExtracted]);
732 }
733 RTMemTmpFree(paExtracted);
734 }
735 else
736 rc = VERR_NO_TMP_MEMORY;
737 }
738
739 RTFileClose(hFile);
740 return rc;
741}
742
743RTR3DECL(int) RTTarExtractByIndex(const char *pszTarFile, const char *pszOutputDir, size_t iIndex, char **ppszFileName, PFNRTPROGRESS pfnProgressCallback, void *pvUser)
744{
745 /* Validate input */
746 AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
747 AssertPtrReturn(pszOutputDir, VERR_INVALID_POINTER);
748
749 /* Open the tar file */
750 RTFILE hFile;
751 int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
752 if (RT_FAILURE(rc))
753 return rc;
754
755 /* Iterate through the tar file record by record. */
756 RTTARRECORD record;
757 size_t iFile = 0;
758 bool fFound = false;
759 for (;;)
760 {
761 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
762 /* Check for error or EOF. */
763 if (RT_FAILURE(rc))
764 break;
765 /* Check for EOF & data integrity */
766 rc = rtTarCheckHeader(&record);
767 if (RT_FAILURE(rc))
768 break;
769 /* We support normal files only */
770 if ( record.h.linkflag == LF_OLDNORMAL
771 || record.h.linkflag == LF_NORMAL)
772 {
773 if (iIndex == iFile)
774 {
775 fFound = true;
776 char *pszTargetName;
777 rc = RTStrAPrintf(&pszTargetName, "%s/%s", pszOutputDir, record.h.name);
778 if (rc > 0)
779 {
780 uint64_t cbOverallSize;
781 uint64_t cbOverallWritten = 0;
782 /* Get the file size */
783 rc = RTStrToUInt64Full(record.h.size, 8, &cbOverallSize);
784 if (RT_FAILURE(rc))
785 break;
786 rc = rtTarCopyFileFrom(hFile, pszTargetName, &record, cbOverallSize, cbOverallWritten, pfnProgressCallback, pvUser);
787 /* On success pass on the filename if requested. */
788 if ( RT_SUCCESS(rc)
789 && ppszFileName)
790 *ppszFileName = pszTargetName;
791 else
792 RTStrFree(pszTargetName);
793 }
794 else
795 rc = VERR_NO_MEMORY;
796 break;
797 }
798 }
799 rc = rtTarSkipData(hFile, &record);
800 if (RT_FAILURE(rc))
801 break;
802 ++iFile;
803 }
804
805 RTFileClose(hFile);
806
807 if (rc == VERR_EOF)
808 rc = VINF_SUCCESS;
809
810 /* If we didn't found the index, indicate an error */
811 if (!fFound && RT_SUCCESS(rc))
812 rc = VERR_FILE_NOT_FOUND;
813
814 return rc;
815}
816
817RTR3DECL(int) RTTarExtractAll(const char *pszTarFile, const char *pszOutputDir, PFNRTPROGRESS pfnProgressCallback, void *pvUser)
818{
819 /* Validate input */
820 AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
821 AssertPtrReturn(pszOutputDir, VERR_INVALID_POINTER);
822
823 char **papszFiles;
824 size_t cFiles;
825
826 /* First fetch the files names contained in the tar file */
827 int rc = RTTarList(pszTarFile, &papszFiles, &cFiles);
828 if (RT_FAILURE(rc))
829 return rc;
830
831 /* Extract all files */
832 return RTTarExtractFiles(pszTarFile, pszOutputDir, papszFiles, cFiles, pfnProgressCallback, pvUser);
833}
834
835RTR3DECL(int) RTTarCreate(const char *pszTarFile, const char * const *papszFiles, size_t cFiles, PFNRTPROGRESS pfnProgressCallback, void *pvUser)
836{
837 /* Validate input */
838 AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
839 AssertPtrReturn(papszFiles, VERR_INVALID_POINTER);
840
841 /* Open the tar file */
842 RTFILE hFile;
843 int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
844 if (RT_FAILURE(rc))
845 return rc;
846
847 /* Get the overall size of all files to pack into the tar archive. Only
848 necessary if there is a progress callback. */
849 uint64_t cbOverallSize = 0;
850 if (pfnProgressCallback)
851 for (size_t i = 0; i < cFiles; ++i)
852 {
853 uint64_t cbSize;
854 rc = RTFileQuerySize(papszFiles[i], &cbSize);
855 if (RT_FAILURE(rc))
856 break;
857 cbOverallSize += cbSize;
858 }
859
860 if (RT_SUCCESS(rc))
861 {
862 uint64_t cbOverallWritten = 0;
863
864 for (size_t i = 0; i < cFiles; ++i)
865 {
866 rc = rtTarCopyFileTo(hFile, papszFiles[i], cbOverallSize, cbOverallWritten, pfnProgressCallback, pvUser);
867 if (RT_FAILURE(rc))
868 break;
869 }
870
871 /* gtar gives a warning, but the documentation says EOF is indicated by a
872 * zero block. Disabled for now. */
873#if 0
874 if (RT_SUCCESS(rc))
875 {
876 /* Append the EOF record which is filled all by zeros */
877 RTTARRECORD record;
878 ASMMemFill32(&record, sizeof(record), 0);
879 rc = RTFileWrite(hFile, &record, sizeof(record), NULL);
880 }
881#endif
882 }
883
884 /* Time to close the new tar archive */
885 RTFileClose(hFile);
886
887 /* Delete the freshly created tar archive on failure */
888 if (RT_FAILURE(rc))
889 RTFileDelete(pszTarFile);
890
891 return rc;
892}
893
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette