diff options
Diffstat (limited to 'src/VBox/Runtime/common/zip/unzipcmd.cpp')
-rw-r--r-- | src/VBox/Runtime/common/zip/unzipcmd.cpp | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/zip/unzipcmd.cpp b/src/VBox/Runtime/common/zip/unzipcmd.cpp new file mode 100644 index 00000000..58d70289 --- /dev/null +++ b/src/VBox/Runtime/common/zip/unzipcmd.cpp @@ -0,0 +1,480 @@ +/* $Id: unzipcmd.cpp $ */ +/** @file + * IPRT - A mini UNZIP Command. + */ + +/* + * Copyright (C) 2014-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/zip.h> +#include <iprt/asm.h> +#include <iprt/getopt.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/vfs.h> +#include <iprt/stream.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * IPRT UNZIP option structure. + */ +typedef struct RTZIPUNZIPCMDOPS +{ + /** The operation. */ + int iOperation; + /** The long operation option name. */ + const char *pszOperation; + /** The directory to change into when upacking. */ + const char *pszDirectory; + /** The unzip file name. */ + const char *pszFile; + /** The number of files/directories to be extracted from archive specified. */ + uint32_t cFiles; + /** Wether we're verbose or quiet. */ + bool fVerbose; + /** Skip the restauration of the modification time for directories. */ + bool fNoModTimeDirectories; + /** Skip the restauration of the modification time for files. */ + bool fNoModTimeFiles; + /** Array of files/directories, terminated by a NULL entry. */ + const char * const *papszFiles; +} RTZIPUNZIPCMDOPS; +/** Pointer to the UNZIP options. */ +typedef RTZIPUNZIPCMDOPS *PRTZIPUNZIPCMDOPS; + +/** + * Callback used by rtZipUnzipDoWithMembers + * + * @returns rcExit or RTEXITCODE_FAILURE. + * @param pOpts The Unzip options. + * @param hVfsObj The Unzip object to display + * @param pszName The name. + * @param rcExit The current exit code. + */ +typedef RTEXITCODE (*PFNDOWITHMEMBER)(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit, PRTFOFF pcBytes); + + +/** + * + */ +static RTEXITCODE rtZipUnzipCmdListCallback(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj, + const char *pszName, RTEXITCODE rcExit, PRTFOFF pcBytes) +{ + RT_NOREF_PV(pOpts); + + /* + * Query all the information. + */ + RTFSOBJINFO UnixInfo; + int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName); + + RTTIME time; + if (!RTTimeExplode(&time, &UnixInfo.ModificationTime)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot explode time on '%s'", pszName); + + RTPrintf("%9RU64 %04d-%02d-%02d %02d:%02d %s\n", + UnixInfo.cbObject, + time.i32Year, time.u8Month, time.u8MonthDay, + time.u8Hour, time.u8Minute, + pszName); + + *pcBytes = UnixInfo.cbObject; + return rcExit; +} + + +/** + * Extracts a file. + */ +static RTEXITCODE rtZipUnzipCmdExtractFile(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj, RTEXITCODE rcExit, + const char *pszDst, PCRTFSOBJINFO pUnixInfo) +{ + /* + * Open the destination file and create a stream object for it. + */ + uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT + | (pUnixInfo->Attr.fMode << RTFILE_O_CREATE_MODE_SHIFT); + RTFILE hFile; + int rc = RTFileOpen(&hFile, pszDst, fOpen); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating file: %Rrc", pszDst, rc); + + RTVFSIOSTREAM hVfsIosDst; + rc = RTVfsIoStrmFromRTFile(hFile, fOpen, true /*fLeaveOpen*/, &hVfsIosDst); + if (RT_SUCCESS(rc)) + { + /* + * Pump the data thru. + */ + RTVFSIOSTREAM hVfsIosSrc = RTVfsObjToIoStream(hVfsObj); + rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(pUnixInfo->cbObject, _1M)); + if (RT_SUCCESS(rc)) + { + /* + * Correct the file mode and other attributes. + */ + if (!pOpts->fNoModTimeFiles) + { + rc = RTFileSetTimes(hFile, NULL, &pUnixInfo->ModificationTime, NULL, NULL); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error setting times: %Rrc", pszDst, rc); + } + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error writing out file: %Rrc", pszDst, rc); + RTVfsIoStrmRelease(hVfsIosSrc); + RTVfsIoStrmRelease(hVfsIosDst); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating I/O stream for file: %Rrc", pszDst, rc); + + return rcExit; +} + + +/** + * + */ +static RTEXITCODE rtZipUnzipCmdExtractCallback(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj, + const char *pszName, RTEXITCODE rcExit, PRTFOFF pcBytes) +{ + if (pOpts->fVerbose) + RTPrintf("%s\n", pszName); + + /* + * Query all the information. + */ + RTFSOBJINFO UnixInfo; + int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName); + + *pcBytes = UnixInfo.cbObject; + + char szDst[RTPATH_MAX]; + rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszName); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Failed to construct destination path for: %Rrc", pszName, rc); + + /* + * Extract according to the type. + */ + switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_FILE: + return rtZipUnzipCmdExtractFile(pOpts, hVfsObj, rcExit, szDst, &UnixInfo); + + case RTFS_TYPE_DIRECTORY: + rc = RTDirCreateFullPath(szDst, UnixInfo.Attr.fMode & RTFS_UNIX_ALL_ACCESS_PERMS); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating directory: %Rrc", szDst, rc); + break; + + default: + return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Unknown file type.", pszName); + } + + if (!pOpts->fNoModTimeDirectories) + { + rc = RTPathSetTimesEx(szDst, NULL, &UnixInfo.ModificationTime, NULL, NULL, RTPATH_F_ON_LINK); + if (RT_FAILURE(rc) && rc != VERR_NOT_SUPPORTED && rc != VERR_NS_SYMLINK_SET_TIME) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error changing modification time: %Rrc.", pszName, rc); + } + + return rcExit; +} + + +/** + * Checks if @a pszName is a member of @a papszNames, optionally returning the + * index. + * + * @returns true if the name is in the list, otherwise false. + * @param pszName The name to find. + * @param papszNames The array of names. + * @param piName Where to optionally return the array index. + */ +static bool rtZipUnzipCmdIsNameInArray(const char *pszName, const char * const *papszNames, uint32_t *piName) +{ + for (uint32_t iName = 0; papszNames[iName]; ++iName) + if (!strcmp(papszNames[iName], pszName)) + { + if (piName) + *piName = iName; + return true; + } + return false; +} + + +/** + * Opens the input archive specified by the options. + * + * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message. + * @param pOpts The options. + * @param phVfsFss Where to return the UNZIP filesystem stream handle. + */ +static RTEXITCODE rtZipUnzipCmdOpenInputArchive(PRTZIPUNZIPCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss) +{ + /* + * Open the input file. + */ + RTVFSIOSTREAM hVfsIos; + uint32_t offError = 0; + RTERRINFOSTATIC ErrInfo; + int rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, + &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core); + + rc = RTZipPkzipFsStreamFromIoStream(hVfsIos, 0 /*fFlags*/, phVfsFss); + RTVfsIoStrmRelease(hVfsIos); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open pkzip filesystem stream: %Rrc", rc); + + return RTEXITCODE_SUCCESS; +} + + +/** + * Worker for the --list and --extract commands. + * + * @returns The appropriate exit code. + * @param pOpts The Unzip options. + * @param pfnCallback The command specific callback. + */ +static RTEXITCODE rtZipUnzipDoWithMembers(PRTZIPUNZIPCMDOPS pOpts, PFNDOWITHMEMBER pfnCallback, + uint32_t *pcFiles, PRTFOFF pcBytes) +{ + /* + * Allocate a bitmap to go with the file list. This will be used to + * indicate which files we've processed and which not. + */ + uint32_t *pbmFound = NULL; + if (pOpts->cFiles) + { + pbmFound = (uint32_t *)RTMemAllocZ(((pOpts->cFiles + 31) / 32) * sizeof(uint32_t)); + if (!pbmFound) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to allocate the found-file-bitmap"); + } + + uint32_t cFiles = 0; + RTFOFF cBytesSum = 0; + + /* + * Open the input archive. + */ + RTVFSFSSTREAM hVfsFssIn; + RTEXITCODE rcExit = rtZipUnzipCmdOpenInputArchive(pOpts, &hVfsFssIn); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Process the stream. + */ + for (;;) + { + /* + * Retrieve the next object. + */ + char *pszName; + RTVFSOBJ hVfsObj; + int rc = RTVfsFsStrmNext(hVfsFssIn, &pszName, NULL, &hVfsObj); + if (RT_FAILURE(rc)) + { + if (rc != VERR_EOF) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext returned %Rrc", rc); + break; + } + + /* + * Should we process this object? + */ + uint32_t iFile = UINT32_MAX; + if ( !pOpts->cFiles + || rtZipUnzipCmdIsNameInArray(pszName, pOpts->papszFiles, &iFile)) + { + if (pbmFound) + ASMBitSet(pbmFound, iFile); + + RTFOFF cBytes = 0; + rcExit = pfnCallback(pOpts, hVfsObj, pszName, rcExit, &cBytes); + + cBytesSum += cBytes; + cFiles++; + } + + /* + * Release the current object and string. + */ + RTVfsObjRelease(hVfsObj); + RTStrFree(pszName); + } + + /* + * Complain about any files we didn't find. + */ + for (uint32_t iFile = 0; iFile <pOpts->cFiles; iFile++) + if (!ASMBitTest(pbmFound, iFile)) + { + RTMsgError("%s: Was not found in the archive", pOpts->papszFiles[iFile]); + rcExit = RTEXITCODE_FAILURE; + } + + RTVfsFsStrmRelease(hVfsFssIn); + } + + RTMemFree(pbmFound); + + *pcFiles = cFiles; + *pcBytes = cBytesSum; + + return RTEXITCODE_SUCCESS; +} + + +RTDECL(RTEXITCODE) RTZipUnzipCmd(unsigned cArgs, char **papszArgs) +{ + /* + * Parse the command line. + */ + static const RTGETOPTDEF s_aOptions[] = + { + /* options */ + { NULL, 'c', RTGETOPT_REQ_NOTHING }, /* extract files to stdout/stderr */ + { NULL, 'd', RTGETOPT_REQ_STRING }, /* extract files to this directory */ + { NULL, 'l', RTGETOPT_REQ_NOTHING }, /* list archive files (short format) */ + { NULL, 'p', RTGETOPT_REQ_NOTHING }, /* extract files to stdout */ + { NULL, 't', RTGETOPT_REQ_NOTHING }, /* test archive files */ + { NULL, 'v', RTGETOPT_REQ_NOTHING }, /* verbose */ + + /* modifiers */ + { NULL, 'a', RTGETOPT_REQ_NOTHING }, /* convert text files */ + { NULL, 'b', RTGETOPT_REQ_NOTHING }, /* no conversion, treat as binary */ + { NULL, 'D', RTGETOPT_REQ_NOTHING }, /* don't restore timestamps for directories + (and files) */ + }; + + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, + RTGETOPTINIT_FLAGS_OPTS_FIRST); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc); + + RTZIPUNZIPCMDOPS Opts; + RT_ZERO(Opts); + + RTGETOPTUNION ValueUnion; + while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0 + && rc != VINF_GETOPT_NOT_OPTION) + { + switch (rc) + { + case 'd': + if (Opts.pszDirectory) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -d once"); + Opts.pszDirectory = ValueUnion.psz; + break; + + case 'D': + if (!Opts.fNoModTimeDirectories) + Opts.fNoModTimeDirectories = true; /* -D */ + else + Opts.fNoModTimeFiles = true; /* -DD */ + break; + + case 'l': + case 't': /* treat 'test' like 'list' */ + if (Opts.iOperation) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, + "Conflicting unzip operation (%s already set, now %s)", + Opts.pszOperation, ValueUnion.pDef->pszLong); + Opts.iOperation = 'l'; + Opts.pszOperation = ValueUnion.pDef->pszLong; + break; + + case 'v': + Opts.fVerbose = true; + break; + + default: + Opts.pszFile = ValueUnion.psz; + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + if (rc == VINF_GETOPT_NOT_OPTION) + { + Assert((unsigned)GetState.iNext - 1 <= cArgs); + Opts.pszFile = papszArgs[GetState.iNext - 1]; + if ((unsigned)GetState.iNext <= cArgs) + { + Opts.papszFiles = (const char * const *)&papszArgs[GetState.iNext]; + Opts.cFiles = cArgs - GetState.iNext; + } + } + + if (!Opts.pszFile) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "No input archive specified"); + + RTFOFF cBytes = 0; + uint32_t cFiles = 0; + switch (Opts.iOperation) + { + case 'l': + { + RTPrintf(" Length Date Time Name\n" + "--------- ---------- ----- ----\n"); + RTEXITCODE rcExit = rtZipUnzipDoWithMembers(&Opts, rtZipUnzipCmdListCallback, &cFiles, &cBytes); + RTPrintf("--------- -------\n" + "%9RU64 %u file%s\n", + cBytes, cFiles, cFiles != 1 ? "s" : ""); + + return rcExit; + } + + default: + return rtZipUnzipDoWithMembers(&Opts, rtZipUnzipCmdExtractCallback, &cFiles, &cBytes); + } +} |