VirtualBox

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

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

Runtime: little fixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.9 KB
Line 
1/* $Id: tar.cpp 21813 2009-07-27 14:44:17Z vboxsync $ */
2/** @file
3 * IPRT - Tar archive I/O.
4 */
5
6/*
7 * Copyright (C) 2009 Sun Microsystems, Inc.
8 *
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.
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 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
27 * Clara, CA 95054 USA or visit http://www.sun.com if you need
28 * additional information or have any questions.
29 */
30
31
32/*******************************************************************************
33* Header Files *
34*******************************************************************************/
35#include "internal/iprt.h"
36#include <iprt/tar.h>
37
38#include <iprt/asm.h>
39#include <iprt/assert.h>
40#include <iprt/err.h>
41#include <iprt/file.h>
42#include <iprt/mem.h>
43#include <iprt/path.h>
44#include <iprt/string.h>
45
46
47/*******************************************************************************
48* Structures and Typedefs *
49*******************************************************************************/
50
51/** @name RTTARRECORD::h::linkflag
52 * @{ */
53#define LF_OLDNORMAL '\0' /**< Normal disk file, Unix compatible */
54#define LF_NORMAL '0' /**< Normal disk file */
55#define LF_LINK '1' /**< Link to previously dumped file */
56#define LF_SYMLINK '2' /**< Symbolic link */
57#define LF_CHR '3' /**< Character special file */
58#define LF_BLK '4' /**< Block special file */
59#define LF_DIR '5' /**< Directory */
60#define LF_FIFO '6' /**< FIFO special file */
61#define LF_CONTIG '7' /**< Contiguous file */
62/** @} */
63
64typedef union RTTARRECORD
65{
66 char d[512];
67 struct h
68 {
69 char name[100];
70 char mode[8];
71 char uid[8];
72 char gid[8];
73 char size[12];
74 char mtime[12];
75 char chksum[8];
76 char linkflag;
77 char linkname[100];
78 char magic[8];
79 char uname[32];
80 char gname[32];
81 char devmajor[8];
82 char devminor[8];
83 } h;
84} RTTARRECORD;
85typedef RTTARRECORD *PRTTARRECORD;
86AssertCompileSize(RTTARRECORD, 512);
87AssertCompileMemberOffset(RTTARRECORD, h.size, 100+8*3);
88
89#if 0 /* not currently used */
90typedef struct RTTARFILELIST
91{
92 char *pszFilename;
93 RTTARFILELIST *pNext;
94} RTTARFILELIST;
95typedef RTTARFILELIST *PRTTARFILELIST;
96#endif
97
98
99/*******************************************************************************
100* Internal Functions *
101*******************************************************************************/
102
103static int rtTarCalcChkSum(PRTTARRECORD pRecord, uint32_t *pChkSum)
104{
105 uint32_t check = 0;
106 uint32_t zero = 0;
107 for (size_t i = 0; i < sizeof(RTTARRECORD); ++i)
108 {
109 /* Calculate the sum of every byte from the header. The checksum field
110 * itself is counted as all blanks. */
111 if ( i < RT_UOFFSETOF(RTTARRECORD, h.chksum)
112 || i >= RT_UOFFSETOF(RTTARRECORD, h.linkflag))
113 check += pRecord->d[i];
114 else
115 check += ' ';
116 /* Additional check if all fields are zero, which indicate EOF. */
117 zero += pRecord->d[i];
118 }
119
120 /* EOF? */
121 if (!zero)
122 return VERR_EOF;
123
124 *pChkSum = check;
125 return VINF_SUCCESS;
126}
127
128static int rtTarCheckHeader(PRTTARRECORD pRecord)
129{
130 uint32_t check;
131 int rc = rtTarCalcChkSum(pRecord, &check);
132 /* EOF? */
133 if (RT_FAILURE(rc))
134 return rc;
135
136 /* Verify the checksum */
137 uint32_t sum;
138 rc = RTStrToUInt32Full(pRecord->h.chksum, 8, &sum);
139 if (RT_SUCCESS(rc) && sum == check)
140 return VINF_SUCCESS;
141 return VERR_TAR_CHKSUM_MISMATCH;
142}
143
144static int rtTarCopyFileFrom(RTFILE hFile, const char *pszTargetName, PRTTARRECORD pRecord)
145{
146 RTFILE hNewFile;
147 /* Open the target file */
148 int rc = RTFileOpen(&hNewFile, pszTargetName, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
149 if (RT_FAILURE(rc))
150 return rc;
151
152/**@todo r=bird: Use a bigger buffer here, see comment in rtTarCopyFileTo. */
153
154 uint64_t cbToCopy = RTStrToUInt64(pRecord->h.size);
155 size_t cbAllWritten = 0;
156 RTTARRECORD record;
157 /* Copy the content from hFile over to pszTargetName. This is done block
158 * wise in 512 byte steps. After this copying is finished hFile will be on
159 * a 512 byte boundary, regardless if the file copied is 512 byte size
160 * aligned. */
161 for (;;)
162 {
163 /* Finished already? */
164 if (cbAllWritten == cbToCopy)
165 break;
166 /* Read one block */
167 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
168 if (RT_FAILURE(rc))
169 break;
170 size_t cbToWrite = sizeof(record);
171 /* Check for the last block which has not to be 512 bytes in size. */
172 if (cbAllWritten + cbToWrite > cbToCopy)
173 cbToWrite = cbToCopy - cbAllWritten;
174 /* Write the block */
175 rc = RTFileWrite(hNewFile, &record, cbToWrite, NULL);
176 if (RT_FAILURE(rc))
177 break;
178 /* Count how many bytes are written already */
179 cbAllWritten += cbToWrite;
180 }
181
182 /* Now set all file attributes */
183 if (RT_SUCCESS(rc))
184 {
185 int32_t mode;
186 rc = RTStrToInt32Full(pRecord->h.mode, 8, &mode);
187 if (RT_SUCCESS(rc))
188 {
189 mode |= RTFS_TYPE_FILE; /* For now we support regular files only */
190 /* Set the mode */
191 rc = RTFileSetMode(hNewFile, mode);
192 }
193 }
194 /* Make sure the called doesn't mix truncated tar files with the official
195 * end indicated by rtTarCalcChkSum. */
196 else if (rc == VERR_EOF)
197 rc = VERR_FILE_IO_ERROR;
198
199 RTFileClose(hNewFile);
200
201 /* Delete the freshly created file in the case of an error */
202 if (RT_FAILURE(rc))
203 RTFileDelete(pszTargetName);
204
205 return rc;
206}
207
208static int rtTarCopyFileTo(RTFILE hFile, const char *pszSrcName)
209{
210 RTFILE hOldFile;
211 /* Open the source file */
212 int rc = RTFileOpen(&hOldFile, pszSrcName, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
213 if (RT_FAILURE(rc))
214 return rc;
215
216 /* Get the size of the source file */
217 uint64_t cbSize;
218 rc = RTFileGetSize(hOldFile, &cbSize);
219 if (RT_FAILURE(rc))
220 {
221 RTFileClose(hOldFile);
222 return rc;
223 }
224 /* Get some info from the source file */
225 RTFSOBJINFO info;
226 RTUID uid = 0;
227 RTGID gid = 0;
228 RTFMODE fmode = 0600; /* Make some save default */
229 int64_t mtime = 0;
230 /* This isn't critical. Use the defaults if it fails. */
231 rc = RTFileQueryInfo(hOldFile, &info, RTFSOBJATTRADD_UNIX);
232 if (RT_SUCCESS(rc))
233 {
234 fmode = info.Attr.fMode & RTFS_UNIX_MASK;
235 uid = info.Attr.u.Unix.uid;
236 gid = info.Attr.u.Unix.gid;
237 mtime = RTTimeSpecGetSeconds(&info.ModificationTime);
238 }
239
240 /* Fill the header record */
241 RTTARRECORD record;
242 RT_ZERO(record);
243 RTStrPrintf(record.h.name, sizeof(record.h.name), "%s", RTPathFilename(pszSrcName));
244 RTStrPrintf(record.h.mode, sizeof(record.h.mode), "%0.7o", fmode);
245 RTStrPrintf(record.h.uid, sizeof(record.h.uid), "%0.7o", uid);
246 RTStrPrintf(record.h.gid, sizeof(record.h.gid), "%0.7o", gid);
247 RTStrPrintf(record.h.size, sizeof(record.h.size), "%0.11o", cbSize);
248 RTStrPrintf(record.h.mtime, sizeof(record.h.mtime), "%0.11o", mtime);
249 RTStrPrintf(record.h.magic, sizeof(record.h.magic), "ustar ");
250 RTStrPrintf(record.h.uname, sizeof(record.h.uname), "someone");
251 RTStrPrintf(record.h.gname, sizeof(record.h.gname), "someone");
252 record.h.linkflag = LF_NORMAL;
253 /* Create the checksum out of the new header */
254 uint32_t chksum;
255 rtTarCalcChkSum(&record, &chksum);
256 RTStrPrintf(record.h.chksum, sizeof(record.h.chksum), "%0.7o", chksum);
257
258 /* Write the header first */
259 rc = RTFileWrite(hFile, &record, sizeof(record), NULL);
260 if (RT_SUCCESS(rc))
261 {
262/** @todo r=bird: using a 64KB buffer here instead of 0.5KB would probably be
263 * a good thing. */
264 uint64_t cbAllWritten = 0;
265 /* Copy the content from pszSrcName over to hFile. This is done block
266 * wise in 512 byte steps. After this copying is finished hFile will be
267 * on a 512 byte boundary, regardless if the file copied is 512 byte
268 * size aligned. */
269 for (;;)
270 {
271 if (cbAllWritten >= cbSize)
272 break;
273 size_t cbToRead = sizeof(record);
274 /* Last record? */
275 if (cbAllWritten + cbToRead > cbSize)
276 {
277 /* Initialize with zeros */
278 RT_ZERO(record);
279 cbToRead = cbSize - cbAllWritten;
280 }
281 /* Read one block */
282 rc = RTFileRead(hOldFile, &record, cbToRead, NULL);
283 if (RT_FAILURE(rc))
284 break;
285 /* Write one block */
286 rc = RTFileWrite(hFile, &record, sizeof(record), NULL);
287 if (RT_FAILURE(rc))
288 break;
289 /* Count how many bytes are written already */
290 cbAllWritten += sizeof(record);
291 }
292
293 /* Make sure the called doesn't mix truncated tar files with the
294 * official end indicated by rtTarCalcChkSum. */
295 if (rc == VERR_EOF)
296 rc == VERR_FILE_IO_ERROR;
297 }
298 RTFileClose(hOldFile);
299
300 return rc;
301}
302
303static int rtTarSkipData(RTFILE hFile, PRTTARRECORD pRecord)
304{
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));
308 if (offSeek > 0)
309 rc = RTFileSeek(hFile, offSeek, RTFILE_SEEK_CURRENT, NULL);
310 return rc;
311}
312
313
314RTR3DECL(int) RTTarQueryFileExists(const char *pszTarFile, const char *pszFile)
315{
316 /* Validate input */
317 AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
318 AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
319
320 /* Open the tar file */
321 RTFILE hFile;
322 int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ);
323 if (RT_FAILURE(rc))
324 return rc;
325
326 bool fFound = false;
327 RTTARRECORD record;
328 for (;;)
329 {
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
335 * file. */
336 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
337 /* Check for error or EOF. */
338 if (RT_FAILURE(rc))
339 break;
340 /* Check for EOF & data integrity */
341 rc = rtTarCheckHeader(&record);
342 if (RT_FAILURE(rc))
343 break;
344 /* We support normal files only */
345 if ( record.h.linkflag == LF_OLDNORMAL
346 || record.h.linkflag == LF_NORMAL)
347 {
348 if (!RTStrCmp(record.h.name, pszFile))
349 {
350 fFound = true;
351 break;
352 }
353 }
354 rc = rtTarSkipData(hFile, &record);
355 if (RT_FAILURE(rc))
356 break;
357 }
358
359 RTFileClose(hFile);
360
361 if (rc == VERR_EOF)
362 rc = VINF_SUCCESS;
363
364 /* Something found? */
365 if ( RT_SUCCESS(rc)
366 && !fFound)
367 rc = VERR_FILE_NOT_FOUND;
368
369 return rc;
370}
371
372RTR3DECL(int) RTTarList(const char *pszTarFile, char ***ppapszFiles, size_t *pcFiles)
373{
374 /* Validate input */
375 AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
376 AssertPtrReturn(ppapszFiles, VERR_INVALID_POINTER);
377 AssertPtrReturn(pcFiles, VERR_INVALID_POINTER);
378
379 /* Open the tar file */
380 RTFILE hFile;
381 int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ);
382 if (RT_FAILURE(rc))
383 return rc;
384
385 /* Initialize the file name array with one slot */
386 size_t cFilesAlloc = 1;
387 char **papszFiles = (char**)RTMemAlloc(sizeof(char *));
388 if (!papszFiles)
389 {
390 RTFileClose(hFile);
391 return VERR_NO_MEMORY;
392 }
393
394 /* Iterate through the tar file record by record. Skip data records as we
395 * didn't need them. */
396 RTTARRECORD record;
397 size_t cFiles = 0;
398 for (;;)
399 {
400 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
401 /* Check for error or EOF. */
402 if (RT_FAILURE(rc))
403 break;
404 /* Check for EOF & data integrity */
405 rc = rtTarCheckHeader(&record);
406 if (RT_FAILURE(rc))
407 break;
408 /* We support normal files only */
409 if ( record.h.linkflag == LF_OLDNORMAL
410 || record.h.linkflag == LF_NORMAL)
411 {
412 if (cFiles >= cFilesAlloc)
413 {
414 /* Double the array size, make sure the size doesn't wrap. */
415 void *pvNew = NULL;
416 size_t cbNew = cFilesAlloc * sizeof(char *) * 2;
417 if (cbNew / sizeof(char *) / 2 == cFilesAlloc)
418 pvNew = RTMemRealloc(papszFiles, cbNew);
419 if (!pvNew)
420 {
421 rc = VERR_NO_MEMORY;
422 break;
423 }
424 papszFiles = (char **)pvNew;
425 cFilesAlloc *= 2;
426 }
427
428 /* Duplicate the name */
429 papszFiles[cFiles] = RTStrDup(record.h.name);
430 if (!papszFiles[cFiles])
431 {
432 rc = VERR_NO_MEMORY;
433 break;
434 }
435 cFiles++;
436 }
437 rc = rtTarSkipData(hFile, &record);
438 if (RT_FAILURE(rc))
439 break;
440 }
441
442 RTFileClose(hFile);
443
444 if (rc == VERR_EOF)
445 rc = VINF_SUCCESS;
446
447 /* Return the file array on success, dispose of it on failure. */
448 if (RT_SUCCESS(rc))
449 {
450 *pcFiles = cFiles;
451 *ppapszFiles = papszFiles;
452 }
453 else
454 {
455 while (cFiles-- > 0)
456 RTStrFree(papszFiles[cFiles]);
457 RTMemFree(papszFiles);
458 }
459 return rc;
460}
461
462RTR3DECL(int) RTTarExtractFiles(const char *pszTarFile, const char *pszOutputDir, const char * const *papszFiles, size_t cFiles)
463{
464 /* Validate input */
465 AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
466 AssertPtrReturn(pszOutputDir, VERR_INVALID_POINTER);
467 AssertPtrReturn(papszFiles, VERR_INVALID_POINTER);
468
469 /* Open the tar file */
470 RTFILE hFile;
471 int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ);
472 if (RT_FAILURE(rc))
473 return rc;
474
475 /* Iterate through the tar file record by record. */
476 RTTARRECORD record;
477 char **paExtracted = (char **)RTMemTmpAllocZ(sizeof(char *) * cFiles);
478 if (paExtracted)
479 {
480 size_t cExtracted = 0;
481 for (;;)
482 {
483 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
484 /* Check for error or EOF. */
485 if (RT_FAILURE(rc))
486 break;
487 /* Check for EOF & data integrity */
488 rc = rtTarCheckHeader(&record);
489 if (RT_FAILURE(rc))
490 break;
491 /* We support normal files only */
492 if ( record.h.linkflag == LF_OLDNORMAL
493 || record.h.linkflag == LF_NORMAL)
494 {
495 bool fFound = false;
496 for (size_t i = 0; i < cFiles; ++i)
497 {
498 if (!RTStrCmp(record.h.name, papszFiles[i]))
499 {
500 fFound = true;
501 if (cExtracted < cFiles)
502 {
503 char *pszTargetFile;
504 rc = RTStrAPrintf(&pszTargetFile, "%s/%s", pszOutputDir, papszFiles[i]);
505 if (rc > 0)
506 {
507 rc = rtTarCopyFileFrom(hFile, pszTargetFile, &record);
508 if (RT_SUCCESS(rc))
509 paExtracted[cExtracted++] = pszTargetFile;
510 else
511 RTStrFree(pszTargetFile);
512 }
513 else
514 rc = VERR_NO_MEMORY;
515 }
516 else
517 rc = VERR_ALREADY_EXISTS;
518 break;
519 }
520 }
521 if (RT_FAILURE(rc))
522 break;
523 /* If the current record isn't a file in the file list we have to
524 * skip the data */
525 if (!fFound)
526 {
527 rc = rtTarSkipData(hFile, &record);
528 if (RT_FAILURE(rc))
529 break;
530 }
531 }
532 }
533
534 if (rc == VERR_EOF)
535 rc = VINF_SUCCESS;
536
537 /* If we didn't found all files, indicate an error */
538 if (cExtracted != cFiles && RT_SUCCESS(rc))
539 rc = VERR_FILE_NOT_FOUND;
540
541 /* Cleanup the names of the extracted files, deleting them on failure. */
542 while (cExtracted-- > 0)
543 {
544 if (RT_FAILURE(rc))
545 RTFileDelete(paExtracted[cExtracted]);
546 RTStrFree(paExtracted[cExtracted]);
547 }
548 RTMemTmpFree(paExtracted);
549 }
550 else
551 rc = VERR_NO_TMP_MEMORY;
552
553 RTFileClose(hFile);
554 return rc;
555}
556
557RTR3DECL(int) RTTarExtractByIndex(const char *pszTarFile, const char *pszOutputDir, size_t iIndex, char **ppszFileName)
558{
559 /* Validate input */
560 AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
561 AssertPtrReturn(pszOutputDir, VERR_INVALID_POINTER);
562
563 /* Open the tar file */
564 RTFILE hFile;
565 int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_READ);
566 if (RT_FAILURE(rc))
567 return rc;
568
569 /* Iterate through the tar file record by record. */
570 RTTARRECORD record;
571 size_t iFile = 0;
572 bool fFound = false;
573 for (;;)
574 {
575 rc = RTFileRead(hFile, &record, sizeof(record), NULL);
576 /* Check for error or EOF. */
577 if (RT_FAILURE(rc))
578 break;
579 /* Check for EOF & data integrity */
580 rc = rtTarCheckHeader(&record);
581 if (RT_FAILURE(rc))
582 break;
583 /* We support normal files only */
584 if ( record.h.linkflag == LF_OLDNORMAL
585 || record.h.linkflag == LF_NORMAL)
586 {
587 if (iIndex == iFile)
588 {
589 fFound = true;
590 char *pszTargetName;
591 rc = RTStrAPrintf(&pszTargetName, "%s/%s", pszOutputDir, record.h.name);
592 if (rc > 0)
593 {
594 rc = rtTarCopyFileFrom(hFile, pszTargetName, &record);
595 /* On success pass on the filename if requested. */
596 if ( RT_SUCCESS(rc)
597 && ppszFileName)
598 *ppszFileName = pszTargetName;
599 else
600 RTStrFree(pszTargetName);
601 }
602 else
603 rc = VERR_NO_MEMORY;
604 break;
605 }
606 }
607 rc = rtTarSkipData(hFile, &record);
608 if (RT_FAILURE(rc))
609 break;
610 ++iFile;
611 }
612
613 RTFileClose(hFile);
614
615 if (rc == VERR_EOF)
616 rc = VINF_SUCCESS;
617
618 /* If we didn't found the index, indicate an error */
619 if (!fFound && RT_SUCCESS(rc))
620 rc = VERR_FILE_NOT_FOUND;
621
622 return rc;
623}
624
625RTR3DECL(int) RTTarCreate(const char *pszTarFile, const char * const *papszFiles, size_t cFiles)
626{
627 /* Validate input */
628 AssertPtrReturn(pszTarFile, VERR_INVALID_POINTER);
629 AssertPtrReturn(papszFiles, VERR_INVALID_POINTER);
630
631 /* Open the tar file */
632 RTFILE hFile;
633 int rc = RTFileOpen(&hFile, pszTarFile, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
634 if (RT_FAILURE(rc))
635 return rc;
636
637 for (size_t i = 0; i < cFiles; ++i)
638 {
639 rc = rtTarCopyFileTo(hFile, papszFiles[i]);
640 if (RT_FAILURE(rc))
641 break;
642 }
643
644 /* gtar gives a warning, but the documentation says EOF is indicated by a
645 * zero block. Disabled for now. */
646#if 0
647 if (RT_SUCCESS(rc))
648 {
649 /* Append the EOF record which is filled all by zeros */
650 RTTARRECORD record;
651 ASMMemFill32(&record, sizeof(record), 0);
652 rc = RTFileWrite(hFile, &record, sizeof(record), NULL);
653 }
654#endif
655
656 /* Time to close the new tar archive */
657 RTFileClose(hFile);
658
659 /* Delete the freshly created tar archive on failure */
660 if (RT_FAILURE(rc))
661 RTFileDelete(pszTarFile);
662
663 return rc;
664}
665
Note: See TracBrowser for help on using the repository browser.

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