VirtualBox

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

最後變更 在這個檔案是 107786,由 vboxsync 提交於 2 月 前

Runtime/common/zip/tarcmd.cpp: Add assertion for status code, bugref:3409

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 78.6 KB
 
1/* $Id: tarcmd.cpp 107786 2025-01-15 15:33:12Z vboxsync $ */
2/** @file
3 * IPRT - A mini TAR Command.
4 */
5
6/*
7 * Copyright (C) 2010-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.alldomusa.eu.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 if (RT_FAILURE(rc))
498 break;
499 }
500
501 RTVfsDirRelease(hVfsIoDir);
502
503 if (rc != VERR_NO_MORE_FILES)
504 return RTMsgErrorExitFailure("RTVfsDirReadEx failed unexpectedly!");
505
506 return RTEXITCODE_SUCCESS;
507}
508
509
510/**
511 * Archives a directory recursively.
512 *
513 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
514 * @param pOpts The options.
515 * @param hVfsFss The TAR filesystem stream handle.
516 * @param pszSrc The directory path or VFS spec. We append to the
517 * buffer as we decend.
518 * @param cchSrc The length of the input.
519 * @param paObjInfo[3] Array of three FS object info structures. The first
520 * one is always filled with RTFSOBJATTRADD_UNIX info.
521 * The next two may contain owner and group names if
522 * available. The three buffers can be reused.
523 * @param pszDst The name to archive it the under. We append to the
524 * buffer as we decend.
525 * @param cchDst The length of the input.
526 * @param pErrInfo Error info buffer (saves stack space).
527 */
528static RTEXITCODE rtZipTarCmdArchiveDir(PRTZIPTARCMDOPS pOpts, RTVFSFSSTREAM hVfsFss, char pszSrc[RTPATH_MAX], size_t cchSrc,
529 RTFSOBJINFO paObjInfo[3], char pszDst[RTPATH_MAX], size_t cchDst,
530 PRTERRINFOSTATIC pErrInfo)
531{
532 RT_NOREF(cchSrc);
533
534 char szSrcAbs[RTPATH_MAX];
535 int rc = RTPathAbs(pszSrc, szSrcAbs, sizeof(szSrcAbs));
536 if (RT_FAILURE(rc))
537 return RTMsgErrorExitFailure("RTPathAbs failed on '%s': %Rrc\n", pszSrc, rc);
538
539 union
540 {
541 uint8_t abPadding[RTZIPTARCMD_DIRENTRY_BUF_SIZE];
542 RTDIRENTRYEX DirEntry;
543 } uBuf;
544
545 return rtZipTarCmdArchiveDirSub(pOpts, hVfsFss, szSrcAbs, strlen(szSrcAbs), paObjInfo, pszDst, cchDst, &uBuf.DirEntry, pErrInfo);
546}
547
548
549/**
550 * Opens the output archive specified by the options.
551 *
552 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
553 * @param pOpts The options.
554 * @param phVfsFss Where to return the TAR filesystem stream handle.
555 */
556static RTEXITCODE rtZipTarCmdOpenOutputArchive(PRTZIPTARCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss)
557{
558 int rc;
559 *phVfsFss = NIL_RTVFSFSSTREAM;
560
561 /*
562 * Open the output file.
563 */
564 RTVFSIOSTREAM hVfsIos;
565 if ( pOpts->pszFile
566 && strcmp(pOpts->pszFile, "-") != 0)
567 {
568 uint32_t offError = 0;
569 RTERRINFOSTATIC ErrInfo;
570 rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE,
571 &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
572 if (RT_FAILURE(rc))
573 return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core);
574 }
575 else
576 {
577 rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT,
578 RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
579 true /*fLeaveOpen*/,
580 &hVfsIos);
581 if (RT_FAILURE(rc))
582 return RTMsgErrorExitFailure("Failed to prepare standard output for writing: %Rrc", rc);
583 }
584
585 /*
586 * Pass it thru a compressor?
587 */
588 RTVFSIOSTREAM hVfsIosComp = NIL_RTVFSIOSTREAM;
589 switch (pOpts->chZipper)
590 {
591 /* no */
592 case '\0':
593 rc = VINF_SUCCESS;
594 break;
595
596 /* gunzip */
597 case 'z':
598 rc = RTZipGzipCompressIoStream(hVfsIos, 0 /*fFlags*/, 6, &hVfsIosComp);
599 if (RT_FAILURE(rc))
600 RTMsgError("Failed to open gzip decompressor: %Rrc", rc);
601 break;
602
603#ifdef IPRT_WITH_LZMA
604 /* xz/lzma */
605 case 'J':
606 rc = RTZipXzCompressIoStream(hVfsIos, 0 /*fFlags*/, 6, &hVfsIosComp);
607 if (RT_FAILURE(rc))
608 RTMsgError("Failed to open xz compressor: %Rrc", rc);
609 break;
610#endif
611
612 /* bunzip2 */
613 case 'j':
614 rc = VERR_NOT_SUPPORTED;
615 RTMsgError("bzip2 is not supported by this build");
616 break;
617
618 /* bug */
619 default:
620 rc = VERR_INTERNAL_ERROR_2;
621 RTMsgError("unknown decompression method '%c'", pOpts->chZipper);
622 break;
623 }
624 if (RT_FAILURE(rc))
625 {
626 RTVfsIoStrmRelease(hVfsIos);
627 return RTEXITCODE_FAILURE;
628 }
629
630 if (hVfsIosComp != NIL_RTVFSIOSTREAM)
631 {
632 RTVfsIoStrmRelease(hVfsIos);
633 hVfsIos = hVfsIosComp;
634 hVfsIosComp = NIL_RTVFSIOSTREAM;
635 }
636
637 /*
638 * Open the filesystem stream creator.
639 */
640 if ( pOpts->enmFormat == RTZIPTARCMDFORMAT_TAR
641 || pOpts->enmFormat == RTZIPTARCMDFORMAT_CPIO
642 || pOpts->enmFormat == RTZIPTARCMDFORMAT_AUTO_DEFAULT)
643 {
644 RTVFSFSSTREAM hVfsFss;
645 rc = RTZipTarFsStreamToIoStream(hVfsIos, pOpts->enmTarFormat, pOpts->fTarCreate, &hVfsFss);
646 if (RT_SUCCESS(rc))
647 {
648 /*
649 * Set transformation options.
650 */
651 rc = RTZipTarFsStreamSetFileMode(hVfsFss, pOpts->fFileModeAndMask, pOpts->fFileModeOrMask);
652 if (RT_SUCCESS(rc))
653 {
654 rc = RTZipTarFsStreamSetDirMode(hVfsFss, pOpts->fDirModeAndMask, pOpts->fDirModeOrMask);
655 if (RT_FAILURE(rc))
656 RTMsgError("RTZipTarFsStreamSetDirMode(%o,%o) failed: %Rrc", pOpts->fDirModeAndMask, pOpts->fDirModeOrMask, rc);
657 }
658 else
659 RTMsgError("RTZipTarFsStreamSetFileMode(%o,%o) failed: %Rrc", pOpts->fFileModeAndMask, pOpts->fFileModeOrMask, rc);
660 if ((pOpts->pszOwner || pOpts->uidOwner != NIL_RTUID) && RT_SUCCESS(rc))
661 {
662 rc = RTZipTarFsStreamSetOwner(hVfsFss, pOpts->uidOwner, pOpts->pszOwner);
663 if (RT_FAILURE(rc))
664 RTMsgError("RTZipTarFsStreamSetOwner(%d,%s) failed: %Rrc", pOpts->uidOwner, pOpts->pszOwner, rc);
665 }
666 if ((pOpts->pszGroup || pOpts->gidGroup != NIL_RTGID) && RT_SUCCESS(rc))
667 {
668 rc = RTZipTarFsStreamSetGroup(hVfsFss, pOpts->gidGroup, pOpts->pszGroup);
669 if (RT_FAILURE(rc))
670 RTMsgError("RTZipTarFsStreamSetGroup(%d,%s) failed: %Rrc", pOpts->gidGroup, pOpts->pszGroup, rc);
671 }
672 if (RT_SUCCESS(rc))
673 *phVfsFss = hVfsFss;
674 else
675 {
676 RTVfsFsStrmRelease(hVfsFss);
677 *phVfsFss = NIL_RTVFSFSSTREAM;
678 }
679 }
680 else
681 rc = RTMsgErrorExitFailure("Failed to open tar filesystem stream: %Rrc", rc);
682 }
683 else
684 rc = VERR_NOT_SUPPORTED;
685 RTVfsIoStrmRelease(hVfsIos);
686 if (RT_FAILURE(rc))
687 return RTMsgErrorExitFailure("Failed to open tar filesystem stream: %Rrc", rc);
688
689 return RTEXITCODE_SUCCESS;
690}
691
692
693/**
694 * Implements archive creation.
695 *
696 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
697 * @param pOpts The options.
698 */
699static RTEXITCODE rtZipTarCreate(PRTZIPTARCMDOPS pOpts)
700{
701 /*
702 * Refuse to create empty archive.
703 */
704 if (pOpts->cFiles == 0)
705 return RTMsgErrorExitFailure("Nothing to archive - refusing to create empty archive!");
706
707 /*
708 * First open the output file.
709 */
710 RTVFSFSSTREAM hVfsFss;
711 RTEXITCODE rcExit = rtZipTarCmdOpenOutputArchive(pOpts, &hVfsFss);
712 if (rcExit != RTEXITCODE_SUCCESS)
713 return rcExit;
714
715 /*
716 * Process the input files.
717 */
718 for (uint32_t iFile = 0; iFile < pOpts->cFiles; iFile++)
719 {
720 const char *pszFile = pOpts->papszFiles[iFile];
721
722 /*
723 * Construct/copy the source name.
724 */
725 int rc = VINF_SUCCESS;
726 char szSrc[RTPATH_MAX];
727 if ( RTPathStartsWithRoot(pszFile)
728 || RTVfsChainIsSpec(pszFile))
729 rc = RTStrCopy(szSrc, sizeof(szSrc), pszFile);
730 else
731 rc = RTPathJoin(szSrc, sizeof(szSrc), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pOpts->papszFiles[iFile]);
732 if (RT_SUCCESS(rc))
733 {
734 /*
735 * Construct the archived name. We must strip leading root specifier.
736 */
737 char *pszFinalPath = NULL;
738 char szDst[RTPATH_MAX];
739 const char *pszDst = pszFile;
740 if (RTVfsChainIsSpec(pszFile))
741 {
742 uint32_t offError;
743 rc = RTVfsChainQueryFinalPath(pszFile, &pszFinalPath, &offError);
744 if (RT_SUCCESS(rc))
745 pszDst = pszFinalPath;
746 else
747 rcExit = RTVfsChainMsgErrorExitFailure("RTVfsChainQueryFinalPath", pszFile, rc, offError, NULL);
748 }
749 if (RT_SUCCESS(rc))
750 {
751 pszDst = RTPathSkipRootSpec(pszDst);
752 if (*pszDst == '\0')
753 {
754 pszDst = pOpts->pszPrefix ? pOpts->pszPrefix : ".";
755 rc = RTStrCopy(szDst, sizeof(szDst), pszDst);
756 }
757 else
758 {
759 if (pOpts->pszPrefix)
760 rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszPrefix, pszDst);
761 else
762 rc = RTStrCopy(szDst, sizeof(szDst), pszDst);
763 }
764 if (RT_SUCCESS(rc))
765 {
766 /*
767 * What kind of object is this and what affiliations does it have?
768 */
769 RTFSOBJINFO aObjInfo[3];
770 rc = rtZipTarCmdQueryObjInfo(szSrc, aObjInfo, RT_ELEMENTS(aObjInfo));
771 if (RT_SUCCESS(rc))
772 {
773 RTERRINFOSTATIC ErrInfo;
774
775 /*
776 * Process on an object type basis.
777 */
778 RTEXITCODE rcExit2;
779 if (RTFS_IS_DIRECTORY(aObjInfo[0].Attr.fMode))
780 rcExit2 = rtZipTarCmdArchiveDir(pOpts, hVfsFss, szSrc, strlen(szSrc), aObjInfo,
781 szDst, strlen(szDst), &ErrInfo);
782 else if (RTFS_IS_FILE(aObjInfo[0].Attr.fMode))
783 rcExit2 = rtZipTarCmdArchiveFile(pOpts, hVfsFss, szSrc, aObjInfo, szDst, &ErrInfo);
784 else if (RTFS_IS_SYMLINK(aObjInfo[0].Attr.fMode))
785 rcExit2 = rtZipTarCmdArchiveSymlink(pOpts, hVfsFss, szSrc, aObjInfo, szDst, &ErrInfo);
786 else if (RTFS_IS_FIFO(aObjInfo[0].Attr.fMode))
787 rcExit2 = RTMsgErrorExitFailure("FIFO archiving is not implemented");
788 else if (RTFS_IS_SOCKET(aObjInfo[0].Attr.fMode))
789 rcExit2 = RTMsgErrorExitFailure("Socket archiving is not implemented");
790 else if (RTFS_IS_DEV_CHAR(aObjInfo[0].Attr.fMode) || RTFS_IS_DEV_BLOCK(aObjInfo[0].Attr.fMode))
791 rcExit2 = RTMsgErrorExitFailure("Device archiving is not implemented");
792 else if (RTFS_IS_WHITEOUT(aObjInfo[0].Attr.fMode))
793 rcExit2 = RTEXITCODE_SUCCESS;
794 else
795 rcExit2 = RTMsgErrorExitFailure("Unknown file type: %#x\n", aObjInfo[0].Attr.fMode);
796 if (rcExit2 != RTEXITCODE_SUCCESS)
797 rcExit = rcExit2;
798 }
799 else
800 rcExit = RTMsgErrorExitFailure("querying object information for '%s' failed (%s)", szSrc, pszFile);
801 }
802 else
803 rcExit = RTMsgErrorExitFailure("archived file name is too long, skipping '%s' (%s)", pszDst, pszFile);
804 }
805 }
806 else
807 rcExit = RTMsgErrorExitFailure("input file name is too long, skipping '%s'", pszFile);
808 }
809
810 /*
811 * Finalize the archive.
812 */
813 int rc = RTVfsFsStrmEnd(hVfsFss);
814 if (RT_FAILURE(rc))
815 rcExit = RTMsgErrorExitFailure("RTVfsFsStrmEnd failed: %Rrc", rc);
816
817 RTVfsFsStrmRelease(hVfsFss);
818 return rcExit;
819}
820
821
822/**
823 * Opens the input archive specified by the options.
824 *
825 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
826 * @param pOpts The options.
827 * @param phVfsFss Where to return the TAR filesystem stream handle.
828 */
829static RTEXITCODE rtZipTarCmdOpenInputArchive(PRTZIPTARCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss)
830{
831 int rc;
832
833 /*
834 * Open the input file.
835 */
836 RTVFSIOSTREAM hVfsIos;
837 if ( pOpts->pszFile
838 && strcmp(pOpts->pszFile, "-") != 0)
839 {
840 uint32_t offError = 0;
841 RTERRINFOSTATIC ErrInfo;
842 rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
843 &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
844 if (RT_FAILURE(rc))
845 return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core);
846 }
847 else
848 {
849 rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT,
850 RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
851 true /*fLeaveOpen*/,
852 &hVfsIos);
853 if (RT_FAILURE(rc))
854 return RTMsgErrorExitFailure("Failed to prepare standard in for reading: %Rrc", rc);
855 }
856
857 /*
858 * Pass it thru a decompressor?
859 */
860 RTVFSIOSTREAM hVfsIosDecomp = NIL_RTVFSIOSTREAM;
861 switch (pOpts->chZipper)
862 {
863 /* no */
864 case '\0':
865 rc = VINF_SUCCESS;
866 break;
867
868 /* gunzip */
869 case 'z':
870 rc = RTZipGzipDecompressIoStream(hVfsIos, 0 /*fFlags*/, &hVfsIosDecomp);
871 if (RT_FAILURE(rc))
872 RTMsgError("Failed to open gzip decompressor: %Rrc", rc);
873 break;
874
875#ifdef IPRT_WITH_LZMA
876 /* xz/lzma */
877 case 'J':
878 rc = RTZipXzDecompressIoStream(hVfsIos, 0 /*fFlags*/, &hVfsIosDecomp);
879 if (RT_FAILURE(rc))
880 RTMsgError("Failed to open gzip decompressor: %Rrc", rc);
881 break;
882#endif
883
884 /* bunzip2 */
885 case 'j':
886 rc = VERR_NOT_SUPPORTED;
887 RTMsgError("bzip2 is not supported by this build");
888 break;
889
890 /* bug */
891 default:
892 rc = VERR_INTERNAL_ERROR_2;
893 RTMsgError("unknown decompression method '%c'", pOpts->chZipper);
894 break;
895 }
896 if (RT_FAILURE(rc))
897 {
898 RTVfsIoStrmRelease(hVfsIos);
899 return RTEXITCODE_FAILURE;
900 }
901
902 if (hVfsIosDecomp != NIL_RTVFSIOSTREAM)
903 {
904 RTVfsIoStrmRelease(hVfsIos);
905 hVfsIos = hVfsIosDecomp;
906 hVfsIosDecomp = NIL_RTVFSIOSTREAM;
907 }
908
909 /*
910 * Open the filesystem stream.
911 */
912 if (pOpts->enmFormat == RTZIPTARCMDFORMAT_TAR)
913 rc = RTZipTarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
914 else if (pOpts->enmFormat == RTZIPTARCMDFORMAT_XAR)
915#ifdef IPRT_WITH_XAR /* Requires C++ and XML, so only in some configruation of IPRT. */
916 rc = RTZipXarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
917#else
918 rc = VERR_NOT_SUPPORTED;
919#endif
920 else if (pOpts->enmFormat == RTZIPTARCMDFORMAT_CPIO)
921 rc = RTZipCpioFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
922 else /** @todo make RTZipTarFsStreamFromIoStream fail if not tar file! */
923 rc = RTZipTarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
924 RTVfsIoStrmRelease(hVfsIos);
925 if (RT_FAILURE(rc))
926 return RTMsgErrorExitFailure("Failed to open tar filesystem stream: %Rrc", rc);
927
928 return RTEXITCODE_SUCCESS;
929}
930
931
932/**
933 * Worker for the --list and --extract commands.
934 *
935 * @returns The appropriate exit code.
936 * @param pOpts The tar options.
937 * @param pfnCallback The command specific callback.
938 */
939static RTEXITCODE rtZipTarDoWithMembers(PRTZIPTARCMDOPS pOpts, PFNDOWITHMEMBER pfnCallback)
940{
941 /*
942 * Allocate a bitmap to go with the file list. This will be used to
943 * indicate which files we've processed and which not.
944 */
945 uint32_t *pbmFound = NULL;
946 if (pOpts->cFiles)
947 {
948 pbmFound = (uint32_t *)RTMemAllocZ(((pOpts->cFiles + 31) / 32) * sizeof(uint32_t));
949 if (!pbmFound)
950 return RTMsgErrorExitFailure("Failed to allocate the found-file-bitmap");
951 }
952
953
954 /*
955 * Open the input archive.
956 */
957 RTVFSFSSTREAM hVfsFssIn;
958 RTEXITCODE rcExit = rtZipTarCmdOpenInputArchive(pOpts, &hVfsFssIn);
959 if (rcExit == RTEXITCODE_SUCCESS)
960 {
961 /*
962 * Process the stream.
963 */
964 for (;;)
965 {
966 /*
967 * Retrive the next object.
968 */
969 char *pszName;
970 RTVFSOBJ hVfsObj;
971 int rc = RTVfsFsStrmNext(hVfsFssIn, &pszName, NULL, &hVfsObj);
972 if (RT_FAILURE(rc))
973 {
974 if (rc != VERR_EOF)
975 rcExit = RTMsgErrorExitFailure("RTVfsFsStrmNext returned %Rrc", rc);
976 break;
977 }
978
979 /*
980 * Should we process this entry?
981 */
982 uint32_t iFile = UINT32_MAX;
983 if ( !pOpts->cFiles
984 || rtZipTarCmdIsNameInArray(pszName, pOpts->papszFiles, &iFile) )
985 {
986 if (pbmFound)
987 ASMBitSet(pbmFound, iFile);
988
989 rcExit = pfnCallback(pOpts, hVfsObj, pszName, rcExit);
990 }
991
992 /*
993 * Release the current object and string.
994 */
995 RTVfsObjRelease(hVfsObj);
996 RTStrFree(pszName);
997 }
998
999 /*
1000 * Complain about any files we didn't find.
1001 */
1002 for (uint32_t iFile = 0; iFile < pOpts->cFiles; iFile++)
1003 if (!ASMBitTest(pbmFound, iFile))
1004 {
1005 RTMsgError("%s: Was not found in the archive", pOpts->papszFiles[iFile]);
1006 rcExit = RTEXITCODE_FAILURE;
1007 }
1008
1009 RTVfsFsStrmRelease(hVfsFssIn);
1010 }
1011 RTMemFree(pbmFound);
1012 return rcExit;
1013}
1014
1015
1016/**
1017 * Checks if the name contains any escape sequences.
1018 *
1019 * An escape sequence would generally be one or more '..' references. On DOS
1020 * like system, something that would make up a drive letter reference is also
1021 * considered an escape sequence.
1022 *
1023 * @returns true / false.
1024 * @param pszName The name to consider.
1025 */
1026static bool rtZipTarHasEscapeSequence(const char *pszName)
1027{
1028#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
1029 if (pszName[0] == ':')
1030 return true;
1031#endif
1032 while (*pszName)
1033 {
1034 while (RTPATH_IS_SEP(*pszName))
1035 pszName++;
1036 if ( pszName[0] == '.'
1037 && pszName[1] == '.'
1038 && (pszName[2] == '\0' || RTPATH_IS_SLASH(pszName[2])) )
1039 return true;
1040 while (*pszName && !RTPATH_IS_SEP(*pszName))
1041 pszName++;
1042 }
1043
1044 return false;
1045}
1046
1047
1048#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
1049
1050/**
1051 * Queries the user ID to use when extracting a member.
1052 *
1053 * @returns rcExit or RTEXITCODE_FAILURE.
1054 * @param pOpts The tar options.
1055 * @param pUser The user info.
1056 * @param pszName The file name to use when complaining.
1057 * @param rcExit The current exit code.
1058 * @param pUid Where to return the user ID.
1059 */
1060static RTEXITCODE rtZipTarQueryExtractOwner(PRTZIPTARCMDOPS pOpts, PCRTFSOBJINFO pOwner, const char *pszName, RTEXITCODE rcExit,
1061 PRTUID pUid)
1062{
1063 if (pOpts->uidOwner != NIL_RTUID)
1064 *pUid = pOpts->uidOwner;
1065 else if (pOpts->fPreserveGroup)
1066 {
1067 if (!pOwner->Attr.u.UnixGroup.szName[0])
1068 *pUid = pOwner->Attr.u.UnixOwner.uid;
1069 else
1070 {
1071 *pUid = NIL_RTUID;
1072 return RTMsgErrorExitFailure("%s: User resolving is not implemented.", pszName);
1073 }
1074 }
1075 else
1076 *pUid = NIL_RTUID;
1077 return rcExit;
1078}
1079
1080
1081/**
1082 * Queries the group ID to use when extracting a member.
1083 *
1084 * @returns rcExit or RTEXITCODE_FAILURE.
1085 * @param pOpts The tar options.
1086 * @param pGroup The group info.
1087 * @param pszName The file name to use when complaining.
1088 * @param rcExit The current exit code.
1089 * @param pGid Where to return the group ID.
1090 */
1091static RTEXITCODE rtZipTarQueryExtractGroup(PRTZIPTARCMDOPS pOpts, PCRTFSOBJINFO pGroup, const char *pszName, RTEXITCODE rcExit,
1092 PRTGID pGid)
1093{
1094 if (pOpts->gidGroup != NIL_RTGID)
1095 *pGid = pOpts->gidGroup;
1096 else if (pOpts->fPreserveGroup)
1097 {
1098 if (!pGroup->Attr.u.UnixGroup.szName[0])
1099 *pGid = pGroup->Attr.u.UnixGroup.gid;
1100 else
1101 {
1102 *pGid = NIL_RTGID;
1103 return RTMsgErrorExitFailure("%s: Group resolving is not implemented.", pszName);
1104 }
1105 }
1106 else
1107 *pGid = NIL_RTGID;
1108 return rcExit;
1109}
1110
1111#endif /* !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) */
1112
1113
1114/**
1115 * Corrects the file mode and other attributes.
1116 *
1117 * Common worker for rtZipTarCmdExtractFile and rtZipTarCmdExtractHardlink.
1118 *
1119 * @returns rcExit or RTEXITCODE_FAILURE.
1120 * @param pOpts The tar options.
1121 * @param rcExit The current exit code.
1122 * @param hFile The handle to the destination file.
1123 * @param pszDst The destination path (for error reporting).
1124 * @param pUnixInfo The unix fs object info.
1125 * @param pOwner The owner info.
1126 * @param pGroup The group info.
1127 */
1128static RTEXITCODE rtZipTarCmdExtractSetAttribs(PRTZIPTARCMDOPS pOpts, RTEXITCODE rcExit, RTFILE hFile, const char *pszDst,
1129 PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner, PCRTFSOBJINFO pGroup)
1130{
1131 int rc;
1132
1133 if (!pOpts->fNoModTime)
1134 {
1135 rc = RTFileSetTimes(hFile, NULL, &pUnixInfo->ModificationTime, NULL, NULL);
1136 if (RT_FAILURE(rc))
1137 rcExit = RTMsgErrorExitFailure("%s: Error setting times: %Rrc", pszDst, rc);
1138 }
1139
1140#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
1141 if ( pOpts->uidOwner != NIL_RTUID
1142 || pOpts->gidGroup != NIL_RTGID
1143 || pOpts->fPreserveOwner
1144 || pOpts->fPreserveGroup)
1145 {
1146 RTUID uidFile;
1147 rcExit = rtZipTarQueryExtractOwner(pOpts, pOwner, pszDst, rcExit, &uidFile);
1148
1149 RTGID gidFile;
1150 rcExit = rtZipTarQueryExtractGroup(pOpts, pGroup, pszDst, rcExit, &gidFile);
1151 if (uidFile != NIL_RTUID || gidFile != NIL_RTGID)
1152 {
1153 rc = RTFileSetOwner(hFile, uidFile, gidFile);
1154 if (RT_FAILURE(rc))
1155 rcExit = RTMsgErrorExitFailure("%s: Error owner/group: %Rrc", pszDst, rc);
1156 }
1157 }
1158#else
1159 RT_NOREF_PV(pOwner); RT_NOREF_PV(pGroup);
1160#endif
1161
1162 RTFMODE fMode = (pUnixInfo->Attr.fMode & pOpts->fFileModeAndMask) | pOpts->fFileModeOrMask;
1163 rc = RTFileSetMode(hFile, fMode | RTFS_TYPE_FILE);
1164 if (RT_FAILURE(rc))
1165 rcExit = RTMsgErrorExitFailure("%s: Error changing mode: %Rrc", pszDst, rc);
1166
1167 return rcExit;
1168}
1169
1170
1171/**
1172 * Extracts a hard linked file.
1173 *
1174 * @returns rcExit or RTEXITCODE_FAILURE.
1175 * @param pOpts The tar options.
1176 * @param rcExit The current exit code.
1177 * @param pszDst The destination path.
1178 * @param pszTarget The target relative path.
1179 * @param pUnixInfo The unix fs object info.
1180 * @param pOwner The owner info.
1181 * @param pGroup The group info.
1182 */
1183static RTEXITCODE rtZipTarCmdExtractHardlink(PRTZIPTARCMDOPS pOpts, RTEXITCODE rcExit, const char *pszDst,
1184 const char *pszTarget, PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner,
1185 PCRTFSOBJINFO pGroup)
1186{
1187 /*
1188 * Construct full target path and check that it exists.
1189 */
1190 char szFullTarget[RTPATH_MAX];
1191 int rc = RTPathJoin(szFullTarget, sizeof(szFullTarget), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszTarget);
1192 if (RT_FAILURE(rc))
1193 return RTMsgErrorExitFailure("%s: Failed to construct full hardlink target path for %s: %Rrc",
1194 pszDst, pszTarget, rc);
1195
1196 if (!RTFileExists(szFullTarget))
1197 return RTMsgErrorExitFailure("%s: Hardlink target not found (or not a file): %s", pszDst, szFullTarget);
1198
1199 /*
1200 * Try hardlink the file, falling back on copying.
1201 */
1202 /** @todo actual hardlinking */
1203 if (true)
1204 {
1205 RTMsgWarning("%s: Hardlinking not available, copying '%s' instead.", pszDst, szFullTarget);
1206
1207 RTFILE hSrcFile;
1208 rc = RTFileOpen(&hSrcFile, szFullTarget, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN);
1209 if (RT_SUCCESS(rc))
1210 {
1211 uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT
1212 | ((RTFS_UNIX_IWUSR | RTFS_UNIX_IRUSR) << RTFILE_O_CREATE_MODE_SHIFT);
1213 RTFILE hDstFile;
1214 rc = RTFileOpen(&hDstFile, pszDst, fOpen);
1215 if (RT_SUCCESS(rc))
1216 {
1217 rc = RTFileCopyByHandles(hSrcFile, hDstFile);
1218 if (RT_SUCCESS(rc))
1219 {
1220 rcExit = rtZipTarCmdExtractSetAttribs(pOpts, rcExit, hDstFile, pszDst, pUnixInfo, pOwner, pGroup);
1221 rc = RTFileClose(hDstFile);
1222 if (RT_FAILURE(rc))
1223 {
1224 rcExit = RTMsgErrorExitFailure("%s: Error closing hardlinked file copy: %Rrc", pszDst, rc);
1225 RTFileDelete(pszDst);
1226 }
1227 }
1228 else
1229 {
1230 rcExit = RTMsgErrorExitFailure("%s: Failed copying hardlinked file '%s': %Rrc", pszDst, szFullTarget, rc);
1231 rc = RTFileClose(hDstFile);
1232 AssertRC(rc);
1233 RTFileDelete(pszDst);
1234 }
1235 }
1236 else
1237 rcExit = RTMsgErrorExitFailure("%s: Error creating file: %Rrc", pszDst, rc);
1238 RTFileClose(hSrcFile);
1239 }
1240 else
1241 rcExit = RTMsgErrorExitFailure("%s: Error opening file '%s' for reading (hardlink target): %Rrc",
1242 pszDst, szFullTarget, rc);
1243 }
1244
1245 return rcExit;
1246}
1247
1248
1249
1250/**
1251 * Extracts a file.
1252 *
1253 * Since we can restore permissions and attributes more efficiently by working
1254 * directly on the file handle, we have special code path for files.
1255 *
1256 * @returns rcExit or RTEXITCODE_FAILURE.
1257 * @param pOpts The tar options.
1258 * @param hVfsObj The tar object to display
1259 * @param rcExit The current exit code.
1260 * @param pszDst The destination path.
1261 * @param pUnixInfo The unix fs object info.
1262 * @param pOwner The owner info.
1263 * @param pGroup The group info.
1264 */
1265static RTEXITCODE rtZipTarCmdExtractFile(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, RTEXITCODE rcExit,
1266 const char *pszDst, PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner, PCRTFSOBJINFO pGroup)
1267{
1268 /*
1269 * Open the destination file and create a stream object for it.
1270 */
1271 uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT
1272 | ((RTFS_UNIX_IWUSR | RTFS_UNIX_IRUSR) << RTFILE_O_CREATE_MODE_SHIFT);
1273 RTFILE hFile;
1274 int rc = RTFileOpen(&hFile, pszDst, fOpen);
1275 if (RT_FAILURE(rc))
1276 return RTMsgErrorExitFailure("%s: Error creating file: %Rrc", pszDst, rc);
1277
1278 RTVFSIOSTREAM hVfsIosDst;
1279 rc = RTVfsIoStrmFromRTFile(hFile, fOpen, true /*fLeaveOpen*/, &hVfsIosDst);
1280 if (RT_SUCCESS(rc))
1281 {
1282 /*
1283 * Convert source to a stream and optionally add a read ahead stage.
1284 */
1285 RTVFSIOSTREAM hVfsIosSrc = RTVfsObjToIoStream(hVfsObj);
1286 if (pOpts->fReadAhead)
1287 {
1288 RTVFSIOSTREAM hVfsReadAhead;
1289 rc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlag*/, 16 /*cBuffers*/, _256K /*cbBuffer*/, &hVfsReadAhead);
1290 if (RT_SUCCESS(rc))
1291 {
1292 RTVfsIoStrmRelease(hVfsIosSrc);
1293 hVfsIosSrc = hVfsReadAhead;
1294 }
1295 else
1296 AssertRC(rc); /* can be ignored in release builds. */
1297 }
1298
1299 /*
1300 * Pump the data thru and correct the file attributes.
1301 */
1302 rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(pUnixInfo->cbObject, _1M));
1303 if (RT_SUCCESS(rc))
1304 rcExit = rtZipTarCmdExtractSetAttribs(pOpts, rcExit, hFile, pszDst, pUnixInfo, pOwner, pGroup);
1305 else
1306 rcExit = RTMsgErrorExitFailure("%s: Error writing out file: %Rrc", pszDst, rc);
1307 RTVfsIoStrmRelease(hVfsIosSrc);
1308 RTVfsIoStrmRelease(hVfsIosDst);
1309 }
1310 else
1311 rcExit = RTMsgErrorExitFailure("%s: Error creating I/O stream for file: %Rrc", pszDst, rc);
1312 RTFileClose(hFile);
1313 return rcExit;
1314}
1315
1316
1317/**
1318 * @callback_method_impl{PFNDOWITHMEMBER, Implements --extract.}
1319 */
1320static RTEXITCODE rtZipTarCmdExtractCallback(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit)
1321{
1322 if (pOpts->fVerbose)
1323 RTPrintf("%s\n", pszName);
1324
1325 /*
1326 * Query all the information.
1327 */
1328 RTFSOBJINFO UnixInfo;
1329 int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
1330 if (RT_FAILURE(rc))
1331 return RTMsgErrorExitFailure("RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
1332
1333 RTFSOBJINFO Owner;
1334 rc = RTVfsObjQueryInfo(hVfsObj, &Owner, RTFSOBJATTRADD_UNIX_OWNER);
1335 if (RT_FAILURE(rc))
1336 return RTMsgErrorExit(RTEXITCODE_FAILURE,
1337 "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
1338 rc, pszName);
1339
1340 RTFSOBJINFO Group;
1341 rc = RTVfsObjQueryInfo(hVfsObj, &Group, RTFSOBJATTRADD_UNIX_GROUP);
1342 if (RT_FAILURE(rc))
1343 return RTMsgErrorExit(RTEXITCODE_FAILURE,
1344 "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
1345 rc, pszName);
1346
1347 bool fIsHardLink = false;
1348 char szTarget[RTPATH_MAX];
1349 szTarget[0] = '\0';
1350 RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
1351 if (hVfsSymlink != NIL_RTVFSSYMLINK)
1352 {
1353 rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
1354 RTVfsSymlinkRelease(hVfsSymlink);
1355 if (RT_FAILURE(rc))
1356 return RTMsgErrorExitFailure("%s: RTVfsSymlinkRead failed: %Rrc", pszName, rc);
1357 if (!szTarget[0])
1358 return RTMsgErrorExitFailure("%s: Link target is empty.", pszName);
1359 if (!RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
1360 {
1361 fIsHardLink = true;
1362 if (!RTFS_IS_FILE(UnixInfo.Attr.fMode))
1363 return RTMsgErrorExitFailure("%s: Hardlinks are only supported for regular files (target=%s).",
1364 pszName, szTarget);
1365 if (rtZipTarHasEscapeSequence(pszName))
1366 return RTMsgErrorExitFailure("%s: Hardlink target '%s' contains an escape sequence.",
1367 pszName, szTarget);
1368 }
1369 }
1370 else if (RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
1371 return RTMsgErrorExitFailure("Failed to get symlink object for '%s'", pszName);
1372
1373 if (rtZipTarHasEscapeSequence(pszName))
1374 return RTMsgErrorExitFailure("Name '%s' contains an escape sequence.", pszName);
1375
1376 /*
1377 * Construct the path to the extracted member.
1378 */
1379 char szDst[RTPATH_MAX];
1380 rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszName);
1381 if (RT_FAILURE(rc))
1382 return RTMsgErrorExitFailure("%s: Failed to construct destination path for: %Rrc", pszName, rc);
1383
1384 /*
1385 * Extract according to the type.
1386 */
1387 if (!fIsHardLink)
1388 switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
1389 {
1390 case RTFS_TYPE_FILE:
1391 return rtZipTarCmdExtractFile(pOpts, hVfsObj, rcExit, szDst, &UnixInfo, &Owner, &Group);
1392
1393 case RTFS_TYPE_DIRECTORY:
1394 rc = RTDirCreateFullPath(szDst, UnixInfo.Attr.fMode & RTFS_UNIX_ALL_ACCESS_PERMS);
1395 if (RT_FAILURE(rc))
1396 return RTMsgErrorExitFailure("%s: Error creating directory: %Rrc", szDst, rc);
1397 break;
1398
1399 case RTFS_TYPE_SYMLINK:
1400 rc = RTSymlinkCreate(szDst, szTarget, RTSYMLINKTYPE_UNKNOWN, 0);
1401 if (RT_FAILURE(rc))
1402 return RTMsgErrorExitFailure("%s: Error creating symbolic link: %Rrc", szDst, rc);
1403 break;
1404
1405 case RTFS_TYPE_FIFO:
1406 return RTMsgErrorExitFailure("%s: FIFOs are not supported.", pszName);
1407 case RTFS_TYPE_DEV_CHAR:
1408 return RTMsgErrorExitFailure("%s: FIFOs are not supported.", pszName);
1409 case RTFS_TYPE_DEV_BLOCK:
1410 return RTMsgErrorExitFailure("%s: Block devices are not supported.", pszName);
1411 case RTFS_TYPE_SOCKET:
1412 return RTMsgErrorExitFailure("%s: Sockets are not supported.", pszName);
1413 case RTFS_TYPE_WHITEOUT:
1414 return RTMsgErrorExitFailure("%s: Whiteouts are not support.", pszName);
1415 default:
1416 return RTMsgErrorExitFailure("%s: Unknown file type.", pszName);
1417 }
1418 else
1419 return rtZipTarCmdExtractHardlink(pOpts, rcExit, szDst, szTarget, &UnixInfo, &Owner, &Group);
1420
1421 /*
1422 * Set other attributes as requested.
1423 *
1424 * Note! File extraction does get here.
1425 */
1426 if (!pOpts->fNoModTime)
1427 {
1428 rc = RTPathSetTimesEx(szDst, NULL, &UnixInfo.ModificationTime, NULL, NULL, RTPATH_F_ON_LINK);
1429 if (RT_FAILURE(rc) && rc != VERR_NOT_SUPPORTED && rc != VERR_NS_SYMLINK_SET_TIME)
1430 rcExit = RTMsgErrorExitFailure("%s: Error changing modification time: %Rrc.", pszName, rc);
1431 }
1432
1433#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
1434 if ( pOpts->uidOwner != NIL_RTUID
1435 || pOpts->gidGroup != NIL_RTGID
1436 || pOpts->fPreserveOwner
1437 || pOpts->fPreserveGroup)
1438 {
1439 RTUID uidFile;
1440 rcExit = rtZipTarQueryExtractOwner(pOpts, &Owner, szDst, rcExit, &uidFile);
1441
1442 RTGID gidFile;
1443 rcExit = rtZipTarQueryExtractGroup(pOpts, &Group, szDst, rcExit, &gidFile);
1444 if (uidFile != NIL_RTUID || gidFile != NIL_RTGID)
1445 {
1446 rc = RTPathSetOwnerEx(szDst, uidFile, gidFile, RTPATH_F_ON_LINK);
1447 if (RT_FAILURE(rc))
1448 rcExit = RTMsgErrorExitFailure("%s: Error owner/group: %Rrc", szDst, rc);
1449 }
1450 }
1451#endif
1452
1453#if !defined(RT_OS_WINDOWS) /** @todo implement RTPathSetMode on windows... */
1454 if (!RTFS_IS_SYMLINK(UnixInfo.Attr.fMode)) /* RTPathSetMode follows symbolic links atm. */
1455 {
1456 RTFMODE fMode;
1457 if (RTFS_IS_DIRECTORY(UnixInfo.Attr.fMode))
1458 fMode = (UnixInfo.Attr.fMode & (pOpts->fDirModeAndMask | RTFS_TYPE_MASK)) | pOpts->fDirModeOrMask;
1459 else
1460 fMode = (UnixInfo.Attr.fMode & (pOpts->fFileModeAndMask | RTFS_TYPE_MASK)) | pOpts->fFileModeOrMask;
1461 rc = RTPathSetMode(szDst, fMode);
1462 if (RT_FAILURE(rc))
1463 rcExit = RTMsgErrorExitFailure("%s: Error changing mode: %Rrc", szDst, rc);
1464 }
1465#endif
1466
1467 return rcExit;
1468}
1469
1470
1471/**
1472 * @callback_method_impl{PFNDOWITHMEMBER, Implements --list.}
1473 */
1474static RTEXITCODE rtZipTarCmdListCallback(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit)
1475{
1476 /*
1477 * This is very simple in non-verbose mode.
1478 */
1479 if (!pOpts->fVerbose)
1480 {
1481 RTPrintf("%s\n", pszName);
1482 return rcExit;
1483 }
1484
1485 /*
1486 * Query all the information.
1487 */
1488 RTFSOBJINFO UnixInfo;
1489 int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
1490 if (RT_FAILURE(rc))
1491 {
1492 rcExit = RTMsgErrorExitFailure("RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
1493 RT_ZERO(UnixInfo);
1494 }
1495
1496 RTFSOBJINFO Owner;
1497 rc = RTVfsObjQueryInfo(hVfsObj, &Owner, RTFSOBJATTRADD_UNIX_OWNER);
1498 if (RT_FAILURE(rc))
1499 {
1500 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
1501 "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
1502 rc, pszName);
1503 RT_ZERO(Owner);
1504 }
1505
1506 RTFSOBJINFO Group;
1507 rc = RTVfsObjQueryInfo(hVfsObj, &Group, RTFSOBJATTRADD_UNIX_GROUP);
1508 if (RT_FAILURE(rc))
1509 {
1510 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
1511 "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
1512 rc, pszName);
1513 RT_ZERO(Group);
1514 }
1515
1516 const char *pszLinkType = NULL;
1517 char szTarget[RTPATH_MAX];
1518 szTarget[0] = '\0';
1519 RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
1520 if (hVfsSymlink != NIL_RTVFSSYMLINK)
1521 {
1522 rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
1523 if (RT_FAILURE(rc))
1524 rcExit = RTMsgErrorExitFailure("RTVfsSymlinkRead returned %Rrc on '%s'", rc, pszName);
1525 RTVfsSymlinkRelease(hVfsSymlink);
1526 pszLinkType = RTFS_IS_SYMLINK(UnixInfo.Attr.fMode) ? "->" : "link to";
1527 }
1528 else if (RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
1529 rcExit = RTMsgErrorExitFailure("Failed to get symlink object for '%s'", pszName);
1530
1531 /*
1532 * Translate the mode mask.
1533 */
1534 char szMode[16];
1535 switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
1536 {
1537 case RTFS_TYPE_FIFO: szMode[0] = 'f'; break;
1538 case RTFS_TYPE_DEV_CHAR: szMode[0] = 'c'; break;
1539 case RTFS_TYPE_DIRECTORY: szMode[0] = 'd'; break;
1540 case RTFS_TYPE_DEV_BLOCK: szMode[0] = 'b'; break;
1541 case RTFS_TYPE_FILE: szMode[0] = '-'; break;
1542 case RTFS_TYPE_SYMLINK: szMode[0] = 'l'; break;
1543 case RTFS_TYPE_SOCKET: szMode[0] = 's'; break;
1544 case RTFS_TYPE_WHITEOUT: szMode[0] = 'w'; break;
1545 default: szMode[0] = '?'; break;
1546 }
1547 if (pszLinkType && szMode[0] != 's')
1548 szMode[0] = 'h';
1549
1550 szMode[1] = UnixInfo.Attr.fMode & RTFS_UNIX_IRUSR ? 'r' : '-';
1551 szMode[2] = UnixInfo.Attr.fMode & RTFS_UNIX_IWUSR ? 'w' : '-';
1552 szMode[3] = UnixInfo.Attr.fMode & RTFS_UNIX_IXUSR ? 'x' : '-';
1553
1554 szMode[4] = UnixInfo.Attr.fMode & RTFS_UNIX_IRGRP ? 'r' : '-';
1555 szMode[5] = UnixInfo.Attr.fMode & RTFS_UNIX_IWGRP ? 'w' : '-';
1556 szMode[6] = UnixInfo.Attr.fMode & RTFS_UNIX_IXGRP ? 'x' : '-';
1557
1558 szMode[7] = UnixInfo.Attr.fMode & RTFS_UNIX_IROTH ? 'r' : '-';
1559 szMode[8] = UnixInfo.Attr.fMode & RTFS_UNIX_IWOTH ? 'w' : '-';
1560 szMode[9] = UnixInfo.Attr.fMode & RTFS_UNIX_IXOTH ? 'x' : '-';
1561 szMode[10] = '\0';
1562
1563 /** @todo sticky and set-uid/gid bits. */
1564
1565 /*
1566 * Make sure we've got valid owner and group strings.
1567 */
1568 if (!Owner.Attr.u.UnixGroup.szName[0])
1569 RTStrPrintf(Owner.Attr.u.UnixOwner.szName, sizeof(Owner.Attr.u.UnixOwner.szName),
1570 "%u", UnixInfo.Attr.u.Unix.uid);
1571
1572 if (!Group.Attr.u.UnixOwner.szName[0])
1573 RTStrPrintf(Group.Attr.u.UnixGroup.szName, sizeof(Group.Attr.u.UnixGroup.szName),
1574 "%u", UnixInfo.Attr.u.Unix.gid);
1575
1576 /*
1577 * Format the modification time.
1578 */
1579 char szModTime[32];
1580 RTTIME ModTime;
1581 PRTTIME pTime;
1582 if (!pOpts->fDisplayUtc)
1583 pTime = RTTimeLocalExplode(&ModTime, &UnixInfo.ModificationTime);
1584 else
1585 pTime = RTTimeExplode(&ModTime, &UnixInfo.ModificationTime);
1586 if (!pTime)
1587 RT_ZERO(ModTime);
1588 RTStrPrintf(szModTime, sizeof(szModTime), "%04d-%02u-%02u %02u:%02u",
1589 ModTime.i32Year, ModTime.u8Month, ModTime.u8MonthDay, ModTime.u8Hour, ModTime.u8Minute);
1590
1591 /*
1592 * Format the size and figure how much space is needed between the
1593 * user/group and the size.
1594 */
1595 char szSize[64];
1596 size_t cchSize;
1597 switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
1598 {
1599 case RTFS_TYPE_DEV_CHAR:
1600 case RTFS_TYPE_DEV_BLOCK:
1601 cchSize = RTStrPrintf(szSize, sizeof(szSize), "%u,%u",
1602 RTDEV_MAJOR(UnixInfo.Attr.u.Unix.Device), RTDEV_MINOR(UnixInfo.Attr.u.Unix.Device));
1603 break;
1604 default:
1605 cchSize = RTStrPrintf(szSize, sizeof(szSize), "%RU64", UnixInfo.cbObject);
1606 break;
1607 }
1608
1609 size_t cchUserGroup = strlen(Owner.Attr.u.UnixOwner.szName)
1610 + 1
1611 + strlen(Group.Attr.u.UnixGroup.szName);
1612 ssize_t cchPad = cchUserGroup + cchSize + 1 < 19
1613 ? 19 - (cchUserGroup + cchSize + 1)
1614 : 0;
1615
1616 /*
1617 * Go to press.
1618 */
1619 if (pszLinkType)
1620 RTPrintf("%s %s/%s%*s %s %s %s %s %s\n",
1621 szMode,
1622 Owner.Attr.u.UnixOwner.szName, Group.Attr.u.UnixGroup.szName,
1623 cchPad, "",
1624 szSize,
1625 szModTime,
1626 pszName,
1627 pszLinkType,
1628 szTarget);
1629 else
1630 RTPrintf("%s %s/%s%*s %s %s %s\n",
1631 szMode,
1632 Owner.Attr.u.UnixOwner.szName, Group.Attr.u.UnixGroup.szName,
1633 cchPad, "",
1634 szSize,
1635 szModTime,
1636 pszName);
1637
1638 return rcExit;
1639}
1640
1641
1642/**
1643 * Display usage.
1644 *
1645 * @param pszProgName The program name.
1646 */
1647static void rtZipTarUsage(const char *pszProgName)
1648{
1649 /*
1650 * 0 1 2 3 4 5 6 7 8
1651 * 012345678901234567890123456789012345678901234567890123456789012345678901234567890
1652 */
1653 RTPrintf("Usage: %s [options]\n"
1654 "\n",
1655 pszProgName);
1656 RTPrintf("Operations:\n"
1657 " -A, --concatenate, --catenate\n"
1658 " Append the content of one tar archive to another. (not impl)\n"
1659 " -c, --create\n"
1660 " Create a new tar archive. (not impl)\n"
1661 " -d, --diff, --compare\n"
1662 " Compare atar archive with the file system. (not impl)\n"
1663 " -r, --append\n"
1664 " Append more files to the tar archive. (not impl)\n"
1665 " -t, --list\n"
1666 " List the contents of the tar archive.\n"
1667 " -u, --update\n"
1668 " Update the archive, adding files that are newer than the\n"
1669 " ones in the archive. (not impl)\n"
1670 " -x, --extract, --get\n"
1671 " Extract the files from the tar archive.\n"
1672 " --delete\n"
1673 " Delete files from the tar archive.\n"
1674 "\n"
1675 );
1676 RTPrintf("Basic Options:\n"
1677 " -C <dir>, --directory <dir> (-A, -c, -d, -r, -u, -x)\n"
1678 " Sets the base directory for input and output file members.\n"
1679 " This does not apply to --file, even if it preceeds it.\n"
1680 " -f <archive>, --file <archive> (all)\n"
1681 " The tar file to create or process. '-' indicates stdout/stdin,\n"
1682 " which is is the default.\n"
1683 " -v, --verbose (all)\n"
1684 " Verbose operation.\n"
1685 " -p, --preserve-permissions (-x)\n"
1686 " Preserve all permissions when extracting. Must be used\n"
1687 " before the mode mask options as it will change some of these.\n"
1688 " -j, --bzip2 (all)\n"
1689 " Compress/decompress the archive with bzip2.\n"
1690 " -z, --gzip, --gunzip, --ungzip (all)\n"
1691 " Compress/decompress the archive with gzip.\n"
1692#ifdef IPRT_WITH_LZMA
1693 " -J, --xz (all)\n"
1694 " Compress/decompress the archive using xz/lzma.\n"
1695#endif
1696 "\n");
1697 RTPrintf("Misc Options:\n"
1698 " --owner <uid/username> (-A, -c, -d, -r, -u, -x)\n"
1699 " Set the owner of extracted and archived files to the user specified.\n"
1700 " --group <uid/username> (-A, -c, -d, -r, -u, -x)\n"
1701 " Set the group of extracted and archived files to the group specified.\n"
1702 " --utc (-t)\n"
1703 " Display timestamps as UTC instead of local time.\n"
1704 " -S, --sparse (-A, -c, -u)\n"
1705 " Detect sparse files and store them (gnu tar extension).\n"
1706 " --format <format> (-A, -c, -u, but also -d, -r, -x)\n"
1707 " The file format:\n"
1708 " auto (gnu tar)\n"
1709 " default (gnu tar)\n"
1710 " tar (gnu tar)"
1711 " gnu (tar v1.13+), "
1712 " ustar (tar POSIX.1-1988), "
1713 " pax (tar POSIX.1-2001),\n"
1714 " xar\n"
1715 " cpio\n"
1716 " Note! Because XAR/TAR/CPIO detection isn't implemented yet, it\n"
1717 " is necessary to specifcy --format=xar when reading a\n"
1718 " XAR file or --format=cpio for a CPIO file.\n"
1719 " Otherwise this option is only for creation.\n"
1720 "\n");
1721 RTPrintf("IPRT Options:\n"
1722 " --prefix <dir-prefix> (-A, -c, -d, -r, -u)\n"
1723 " Directory prefix to give the members added to the archive.\n"
1724 " --file-mode-and-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
1725 " Restrict the access mode of regular and special files.\n"
1726 " --file-mode-or-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
1727 " Include the given access mode for regular and special files.\n"
1728 " --dir-mode-and-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
1729 " Restrict the access mode of directories.\n"
1730 " --dir-mode-or-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
1731 " Include the given access mode for directories.\n"
1732 " --read-ahead (-x)\n"
1733 " Enabled read ahead thread when extracting files.\n"
1734 " --push-file (-A, -c, -u)\n"
1735 " Use RTVfsFsStrmPushFile instead of RTVfsFsStrmAdd.\n"
1736 "\n");
1737 RTPrintf("Standard Options:\n"
1738 " -h, -?, --help\n"
1739 " Display this help text.\n"
1740 " -V, --version\n"
1741 " Display version number.\n");
1742}
1743
1744
1745RTDECL(RTEXITCODE) RTZipTarCmd(unsigned cArgs, char **papszArgs)
1746{
1747 /*
1748 * Parse the command line.
1749 *
1750 * N.B. This is less flexible that your regular tar program in that it
1751 * requires the operation to be specified as an option. On the other
1752 * hand, you can specify it where ever you like in the command line.
1753 */
1754 static const RTGETOPTDEF s_aOptions[] =
1755 {
1756 /* operations */
1757 { "--concatenate", 'A', RTGETOPT_REQ_NOTHING },
1758 { "--catenate", 'A', RTGETOPT_REQ_NOTHING },
1759 { "--create", 'c', RTGETOPT_REQ_NOTHING },
1760 { "--diff", 'd', RTGETOPT_REQ_NOTHING },
1761 { "--compare", 'd', RTGETOPT_REQ_NOTHING },
1762 { "--append", 'r', RTGETOPT_REQ_NOTHING },
1763 { "--list", 't', RTGETOPT_REQ_NOTHING },
1764 { "--update", 'u', RTGETOPT_REQ_NOTHING },
1765 { "--extract", 'x', RTGETOPT_REQ_NOTHING },
1766 { "--get", 'x', RTGETOPT_REQ_NOTHING },
1767 { "--delete", RTZIPTARCMD_OPT_DELETE, RTGETOPT_REQ_NOTHING },
1768
1769 /* basic options */
1770 { "--directory", 'C', RTGETOPT_REQ_STRING },
1771 { "--file", 'f', RTGETOPT_REQ_STRING },
1772 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
1773 { "--preserve-permissions", 'p', RTGETOPT_REQ_NOTHING },
1774 { "--bzip2", 'j', RTGETOPT_REQ_NOTHING },
1775 { "--gzip", 'z', RTGETOPT_REQ_NOTHING },
1776 { "--gunzip", 'z', RTGETOPT_REQ_NOTHING },
1777 { "--ungzip", 'z', RTGETOPT_REQ_NOTHING },
1778#ifdef IPRT_WITH_LZMA
1779 { "--xz", 'J', RTGETOPT_REQ_NOTHING },
1780#endif
1781
1782 /* other options. */
1783 { "--owner", RTZIPTARCMD_OPT_OWNER, RTGETOPT_REQ_STRING },
1784 { "--group", RTZIPTARCMD_OPT_GROUP, RTGETOPT_REQ_STRING },
1785 { "--utc", RTZIPTARCMD_OPT_UTC, RTGETOPT_REQ_NOTHING },
1786 { "--sparse", 'S', RTGETOPT_REQ_NOTHING },
1787 { "--format", RTZIPTARCMD_OPT_FORMAT, RTGETOPT_REQ_STRING },
1788 { "--no-recursion", RTZIPTARCMD_OPT_NO_RECURSION, RTGETOPT_REQ_NOTHING },
1789
1790 /* IPRT extensions */
1791 { "--prefix", RTZIPTARCMD_OPT_PREFIX, RTGETOPT_REQ_STRING },
1792 { "--file-mode-and-mask", RTZIPTARCMD_OPT_FILE_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
1793 { "--file-mode-or-mask", RTZIPTARCMD_OPT_FILE_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
1794 { "--dir-mode-and-mask", RTZIPTARCMD_OPT_DIR_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
1795 { "--dir-mode-or-mask", RTZIPTARCMD_OPT_DIR_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
1796 { "--read-ahead", RTZIPTARCMD_OPT_READ_AHEAD, RTGETOPT_REQ_NOTHING },
1797 { "--use-push-file", RTZIPTARCMD_OPT_USE_PUSH_FILE, RTGETOPT_REQ_NOTHING },
1798 };
1799
1800 RTGETOPTSTATE GetState;
1801 int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
1802 RTGETOPTINIT_FLAGS_OPTS_FIRST);
1803 if (RT_FAILURE(rc))
1804 return RTMsgErrorExitFailure("RTGetOpt failed: %Rrc", rc);
1805
1806 RTZIPTARCMDOPS Opts;
1807 RT_ZERO(Opts);
1808 Opts.enmFormat = RTZIPTARCMDFORMAT_AUTO_DEFAULT;
1809 Opts.uidOwner = NIL_RTUID;
1810 Opts.gidGroup = NIL_RTUID;
1811 Opts.fFileModeAndMask = RTFS_UNIX_ALL_ACCESS_PERMS;
1812 Opts.fDirModeAndMask = RTFS_UNIX_ALL_ACCESS_PERMS;
1813#if 0
1814 if (RTPermIsSuperUser())
1815 {
1816 Opts.fFileModeAndMask = RTFS_UNIX_ALL_PERMS;
1817 Opts.fDirModeAndMask = RTFS_UNIX_ALL_PERMS;
1818 Opts.fPreserveOwner = true;
1819 Opts.fPreserveGroup = true;
1820 }
1821#endif
1822 Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
1823 Opts.fRecursive = true; /* Recursion is implicit unless otherwise specified. */
1824
1825 RTGETOPTUNION ValueUnion;
1826 while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
1827 && rc != VINF_GETOPT_NOT_OPTION)
1828 {
1829 switch (rc)
1830 {
1831 /* operations */
1832 case 'A':
1833 case 'c':
1834 case 'd':
1835 case 'r':
1836 case 't':
1837 case 'u':
1838 case 'x':
1839 case RTZIPTARCMD_OPT_DELETE:
1840 if (Opts.iOperation)
1841 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Conflicting tar operation (%s already set, now %s)",
1842 Opts.pszOperation, ValueUnion.pDef->pszLong);
1843 Opts.iOperation = rc;
1844 Opts.pszOperation = ValueUnion.pDef->pszLong;
1845 break;
1846
1847 /* basic options */
1848 case 'C':
1849 if (Opts.pszDirectory)
1850 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -C/--directory once");
1851 Opts.pszDirectory = ValueUnion.psz;
1852 break;
1853
1854 case 'f':
1855 if (Opts.pszFile)
1856 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -f/--file once");
1857 Opts.pszFile = ValueUnion.psz;
1858 break;
1859
1860 case 'v':
1861 Opts.fVerbose = true;
1862 break;
1863
1864 case 'p':
1865 Opts.fFileModeAndMask = RTFS_UNIX_ALL_PERMS;
1866 Opts.fDirModeAndMask = RTFS_UNIX_ALL_PERMS;
1867 Opts.fPreserveOwner = true;
1868 Opts.fPreserveGroup = true;
1869 break;
1870
1871 case 'j':
1872 case 'z':
1873#ifdef IPRT_WITH_LZMA
1874 case 'J':
1875#endif
1876 if (Opts.chZipper)
1877 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify one compressor / decompressor");
1878 Opts.chZipper = rc;
1879 break;
1880
1881 case RTZIPTARCMD_OPT_OWNER:
1882 if (Opts.pszOwner)
1883 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --owner once");
1884 Opts.pszOwner = ValueUnion.psz;
1885
1886 rc = RTStrToUInt32Full(Opts.pszOwner, 0, &ValueUnion.u32);
1887 if (RT_SUCCESS(rc) && rc != VINF_SUCCESS)
1888 return RTMsgErrorExit(RTEXITCODE_SYNTAX,
1889 "Error convering --owner '%s' into a number: %Rrc", Opts.pszOwner, rc);
1890 if (RT_SUCCESS(rc))
1891 {
1892 Opts.uidOwner = ValueUnion.u32;
1893 Opts.pszOwner = NULL;
1894 }
1895 break;
1896
1897 case RTZIPTARCMD_OPT_GROUP:
1898 if (Opts.pszGroup)
1899 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --group once");
1900 Opts.pszGroup = ValueUnion.psz;
1901
1902 rc = RTStrToUInt32Full(Opts.pszGroup, 0, &ValueUnion.u32);
1903 if (RT_SUCCESS(rc) && rc != VINF_SUCCESS)
1904 return RTMsgErrorExit(RTEXITCODE_SYNTAX,
1905 "Error convering --group '%s' into a number: %Rrc", Opts.pszGroup, rc);
1906 if (RT_SUCCESS(rc))
1907 {
1908 Opts.gidGroup = ValueUnion.u32;
1909 Opts.pszGroup = NULL;
1910 }
1911 break;
1912
1913 case RTZIPTARCMD_OPT_UTC:
1914 Opts.fDisplayUtc = true;
1915 break;
1916
1917 case RTZIPTARCMD_OPT_NO_RECURSION:
1918 Opts.fRecursive = false;
1919 break;
1920
1921 /* GNU */
1922 case 'S':
1923 Opts.fTarCreate |= RTZIPTAR_C_SPARSE;
1924 break;
1925
1926 /* iprt extensions */
1927 case RTZIPTARCMD_OPT_PREFIX:
1928 if (Opts.pszPrefix)
1929 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --prefix once");
1930 Opts.pszPrefix = ValueUnion.psz;
1931 break;
1932
1933 case RTZIPTARCMD_OPT_FILE_MODE_AND_MASK:
1934 Opts.fFileModeAndMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
1935 break;
1936
1937 case RTZIPTARCMD_OPT_FILE_MODE_OR_MASK:
1938 Opts.fFileModeOrMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
1939 break;
1940
1941 case RTZIPTARCMD_OPT_DIR_MODE_AND_MASK:
1942 Opts.fDirModeAndMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
1943 break;
1944
1945 case RTZIPTARCMD_OPT_DIR_MODE_OR_MASK:
1946 Opts.fDirModeOrMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
1947 break;
1948
1949 case RTZIPTARCMD_OPT_FORMAT:
1950 if (!strcmp(ValueUnion.psz, "auto") || !strcmp(ValueUnion.psz, "default"))
1951 {
1952 Opts.enmFormat = RTZIPTARCMDFORMAT_AUTO_DEFAULT;
1953 Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
1954 }
1955 else if (!strcmp(ValueUnion.psz, "tar"))
1956 {
1957 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
1958 Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
1959 }
1960 else if (!strcmp(ValueUnion.psz, "gnu"))
1961 {
1962 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
1963 Opts.enmTarFormat = RTZIPTARFORMAT_GNU;
1964 }
1965 else if (!strcmp(ValueUnion.psz, "ustar"))
1966 {
1967 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
1968 Opts.enmTarFormat = RTZIPTARFORMAT_USTAR;
1969 }
1970 else if ( !strcmp(ValueUnion.psz, "posix")
1971 || !strcmp(ValueUnion.psz, "pax"))
1972 {
1973 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
1974 Opts.enmTarFormat = RTZIPTARFORMAT_PAX;
1975 }
1976 else if (!strcmp(ValueUnion.psz, "xar"))
1977 Opts.enmFormat = RTZIPTARCMDFORMAT_XAR;
1978 else if (!strcmp(ValueUnion.psz, "cpio"))
1979 {
1980 Opts.enmFormat = RTZIPTARCMDFORMAT_CPIO;
1981 Opts.enmTarFormat = RTZIPTARFORMAT_CPIO_ASCII_NEW;
1982 }
1983 else
1984 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown archive format: '%s'", ValueUnion.psz);
1985 break;
1986
1987 case RTZIPTARCMD_OPT_READ_AHEAD:
1988 Opts.fReadAhead = true;
1989 break;
1990
1991 case RTZIPTARCMD_OPT_USE_PUSH_FILE:
1992 Opts.fUsePushFile = true;
1993 break;
1994
1995 /* Standard bits. */
1996 case 'h':
1997 rtZipTarUsage(RTPathFilename(papszArgs[0]));
1998 return RTEXITCODE_SUCCESS;
1999
2000 case 'V':
2001 RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
2002 return RTEXITCODE_SUCCESS;
2003
2004 default:
2005 return RTGetOptPrintError(rc, &ValueUnion);
2006 }
2007 }
2008
2009 if (rc == VINF_GETOPT_NOT_OPTION)
2010 {
2011 /* this is kind of ugly. */
2012 Assert((unsigned)GetState.iNext - 1 <= cArgs);
2013 Opts.papszFiles = (const char * const *)&papszArgs[GetState.iNext - 1];
2014 Opts.cFiles = cArgs - GetState.iNext + 1;
2015 }
2016
2017 if (!Opts.pszFile)
2018 return RTMsgErrorExitFailure("No archive specified");
2019
2020 /*
2021 * Post proceess the options.
2022 */
2023 if (Opts.iOperation == 0)
2024 {
2025 Opts.iOperation = 't';
2026 Opts.pszOperation = "--list";
2027 }
2028
2029 if ( Opts.iOperation == 'x'
2030 && Opts.pszOwner)
2031 return RTMsgErrorExitFailure("The use of --owner with %s has not implemented yet", Opts.pszOperation);
2032
2033 if ( Opts.iOperation == 'x'
2034 && Opts.pszGroup)
2035 return RTMsgErrorExitFailure("The use of --group with %s has not implemented yet", Opts.pszOperation);
2036
2037 /*
2038 * Do the job.
2039 */
2040 switch (Opts.iOperation)
2041 {
2042 case 't':
2043 return rtZipTarDoWithMembers(&Opts, rtZipTarCmdListCallback);
2044
2045 case 'x':
2046 return rtZipTarDoWithMembers(&Opts, rtZipTarCmdExtractCallback);
2047
2048 case 'c':
2049 return rtZipTarCreate(&Opts);
2050
2051 case 'A':
2052 case 'd':
2053 case 'r':
2054 case 'u':
2055 case RTZIPTARCMD_OPT_DELETE:
2056 return RTMsgErrorExitFailure("The operation %s is not implemented yet", Opts.pszOperation);
2057
2058 default:
2059 return RTMsgErrorExitFailure("Internal error");
2060 }
2061}
2062
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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