VirtualBox

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

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

Runtime/common/zip/tarcmd.cpp: Add missing error check after switch () to break out of the loop when an error occurs, bugref:3409

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 78.6 KB
 
1/* $Id: tarcmd.cpp 107631 2025-01-10 10:41:11Z 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 RTFileDelete(pszDst);
1233 }
1234 }
1235 else
1236 rcExit = RTMsgErrorExitFailure("%s: Error creating file: %Rrc", pszDst, rc);
1237 RTFileClose(hSrcFile);
1238 }
1239 else
1240 rcExit = RTMsgErrorExitFailure("%s: Error opening file '%s' for reading (hardlink target): %Rrc",
1241 pszDst, szFullTarget, rc);
1242 }
1243
1244 return rcExit;
1245}
1246
1247
1248
1249/**
1250 * Extracts a file.
1251 *
1252 * Since we can restore permissions and attributes more efficiently by working
1253 * directly on the file handle, we have special code path for files.
1254 *
1255 * @returns rcExit or RTEXITCODE_FAILURE.
1256 * @param pOpts The tar options.
1257 * @param hVfsObj The tar object to display
1258 * @param rcExit The current exit code.
1259 * @param pszDst The destination path.
1260 * @param pUnixInfo The unix fs object info.
1261 * @param pOwner The owner info.
1262 * @param pGroup The group info.
1263 */
1264static RTEXITCODE rtZipTarCmdExtractFile(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, RTEXITCODE rcExit,
1265 const char *pszDst, PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner, PCRTFSOBJINFO pGroup)
1266{
1267 /*
1268 * Open the destination file and create a stream object for it.
1269 */
1270 uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT
1271 | ((RTFS_UNIX_IWUSR | RTFS_UNIX_IRUSR) << RTFILE_O_CREATE_MODE_SHIFT);
1272 RTFILE hFile;
1273 int rc = RTFileOpen(&hFile, pszDst, fOpen);
1274 if (RT_FAILURE(rc))
1275 return RTMsgErrorExitFailure("%s: Error creating file: %Rrc", pszDst, rc);
1276
1277 RTVFSIOSTREAM hVfsIosDst;
1278 rc = RTVfsIoStrmFromRTFile(hFile, fOpen, true /*fLeaveOpen*/, &hVfsIosDst);
1279 if (RT_SUCCESS(rc))
1280 {
1281 /*
1282 * Convert source to a stream and optionally add a read ahead stage.
1283 */
1284 RTVFSIOSTREAM hVfsIosSrc = RTVfsObjToIoStream(hVfsObj);
1285 if (pOpts->fReadAhead)
1286 {
1287 RTVFSIOSTREAM hVfsReadAhead;
1288 rc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlag*/, 16 /*cBuffers*/, _256K /*cbBuffer*/, &hVfsReadAhead);
1289 if (RT_SUCCESS(rc))
1290 {
1291 RTVfsIoStrmRelease(hVfsIosSrc);
1292 hVfsIosSrc = hVfsReadAhead;
1293 }
1294 else
1295 AssertRC(rc); /* can be ignored in release builds. */
1296 }
1297
1298 /*
1299 * Pump the data thru and correct the file attributes.
1300 */
1301 rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(pUnixInfo->cbObject, _1M));
1302 if (RT_SUCCESS(rc))
1303 rcExit = rtZipTarCmdExtractSetAttribs(pOpts, rcExit, hFile, pszDst, pUnixInfo, pOwner, pGroup);
1304 else
1305 rcExit = RTMsgErrorExitFailure("%s: Error writing out file: %Rrc", pszDst, rc);
1306 RTVfsIoStrmRelease(hVfsIosSrc);
1307 RTVfsIoStrmRelease(hVfsIosDst);
1308 }
1309 else
1310 rcExit = RTMsgErrorExitFailure("%s: Error creating I/O stream for file: %Rrc", pszDst, rc);
1311 RTFileClose(hFile);
1312 return rcExit;
1313}
1314
1315
1316/**
1317 * @callback_method_impl{PFNDOWITHMEMBER, Implements --extract.}
1318 */
1319static RTEXITCODE rtZipTarCmdExtractCallback(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit)
1320{
1321 if (pOpts->fVerbose)
1322 RTPrintf("%s\n", pszName);
1323
1324 /*
1325 * Query all the information.
1326 */
1327 RTFSOBJINFO UnixInfo;
1328 int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
1329 if (RT_FAILURE(rc))
1330 return RTMsgErrorExitFailure("RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
1331
1332 RTFSOBJINFO Owner;
1333 rc = RTVfsObjQueryInfo(hVfsObj, &Owner, RTFSOBJATTRADD_UNIX_OWNER);
1334 if (RT_FAILURE(rc))
1335 return RTMsgErrorExit(RTEXITCODE_FAILURE,
1336 "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
1337 rc, pszName);
1338
1339 RTFSOBJINFO Group;
1340 rc = RTVfsObjQueryInfo(hVfsObj, &Group, RTFSOBJATTRADD_UNIX_GROUP);
1341 if (RT_FAILURE(rc))
1342 return RTMsgErrorExit(RTEXITCODE_FAILURE,
1343 "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
1344 rc, pszName);
1345
1346 bool fIsHardLink = false;
1347 char szTarget[RTPATH_MAX];
1348 szTarget[0] = '\0';
1349 RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
1350 if (hVfsSymlink != NIL_RTVFSSYMLINK)
1351 {
1352 rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
1353 RTVfsSymlinkRelease(hVfsSymlink);
1354 if (RT_FAILURE(rc))
1355 return RTMsgErrorExitFailure("%s: RTVfsSymlinkRead failed: %Rrc", pszName, rc);
1356 if (!szTarget[0])
1357 return RTMsgErrorExitFailure("%s: Link target is empty.", pszName);
1358 if (!RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
1359 {
1360 fIsHardLink = true;
1361 if (!RTFS_IS_FILE(UnixInfo.Attr.fMode))
1362 return RTMsgErrorExitFailure("%s: Hardlinks are only supported for regular files (target=%s).",
1363 pszName, szTarget);
1364 if (rtZipTarHasEscapeSequence(pszName))
1365 return RTMsgErrorExitFailure("%s: Hardlink target '%s' contains an escape sequence.",
1366 pszName, szTarget);
1367 }
1368 }
1369 else if (RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
1370 return RTMsgErrorExitFailure("Failed to get symlink object for '%s'", pszName);
1371
1372 if (rtZipTarHasEscapeSequence(pszName))
1373 return RTMsgErrorExitFailure("Name '%s' contains an escape sequence.", pszName);
1374
1375 /*
1376 * Construct the path to the extracted member.
1377 */
1378 char szDst[RTPATH_MAX];
1379 rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszName);
1380 if (RT_FAILURE(rc))
1381 return RTMsgErrorExitFailure("%s: Failed to construct destination path for: %Rrc", pszName, rc);
1382
1383 /*
1384 * Extract according to the type.
1385 */
1386 if (!fIsHardLink)
1387 switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
1388 {
1389 case RTFS_TYPE_FILE:
1390 return rtZipTarCmdExtractFile(pOpts, hVfsObj, rcExit, szDst, &UnixInfo, &Owner, &Group);
1391
1392 case RTFS_TYPE_DIRECTORY:
1393 rc = RTDirCreateFullPath(szDst, UnixInfo.Attr.fMode & RTFS_UNIX_ALL_ACCESS_PERMS);
1394 if (RT_FAILURE(rc))
1395 return RTMsgErrorExitFailure("%s: Error creating directory: %Rrc", szDst, rc);
1396 break;
1397
1398 case RTFS_TYPE_SYMLINK:
1399 rc = RTSymlinkCreate(szDst, szTarget, RTSYMLINKTYPE_UNKNOWN, 0);
1400 if (RT_FAILURE(rc))
1401 return RTMsgErrorExitFailure("%s: Error creating symbolic link: %Rrc", szDst, rc);
1402 break;
1403
1404 case RTFS_TYPE_FIFO:
1405 return RTMsgErrorExitFailure("%s: FIFOs are not supported.", pszName);
1406 case RTFS_TYPE_DEV_CHAR:
1407 return RTMsgErrorExitFailure("%s: FIFOs are not supported.", pszName);
1408 case RTFS_TYPE_DEV_BLOCK:
1409 return RTMsgErrorExitFailure("%s: Block devices are not supported.", pszName);
1410 case RTFS_TYPE_SOCKET:
1411 return RTMsgErrorExitFailure("%s: Sockets are not supported.", pszName);
1412 case RTFS_TYPE_WHITEOUT:
1413 return RTMsgErrorExitFailure("%s: Whiteouts are not support.", pszName);
1414 default:
1415 return RTMsgErrorExitFailure("%s: Unknown file type.", pszName);
1416 }
1417 else
1418 return rtZipTarCmdExtractHardlink(pOpts, rcExit, szDst, szTarget, &UnixInfo, &Owner, &Group);
1419
1420 /*
1421 * Set other attributes as requested.
1422 *
1423 * Note! File extraction does get here.
1424 */
1425 if (!pOpts->fNoModTime)
1426 {
1427 rc = RTPathSetTimesEx(szDst, NULL, &UnixInfo.ModificationTime, NULL, NULL, RTPATH_F_ON_LINK);
1428 if (RT_FAILURE(rc) && rc != VERR_NOT_SUPPORTED && rc != VERR_NS_SYMLINK_SET_TIME)
1429 rcExit = RTMsgErrorExitFailure("%s: Error changing modification time: %Rrc.", pszName, rc);
1430 }
1431
1432#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
1433 if ( pOpts->uidOwner != NIL_RTUID
1434 || pOpts->gidGroup != NIL_RTGID
1435 || pOpts->fPreserveOwner
1436 || pOpts->fPreserveGroup)
1437 {
1438 RTUID uidFile;
1439 rcExit = rtZipTarQueryExtractOwner(pOpts, &Owner, szDst, rcExit, &uidFile);
1440
1441 RTGID gidFile;
1442 rcExit = rtZipTarQueryExtractGroup(pOpts, &Group, szDst, rcExit, &gidFile);
1443 if (uidFile != NIL_RTUID || gidFile != NIL_RTGID)
1444 {
1445 rc = RTPathSetOwnerEx(szDst, uidFile, gidFile, RTPATH_F_ON_LINK);
1446 if (RT_FAILURE(rc))
1447 rcExit = RTMsgErrorExitFailure("%s: Error owner/group: %Rrc", szDst, rc);
1448 }
1449 }
1450#endif
1451
1452#if !defined(RT_OS_WINDOWS) /** @todo implement RTPathSetMode on windows... */
1453 if (!RTFS_IS_SYMLINK(UnixInfo.Attr.fMode)) /* RTPathSetMode follows symbolic links atm. */
1454 {
1455 RTFMODE fMode;
1456 if (RTFS_IS_DIRECTORY(UnixInfo.Attr.fMode))
1457 fMode = (UnixInfo.Attr.fMode & (pOpts->fDirModeAndMask | RTFS_TYPE_MASK)) | pOpts->fDirModeOrMask;
1458 else
1459 fMode = (UnixInfo.Attr.fMode & (pOpts->fFileModeAndMask | RTFS_TYPE_MASK)) | pOpts->fFileModeOrMask;
1460 rc = RTPathSetMode(szDst, fMode);
1461 if (RT_FAILURE(rc))
1462 rcExit = RTMsgErrorExitFailure("%s: Error changing mode: %Rrc", szDst, rc);
1463 }
1464#endif
1465
1466 return rcExit;
1467}
1468
1469
1470/**
1471 * @callback_method_impl{PFNDOWITHMEMBER, Implements --list.}
1472 */
1473static RTEXITCODE rtZipTarCmdListCallback(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit)
1474{
1475 /*
1476 * This is very simple in non-verbose mode.
1477 */
1478 if (!pOpts->fVerbose)
1479 {
1480 RTPrintf("%s\n", pszName);
1481 return rcExit;
1482 }
1483
1484 /*
1485 * Query all the information.
1486 */
1487 RTFSOBJINFO UnixInfo;
1488 int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
1489 if (RT_FAILURE(rc))
1490 {
1491 rcExit = RTMsgErrorExitFailure("RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
1492 RT_ZERO(UnixInfo);
1493 }
1494
1495 RTFSOBJINFO Owner;
1496 rc = RTVfsObjQueryInfo(hVfsObj, &Owner, RTFSOBJATTRADD_UNIX_OWNER);
1497 if (RT_FAILURE(rc))
1498 {
1499 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
1500 "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
1501 rc, pszName);
1502 RT_ZERO(Owner);
1503 }
1504
1505 RTFSOBJINFO Group;
1506 rc = RTVfsObjQueryInfo(hVfsObj, &Group, RTFSOBJATTRADD_UNIX_GROUP);
1507 if (RT_FAILURE(rc))
1508 {
1509 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
1510 "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
1511 rc, pszName);
1512 RT_ZERO(Group);
1513 }
1514
1515 const char *pszLinkType = NULL;
1516 char szTarget[RTPATH_MAX];
1517 szTarget[0] = '\0';
1518 RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
1519 if (hVfsSymlink != NIL_RTVFSSYMLINK)
1520 {
1521 rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
1522 if (RT_FAILURE(rc))
1523 rcExit = RTMsgErrorExitFailure("RTVfsSymlinkRead returned %Rrc on '%s'", rc, pszName);
1524 RTVfsSymlinkRelease(hVfsSymlink);
1525 pszLinkType = RTFS_IS_SYMLINK(UnixInfo.Attr.fMode) ? "->" : "link to";
1526 }
1527 else if (RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
1528 rcExit = RTMsgErrorExitFailure("Failed to get symlink object for '%s'", pszName);
1529
1530 /*
1531 * Translate the mode mask.
1532 */
1533 char szMode[16];
1534 switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
1535 {
1536 case RTFS_TYPE_FIFO: szMode[0] = 'f'; break;
1537 case RTFS_TYPE_DEV_CHAR: szMode[0] = 'c'; break;
1538 case RTFS_TYPE_DIRECTORY: szMode[0] = 'd'; break;
1539 case RTFS_TYPE_DEV_BLOCK: szMode[0] = 'b'; break;
1540 case RTFS_TYPE_FILE: szMode[0] = '-'; break;
1541 case RTFS_TYPE_SYMLINK: szMode[0] = 'l'; break;
1542 case RTFS_TYPE_SOCKET: szMode[0] = 's'; break;
1543 case RTFS_TYPE_WHITEOUT: szMode[0] = 'w'; break;
1544 default: szMode[0] = '?'; break;
1545 }
1546 if (pszLinkType && szMode[0] != 's')
1547 szMode[0] = 'h';
1548
1549 szMode[1] = UnixInfo.Attr.fMode & RTFS_UNIX_IRUSR ? 'r' : '-';
1550 szMode[2] = UnixInfo.Attr.fMode & RTFS_UNIX_IWUSR ? 'w' : '-';
1551 szMode[3] = UnixInfo.Attr.fMode & RTFS_UNIX_IXUSR ? 'x' : '-';
1552
1553 szMode[4] = UnixInfo.Attr.fMode & RTFS_UNIX_IRGRP ? 'r' : '-';
1554 szMode[5] = UnixInfo.Attr.fMode & RTFS_UNIX_IWGRP ? 'w' : '-';
1555 szMode[6] = UnixInfo.Attr.fMode & RTFS_UNIX_IXGRP ? 'x' : '-';
1556
1557 szMode[7] = UnixInfo.Attr.fMode & RTFS_UNIX_IROTH ? 'r' : '-';
1558 szMode[8] = UnixInfo.Attr.fMode & RTFS_UNIX_IWOTH ? 'w' : '-';
1559 szMode[9] = UnixInfo.Attr.fMode & RTFS_UNIX_IXOTH ? 'x' : '-';
1560 szMode[10] = '\0';
1561
1562 /** @todo sticky and set-uid/gid bits. */
1563
1564 /*
1565 * Make sure we've got valid owner and group strings.
1566 */
1567 if (!Owner.Attr.u.UnixGroup.szName[0])
1568 RTStrPrintf(Owner.Attr.u.UnixOwner.szName, sizeof(Owner.Attr.u.UnixOwner.szName),
1569 "%u", UnixInfo.Attr.u.Unix.uid);
1570
1571 if (!Group.Attr.u.UnixOwner.szName[0])
1572 RTStrPrintf(Group.Attr.u.UnixGroup.szName, sizeof(Group.Attr.u.UnixGroup.szName),
1573 "%u", UnixInfo.Attr.u.Unix.gid);
1574
1575 /*
1576 * Format the modification time.
1577 */
1578 char szModTime[32];
1579 RTTIME ModTime;
1580 PRTTIME pTime;
1581 if (!pOpts->fDisplayUtc)
1582 pTime = RTTimeLocalExplode(&ModTime, &UnixInfo.ModificationTime);
1583 else
1584 pTime = RTTimeExplode(&ModTime, &UnixInfo.ModificationTime);
1585 if (!pTime)
1586 RT_ZERO(ModTime);
1587 RTStrPrintf(szModTime, sizeof(szModTime), "%04d-%02u-%02u %02u:%02u",
1588 ModTime.i32Year, ModTime.u8Month, ModTime.u8MonthDay, ModTime.u8Hour, ModTime.u8Minute);
1589
1590 /*
1591 * Format the size and figure how much space is needed between the
1592 * user/group and the size.
1593 */
1594 char szSize[64];
1595 size_t cchSize;
1596 switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
1597 {
1598 case RTFS_TYPE_DEV_CHAR:
1599 case RTFS_TYPE_DEV_BLOCK:
1600 cchSize = RTStrPrintf(szSize, sizeof(szSize), "%u,%u",
1601 RTDEV_MAJOR(UnixInfo.Attr.u.Unix.Device), RTDEV_MINOR(UnixInfo.Attr.u.Unix.Device));
1602 break;
1603 default:
1604 cchSize = RTStrPrintf(szSize, sizeof(szSize), "%RU64", UnixInfo.cbObject);
1605 break;
1606 }
1607
1608 size_t cchUserGroup = strlen(Owner.Attr.u.UnixOwner.szName)
1609 + 1
1610 + strlen(Group.Attr.u.UnixGroup.szName);
1611 ssize_t cchPad = cchUserGroup + cchSize + 1 < 19
1612 ? 19 - (cchUserGroup + cchSize + 1)
1613 : 0;
1614
1615 /*
1616 * Go to press.
1617 */
1618 if (pszLinkType)
1619 RTPrintf("%s %s/%s%*s %s %s %s %s %s\n",
1620 szMode,
1621 Owner.Attr.u.UnixOwner.szName, Group.Attr.u.UnixGroup.szName,
1622 cchPad, "",
1623 szSize,
1624 szModTime,
1625 pszName,
1626 pszLinkType,
1627 szTarget);
1628 else
1629 RTPrintf("%s %s/%s%*s %s %s %s\n",
1630 szMode,
1631 Owner.Attr.u.UnixOwner.szName, Group.Attr.u.UnixGroup.szName,
1632 cchPad, "",
1633 szSize,
1634 szModTime,
1635 pszName);
1636
1637 return rcExit;
1638}
1639
1640
1641/**
1642 * Display usage.
1643 *
1644 * @param pszProgName The program name.
1645 */
1646static void rtZipTarUsage(const char *pszProgName)
1647{
1648 /*
1649 * 0 1 2 3 4 5 6 7 8
1650 * 012345678901234567890123456789012345678901234567890123456789012345678901234567890
1651 */
1652 RTPrintf("Usage: %s [options]\n"
1653 "\n",
1654 pszProgName);
1655 RTPrintf("Operations:\n"
1656 " -A, --concatenate, --catenate\n"
1657 " Append the content of one tar archive to another. (not impl)\n"
1658 " -c, --create\n"
1659 " Create a new tar archive. (not impl)\n"
1660 " -d, --diff, --compare\n"
1661 " Compare atar archive with the file system. (not impl)\n"
1662 " -r, --append\n"
1663 " Append more files to the tar archive. (not impl)\n"
1664 " -t, --list\n"
1665 " List the contents of the tar archive.\n"
1666 " -u, --update\n"
1667 " Update the archive, adding files that are newer than the\n"
1668 " ones in the archive. (not impl)\n"
1669 " -x, --extract, --get\n"
1670 " Extract the files from the tar archive.\n"
1671 " --delete\n"
1672 " Delete files from the tar archive.\n"
1673 "\n"
1674 );
1675 RTPrintf("Basic Options:\n"
1676 " -C <dir>, --directory <dir> (-A, -c, -d, -r, -u, -x)\n"
1677 " Sets the base directory for input and output file members.\n"
1678 " This does not apply to --file, even if it preceeds it.\n"
1679 " -f <archive>, --file <archive> (all)\n"
1680 " The tar file to create or process. '-' indicates stdout/stdin,\n"
1681 " which is is the default.\n"
1682 " -v, --verbose (all)\n"
1683 " Verbose operation.\n"
1684 " -p, --preserve-permissions (-x)\n"
1685 " Preserve all permissions when extracting. Must be used\n"
1686 " before the mode mask options as it will change some of these.\n"
1687 " -j, --bzip2 (all)\n"
1688 " Compress/decompress the archive with bzip2.\n"
1689 " -z, --gzip, --gunzip, --ungzip (all)\n"
1690 " Compress/decompress the archive with gzip.\n"
1691#ifdef IPRT_WITH_LZMA
1692 " -J, --xz (all)\n"
1693 " Compress/decompress the archive using xz/lzma.\n"
1694#endif
1695 "\n");
1696 RTPrintf("Misc Options:\n"
1697 " --owner <uid/username> (-A, -c, -d, -r, -u, -x)\n"
1698 " Set the owner of extracted and archived files to the user specified.\n"
1699 " --group <uid/username> (-A, -c, -d, -r, -u, -x)\n"
1700 " Set the group of extracted and archived files to the group specified.\n"
1701 " --utc (-t)\n"
1702 " Display timestamps as UTC instead of local time.\n"
1703 " -S, --sparse (-A, -c, -u)\n"
1704 " Detect sparse files and store them (gnu tar extension).\n"
1705 " --format <format> (-A, -c, -u, but also -d, -r, -x)\n"
1706 " The file format:\n"
1707 " auto (gnu tar)\n"
1708 " default (gnu tar)\n"
1709 " tar (gnu tar)"
1710 " gnu (tar v1.13+), "
1711 " ustar (tar POSIX.1-1988), "
1712 " pax (tar POSIX.1-2001),\n"
1713 " xar\n"
1714 " cpio\n"
1715 " Note! Because XAR/TAR/CPIO detection isn't implemented yet, it\n"
1716 " is necessary to specifcy --format=xar when reading a\n"
1717 " XAR file or --format=cpio for a CPIO file.\n"
1718 " Otherwise this option is only for creation.\n"
1719 "\n");
1720 RTPrintf("IPRT Options:\n"
1721 " --prefix <dir-prefix> (-A, -c, -d, -r, -u)\n"
1722 " Directory prefix to give the members added to the archive.\n"
1723 " --file-mode-and-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
1724 " Restrict the access mode of regular and special files.\n"
1725 " --file-mode-or-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
1726 " Include the given access mode for regular and special files.\n"
1727 " --dir-mode-and-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
1728 " Restrict the access mode of directories.\n"
1729 " --dir-mode-or-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
1730 " Include the given access mode for directories.\n"
1731 " --read-ahead (-x)\n"
1732 " Enabled read ahead thread when extracting files.\n"
1733 " --push-file (-A, -c, -u)\n"
1734 " Use RTVfsFsStrmPushFile instead of RTVfsFsStrmAdd.\n"
1735 "\n");
1736 RTPrintf("Standard Options:\n"
1737 " -h, -?, --help\n"
1738 " Display this help text.\n"
1739 " -V, --version\n"
1740 " Display version number.\n");
1741}
1742
1743
1744RTDECL(RTEXITCODE) RTZipTarCmd(unsigned cArgs, char **papszArgs)
1745{
1746 /*
1747 * Parse the command line.
1748 *
1749 * N.B. This is less flexible that your regular tar program in that it
1750 * requires the operation to be specified as an option. On the other
1751 * hand, you can specify it where ever you like in the command line.
1752 */
1753 static const RTGETOPTDEF s_aOptions[] =
1754 {
1755 /* operations */
1756 { "--concatenate", 'A', RTGETOPT_REQ_NOTHING },
1757 { "--catenate", 'A', RTGETOPT_REQ_NOTHING },
1758 { "--create", 'c', RTGETOPT_REQ_NOTHING },
1759 { "--diff", 'd', RTGETOPT_REQ_NOTHING },
1760 { "--compare", 'd', RTGETOPT_REQ_NOTHING },
1761 { "--append", 'r', RTGETOPT_REQ_NOTHING },
1762 { "--list", 't', RTGETOPT_REQ_NOTHING },
1763 { "--update", 'u', RTGETOPT_REQ_NOTHING },
1764 { "--extract", 'x', RTGETOPT_REQ_NOTHING },
1765 { "--get", 'x', RTGETOPT_REQ_NOTHING },
1766 { "--delete", RTZIPTARCMD_OPT_DELETE, RTGETOPT_REQ_NOTHING },
1767
1768 /* basic options */
1769 { "--directory", 'C', RTGETOPT_REQ_STRING },
1770 { "--file", 'f', RTGETOPT_REQ_STRING },
1771 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
1772 { "--preserve-permissions", 'p', RTGETOPT_REQ_NOTHING },
1773 { "--bzip2", 'j', RTGETOPT_REQ_NOTHING },
1774 { "--gzip", 'z', RTGETOPT_REQ_NOTHING },
1775 { "--gunzip", 'z', RTGETOPT_REQ_NOTHING },
1776 { "--ungzip", 'z', RTGETOPT_REQ_NOTHING },
1777#ifdef IPRT_WITH_LZMA
1778 { "--xz", 'J', RTGETOPT_REQ_NOTHING },
1779#endif
1780
1781 /* other options. */
1782 { "--owner", RTZIPTARCMD_OPT_OWNER, RTGETOPT_REQ_STRING },
1783 { "--group", RTZIPTARCMD_OPT_GROUP, RTGETOPT_REQ_STRING },
1784 { "--utc", RTZIPTARCMD_OPT_UTC, RTGETOPT_REQ_NOTHING },
1785 { "--sparse", 'S', RTGETOPT_REQ_NOTHING },
1786 { "--format", RTZIPTARCMD_OPT_FORMAT, RTGETOPT_REQ_STRING },
1787 { "--no-recursion", RTZIPTARCMD_OPT_NO_RECURSION, RTGETOPT_REQ_NOTHING },
1788
1789 /* IPRT extensions */
1790 { "--prefix", RTZIPTARCMD_OPT_PREFIX, RTGETOPT_REQ_STRING },
1791 { "--file-mode-and-mask", RTZIPTARCMD_OPT_FILE_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
1792 { "--file-mode-or-mask", RTZIPTARCMD_OPT_FILE_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
1793 { "--dir-mode-and-mask", RTZIPTARCMD_OPT_DIR_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
1794 { "--dir-mode-or-mask", RTZIPTARCMD_OPT_DIR_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
1795 { "--read-ahead", RTZIPTARCMD_OPT_READ_AHEAD, RTGETOPT_REQ_NOTHING },
1796 { "--use-push-file", RTZIPTARCMD_OPT_USE_PUSH_FILE, RTGETOPT_REQ_NOTHING },
1797 };
1798
1799 RTGETOPTSTATE GetState;
1800 int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
1801 RTGETOPTINIT_FLAGS_OPTS_FIRST);
1802 if (RT_FAILURE(rc))
1803 return RTMsgErrorExitFailure("RTGetOpt failed: %Rrc", rc);
1804
1805 RTZIPTARCMDOPS Opts;
1806 RT_ZERO(Opts);
1807 Opts.enmFormat = RTZIPTARCMDFORMAT_AUTO_DEFAULT;
1808 Opts.uidOwner = NIL_RTUID;
1809 Opts.gidGroup = NIL_RTUID;
1810 Opts.fFileModeAndMask = RTFS_UNIX_ALL_ACCESS_PERMS;
1811 Opts.fDirModeAndMask = RTFS_UNIX_ALL_ACCESS_PERMS;
1812#if 0
1813 if (RTPermIsSuperUser())
1814 {
1815 Opts.fFileModeAndMask = RTFS_UNIX_ALL_PERMS;
1816 Opts.fDirModeAndMask = RTFS_UNIX_ALL_PERMS;
1817 Opts.fPreserveOwner = true;
1818 Opts.fPreserveGroup = true;
1819 }
1820#endif
1821 Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
1822 Opts.fRecursive = true; /* Recursion is implicit unless otherwise specified. */
1823
1824 RTGETOPTUNION ValueUnion;
1825 while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
1826 && rc != VINF_GETOPT_NOT_OPTION)
1827 {
1828 switch (rc)
1829 {
1830 /* operations */
1831 case 'A':
1832 case 'c':
1833 case 'd':
1834 case 'r':
1835 case 't':
1836 case 'u':
1837 case 'x':
1838 case RTZIPTARCMD_OPT_DELETE:
1839 if (Opts.iOperation)
1840 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Conflicting tar operation (%s already set, now %s)",
1841 Opts.pszOperation, ValueUnion.pDef->pszLong);
1842 Opts.iOperation = rc;
1843 Opts.pszOperation = ValueUnion.pDef->pszLong;
1844 break;
1845
1846 /* basic options */
1847 case 'C':
1848 if (Opts.pszDirectory)
1849 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -C/--directory once");
1850 Opts.pszDirectory = ValueUnion.psz;
1851 break;
1852
1853 case 'f':
1854 if (Opts.pszFile)
1855 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -f/--file once");
1856 Opts.pszFile = ValueUnion.psz;
1857 break;
1858
1859 case 'v':
1860 Opts.fVerbose = true;
1861 break;
1862
1863 case 'p':
1864 Opts.fFileModeAndMask = RTFS_UNIX_ALL_PERMS;
1865 Opts.fDirModeAndMask = RTFS_UNIX_ALL_PERMS;
1866 Opts.fPreserveOwner = true;
1867 Opts.fPreserveGroup = true;
1868 break;
1869
1870 case 'j':
1871 case 'z':
1872#ifdef IPRT_WITH_LZMA
1873 case 'J':
1874#endif
1875 if (Opts.chZipper)
1876 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify one compressor / decompressor");
1877 Opts.chZipper = rc;
1878 break;
1879
1880 case RTZIPTARCMD_OPT_OWNER:
1881 if (Opts.pszOwner)
1882 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --owner once");
1883 Opts.pszOwner = ValueUnion.psz;
1884
1885 rc = RTStrToUInt32Full(Opts.pszOwner, 0, &ValueUnion.u32);
1886 if (RT_SUCCESS(rc) && rc != VINF_SUCCESS)
1887 return RTMsgErrorExit(RTEXITCODE_SYNTAX,
1888 "Error convering --owner '%s' into a number: %Rrc", Opts.pszOwner, rc);
1889 if (RT_SUCCESS(rc))
1890 {
1891 Opts.uidOwner = ValueUnion.u32;
1892 Opts.pszOwner = NULL;
1893 }
1894 break;
1895
1896 case RTZIPTARCMD_OPT_GROUP:
1897 if (Opts.pszGroup)
1898 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --group once");
1899 Opts.pszGroup = ValueUnion.psz;
1900
1901 rc = RTStrToUInt32Full(Opts.pszGroup, 0, &ValueUnion.u32);
1902 if (RT_SUCCESS(rc) && rc != VINF_SUCCESS)
1903 return RTMsgErrorExit(RTEXITCODE_SYNTAX,
1904 "Error convering --group '%s' into a number: %Rrc", Opts.pszGroup, rc);
1905 if (RT_SUCCESS(rc))
1906 {
1907 Opts.gidGroup = ValueUnion.u32;
1908 Opts.pszGroup = NULL;
1909 }
1910 break;
1911
1912 case RTZIPTARCMD_OPT_UTC:
1913 Opts.fDisplayUtc = true;
1914 break;
1915
1916 case RTZIPTARCMD_OPT_NO_RECURSION:
1917 Opts.fRecursive = false;
1918 break;
1919
1920 /* GNU */
1921 case 'S':
1922 Opts.fTarCreate |= RTZIPTAR_C_SPARSE;
1923 break;
1924
1925 /* iprt extensions */
1926 case RTZIPTARCMD_OPT_PREFIX:
1927 if (Opts.pszPrefix)
1928 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --prefix once");
1929 Opts.pszPrefix = ValueUnion.psz;
1930 break;
1931
1932 case RTZIPTARCMD_OPT_FILE_MODE_AND_MASK:
1933 Opts.fFileModeAndMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
1934 break;
1935
1936 case RTZIPTARCMD_OPT_FILE_MODE_OR_MASK:
1937 Opts.fFileModeOrMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
1938 break;
1939
1940 case RTZIPTARCMD_OPT_DIR_MODE_AND_MASK:
1941 Opts.fDirModeAndMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
1942 break;
1943
1944 case RTZIPTARCMD_OPT_DIR_MODE_OR_MASK:
1945 Opts.fDirModeOrMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
1946 break;
1947
1948 case RTZIPTARCMD_OPT_FORMAT:
1949 if (!strcmp(ValueUnion.psz, "auto") || !strcmp(ValueUnion.psz, "default"))
1950 {
1951 Opts.enmFormat = RTZIPTARCMDFORMAT_AUTO_DEFAULT;
1952 Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
1953 }
1954 else if (!strcmp(ValueUnion.psz, "tar"))
1955 {
1956 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
1957 Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
1958 }
1959 else if (!strcmp(ValueUnion.psz, "gnu"))
1960 {
1961 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
1962 Opts.enmTarFormat = RTZIPTARFORMAT_GNU;
1963 }
1964 else if (!strcmp(ValueUnion.psz, "ustar"))
1965 {
1966 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
1967 Opts.enmTarFormat = RTZIPTARFORMAT_USTAR;
1968 }
1969 else if ( !strcmp(ValueUnion.psz, "posix")
1970 || !strcmp(ValueUnion.psz, "pax"))
1971 {
1972 Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
1973 Opts.enmTarFormat = RTZIPTARFORMAT_PAX;
1974 }
1975 else if (!strcmp(ValueUnion.psz, "xar"))
1976 Opts.enmFormat = RTZIPTARCMDFORMAT_XAR;
1977 else if (!strcmp(ValueUnion.psz, "cpio"))
1978 {
1979 Opts.enmFormat = RTZIPTARCMDFORMAT_CPIO;
1980 Opts.enmTarFormat = RTZIPTARFORMAT_CPIO_ASCII_NEW;
1981 }
1982 else
1983 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown archive format: '%s'", ValueUnion.psz);
1984 break;
1985
1986 case RTZIPTARCMD_OPT_READ_AHEAD:
1987 Opts.fReadAhead = true;
1988 break;
1989
1990 case RTZIPTARCMD_OPT_USE_PUSH_FILE:
1991 Opts.fUsePushFile = true;
1992 break;
1993
1994 /* Standard bits. */
1995 case 'h':
1996 rtZipTarUsage(RTPathFilename(papszArgs[0]));
1997 return RTEXITCODE_SUCCESS;
1998
1999 case 'V':
2000 RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
2001 return RTEXITCODE_SUCCESS;
2002
2003 default:
2004 return RTGetOptPrintError(rc, &ValueUnion);
2005 }
2006 }
2007
2008 if (rc == VINF_GETOPT_NOT_OPTION)
2009 {
2010 /* this is kind of ugly. */
2011 Assert((unsigned)GetState.iNext - 1 <= cArgs);
2012 Opts.papszFiles = (const char * const *)&papszArgs[GetState.iNext - 1];
2013 Opts.cFiles = cArgs - GetState.iNext + 1;
2014 }
2015
2016 if (!Opts.pszFile)
2017 return RTMsgErrorExitFailure("No archive specified");
2018
2019 /*
2020 * Post proceess the options.
2021 */
2022 if (Opts.iOperation == 0)
2023 {
2024 Opts.iOperation = 't';
2025 Opts.pszOperation = "--list";
2026 }
2027
2028 if ( Opts.iOperation == 'x'
2029 && Opts.pszOwner)
2030 return RTMsgErrorExitFailure("The use of --owner with %s has not implemented yet", Opts.pszOperation);
2031
2032 if ( Opts.iOperation == 'x'
2033 && Opts.pszGroup)
2034 return RTMsgErrorExitFailure("The use of --group with %s has not implemented yet", Opts.pszOperation);
2035
2036 /*
2037 * Do the job.
2038 */
2039 switch (Opts.iOperation)
2040 {
2041 case 't':
2042 return rtZipTarDoWithMembers(&Opts, rtZipTarCmdListCallback);
2043
2044 case 'x':
2045 return rtZipTarDoWithMembers(&Opts, rtZipTarCmdExtractCallback);
2046
2047 case 'c':
2048 return rtZipTarCreate(&Opts);
2049
2050 case 'A':
2051 case 'd':
2052 case 'r':
2053 case 'u':
2054 case RTZIPTARCMD_OPT_DELETE:
2055 return RTMsgErrorExitFailure("The operation %s is not implemented yet", Opts.pszOperation);
2056
2057 default:
2058 return RTMsgErrorExitFailure("Internal error");
2059 }
2060}
2061
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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