VirtualBox

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

Last change on this file since 73516 was 72034, checked in by vboxsync, 7 years ago

unzipcmd.cpp: fix for -t.

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