VirtualBox

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

Last change on this file since 51696 was 51696, checked in by vboxsync, 11 years ago

Runtime: PKZip stream reader

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