VirtualBox

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

Last change on this file since 78306 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

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

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