3
* IPRT - Tar archive I/O.
7
* Copyright (C) 2009 Oracle Corporation
9
* This file is part of VirtualBox Open Source Edition (OSE), as
10
* available from http://www.virtualbox.org. 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.
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.
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.
28
/*******************************************************************************
30
*******************************************************************************/
31
#include "internal/iprt.h"
35
#include <iprt/assert.h>
37
#include <iprt/file.h>
39
#include <iprt/path.h>
40
#include <iprt/string.h>
43
/*******************************************************************************
44
* Structures and Typedefs *
45
*******************************************************************************/
47
/** @name RTTARRECORD::h::linkflag
49
#define LF_OLDNORMAL '\0' /**< Normal disk file, Unix compatible */
50
#define LF_NORMAL '0' /**< Normal disk file */
51
#define LF_LINK '1' /**< Link to previously dumped file */
52
#define LF_SYMLINK '2' /**< Symbolic link */
53
#define LF_CHR '3' /**< Character special file */
54
#define LF_BLK '4' /**< Block special file */
55
#define LF_DIR '5' /**< Directory */
56
#define LF_FIFO '6' /**< FIFO special file */
57
#define LF_CONTIG '7' /**< Contiguous file */
60
typedef union RTTARRECORD
81
typedef RTTARRECORD *PRTTARRECORD;
82
AssertCompileSize(RTTARRECORD, 512);
83
AssertCompileMemberOffset(RTTARRECORD, h.size, 100+8*3);
85
#if 0 /* not currently used */
86
typedef struct RTTARFILELIST
91
typedef RTTARFILELIST *PRTTARFILELIST;
95
/*******************************************************************************
96
* Internal Functions *
97
*******************************************************************************/
99
static int rtTarCalcChkSum(PRTTARRECORD pRecord, uint32_t *pChkSum)
103
for (size_t i = 0; i < sizeof(RTTARRECORD); ++i)
105
/* Calculate the sum of every byte from the header. The checksum field
106
* itself is counted as all blanks. */
107
if ( i < RT_UOFFSETOF(RTTARRECORD, h.chksum)
108
|| i >= RT_UOFFSETOF(RTTARRECORD, h.linkflag))
109
check += pRecord->d[i];
112
/* Additional check if all fields are zero, which indicate EOF. */
113
zero += pRecord->d[i];
124
static int rtTarCheckHeader(PRTTARRECORD pRecord)
127
int rc = rtTarCalcChkSum(pRecord, &check);
132
/* Verify the checksum */
134
rc = RTStrToUInt32Full(pRecord->h.chksum, 8, &sum);
135
if (RT_SUCCESS(rc) && sum == check)
137
return VERR_TAR_CHKSUM_MISMATCH;
140
static int rtTarCopyFileFrom(RTFILE hFile, const char *pszTargetName, PRTTARRECORD pRecord)
143
/* Open the target file */
144
int rc = RTFileOpen(&hNewFile, pszTargetName, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
148
/**@todo r=bird: Use a bigger buffer here, see comment in rtTarCopyFileTo. */
150
uint64_t cbToCopy = RTStrToUInt64(pRecord->h.size);
151
size_t cbAllWritten = 0;
153
/* Copy the content from hFile over to pszTargetName. This is done block
154
* wise in 512 byte steps. After this copying is finished hFile will be on
155
* a 512 byte boundary, regardless if the file copied is 512 byte size
159
/* Finished already? */
160
if (cbAllWritten == cbToCopy)
163
rc = RTFileRead(hFile, &record, sizeof(record), NULL);
166
size_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
rc = RTFileWrite(hNewFile, &record, cbToWrite, NULL);
174
/* Count how many bytes are written already */
175
cbAllWritten += cbToWrite;
178
/* Now set all file attributes */
182
rc = RTStrToInt32Full(pRecord->h.mode, 8, &mode);
185
mode |= RTFS_TYPE_FILE; /* For now we support regular files only */
187
rc = RTFileSetMode(hNewFile, mode);
190
/* Make sure the called doesn't mix truncated tar files with the official
191
* end indicated by rtTarCalcChkSum. */
192
else if (rc == VERR_EOF)
193
rc = VERR_FILE_IO_ERROR;
195
RTFileClose(hNewFile);
197
/* Delete the freshly created file in the case of an error */
199
RTFileDelete(pszTargetName);
204
static int rtTarCopyFileTo(RTFILE hFile, const char *pszSrcName)
207
/* Open the source file */
208
int rc = RTFileOpen(&hOldFile, pszSrcName, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
212
/* Get the size of the source file */
214
rc = RTFileGetSize(hOldFile, &cbSize);
217
RTFileClose(hOldFile);
220
/* Get some info from the source file */
224
RTFMODE fmode = 0600; /* Make some save default */
226
/* This isn't critical. Use the defaults if it fails. */
227
rc = RTFileQueryInfo(hOldFile, &info, RTFSOBJATTRADD_UNIX);
230
fmode = info.Attr.fMode & RTFS_UNIX_MASK;
231
uid = info.Attr.u.Unix.uid;
232
gid = info.Attr.u.Unix.gid;
233
mtime = RTTimeSpecGetSeconds(&info.ModificationTime);
236
/* Fill the header record */
239
RTStrPrintf(record.h.name, sizeof(record.h.name), "%s", RTPathFilename(pszSrcName));
240
RTStrPrintf(record.h.mode, sizeof(record.h.mode), "%0.7o", fmode);
241
RTStrPrintf(record.h.uid, sizeof(record.h.uid), "%0.7o", uid);
242
RTStrPrintf(record.h.gid, sizeof(record.h.gid), "%0.7o", gid);
243
RTStrPrintf(record.h.size, sizeof(record.h.size), "%0.11o", cbSize);
244
RTStrPrintf(record.h.mtime, sizeof(record.h.mtime), "%0.11o", mtime);
245
RTStrPrintf(record.h.magic, sizeof(record.h.magic), "ustar ");
246
RTStrPrintf(record.h.uname, sizeof(record.h.uname), "someone");
247
RTStrPrintf(record.h.gname, sizeof(record.h.gname), "someone");
248
record.h.linkflag = LF_NORMAL;
250
/* Create the checksum out of the new header */
252
rc = rtTarCalcChkSum(&record, &chksum);
255
RTStrPrintf(record.h.chksum, sizeof(record.h.chksum), "%0.7o", chksum);
257
/* Write the header first */
258
rc = RTFileWrite(hFile, &record, sizeof(record), NULL);
261
/** @todo r=bird: using a 64KB buffer here instead of 0.5KB would probably be
263
uint64_t cbAllWritten = 0;
264
/* Copy the content from pszSrcName over to hFile. This is done block
265
* wise in 512 byte steps. After this copying is finished hFile will be
266
* on a 512 byte boundary, regardless if the file copied is 512 byte
270
if (cbAllWritten >= cbSize)
272
size_t cbToRead = sizeof(record);
274
if (cbAllWritten + cbToRead > cbSize)
276
/* Initialize with zeros */
278
cbToRead = cbSize - cbAllWritten;
281
rc = RTFileRead(hOldFile, &record, cbToRead, NULL);
284
/* Write one block */
285
rc = RTFileWrite(hFile, &record, sizeof(record), NULL);
288
/* Count how many bytes are written already */
289
cbAllWritten += sizeof(record);
292
/* Make sure the called doesn't mix truncated tar files with the
293
* official end indicated by rtTarCalcChkSum. */
295
rc = VERR_FILE_IO_ERROR;
299
RTFileClose(hOldFile);
303
static int rtTarSkipData(RTFILE hFile, PRTTARRECORD pRecord)
305
int rc = VINF_SUCCESS;
306
/* Seek over the data parts (512 bytes aligned) */
307
int64_t offSeek = RT_ALIGN(RTStrToInt64(pRecord->h.size), sizeof(RTTARRECORD));
309
rc = RTFileSeek(hFile, offSeek, RTFILE_SEEK_CURRENT, NULL);
314
RTR3DECL(int) RTTarQueryFileExists(const char *pszTarFile, const char *pszFile)
317
AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
318
AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
320
/* Open the tar file */
322
int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
330
/** @todo r=bird: the reading, validation and EOF check done here should be
331
* moved to a separate helper function. That would make it easiser to
332
* distinguish genuine-end-of-tar-file and VERR_EOF caused by a
333
* trunacted file. That said, rtTarSkipData won't return VERR_EOF, at
334
* least not on unix, since it's not a sin to seek beyond the end of a
336
rc = RTFileRead(hFile, &record, sizeof(record), NULL);
337
/* Check for error or EOF. */
340
/* Check for EOF & data integrity */
341
rc = rtTarCheckHeader(&record);
344
/* We support normal files only */
345
if ( record.h.linkflag == LF_OLDNORMAL
346
|| record.h.linkflag == LF_NORMAL)
348
if (!RTStrCmp(record.h.name, pszFile))
354
rc = rtTarSkipData(hFile, &record);
364
/* Something found? */
367
rc = VERR_FILE_NOT_FOUND;
372
RTR3DECL(int) RTTarList(const char *pszTarFile, char ***ppapszFiles, size_t *pcFiles)
375
AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
376
AssertPtrReturn(ppapszFiles, VERR_INVALID_POINTER);
377
AssertPtrReturn(pcFiles, VERR_INVALID_POINTER);
379
/* Open the tar file */
381
int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
385
/* Initialize the file name array with one slot */
386
size_t cFilesAlloc = 1;
387
char **papszFiles = (char**)RTMemAlloc(sizeof(char *));
391
return VERR_NO_MEMORY;
394
/* Iterate through the tar file record by record. Skip data records as we
395
* didn't need them. */
400
rc = RTFileRead(hFile, &record, sizeof(record), NULL);
401
/* Check for error or EOF. */
404
/* Check for EOF & data integrity */
405
rc = rtTarCheckHeader(&record);
408
/* We support normal files only */
409
if ( record.h.linkflag == LF_OLDNORMAL
410
|| record.h.linkflag == LF_NORMAL)
412
if (cFiles >= cFilesAlloc)
414
/* Double the array size, make sure the size doesn't wrap. */
416
size_t cbNew = cFilesAlloc * sizeof(char *) * 2;
417
if (cbNew / sizeof(char *) / 2 == cFilesAlloc)
418
pvNew = RTMemRealloc(papszFiles, cbNew);
424
papszFiles = (char **)pvNew;
428
/* Duplicate the name */
429
papszFiles[cFiles] = RTStrDup(record.h.name);
430
if (!papszFiles[cFiles])
437
rc = rtTarSkipData(hFile, &record);
447
/* Return the file array on success, dispose of it on failure. */
451
*ppapszFiles = papszFiles;
456
RTStrFree(papszFiles[cFiles]);
457
RTMemFree(papszFiles);
462
RTR3DECL(int) RTTarExtractFiles(const char *pszTarFile, const char *pszOutputDir, const char * const *papszFiles, size_t cFiles)
465
AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
466
AssertPtrReturn(pszOutputDir, VERR_INVALID_POINTER);
467
AssertPtrReturn(papszFiles, VERR_INVALID_POINTER);
469
/* Open the tar file */
471
int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
475
/* Iterate through the tar file record by record. */
477
char **paExtracted = (char **)RTMemTmpAllocZ(sizeof(char *) * cFiles);
480
size_t cExtracted = 0;
483
rc = RTFileRead(hFile, &record, sizeof(record), NULL);
484
/* Check for error or EOF. */
487
/* Check for EOF & data integrity */
488
rc = rtTarCheckHeader(&record);
491
/* We support normal files only */
492
if ( record.h.linkflag == LF_OLDNORMAL
493
|| record.h.linkflag == LF_NORMAL)
496
for (size_t i = 0; i < cFiles; ++i)
498
if (!RTStrCmp(record.h.name, papszFiles[i]))
501
if (cExtracted < cFiles)
504
rc = RTStrAPrintf(&pszTargetFile, "%s/%s", pszOutputDir, papszFiles[i]);
507
rc = rtTarCopyFileFrom(hFile, pszTargetFile, &record);
509
paExtracted[cExtracted++] = pszTargetFile;
511
RTStrFree(pszTargetFile);
517
rc = VERR_ALREADY_EXISTS;
523
/* If the current record isn't a file in the file list we have to
527
rc = rtTarSkipData(hFile, &record);
537
/* If we didn't found all files, indicate an error */
538
if (cExtracted != cFiles && RT_SUCCESS(rc))
539
rc = VERR_FILE_NOT_FOUND;
541
/* Cleanup the names of the extracted files, deleting them on failure. */
542
while (cExtracted-- > 0)
545
RTFileDelete(paExtracted[cExtracted]);
546
RTStrFree(paExtracted[cExtracted]);
548
RTMemTmpFree(paExtracted);
551
rc = VERR_NO_TMP_MEMORY;
557
RTR3DECL(int) RTTarExtractByIndex(const char *pszTarFile, const char *pszOutputDir, size_t iIndex, char **ppszFileName)
560
AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
561
AssertPtrReturn(pszOutputDir, VERR_INVALID_POINTER);
563
/* Open the tar file */
565
int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
569
/* Iterate through the tar file record by record. */
575
rc = RTFileRead(hFile, &record, sizeof(record), NULL);
576
/* Check for error or EOF. */
579
/* Check for EOF & data integrity */
580
rc = rtTarCheckHeader(&record);
583
/* We support normal files only */
584
if ( record.h.linkflag == LF_OLDNORMAL
585
|| record.h.linkflag == LF_NORMAL)
591
rc = RTStrAPrintf(&pszTargetName, "%s/%s", pszOutputDir, record.h.name);
594
rc = rtTarCopyFileFrom(hFile, pszTargetName, &record);
595
/* On success pass on the filename if requested. */
598
*ppszFileName = pszTargetName;
600
RTStrFree(pszTargetName);
607
rc = rtTarSkipData(hFile, &record);
618
/* If we didn't found the index, indicate an error */
619
if (!fFound && RT_SUCCESS(rc))
620
rc = VERR_FILE_NOT_FOUND;
625
RTR3DECL(int) RTTarCreate(const char *pszTarFile, const char * const *papszFiles, size_t cFiles)
628
AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
629
AssertPtrReturn(papszFiles, VERR_INVALID_POINTER);
631
/* Open the tar file */
633
int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
637
for (size_t i = 0; i < cFiles; ++i)
639
rc = rtTarCopyFileTo(hFile, papszFiles[i]);
644
/* gtar gives a warning, but the documentation says EOF is indicated by a
645
* zero block. Disabled for now. */
649
/* Append the EOF record which is filled all by zeros */
651
ASMMemFill32(&record, sizeof(record), 0);
652
rc = RTFileWrite(hFile, &record, sizeof(record), NULL);
656
/* Time to close the new tar archive */
659
/* Delete the freshly created tar archive on failure */
661
RTFileDelete(pszTarFile);