diff options
Diffstat (limited to 'src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp')
-rw-r--r-- | src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp | 1763 |
1 files changed, 1763 insertions, 0 deletions
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp new file mode 100644 index 00000000..bc91b710 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp @@ -0,0 +1,1763 @@ +/* $Id: VBoxServiceToolBox.cpp $ */ +/** @file + * VBoxServiceToolbox - Internal (BusyBox-like) toolbox. + */ + +/* + * Copyright (C) 2012-2019 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdio.h> + +#include <iprt/assert.h> +#include <iprt/buildconfig.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/symlink.h> + +#ifndef RT_OS_WINDOWS +# include <sys/stat.h> /* need umask */ +#endif + +#include <VBox/VBoxGuestLib.h> +#include <VBox/version.h> + +#include <VBox/GuestHost/GuestControl.h> + +#include "VBoxServiceInternal.h" +#include "VBoxServiceToolBox.h" +#include "VBoxServiceUtils.h" + +using namespace guestControl; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** Generic option indices for commands. */ +enum +{ + VBOXSERVICETOOLBOXOPT_MACHINE_READABLE = 1000, + VBOXSERVICETOOLBOXOPT_VERBOSE +}; + +/** Options indices for "vbox_cat". */ +typedef enum VBOXSERVICETOOLBOXCATOPT +{ + VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED = 1000 +} VBOXSERVICETOOLBOXCATOPT; + +/** Flags for "vbox_ls". */ +typedef enum VBOXSERVICETOOLBOXLSFLAG +{ + VBOXSERVICETOOLBOXLSFLAG_NONE, + VBOXSERVICETOOLBOXLSFLAG_RECURSIVE, + VBOXSERVICETOOLBOXLSFLAG_SYMLINKS +} VBOXSERVICETOOLBOXLSFLAG; + +/** Flags for fs object output. */ +typedef enum VBOXSERVICETOOLBOXOUTPUTFLAG +{ + VBOXSERVICETOOLBOXOUTPUTFLAG_NONE, + VBOXSERVICETOOLBOXOUTPUTFLAG_LONG, + VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE +} VBOXSERVICETOOLBOXOUTPUTFLAG; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to a tool handler function. */ +typedef RTEXITCODE (*PFNHANDLER)(int , char **); + +/** Definition for a specific toolbox tool. */ +typedef struct VBOXSERVICETOOLBOXTOOL +{ + /** Friendly name of the tool. */ + const char *pszName; + /** Main handler to be invoked to use the tool. */ + RTEXITCODE (*pfnHandler)(int argc, char **argv); + /** Conversion routine to convert the tool's exit code back to an IPRT rc. Optional. + * + * @todo r=bird: You better revert this, i.e. having pfnHandler return a VBox + * status code and have a routine for converting it to RTEXITCODE. + * Unless, what you really want to do here is to get a cached status, in + * which case you better call it what it is. + */ + int (*pfnExitCodeConvertToRc)(RTEXITCODE rcExit); +} VBOXSERVICETOOLBOXTOOL; +/** Pointer to a const tool definition. */ +typedef VBOXSERVICETOOLBOXTOOL const *PCVBOXSERVICETOOLBOXTOOL; + +/** + * An file/directory entry. Used to cache + * file names/paths for later processing. + */ +typedef struct VBOXSERVICETOOLBOXPATHENTRY +{ + /** Our node. */ + RTLISTNODE Node; + /** Name of the entry. */ + char *pszName; +} VBOXSERVICETOOLBOXPATHENTRY, *PVBOXSERVICETOOLBOXPATHENTRY; + +typedef struct VBOXSERVICETOOLBOXDIRENTRY +{ + /** Our node. */ + RTLISTNODE Node; + /** The actual entry. */ + RTDIRENTRYEX dirEntry; +} VBOXSERVICETOOLBOXDIRENTRY, *PVBOXSERVICETOOLBOXDIRENTRY; + +/** ID cache entry. */ +typedef struct VGSVCTOOLBOXUIDENTRY +{ + /** The identifier name. */ + uint32_t id; + /** Set if UID, clear if GID. */ + bool fIsUid; + /** The name. */ + char szName[128 - 4 - 1]; +} VGSVCTOOLBOXUIDENTRY; +typedef VGSVCTOOLBOXUIDENTRY *PVGSVCTOOLBOXUIDENTRY; + + +/** ID cache. */ +typedef struct VGSVCTOOLBOXIDCACHE +{ + /** Number of valid cache entries. */ + uint32_t cEntries; + /** The next entry to replace. */ + uint32_t iNextReplace; + /** The cache entries. */ + VGSVCTOOLBOXUIDENTRY aEntries[16]; +} VGSVCTOOLBOXIDCACHE; +typedef VGSVCTOOLBOXIDCACHE *PVGSVCTOOLBOXIDCACHE; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static RTEXITCODE vgsvcToolboxCat(int argc, char **argv); +static RTEXITCODE vgsvcToolboxLs(int argc, char **argv); +static RTEXITCODE vgsvcToolboxRm(int argc, char **argv); +static RTEXITCODE vgsvcToolboxMkTemp(int argc, char **argv); +static RTEXITCODE vgsvcToolboxMkDir(int argc, char **argv); +static RTEXITCODE vgsvcToolboxStat(int argc, char **argv); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Tool definitions. */ +static VBOXSERVICETOOLBOXTOOL const g_aTools[] = +{ + { VBOXSERVICE_TOOL_CAT, vgsvcToolboxCat , NULL }, + { VBOXSERVICE_TOOL_LS, vgsvcToolboxLs , NULL }, + { VBOXSERVICE_TOOL_RM, vgsvcToolboxRm , NULL }, + { VBOXSERVICE_TOOL_MKTEMP, vgsvcToolboxMkTemp, NULL }, + { VBOXSERVICE_TOOL_MKDIR, vgsvcToolboxMkDir , NULL }, + { VBOXSERVICE_TOOL_STAT, vgsvcToolboxStat , NULL } +}; + + + + +/** + * Displays a common header for all help text to stdout. + */ +static void vgsvcToolboxShowUsageHeader(void) +{ + RTPrintf(VBOX_PRODUCT " Guest Toolbox Version " + VBOX_VERSION_STRING "\n" + "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n" + "All rights reserved.\n" + "\n"); + RTPrintf("Usage:\n\n"); +} + + +/** + * Displays a help text to stdout. + */ +static void vgsvcToolboxShowUsage(void) +{ + vgsvcToolboxShowUsageHeader(); + RTPrintf(" VBoxService [--use-toolbox] vbox_<command> [<general options>] <parameters>\n\n" + "General options:\n\n" + " --machinereadable produce all output in machine-readable form\n" + " -V print version number and exit\n" + "\n" + "Commands:\n\n" + " vbox_cat [<general options>] <file>...\n" + " vbox_ls [<general options>] [--dereference|-L] [-l] [-R]\n" + " [--verbose|-v] [<file>...]\n" + " vbox_rm [<general options>] [-r|-R] <file>...\n" + " vbox_mktemp [<general options>] [--directory|-d] [--mode|-m <mode>]\n" + " [--secure|-s] [--tmpdir|-t <path>] <template>\n" + " vbox_mkdir [<general options>] [--mode|-m <mode>] [--parents|-p]\n" + " [--verbose|-v] <directory>...\n" + " vbox_stat [<general options>] [--file-system|-f]\n" + " [--dereference|-L] [--terse|-t] [--verbose|-v] <file>...\n" + "\n"); +} + + +/** + * Displays the program's version number. + */ +static void vgsvcToolboxShowVersion(void) +{ + RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision()); +} + + +/** + * Initializes the parseable stream(s). + * + * @return IPRT status code. + */ +static int vgsvcToolboxStrmInit(void) +{ + /* Set stdout's mode to binary. This is required for outputting all the machine-readable + * data correctly. */ + int rc = RTStrmSetMode(g_pStdOut, 1 /* Binary mode */, -1 /* Current code set, not changed */); + if (RT_FAILURE(rc)) + RTMsgError("Unable to set stdout to binary mode, rc=%Rrc\n", rc); + + return rc; +} + + +/** + * Prints a parseable stream header which contains the actual tool + * which was called/used along with its stream version. + * + * @param pszToolName Name of the tool being used, e.g. "vbt_ls". + * @param uVersion Stream version name. Handy for distinguishing + * different stream versions later. + */ +static void vgsvcToolboxPrintStrmHeader(const char *pszToolName, uint32_t uVersion) +{ + AssertPtrReturnVoid(pszToolName); + RTPrintf("hdr_id=%s%chdr_ver=%u%c", pszToolName, 0, uVersion, 0); +} + + +/** + * Prints a standardized termination sequence indicating that the + * parseable stream just ended. + * + */ +static void vgsvcToolboxPrintStrmTermination() +{ + RTPrintf("%c%c%c%c", 0, 0, 0, 0); +} + + +/** + * Parse a file mode string from the command line (currently octal only) + * and print an error message and return an error if necessary. + */ +static int vgsvcToolboxParseMode(const char *pcszMode, RTFMODE *pfMode) +{ + int rc = RTStrToUInt32Ex(pcszMode, NULL, 8 /* Base */, pfMode); + if (RT_FAILURE(rc)) /* Only octet based values supported right now! */ + RTMsgError("Mode flag strings not implemented yet! Use octal numbers instead. (%s)\n", pcszMode); + return rc; +} + + +/** + * Destroys a path buffer list. + * + * @return IPRT status code. + * @param pList Pointer to list to destroy. + */ +static void vgsvcToolboxPathBufDestroy(PRTLISTNODE pList) +{ + AssertPtr(pList); + /** @todo use RTListForEachSafe */ + PVBOXSERVICETOOLBOXPATHENTRY pNode = RTListGetFirst(pList, VBOXSERVICETOOLBOXPATHENTRY, Node); + while (pNode) + { + PVBOXSERVICETOOLBOXPATHENTRY pNext = RTListNodeIsLast(pList, &pNode->Node) + ? NULL + : RTListNodeGetNext(&pNode->Node, VBOXSERVICETOOLBOXPATHENTRY, Node); + RTListNodeRemove(&pNode->Node); + + RTStrFree(pNode->pszName); + + RTMemFree(pNode); + pNode = pNext; + } +} + + +/** + * Adds a path entry (file/directory/whatever) to a given path buffer list. + * + * @return IPRT status code. + * @param pList Pointer to list to add entry to. + * @param pszName Name of entry to add. + */ +static int vgsvcToolboxPathBufAddPathEntry(PRTLISTNODE pList, const char *pszName) +{ + AssertPtrReturn(pList, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + PVBOXSERVICETOOLBOXPATHENTRY pNode = (PVBOXSERVICETOOLBOXPATHENTRY)RTMemAlloc(sizeof(VBOXSERVICETOOLBOXPATHENTRY)); + if (pNode) + { + pNode->pszName = RTStrDup(pszName); + AssertPtr(pNode->pszName); + + RTListAppend(pList, &pNode->Node); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Performs the actual output operation of "vbox_cat". + * + * @return IPRT status code. + * @param hInput Handle of input file (if any) to use; + * else stdin will be used. + * @param hOutput Handle of output file (if any) to use; + * else stdout will be used. + */ +static int vgsvcToolboxCatOutput(RTFILE hInput, RTFILE hOutput) +{ + int rc = VINF_SUCCESS; + if (hInput == NIL_RTFILE) + { + rc = RTFileFromNative(&hInput, RTFILE_NATIVE_STDIN); + if (RT_FAILURE(rc)) + RTMsgError("Could not translate input file to native handle, rc=%Rrc\n", rc); + } + + if (hOutput == NIL_RTFILE) + { + rc = RTFileFromNative(&hOutput, RTFILE_NATIVE_STDOUT); + if (RT_FAILURE(rc)) + RTMsgError("Could not translate output file to native handle, rc=%Rrc\n", rc); + } + + if (RT_SUCCESS(rc)) + { + uint8_t abBuf[_64K]; + size_t cbRead; + for (;;) + { + rc = RTFileRead(hInput, abBuf, sizeof(abBuf), &cbRead); + if (RT_SUCCESS(rc) && cbRead > 0) + { + rc = RTFileWrite(hOutput, abBuf, cbRead, NULL /* Try to write all at once! */); + if (RT_FAILURE(rc)) + { + RTMsgError("Error while writing output, rc=%Rrc\n", rc); + break; + } + } + else + { + if (rc == VERR_BROKEN_PIPE) + rc = VINF_SUCCESS; + else if (RT_FAILURE(rc)) + RTMsgError("Error while reading input, rc=%Rrc\n", rc); + break; + } + } + } + return rc; +} + + +/** @todo Document options! */ +static char g_paszCatHelp[] = + " VBoxService [--use-toolbox] vbox_cat [<general options>] <file>...\n\n" + "Concatenate files, or standard input, to standard output.\n" + "\n"; + + +/** + * Main function for tool "vbox_cat". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxCat(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + /* Sorted by short ops. */ + { "--show-all", 'a', RTGETOPT_REQ_NOTHING }, + { "--number-nonblank", 'b', RTGETOPT_REQ_NOTHING}, + { NULL, 'e', RTGETOPT_REQ_NOTHING}, + { NULL, 'E', RTGETOPT_REQ_NOTHING}, + { "--flags", 'f', RTGETOPT_REQ_STRING}, + { "--no-content-indexed", VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED, RTGETOPT_REQ_NOTHING}, + { "--number", 'n', RTGETOPT_REQ_NOTHING}, + { "--output", 'o', RTGETOPT_REQ_STRING}, + { "--squeeze-blank", 's', RTGETOPT_REQ_NOTHING}, + { NULL, 't', RTGETOPT_REQ_NOTHING}, + { "--show-tabs", 'T', RTGETOPT_REQ_NOTHING}, + { NULL, 'u', RTGETOPT_REQ_NOTHING}, + { "--show-noneprinting", 'v', RTGETOPT_REQ_NOTHING} + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, 0 /*fFlags*/); + + int rc = VINF_SUCCESS; + + const char *pszOutput = NULL; + RTFILE hOutput = NIL_RTFILE; + uint32_t fFlags = RTFILE_O_CREATE_REPLACE /* Output file flags. */ + | RTFILE_O_WRITE + | RTFILE_O_DENY_WRITE; + + /* Init directory list. */ + RTLISTANCHOR inputList; + RTListInit(&inputList); + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(rc)) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'a': + case 'b': + case 'e': + case 'E': + case 'n': + case 's': + case 't': + case 'T': + case 'v': + RTMsgError("Sorry, option '%s' is not implemented yet!\n", + ValueUnion.pDef->pszLong); + rc = VERR_INVALID_PARAMETER; + break; + + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszCatHelp); + return RTEXITCODE_SUCCESS; + + case 'o': + pszOutput = ValueUnion.psz; + break; + + case 'u': + /* Ignored. */ + break; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED: + fFlags |= RTFILE_O_NOT_CONTENT_INDEXED; + break; + + case VINF_GETOPT_NOT_OPTION: + /* Add file(s) to buffer. This enables processing multiple paths + * at once. + * + * Since the non-options (RTGETOPTINIT_FLAGS_OPTS_FIRST) come last when + * processing this loop it's safe to immediately exit on syntax errors + * or showing the help text (see above). */ + rc = vgsvcToolboxPathBufAddPathEntry(&inputList, ValueUnion.psz); + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + if (RT_SUCCESS(rc)) + { + if (pszOutput) + { + rc = RTFileOpen(&hOutput, pszOutput, fFlags); + if (RT_FAILURE(rc)) + RTMsgError("Could not create output file '%s', rc=%Rrc\n", pszOutput, rc); + } + + if (RT_SUCCESS(rc)) + { + /* Process each input file. */ + RTFILE hInput = NIL_RTFILE; + PVBOXSERVICETOOLBOXPATHENTRY pNodeIt; + RTListForEach(&inputList, pNodeIt, VBOXSERVICETOOLBOXPATHENTRY, Node) + { + rc = RTFileOpen(&hInput, pNodeIt->pszName, + RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + rc = vgsvcToolboxCatOutput(hInput, hOutput); + RTFileClose(hInput); + } + else + { + PCRTSTATUSMSG pMsg = RTErrGet(rc); + if (pMsg) + RTMsgError("Could not open input file '%s': %s\n", pNodeIt->pszName, pMsg->pszMsgFull); + else + RTMsgError("Could not open input file '%s', rc=%Rrc\n", pNodeIt->pszName, rc); + } + + if (RT_FAILURE(rc)) + break; + } + + /* If no input files were defined, process stdin. */ + if (RTListNodeIsFirst(&inputList, &inputList)) + rc = vgsvcToolboxCatOutput(hInput, hOutput); + } + } + + if (hOutput != NIL_RTFILE) + RTFileClose(hOutput); + vgsvcToolboxPathBufDestroy(&inputList); + + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_ACCESS_DENIED: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_ACCESS_DENIED; + + case VERR_FILE_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_FILE_NOT_FOUND; + + case VERR_PATH_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_PATH_NOT_FOUND; + + case VERR_SHARING_VIOLATION: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_SHARING_VIOLATION; + + case VERR_IS_A_DIRECTORY: + return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_IS_A_DIRECTORY; + + default: +#ifdef DEBUG_andy + AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc)); +#endif + break; + } + + return RTEXITCODE_FAILURE; + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Resolves the UID to a name as best as we can. + * + * @returns Read-only name string. Only valid till the next cache call. + * @param pIdCache The ID cache. + * @param uid The UID to resolve. + * @param pszEntry The filename of the UID. + * @param pszRelativeTo What @a pszEntry is relative to, NULL if absolute. + */ +static const char *vgsvcToolboxIdCacheGetUidName(PVGSVCTOOLBOXIDCACHE pIdCache, RTUID uid, + const char *pszEntry, const char *pszRelativeTo) +{ + /* Check cached entries. */ + for (uint32_t i = 0; i < pIdCache->cEntries; i++) + if ( pIdCache->aEntries[i].id == uid + && pIdCache->aEntries[i].fIsUid) + return pIdCache->aEntries[i].szName; + + /* Miss. */ + RTFSOBJINFO ObjInfo; + RT_ZERO(ObjInfo); /* shut up msc */ + int rc; + if (!pszRelativeTo) + rc = RTPathQueryInfoEx(pszEntry, &ObjInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK); + else + { + char szPath[RTPATH_MAX]; + rc = RTPathJoin(szPath, sizeof(szPath), pszRelativeTo, pszEntry); + if (RT_SUCCESS(rc)) + rc = RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK); + } + + if ( RT_SUCCESS(rc) + && ObjInfo.Attr.u.UnixOwner.uid == uid) + { + uint32_t i = pIdCache->cEntries; + if (i < RT_ELEMENTS(pIdCache->aEntries)) + pIdCache->cEntries = i + 1; + else + i = pIdCache->iNextReplace++ % RT_ELEMENTS(pIdCache->aEntries); + pIdCache->aEntries[i].id = uid; + pIdCache->aEntries[i].fIsUid = true; + RTStrCopy(pIdCache->aEntries[i].szName, sizeof(pIdCache->aEntries[i].szName), ObjInfo.Attr.u.UnixOwner.szName); + return pIdCache->aEntries[i].szName; + } + return ""; +} + + +/** + * Resolves the GID to a name as best as we can. + * + * @returns Read-only name string. Only valid till the next cache call. + * @param pIdCache The ID cache. + * @param gid The GID to resolve. + * @param pszEntry The filename of the GID. + * @param pszRelativeTo What @a pszEntry is relative to, NULL if absolute. + */ +static const char *vgsvcToolboxIdCacheGetGidName(PVGSVCTOOLBOXIDCACHE pIdCache, RTGID gid, + const char *pszEntry, const char *pszRelativeTo) +{ + /* Check cached entries. */ + for (uint32_t i = 0; i < pIdCache->cEntries; i++) + if ( pIdCache->aEntries[i].id == gid + && !pIdCache->aEntries[i].fIsUid) + return pIdCache->aEntries[i].szName; + + /* Miss. */ + RTFSOBJINFO ObjInfo; + RT_ZERO(ObjInfo); /* shut up msc */ + int rc; + if (!pszRelativeTo) + rc = RTPathQueryInfoEx(pszEntry, &ObjInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK); + else + { + char szPath[RTPATH_MAX]; + rc = RTPathJoin(szPath, sizeof(szPath), pszRelativeTo, pszEntry); + if (RT_SUCCESS(rc)) + rc = RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK); + } + + if ( RT_SUCCESS(rc) + && ObjInfo.Attr.u.UnixGroup.gid == gid) + { + uint32_t i = pIdCache->cEntries; + if (i < RT_ELEMENTS(pIdCache->aEntries)) + pIdCache->cEntries = i + 1; + else + i = pIdCache->iNextReplace++ % RT_ELEMENTS(pIdCache->aEntries); + pIdCache->aEntries[i].id = gid; + pIdCache->aEntries[i].fIsUid = false; + RTStrCopy(pIdCache->aEntries[i].szName, sizeof(pIdCache->aEntries[i].szName), ObjInfo.Attr.u.UnixGroup.szName); + return pIdCache->aEntries[i].szName; + } + return ""; +} + + +/** + * Prints information (based on given flags) of a file system object (file/directory/...) + * to stdout. + * + * @return IPRT status code. + * @param pszName Object name. + * @param cchName Length of pszName. + * @param fOutputFlags Output / handling flags of type + * VBOXSERVICETOOLBOXOUTPUTFLAG. + * @param pszRelativeTo What pszName is relative to. + * @param pIdCache The ID cache. + * @param pObjInfo Pointer to object information. + */ +static int vgsvcToolboxPrintFsInfo(const char *pszName, size_t cchName, uint32_t fOutputFlags, const char *pszRelativeTo, + PVGSVCTOOLBOXIDCACHE pIdCache, PRTFSOBJINFO pObjInfo) +{ + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertReturn(cchName, VERR_INVALID_PARAMETER); + AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER); + + RTFMODE fMode = pObjInfo->Attr.fMode; + char chFileType; + switch (fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_FIFO: chFileType = 'f'; break; + case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break; + case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break; + case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break; + case RTFS_TYPE_FILE: chFileType = '-'; break; + case RTFS_TYPE_SYMLINK: chFileType = 'l'; break; + case RTFS_TYPE_SOCKET: chFileType = 's'; break; + case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break; + default: chFileType = '?'; break; + } + /** @todo sticy bits++ */ + +/** @todo r=bird: turns out the host doesn't use or need cname_len, so perhaps we could drop it? */ + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_LONG)) + { + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + RTPrintf("ftype=%c%cnode_id=%RU64%inode_dev=%RU32%ccname_len=%zu%cname=%s%c", + chFileType, 0, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0, + (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0, cchName, 0, pszName, 0); + RTPrintf("%c%c", 0, 0); + } + else + RTPrintf("%c %#18llx %3zu %s\n", chFileType, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, cchName, pszName); + } + else + { + char szTimeBirth[RTTIME_STR_LEN]; + char szTimeChange[RTTIME_STR_LEN]; + char szTimeModification[RTTIME_STR_LEN]; + char szTimeAccess[RTTIME_STR_LEN]; + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + RTPrintf("ftype=%c%c", chFileType, 0); + if (pObjInfo->Attr.u.Unix.INodeId || pObjInfo->Attr.u.Unix.INodeIdDevice) + RTPrintf("node_id=%RU64%cinode_dev=%RU32%c", (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0, + (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0); + RTPrintf("owner_mask=%c%c%c%c", + fMode & RTFS_UNIX_IRUSR ? 'r' : '-', + fMode & RTFS_UNIX_IWUSR ? 'w' : '-', + fMode & RTFS_UNIX_IXUSR ? 'x' : '-', 0); + RTPrintf("group_mask=%c%c%c%c", + fMode & RTFS_UNIX_IRGRP ? 'r' : '-', + fMode & RTFS_UNIX_IWGRP ? 'w' : '-', + fMode & RTFS_UNIX_IXGRP ? 'x' : '-', 0); + RTPrintf("other_mask=%c%c%c%c", + fMode & RTFS_UNIX_IROTH ? 'r' : '-', + fMode & RTFS_UNIX_IWOTH ? 'w' : '-', + fMode & RTFS_UNIX_IXOTH ? 'x' : '-', 0); + /** @todo sticky bits. */ + RTPrintf("dos_mask=%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", + fMode & RTFS_DOS_READONLY ? 'R' : '-', + fMode & RTFS_DOS_HIDDEN ? 'H' : '-', + fMode & RTFS_DOS_SYSTEM ? 'S' : '-', + fMode & RTFS_DOS_DIRECTORY ? 'D' : '-', + fMode & RTFS_DOS_ARCHIVED ? 'A' : '-', + fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-', + fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-', + fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-', + fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-', + fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-', + fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-', + fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-', + fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-', + fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-', 0); + RTPrintf("hlinks=%RU32%cst_size=%RI64%calloc=%RI64%c", + pObjInfo->Attr.u.Unix.cHardlinks, 0, + pObjInfo->cbObject, 0, + pObjInfo->cbAllocated, 0); + RTPrintf("st_birthtime=%s%cst_ctime=%s%cst_mtime=%s%cst_atime=%s%c", + RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)), 0, + RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)), 0, + RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)), 0, + RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)), 0); + if (pObjInfo->Attr.u.Unix.uid != NIL_RTUID) + RTPrintf("uid=%RU32%cusername=%s%c", pObjInfo->Attr.u.Unix.uid, 0, + vgsvcToolboxIdCacheGetUidName(pIdCache, pObjInfo->Attr.u.Unix.uid, pszName, pszRelativeTo), 0); + if (pObjInfo->Attr.u.Unix.gid != NIL_RTGID) + RTPrintf("gid=%RU32%cgroupname=%s%c", pObjInfo->Attr.u.Unix.gid, 0, + vgsvcToolboxIdCacheGetGidName(pIdCache, pObjInfo->Attr.u.Unix.gid, pszName, pszRelativeTo), 0); + if ( (RTFS_IS_DEV_BLOCK(pObjInfo->Attr.fMode) || RTFS_IS_DEV_CHAR(pObjInfo->Attr.fMode)) + && pObjInfo->Attr.u.Unix.Device) + RTPrintf("st_rdev=%RU32%c", pObjInfo->Attr.u.Unix.Device, 0); + if (pObjInfo->Attr.u.Unix.GenerationId) + RTPrintf("st_gen=%RU32%c", pObjInfo->Attr.u.Unix.GenerationId, 0); + if (pObjInfo->Attr.u.Unix.fFlags) + RTPrintf("st_flags=%RU32%c", pObjInfo->Attr.u.Unix.fFlags, 0); + RTPrintf("cname_len=%zu%cname=%s%c", cchName, 0, pszName, 0); + RTPrintf("%c%c", 0, 0); /* End of data block. */ + } + else + { + RTPrintf("%c", chFileType); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IRUSR ? 'r' : '-', + fMode & RTFS_UNIX_IWUSR ? 'w' : '-', + fMode & RTFS_UNIX_IXUSR ? 'x' : '-'); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IRGRP ? 'r' : '-', + fMode & RTFS_UNIX_IWGRP ? 'w' : '-', + fMode & RTFS_UNIX_IXGRP ? 'x' : '-'); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IROTH ? 'r' : '-', + fMode & RTFS_UNIX_IWOTH ? 'w' : '-', + fMode & RTFS_UNIX_IXOTH ? 'x' : '-'); + RTPrintf(" %c%c%c%c%c%c%c%c%c%c%c%c%c%c", + fMode & RTFS_DOS_READONLY ? 'R' : '-', + fMode & RTFS_DOS_HIDDEN ? 'H' : '-', + fMode & RTFS_DOS_SYSTEM ? 'S' : '-', + fMode & RTFS_DOS_DIRECTORY ? 'D' : '-', + fMode & RTFS_DOS_ARCHIVED ? 'A' : '-', + fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-', + fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-', + fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-', + fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-', + fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-', + fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-', + fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-', + fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-', + fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-'); + RTPrintf(" %d %4d %4d %10lld %10lld", + pObjInfo->Attr.u.Unix.cHardlinks, + pObjInfo->Attr.u.Unix.uid, + pObjInfo->Attr.u.Unix.gid, + pObjInfo->cbObject, + pObjInfo->cbAllocated); + RTPrintf(" %s %s %s %s", + RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)), + RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)), + RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)), + RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) ); + RTPrintf(" %2zu %s\n", cchName, pszName); + } + } + + return VINF_SUCCESS; +} + + +/** + * Helper routine for ls tool doing the actual parsing and output of + * a specified directory. + * + * @return IPRT status code. + * @param pszDir Directory (path) to ouptut. + * @param fFlags Flags of type VBOXSERVICETOOLBOXLSFLAG. + * @param fOutputFlags Flags of type VBOXSERVICETOOLBOXOUTPUTFLAG. + * @param pIdCache The ID cache. + */ +static int vgsvcToolboxLsHandleDir(const char *pszDir, uint32_t fFlags, uint32_t fOutputFlags, PVGSVCTOOLBOXIDCACHE pIdCache) +{ + AssertPtrReturn(pszDir, VERR_INVALID_PARAMETER); + + if (fFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + RTPrintf("dname=%s%c", pszDir, 0); + else if (fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE) + RTPrintf("%s:\n", pszDir); + + char szPathAbs[RTPATH_MAX + 1]; + int rc = RTPathAbs(pszDir, szPathAbs, sizeof(szPathAbs)); + if (RT_FAILURE(rc)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Failed to retrieve absolute path of '%s', rc=%Rrc\n", pszDir, rc); + return rc; + } + + RTDIR hDir; + rc = RTDirOpen(&hDir, szPathAbs); + if (RT_FAILURE(rc)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Failed to open directory '%s', rc=%Rrc\n", szPathAbs, rc); + return rc; + } + + RTLISTANCHOR dirList; + RTListInit(&dirList); + + /* To prevent races we need to read in the directory entries once + * and process them afterwards: First loop is displaying the current + * directory's content and second loop is diving deeper into + * sub directories (if wanted). */ +/** @todo r=bird: Which races are these exactly??? Please, do considering that directory with half a + * million files in it, because this isn't going to fly well there (especially not in the recursive case)... + * So, this needs to be rewritten unless there is an actual race you're avoiding by doing this! */ + do + { + RTDIRENTRYEX DirEntry; + rc = RTDirReadEx(hDir, &DirEntry, NULL, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + { + PVBOXSERVICETOOLBOXDIRENTRY pNode = (PVBOXSERVICETOOLBOXDIRENTRY)RTMemAlloc(sizeof(VBOXSERVICETOOLBOXDIRENTRY)); + if (pNode) + { + memcpy(&pNode->dirEntry, &DirEntry, sizeof(RTDIRENTRYEX)); + RTListAppend(&dirList, &pNode->Node); + } + else + rc = VERR_NO_MEMORY; + } + /** @todo r=bird: missing DirEntry overflow handling. */ + } while (RT_SUCCESS(rc)); + + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + int rc2 = RTDirClose(hDir); + if (RT_FAILURE(rc2)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Failed to close dir '%s', rc=%Rrc\n", pszDir, rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (RT_SUCCESS(rc)) + { + PVBOXSERVICETOOLBOXDIRENTRY pNodeIt; + RTListForEach(&dirList, pNodeIt, VBOXSERVICETOOLBOXDIRENTRY, Node) + { + rc = vgsvcToolboxPrintFsInfo(pNodeIt->dirEntry.szName, pNodeIt->dirEntry.cbName, fOutputFlags, + szPathAbs, pIdCache, &pNodeIt->dirEntry.Info); + if (RT_FAILURE(rc)) + break; + } + + /* If everything went fine we do the second run (if needed) ... */ + if ( RT_SUCCESS(rc) + && (fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE)) + { + /* Process all sub-directories. */ + RTListForEach(&dirList, pNodeIt, VBOXSERVICETOOLBOXDIRENTRY, Node) + { + RTFMODE fMode = pNodeIt->dirEntry.Info.Attr.fMode; + switch (fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_SYMLINK: + if (!(fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS)) + break; + RT_FALL_THRU(); + case RTFS_TYPE_DIRECTORY: + { + const char *pszName = pNodeIt->dirEntry.szName; + if ( !RTStrICmp(pszName, ".") /** @todo r=bird: Please do explain what the upper/lower casing of '.' is! I'm really curious. */ + || !RTStrICmp(pszName, "..")) /** @todo r=bird: There is a RTDir API for checking these. Use it! */ + { + /* Skip dot directories. */ + continue; + } + + char szPath[RTPATH_MAX]; /** @todo r=bird: This is going to kill your stack pretty quickly if deep + * directory nesting. There is another buffer further up the function too. + * You need to share the path buffer between recursions! There should be + * several examples of how to efficiently traverse a tree. */ + rc = RTPathJoin(szPath, sizeof(szPath), pszDir, pNodeIt->dirEntry.szName); + if (RT_SUCCESS(rc)) + rc = vgsvcToolboxLsHandleDir(szPath, fFlags, fOutputFlags, pIdCache); + break; + } + + default: /* Ignore the rest. */ + break; + } + if (RT_FAILURE(rc)) + break; + } + } + } + + /* Clean up the mess. */ + PVBOXSERVICETOOLBOXDIRENTRY pNode, pSafe; + RTListForEachSafe(&dirList, pNode, pSafe, VBOXSERVICETOOLBOXDIRENTRY, Node) + { + RTListNodeRemove(&pNode->Node); + RTMemFree(pNode); + } + return rc; +} + + +/** @todo Document options! */ +static char g_paszLsHelp[] = + " VBoxService [--use-toolbox] vbox_ls [<general options>] [option]...\n" + " [<file>...]\n\n" + "List information about files (the current directory by default).\n\n" + "Options:\n\n" + " [--dereference|-L]\n" + " [-l][-R]\n" + " [--verbose|-v]\n" + " [<file>...]\n" + "\n"; + + +/** + * Main function for tool "vbox_ls". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxLs(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING }, + { "--dereference", 'L', RTGETOPT_REQ_NOTHING }, + { NULL, 'l', RTGETOPT_REQ_NOTHING }, + { NULL, 'R', RTGETOPT_REQ_NOTHING }, + { "--verbose", VBOXSERVICETOOLBOXOPT_VERBOSE, RTGETOPT_REQ_NOTHING} + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), + 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_INIT); + + bool fVerbose = false; + uint32_t fFlags = VBOXSERVICETOOLBOXLSFLAG_NONE; + uint32_t fOutputFlags = VBOXSERVICETOOLBOXOUTPUTFLAG_NONE; + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(rc) /** @todo r=bird: WTF is this doing here? rc isn't set in the loop!! And there is an AssertRCReturn after the previous place it was set. */) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszLsHelp); + return RTEXITCODE_SUCCESS; + + case 'L': /* Dereference symlinks. */ + fFlags |= VBOXSERVICETOOLBOXLSFLAG_SYMLINKS; + break; + + case 'l': /* Print long format. */ + fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_LONG; + break; + + case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE: + fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE; + break; + + case 'R': /* Recursive processing. */ + fFlags |= VBOXSERVICETOOLBOXLSFLAG_RECURSIVE; + break; + + case VBOXSERVICETOOLBOXOPT_VERBOSE: + fVerbose = true; + break; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VINF_GETOPT_NOT_OPTION: + Assert(GetState.iNext); + GetState.iNext--; + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + + /* All flags / options processed? Bail out here. + * Processing the file / directory list comes down below. */ + if (ch == VINF_GETOPT_NOT_OPTION) + break; + } + + if (RT_SUCCESS(rc)) /** @todo r=bird: WTF?!? The state handling here is certifiably insane. Crap like this drives me CRAZY!! */ + { + /* Print magic/version. */ + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + rc = vgsvcToolboxStrmInit(); + if (RT_FAILURE(rc)) + RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc); + vgsvcToolboxPrintStrmHeader("vbt_ls", 1 /* Stream version */); + } + + VGSVCTOOLBOXIDCACHE IdCache; + RT_ZERO(IdCache); + + ch = RTGetOpt(&GetState, &ValueUnion); + do + { + char *pszEntry = NULL; /** @todo r=bird: Bad name choice. pszEntry sounds like RTDIRENTRY::szName, i.e. no path. */ + + if (ch == 0) /* Use current directory if no element specified. */ + { + char szDirCur[RTPATH_MAX + 1]; /** @todo r=bird: Just put this outside the if(ch==0) and make pszEntry point to it. There is no need to duplicate any strings here! */ + rc = RTPathGetCurrent(szDirCur, sizeof(szDirCur)); + if (RT_FAILURE(rc)) + RTMsgError("Getting current directory failed, rc=%Rrc\n", rc); + + pszEntry = RTStrDup(szDirCur); + if (!pszEntry) + RTMsgError("Allocating current directory failed\n"); + } + else + { + pszEntry = RTStrDup(ValueUnion.psz); + if (!pszEntry) + RTMsgError("Allocating directory '%s' failed\n", ValueUnion.psz); + } + + /** @todo r=bird: RTFileExists == RTPathQueryInfo, so just do + * RTPathQueryInfoEx here! Also, you _need_ to figure out whether or + * not to follow "commandline" links! */ + if (RTFileExists(pszEntry)) + { + RTFSOBJINFO objInfo; + int rc2 = RTPathQueryInfoEx(pszEntry, &objInfo, + RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK /** @todo Follow link? */); + if (RT_FAILURE(rc2)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Cannot access '%s': No such file or directory\n", pszEntry); + rc = VERR_FILE_NOT_FOUND; + /* Do not break here -- process every element in the list + * and keep failing rc. */ + } + else + { + rc2 = vgsvcToolboxPrintFsInfo(pszEntry, strlen(pszEntry), fOutputFlags, NULL, &IdCache, &objInfo); + if (RT_FAILURE(rc2)) + rc = rc2; + } + } + else + { + int rc2 = vgsvcToolboxLsHandleDir(pszEntry, fFlags, fOutputFlags, &IdCache); + if (RT_FAILURE(rc2)) + rc = rc2; + } + + RTStrFree(pszEntry); + } while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0); + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + vgsvcToolboxPrintStrmTermination(); + } + else if (fVerbose) + RTMsgError("Failed with rc=%Rrc\n", rc); + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/* Try using RTPathRmCmd. */ +static RTEXITCODE vgsvcToolboxRm(int argc, char **argv) +{ + return RTPathRmCmd(argc, argv); +} + + +static char g_paszMkTempHelp[] = + " VBoxService [--use-toolbox] vbox_mktemp [<general options>] [<options>]\n" + " <template>\n\n" + "Create a temporary directory based on the template supplied. The first string\n" + "of consecutive 'X' characters in the template will be replaced to form a unique\n" + "name for the directory. The template may not contain a path. The default\n" + "creation mode is 0600 for files and 0700 for directories. If no path is\n" + "specified the default temporary directory will be used.\n" + "Options:\n\n" + " [--directory|-d] Create a directory instead of a file.\n" + " [--mode|-m <mode>] Create the object with mode <mode>.\n" + " [--secure|-s] Fail if the object cannot be created securely.\n" + " [--tmpdir|-t <path>] Create the object with the absolute path <path>.\n" + "\n"; + + +/** + * Report the result of a vbox_mktemp operation. + * + * Either errors to stderr (not machine-readable) or everything to stdout as + * {name}\0{rc}\0 (machine- readable format). The message may optionally + * contain a '%s' for the file name and an %Rrc for the result code in that + * order. In future a "verbose" flag may be added, without which nothing will + * be output in non-machine- readable mode. Sets prc if rc is a non-success + * code. + */ +static void toolboxMkTempReport(const char *pcszMessage, const char *pcszFile, + bool fActive, int rc, uint32_t fOutputFlags, int *prc) +{ + if (!fActive) + return; + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + if (RT_SUCCESS(rc)) + RTPrintf(pcszMessage, pcszFile, rc); + else + RTMsgError(pcszMessage, pcszFile, rc); + else + RTPrintf("name=%s%crc=%d%c", pcszFile, 0, rc, 0); + if (prc && RT_FAILURE(rc)) + *prc = rc; +} + + +/** + * Main function for tool "vbox_mktemp". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxMkTemp(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, + RTGETOPT_REQ_NOTHING }, + { "--directory", 'd', RTGETOPT_REQ_NOTHING }, + { "--mode", 'm', RTGETOPT_REQ_STRING }, + { "--secure", 's', RTGETOPT_REQ_NOTHING }, + { "--tmpdir", 't', RTGETOPT_REQ_STRING }, + }; + + enum + { + /* Isn't that a bit long? s/VBOXSERVICETOOLBOX/VSTB/ ? */ + /** Create a temporary directory instead of a temporary file. */ + VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY = RT_BIT_32(0), + /** Only create the temporary object if the operation is expected + * to be secure. Not guaranteed to be supported on a particular + * set-up. */ + VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE = RT_BIT_32(1) + }; + + int ch, rc; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_INIT); + + uint32_t fFlags = 0; + uint32_t fOutputFlags = 0; + int cNonOptions = 0; + RTFMODE fMode = 0700; + bool fModeSet = false; + const char *pcszPath = NULL; + const char *pcszTemplate; + char szTemplateWithPath[RTPATH_MAX] = ""; + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(rc)) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszMkTempHelp); + return RTEXITCODE_SUCCESS; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE: + fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE; + break; + + case 'd': + fFlags |= VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY; + break; + + case 'm': + rc = vgsvcToolboxParseMode(ValueUnion.psz, &fMode); + if (RT_FAILURE(rc)) + return RTEXITCODE_SYNTAX; + fModeSet = true; +#ifndef RT_OS_WINDOWS + umask(0); /* RTDirCreate workaround */ +#endif + break; + case 's': + fFlags |= VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE; + break; + + case 't': + pcszPath = ValueUnion.psz; + break; + + case VINF_GETOPT_NOT_OPTION: + /* RTGetOpt will sort these to the end of the argv vector so + * that we will deal with them afterwards. */ + ++cNonOptions; + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + /* Print magic/version. */ + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) + { + rc = vgsvcToolboxStrmInit(); + if (RT_FAILURE(rc)) + RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc); + vgsvcToolboxPrintStrmHeader("vbt_mktemp", 1 /* Stream version */); + } + + if (fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE && fModeSet) + { + toolboxMkTempReport("'-s' and '-m' parameters cannot be used together.\n", "", + true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_SYNTAX; + } + + /* We need exactly one template, containing at least one 'X'. */ + if (cNonOptions != 1) + { + toolboxMkTempReport("Please specify exactly one template.\n", "", true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_SYNTAX; + } + pcszTemplate = argv[argc - 1]; + + /* Validate that the template is as IPRT requires (asserted by IPRT). */ + if ( RTPathHasPath(pcszTemplate) + || ( !strstr(pcszTemplate, "XXX") + && pcszTemplate[strlen(pcszTemplate) - 1] != 'X')) + { + toolboxMkTempReport("Template '%s' should contain a file name with no path and at least three consecutive 'X' characters or ending in 'X'.\n", + pcszTemplate, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + if (pcszPath && !RTPathStartsWithRoot(pcszPath)) + { + toolboxMkTempReport("Path '%s' should be absolute.\n", pcszPath, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + if (pcszPath) + { + rc = RTStrCopy(szTemplateWithPath, sizeof(szTemplateWithPath), pcszPath); + if (RT_FAILURE(rc)) + { + toolboxMkTempReport("Path '%s' too long.\n", pcszPath, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + } + else + { + rc = RTPathTemp(szTemplateWithPath, sizeof(szTemplateWithPath)); + if (RT_FAILURE(rc)) + { + toolboxMkTempReport("Failed to get the temporary directory.\n", "", true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + } + rc = RTPathAppend(szTemplateWithPath, sizeof(szTemplateWithPath), pcszTemplate); + if (RT_FAILURE(rc)) + { + toolboxMkTempReport("Template '%s' too long for path.\n", pcszTemplate, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc); + return RTEXITCODE_FAILURE; + } + + if (fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY) + { + rc = fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE + ? RTDirCreateTempSecure(szTemplateWithPath) + : RTDirCreateTemp(szTemplateWithPath, fMode); + toolboxMkTempReport("Created temporary directory '%s'.\n", + szTemplateWithPath, RT_SUCCESS(rc), rc, + fOutputFlags, NULL); + /* RTDirCreateTemp[Secure] sets the template to "" on failure. */ + toolboxMkTempReport("The following error occurred while creating a temporary directory from template '%s': %Rrc.\n", + pcszTemplate, RT_FAILURE(rc), rc, fOutputFlags, NULL /*prc*/); + } + else + { + rc = fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE + ? RTFileCreateTempSecure(szTemplateWithPath) + : RTFileCreateTemp(szTemplateWithPath, fMode); + toolboxMkTempReport("Created temporary file '%s'.\n", + szTemplateWithPath, RT_SUCCESS(rc), rc, + fOutputFlags, NULL); + /* RTFileCreateTemp[Secure] sets the template to "" on failure. */ + toolboxMkTempReport("The following error occurred while creating a temporary file from template '%s': %Rrc.\n", + pcszTemplate, RT_FAILURE(rc), rc, fOutputFlags, NULL /*prc*/); + } + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + vgsvcToolboxPrintStrmTermination(); + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** @todo Document options! */ +static char g_paszMkDirHelp[] = + " VBoxService [--use-toolbox] vbox_mkdir [<general options>] [<options>]\n" + " <directory>...\n\n" + "Options:\n\n" + " [--mode|-m <mode>] The file mode to set (chmod) on the created\n" + " directories. Default: a=rwx & umask.\n" + " [--parents|-p] Create parent directories as needed, no\n" + " error if the directory already exists.\n" + " [--verbose|-v] Display a message for each created directory.\n" + "\n"; + + +/** + * Main function for tool "vbox_mkdir". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxMkDir(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--mode", 'm', RTGETOPT_REQ_STRING }, + { "--parents", 'p', RTGETOPT_REQ_NOTHING}, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING} + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), + 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + AssertRCReturn(rc, RTEXITCODE_INIT); + + bool fMakeParentDirs = false; + bool fVerbose = false; + RTFMODE fDirMode = RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG | RTFS_UNIX_IRWXO; + int cDirsCreated = 0; + + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'p': + fMakeParentDirs = true; + break; + + case 'm': + rc = vgsvcToolboxParseMode(ValueUnion.psz, &fDirMode); + if (RT_FAILURE(rc)) + return RTEXITCODE_SYNTAX; +#ifndef RT_OS_WINDOWS + umask(0); /* RTDirCreate workaround */ +#endif + break; + + case 'v': + fVerbose = true; + break; + + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszMkDirHelp); + return RTEXITCODE_SUCCESS; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VINF_GETOPT_NOT_OPTION: + if (fMakeParentDirs) + /** @todo r=bird: If fVerbose is set, we should also show + * which directories that get created, parents as well as + * omitting existing final dirs. Annoying, but check any + * mkdir implementation (try "mkdir -pv asdf/1/2/3/4" + * twice). */ + rc = RTDirCreateFullPath(ValueUnion.psz, fDirMode); + else + rc = RTDirCreate(ValueUnion.psz, fDirMode, 0); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Could not create directory '%s': %Rra\n", + ValueUnion.psz, rc); + if (fVerbose) + RTMsgInfo("Created directory '%s', mode %#RTfmode\n", ValueUnion.psz, fDirMode); + cDirsCreated++; + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + AssertRC(rc); + + if (cDirsCreated == 0) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No directory argument."); + + return RTEXITCODE_SUCCESS; +} + + +/** @todo Document options! */ +static char g_paszStatHelp[] = + " VBoxService [--use-toolbox] vbox_stat [<general options>] [<options>]\n" + " <file>...\n\n" + "Display file or file system status.\n\n" + "Options:\n\n" + " [--file-system|-f]\n" + " [--dereference|-L]\n" + " [--terse|-t]\n" + " [--verbose|-v]\n" + "\n"; + + +/** + * Main function for tool "vbox_stat". + * + * @return RTEXITCODE. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + */ +static RTEXITCODE vgsvcToolboxStat(int argc, char **argv) +{ + static const RTGETOPTDEF s_aOptions[] = + { + { "--file-system", 'f', RTGETOPT_REQ_NOTHING }, + { "--dereference", 'L', RTGETOPT_REQ_NOTHING }, + { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING }, + { "--terse", 't', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING } + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST); + + int rc = VINF_SUCCESS; + uint32_t fOutputFlags = VBOXSERVICETOOLBOXOUTPUTFLAG_LONG; /* Use long mode by default. */ + uint32_t fQueryInfoFlags = RTPATH_F_ON_LINK; + + while ( (ch = RTGetOpt(&GetState, &ValueUnion)) + && RT_SUCCESS(rc)) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'f': + RTMsgError("Sorry, option '%s' is not implemented yet!\n", ValueUnion.pDef->pszLong); + rc = VERR_INVALID_PARAMETER; + break; + + case 'L': + fQueryInfoFlags &= ~RTPATH_F_ON_LINK; + fQueryInfoFlags |= RTPATH_F_FOLLOW_LINK; + break; + + case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE: + fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE; + break; + + case 'h': + vgsvcToolboxShowUsageHeader(); + RTPrintf("%s", g_paszStatHelp); + return RTEXITCODE_SUCCESS; + + case 'V': + vgsvcToolboxShowVersion(); + return RTEXITCODE_SUCCESS; + + case VINF_GETOPT_NOT_OPTION: + { + Assert(GetState.iNext); + GetState.iNext--; + break; + } + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + + /* All flags / options processed? Bail out here. + * Processing the file / directory list comes down below. */ + if (ch == VINF_GETOPT_NOT_OPTION) + break; + } + + if (RT_SUCCESS(rc)) + { + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + { + rc = vgsvcToolboxStrmInit(); + if (RT_FAILURE(rc)) + RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc); + vgsvcToolboxPrintStrmHeader("vbt_stat", 1 /* Stream version */); + } + + VGSVCTOOLBOXIDCACHE IdCache; + RT_ZERO(IdCache); + + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + RTFSOBJINFO objInfo; + int rc2 = RTPathQueryInfoEx(ValueUnion.psz, &objInfo, RTFSOBJATTRADD_UNIX, fQueryInfoFlags); + if (RT_FAILURE(rc2)) + { + if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)) + RTMsgError("Cannot stat for '%s': %Rrc\n", ValueUnion.psz, rc2); + } + else + rc2 = vgsvcToolboxPrintFsInfo(ValueUnion.psz, strlen(ValueUnion.psz), fOutputFlags, NULL, &IdCache, &objInfo); + /** @todo r=bird: You're checking rc not rc2 here... */ + if (RT_SUCCESS(rc)) + rc = rc2; + /* Do not break here -- process every element in the list + * and keep (initial) failing rc. */ + } + + if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */ + vgsvcToolboxPrintStrmTermination(); + + /* At this point the overall result (success/failure) should be in rc. */ + } + else + RTMsgError("Failed with rc=%Rrc\n", rc); + + if (RT_FAILURE(rc)) + { + switch (rc) + { + case VERR_ACCESS_DENIED: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_ACCESS_DENIED; + + case VERR_FILE_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_FILE_NOT_FOUND; + + case VERR_PATH_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_PATH_NOT_FOUND; + + case VERR_NET_PATH_NOT_FOUND: + return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_NET_PATH_NOT_FOUND; + + default: +#ifdef DEBUG_andy + AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc)); +#endif + break; + } + + return RTEXITCODE_FAILURE; + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * Looks up the tool definition entry for the tool give by @a pszTool. + * + * @returns Pointer to the tool definition. NULL if not found. + * @param pszTool The name of the tool. + */ +static PCVBOXSERVICETOOLBOXTOOL vgsvcToolboxLookUp(const char *pszTool) +{ + AssertPtrReturn(pszTool, NULL); + + /* Do a linear search, since we don't have that much stuff in the table. */ + for (unsigned i = 0; i < RT_ELEMENTS(g_aTools); i++) + if (!strcmp(g_aTools[i].pszName, pszTool)) + return &g_aTools[i]; + + return NULL; +} + + +/** + * Converts a tool's exit code back to an IPRT error code. + * + * @return Converted IPRT status code. + * @param pszTool Name of the toolbox tool to convert exit code for. + * @param rcExit The tool's exit code to convert. + */ +int VGSvcToolboxExitCodeConvertToRc(const char *pszTool, RTEXITCODE rcExit) +{ + AssertPtrReturn(pszTool, VERR_INVALID_POINTER); + + PCVBOXSERVICETOOLBOXTOOL pTool = vgsvcToolboxLookUp(pszTool); + if (pTool) + return pTool->pfnExitCodeConvertToRc(rcExit); + + AssertMsgFailed(("Tool '%s' not found\n", pszTool)); + return VERR_GENERAL_FAILURE; /* Lookup failed, should not happen. */ +} + + +/** + * Entry point for internal toolbox. + * + * @return True if an internal tool was handled, false if not. + * @param argc Number of arguments. + * @param argv Pointer to argument array. + * @param prcExit Where to store the exit code when an + * internal toolbox command was handled. + */ +bool VGSvcToolboxMain(int argc, char **argv, RTEXITCODE *prcExit) +{ + + /* + * Check if the file named in argv[0] is one of the toolbox programs. + */ + AssertReturn(argc > 0, false); + const char *pszTool = RTPathFilename(argv[0]); + PCVBOXSERVICETOOLBOXTOOL pTool = vgsvcToolboxLookUp(pszTool); + if (!pTool) + { + /* + * For debugging and testing purposes we also allow toolbox program access + * when the first VBoxService argument is --use-toolbox. + */ + if (argc < 2 || strcmp(argv[1], "--use-toolbox")) + return false; + + /* No tool specified? Show toolbox help. */ + if (argc < 3) + { + vgsvcToolboxShowUsage(); + *prcExit = RTEXITCODE_SYNTAX; + return true; + } + + argc -= 2; + argv += 2; + pszTool = argv[0]; + pTool = vgsvcToolboxLookUp(pszTool); + if (!pTool) + { + *prcExit = RTEXITCODE_SUCCESS; + if (!strcmp(pszTool, "-V")) + { + vgsvcToolboxShowVersion(); + return true; + } + if ( strcmp(pszTool, "help") + && strcmp(pszTool, "--help") + && strcmp(pszTool, "-h")) + *prcExit = RTEXITCODE_SYNTAX; + vgsvcToolboxShowUsage(); + return true; + } + } + + /* + * Invoke the handler. + */ + RTMsgSetProgName("VBoxService/%s", pszTool); + AssertPtr(pTool); + *prcExit = pTool->pfnHandler(argc, argv); + + return true; +} + |