tarcmd.cpp revision d809f5627a423664bd420048579ee741164d13f5
/* $Id$ */
/** @file
* IPRT - A mini TAR Command.
*/
/*
* Copyright (C) 2010-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE 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.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <iprt/zip.h>
#include <iprt/asm.h>
#include <iprt/buildconfig.h>
#include <iprt/ctype.h>
#include <iprt/dir.h>
#include <iprt/file.h>
#include <iprt/getopt.h>
#include <iprt/initterm.h>
#include <iprt/mem.h>
#include <iprt/message.h>
#include <iprt/param.h>
#include <iprt/path.h>
#include <iprt/stream.h>
#include <iprt/string.h>
#include <iprt/symlink.h>
#include <iprt/vfs.h>
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
#define RTZIPTARCMD_OPT_DELETE 1000
#define RTZIPTARCMD_OPT_OWNER 1001
#define RTZIPTARCMD_OPT_GROUP 1002
#define RTZIPTARCMD_OPT_UTC 1003
#define RTZIPTARCMD_OPT_PREFIX 1004
#define RTZIPTARCMD_OPT_FILE_MODE_AND_MASK 1005
#define RTZIPTARCMD_OPT_FILE_MODE_OR_MASK 1006
#define RTZIPTARCMD_OPT_DIR_MODE_AND_MASK 1007
#define RTZIPTARCMD_OPT_DIR_MODE_OR_MASK 1008
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* IPRT TAR option structure.
*/
typedef struct RTZIPTARCMDOPS
{
/** The operation (Acdrtux or RTZIPTARCMD_OPT_DELETE). */
int iOperation;
/** The long operation option name. */
const char *pszOperation;
/** The directory to change into when packing and unpacking. */
const char *pszDirectory;
/** The tar file name. */
const char *pszFile;
/** Whether we're verbose or quiet. */
bool fVerbose;
/** Whether to preserve the original file owner when restoring. */
bool fPreserveOwner;
/** Whether to preserve the original file group when restoring. */
bool fPreserveGroup;
/** Whether to skip restoring the modification time (only time stored by the
* traditional TAR format). */
bool fNoModTime;
/** The compressor/decompressor method to employ (0, z or j). */
char chZipper;
/** The owner to set. NULL if not applicable.
* Always resolved into uidOwner for extraction. */
const char *pszOwner;
/** The owner ID to set. NIL_RTUID if not applicable. */
RTUID uidOwner;
/** The group to set. NULL if not applicable.
* Always resolved into gidGroup for extraction. */
const char *pszGroup;
/** The group ID to set. NIL_RTGUID if not applicable. */
RTGID gidGroup;
/** Display the modification times in UTC instead of local time. */
bool fDisplayUtc;
/** File mode AND mask. */
RTFMODE fFileModeAndMask;
/** File mode OR mask. */
RTFMODE fFileModeOrMask;
/** Directory mode AND mask. */
RTFMODE fDirModeAndMask;
/** Directory mode OR mask. */
RTFMODE fDirModeOrMask;
/** What to prefix all names with when creating, adding, whatever. */
const char *pszPrefix;
/** The number of files(, directories or whatever) specified. */
uint32_t cFiles;
/** Array of files(, directories or whatever).
* Terminated by a NULL entry. */
const char * const *papszFiles;
} RTZIPTARCMDOPS;
/** Pointer to the IPRT tar options. */
typedef RTZIPTARCMDOPS *PRTZIPTARCMDOPS;
/**
* Callback used by rtZipTarDoWithMembers
*
* @returns rcExit or RTEXITCODE_FAILURE.
* @param pOpts The tar options.
* @param hVfsObj The tar object to display
* @param pszName The name.
* @param rcExit The current exit code.
*/
typedef RTEXITCODE (*PFNDOWITHMEMBER)(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE 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 rtZipTarCmdIsNameInArray(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 TAR filesystem stream handle.
*/
static RTEXITCODE rtZipTarCmdOpenInputArchive(PRTZIPTARCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss)
{
int rc;
/*
* Open the input file.
*/
RTVFSIOSTREAM hVfsIos;
if ( pOpts->pszFile
&& strcmp(pOpts->pszFile, "-") != 0)
{
const char *pszError;
rc = RTVfsChainOpenIoStream(pOpts->pszFile,
RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
&hVfsIos,
&pszError);
if (RT_FAILURE(rc))
{
if (pszError && *pszError)
return RTMsgErrorExit(RTEXITCODE_FAILURE,
"RTVfsChainOpenIoStream failed with rc=%Rrc:\n"
" '%s'\n",
" %*s^\n",
rc, pOpts->pszFile, pszError - pOpts->pszFile, "");
return RTMsgErrorExit(RTEXITCODE_FAILURE,
"Failed with %Rrc opening the input archive '%s'", rc, pOpts->pszFile);
}
}
else
{
rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT,
RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
true /*fLeaveOpen*/,
&hVfsIos);
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to prepare standard in for reading: %Rrc", rc);
}
/*
* Pass it thru a decompressor?
*/
RTVFSIOSTREAM hVfsIosDecomp = NIL_RTVFSIOSTREAM;
switch (pOpts->chZipper)
{
/* no */
case '\0':
rc = VINF_SUCCESS;
break;
/* gunzip */
case 'z':
rc = RTZipGzipDecompressIoStream(hVfsIos, 0 /*fFlags*/, &hVfsIosDecomp);
if (RT_FAILURE(rc))
RTMsgError("Failed to open gzip decompressor: %Rrc", rc);
break;
/* bunzip2 */
case 'j':
rc = VERR_NOT_SUPPORTED;
RTMsgError("bzip2 is not supported by this build");
break;
/* bug */
default:
rc = VERR_INTERNAL_ERROR_2;
RTMsgError("unknown decompression method '%c'", pOpts->chZipper);
break;
}
if (RT_FAILURE(rc))
{
RTVfsIoStrmRelease(hVfsIos);
return RTEXITCODE_FAILURE;
}
if (hVfsIosDecomp != NIL_RTVFSIOSTREAM)
{
RTVfsIoStrmRelease(hVfsIos);
hVfsIos = hVfsIosDecomp;
hVfsIosDecomp = NIL_RTVFSIOSTREAM;
}
/*
* Open the tar filesystem stream.
*/
rc = RTZipTarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
RTVfsIoStrmRelease(hVfsIos);
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open tar filesystem stream: %Rrc", rc);
return RTEXITCODE_SUCCESS;
}
/**
* Worker for the --list and --extract commands.
*
* @returns The appropriate exit code.
* @param pOpts The tar options.
* @param pfnCallback The command specific callback.
*/
static RTEXITCODE rtZipTarDoWithMembers(PRTZIPTARCMDOPS pOpts, PFNDOWITHMEMBER pfnCallback)
{
/*
* 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");
}
/*
* Open the input archive.
*/
RTVFSFSSTREAM hVfsFssIn;
RTEXITCODE rcExit = rtZipTarCmdOpenInputArchive(pOpts, &hVfsFssIn);
if (rcExit == RTEXITCODE_SUCCESS)
{
/*
* Process the stream.
*/
for (;;)
{
/*
* Retrive 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 entry?
*/
uint32_t iFile = UINT32_MAX;
if ( !pOpts->cFiles
|| rtZipTarCmdIsNameInArray(pszName, pOpts->papszFiles, &iFile) )
{
if (pbmFound)
ASMBitSet(pbmFound, iFile);
rcExit = pfnCallback(pOpts, hVfsObj, pszName, rcExit);
}
/*
* 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;
}
}
RTMemFree(pbmFound);
return rcExit;
}
/**
* Checks if the name contains any escape sequences.
*
* An escape sequence would generally be one or more '..' references. On DOS
* like system, something that would make up a drive letter reference is also
* considered an escape sequence.
*
* @returns true / false.
* @param pszName The name to consider.
*/
static bool rtZipTarHasEscapeSequence(const char *pszName)
{
#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
if (pszName[0] == ':')
return true;
#endif
while (*pszName)
{
while (RTPATH_IS_SEP(*pszName))
pszName++;
if ( pszName[0] == '.'
&& pszName[1] == '.'
&& (pszName[2] == '\0' || RTPATH_IS_SLASH(pszName[2])) )
return true;
while (*pszName && !RTPATH_IS_SEP(*pszName))
pszName++;
}
return false;
}
/**
* Queries the user ID to use when extracting a member.
*
* @returns rcExit or RTEXITCODE_FAILURE.
* @param pOpts The tar options.
* @param pUser The user info.
* @param pszName The file name to use when complaining.
* @param rcExit The current exit code.
* @param pUid Where to return the user ID.
*/
static RTEXITCODE rtZipTarQueryExtractOwner(PRTZIPTARCMDOPS pOpts, PCRTFSOBJINFO pOwner, const char *pszName, RTEXITCODE rcExit,
PRTUID pUid)
{
if (pOpts->uidOwner != NIL_RTUID)
*pUid = pOpts->uidOwner;
else if (pOpts->fPreserveGroup)
{
if (!pOwner->Attr.u.UnixGroup.szName[0])
*pUid = pOwner->Attr.u.UnixOwner.uid;
else
{
*pUid = NIL_RTUID;
return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: User resolving is not implemented.", pszName);
}
}
else
*pUid = NIL_RTUID;
return rcExit;
}
/**
* Queries the group ID to use when extracting a member.
*
* @returns rcExit or RTEXITCODE_FAILURE.
* @param pOpts The tar options.
* @param pGroup The group info.
* @param pszName The file name to use when complaining.
* @param rcExit The current exit code.
* @param pGid Where to return the group ID.
*/
static RTEXITCODE rtZipTarQueryExtractGroup(PRTZIPTARCMDOPS pOpts, PCRTFSOBJINFO pGroup, const char *pszName, RTEXITCODE rcExit,
PRTGID pGid)
{
if (pOpts->gidGroup != NIL_RTGID)
*pGid = pOpts->gidGroup;
else if (pOpts->fPreserveGroup)
{
if (!pGroup->Attr.u.UnixGroup.szName[0])
*pGid = pGroup->Attr.u.UnixGroup.gid;
else
{
*pGid = NIL_RTGID;
return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Group resolving is not implemented.", pszName);
}
}
else
*pGid = NIL_RTGID;
return rcExit;
}
/**
* Extracts a file.
*
* Since we can restore permissions and attributes more efficiently by working
* directly on the file handle, we have special code path for files.
*
* @returns rcExit or RTEXITCODE_FAILURE.
* @param pOpts The tar options.
* @param hVfsObj The tar object to display
* @param rcExit The current exit code.
* @param pUnixInfo The unix fs object info.
* @param pOwner The owner info.
* @param pGroup The group info.
*/
static RTEXITCODE rtZipTarCmdExtractFile(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, RTEXITCODE rcExit,
const char *pszDst, PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner, PCRTFSOBJINFO pGroup)
{
/*
* 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
| ((RTFS_UNIX_IWUSR | RTFS_UNIX_IRUSR) << 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->fNoModTime)
{
rc = RTFileSetTimes(hFile, NULL, &pUnixInfo->ModificationTime, NULL, NULL);
if (RT_FAILURE(rc))
rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error setting times: %Rrc", pszDst, rc);
}
#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
if ( pOpts->uidOwner != NIL_RTUID
|| pOpts->gidGroup != NIL_RTGID
|| pOpts->fPreserveOwner
|| pOpts->fPreserveGroup)
{
RTUID uidFile;
rcExit = rtZipTarQueryExtractOwner(pOpts, pOwner, pszDst, rcExit, &uidFile);
RTGID gidFile;
rcExit = rtZipTarQueryExtractGroup(pOpts, pGroup, pszDst, rcExit, &gidFile);
if (uidFile != NIL_RTUID || gidFile != NIL_RTGID)
{
rc = RTFileSetOwner(hFile, uidFile, gidFile);
if (RT_FAILURE(rc))
rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error owner/group: %Rrc", pszDst, rc);
}
}
#endif
RTFMODE fMode = (pUnixInfo->Attr.fMode & pOpts->fFileModeAndMask) | pOpts->fFileModeOrMask;
rc = RTFileSetMode(hFile, fMode | RTFS_TYPE_FILE);
if (RT_FAILURE(rc))
rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error changing mode: %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);
RTFileClose(hFile);
return rcExit;
}
/**
* @callback_method_impl{PFNDOWITHMEMBER, Implements --extract.}
*/
static RTEXITCODE rtZipTarCmdExtractCallback(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit)
{
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);
RTFSOBJINFO Owner;
rc = RTVfsObjQueryInfo(hVfsObj, &Owner, RTFSOBJATTRADD_UNIX_OWNER);
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE,
"RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
rc, pszName);
RTFSOBJINFO Group;
rc = RTVfsObjQueryInfo(hVfsObj, &Group, RTFSOBJATTRADD_UNIX_GROUP);
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE,
"RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
rc, pszName);
const char *pszLinkType = NULL;
char szTarget[RTPATH_MAX];
szTarget[0] = '\0';
RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
if (hVfsSymlink != NIL_RTVFSSYMLINK)
{
rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
RTVfsSymlinkRelease(hVfsSymlink);
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: RTVfsSymlinkRead failed: %Rrc", pszName, rc);
if (!RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Hardlinks are not supported.", pszName);
if (!szTarget[0])
return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Link target is empty.", pszName);
}
else if (RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to get symlink object for '%s'", pszName);
if (rtZipTarHasEscapeSequence(pszName))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Name '%s' contains an escape sequence.", pszName);
/*
* Construct the path to the extracted member.
*/
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 rtZipTarCmdExtractFile(pOpts, hVfsObj, rcExit, szDst, &UnixInfo, &Owner, &Group);
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;
case RTFS_TYPE_SYMLINK:
rc = RTSymlinkCreate(szDst, szTarget, RTSYMLINKTYPE_UNKNOWN, 0);
if (RT_FAILURE(rc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating symbolic link: %Rrc", szDst, rc);
break;
case RTFS_TYPE_FIFO:
return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: FIFOs are not supported.", pszName);
case RTFS_TYPE_DEV_CHAR:
return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: FIFOs are not supported.", pszName);
case RTFS_TYPE_DEV_BLOCK:
return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Block devices are not supported.", pszName);
case RTFS_TYPE_SOCKET:
return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Sockets are not supported.", pszName);
case RTFS_TYPE_WHITEOUT:
return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Whiteouts are not support.", pszName);
default:
return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Unknown file type.", pszName);
}
/*
* Set other attributes as requested .
* .
* Note! File extraction does get here.
*/
if (!pOpts->fNoModTime)
{
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);
}
#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
if ( pOpts->uidOwner != NIL_RTUID
|| pOpts->gidGroup != NIL_RTGID
|| pOpts->fPreserveOwner
|| pOpts->fPreserveGroup)
{
RTUID uidFile;
rcExit = rtZipTarQueryExtractOwner(pOpts, &Owner, szDst, rcExit, &uidFile);
RTGID gidFile;
rcExit = rtZipTarQueryExtractGroup(pOpts, &Group, szDst, rcExit, &gidFile);
if (uidFile != NIL_RTUID || gidFile != NIL_RTGID)
{
rc = RTPathSetOwnerEx(szDst, uidFile, gidFile, RTPATH_F_ON_LINK);
if (RT_FAILURE(rc))
rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error owner/group: %Rrc", szDst, rc);
}
}
#endif
#if !defined(RT_OS_WINDOWS) /** @todo implement RTPathSetMode on windows... */
if (!RTFS_IS_SYMLINK(UnixInfo.Attr.fMode)) /* RTPathSetMode follows symbolic links atm. */
{
RTFMODE fMode;
if (RTFS_IS_DIRECTORY(UnixInfo.Attr.fMode))
fMode = (UnixInfo.Attr.fMode & (pOpts->fDirModeAndMask | RTFS_TYPE_MASK)) | pOpts->fDirModeOrMask;
else
fMode = (UnixInfo.Attr.fMode & (pOpts->fFileModeAndMask | RTFS_TYPE_MASK)) | pOpts->fFileModeOrMask;
rc = RTPathSetMode(szDst, fMode);
if (RT_FAILURE(rc))
rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error changing mode: %Rrc", szDst, rc);
}
#endif
return rcExit;
}
/**
* @callback_method_impl{PFNDOWITHMEMBER, Implements --list.}
*/
static RTEXITCODE rtZipTarCmdListCallback(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit)
{
/*
* This is very simple in non-verbose mode.
*/
if (!pOpts->fVerbose)
{
RTPrintf("%s\n", pszName);
return rcExit;
}
/*
* Query all the information.
*/
RTFSOBJINFO UnixInfo;
int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
if (RT_FAILURE(rc))
{
rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
RT_ZERO(UnixInfo);
}
RTFSOBJINFO Owner;
rc = RTVfsObjQueryInfo(hVfsObj, &Owner, RTFSOBJATTRADD_UNIX_OWNER);
if (RT_FAILURE(rc))
{
rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
"RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
rc, pszName);
RT_ZERO(Owner);
}
RTFSOBJINFO Group;
rc = RTVfsObjQueryInfo(hVfsObj, &Group, RTFSOBJATTRADD_UNIX_GROUP);
if (RT_FAILURE(rc))
{
rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
"RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
rc, pszName);
RT_ZERO(Group);
}
const char *pszLinkType = NULL;
char szTarget[RTPATH_MAX];
szTarget[0] = '\0';
RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
if (hVfsSymlink != NIL_RTVFSSYMLINK)
{
rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
if (RT_FAILURE(rc))
rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsSymlinkRead returned %Rrc on '%s'", rc, pszName);
RTVfsSymlinkRelease(hVfsSymlink);
pszLinkType = RTFS_IS_SYMLINK(UnixInfo.Attr.fMode) ? "->" : "link to";
}
else if (RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to get symlink object for '%s'", pszName);
/*
* Translate the mode mask.
*/
char szMode[16];
switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
{
case RTFS_TYPE_FIFO: szMode[0] = 'f'; break;
case RTFS_TYPE_DEV_CHAR: szMode[0] = 'c'; break;
case RTFS_TYPE_DIRECTORY: szMode[0] = 'd'; break;
case RTFS_TYPE_DEV_BLOCK: szMode[0] = 'b'; break;
case RTFS_TYPE_FILE: szMode[0] = '-'; break;
case RTFS_TYPE_SYMLINK: szMode[0] = 'l'; break;
case RTFS_TYPE_SOCKET: szMode[0] = 's'; break;
case RTFS_TYPE_WHITEOUT: szMode[0] = 'w'; break;
default: szMode[0] = '?'; break;
}
if (pszLinkType && szMode[0] != 's')
szMode[0] = 'h';
szMode[1] = UnixInfo.Attr.fMode & RTFS_UNIX_IRUSR ? 'r' : '-';
szMode[2] = UnixInfo.Attr.fMode & RTFS_UNIX_IWUSR ? 'w' : '-';
szMode[3] = UnixInfo.Attr.fMode & RTFS_UNIX_IXUSR ? 'x' : '-';
szMode[4] = UnixInfo.Attr.fMode & RTFS_UNIX_IRGRP ? 'r' : '-';
szMode[5] = UnixInfo.Attr.fMode & RTFS_UNIX_IWGRP ? 'w' : '-';
szMode[6] = UnixInfo.Attr.fMode & RTFS_UNIX_IXGRP ? 'x' : '-';
szMode[7] = UnixInfo.Attr.fMode & RTFS_UNIX_IROTH ? 'r' : '-';
szMode[8] = UnixInfo.Attr.fMode & RTFS_UNIX_IWOTH ? 'w' : '-';
szMode[9] = UnixInfo.Attr.fMode & RTFS_UNIX_IXOTH ? 'x' : '-';
szMode[10] = '\0';
/** @todo sticky and set-uid/gid bits. */
/*
* Make sure we've got valid owner and group strings.
*/
if (!Owner.Attr.u.UnixGroup.szName[0])
RTStrPrintf(Owner.Attr.u.UnixOwner.szName, sizeof(Owner.Attr.u.UnixOwner.szName),
"%u", UnixInfo.Attr.u.Unix.uid);
if (!Group.Attr.u.UnixOwner.szName[0])
RTStrPrintf(Group.Attr.u.UnixGroup.szName, sizeof(Group.Attr.u.UnixGroup.szName),
"%u", UnixInfo.Attr.u.Unix.gid);
/*
* Format the modification time.
*/
char szModTime[32];
RTTIME ModTime;
PRTTIME pTime;
if (!pOpts->fDisplayUtc)
pTime = RTTimeLocalExplode(&ModTime, &UnixInfo.ModificationTime);
else
pTime = RTTimeExplode(&ModTime, &UnixInfo.ModificationTime);
if (!pTime)
RT_ZERO(ModTime);
RTStrPrintf(szModTime, sizeof(szModTime), "%04d-%02u-%02u %02u:%02u",
ModTime.i32Year, ModTime.u8Month, ModTime.u8MonthDay, ModTime.u8Hour, ModTime.u8Minute);
/*
* Format the size and figure how much space is needed between the
* user/group and the size.
*/
char szSize[64];
size_t cchSize;
switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
{
case RTFS_TYPE_DEV_CHAR:
case RTFS_TYPE_DEV_BLOCK:
cchSize = RTStrPrintf(szSize, sizeof(szSize), "%u,%u",
RTDEV_MAJOR(UnixInfo.Attr.u.Unix.Device), RTDEV_MINOR(UnixInfo.Attr.u.Unix.Device));
break;
default:
cchSize = RTStrPrintf(szSize, sizeof(szSize), "%RU64", UnixInfo.cbObject);
break;
}
size_t cchUserGroup = strlen(Owner.Attr.u.UnixOwner.szName)
+ 1
+ strlen(Group.Attr.u.UnixGroup.szName);
ssize_t cchPad = cchUserGroup + cchSize + 1 < 19
? 19 - (cchUserGroup + cchSize + 1)
: 0;
/*
* Go to press.
*/
if (pszLinkType)
RTPrintf("%s %s/%s%*s %s %s %s %s %s\n",
szMode,
Owner.Attr.u.UnixOwner.szName, Group.Attr.u.UnixGroup.szName,
cchPad, "",
szSize,
szModTime,
pszName,
pszLinkType,
szTarget);
else
RTPrintf("%s %s/%s%*s %s %s %s\n",
szMode,
Owner.Attr.u.UnixOwner.szName, Group.Attr.u.UnixGroup.szName,
cchPad, "",
szSize,
szModTime,
pszName);
return rcExit;
}
/**
* Display usage.
*
* @param pszProgName The program name.
*/
static void rtZipTarUsage(const char *pszProgName)
{
/*
* 0 1 2 3 4 5 6 7 8
* 012345678901234567890123456789012345678901234567890123456789012345678901234567890
*/
RTPrintf("Usage: %s [options]\n"
"\n",
pszProgName);
RTPrintf("Operations:\n"
" -A, --concatenate, --catenate\n"
" Append the content of one tar archive to another. (not impl)\n"
" -c, --create\n"
" Create a new tar archive. (not impl)\n"
" -d, --diff, --compare\n"
" Compare atar archive with the file system. (not impl)\n"
" -r, --append\n"
" Append more files to the tar archive. (not impl)\n"
" -t, --list\n"
" List the contents of the tar archive.\n"
" -u, --update\n"
" Update the archive, adding files that are newer than the\n"
" ones in the archive. (not impl)\n"
" -x, --extract, --get\n"
" Extract the files from the tar archive.\n"
" --delete\n"
" Delete files from the tar archive.\n"
"\n"
);
RTPrintf("Basic Options:\n"
" -C <dir>, --directory <dir> (-A, -C, -d, -r, -u, -x)\n"
" Sets the base directory for input and output file members.\n"
" This does not apply to --file, even if it preceeds it.\n"
" -f <archive>, --file <archive> (all)\n"
" The tar file to create or process. '-' indicates stdout/stdin,\n"
" which is is the default.\n"
" -v, --verbose (all)\n"
" Verbose operation.\n"
" -p, --preserve-permissions (-x)\n"
" Preserve all permissions when extracting. Must be used\n"
" before the mode mask options as it will change some of these.\n"
" -j, --bzip2 (all)\n"
" Compress/decompress the archive with bzip2.\n"
" -z, --gzip, --gunzip, --ungzip (all)\n"
" Compress/decompress the archive with gzip.\n"
"\n");
RTPrintf("Misc Options:\n"
" --owner <uid/username> (-A, -C, -d, -r, -u, -x)\n"
" Set the owner of extracted and archived files to the user specified.\n"
" --group <uid/username> (-A, -C, -d, -r, -u, -x)\n"
" Set the group of extracted and archived files to the group specified.\n"
" --utc (-t)\n"
" Display timestamps as UTC instead of local time.\n"
"\n");
RTPrintf("IPRT Options:\n"
" --prefix <dir-prefix> (-A, -C, -d, -r, -u)\n"
" Directory prefix to give the members added to the archive.\n"
" --file-mode-and-mask <octal-mode> (-A, -C, -d, -r, -u, -x)\n"
" Restrict the access mode of regular and special files.\n"
" --file-mode-and-mask <octal-mode> (-A, -C, -d, -r, -u, -x)\n"
" Include the given access mode for regular and special files.\n"
" --dir-mode-and-mask <octal-mode> (-A, -C, -d, -r, -u, -x)\n"
" Restrict the access mode of directories.\n"
" --dir-mode-and-mask <octal-mode> (-A, -C, -d, -r, -u, -x)\n"
" Include the given access mode for directories.\n"
"\n");
RTPrintf("Standard Options:\n"
" -h, -?, --help\n"
" Display this help text.\n"
" -V, --version\n"
" Display version number.\n");
}
RTDECL(RTEXITCODE) RTZipTarCmd(unsigned cArgs, char **papszArgs)
{
/*
* Parse the command line.
*
* N.B. This is less flexible that your regular tar program in that it
* requires the operation to be specified as an option. On the other
* hand, you can specify it where ever you like in the command line.
*/
static const RTGETOPTDEF s_aOptions[] =
{
/* operations */
{ "--concatenate", 'A', RTGETOPT_REQ_NOTHING },
{ "--catenate", 'A', RTGETOPT_REQ_NOTHING },
{ "--create", 'c', RTGETOPT_REQ_NOTHING },
{ "--diff", 'd', RTGETOPT_REQ_NOTHING },
{ "--compare", 'd', RTGETOPT_REQ_NOTHING },
{ "--append", 'r', RTGETOPT_REQ_NOTHING },
{ "--list", 't', RTGETOPT_REQ_NOTHING },
{ "--update", 'u', RTGETOPT_REQ_NOTHING },
{ "--extract", 'x', RTGETOPT_REQ_NOTHING },
{ "--get", 'x', RTGETOPT_REQ_NOTHING },
{ "--delete", RTZIPTARCMD_OPT_DELETE, RTGETOPT_REQ_NOTHING },
/* basic options */
{ "--directory", 'C', RTGETOPT_REQ_STRING },
{ "--file", 'f', RTGETOPT_REQ_STRING },
{ "--verbose", 'v', RTGETOPT_REQ_NOTHING },
{ "--preserve-permissions", 'p', RTGETOPT_REQ_NOTHING },
{ "--bzip2", 'j', RTGETOPT_REQ_NOTHING },
{ "--gzip", 'z', RTGETOPT_REQ_NOTHING },
{ "--gunzip", 'z', RTGETOPT_REQ_NOTHING },
{ "--ungzip", 'z', RTGETOPT_REQ_NOTHING },
/* other options. */
{ "--owner", RTZIPTARCMD_OPT_OWNER, RTGETOPT_REQ_STRING },
{ "--group", RTZIPTARCMD_OPT_GROUP, RTGETOPT_REQ_STRING },
{ "--utc", RTZIPTARCMD_OPT_UTC, RTGETOPT_REQ_NOTHING },
/* IPRT extensions */
{ "--prefix", RTZIPTARCMD_OPT_PREFIX, RTGETOPT_REQ_STRING },
{ "--file-mode-and-mask", RTZIPTARCMD_OPT_FILE_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
{ "--file-mode-or-mask", RTZIPTARCMD_OPT_FILE_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
{ "--dir-mode-and-mask", RTZIPTARCMD_OPT_DIR_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
{ "--dir-mode-or-mask", RTZIPTARCMD_OPT_DIR_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
};
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);
RTZIPTARCMDOPS Opts;
RT_ZERO(Opts);
Opts.uidOwner = NIL_RTUID;
Opts.gidGroup = NIL_RTUID;
Opts.fFileModeAndMask = RTFS_UNIX_ALL_ACCESS_PERMS;
Opts.fDirModeAndMask = RTFS_UNIX_ALL_ACCESS_PERMS;
#if 0
if (RTPermIsSuperUser())
{
Opts.fFileModeAndMask = RTFS_UNIX_ALL_PERMS;
Opts.fDirModeAndMask = RTFS_UNIX_ALL_PERMS;
Opts.fPreserveOwner = true;
Opts.fPreserveGroup = true;
}
#endif
RTGETOPTUNION ValueUnion;
while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
&& rc != VINF_GETOPT_NOT_OPTION)
{
switch (rc)
{
/* operations */
case 'A':
case 'c':
case 'd':
case 'r':
case 't':
case 'u':
case 'x':
case RTZIPTARCMD_OPT_DELETE:
if (Opts.iOperation)
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Conflicting tar operation (%s already set, now %s)",
Opts.pszOperation, ValueUnion.pDef->pszLong);
Opts.iOperation = rc;
Opts.pszOperation = ValueUnion.pDef->pszLong;
break;
/* basic options */
case 'C':
if (Opts.pszDirectory)
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -C/--directory once");
Opts.pszDirectory = ValueUnion.psz;
break;
case 'f':
if (Opts.pszFile)
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -f/--file once");
Opts.pszFile = ValueUnion.psz;
break;
case 'v':
Opts.fVerbose = true;
break;
case 'p':
Opts.fFileModeAndMask = RTFS_UNIX_ALL_PERMS;
Opts.fDirModeAndMask = RTFS_UNIX_ALL_PERMS;
Opts.fPreserveOwner = true;
Opts.fPreserveGroup = true;
break;
case 'j':
case 'z':
if (Opts.chZipper)
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify one compressor / decompressor");
Opts.chZipper = rc;
break;
case RTZIPTARCMD_OPT_OWNER:
if (Opts.pszOwner)
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --owner once");
Opts.pszOwner = ValueUnion.psz;
rc = RTStrToUInt32Full(Opts.pszOwner, 0, &ValueUnion.u32);
if (RT_SUCCESS(rc) && rc != VINF_SUCCESS)
return RTMsgErrorExit(RTEXITCODE_SYNTAX,
"Error convering --owner '%s' into a number: %Rrc", Opts.pszOwner, rc);
if (RT_SUCCESS(rc))
{
Opts.uidOwner = ValueUnion.u32;
Opts.pszOwner = NULL;
}
break;
case RTZIPTARCMD_OPT_GROUP:
if (Opts.pszGroup)
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --group once");
Opts.pszGroup = ValueUnion.psz;
rc = RTStrToUInt32Full(Opts.pszGroup, 0, &ValueUnion.u32);
if (RT_SUCCESS(rc) && rc != VINF_SUCCESS)
return RTMsgErrorExit(RTEXITCODE_SYNTAX,
"Error convering --group '%s' into a number: %Rrc", Opts.pszGroup, rc);
if (RT_SUCCESS(rc))
{
Opts.gidGroup = ValueUnion.u32;
Opts.pszGroup = NULL;
}
break;
case RTZIPTARCMD_OPT_UTC:
Opts.fDisplayUtc = true;
break;
/* iprt extensions */
case RTZIPTARCMD_OPT_PREFIX:
if (Opts.pszPrefix)
return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --prefix once");
Opts.pszPrefix = ValueUnion.psz;
break;
case RTZIPTARCMD_OPT_FILE_MODE_AND_MASK:
Opts.fFileModeAndMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
break;
case RTZIPTARCMD_OPT_FILE_MODE_OR_MASK:
Opts.fFileModeOrMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
break;
case RTZIPTARCMD_OPT_DIR_MODE_AND_MASK:
Opts.fDirModeAndMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
break;
case RTZIPTARCMD_OPT_DIR_MODE_OR_MASK:
Opts.fDirModeOrMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
break;
/* Standard bits. */
case 'h':
rtZipTarUsage(RTPathFilename(papszArgs[0]));
return RTEXITCODE_SUCCESS;
case 'V':
RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
return RTEXITCODE_SUCCESS;
default:
return RTGetOptPrintError(rc, &ValueUnion);
}
}
if (rc == VINF_GETOPT_NOT_OPTION)
{
/* this is kind of ugly. */
Assert((unsigned)GetState.iNext - 1 <= cArgs);
Opts.papszFiles = (const char * const *)&papszArgs[GetState.iNext - 1];
Opts.cFiles = cArgs - GetState.iNext + 1;
}
/*
* Post proceess the options.
*/
if (Opts.iOperation == 0)
{
Opts.iOperation = 't';
Opts.pszOperation = "--list";
}
if ( Opts.iOperation == 'x'
&& Opts.pszOwner)
return RTMsgErrorExit(RTEXITCODE_FAILURE, "The use of --owner with %s has not implemented yet", Opts.pszOperation);
if ( Opts.iOperation == 'x'
&& Opts.pszGroup)
return RTMsgErrorExit(RTEXITCODE_FAILURE, "The use of --group with %s has not implemented yet", Opts.pszOperation);
/*
* Do the job.
*/
switch (Opts.iOperation)
{
case 't':
return rtZipTarDoWithMembers(&Opts, rtZipTarCmdListCallback);
case 'x':
return rtZipTarDoWithMembers(&Opts, rtZipTarCmdExtractCallback);
case 'A':
case 'c':
case 'd':
case 'r':
case 'u':
case RTZIPTARCMD_OPT_DELETE:
return RTMsgErrorExit(RTEXITCODE_FAILURE, "The operation %s is not implemented yet", Opts.pszOperation);
default:
return RTMsgErrorExit(RTEXITCODE_FAILURE, "Internal error");
}
}