VirtualBox

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

Last change on this file was 98732, checked in by vboxsync, 15 months ago

Config.kmk,Runtime: Add support for xz archives using liblzma-5.4.1, bugref:10254

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

© 2023 Oracle
ContactPrivacy policyTerms of Use