VirtualBox

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

Last change on this file was 98103, checked in by vboxsync, 16 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.6 KB
Line 
1/* $Id: unzipcmd.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * IPRT - A mini UNZIP Command.
4 */
5
6/*
7 * Copyright (C) 2014-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#include <iprt/zip.h>
42#include <iprt/asm.h>
43#include <iprt/getopt.h>
44#include <iprt/file.h>
45#include <iprt/err.h>
46#include <iprt/mem.h>
47#include <iprt/message.h>
48#include <iprt/path.h>
49#include <iprt/string.h>
50#include <iprt/vfs.h>
51#include <iprt/stream.h>
52
53
54/*********************************************************************************************************************************
55* Structures and Typedefs *
56*********************************************************************************************************************************/
57
58/**
59 * IPRT UNZIP option structure.
60 */
61typedef struct RTZIPUNZIPCMDOPS
62{
63 /** The operation. */
64 int iOperation;
65 /** The long operation option name. */
66 const char *pszOperation;
67 /** The directory to change into when upacking. */
68 const char *pszDirectory;
69 /** The unzip file name. */
70 const char *pszFile;
71 /** The number of files/directories to be extracted from archive specified. */
72 uint32_t cFiles;
73 /** Wether we're verbose or quiet. */
74 bool fVerbose;
75 /** Skip the restauration of the modification time for directories. */
76 bool fNoModTimeDirectories;
77 /** Skip the restauration of the modification time for files. */
78 bool fNoModTimeFiles;
79 /** Array of files/directories, terminated by a NULL entry. */
80 const char * const *papszFiles;
81} RTZIPUNZIPCMDOPS;
82/** Pointer to the UNZIP options. */
83typedef RTZIPUNZIPCMDOPS *PRTZIPUNZIPCMDOPS;
84
85/**
86 * Callback used by rtZipUnzipDoWithMembers
87 *
88 * @returns rcExit or RTEXITCODE_FAILURE.
89 * @param pOpts The Unzip options.
90 * @param hVfsObj The Unzip object to display
91 * @param pszName The name.
92 * @param rcExit The current exit code.
93 */
94typedef RTEXITCODE (*PFNDOWITHMEMBER)(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit, PRTFOFF pcBytes);
95
96
97/**
98 *
99 */
100static RTEXITCODE rtZipUnzipCmdListCallback(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj,
101 const char *pszName, RTEXITCODE rcExit, PRTFOFF pcBytes)
102{
103 RT_NOREF_PV(pOpts);
104
105 /*
106 * Query all the information.
107 */
108 RTFSOBJINFO UnixInfo;
109 int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
110 if (RT_FAILURE(rc))
111 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
112
113 RTTIME time;
114 if (!RTTimeExplode(&time, &UnixInfo.ModificationTime))
115 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot explode time on '%s'", pszName);
116
117 RTPrintf("%9RU64 %04d-%02d-%02d %02d:%02d %s\n",
118 UnixInfo.cbObject,
119 time.i32Year, time.u8Month, time.u8MonthDay,
120 time.u8Hour, time.u8Minute,
121 pszName);
122
123 *pcBytes = UnixInfo.cbObject;
124 return rcExit;
125}
126
127
128/**
129 * Extracts a file.
130 */
131static RTEXITCODE rtZipUnzipCmdExtractFile(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj, RTEXITCODE rcExit,
132 const char *pszDst, PCRTFSOBJINFO pUnixInfo)
133{
134 /*
135 * Open the destination file and create a stream object for it.
136 */
137 uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT
138 | (pUnixInfo->Attr.fMode << RTFILE_O_CREATE_MODE_SHIFT);
139 RTFILE hFile;
140 int rc = RTFileOpen(&hFile, pszDst, fOpen);
141 if (RT_FAILURE(rc))
142 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating file: %Rrc", pszDst, rc);
143
144 RTVFSIOSTREAM hVfsIosDst;
145 rc = RTVfsIoStrmFromRTFile(hFile, fOpen, true /*fLeaveOpen*/, &hVfsIosDst);
146 if (RT_SUCCESS(rc))
147 {
148 /*
149 * Pump the data thru.
150 */
151 RTVFSIOSTREAM hVfsIosSrc = RTVfsObjToIoStream(hVfsObj);
152 rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(pUnixInfo->cbObject, _1M));
153 if (RT_SUCCESS(rc))
154 {
155 /*
156 * Correct the file mode and other attributes.
157 */
158 if (!pOpts->fNoModTimeFiles)
159 {
160 rc = RTFileSetTimes(hFile, NULL, &pUnixInfo->ModificationTime, NULL, NULL);
161 if (RT_FAILURE(rc))
162 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error setting times: %Rrc", pszDst, rc);
163 }
164 }
165 else
166 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error writing out file: %Rrc", pszDst, rc);
167 RTVfsIoStrmRelease(hVfsIosSrc);
168 RTVfsIoStrmRelease(hVfsIosDst);
169 }
170 else
171 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating I/O stream for file: %Rrc", pszDst, rc);
172
173 return rcExit;
174}
175
176
177/**
178 *
179 */
180static RTEXITCODE rtZipUnzipCmdExtractCallback(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj,
181 const char *pszName, RTEXITCODE rcExit, PRTFOFF pcBytes)
182{
183 if (pOpts->fVerbose)
184 RTPrintf("%s\n", pszName);
185
186 /*
187 * Query all the information.
188 */
189 RTFSOBJINFO UnixInfo;
190 int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
191 if (RT_FAILURE(rc))
192 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
193
194 *pcBytes = UnixInfo.cbObject;
195
196 char szDst[RTPATH_MAX];
197 rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszName);
198 if (RT_FAILURE(rc))
199 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Failed to construct destination path for: %Rrc", pszName, rc);
200
201 /*
202 * Extract according to the type.
203 */
204 switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
205 {
206 case RTFS_TYPE_FILE:
207 return rtZipUnzipCmdExtractFile(pOpts, hVfsObj, rcExit, szDst, &UnixInfo);
208
209 case RTFS_TYPE_DIRECTORY:
210 rc = RTDirCreateFullPath(szDst, UnixInfo.Attr.fMode & RTFS_UNIX_ALL_ACCESS_PERMS);
211 if (RT_FAILURE(rc))
212 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating directory: %Rrc", szDst, rc);
213 break;
214
215 default:
216 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Unknown file type.", pszName);
217 }
218
219 if (!pOpts->fNoModTimeDirectories)
220 {
221 rc = RTPathSetTimesEx(szDst, NULL, &UnixInfo.ModificationTime, NULL, NULL, RTPATH_F_ON_LINK);
222 if (RT_FAILURE(rc) && rc != VERR_NOT_SUPPORTED && rc != VERR_NS_SYMLINK_SET_TIME)
223 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error changing modification time: %Rrc.", pszName, rc);
224 }
225
226 return rcExit;
227}
228
229
230/**
231 * Checks if @a pszName is a member of @a papszNames, optionally returning the
232 * index.
233 *
234 * @returns true if the name is in the list, otherwise false.
235 * @param pszName The name to find.
236 * @param papszNames The array of names.
237 * @param piName Where to optionally return the array index.
238 */
239static bool rtZipUnzipCmdIsNameInArray(const char *pszName, const char * const *papszNames, uint32_t *piName)
240{
241 for (uint32_t iName = 0; papszNames[iName]; ++iName)
242 if (!strcmp(papszNames[iName], pszName))
243 {
244 if (piName)
245 *piName = iName;
246 return true;
247 }
248 return false;
249}
250
251
252/**
253 * Opens the input archive specified by the options.
254 *
255 * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
256 * @param pOpts The options.
257 * @param phVfsFss Where to return the UNZIP filesystem stream handle.
258 */
259static RTEXITCODE rtZipUnzipCmdOpenInputArchive(PRTZIPUNZIPCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss)
260{
261 /*
262 * Open the input file.
263 */
264 RTVFSIOSTREAM hVfsIos;
265 uint32_t offError = 0;
266 RTERRINFOSTATIC ErrInfo;
267 int rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
268 &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
269 if (RT_FAILURE(rc))
270 return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core);
271
272 rc = RTZipPkzipFsStreamFromIoStream(hVfsIos, 0 /*fFlags*/, phVfsFss);
273 RTVfsIoStrmRelease(hVfsIos);
274 if (RT_FAILURE(rc))
275 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open pkzip filesystem stream: %Rrc", rc);
276
277 return RTEXITCODE_SUCCESS;
278}
279
280
281/**
282 * Worker for the --list and --extract commands.
283 *
284 * @returns The appropriate exit code.
285 * @param pOpts The Unzip options.
286 * @param pfnCallback The command specific callback.
287 */
288static RTEXITCODE rtZipUnzipDoWithMembers(PRTZIPUNZIPCMDOPS pOpts, PFNDOWITHMEMBER pfnCallback,
289 uint32_t *pcFiles, PRTFOFF pcBytes)
290{
291 /*
292 * Allocate a bitmap to go with the file list. This will be used to
293 * indicate which files we've processed and which not.
294 */
295 uint32_t *pbmFound = NULL;
296 if (pOpts->cFiles)
297 {
298 pbmFound = (uint32_t *)RTMemAllocZ(((pOpts->cFiles + 31) / 32) * sizeof(uint32_t));
299 if (!pbmFound)
300 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to allocate the found-file-bitmap");
301 }
302
303 uint32_t cFiles = 0;
304 RTFOFF cBytesSum = 0;
305
306 /*
307 * Open the input archive.
308 */
309 RTVFSFSSTREAM hVfsFssIn;
310 RTEXITCODE rcExit = rtZipUnzipCmdOpenInputArchive(pOpts, &hVfsFssIn);
311 if (rcExit == RTEXITCODE_SUCCESS)
312 {
313 /*
314 * Process the stream.
315 */
316 for (;;)
317 {
318 /*
319 * Retrieve the next object.
320 */
321 char *pszName;
322 RTVFSOBJ hVfsObj;
323 int rc = RTVfsFsStrmNext(hVfsFssIn, &pszName, NULL, &hVfsObj);
324 if (RT_FAILURE(rc))
325 {
326 if (rc != VERR_EOF)
327 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext returned %Rrc", rc);
328 break;
329 }
330
331 /*
332 * Should we process this object?
333 */
334 uint32_t iFile = UINT32_MAX;
335 if ( !pOpts->cFiles
336 || rtZipUnzipCmdIsNameInArray(pszName, pOpts->papszFiles, &iFile))
337 {
338 if (pbmFound)
339 ASMBitSet(pbmFound, iFile);
340
341 RTFOFF cBytes = 0;
342 rcExit = pfnCallback(pOpts, hVfsObj, pszName, rcExit, &cBytes);
343
344 cBytesSum += cBytes;
345 cFiles++;
346 }
347
348 /*
349 * Release the current object and string.
350 */
351 RTVfsObjRelease(hVfsObj);
352 RTStrFree(pszName);
353 }
354
355 /*
356 * Complain about any files we didn't find.
357 */
358 for (uint32_t iFile = 0; iFile <pOpts->cFiles; iFile++)
359 if (!ASMBitTest(pbmFound, iFile))
360 {
361 RTMsgError("%s: Was not found in the archive", pOpts->papszFiles[iFile]);
362 rcExit = RTEXITCODE_FAILURE;
363 }
364
365 RTVfsFsStrmRelease(hVfsFssIn);
366 }
367
368 RTMemFree(pbmFound);
369
370 *pcFiles = cFiles;
371 *pcBytes = cBytesSum;
372
373 return RTEXITCODE_SUCCESS;
374}
375
376
377RTDECL(RTEXITCODE) RTZipUnzipCmd(unsigned cArgs, char **papszArgs)
378{
379 /*
380 * Parse the command line.
381 */
382 static const RTGETOPTDEF s_aOptions[] =
383 {
384 /* options */
385 { NULL, 'c', RTGETOPT_REQ_NOTHING }, /* extract files to stdout/stderr */
386 { NULL, 'd', RTGETOPT_REQ_STRING }, /* extract files to this directory */
387 { NULL, 'l', RTGETOPT_REQ_NOTHING }, /* list archive files (short format) */
388 { NULL, 'p', RTGETOPT_REQ_NOTHING }, /* extract files to stdout */
389 { NULL, 't', RTGETOPT_REQ_NOTHING }, /* test archive files */
390 { NULL, 'v', RTGETOPT_REQ_NOTHING }, /* verbose */
391
392 /* modifiers */
393 { NULL, 'a', RTGETOPT_REQ_NOTHING }, /* convert text files */
394 { NULL, 'b', RTGETOPT_REQ_NOTHING }, /* no conversion, treat as binary */
395 { NULL, 'D', RTGETOPT_REQ_NOTHING }, /* don't restore timestamps for directories
396 (and files) */
397 };
398
399 RTGETOPTSTATE GetState;
400 int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
401 RTGETOPTINIT_FLAGS_OPTS_FIRST);
402 if (RT_FAILURE(rc))
403 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc);
404
405 RTZIPUNZIPCMDOPS Opts;
406 RT_ZERO(Opts);
407
408 RTGETOPTUNION ValueUnion;
409 while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
410 && rc != VINF_GETOPT_NOT_OPTION)
411 {
412 switch (rc)
413 {
414 case 'd':
415 if (Opts.pszDirectory)
416 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -d once");
417 Opts.pszDirectory = ValueUnion.psz;
418 break;
419
420 case 'D':
421 if (!Opts.fNoModTimeDirectories)
422 Opts.fNoModTimeDirectories = true; /* -D */
423 else
424 Opts.fNoModTimeFiles = true; /* -DD */
425 break;
426
427 case 'l':
428 case 't': /* treat 'test' like 'list' */
429 if (Opts.iOperation)
430 return RTMsgErrorExit(RTEXITCODE_SYNTAX,
431 "Conflicting unzip operation (%s already set, now %s)",
432 Opts.pszOperation, ValueUnion.pDef->pszLong);
433 Opts.iOperation = 'l';
434 Opts.pszOperation = ValueUnion.pDef->pszLong;
435 break;
436
437 case 'v':
438 Opts.fVerbose = true;
439 break;
440
441 default:
442 Opts.pszFile = ValueUnion.psz;
443 return RTGetOptPrintError(rc, &ValueUnion);
444 }
445 }
446
447 if (rc == VINF_GETOPT_NOT_OPTION)
448 {
449 Assert((unsigned)GetState.iNext - 1 <= cArgs);
450 Opts.pszFile = papszArgs[GetState.iNext - 1];
451 if ((unsigned)GetState.iNext <= cArgs)
452 {
453 Opts.papszFiles = (const char * const *)&papszArgs[GetState.iNext];
454 Opts.cFiles = cArgs - GetState.iNext;
455 }
456 }
457
458 if (!Opts.pszFile)
459 return RTMsgErrorExit(RTEXITCODE_FAILURE, "No input archive specified");
460
461 RTFOFF cBytes = 0;
462 uint32_t cFiles = 0;
463 switch (Opts.iOperation)
464 {
465 case 'l':
466 {
467 RTPrintf(" Length Date Time Name\n"
468 "--------- ---------- ----- ----\n");
469 RTEXITCODE rcExit = rtZipUnzipDoWithMembers(&Opts, rtZipUnzipCmdListCallback, &cFiles, &cBytes);
470 RTPrintf("--------- -------\n"
471 "%9RU64 %u file%s\n",
472 cBytes, cFiles, cFiles != 1 ? "s" : "");
473
474 return rcExit;
475 }
476
477 default:
478 return rtZipUnzipDoWithMembers(&Opts, rtZipUnzipCmdExtractCallback, &cFiles, &cBytes);
479 }
480}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use