VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/zip/tarcmd.cpp@ 87203

Last change on this file since 87203 was 86036, checked in by vboxsync, 4 years ago

Runtime/cpiovfs.cpp: A simple CPIO archive reader (WIP, writer comes next and then some cleanup) [build fix]

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 74.5 KB
Line 
1/* $Id: tarcmd.cpp 86036 2020-09-06 20:33:03Z vboxsync $ */
2/** @file
3 * IPRT - A mini TAR Command.
4 */
5
6/*
7 * Copyright (C) 2010-2020 Oracle Corporation
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
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include <iprt/zip.h>
32
33#include <iprt/asm.h>
34#include <iprt/buildconfig.h>
35#include <iprt/ctype.h>
36#include <iprt/dir.h>
37#include <iprt/err.h>
38#include <iprt/file.h>
39#include <iprt/getopt.h>
40#include <iprt/initterm.h>
41#include <iprt/mem.h>
42#include <iprt/message.h>
43#include <iprt/param.h>
44#include <iprt/path.h>
45#include <iprt/stream.h>
46#include <iprt/string.h>
47#include <iprt/symlink.h>
48#include <iprt/vfs.h>
49
50
51/*********************************************************************************************************************************
52* Defined Constants And Macros *
53*********************************************************************************************************************************/
54#define RTZIPTARCMD_OPT_DELETE 1000
55#define RTZIPTARCMD_OPT_OWNER 1001
56#define RTZIPTARCMD_OPT_GROUP 1002
57#define RTZIPTARCMD_OPT_UTC 1003
58#define RTZIPTARCMD_OPT_PREFIX 1004
59#define RTZIPTARCMD_OPT_FILE_MODE_AND_MASK 1005
60#define RTZIPTARCMD_OPT_FILE_MODE_OR_MASK 1006
61#define RTZIPTARCMD_OPT_DIR_MODE_AND_MASK 1007
62#define RTZIPTARCMD_OPT_DIR_MODE_OR_MASK 1008
63#define RTZIPTARCMD_OPT_FORMAT 1009
64#define RTZIPTARCMD_OPT_READ_AHEAD 1010
65#define RTZIPTARCMD_OPT_USE_PUSH_FILE 1011
66#define RTZIPTARCMD_OPT_NO_RECURSION 1012
67
68/** File format. */
69typedef enum RTZIPTARCMDFORMAT
70{
71 RTZIPTARCMDFORMAT_INVALID = 0,
72 /** Autodetect if possible, defaulting to TAR. */
73 RTZIPTARCMDFORMAT_AUTO_DEFAULT,
74 /** TAR. */
75 RTZIPTARCMDFORMAT_TAR,
76 /** XAR. */
77 RTZIPTARCMDFORMAT_XAR,
78 /** CPIO. */
79 RTZIPTARCMDFORMAT_CPIO
80} RTZIPTARCMDFORMAT;
81
82
83/*********************************************************************************************************************************
84* Structures and Typedefs *
85*********************************************************************************************************************************/
86/**
87 * IPRT TAR option structure.
88 */
89typedef struct RTZIPTARCMDOPS
90{
91 /** The file format. */
92 RTZIPTARCMDFORMAT enmFormat;
93
94 /** The operation (Acdrtux or RTZIPTARCMD_OPT_DELETE). */
95 int iOperation;
96 /** The long operation option name. */
97 const char *pszOperation;
98
99 /** The directory to change into when packing and unpacking. */
100 const char *pszDirectory;
101 /** The tar file name. */
102 const char *pszFile;
103 /** Whether we're verbose or quiet. */
104 bool fVerbose;
105 /** Whether to preserve the original file owner when restoring. */
106 bool fPreserveOwner;
107 /** Whether to preserve the original file group when restoring. */
108 bool fPreserveGroup;
109 /** Whether to skip restoring the modification time (only time stored by the
110 * traditional TAR format). */
111 bool fNoModTime;
112 /** Whether to add a read ahead thread. */
113 bool fReadAhead;
114 /** Use RTVfsFsStrmPushFile instead of RTVfsFsStrmAdd for files. */
115 bool fUsePushFile;
116 /** Whether to handle directories recursively or not. Defaults to \c true. */
117 bool fRecursive;
118 /** The compressor/decompressor method to employ (0, z or j). */
119 char chZipper;
120
121 /** The owner to set. NULL if not applicable.
122 * Always resolved into uidOwner for extraction. */
123 const char *pszOwner;
124 /** The owner ID to set. NIL_RTUID if not applicable. */
125 RTUID uidOwner;
126 /** The group to set. NULL if not applicable.
127 * Always resolved into gidGroup for extraction. */
128 const char *pszGroup;
129 /** The group ID to set. NIL_RTGUID if not applicable. */
130 RTGID gidGroup;
131 /** Display the modification times in UTC instead of local time. */
132 bool fDisplayUtc;
133 /** File mode AND mask. */
134 RTFMODE fFileModeAndMask;
135 /** File mode OR mask. */
136 RTFMODE fFileModeOrMask;
137 /** Directory mode AND mask. */
138 RTFMODE fDirModeAndMask;
139 /** Directory mode OR mask. */
140 RTFMODE fDirModeOrMask;
141
142 /** What to prefix all names with when creating, adding, whatever. */
143 const char *pszPrefix;
144
145 /** The number of files(, directories or whatever) specified. */
146 uint32_t cFiles;
147 /** Array of files(, directories or whatever).
148 * Terminated by a NULL entry. */
149 const char * const *papszFiles;
150
151 /** The TAR format to create. */
152 RTZIPTARFORMAT enmTarFormat;
153 /** TAR creation flags. */
154 uint32_t fTarCreate;
155
156} RTZIPTARCMDOPS;
157/** Pointer to the IPRT tar options. */
158typedef RTZIPTARCMDOPS *PRTZIPTARCMDOPS;
159
160/** The size of the directory entry buffer we're using. */
161#define RTZIPTARCMD_DIRENTRY_BUF_SIZE (sizeof(RTDIRENTRYEX) + RTPATH_MAX)
162
163/**
164 * Callback used by rtZipTarDoWithMembers
165 *
166 * @returns rcExit or RTEXITCODE_FAILURE.
167 * @param pOpts The tar options.
168 * @param hVfsObj The tar object to display
169 * @param pszName The name.
170 * @param rcExit The current exit code.
171 */
172typedef RTEXITCODE (*PFNDOWITHMEMBER)(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit);
173
174
175/**
176 * Checks if @a pszName is a member of @a papszNames, optionally returning the
177 * index.
178 *
179 * @returns true if the name is in the list, otherwise false.
180 * @param pszName The name to find.
181 * @param papszNames The array of names.
182 * @param piName Where to optionally return the array index.
183 */
184static bool rtZipTarCmdIsNameInArray(const char *pszName, const char * const *papszNames, uint32_t *piName)
185{
186 for (uint32_t iName = 0; papszNames[iName]; iName++)
187 if (!strcmp(papszNames[iName], pszName))
188 {
189 if (piName)
190 *piName = iName;
191 return true;
192 }
193 return false;
194}
195
196
197/**
198 * Queries information about a VFS object.
199 *
200 * @returns VBox status code.
201 * @param pszSpec VFS object spec to use.
202 * @param paObjInfo Where to store the queried object information.
203 * Must at least provide 3 structs, namely for UNIX, UNIX_OWNER and UNIX_GROUP attributes.
204 * @param cObjInfo Number of objection information structs handed in.
205 */
206static int rtZipTarCmdQueryObjInfo(const char *pszSpec, PRTFSOBJINFO paObjInfo, unsigned cObjInfo)
207{
208 AssertPtrReturn(paObjInfo, VERR_INVALID_POINTER);
209 AssertReturn(cObjInfo >= 3, VERR_INVALID_PARAMETER);
210
211 RTERRINFOSTATIC ErrInfo;
212 uint32_t offError;
213 int rc = RTVfsChainQueryInfo(pszSpec, &paObjInfo[0], RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK,
214 &offError, RTErrInfoInitStatic(&ErrInfo));
215 if (RT_SUCCESS(rc))
216 {
217 rc = RTVfsChainQueryInfo(pszSpec, &paObjInfo[1], RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK,
218 &offError, RTErrInfoInitStatic(&ErrInfo));
219 if (RT_SUCCESS(rc))
220 {
221 rc = RTVfsChainQueryInfo(pszSpec, &paObjInfo[2], RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK,
222 &offError, RTErrInfoInitStatic(&ErrInfo));
223 if (RT_FAILURE(rc))
224 RT_BZERO(&paObjInfo[2], sizeof(RTFSOBJINFO));
225 }
226 else
227 {
228 RT_BZERO(&paObjInfo[1], sizeof(RTFSOBJINFO));
229 RT_BZERO(&paObjInfo[2], sizeof(RTFSOBJINFO));
230 }
231
232 rc = VINF_SUCCESS; /* aObjInfo[1] + aObjInfo[2] are optional. */
233 }
234 else
235 RTVfsChainMsgError("RTVfsChainQueryInfo", pszSpec, rc, offError, &ErrInfo.Core);
236
237 return rc;
238}
239
240
241/**
242 * Archives a file.
243 *
244 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
245 * @param pOpts The options.
246 * @param hVfsFss The TAR filesystem stream handle.
247 * @param pszSrc The file path or VFS spec.
248 * @param paObjInfo[3] Array of three FS object info structures. The first
249 * one is always filled with RTFSOBJATTRADD_UNIX info.
250 * The next two may contain owner and group names if
251 * available. Buffers can be modified.
252 * @param pszDst The name to archive the file under.
253 * @param pErrInfo Error info buffer (saves stack space).
254 */
255static RTEXITCODE rtZipTarCmdArchiveFile(PRTZIPTARCMDOPS pOpts, RTVFSFSSTREAM hVfsFss, const char *pszSrc,
256 RTFSOBJINFO paObjInfo[3], const char *pszDst, PRTERRINFOSTATIC pErrInfo)
257{
258 if (pOpts->fVerbose)
259 RTPrintf("%s\n", pszDst);
260
261 /* Open the file. */
262 uint32_t offError;
263 RTVFSIOSTREAM hVfsIosSrc;
264 int rc = RTVfsChainOpenIoStream(pszSrc, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
265 &hVfsIosSrc, &offError, RTErrInfoInitStatic(pErrInfo));
266 if (RT_FAILURE(rc))
267 return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pszSrc, rc, offError, &pErrInfo->Core);
268
269 /* I/O stream to base object. */
270 RTVFSOBJ hVfsObjSrc = RTVfsObjFromIoStream(hVfsIosSrc);
271 if (hVfsObjSrc != NIL_RTVFSOBJ)
272 {
273 /*
274 * Add it to the stream. Got to variants here so we can test the
275 * RTVfsFsStrmPushFile API too.
276 */
277 if (!pOpts->fUsePushFile)
278 rc = RTVfsFsStrmAdd(hVfsFss, pszDst, hVfsObjSrc, 0 /*fFlags*/);
279 else
280 {
281 uint32_t cObjInfo = 1 + (paObjInfo[1].Attr.enmAdditional == RTFSOBJATTRADD_UNIX_OWNER)
282 + (paObjInfo[2].Attr.enmAdditional == RTFSOBJATTRADD_UNIX_GROUP);
283 RTVFSIOSTREAM hVfsIosDst;
284 rc = RTVfsFsStrmPushFile(hVfsFss, pszDst, paObjInfo[0].cbObject, paObjInfo, cObjInfo, 0 /*fFlags*/, &hVfsIosDst);
285 if (RT_SUCCESS(rc))
286 {
287 rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, 0);
288 RTVfsIoStrmRelease(hVfsIosDst);
289 }
290 }
291 RTVfsIoStrmRelease(hVfsIosSrc);
292 RTVfsObjRelease(hVfsObjSrc);
293
294 if (RT_SUCCESS(rc))
295 {
296 if (rc != VINF_SUCCESS)
297 RTMsgWarning("%Rrc adding '%s'", rc, pszDst);
298 return RTEXITCODE_SUCCESS;
299 }
300 return RTMsgErrorExitFailure("%Rrc adding '%s'", rc, pszDst);
301 }
302 RTVfsIoStrmRelease(hVfsIosSrc);
303 return RTMsgErrorExitFailure("RTVfsObjFromIoStream failed unexpectedly!");
304}
305
306
307/**
308 * Sub-directory helper for creating archives.
309 *
310 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
311 * @param pOpts The options.
312 * @param hVfsFss The TAR filesystem stream handle.
313 * @param pszSrc The directory path or VFS spec. We append to the
314 * buffer as we decend.
315 * @param cchSrc The length of the input.
316 * @param paObjInfo[3] Array of three FS object info structures. The first
317 * one is always filled with RTFSOBJATTRADD_UNIX info.
318 * The next two may contain owner and group names if
319 * available. The three buffers can be reused.
320 * @param pszDst The name to archive it the under. We append to the
321 * buffer as we decend.
322 * @param cchDst The length of the input.
323 * @param pDirEntry Directory entry to use for the directory to handle.
324 * @param pErrInfo Error info buffer (saves stack space).
325 */
326static RTEXITCODE rtZipTarCmdArchiveDirSub(PRTZIPTARCMDOPS pOpts, RTVFSFSSTREAM hVfsFss,
327 char *pszSrc, size_t cchSrc, RTFSOBJINFO paObjInfo[3],
328 char pszDst[RTPATH_MAX], size_t cchDst, PRTDIRENTRYEX pDirEntry,
329 PRTERRINFOSTATIC pErrInfo)
330{
331 if (pOpts->fVerbose)
332 RTPrintf("%s\n", pszDst);
333
334 uint32_t offError;
335 RTVFSDIR hVfsIoDir;
336 int rc = RTVfsChainOpenDir(pszSrc, 0 /*fFlags*/,
337 &hVfsIoDir, &offError, RTErrInfoInitStatic(pErrInfo));
338 if (RT_FAILURE(rc))
339 return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenDir", pszSrc, rc, offError, &pErrInfo->Core);
340
341 /* Make sure we've got some room in the path, to save us extra work further down. */
342 if (cchSrc + 3 >= RTPATH_MAX)
343 return RTMsgErrorExitFailure("Source path too long: '%s'\n", pszSrc);
344
345 /* Ensure we've got a trailing slash (there is space for it see above). */
346 if (!RTPATH_IS_SEP(pszSrc[cchSrc - 1]))
347 {
348 pszSrc[cchSrc++] = RTPATH_SLASH;
349 pszSrc[cchSrc] = '\0';
350 }
351
352 /* Ditto for destination. */
353 if (cchDst + 3 >= RTPATH_MAX)
354 return RTMsgErrorExitFailure("Destination path too long: '%s'\n", pszDst);
355
356 if (!RTPATH_IS_SEP(pszDst[cchDst - 1]))
357 {
358 pszDst[cchDst++] = RTPATH_SLASH;
359 pszDst[cchDst] = '\0';
360 }
361
362 /*
363 * Process the files and subdirs.
364 */
365 for (;;)
366 {
367 size_t cbDirEntry = RTZIPTARCMD_DIRENTRY_BUF_SIZE;
368 rc = RTVfsDirReadEx(hVfsIoDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
369 if (RT_FAILURE(rc))
370 break;
371
372 /* Check length. */
373 if (pDirEntry->cbName + cchSrc + 3 >= RTPATH_MAX)
374 {
375 rc = VERR_BUFFER_OVERFLOW;
376 break;
377 }
378
379 switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
380 {
381 case RTFS_TYPE_DIRECTORY:
382 {
383 if (RTDirEntryExIsStdDotLink(pDirEntry))
384 continue;
385
386 if (!pOpts->fRecursive)
387 continue;
388
389 memcpy(&pszSrc[cchSrc], pDirEntry->szName, pDirEntry->cbName + 1);
390 if (RT_SUCCESS(rc))
391 {
392 memcpy(&pszDst[cchDst], pDirEntry->szName, pDirEntry->cbName + 1);
393 rc = rtZipTarCmdArchiveDirSub(pOpts, hVfsFss, pszSrc, cchSrc + pDirEntry->cbName, paObjInfo,
394 pszDst, cchDst + pDirEntry->cbName, pDirEntry, pErrInfo);
395 }
396
397 break;
398 }
399
400 case RTFS_TYPE_FILE:
401 {
402 memcpy(&pszSrc[cchSrc], pDirEntry->szName, pDirEntry->cbName + 1);
403 rc = rtZipTarCmdQueryObjInfo(pszSrc, paObjInfo, 3 /* cObjInfo */);
404 if (RT_SUCCESS(rc))
405 {
406 memcpy(&pszDst[cchDst], pDirEntry->szName, pDirEntry->cbName + 1);
407 rc = rtZipTarCmdArchiveFile(pOpts, hVfsFss, pszSrc, paObjInfo, pszDst, pErrInfo);
408 }
409 break;
410 }
411
412 default:
413 {
414 if (pOpts->fVerbose)
415 RTPrintf("Warning: File system type %#x for '%s' not implemented yet, sorry! Skipping ...\n",
416 pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK, pDirEntry->szName);
417 break;
418 }
419 }
420 }
421
422 RTVfsDirRelease(hVfsIoDir);
423
424 if (rc != VERR_NO_MORE_FILES)
425 return RTMsgErrorExitFailure("RTVfsDirReadEx failed unexpectedly!");
426
427 return RTEXITCODE_SUCCESS;
428}
429
430
431/**
432 * Archives a directory recursively.
433 *
434 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
435 * @param pOpts The options.
436 * @param hVfsFss The TAR filesystem stream handle.
437 * @param pszSrc The directory path or VFS spec. We append to the
438 * buffer as we decend.
439 * @param cchSrc The length of the input.
440 * @param paObjInfo[3] Array of three FS object info structures. The first
441 * one is always filled with RTFSOBJATTRADD_UNIX info.
442 * The next two may contain owner and group names if
443 * available. The three buffers can be reused.
444 * @param pszDst The name to archive it the under. We append to the
445 * buffer as we decend.
446 * @param cchDst The length of the input.
447 * @param pErrInfo Error info buffer (saves stack space).
448 */
449static RTEXITCODE rtZipTarCmdArchiveDir(PRTZIPTARCMDOPS pOpts, RTVFSFSSTREAM hVfsFss, char pszSrc[RTPATH_MAX], size_t cchSrc,
450 RTFSOBJINFO paObjInfo[3], char pszDst[RTPATH_MAX], size_t cchDst,
451 PRTERRINFOSTATIC pErrInfo)
452{
453 RT_NOREF(cchSrc);
454
455 char szSrcAbs[RTPATH_MAX];
456 int rc = RTPathAbs(pszSrc, szSrcAbs, sizeof(szSrcAbs));
457 if (RT_FAILURE(rc))
458 return RTMsgErrorExitFailure("RTPathAbs failed on '%s': %Rrc\n", pszSrc, rc);
459
460 union
461 {
462 uint8_t abPadding[RTZIPTARCMD_DIRENTRY_BUF_SIZE];
463 RTDIRENTRYEX DirEntry;
464 } uBuf;
465
466 return rtZipTarCmdArchiveDirSub(pOpts, hVfsFss, szSrcAbs, strlen(szSrcAbs), paObjInfo, pszDst, cchDst, &uBuf.DirEntry, pErrInfo);
467}
468
469
470/**
471 * Opens the output archive specified by the options.
472 *
473 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
474 * @param pOpts The options.
475 * @param phVfsFss Where to return the TAR filesystem stream handle.
476 */
477static RTEXITCODE rtZipTarCmdOpenOutputArchive(PRTZIPTARCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss)
478{
479 int rc;
480 *phVfsFss = NIL_RTVFSFSSTREAM;
481
482 /*
483 * Open the output file.
484 */
485 RTVFSIOSTREAM hVfsIos;
486 if ( pOpts->pszFile
487 && strcmp(pOpts->pszFile, "-") != 0)
488 {
489 uint32_t offError = 0;
490 RTERRINFOSTATIC ErrInfo;
491 rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE,
492 &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
493 if (RT_FAILURE(rc))
494 return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core);
495 }
496 else
497 {
498 rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT,
499 RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
500 true /*fLeaveOpen*/,
501 &hVfsIos);
502 if (RT_FAILURE(rc))
503 return RTMsgErrorExitFailure("Failed to prepare standard output for writing: %Rrc", rc);
504 }
505
506 /*
507 * Pass it thru a compressor?
508 */
509 RTVFSIOSTREAM hVfsIosComp = NIL_RTVFSIOSTREAM;
510 switch (pOpts->chZipper)
511 {
512 /* no */
513 case '\0':
514 rc = VINF_SUCCESS;
515 break;
516
517 /* gunzip */
518 case 'z':
519 rc = RTZipGzipCompressIoStream(hVfsIos, 0 /*fFlags*/, 6, &hVfsIosComp);
520 if (RT_FAILURE(rc))
521 RTMsgError("Failed to open gzip decompressor: %Rrc", rc);
522 break;
523
524 /* bunzip2 */
525 case 'j':
526 rc = VERR_NOT_SUPPORTED;
527 RTMsgError("bzip2 is not supported by this build");
528 break;
529
530 /* bug */
531 default:
532 rc = VERR_INTERNAL_ERROR_2;
533 RTMsgError("unknown decompression method '%c'", pOpts->chZipper);
534 break;
535 }
536 if (RT_FAILURE(rc))
537 {
538 RTVfsIoStrmRelease(hVfsIos);
539 return RTEXITCODE_FAILURE;
540 }
541
542 if (hVfsIosComp != NIL_RTVFSIOSTREAM)
543 {
544 RTVfsIoStrmRelease(hVfsIos);
545 hVfsIos = hVfsIosComp;
546 hVfsIosComp = NIL_RTVFSIOSTREAM;
547 }
548
549 /*
550 * Open the filesystem stream creator.
551 */
552 if ( pOpts->enmFormat == RTZIPTARCMDFORMAT_TAR
553 || pOpts->enmFormat == RTZIPTARCMDFORMAT_AUTO_DEFAULT)
554 {
555 RTVFSFSSTREAM hVfsFss;
556 rc = RTZipTarFsStreamToIoStream(hVfsIos, pOpts->enmTarFormat, pOpts->fTarCreate, &hVfsFss);
557 if (RT_SUCCESS(rc))
558 {
559 /*
560 * Set transformation options.
561 */
562 rc = RTZipTarFsStreamSetFileMode(hVfsFss, pOpts->fFileModeAndMask, pOpts->fFileModeOrMask);
563 if (RT_SUCCESS(rc))
564 {
565 rc = RTZipTarFsStreamSetDirMode(hVfsFss, pOpts->fDirModeAndMask, pOpts->fDirModeOrMask);
566 if (RT_FAILURE(rc))
567 RTMsgError("RTZipTarFsStreamSetDirMode(%o,%o) failed: %Rrc", pOpts->fDirModeAndMask, pOpts->fDirModeOrMask, rc);
568 }
569 else
570 RTMsgError("RTZipTarFsStreamSetFileMode(%o,%o) failed: %Rrc", pOpts->fFileModeAndMask, pOpts->fFileModeOrMask, rc);
571 if ((pOpts->pszOwner || pOpts->uidOwner != NIL_RTUID) && RT_SUCCESS(rc))
572 {
573 rc = RTZipTarFsStreamSetOwner(hVfsFss, pOpts->uidOwner, pOpts->pszOwner);
574 if (RT_FAILURE(rc))
575 RTMsgError("RTZipTarFsStreamSetOwner(%d,%s) failed: %Rrc", pOpts->uidOwner, pOpts->pszOwner, rc);
576 }
577 if ((pOpts->pszGroup || pOpts->gidGroup != NIL_RTGID) && RT_SUCCESS(rc))
578 {
579 rc = RTZipTarFsStreamSetGroup(hVfsFss, pOpts->gidGroup, pOpts->pszGroup);
580 if (RT_FAILURE(rc))
581 RTMsgError("RTZipTarFsStreamSetGroup(%d,%s) failed: %Rrc", pOpts->gidGroup, pOpts->pszGroup, rc);
582 }
583 if (RT_SUCCESS(rc))
584 *phVfsFss = hVfsFss;
585 else
586 {
587 RTVfsFsStrmRelease(hVfsFss);
588 *phVfsFss = NIL_RTVFSFSSTREAM;
589 }
590 }
591 else
592 rc = RTMsgErrorExitFailure("Failed to open tar filesystem stream: %Rrc", rc);
593 }
594 else
595 rc = VERR_NOT_SUPPORTED;
596 RTVfsIoStrmRelease(hVfsIos);
597 if (RT_FAILURE(rc))
598 return RTMsgErrorExitFailure("Failed to open tar filesystem stream: %Rrc", rc);
599
600 return RTEXITCODE_SUCCESS;
601}
602
603
604/**
605 * Implements archive creation.
606 *
607 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
608 * @param pOpts The options.
609 */
610static RTEXITCODE rtZipTarCreate(PRTZIPTARCMDOPS pOpts)
611{
612 /*
613 * Refuse to create empty archive.
614 */
615 if (pOpts->cFiles == 0)
616 return RTMsgErrorExitFailure("Nothing to archive - refusing to create empty archive!");
617
618 /*
619 * First open the output file.
620 */
621 RTVFSFSSTREAM hVfsFss;
622 RTEXITCODE rcExit = rtZipTarCmdOpenOutputArchive(pOpts, &hVfsFss);
623 if (rcExit != RTEXITCODE_SUCCESS)
624 return rcExit;
625
626 /*
627 * Process the input files.
628 */
629 for (uint32_t iFile = 0; iFile < pOpts->cFiles; iFile++)
630 {
631 const char *pszFile = pOpts->papszFiles[iFile];
632
633 /*
634 * Construct/copy the source name.
635 */
636 int rc = VINF_SUCCESS;
637 char szSrc[RTPATH_MAX];
638 if ( RTPathStartsWithRoot(pszFile)
639 || RTVfsChainIsSpec(pszFile))
640 rc = RTStrCopy(szSrc, sizeof(szSrc), pszFile);
641 else
642 rc = RTPathJoin(szSrc, sizeof(szSrc), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pOpts->papszFiles[iFile]);
643 if (RT_SUCCESS(rc))
644 {
645 /*
646 * Construct the archived name. We must strip leading root specifier.
647 */
648 char *pszFinalPath = NULL;
649 char szDst[RTPATH_MAX];
650 const char *pszDst = pszFile;
651 if (RTVfsChainIsSpec(pszFile))
652 {
653 uint32_t offError;
654 rc = RTVfsChainQueryFinalPath(pszFile, &pszFinalPath, &offError);
655 if (RT_SUCCESS(rc))
656 pszDst = pszFinalPath;
657 else
658 rcExit = RTVfsChainMsgErrorExitFailure("RTVfsChainQueryFinalPath", pszFile, rc, offError, NULL);
659 }
660 if (RT_SUCCESS(rc))
661 {
662 pszDst = RTPathSkipRootSpec(pszDst);
663 if (*pszDst == '\0')
664 {
665 pszDst = pOpts->pszPrefix ? pOpts->pszPrefix : ".";
666 rc = RTStrCopy(szDst, sizeof(szDst), pszDst);
667 }
668 else
669 {
670 if (pOpts->pszPrefix)
671 rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszPrefix, pszDst);
672 else
673 rc = RTStrCopy(szDst, sizeof(szDst), pszDst);
674 }
675 if (RT_SUCCESS(rc))
676 {
677 /*
678 * What kind of object is this and what affiliations does it have?
679 */
680 RTFSOBJINFO aObjInfo[3];
681 rc = rtZipTarCmdQueryObjInfo(szSrc, aObjInfo, RT_ELEMENTS(aObjInfo));
682 if (RT_SUCCESS(rc))
683 {
684 RTERRINFOSTATIC ErrInfo;
685
686 /*
687 * Process on an object type basis.
688 */
689 RTEXITCODE rcExit2;
690 if (RTFS_IS_DIRECTORY(aObjInfo[0].Attr.fMode))
691 rcExit2 = rtZipTarCmdArchiveDir(pOpts, hVfsFss, szSrc, strlen(szSrc), aObjInfo,
692 szDst, strlen(szDst), &ErrInfo);
693 else if (RTFS_IS_FILE(aObjInfo[0].Attr.fMode))
694 rcExit2 = rtZipTarCmdArchiveFile(pOpts, hVfsFss, szSrc, aObjInfo, szDst, &ErrInfo);
695 else if (RTFS_IS_SYMLINK(aObjInfo[0].Attr.fMode))
696 rcExit2 = RTMsgErrorExitFailure("Symlink archiving is not implemented");
697 else if (RTFS_IS_FIFO(aObjInfo[0].Attr.fMode))
698 rcExit2 = RTMsgErrorExitFailure("FIFO archiving is not implemented");
699 else if (RTFS_IS_SOCKET(aObjInfo[0].Attr.fMode))
700 rcExit2 = RTMsgErrorExitFailure("Socket archiving is not implemented");
701 else if (RTFS_IS_DEV_CHAR(aObjInfo[0].Attr.fMode) || RTFS_IS_DEV_BLOCK(aObjInfo[0].Attr.fMode))
702 rcExit2 = RTMsgErrorExitFailure("Device archiving is not implemented");
703 else if (RTFS_IS_WHITEOUT(aObjInfo[0].Attr.fMode))
704 rcExit2 = RTEXITCODE_SUCCESS;
705 else
706 rcExit2 = RTMsgErrorExitFailure("Unknown file type: %#x\n", aObjInfo[0].Attr.fMode);
707 if (rcExit2 != RTEXITCODE_SUCCESS)
708 rcExit = rcExit2;
709 }
710 else
711 rcExit = RTMsgErrorExitFailure("querying object information for '%s' failed (%s)", szSrc, pszFile);
712 }
713 else
714 rcExit = RTMsgErrorExitFailure("archived file name is too long, skipping '%s' (%s)", pszDst, pszFile);
715 }
716 }
717 else
718 rcExit = RTMsgErrorExitFailure("input file name is too long, skipping '%s'", pszFile);
719 }
720
721 /*
722 * Finalize the archive.
723 */
724 int rc = RTVfsFsStrmEnd(hVfsFss);
725 if (RT_FAILURE(rc))
726 rcExit = RTMsgErrorExitFailure("RTVfsFsStrmEnd failed: %Rrc", rc);
727
728 RTVfsFsStrmRelease(hVfsFss);
729 return rcExit;
730}
731
732
733/**
734 * Opens the input archive specified by the options.
735 *
736 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
737 * @param pOpts The options.
738 * @param phVfsFss Where to return the TAR filesystem stream handle.
739 */
740static RTEXITCODE rtZipTarCmdOpenInputArchive(PRTZIPTARCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss)
741{
742 int rc;
743
744 /*
745 * Open the input file.
746 */
747 RTVFSIOSTREAM hVfsIos;
748 if ( pOpts->pszFile
749 && strcmp(pOpts->pszFile, "-") != 0)
750 {
751 uint32_t offError = 0;
752 RTERRINFOSTATIC ErrInfo;
753 rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
754 &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
755 if (RT_FAILURE(rc))
756 return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core);
757 }
758 else
759 {
760 rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT,
761 RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
762 true /*fLeaveOpen*/,
763 &hVfsIos);
764 if (RT_FAILURE(rc))
765 return RTMsgErrorExitFailure("Failed to prepare standard in for reading: %Rrc", rc);
766 }
767
768 /*
769 * Pass it thru a decompressor?
770 */
771 RTVFSIOSTREAM hVfsIosDecomp = NIL_RTVFSIOSTREAM;
772 switch (pOpts->chZipper)
773 {
774 /* no */
775 case '\0':
776 rc = VINF_SUCCESS;
777 break;
778
779 /* gunzip */
780 case 'z':
781 rc = RTZipGzipDecompressIoStream(hVfsIos, 0 /*fFlags*/, &hVfsIosDecomp);
782 if (RT_FAILURE(rc))
783 RTMsgError("Failed to open gzip decompressor: %Rrc", rc);
784 break;
785
786 /* bunzip2 */
787 case 'j':
788 rc = VERR_NOT_SUPPORTED;
789 RTMsgError("bzip2 is not supported by this build");
790 break;
791
792 /* bug */
793 default:
794 rc = VERR_INTERNAL_ERROR_2;
795 RTMsgError("unknown decompression method '%c'", pOpts->chZipper);
796 break;
797 }
798 if (RT_FAILURE(rc))
799 {
800 RTVfsIoStrmRelease(hVfsIos);
801 return RTEXITCODE_FAILURE;
802 }
803
804 if (hVfsIosDecomp != NIL_RTVFSIOSTREAM)
805 {
806 RTVfsIoStrmRelease(hVfsIos);
807 hVfsIos = hVfsIosDecomp;
808 hVfsIosDecomp = NIL_RTVFSIOSTREAM;
809 }
810
811 /*
812 * Open the filesystem stream.
813 */
814 if (pOpts->enmFormat == RTZIPTARCMDFORMAT_TAR)
815 rc = RTZipTarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
816 else if (pOpts->enmFormat == RTZIPTARCMDFORMAT_XAR)
817#ifdef IPRT_WITH_XAR /* Requires C++ and XML, so only in some configruation of IPRT. */
818 rc = RTZipXarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
819#else
820 rc = VERR_NOT_SUPPORTED;
821#endif
822 else if (pOpts->enmFormat == RTZIPTARCMDFORMAT_CPIO)
823 rc = RTZipCpioFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
824 else /** @todo make RTZipTarFsStreamFromIoStream fail if not tar file! */
825 rc = RTZipTarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
826 RTVfsIoStrmRelease(hVfsIos);
827 if (RT_FAILURE(rc))
828 return RTMsgErrorExitFailure("Failed to open tar filesystem stream: %Rrc", rc);
829
830 return RTEXITCODE_SUCCESS;
831}
832
833
834/**
835 * Worker for the --list and --extract commands.
836 *
837 * @returns The appropriate exit code.
838 * @param pOpts The tar options.
839 * @param pfnCallback The command specific callback.
840 */
841static RTEXITCODE rtZipTarDoWithMembers(PRTZIPTARCMDOPS pOpts, PFNDOWITHMEMBER pfnCallback)
842{
843 /*
844 * Allocate a bitmap to go with the file list. This will be used to
845 * indicate which files we've processed and which not.
846 */
847 uint32_t *pbmFound = NULL;
848 if (pOpts->cFiles)
849 {
850 pbmFound = (uint32_t *)RTMemAllocZ(((pOpts->cFiles + 31) / 32) * sizeof(uint32_t));
851 if (!pbmFound)
852 return RTMsgErrorExitFailure("Failed to allocate the found-file-bitmap");
853 }
854
855
856 /*
857 * Open the input archive.
858 */
859 RTVFSFSSTREAM hVfsFssIn;
860 RTEXITCODE rcExit = rtZipTarCmdOpenInputArchive(pOpts, &hVfsFssIn);
861 if (rcExit == RTEXITCODE_SUCCESS)
862 {
863 /*
864 * Process the stream.
865 */
866 for (;;)
867 {
868 /*
869 * Retrive the next object.
870 */
871 char *pszName;
872 RTVFSOBJ hVfsObj;
873 int rc = RTVfsFsStrmNext(hVfsFssIn, &pszName, NULL, &hVfsObj);
874 if (RT_FAILURE(rc))
875 {
876 if (rc != VERR_EOF)
877 rcExit = RTMsgErrorExitFailure("RTVfsFsStrmNext returned %Rrc", rc);
878 break;
879 }
880
881 /*
882 * Should we process this entry?
883 */
884 uint32_t iFile = UINT32_MAX;
885 if ( !pOpts->cFiles
886 || rtZipTarCmdIsNameInArray(pszName, pOpts->papszFiles, &iFile) )
887 {
888 if (pbmFound)
889 ASMBitSet(pbmFound, iFile);
890
891 rcExit = pfnCallback(pOpts, hVfsObj, pszName, rcExit);
892 }
893
894 /*
895 * Release the current object and string.
896 */
897 RTVfsObjRelease(hVfsObj);
898 RTStrFree(pszName);
899 }
900
901 /*
902 * Complain about any files we didn't find.
903 */
904 for (uint32_t iFile = 0; iFile < pOpts->cFiles; iFile++)
905 if (!ASMBitTest(pbmFound, iFile))
906 {
907 RTMsgError("%s: Was not found in the archive", pOpts->papszFiles[iFile]);
908 rcExit = RTEXITCODE_FAILURE;
909 }
910
911 RTVfsFsStrmRelease(hVfsFssIn);
912 }
913 RTMemFree(pbmFound);
914 return rcExit;
915}
916
917
918/**
919 * Checks if the name contains any escape sequences.
920 *
921 * An escape sequence would generally be one or more '..' references. On DOS
922 * like system, something that would make up a drive letter reference is also
923 * considered an escape sequence.
924 *
925 * @returns true / false.
926 * @param pszName The name to consider.
927 */
928static bool rtZipTarHasEscapeSequence(const char *pszName)
929{
930#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
931 if (pszName[0] == ':')
932 return true;
933#endif
934 while (*pszName)
935 {
936 while (RTPATH_IS_SEP(*pszName))
937 pszName++;
938 if ( pszName[0] == '.'
939 && pszName[1] == '.'
940 && (pszName[2] == '\0' || RTPATH_IS_SLASH(pszName[2])) )
941 return true;
942 while (*pszName && !RTPATH_IS_SEP(*pszName))
943 pszName++;
944 }
945
946 return false;
947}
948
949
950#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
951
952/**
953 * Queries the user ID to use when extracting a member.
954 *
955 * @returns rcExit or RTEXITCODE_FAILURE.
956 * @param pOpts The tar options.
957 * @param pUser The user info.
958 * @param pszName The file name to use when complaining.
959 * @param rcExit The current exit code.
960 * @param pUid Where to return the user ID.
961 */
962static RTEXITCODE rtZipTarQueryExtractOwner(PRTZIPTARCMDOPS pOpts, PCRTFSOBJINFO pOwner, const char *pszName, RTEXITCODE rcExit,
963 PRTUID pUid)
964{
965 if (pOpts->uidOwner != NIL_RTUID)
966 *pUid = pOpts->uidOwner;
967 else if (pOpts->fPreserveGroup)
968 {
969 if (!pOwner->Attr.u.UnixGroup.szName[0])
970 *pUid = pOwner->Attr.u.UnixOwner.uid;
971 else
972 {
973 *pUid = NIL_RTUID;
974 return RTMsgErrorExitFailure("%s: User resolving is not implemented.", pszName);
975 }
976 }
977 else
978 *pUid = NIL_RTUID;
979 return rcExit;
980}
981
982
983/**
984 * Queries the group ID to use when extracting a member.
985 *
986 * @returns rcExit or RTEXITCODE_FAILURE.
987 * @param pOpts The tar options.
988 * @param pGroup The group info.
989 * @param pszName The file name to use when complaining.
990 * @param rcExit The current exit code.
991 * @param pGid Where to return the group ID.
992 */
993static RTEXITCODE rtZipTarQueryExtractGroup(PRTZIPTARCMDOPS pOpts, PCRTFSOBJINFO pGroup, const char *pszName, RTEXITCODE rcExit,
994 PRTGID pGid)
995{
996 if (pOpts->gidGroup != NIL_RTGID)
997 *pGid = pOpts->gidGroup;
998 else if (pOpts->fPreserveGroup)
999 {
1000 if (!pGroup->Attr.u.UnixGroup.szName[0])
1001 *pGid = pGroup->Attr.u.UnixGroup.gid;
1002 else
1003 {
1004 *pGid = NIL_RTGID;
1005 return RTMsgErrorExitFailure("%s: Group resolving is not implemented.", pszName);
1006 }
1007 }
1008 else
1009 *pGid = NIL_RTGID;
1010 return rcExit;
1011}
1012
1013#endif /* !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) */
1014
1015
1016/**
1017 * Corrects the file mode and other attributes.
1018 *
1019 * Common worker for rtZipTarCmdExtractFile and rtZipTarCmdExtractHardlink.
1020 *
1021 * @returns rcExit or RTEXITCODE_FAILURE.
1022 * @param pOpts The tar options.
1023 * @param rcExit The current exit code.
1024 * @param hFile The handle to the destination file.
1025 * @param pszDst The destination path (for error reporting).
1026 * @param pUnixInfo The unix fs object info.
1027 * @param pOwner The owner info.
1028 * @param pGroup The group info.
1029 */
1030static RTEXITCODE rtZipTarCmdExtractSetAttribs(PRTZIPTARCMDOPS pOpts, RTEXITCODE rcExit, RTFILE hFile, const char *pszDst,
1031 PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner, PCRTFSOBJINFO pGroup)
1032{
1033 int rc;
1034
1035 if (!pOpts->fNoModTime)
1036 {
1037 rc = RTFileSetTimes(hFile, NULL, &pUnixInfo->ModificationTime, NULL, NULL);
1038 if (RT_FAILURE(rc))
1039 rcExit = RTMsgErrorExitFailure("%s: Error setting times: %Rrc", pszDst, rc);
1040 }
1041
1042#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
1043 if ( pOpts->uidOwner != NIL_RTUID
1044 || pOpts->gidGroup != NIL_RTGID
1045 || pOpts->fPreserveOwner
1046 || pOpts->fPreserveGroup)
1047 {
1048 RTUID uidFile;
1049 rcExit = rtZipTarQueryExtractOwner(pOpts, pOwner, pszDst, rcExit, &uidFile);
1050
1051 RTGID gidFile;
1052 rcExit = rtZipTarQueryExtractGroup(pOpts, pGroup, pszDst, rcExit, &gidFile);
1053 if (uidFile != NIL_RTUID || gidFile != NIL_RTGID)
1054 {
1055 rc = RTFileSetOwner(hFile, uidFile, gidFile);
1056 if (RT_FAILURE(rc))
1057 rcExit = RTMsgErrorExitFailure("%s: Error owner/group: %Rrc", pszDst, rc);
1058 }
1059 }
1060#else
1061 RT_NOREF_PV(pOwner); RT_NOREF_PV(pGroup);
1062#endif
1063
1064 RTFMODE fMode = (pUnixInfo->Attr.fMode & pOpts->fFileModeAndMask) | pOpts->fFileModeOrMask;
1065 rc = RTFileSetMode(hFile, fMode | RTFS_TYPE_FILE);
1066 if (RT_FAILURE(rc))
1067 rcExit = RTMsgErrorExitFailure("%s: Error changing mode: %Rrc", pszDst, rc);
1068
1069 return rcExit;
1070}
1071
1072
1073/**
1074 * Extracts a hard linked file.
1075 *
1076 * @returns rcExit or RTEXITCODE_FAILURE.
1077 * @param pOpts The tar options.
1078 * @param rcExit The current exit code.
1079 * @param pszDst The destination path.
1080 * @param pszTarget The target relative path.
1081 * @param pUnixInfo The unix fs object info.
1082 * @param pOwner The owner info.
1083 * @param pGroup The group info.
1084 */
1085static RTEXITCODE rtZipTarCmdExtractHardlink(PRTZIPTARCMDOPS pOpts, RTEXITCODE rcExit, const char *pszDst,
1086 const char *pszTarget, PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner,
1087 PCRTFSOBJINFO pGroup)
1088{
1089 /*
1090 * Construct full target path and check that it exists.
1091 */
1092 char szFullTarget[RTPATH_MAX];
1093 int rc = RTPathJoin(szFullTarget, sizeof(szFullTarget), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszTarget);
1094 if (RT_FAILURE(rc))
1095 return RTMsgErrorExitFailure("%s: Failed to construct full hardlink target path for %s: %Rrc",
1096 pszDst, pszTarget, rc);
1097
1098 if (!RTFileExists(szFullTarget))
1099 return RTMsgErrorExitFailure("%s: Hardlink target not found (or not a file): %s", pszDst, szFullTarget);
1100
1101 /*
1102 * Try hardlink the file, falling back on copying.
1103 */
1104 /** @todo actual hardlinking */
1105 if (true)
1106 {
1107 RTMsgWarning("%s: Hardlinking not available, copying '%s' instead.", pszDst, szFullTarget);
1108
1109 RTFILE hSrcFile;
1110 rc = RTFileOpen(&hSrcFile, szFullTarget, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN);
1111 if (RT_SUCCESS(rc))
1112 {
1113 uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT
1114 | ((RTFS_UNIX_IWUSR | RTFS_UNIX_IRUSR) << RTFILE_O_CREATE_MODE_SHIFT);
1115 RTFILE hDstFile;
1116 rc = RTFileOpen(&hDstFile, pszDst, fOpen);
1117 if (RT_SUCCESS(rc))
1118 {
1119 rc = RTFileCopyByHandles(hSrcFile, hDstFile);
1120 if (RT_SUCCESS(rc))
1121 {
1122 rcExit = rtZipTarCmdExtractSetAttribs(pOpts, rcExit, hDstFile, pszDst, pUnixInfo, pOwner, pGroup);
1123 rc = RTFileClose(hDstFile);
1124 if (RT_FAILURE(rc))
1125 {
1126 rcExit = RTMsgErrorExitFailure("%s: Error closing hardlinked file copy: %Rrc", pszDst, rc);
1127 RTFileDelete(pszDst);
1128 }
1129 }
1130 else
1131 {
1132 rcExit = RTMsgErrorExitFailure("%s: Failed copying hardlinked file '%s': %Rrc", pszDst, szFullTarget, rc);
1133 rc = RTFileClose(hDstFile);
1134 RTFileDelete(pszDst);
1135 }
1136 }
1137 else
1138 rcExit = RTMsgErrorExitFailure("%s: Error creating file: %Rrc", pszDst, rc);
1139 RTFileClose(hSrcFile);
1140 }
1141 else
1142 rcExit = RTMsgErrorExitFailure("%s: Error opening file '%s' for reading (hardlink target): %Rrc",
1143 pszDst, szFullTarget, rc);
1144 }
1145
1146 return rcExit;
1147}
1148
1149
1150
1151/**
1152 * Extracts a file.
1153 *
1154 * Since we can restore permissions and attributes more efficiently by working
1155 * directly on the file handle, we have special code path for files.
1156 *
1157 * @returns rcExit or RTEXITCODE_FAILURE.
1158 * @param pOpts The tar options.
1159 * @param hVfsObj The tar object to display
1160 * @param rcExit The current exit code.
1161 * @param pszDst The destination path.
1162 * @param pUnixInfo The unix fs object info.
1163 * @param pOwner The owner info.
1164 * @param pGroup The group info.
1165 */
1166static RTEXITCODE rtZipTarCmdExtractFile(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, RTEXITCODE rcExit,
1167 const char *pszDst, PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner, PCRTFSOBJINFO pGroup)
1168{
1169 /*
1170 * Open the destination file and create a stream object for it.
1171 */
1172 uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT
1173 | ((RTFS_UNIX_IWUSR | RTFS_UNIX_IRUSR) << RTFILE_O_CREATE_MODE_SHIFT);
1174 RTFILE hFile;
1175 int rc = RTFileOpen(&hFile, pszDst, fOpen);
1176 if (RT_FAILURE(rc))
1177 return RTMsgErrorExitFailure("%s: Error creating file: %Rrc", pszDst, rc);
1178
1179 RTVFSIOSTREAM hVfsIosDst;
1180 rc = RTVfsIoStrmFromRTFile(hFile, fOpen, true /*fLeaveOpen*/, &hVfsIosDst);
1181 if (RT_SUCCESS(rc))
1182 {
1183 /*
1184 * Convert source to a stream and optionally add a read ahead stage.
1185 */
1186 RTVFSIOSTREAM hVfsIosSrc = RTVfsObjToIoStream(hVfsObj);
1187 if (pOpts->fReadAhead)
1188 {
1189 RTVFSIOSTREAM hVfsReadAhead;
1190 rc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlag*/, 16 /*cBuffers*/, _256K /*cbBuffer*/, &hVfsReadAhead);
1191 if (RT_SUCCESS(rc))
1192 {
1193 RTVfsIoStrmRelease(hVfsIosSrc);
1194 hVfsIosSrc = hVfsReadAhead;
1195 }
1196 else
1197 AssertRC(rc); /* can be ignored in release builds. */
1198 }
1199
1200 /*
1201 * Pump the data thru and correct the file attributes.
1202 */
1203 rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(pUnixInfo->cbObject, _1M));
1204 if (RT_SUCCESS(rc))
1205 rcExit = rtZipTarCmdExtractSetAttribs(pOpts, rcExit, hFile, pszDst, pUnixInfo, pOwner, pGroup);
1206 else
1207 rcExit = RTMsgErrorExitFailure("%s: Error writing out file: %Rrc", pszDst, rc);
1208 RTVfsIoStrmRelease(hVfsIosSrc);
1209 RTVfsIoStrmRelease(hVfsIosDst);
1210 }
1211 else
1212 rcExit = RTMsgErrorExitFailure("%s: Error creating I/O stream for file: %Rrc", pszDst, rc);
1213 RTFileClose(hFile);
1214 return rcExit;
1215}
1216
1217
1218/**
1219 * @callback_method_impl{PFNDOWITHMEMBER, Implements --extract.}
1220 */
1221static RTEXITCODE rtZipTarCmdExtractCallback(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit)
1222{
1223 if (pOpts->fVerbose)
1224 RTPrintf("%s\n", pszName);
1225
1226 /*
1227 * Query all the information.
1228 */
1229 RTFSOBJINFO UnixInfo;
1230 int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
1231 if (RT_FAILURE(rc))
1232 return RTMsgErrorExitFailure("RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
1233
1234 RTFSOBJINFO Owner;
1235 rc = RTVfsObjQueryInfo(hVfsObj, &Owner, RTFSOBJATTRADD_UNIX_OWNER);
1236 if (RT_FAILURE(rc))
1237 return RTMsgErrorExit(RTEXITCODE_FAILURE,
1238 "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
1239 rc, pszName);
1240
1241 RTFSOBJINFO Group;
1242 rc = RTVfsObjQueryInfo(hVfsObj, &Group, RTFSOBJATTRADD_UNIX_GROUP);
1243 if (RT_FAILURE(rc))
1244 return RTMsgErrorExit(RTEXITCODE_FAILURE,
1245 "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
1246 rc, pszName);
1247
1248 bool fIsHardLink = false;
1249 char szTarget[RTPATH_MAX];
1250 szTarget[0] = '\0';
1251 RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
1252 if (hVfsSymlink != NIL_RTVFSSYMLINK)
1253 {
1254 rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
1255 RTVfsSymlinkRelease(hVfsSymlink);
1256 if (RT_FAILURE(rc))
1257 return RTMsgErrorExitFailure("%s: RTVfsSymlinkRead failed: %Rrc", pszName, rc);
1258 if (!szTarget[0])
1259 return RTMsgErrorExitFailure("%s: Link target is empty.", pszName);
1260 if (!RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
1261 {
1262 fIsHardLink = true;
1263 if (!RTFS_IS_FILE(UnixInfo.Attr.fMode))
1264 return RTMsgErrorExitFailure("%s: Hardlinks are only supported for regular files (target=%s).",
1265 pszName, szTarget);
1266 if (rtZipTarHasEscapeSequence(pszName))
1267 return RTMsgErrorExitFailure("%s: Hardlink target '%s' contains an escape sequence.",
1268 pszName, szTarget);
1269 }
1270 }
1271 else if (RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
1272 return RTMsgErrorExitFailure("Failed to get symlink object for '%s'", pszName);
1273
1274 if (rtZipTarHasEscapeSequence(pszName))
1275 return RTMsgErrorExitFailure("Name '%s' contains an escape sequence.", pszName);
1276
1277 /*
1278 * Construct the path to the extracted member.
1279 */
1280 char szDst[RTPATH_MAX];
1281 rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszName);
1282 if (RT_FAILURE(rc))
1283 return RTMsgErrorExitFailure("%s: Failed to construct destination path for: %Rrc", pszName, rc);
1284
1285 /*
1286 * Extract according to the type.
1287 */
1288 if (!fIsHardLink)
1289 switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
1290 {
1291 case RTFS_TYPE_FILE:
1292 return rtZipTarCmdExtractFile(pOpts, hVfsObj, rcExit, szDst, &UnixInfo, &Owner, &Group);
1293
1294 case RTFS_TYPE_DIRECTORY:
1295 rc = RTDirCreateFullPath(szDst, UnixInfo.Attr.fMode & RTFS_UNIX_ALL_ACCESS_PERMS);
1296 if (RT_FAILURE(rc))
1297 return RTMsgErrorExitFailure("%s: Error creating directory: %Rrc", szDst, rc);
1298 break;
1299
1300 case RTFS_TYPE_SYMLINK:
1301 rc = RTSymlinkCreate(szDst, szTarget, RTSYMLINKTYPE_UNKNOWN, 0);
1302 if (RT_FAILURE(rc))
1303 return RTMsgErrorExitFailure("%s: Error creating symbolic link: %Rrc", szDst, rc);
1304 break;
1305
1306 case RTFS_TYPE_FIFO:
1307 return RTMsgErrorExitFailure("%s: FIFOs are not supported.", pszName);
1308 case RTFS_TYPE_DEV_CHAR:
1309 return RTMsgErrorExitFailure("%s: FIFOs are not supported.", pszName);
1310 case RTFS_TYPE_DEV_BLOCK:
1311 return RTMsgErrorExitFailure("%s: Block devices are not supported.", pszName);
1312 case RTFS_TYPE_SOCKET:
1313 return RTMsgErrorExitFailure("%s: Sockets are not supported.", pszName);
1314 case RTFS_TYPE_WHITEOUT:
1315 return RTMsgErrorExitFailure("%s: Whiteouts are not support.", pszName);
1316 default:
1317 return RTMsgErrorExitFailure("%s: Unknown file type.", pszName);
1318 }
1319 else
1320 return rtZipTarCmdExtractHardlink(pOpts, rcExit, szDst, szTarget, &UnixInfo, &Owner, &Group);
1321
1322 /*
1323 * Set other attributes as requested.
1324 *
1325 * Note! File extraction does get here.
1326 */
1327 if (!pOpts->fNoModTime)
1328 {
1329 rc = RTPathSetTimesEx(szDst, NULL, &UnixInfo.ModificationTime, NULL, NULL, RTPATH_F_ON_LINK);
1330 if (RT_FAILURE(rc) && rc != VERR_NOT_SUPPORTED && rc != VERR_NS_SYMLINK_SET_TIME)
1331 rcExit = RTMsgErrorExitFailure("%s: Error changing modification time: %Rrc.", pszName, rc);
1332 }
1333
1334#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
1335 if ( pOpts->uidOwner != NIL_RTUID
1336 || pOpts->gidGroup != NIL_RTGID
1337 || pOpts->fPreserveOwner
1338 || pOpts->fPreserveGroup)
1339 {
1340 RTUID uidFile;
1341 rcExit = rtZipTarQueryExtractOwner(pOpts, &Owner, szDst, rcExit, &uidFile);
1342
1343 RTGID gidFile;
1344 rcExit = rtZipTarQueryExtractGroup(pOpts, &Group, szDst, rcExit, &gidFile);
1345 if (uidFile != NIL_RTUID || gidFile != NIL_RTGID)
1346 {
1347 rc = RTPathSetOwnerEx(szDst, uidFile, gidFile, RTPATH_F_ON_LINK);
1348 if (RT_FAILURE(rc))
1349 rcExit = RTMsgErrorExitFailure("%s: Error owner/group: %Rrc", szDst, rc);
1350 }
1351 }
1352#endif
1353
1354#if !defined(RT_OS_WINDOWS) /** @todo implement RTPathSetMode on windows... */
1355 if (!RTFS_IS_SYMLINK(UnixInfo.Attr.fMode)) /* RTPathSetMode follows symbolic links atm. */
1356 {
1357 RTFMODE fMode;
1358 if (RTFS_IS_DIRECTORY(UnixInfo.Attr.fMode))
1359 fMode = (UnixInfo.Attr.fMode & (pOpts->fDirModeAndMask | RTFS_TYPE_MASK)) | pOpts->fDirModeOrMask;
1360 else
1361 fMode = (UnixInfo.Attr.fMode & (pOpts->fFileModeAndMask | RTFS_TYPE_MASK)) | pOpts->fFileModeOrMask;
1362 rc = RTPathSetMode(szDst, fMode);
1363 if (RT_FAILURE(rc))
1364 rcExit = RTMsgErrorExitFailure("%s: Error changing mode: %Rrc", szDst, rc);
1365 }
1366#endif
1367
1368 return rcExit;
1369}
1370
1371
1372/**
1373 * @callback_method_impl{PFNDOWITHMEMBER, Implements --list.}
1374 */
1375static RTEXITCODE rtZipTarCmdListCallback(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit)
1376{
1377 /*
1378 * This is very simple in non-verbose mode.
1379 */
1380 if (!pOpts->fVerbose)
1381 {
1382 RTPrintf("%s\n", pszName);
1383 return rcExit;
1384 }
1385
1386 /*
1387 * Query all the information.
1388 */
1389 RTFSOBJINFO UnixInfo;
1390 int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
1391 if (RT_FAILURE(rc))
1392 {
1393 rcExit = RTMsgErrorExitFailure("RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
1394 RT_ZERO(UnixInfo);
1395 }
1396
1397 RTFSOBJINFO Owner;
1398 rc = RTVfsObjQueryInfo(hVfsObj, &Owner, RTFSOBJATTRADD_UNIX_OWNER);
1399 if (RT_FAILURE(rc))
1400 {
1401 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
1402 "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
1403 rc, pszName);
1404 RT_ZERO(Owner);
1405 }
1406
1407 RTFSOBJINFO Group;
1408 rc = RTVfsObjQueryInfo(hVfsObj, &Group, RTFSOBJATTRADD_UNIX_GROUP);
1409 if (RT_FAILURE(rc))
1410 {
1411 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
1412 "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
1413 rc, pszName);
1414 RT_ZERO(Group);
1415 }
1416
1417 const char *pszLinkType = NULL;
1418 char szTarget[RTPATH_MAX];
1419 szTarget[0] = '\0';
1420 RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
1421 if (hVfsSymlink != NIL_RTVFSSYMLINK)
1422 {
1423 rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
1424 if (RT_FAILURE(rc))
1425 rcExit = RTMsgErrorExitFailure("RTVfsSymlinkRead returned %Rrc on '%s'", rc, pszName);
1426 RTVfsSymlinkRelease(hVfsSymlink);
1427 pszLinkType = RTFS_IS_SYMLINK(UnixInfo.Attr.fMode) ? "->" : "link to";
1428 }
1429 else if (RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
1430 rcExit = RTMsgErrorExitFailure("Failed to get symlink object for '%s'", pszName);
1431
1432 /*
1433 * Translate the mode mask.
1434 */
1435 char szMode[16];
1436 switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
1437 {
1438 case RTFS_TYPE_FIFO: szMode[0] = 'f'; break;
1439 case RTFS_TYPE_DEV_CHAR: szMode[0] = 'c'; break;
1440 case RTFS_TYPE_DIRECTORY: szMode[0] = 'd'; break;
1441 case RTFS_TYPE_DEV_BLOCK: szMode[0] = 'b'; break;
1442 case RTFS_TYPE_FILE: szMode[0] = '-'; break;
1443 case RTFS_TYPE_SYMLINK: szMode[0] = 'l'; break;
1444 case RTFS_TYPE_SOCKET: szMode[0] = 's'; break;
1445 case RTFS_TYPE_WHITEOUT: szMode[0] = 'w'; break;
1446 default: szMode[0] = '?'; break;
1447 }
1448 if (pszLinkType && szMode[0] != 's')
1449 szMode[0] = 'h';
1450
1451 szMode[1] = UnixInfo.Attr.fMode & RTFS_UNIX_IRUSR ? 'r' : '-';
1452 szMode[2] = UnixInfo.Attr.fMode & RTFS_UNIX_IWUSR ? 'w' : '-';
1453 szMode[3] = UnixInfo.Attr.fMode & RTFS_UNIX_IXUSR ? 'x' : '-';
1454
1455 szMode[4] = UnixInfo.Attr.fMode & RTFS_UNIX_IRGRP ? 'r' : '-';
1456 szMode[5] = UnixInfo.Attr.fMode & RTFS_UNIX_IWGRP ? 'w' : '-';
1457 szMode[6] = UnixInfo.Attr.fMode & RTFS_UNIX_IXGRP ? 'x' : '-';
1458
1459 szMode[7] = UnixInfo.Attr.fMode & RTFS_UNIX_IROTH ? 'r' : '-';
1460 szMode[8] = UnixInfo.Attr.fMode & RTFS_UNIX_IWOTH ? 'w' : '-';
1461 szMode[9] = UnixInfo.Attr.fMode & RTFS_UNIX_IXOTH ? 'x' : '-';
1462 szMode[10] = '\0';
1463
1464 /** @todo sticky and set-uid/gid bits. */
1465
1466 /*
1467 * Make sure we've got valid owner and group strings.
1468 */
1469 if (!Owner.Attr.u.UnixGroup.szName[0])
1470 RTStrPrintf(Owner.Attr.u.UnixOwner.szName, sizeof(Owner.Attr.u.UnixOwner.szName),
1471 "%u", UnixInfo.Attr.u.Unix.uid);
1472
1473 if (!Group.Attr.u.UnixOwner.szName[0])
1474 RTStrPrintf(Group.Attr.u.UnixGroup.szName, sizeof(Group.Attr.u.UnixGroup.szName),
1475 "%u", UnixInfo.Attr.u.Unix.gid);
1476
1477 /*
1478 * Format the modification time.
1479 */
1480 char szModTime[32];
1481 RTTIME ModTime;
1482 PRTTIME pTime;
1483 if (!pOpts->fDisplayUtc)
1484 pTime = RTTimeLocalExplode(&ModTime, &UnixInfo.ModificationTime);
1485 else
1486 pTime = RTTimeExplode(&ModTime, &UnixInfo.ModificationTime);
1487 if (!pTime)
1488 RT_ZERO(ModTime);
1489 RTStrPrintf(szModTime, sizeof(szModTime), "%04d-%02u-%02u %02u:%02u",
1490 ModTime.i32Year, ModTime.u8Month, ModTime.u8MonthDay, ModTime.u8Hour, ModTime.u8Minute);
1491
1492 /*
1493 * Format the size and figure how much space is needed between the
1494 * user/group and the size.
1495 */
1496 char szSize[64];
1497 size_t cchSize;
1498 switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
1499 {
1500 case RTFS_TYPE_DEV_CHAR:
1501 case RTFS_TYPE_DEV_BLOCK:
1502 cchSize = RTStrPrintf(szSize, sizeof(szSize), "%u,%u",
1503 RTDEV_MAJOR(UnixInfo.Attr.u.Unix.Device), RTDEV_MINOR(UnixInfo.Attr.u.Unix.Device));
1504 break;
1505 default:
1506 cchSize = RTStrPrintf(szSize, sizeof(szSize), "%RU64", UnixInfo.cbObject);
1507 break;
1508 }
1509
1510 size_t cchUserGroup = strlen(Owner.Attr.u.UnixOwner.szName)
1511 + 1
1512 + strlen(Group.Attr.u.UnixGroup.szName);
1513 ssize_t cchPad = cchUserGroup + cchSize + 1 < 19
1514 ? 19 - (cchUserGroup + cchSize + 1)
1515 : 0;
1516
1517 /*
1518 * Go to press.
1519 */
1520 if (pszLinkType)
1521 RTPrintf("%s %s/%s%*s %s %s %s %s %s\n",
1522 szMode,
1523 Owner.Attr.u.UnixOwner.szName, Group.Attr.u.UnixGroup.szName,
1524 cchPad, "",
1525 szSize,
1526 szModTime,
1527 pszName,
1528 pszLinkType,
1529 szTarget);
1530 else
1531 RTPrintf("%s %s/%s%*s %s %s %s\n",
1532 szMode,
1533 Owner.Attr.u.UnixOwner.szName, Group.Attr.u.UnixGroup.szName,
1534 cchPad, "",
1535 szSize,
1536 szModTime,
1537 pszName);
1538
1539 return rcExit;
1540}
1541
1542
1543/**
1544 * Display usage.
1545 *
1546 * @param pszProgName The program name.
1547 */
1548static void rtZipTarUsage(const char *pszProgName)
1549{
1550 /*
1551 * 0 1 2 3 4 5 6 7 8
1552 * 012345678901234567890123456789012345678901234567890123456789012345678901234567890
1553 */
1554 RTPrintf("Usage: %s [options]\n"
1555 "\n",
1556 pszProgName);
1557 RTPrintf("Operations:\n"
1558 " -A, --concatenate, --catenate\n"
1559 " Append the content of one tar archive to another. (not impl)\n"
1560 " -c, --create\n"
1561 " Create a new tar archive. (not impl)\n"
1562 " -d, --diff, --compare\n"
1563 " Compare atar archive with the file system. (not impl)\n"
1564 " -r, --append\n"
1565 " Append more files to the tar archive. (not impl)\n"
1566 " -t, --list\n"
1567 " List the contents of the tar archive.\n"
1568 " -u, --update\n"
1569 " Update the archive, adding files that are newer than the\n"
1570 " ones in the archive. (not impl)\n"
1571 " -x, --extract, --get\n"
1572 " Extract the files from the tar archive.\n"
1573 " --delete\n"
1574 " Delete files from the tar archive.\n"
1575 "\n"
1576 );
1577 RTPrintf("Basic Options:\n"
1578 " -C <dir>, --directory <dir> (-A, -c, -d, -r, -u, -x)\n"
1579 " Sets the base directory for input and output file members.\n"
1580 " This does not apply to --file, even if it preceeds it.\n"
1581 " -f <archive>, --file <archive> (all)\n"
1582 " The tar file to create or process. '-' indicates stdout/stdin,\n"
1583 " which is is the default.\n"
1584 " -v, --verbose (all)\n"
1585 " Verbose operation.\n"
1586 " -p, --preserve-permissions (-x)\n"
1587 " Preserve all permissions when extracting. Must be used\n"
1588 " before the mode mask options as it will change some of these.\n"
1589 " -j, --bzip2 (all)\n"
1590 " Compress/decompress the archive with bzip2.\n"
1591 " -z, --gzip, --gunzip, --ungzip (all)\n"
1592 " Compress/decompress the archive with gzip.\n"
1593 "\n");
1594 RTPrintf("Misc Options:\n"
1595 " --owner <uid/username> (-A, -c, -d, -r, -u, -x)\n"
1596 " Set the owner of extracted and archived files to the user specified.\n"
1597 " --group <uid/username> (-A, -c, -d, -r, -u, -x)\n"
1598 " Set the group of extracted and archived files to the group specified.\n"
1599 " --utc (-t)\n"
1600 " Display timestamps as UTC instead of local time.\n"
1601 " -S, --sparse (-A, -c, -u)\n"
1602 " Detect sparse files and store them (gnu tar extension).\n"
1603 " --format <format> (-A, -c, -u, but also -d, -r, -x)\n"
1604 " The file format:\n"
1605 " auto (gnu tar)\n"
1606 " default (gnu tar)\n"
1607 " tar (gnu tar)"
1608 " gnu (tar v1.13+), "
1609 " ustar (tar POSIX.1-1988), "
1610 " pax (tar POSIX.1-2001),\n"
1611 " xar\n"
1612 " cpio\n"
1613 " Note! Because XAR/TAR/CPIO detection isn't implemented yet, it\n"
1614 " is necessary to specifcy --format=xar when reading a\n"
1615 " XAR file or --format=cpio for a CPIO file.\n"
1616 " Otherwise this option is only for creation.\n"
1617 "\n");
1618 RTPrintf("IPRT Options:\n"
1619 " --prefix <dir-prefix> (-A, -c, -d, -r, -u)\n"
1620 " Directory prefix to give the members added to the archive.\n"
1621 " --file-mode-and-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
1622 " Restrict the access mode of regular and special files.\n"
1623 " --file-mode-or-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
1624 " Include the given access mode for regular and special files.\n"
1625 " --dir-mode-and-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
1626 " Restrict the access mode of directories.\n"
1627 " --dir-mode-or-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
1628 " Include the given access mode for directories.\n"
1629 " --read-ahead (-x)\n"
1630 " Enabled read ahead thread when extracting files.\n"
1631 " --push-file (-A, -c, -u)\n"
1632 " Use RTVfsFsStrmPushFile instead of RTVfsFsStrmAdd.\n"
1633 "\n");
1634 RTPrintf("Standard Options:\n"
1635 " -h, -?, --help\n"
1636 " Display this help text.\n"
1637 " -V, --version\n"
1638 " Display version number.\n");
1639}
1640
1641
1642RTDECL(RTEXITCODE) RTZipTarCmd(unsigned cArgs, char **papszArgs)
1643{
1644 /*
1645 * Parse the command line.
1646 *
1647 * N.B. This is less flexible that your regular tar program in that it
1648 * requires the operation to be specified as an option. On the other
1649 * hand, you can specify it where ever you like in the command line.
1650 */
1651 static const RTGETOPTDEF s_aOptions[] =
1652 {
1653 /* operations */
1654 { "--concatenate", 'A', RTGETOPT_REQ_NOTHING },
1655 { "--catenate", 'A', RTGETOPT_REQ_NOTHING },
1656 { "--create", 'c', RTGETOPT_REQ_NOTHING },
1657 { "--diff", 'd', RTGETOPT_REQ_NOTHING },
1658 { "--compare", 'd', RTGETOPT_REQ_NOTHING },
1659 { "--append", 'r', RTGETOPT_REQ_NOTHING },
1660 { "--list", 't', RTGETOPT_REQ_NOTHING },
1661 { "--update", 'u', RTGETOPT_REQ_NOTHING },
1662 { "--extract", 'x', RTGETOPT_REQ_NOTHING },
1663 { "--get", 'x', RTGETOPT_REQ_NOTHING },
1664 { "--delete", RTZIPTARCMD_OPT_DELETE, RTGETOPT_REQ_NOTHING },
1665
1666 /* basic options */
1667 { "--directory", 'C', RTGETOPT_REQ_STRING },
1668 { "--file", 'f', RTGETOPT_REQ_STRING },
1669 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
1670 { "--preserve-permissions", 'p', RTGETOPT_REQ_NOTHING },
1671 { "--bzip2", 'j', RTGETOPT_REQ_NOTHING },
1672 { "--gzip", 'z', RTGETOPT_REQ_NOTHING },
1673 { "--gunzip", 'z', RTGETOPT_REQ_NOTHING },
1674 { "--ungzip", 'z', RTGETOPT_REQ_NOTHING },
1675
1676 /* other options. */
1677 { "--owner", RTZIPTARCMD_OPT_OWNER, RTGETOPT_REQ_STRING },
1678 { "--group", RTZIPTARCMD_OPT_GROUP, RTGETOPT_REQ_STRING },
1679 { "--utc", RTZIPTARCMD_OPT_UTC, RTGETOPT_REQ_NOTHING },
1680 { "--sparse", 'S', RTGETOPT_REQ_NOTHING },
1681 { "--format", RTZIPTARCMD_OPT_FORMAT, RTGETOPT_REQ_STRING },
1682 { "--no-recursion", RTZIPTARCMD_OPT_NO_RECURSION, RTGETOPT_REQ_NOTHING },
1683
1684 /* IPRT extensions */
1685 { "--prefix", RTZIPTARCMD_OPT_PREFIX, RTGETOPT_REQ_STRING },
1686 { "--file-mode-and-mask", RTZIPTARCMD_OPT_FILE_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
1687 { "--file-mode-or-mask", RTZIPTARCMD_OPT_FILE_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
1688 { "--dir-mode-and-mask", RTZIPTARCMD_OPT_DIR_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
1689 { "--dir-mode-or-mask", RTZIPTARCMD_OPT_DIR_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
1690 { "--read-ahead", RTZIPTARCMD_OPT_READ_AHEAD, RTGETOPT_REQ_NOTHING },
1691 { "--use-push-file", RTZIPTARCMD_OPT_USE_PUSH_FILE, RTGETOPT_REQ_NOTHING },
1692 };
1693
1694 RTGETOPTSTATE GetState;
1695 int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
1696 RTGETOPTINIT_FLAGS_OPTS_FIRST);
1697 if (RT_FAILURE(rc))
1698 return RTMsgErrorExitFailure("RTGetOpt failed: %Rrc", rc);
1699
1700 RTZIPTARCMDOPS Opts;
1701 RT_ZERO(Opts);
1702 Opts.enmFormat = RTZIPTARCMDFORMAT_AUTO_DEFAULT;
1703 Opts.uidOwner = NIL_RTUID;
1704 Opts.gidGroup = NIL_RTUID;
1705 Opts.fFileModeAndMask = RTFS_UNIX_ALL_ACCESS_PERMS;
1706 Opts.fDirModeAndMask = RTFS_UNIX_ALL_ACCESS_PERMS;
1707#if 0
1708 if (RTPermIsSuperUser())
1709 {
1710 Opts.fFileModeAndMask = RTFS_UNIX_ALL_PERMS;
1711 Opts.fDirModeAndMask = RTFS_UNIX_ALL_PERMS;
1712 Opts.fPreserveOwner = true;
1713 Opts.fPreserveGroup = true;
1714 }
1715#endif
1716 Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
1717 Opts.fRecursive = true; /* Recursion is implicit unless otherwise specified. */
1718
1719 RTGETOPTUNION ValueUnion;
1720 while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
1721 && rc != VINF_GETOPT_NOT_OPTION)
1722 {
1723 switch (rc)
1724 {
1725 /* operations */
1726 case 'A':
1727 case 'c':
1728 case 'd':
1729 case 'r':
1730 case 't':
1731 case 'u':
1732 case 'x':
1733 case RTZIPTARCMD_OPT_DELETE:
1734 if (Opts.iOperation)
1735 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Conflicting tar operation (%s already set, now %s)",
1736 Opts.pszOperation, ValueUnion.pDef->pszLong);
1737 Opts.iOperation = rc;
1738 Opts.pszOperation = ValueUnion.pDef->pszLong;
1739 break;
1740
1741 /* basic options */
1742 case 'C':
1743 if (Opts.pszDirectory)
1744 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -C/--directory once");
1745 Opts.pszDirectory = ValueUnion.psz;
1746 break;
1747
1748 case 'f':
1749 if (Opts.pszFile)
1750 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -f/--file once");
1751 Opts.pszFile = ValueUnion.psz;
1752 break;
1753
1754 case 'v':
1755 Opts.fVerbose = true;
1756 break;
1757
1758 case 'p':
1759 Opts.fFileModeAndMask = RTFS_UNIX_ALL_PERMS;
1760 Opts.fDirModeAndMask = RTFS_UNIX_ALL_PERMS;
1761 Opts.fPreserveOwner = true;
1762 Opts.fPreserveGroup = true;
1763 break;
1764
1765 case 'j':
1766 case 'z':
1767 if (Opts.chZipper)
1768 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify one compressor / decompressor");
1769 Opts.chZipper = rc;
1770 break;
1771
1772 case RTZIPTARCMD_OPT_OWNER:
1773 if (Opts.pszOwner)
1774 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --owner once");
1775 Opts.pszOwner = ValueUnion.psz;
1776
1777 rc = RTStrToUInt32Full(Opts.pszOwner, 0, &ValueUnion.u32);
1778 if (RT_SUCCESS(rc) && rc != VINF_SUCCESS)
1779 return RTMsgErrorExit(RTEXITCODE_SYNTAX,
1780 "Error convering --owner '%s' into a number: %Rrc", Opts.pszOwner, rc);
1781 if (RT_SUCCESS(rc))
1782 {
1783 Opts.uidOwner = ValueUnion.u32;
1784 Opts.pszOwner = NULL;
1785 }
1786 break;
1787
1788 case RTZIPTARCMD_OPT_GROUP:
1789 if (Opts.pszGroup)
1790 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --group once");
1791 Opts.pszGroup = ValueUnion.psz;
1792
1793 rc = RTStrToUInt32Full(Opts.pszGroup, 0, &ValueUnion.u32);
1794 if (RT_SUCCESS(rc) && rc != VINF_SUCCESS)
1795 return RTMsgErrorExit(RTEXITCODE_SYNTAX,
1796 "Error convering --group '%s' into a number: %Rrc", Opts.pszGroup, rc);
1797 if (RT_SUCCESS(rc))
1798 {
1799 Opts.gidGroup = ValueUnion.u32;
1800 Opts.pszGroup = NULL;
1801 }
1802 break;
1803
1804 case RTZIPTARCMD_OPT_UTC:
1805 Opts.fDisplayUtc = true;
1806 break;
1807
1808 case RTZIPTARCMD_OPT_NO_RECURSION:
1809 Opts.fRecursive = false;
1810 break;
1811
1812 /* GNU */
1813 case 'S':
1814 Opts.fTarCreate |= RTZIPTAR_C_SPARSE;
1815 break;
1816
1817 /* iprt extensions */
1818 case RTZIPTARCMD_OPT_PREFIX:
1819 if (Opts.pszPrefix)
1820 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --prefix once");
1821 Opts.pszPrefix = ValueUnion.psz;
1822 break;
1823
1824 case RTZIPTARCMD_OPT_FILE_MODE_AND_MASK:
1825 Opts.fFileModeAndMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
1826 break;
1827
1828 case RTZIPTARCMD_OPT_FILE_MODE_OR_MASK:
1829 Opts.fFileModeOrMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
1830 break;
1831
1832 case RTZIPTARCMD_OPT_DIR_MODE_AND_MASK:
1833 Opts.fDirModeAndMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
1834 break;
1835
1836 case RTZIPTARCMD_OPT_DIR_MODE_OR_MASK:
1837 Opts.fDirModeOrMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
1838 break;
1839
1840 case RTZIPTARCMD_OPT_FORMAT:
1841 if (!strcmp(ValueUnion.psz, "auto") || !strcmp(ValueUnion.psz, "default"))
1842 {
1843 Opts.enmFormat = RTZIPTARCMDFORMAT_AUTO_DEFAULT;
1844 Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
1845 }
1846 else if (!strcmp(ValueUnion.psz, "tar"))
1847 {
1848 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
1849 Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
1850 }
1851 else if (!strcmp(ValueUnion.psz, "gnu"))
1852 {
1853 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
1854 Opts.enmTarFormat = RTZIPTARFORMAT_GNU;
1855 }
1856 else if (!strcmp(ValueUnion.psz, "ustar"))
1857 {
1858 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
1859 Opts.enmTarFormat = RTZIPTARFORMAT_USTAR;
1860 }
1861 else if ( !strcmp(ValueUnion.psz, "posix")
1862 || !strcmp(ValueUnion.psz, "pax"))
1863 {
1864 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
1865 Opts.enmTarFormat = RTZIPTARFORMAT_PAX;
1866 }
1867 else if (!strcmp(ValueUnion.psz, "xar"))
1868 Opts.enmFormat = RTZIPTARCMDFORMAT_XAR;
1869 else if (!strcmp(ValueUnion.psz, "cpio"))
1870 Opts.enmFormat = RTZIPTARCMDFORMAT_CPIO;
1871 else
1872 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown archive format: '%s'", ValueUnion.psz);
1873 break;
1874
1875 case RTZIPTARCMD_OPT_READ_AHEAD:
1876 Opts.fReadAhead = true;
1877 break;
1878
1879 case RTZIPTARCMD_OPT_USE_PUSH_FILE:
1880 Opts.fUsePushFile = true;
1881 break;
1882
1883 /* Standard bits. */
1884 case 'h':
1885 rtZipTarUsage(RTPathFilename(papszArgs[0]));
1886 return RTEXITCODE_SUCCESS;
1887
1888 case 'V':
1889 RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
1890 return RTEXITCODE_SUCCESS;
1891
1892 default:
1893 return RTGetOptPrintError(rc, &ValueUnion);
1894 }
1895 }
1896
1897 if (rc == VINF_GETOPT_NOT_OPTION)
1898 {
1899 /* this is kind of ugly. */
1900 Assert((unsigned)GetState.iNext - 1 <= cArgs);
1901 Opts.papszFiles = (const char * const *)&papszArgs[GetState.iNext - 1];
1902 Opts.cFiles = cArgs - GetState.iNext + 1;
1903 }
1904
1905 if (!Opts.pszFile)
1906 return RTMsgErrorExitFailure("No archive specified");
1907
1908 /*
1909 * Post proceess the options.
1910 */
1911 if (Opts.iOperation == 0)
1912 {
1913 Opts.iOperation = 't';
1914 Opts.pszOperation = "--list";
1915 }
1916
1917 if ( Opts.iOperation == 'x'
1918 && Opts.pszOwner)
1919 return RTMsgErrorExitFailure("The use of --owner with %s has not implemented yet", Opts.pszOperation);
1920
1921 if ( Opts.iOperation == 'x'
1922 && Opts.pszGroup)
1923 return RTMsgErrorExitFailure("The use of --group with %s has not implemented yet", Opts.pszOperation);
1924
1925 /*
1926 * Do the job.
1927 */
1928 switch (Opts.iOperation)
1929 {
1930 case 't':
1931 return rtZipTarDoWithMembers(&Opts, rtZipTarCmdListCallback);
1932
1933 case 'x':
1934 return rtZipTarDoWithMembers(&Opts, rtZipTarCmdExtractCallback);
1935
1936 case 'c':
1937 return rtZipTarCreate(&Opts);
1938
1939 case 'A':
1940 case 'd':
1941 case 'r':
1942 case 'u':
1943 case RTZIPTARCMD_OPT_DELETE:
1944 return RTMsgErrorExitFailure("The operation %s is not implemented yet", Opts.pszOperation);
1945
1946 default:
1947 return RTMsgErrorExitFailure("Internal error");
1948 }
1949}
1950
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