/* $Id: tarcmd.cpp 47359 2013-07-24 00:45:47Z vboxsync $ */ /** @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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /******************************************************************************* * 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 , --directory (-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 , --file (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 (-A, -C, -d, -r, -u, -x)\n" " Set the owner of extracted and archived files to the user specified.\n" " --group (-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 (-A, -C, -d, -r, -u)\n" " Directory prefix to give the members added to the archive.\n" " --file-mode-and-mask (-A, -C, -d, -r, -u, -x)\n" " Restrict the access mode of regular and special files.\n" " --file-mode-and-mask (-A, -C, -d, -r, -u, -x)\n" " Include the given access mode for regular and special files.\n" " --dir-mode-and-mask (-A, -C, -d, -r, -u, -x)\n" " Restrict the access mode of directories.\n" " --dir-mode-and-mask (-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"); } }