VirtualBox

source: vbox/trunk/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp@ 98709

Last change on this file since 98709 was 98709, checked in by vboxsync, 22 months ago

Guest Control: Implemented directory handling / walking as non-toolbox variants. bugref:9783

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 60.3 KB
Line 
1/* $Id: VBoxServiceToolBox.cpp 98709 2023-02-24 08:49:40Z vboxsync $ */
2/** @file
3 * VBoxServiceToolbox - Internal (BusyBox-like) toolbox.
4 */
5
6/*
7 * Copyright (C) 2012-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include <iprt/assert.h>
33#include <iprt/buildconfig.h>
34#include <iprt/dir.h>
35#include <iprt/file.h>
36#include <iprt/getopt.h>
37#include <iprt/list.h>
38#include <iprt/mem.h>
39#include <iprt/message.h>
40#include <iprt/path.h>
41#include <iprt/string.h>
42#include <iprt/stream.h>
43#include <iprt/symlink.h>
44
45#ifndef RT_OS_WINDOWS
46# include <sys/stat.h> /* need umask */
47#endif
48
49#include <VBox/VBoxGuestLib.h>
50#include <VBox/version.h>
51
52#include <VBox/GuestHost/GuestControl.h>
53
54#include "VBoxServiceInternal.h"
55#include "VBoxServiceToolBox.h"
56#include "VBoxServiceUtils.h"
57
58using namespace guestControl;
59
60
61/*********************************************************************************************************************************
62* Defined Constants And Macros *
63*********************************************************************************************************************************/
64
65/** Generic option indices for commands. */
66enum
67{
68 VBOXSERVICETOOLBOXOPT_MACHINE_READABLE = 1000,
69 VBOXSERVICETOOLBOXOPT_VERBOSE
70};
71
72/** Options indices for "vbox_cat". */
73typedef enum VBOXSERVICETOOLBOXCATOPT
74{
75 VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED = 1000
76} VBOXSERVICETOOLBOXCATOPT;
77
78/** Flags for "vbox_ls". */
79typedef enum VBOXSERVICETOOLBOXLSFLAG
80{
81 VBOXSERVICETOOLBOXLSFLAG_NONE,
82 VBOXSERVICETOOLBOXLSFLAG_RECURSIVE,
83 VBOXSERVICETOOLBOXLSFLAG_SYMLINKS
84} VBOXSERVICETOOLBOXLSFLAG;
85
86/** Flags for fs object output. */
87typedef enum VBOXSERVICETOOLBOXOUTPUTFLAG
88{
89 VBOXSERVICETOOLBOXOUTPUTFLAG_NONE,
90 VBOXSERVICETOOLBOXOUTPUTFLAG_LONG,
91 VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE
92} VBOXSERVICETOOLBOXOUTPUTFLAG;
93
94/** The size of the directory entry buffer we're using. */
95#define VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE (sizeof(RTDIRENTRYEX) + RTPATH_MAX)
96
97
98/*********************************************************************************************************************************
99* Structures and Typedefs *
100*********************************************************************************************************************************/
101/** Pointer to a tool handler function. */
102typedef RTEXITCODE (*PFNHANDLER)(int , char **);
103
104/** Definition for a specific toolbox tool. */
105typedef struct VBOXSERVICETOOLBOXTOOL
106{
107 /** Friendly name of the tool. */
108 const char *pszName;
109 /** Main handler to be invoked to use the tool. */
110 RTEXITCODE (*pfnHandler)(int argc, char **argv);
111 /** Conversion routine to convert the tool's exit code back to an IPRT rc. Optional.
112 *
113 * @todo r=bird: You better revert this, i.e. having pfnHandler return a VBox
114 * status code and have a routine for converting it to RTEXITCODE.
115 * Unless, what you really want to do here is to get a cached status, in
116 * which case you better call it what it is.
117 */
118 int (*pfnExitCodeConvertToRc)(RTEXITCODE rcExit);
119} VBOXSERVICETOOLBOXTOOL;
120/** Pointer to a const tool definition. */
121typedef VBOXSERVICETOOLBOXTOOL const *PCVBOXSERVICETOOLBOXTOOL;
122
123/**
124 * An file/directory entry. Used to cache
125 * file names/paths for later processing.
126 */
127typedef struct VBOXSERVICETOOLBOXPATHENTRY
128{
129 /** Our node. */
130 RTLISTNODE Node;
131 /** Name of the entry. */
132 char *pszName;
133} VBOXSERVICETOOLBOXPATHENTRY, *PVBOXSERVICETOOLBOXPATHENTRY;
134
135
136/*********************************************************************************************************************************
137* Internal Functions *
138*********************************************************************************************************************************/
139static RTEXITCODE vgsvcToolboxCat(int argc, char **argv);
140static RTEXITCODE vgsvcToolboxLs(int argc, char **argv);
141static RTEXITCODE vgsvcToolboxRm(int argc, char **argv);
142static RTEXITCODE vgsvcToolboxMkTemp(int argc, char **argv);
143static RTEXITCODE vgsvcToolboxMkDir(int argc, char **argv);
144static RTEXITCODE vgsvcToolboxStat(int argc, char **argv);
145
146
147/*********************************************************************************************************************************
148* Global Variables *
149*********************************************************************************************************************************/
150/** Tool definitions. */
151static VBOXSERVICETOOLBOXTOOL const g_aTools[] =
152{
153 { VBOXSERVICE_TOOL_CAT, vgsvcToolboxCat , NULL },
154 { VBOXSERVICE_TOOL_LS, vgsvcToolboxLs , NULL },
155 { VBOXSERVICE_TOOL_RM, vgsvcToolboxRm , NULL },
156 { VBOXSERVICE_TOOL_MKTEMP, vgsvcToolboxMkTemp, NULL },
157 { VBOXSERVICE_TOOL_MKDIR, vgsvcToolboxMkDir , NULL },
158 { VBOXSERVICE_TOOL_STAT, vgsvcToolboxStat , NULL }
159};
160
161
162
163
164/**
165 * Displays a common header for all help text to stdout.
166 */
167static void vgsvcToolboxShowUsageHeader(void)
168{
169 RTPrintf(VBOX_PRODUCT " Guest Toolbox Version "
170 VBOX_VERSION_STRING "\n"
171 "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
172 RTPrintf("Usage:\n\n");
173}
174
175
176/**
177 * Displays a help text to stdout.
178 */
179static void vgsvcToolboxShowUsage(void)
180{
181 vgsvcToolboxShowUsageHeader();
182 RTPrintf(" VBoxService [--use-toolbox] vbox_<command> [<general options>] <parameters>\n\n"
183 "General options:\n\n"
184 " --machinereadable produce all output in machine-readable form\n"
185 " -V print version number and exit\n"
186 "\n"
187 "Commands:\n\n"
188 " vbox_cat [<general options>] <file>...\n"
189 " vbox_ls [<general options>] [--dereference|-L] [-l] [-R]\n"
190 " [--verbose|-v] [<file>...]\n"
191 " vbox_rm [<general options>] [-r|-R] <file>...\n"
192 " vbox_mktemp [<general options>] [--directory|-d] [--mode|-m <mode>]\n"
193 " [--secure|-s] [--tmpdir|-t <path>] <template>\n"
194 " vbox_mkdir [<general options>] [--mode|-m <mode>] [--parents|-p]\n"
195 " [--verbose|-v] <directory>...\n"
196 " vbox_stat [<general options>] [--file-system|-f]\n"
197 " [--dereference|-L] [--terse|-t] [--verbose|-v] <file>...\n"
198 "\n");
199}
200
201
202/**
203 * Displays the program's version number.
204 */
205static void vgsvcToolboxShowVersion(void)
206{
207 RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
208}
209
210
211/**
212 * Initializes the parseable stream(s).
213 *
214 * @return IPRT status code.
215 */
216static int vgsvcToolboxStrmInit(void)
217{
218 /* Set stdout's mode to binary. This is required for outputting all the machine-readable
219 * data correctly. */
220 int rc = RTStrmSetMode(g_pStdOut, true /* Binary mode */, -1 /* Current code set, not changed */);
221 if (RT_FAILURE(rc))
222 RTMsgError("Unable to set stdout to binary mode, rc=%Rrc\n", rc);
223
224 return rc;
225}
226
227
228/**
229 * Prints a parseable stream header which contains the actual tool
230 * which was called/used along with its stream version.
231 *
232 * @param pszToolName Name of the tool being used, e.g. "vbt_ls".
233 * @param uVersion Stream version name. Handy for distinguishing
234 * different stream versions later.
235 */
236static void vgsvcToolboxPrintStrmHeader(const char *pszToolName, uint32_t uVersion)
237{
238 AssertPtrReturnVoid(pszToolName);
239 RTPrintf("hdr_id=%s%chdr_ver=%u%c", pszToolName, 0, uVersion, 0);
240}
241
242
243/**
244 * Prints a standardized termination sequence indicating that the
245 * parseable stream just ended.
246 *
247 */
248static void vgsvcToolboxPrintStrmTermination()
249{
250 RTPrintf("%c%c%c%c", 0, 0, 0, 0);
251}
252
253
254/**
255 * Parse a file mode string from the command line (currently octal only)
256 * and print an error message and return an error if necessary.
257 */
258static int vgsvcToolboxParseMode(const char *pcszMode, RTFMODE *pfMode)
259{
260 int rc = RTStrToUInt32Ex(pcszMode, NULL, 8 /* Base */, pfMode);
261 if (RT_FAILURE(rc)) /* Only octet based values supported right now! */
262 RTMsgError("Mode flag strings not implemented yet! Use octal numbers instead. (%s)\n", pcszMode);
263 return rc;
264}
265
266
267/**
268 * Destroys a path buffer list.
269 *
270 * @return IPRT status code.
271 * @param pList Pointer to list to destroy.
272 */
273static void vgsvcToolboxPathBufDestroy(PRTLISTNODE pList)
274{
275 if (!pList)
276 return;
277
278 PVBOXSERVICETOOLBOXPATHENTRY pEntry, pEntryNext;
279 RTListForEachSafe(pList, pEntry, pEntryNext, VBOXSERVICETOOLBOXPATHENTRY, Node)
280 {
281 RTListNodeRemove(&pEntry->Node);
282
283 RTStrFree(pEntry->pszName);
284 RTMemFree(pEntry);
285 }
286}
287
288
289/**
290 * Adds a path entry (file/directory/whatever) to a given path buffer list.
291 *
292 * @return IPRT status code.
293 * @param pList Pointer to list to add entry to.
294 * @param pszName Name of entry to add.
295 */
296static int vgsvcToolboxPathBufAddPathEntry(PRTLISTNODE pList, const char *pszName)
297{
298 AssertPtrReturn(pList, VERR_INVALID_PARAMETER);
299
300 int rc = VINF_SUCCESS;
301 PVBOXSERVICETOOLBOXPATHENTRY pNode = (PVBOXSERVICETOOLBOXPATHENTRY)RTMemAlloc(sizeof(VBOXSERVICETOOLBOXPATHENTRY));
302 if (pNode)
303 {
304 pNode->pszName = RTStrDup(pszName);
305 AssertPtr(pNode->pszName);
306
307 RTListAppend(pList, &pNode->Node);
308 }
309 else
310 rc = VERR_NO_MEMORY;
311 return rc;
312}
313
314
315/**
316 * Performs the actual output operation of "vbox_cat".
317 *
318 * @return IPRT status code.
319 * @param hInput Handle of input file (if any) to use;
320 * else stdin will be used.
321 * @param hOutput Handle of output file (if any) to use;
322 * else stdout will be used.
323 */
324static int vgsvcToolboxCatOutput(RTFILE hInput, RTFILE hOutput)
325{
326 int rc = VINF_SUCCESS;
327 if (hInput == NIL_RTFILE)
328 {
329 rc = RTFileFromNative(&hInput, RTFILE_NATIVE_STDIN);
330 if (RT_FAILURE(rc))
331 RTMsgError("Could not translate input file to native handle, rc=%Rrc\n", rc);
332 }
333
334 if (hOutput == NIL_RTFILE)
335 {
336 rc = RTFileFromNative(&hOutput, RTFILE_NATIVE_STDOUT);
337 if (RT_FAILURE(rc))
338 RTMsgError("Could not translate output file to native handle, rc=%Rrc\n", rc);
339 }
340
341 if (RT_SUCCESS(rc))
342 {
343 uint8_t abBuf[_64K];
344 size_t cbRead;
345 for (;;)
346 {
347 rc = RTFileRead(hInput, abBuf, sizeof(abBuf), &cbRead);
348 if (RT_SUCCESS(rc) && cbRead > 0)
349 {
350 rc = RTFileWrite(hOutput, abBuf, cbRead, NULL /* Try to write all at once! */);
351 if (RT_FAILURE(rc))
352 {
353 RTMsgError("Error while writing output, rc=%Rrc\n", rc);
354 break;
355 }
356 }
357 else
358 {
359 if (rc == VERR_BROKEN_PIPE)
360 rc = VINF_SUCCESS;
361 else if (RT_FAILURE(rc))
362 RTMsgError("Error while reading input, rc=%Rrc\n", rc);
363 break;
364 }
365 }
366 }
367 return rc;
368}
369
370
371/** @todo Document options! */
372static char g_paszCatHelp[] =
373 " VBoxService [--use-toolbox] vbox_cat [<general options>] <file>...\n\n"
374 "Concatenate files, or standard input, to standard output.\n"
375 "\n";
376
377
378/**
379 * Main function for tool "vbox_cat".
380 *
381 * @return RTEXITCODE.
382 * @param argc Number of arguments.
383 * @param argv Pointer to argument array.
384 */
385static RTEXITCODE vgsvcToolboxCat(int argc, char **argv)
386{
387 static const RTGETOPTDEF s_aOptions[] =
388 {
389 /* Sorted by short ops. */
390 { "--show-all", 'a', RTGETOPT_REQ_NOTHING },
391 { "--number-nonblank", 'b', RTGETOPT_REQ_NOTHING},
392 { NULL, 'e', RTGETOPT_REQ_NOTHING},
393 { NULL, 'E', RTGETOPT_REQ_NOTHING},
394 { "--flags", 'f', RTGETOPT_REQ_STRING},
395 { "--no-content-indexed", VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED, RTGETOPT_REQ_NOTHING},
396 { "--number", 'n', RTGETOPT_REQ_NOTHING},
397 { "--output", 'o', RTGETOPT_REQ_STRING},
398 { "--squeeze-blank", 's', RTGETOPT_REQ_NOTHING},
399 { NULL, 't', RTGETOPT_REQ_NOTHING},
400 { "--show-tabs", 'T', RTGETOPT_REQ_NOTHING},
401 { NULL, 'u', RTGETOPT_REQ_NOTHING},
402 { "--show-noneprinting", 'v', RTGETOPT_REQ_NOTHING}
403 };
404
405 int ch;
406 RTGETOPTUNION ValueUnion;
407 RTGETOPTSTATE GetState;
408
409 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, 0 /*fFlags*/);
410
411 int rc = VINF_SUCCESS;
412
413 const char *pszOutput = NULL;
414 RTFILE hOutput = NIL_RTFILE;
415 uint32_t fFlags = RTFILE_O_CREATE_REPLACE /* Output file flags. */
416 | RTFILE_O_WRITE
417 | RTFILE_O_DENY_WRITE;
418
419 /* Init directory list. */
420 RTLISTANCHOR inputList;
421 RTListInit(&inputList);
422
423 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
424 && RT_SUCCESS(rc))
425 {
426 /* For options that require an argument, ValueUnion has received the value. */
427 switch (ch)
428 {
429 case 'a':
430 case 'b':
431 case 'e':
432 case 'E':
433 case 'n':
434 case 's':
435 case 't':
436 case 'T':
437 case 'v':
438 RTMsgError("Sorry, option '%s' is not implemented yet!\n",
439 ValueUnion.pDef->pszLong);
440 rc = VERR_INVALID_PARAMETER;
441 break;
442
443 case 'h':
444 vgsvcToolboxShowUsageHeader();
445 RTPrintf("%s", g_paszCatHelp);
446 return RTEXITCODE_SUCCESS;
447
448 case 'o':
449 pszOutput = ValueUnion.psz;
450 break;
451
452 case 'u':
453 /* Ignored. */
454 break;
455
456 case 'V':
457 vgsvcToolboxShowVersion();
458 return RTEXITCODE_SUCCESS;
459
460 case VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED:
461 fFlags |= RTFILE_O_NOT_CONTENT_INDEXED;
462 break;
463
464 case VINF_GETOPT_NOT_OPTION:
465 /* Add file(s) to buffer. This enables processing multiple paths
466 * at once.
467 *
468 * Since the non-options (RTGETOPTINIT_FLAGS_OPTS_FIRST) come last when
469 * processing this loop it's safe to immediately exit on syntax errors
470 * or showing the help text (see above). */
471 rc = vgsvcToolboxPathBufAddPathEntry(&inputList, ValueUnion.psz);
472 break;
473
474 default:
475 return RTGetOptPrintError(ch, &ValueUnion);
476 }
477 }
478
479 if (RT_SUCCESS(rc))
480 {
481 if (pszOutput)
482 {
483 rc = RTFileOpen(&hOutput, pszOutput, fFlags);
484 if (RT_FAILURE(rc))
485 RTMsgError("Could not create output file '%s', rc=%Rrc\n", pszOutput, rc);
486 }
487
488 if (RT_SUCCESS(rc))
489 {
490 /* Process each input file. */
491 RTFILE hInput = NIL_RTFILE;
492 PVBOXSERVICETOOLBOXPATHENTRY pNodeIt;
493 RTListForEach(&inputList, pNodeIt, VBOXSERVICETOOLBOXPATHENTRY, Node)
494 {
495 rc = RTFileOpen(&hInput, pNodeIt->pszName,
496 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
497 if (RT_SUCCESS(rc))
498 {
499 rc = vgsvcToolboxCatOutput(hInput, hOutput);
500 RTFileClose(hInput);
501 }
502 else
503 RTMsgError("Could not open input file '%s': %Rrc\n", pNodeIt->pszName, rc);
504 if (RT_FAILURE(rc))
505 break;
506 }
507
508 /* If no input files were defined, process stdin. */
509 if (RTListNodeIsFirst(&inputList, &inputList))
510 rc = vgsvcToolboxCatOutput(hInput, hOutput);
511 }
512 }
513
514 if (hOutput != NIL_RTFILE)
515 RTFileClose(hOutput);
516 vgsvcToolboxPathBufDestroy(&inputList);
517
518 if (RT_FAILURE(rc))
519 {
520 switch (rc)
521 {
522 case VERR_ACCESS_DENIED:
523 return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_ACCESS_DENIED;
524
525 case VERR_FILE_NOT_FOUND:
526 return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_FILE_NOT_FOUND;
527
528 case VERR_PATH_NOT_FOUND:
529 return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_PATH_NOT_FOUND;
530
531 case VERR_SHARING_VIOLATION:
532 return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_SHARING_VIOLATION;
533
534 case VERR_IS_A_DIRECTORY:
535 return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_IS_A_DIRECTORY;
536
537 default:
538#ifdef DEBUG_andy
539 AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc));
540#endif
541 break;
542 }
543
544 return RTEXITCODE_FAILURE;
545 }
546
547 return RTEXITCODE_SUCCESS;
548}
549
550
551/**
552 * Prints information (based on given flags) of a file system object (file/directory/...)
553 * to stdout.
554 *
555 * @return IPRT status code.
556 * @param pszName Object name.
557 * @param cchName Length of pszName.
558 * @param fOutputFlags Output / handling flags of type
559 * VBOXSERVICETOOLBOXOUTPUTFLAG.
560 * @param pszRelativeTo What pszName is relative to.
561 * @param pIdCache The ID cache.
562 * @param pObjInfo Pointer to object information.
563 */
564static int vgsvcToolboxPrintFsInfo(const char *pszName, size_t cchName, uint32_t fOutputFlags, const char *pszRelativeTo,
565 PVGSVCIDCACHE pIdCache, PRTFSOBJINFO pObjInfo)
566{
567 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
568 AssertReturn(cchName, VERR_INVALID_PARAMETER);
569 AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
570
571 RTFMODE fMode = pObjInfo->Attr.fMode;
572 char chFileType;
573 switch (fMode & RTFS_TYPE_MASK)
574 {
575 case RTFS_TYPE_FIFO: chFileType = 'f'; break;
576 case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break;
577 case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break;
578 case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break;
579 case RTFS_TYPE_FILE: chFileType = '-'; break;
580 case RTFS_TYPE_SYMLINK: chFileType = 'l'; break;
581 case RTFS_TYPE_SOCKET: chFileType = 's'; break;
582 case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break;
583 default: chFileType = '?'; break;
584 }
585 /** @todo sticy bits++ */
586
587/** @todo r=bird: turns out the host doesn't use or need cname_len, so perhaps we could drop it? */
588 if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_LONG))
589 {
590 if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
591 {
592 RTPrintf("ftype=%c%cnode_id=%RU64%inode_dev=%RU32%ccname_len=%zu%cname=%s%c",
593 chFileType, 0, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0,
594 (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0, cchName, 0, pszName, 0);
595 RTPrintf("%c%c", 0, 0);
596 }
597 else
598 RTPrintf("%c %#18llx %3zu %s\n", chFileType, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, cchName, pszName);
599 }
600 else
601 {
602 char szTimeBirth[RTTIME_STR_LEN];
603 char szTimeChange[RTTIME_STR_LEN];
604 char szTimeModification[RTTIME_STR_LEN];
605 char szTimeAccess[RTTIME_STR_LEN];
606
607 if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
608 {
609 RTPrintf("ftype=%c%c", chFileType, 0);
610 if (pObjInfo->Attr.u.Unix.INodeId || pObjInfo->Attr.u.Unix.INodeIdDevice)
611 RTPrintf("node_id=%RU64%cinode_dev=%RU32%c", (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0,
612 (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0);
613 RTPrintf("owner_mask=%c%c%c%c",
614 fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
615 fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
616 fMode & RTFS_UNIX_IXUSR ? 'x' : '-', 0);
617 RTPrintf("group_mask=%c%c%c%c",
618 fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
619 fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
620 fMode & RTFS_UNIX_IXGRP ? 'x' : '-', 0);
621 RTPrintf("other_mask=%c%c%c%c",
622 fMode & RTFS_UNIX_IROTH ? 'r' : '-',
623 fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
624 fMode & RTFS_UNIX_IXOTH ? 'x' : '-', 0);
625 /** @todo sticky bits. */
626 RTPrintf("dos_mask=%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c",
627 fMode & RTFS_DOS_READONLY ? 'R' : '-',
628 fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
629 fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
630 fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
631 fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
632 fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
633 fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
634 fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
635 fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
636 fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
637 fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
638 fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
639 fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
640 fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-', 0);
641 RTPrintf("hlinks=%RU32%cst_size=%RI64%calloc=%RI64%c",
642 pObjInfo->Attr.u.Unix.cHardlinks, 0,
643 pObjInfo->cbObject, 0,
644 pObjInfo->cbAllocated, 0);
645 RTPrintf("st_birthtime=%s%cst_ctime=%s%cst_mtime=%s%cst_atime=%s%c",
646 RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)), 0,
647 RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)), 0,
648 RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)), 0,
649 RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)), 0);
650 if (pObjInfo->Attr.u.Unix.uid != NIL_RTUID)
651 RTPrintf("uid=%RU32%cusername=%s%c", pObjInfo->Attr.u.Unix.uid, 0,
652 VGSvcIdCacheGetUidName(pIdCache, pObjInfo->Attr.u.Unix.uid, pszName, pszRelativeTo), 0);
653 if (pObjInfo->Attr.u.Unix.gid != NIL_RTGID)
654 RTPrintf("gid=%RU32%cgroupname=%s%c", pObjInfo->Attr.u.Unix.gid, 0,
655 VGSvcIdCacheGetGidName(pIdCache, pObjInfo->Attr.u.Unix.gid, pszName, pszRelativeTo), 0);
656 if ( (RTFS_IS_DEV_BLOCK(pObjInfo->Attr.fMode) || RTFS_IS_DEV_CHAR(pObjInfo->Attr.fMode))
657 && pObjInfo->Attr.u.Unix.Device)
658 RTPrintf("st_rdev=%RU32%c", pObjInfo->Attr.u.Unix.Device, 0);
659 if (pObjInfo->Attr.u.Unix.GenerationId)
660 RTPrintf("st_gen=%RU32%c", pObjInfo->Attr.u.Unix.GenerationId, 0);
661 if (pObjInfo->Attr.u.Unix.fFlags)
662 RTPrintf("st_flags=%RU32%c", pObjInfo->Attr.u.Unix.fFlags, 0);
663 RTPrintf("cname_len=%zu%cname=%s%c", cchName, 0, pszName, 0);
664 RTPrintf("%c%c", 0, 0); /* End of data block. */
665 }
666 else
667 {
668 RTPrintf("%c", chFileType);
669 RTPrintf("%c%c%c",
670 fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
671 fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
672 fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
673 RTPrintf("%c%c%c",
674 fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
675 fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
676 fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
677 RTPrintf("%c%c%c",
678 fMode & RTFS_UNIX_IROTH ? 'r' : '-',
679 fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
680 fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
681 RTPrintf(" %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
682 fMode & RTFS_DOS_READONLY ? 'R' : '-',
683 fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
684 fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
685 fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
686 fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
687 fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
688 fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
689 fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
690 fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
691 fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
692 fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
693 fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
694 fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
695 fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-');
696 RTPrintf(" %d %4d %4d %10lld %10lld",
697 pObjInfo->Attr.u.Unix.cHardlinks,
698 pObjInfo->Attr.u.Unix.uid,
699 pObjInfo->Attr.u.Unix.gid,
700 pObjInfo->cbObject,
701 pObjInfo->cbAllocated);
702 RTPrintf(" %s %s %s %s",
703 RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)),
704 RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)),
705 RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)),
706 RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) );
707 RTPrintf(" %2zu %s\n", cchName, pszName);
708 }
709 }
710
711 return VINF_SUCCESS;
712}
713
714/**
715 * Helper routine for ls tool for handling sub directories.
716 *
717 * @return IPRT status code.
718 * @param pszDir Pointer to the directory buffer.
719 * @param cchDir The length of pszDir in pszDir.
720 * @param pDirEntry Pointer to the directory entry.
721 * @param fFlags Flags of type VBOXSERVICETOOLBOXLSFLAG.
722 * @param fOutputFlags Flags of type VBOXSERVICETOOLBOXOUTPUTFLAG.
723 * @param pIdCache The ID cache.
724 */
725static int vgsvcToolboxLsHandleDirSub(char *pszDir, size_t cchDir, PRTDIRENTRYEX pDirEntry,
726 uint32_t fFlags, uint32_t fOutputFlags, PVGSVCIDCACHE pIdCache)
727{
728 Assert(cchDir > 0); Assert(pszDir[cchDir] == '\0');
729
730 if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
731 RTPrintf("dname=%s%c", pszDir, 0);
732 else if (fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE)
733 RTPrintf("%s:\n", pszDir);
734
735 /* Make sure we've got some room in the path, to save us extra work further down. */
736 if (cchDir + 3 >= RTPATH_MAX)
737 {
738 if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
739 RTMsgError("Path too long: '%s'\n", pszDir);
740 return VERR_BUFFER_OVERFLOW;
741 }
742
743 /* Open directory. */
744 RTDIR hDir;
745 int rc = RTDirOpen(&hDir, pszDir);
746 if (RT_FAILURE(rc))
747 {
748 if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
749 RTMsgError("Failed to open directory '%s', rc=%Rrc\n", pszDir, rc);
750 return rc;
751 }
752
753 /* Ensure we've got a trailing slash (there is space for it see above). */
754 if (!RTPATH_IS_SEP(pszDir[cchDir - 1]))
755 {
756 pszDir[cchDir++] = RTPATH_SLASH;
757 pszDir[cchDir] = '\0';
758 }
759
760 /*
761 * Process the files and subdirs.
762 */
763 for (;;)
764 {
765 /* Get the next directory. */
766 size_t cbDirEntry = VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE;
767 rc = RTDirReadEx(hDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
768 if (RT_FAILURE(rc))
769 break;
770
771 /* Check length. */
772 if (pDirEntry->cbName + cchDir + 3 >= RTPATH_MAX)
773 {
774 if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
775 RTMsgError("Path too long: '%s' in '%.*s'\n", pDirEntry->szName, cchDir, pszDir);
776 rc = VERR_BUFFER_OVERFLOW;
777 break;
778 }
779
780 switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
781 {
782 case RTFS_TYPE_SYMLINK:
783 {
784 if (!(fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS))
785 break;
786 RT_FALL_THRU();
787 }
788 case RTFS_TYPE_DIRECTORY:
789 {
790 rc = vgsvcToolboxPrintFsInfo(pDirEntry->szName, pDirEntry->cbName, fOutputFlags, pszDir,
791 pIdCache, &pDirEntry->Info);
792 if (RT_FAILURE(rc))
793 break;
794
795 if (RTDirEntryExIsStdDotLink(pDirEntry))
796 continue;
797
798 if (!(fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE))
799 continue;
800
801 memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1);
802 int rc2 = vgsvcToolboxLsHandleDirSub(pszDir, cchDir + pDirEntry->cbName, pDirEntry, fFlags, fOutputFlags, pIdCache);
803 if (RT_SUCCESS(rc))
804 rc = rc2;
805 break;
806 }
807
808 case RTFS_TYPE_FILE:
809 {
810 rc = vgsvcToolboxPrintFsInfo(pDirEntry->szName, pDirEntry->cbName, fOutputFlags, pszDir,
811 pIdCache, &pDirEntry->Info);
812 break;
813 }
814
815 default:
816 {
817 if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
818 RTMsgError("Entry '%.*s%s' of mode %#x not supported, skipping",
819 cchDir, pszDir, pDirEntry->szName, pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK);
820 break;
821 }
822 }
823 }
824 if (rc != VERR_NO_MORE_FILES)
825 {
826 if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
827 RTMsgError("RTDirReadEx failed: %Rrc\npszDir=%.*s", rc, cchDir, pszDir);
828 }
829
830 rc = RTDirClose(hDir);
831 if (RT_FAILURE(rc))
832 {
833 if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
834 RTMsgError("RTDirClose failed: %Rrc\npszDir=%.*s", rc, cchDir, pszDir);
835 }
836
837 return rc;
838}
839
840/**
841 * Helper routine for ls tool doing the actual parsing and output of
842 * a specified directory.
843 *
844 * @return IPRT status code.
845 * @param pszDir Absolute path to directory to ouptut.
846 * @param fFlags Flags of type VBOXSERVICETOOLBOXLSFLAG.
847 * @param fOutputFlags Flags of type VBOXSERVICETOOLBOXOUTPUTFLAG.
848 * @param pIdCache The ID cache.
849 */
850static int vgsvcToolboxLsHandleDir(const char *pszDir, uint32_t fFlags, uint32_t fOutputFlags, PVGSVCIDCACHE pIdCache)
851{
852 AssertPtrReturn(pszDir, VERR_INVALID_PARAMETER);
853 AssertPtrReturn(pIdCache, VERR_INVALID_PARAMETER);
854
855 char szPath[RTPATH_MAX];
856 int rc = RTPathAbs(pszDir, szPath, sizeof(szPath));
857 if (RT_FAILURE(rc))
858 {
859 if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
860 RTMsgError("RTPathAbs failed on '%s': %Rrc\n", pszDir, rc);
861 return rc;
862 }
863
864 union
865 {
866 uint8_t abPadding[VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE];
867 RTDIRENTRYEX DirEntry;
868 } uBuf;
869 return vgsvcToolboxLsHandleDirSub(szPath, strlen(szPath), &uBuf.DirEntry, fFlags, fOutputFlags, pIdCache);
870}
871
872
873/** @todo Document options! */
874static char g_paszLsHelp[] =
875 " VBoxService [--use-toolbox] vbox_ls [<general options>] [option]...\n"
876 " [<file>...]\n\n"
877 "List information about files (the current directory by default).\n\n"
878 "Options:\n\n"
879 " [--dereference|-L]\n"
880 " [-l][-R]\n"
881 " [--verbose|-v]\n"
882 " [<file>...]\n"
883 "\n";
884
885
886/**
887 * Main function for tool "vbox_ls".
888 *
889 * @return RTEXITCODE.
890 * @param argc Number of arguments.
891 * @param argv Pointer to argument array.
892 */
893static RTEXITCODE vgsvcToolboxLs(int argc, char **argv)
894{
895 static const RTGETOPTDEF s_aOptions[] =
896 {
897 { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
898 { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
899 { NULL, 'l', RTGETOPT_REQ_NOTHING },
900 { NULL, 'R', RTGETOPT_REQ_NOTHING },
901 { "--verbose", VBOXSERVICETOOLBOXOPT_VERBOSE, RTGETOPT_REQ_NOTHING}
902 };
903
904 int ch;
905 RTGETOPTUNION ValueUnion;
906 RTGETOPTSTATE GetState;
907 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions),
908 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
909 AssertRCReturn(rc, RTEXITCODE_INIT);
910
911 bool fVerbose = false;
912 uint32_t fFlags = VBOXSERVICETOOLBOXLSFLAG_NONE;
913 uint32_t fOutputFlags = VBOXSERVICETOOLBOXOUTPUTFLAG_NONE;
914
915 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
916 {
917 /* For options that require an argument, ValueUnion has received the value. */
918 switch (ch)
919 {
920 case 'h':
921 vgsvcToolboxShowUsageHeader();
922 RTPrintf("%s", g_paszLsHelp);
923 return RTEXITCODE_SUCCESS;
924
925 case 'L': /* Dereference symlinks. */
926 fFlags |= VBOXSERVICETOOLBOXLSFLAG_SYMLINKS;
927 break;
928
929 case 'l': /* Print long format. */
930 fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_LONG;
931 break;
932
933 case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE:
934 fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE;
935 break;
936
937 case 'R': /* Recursive processing. */
938 fFlags |= VBOXSERVICETOOLBOXLSFLAG_RECURSIVE;
939 break;
940
941 case VBOXSERVICETOOLBOXOPT_VERBOSE:
942 fVerbose = true;
943 break;
944
945 case 'V':
946 vgsvcToolboxShowVersion();
947 return RTEXITCODE_SUCCESS;
948
949 case VINF_GETOPT_NOT_OPTION:
950 Assert(GetState.iNext);
951 GetState.iNext--;
952 break;
953
954 default:
955 return RTGetOptPrintError(ch, &ValueUnion);
956 }
957
958 /* All flags / options processed? Bail out here.
959 * Processing the file / directory list comes down below. */
960 if (ch == VINF_GETOPT_NOT_OPTION)
961 break;
962 }
963
964 /* Print magic/version. */
965 if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
966 {
967 rc = vgsvcToolboxStrmInit();
968 if (RT_FAILURE(rc))
969 RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc);
970 vgsvcToolboxPrintStrmHeader("vbt_ls", 1 /* Stream version */);
971 }
972
973 VGSVCIDCACHE IdCache;
974 RT_ZERO(IdCache);
975
976 char szDirCur[RTPATH_MAX];
977 rc = RTPathGetCurrent(szDirCur, sizeof(szDirCur));
978 if (RT_FAILURE(rc))
979 {
980 RTMsgError("Getting current directory failed, rc=%Rrc\n", rc);
981 return RTEXITCODE_FAILURE;
982 }
983
984 ch = RTGetOpt(&GetState, &ValueUnion);
985 do
986 {
987 char const *pszPath;
988
989 if (ch == 0) /* Use current directory if no element specified. */
990 pszPath = szDirCur;
991 else
992 pszPath = ValueUnion.psz;
993
994 RTFSOBJINFO objInfo;
995 int rc2 = RTPathQueryInfoEx(pszPath, &objInfo,
996 RTFSOBJATTRADD_UNIX,
997 fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK);
998 if (RT_SUCCESS(rc2))
999 {
1000 if ( RTFS_IS_FILE(objInfo.Attr.fMode)
1001 || ( RTFS_IS_SYMLINK(objInfo.Attr.fMode)
1002 && (fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS)))
1003 {
1004 rc2 = vgsvcToolboxPrintFsInfo(pszPath, strlen(pszPath), fOutputFlags, NULL, &IdCache, &objInfo);
1005 if (RT_SUCCESS(rc)) /* Keep initial failing rc. */
1006 rc = rc2;
1007 }
1008 else if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
1009 {
1010 rc2 = vgsvcToolboxLsHandleDir(pszPath, fFlags, fOutputFlags, &IdCache);
1011 if (RT_SUCCESS(rc)) /* Keep initial failing rc. */
1012 rc = rc2;
1013 }
1014 }
1015 else
1016 {
1017 if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
1018 RTMsgError("Cannot access '%s': No such file or directory\n", pszPath);
1019 if (RT_SUCCESS(rc))
1020 rc = VERR_FILE_NOT_FOUND;
1021 /* Do not break here -- process every element in the list
1022 * and keep failing rc. */
1023 }
1024
1025 } while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0);
1026
1027 if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */
1028 vgsvcToolboxPrintStrmTermination();
1029
1030 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1031}
1032
1033
1034/* Try using RTPathRmCmd. */
1035static RTEXITCODE vgsvcToolboxRm(int argc, char **argv)
1036{
1037 return RTPathRmCmd(argc, argv);
1038}
1039
1040
1041static char g_paszMkTempHelp[] =
1042 " VBoxService [--use-toolbox] vbox_mktemp [<general options>] [<options>]\n"
1043 " <template>\n\n"
1044 "Create a temporary directory based on the template supplied. The first string\n"
1045 "of consecutive 'X' characters in the template will be replaced to form a unique\n"
1046 "name for the directory. The template may not contain a path. The default\n"
1047 "creation mode is 0600 for files and 0700 for directories. If no path is\n"
1048 "specified the default temporary directory will be used.\n"
1049 "Options:\n\n"
1050 " [--directory|-d] Create a directory instead of a file.\n"
1051 " [--mode|-m <mode>] Create the object with mode <mode>.\n"
1052 " [--secure|-s] Fail if the object cannot be created securely.\n"
1053 " [--tmpdir|-t <path>] Create the object with the absolute path <path>.\n"
1054 "\n";
1055
1056
1057/**
1058 * Report the result of a vbox_mktemp operation.
1059 *
1060 * Either errors to stderr (not machine-readable) or everything to stdout as
1061 * {name}\0{rc}\0 (machine- readable format). The message may optionally
1062 * contain a '%s' for the file name and an %Rrc for the result code in that
1063 * order. In future a "verbose" flag may be added, without which nothing will
1064 * be output in non-machine- readable mode. Sets prc if rc is a non-success
1065 * code.
1066 */
1067static void toolboxMkTempReport(const char *pcszMessage, const char *pcszFile,
1068 bool fActive, int rc, uint32_t fOutputFlags, int *prc)
1069{
1070 if (!fActive)
1071 return;
1072 if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
1073 if (RT_SUCCESS(rc))
1074 RTPrintf(pcszMessage, pcszFile, rc);
1075 else
1076 RTMsgError(pcszMessage, pcszFile, rc);
1077 else
1078 RTPrintf("name=%s%crc=%d%c", pcszFile, 0, rc, 0);
1079 if (prc && RT_FAILURE(rc))
1080 *prc = rc;
1081}
1082
1083
1084/**
1085 * Main function for tool "vbox_mktemp".
1086 *
1087 * @return RTEXITCODE.
1088 * @param argc Number of arguments.
1089 * @param argv Pointer to argument array.
1090 */
1091static RTEXITCODE vgsvcToolboxMkTemp(int argc, char **argv)
1092{
1093 static const RTGETOPTDEF s_aOptions[] =
1094 {
1095 { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE,
1096 RTGETOPT_REQ_NOTHING },
1097 { "--directory", 'd', RTGETOPT_REQ_NOTHING },
1098 { "--mode", 'm', RTGETOPT_REQ_STRING },
1099 { "--secure", 's', RTGETOPT_REQ_NOTHING },
1100 { "--tmpdir", 't', RTGETOPT_REQ_STRING },
1101 };
1102
1103 enum
1104 {
1105 /* Isn't that a bit long? s/VBOXSERVICETOOLBOX/VSTB/ ? */
1106 /** Create a temporary directory instead of a temporary file. */
1107 VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY = RT_BIT_32(0),
1108 /** Only create the temporary object if the operation is expected
1109 * to be secure. Not guaranteed to be supported on a particular
1110 * set-up. */
1111 VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE = RT_BIT_32(1)
1112 };
1113
1114 int ch, rc;
1115 RTGETOPTUNION ValueUnion;
1116 RTGETOPTSTATE GetState;
1117 rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1118 AssertRCReturn(rc, RTEXITCODE_INIT);
1119
1120 uint32_t fFlags = 0;
1121 uint32_t fOutputFlags = 0;
1122 int cNonOptions = 0;
1123 RTFMODE fMode = 0700;
1124 bool fModeSet = false;
1125 const char *pcszPath = NULL;
1126 const char *pcszTemplate;
1127 char szTemplateWithPath[RTPATH_MAX] = "";
1128
1129 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
1130 && RT_SUCCESS(rc))
1131 {
1132 /* For options that require an argument, ValueUnion has received the value. */
1133 switch (ch)
1134 {
1135 case 'h':
1136 vgsvcToolboxShowUsageHeader();
1137 RTPrintf("%s", g_paszMkTempHelp);
1138 return RTEXITCODE_SUCCESS;
1139
1140 case 'V':
1141 vgsvcToolboxShowVersion();
1142 return RTEXITCODE_SUCCESS;
1143
1144 case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE:
1145 fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE;
1146 break;
1147
1148 case 'd':
1149 fFlags |= VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY;
1150 break;
1151
1152 case 'm':
1153 rc = vgsvcToolboxParseMode(ValueUnion.psz, &fMode);
1154 if (RT_FAILURE(rc))
1155 return RTEXITCODE_SYNTAX;
1156 fModeSet = true;
1157#ifndef RT_OS_WINDOWS
1158 umask(0); /* RTDirCreate workaround */
1159#endif
1160 break;
1161 case 's':
1162 fFlags |= VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE;
1163 break;
1164
1165 case 't':
1166 pcszPath = ValueUnion.psz;
1167 break;
1168
1169 case VINF_GETOPT_NOT_OPTION:
1170 /* RTGetOpt will sort these to the end of the argv vector so
1171 * that we will deal with them afterwards. */
1172 ++cNonOptions;
1173 break;
1174
1175 default:
1176 return RTGetOptPrintError(ch, &ValueUnion);
1177 }
1178 }
1179
1180 /* Print magic/version. */
1181 if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
1182 {
1183 rc = vgsvcToolboxStrmInit();
1184 if (RT_FAILURE(rc))
1185 RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc);
1186 vgsvcToolboxPrintStrmHeader("vbt_mktemp", 1 /* Stream version */);
1187 }
1188
1189 if (fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE && fModeSet)
1190 {
1191 toolboxMkTempReport("'-s' and '-m' parameters cannot be used together.\n", "",
1192 true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
1193 return RTEXITCODE_SYNTAX;
1194 }
1195
1196 /* We need exactly one template, containing at least one 'X'. */
1197 if (cNonOptions != 1)
1198 {
1199 toolboxMkTempReport("Please specify exactly one template.\n", "", true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
1200 return RTEXITCODE_SYNTAX;
1201 }
1202 pcszTemplate = argv[argc - 1];
1203
1204 /* Validate that the template is as IPRT requires (asserted by IPRT). */
1205 if ( RTPathHasPath(pcszTemplate)
1206 || ( !strstr(pcszTemplate, "XXX")
1207 && pcszTemplate[strlen(pcszTemplate) - 1] != 'X'))
1208 {
1209 toolboxMkTempReport("Template '%s' should contain a file name with no path and at least three consecutive 'X' characters or ending in 'X'.\n",
1210 pcszTemplate, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
1211 return RTEXITCODE_FAILURE;
1212 }
1213 if (pcszPath && !RTPathStartsWithRoot(pcszPath))
1214 {
1215 toolboxMkTempReport("Path '%s' should be absolute.\n", pcszPath, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
1216 return RTEXITCODE_FAILURE;
1217 }
1218 if (pcszPath)
1219 {
1220 rc = RTStrCopy(szTemplateWithPath, sizeof(szTemplateWithPath), pcszPath);
1221 if (RT_FAILURE(rc))
1222 {
1223 toolboxMkTempReport("Path '%s' too long.\n", pcszPath, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
1224 return RTEXITCODE_FAILURE;
1225 }
1226 }
1227 else
1228 {
1229 rc = RTPathTemp(szTemplateWithPath, sizeof(szTemplateWithPath));
1230 if (RT_FAILURE(rc))
1231 {
1232 toolboxMkTempReport("Failed to get the temporary directory.\n", "", true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
1233 return RTEXITCODE_FAILURE;
1234 }
1235 }
1236 rc = RTPathAppend(szTemplateWithPath, sizeof(szTemplateWithPath), pcszTemplate);
1237 if (RT_FAILURE(rc))
1238 {
1239 toolboxMkTempReport("Template '%s' too long for path.\n", pcszTemplate, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
1240 return RTEXITCODE_FAILURE;
1241 }
1242
1243 if (fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY)
1244 {
1245 rc = fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE
1246 ? RTDirCreateTempSecure(szTemplateWithPath)
1247 : RTDirCreateTemp(szTemplateWithPath, fMode);
1248 toolboxMkTempReport("Created temporary directory '%s'.\n",
1249 szTemplateWithPath, RT_SUCCESS(rc), rc,
1250 fOutputFlags, NULL);
1251 /* RTDirCreateTemp[Secure] sets the template to "" on failure. */
1252 toolboxMkTempReport("The following error occurred while creating a temporary directory from template '%s': %Rrc.\n",
1253 pcszTemplate, RT_FAILURE(rc), rc, fOutputFlags, NULL /*prc*/);
1254 }
1255 else
1256 {
1257 rc = fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE
1258 ? RTFileCreateTempSecure(szTemplateWithPath)
1259 : RTFileCreateTemp(szTemplateWithPath, fMode);
1260 toolboxMkTempReport("Created temporary file '%s'.\n",
1261 szTemplateWithPath, RT_SUCCESS(rc), rc,
1262 fOutputFlags, NULL);
1263 /* RTFileCreateTemp[Secure] sets the template to "" on failure. */
1264 toolboxMkTempReport("The following error occurred while creating a temporary file from template '%s': %Rrc.\n",
1265 pcszTemplate, RT_FAILURE(rc), rc, fOutputFlags, NULL /*prc*/);
1266 }
1267 if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */
1268 vgsvcToolboxPrintStrmTermination();
1269 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1270}
1271
1272
1273/** @todo Document options! */
1274static char g_paszMkDirHelp[] =
1275 " VBoxService [--use-toolbox] vbox_mkdir [<general options>] [<options>]\n"
1276 " <directory>...\n\n"
1277 "Options:\n\n"
1278 " [--mode|-m <mode>] The file mode to set (chmod) on the created\n"
1279 " directories. Default: a=rwx & umask.\n"
1280 " [--parents|-p] Create parent directories as needed, no\n"
1281 " error if the directory already exists.\n"
1282 " [--verbose|-v] Display a message for each created directory.\n"
1283 "\n";
1284
1285
1286/**
1287 * Main function for tool "vbox_mkdir".
1288 *
1289 * @return RTEXITCODE.
1290 * @param argc Number of arguments.
1291 * @param argv Pointer to argument array.
1292 */
1293static RTEXITCODE vgsvcToolboxMkDir(int argc, char **argv)
1294{
1295 static const RTGETOPTDEF s_aOptions[] =
1296 {
1297 { "--mode", 'm', RTGETOPT_REQ_STRING },
1298 { "--parents", 'p', RTGETOPT_REQ_NOTHING},
1299 { "--verbose", 'v', RTGETOPT_REQ_NOTHING}
1300 };
1301
1302 int ch;
1303 RTGETOPTUNION ValueUnion;
1304 RTGETOPTSTATE GetState;
1305 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions),
1306 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1307 AssertRCReturn(rc, RTEXITCODE_INIT);
1308
1309 bool fMakeParentDirs = false;
1310 bool fVerbose = false;
1311 RTFMODE fDirMode = RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG | RTFS_UNIX_IRWXO;
1312 int cDirsCreated = 0;
1313
1314 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1315 {
1316 /* For options that require an argument, ValueUnion has received the value. */
1317 switch (ch)
1318 {
1319 case 'p':
1320 fMakeParentDirs = true;
1321 break;
1322
1323 case 'm':
1324 rc = vgsvcToolboxParseMode(ValueUnion.psz, &fDirMode);
1325 if (RT_FAILURE(rc))
1326 return RTEXITCODE_SYNTAX;
1327#ifndef RT_OS_WINDOWS
1328 umask(0); /* RTDirCreate workaround */
1329#endif
1330 break;
1331
1332 case 'v':
1333 fVerbose = true;
1334 break;
1335
1336 case 'h':
1337 vgsvcToolboxShowUsageHeader();
1338 RTPrintf("%s", g_paszMkDirHelp);
1339 return RTEXITCODE_SUCCESS;
1340
1341 case 'V':
1342 vgsvcToolboxShowVersion();
1343 return RTEXITCODE_SUCCESS;
1344
1345 case VINF_GETOPT_NOT_OPTION:
1346 if (fMakeParentDirs)
1347 /** @todo r=bird: If fVerbose is set, we should also show
1348 * which directories that get created, parents as well as
1349 * omitting existing final dirs. Annoying, but check any
1350 * mkdir implementation (try "mkdir -pv asdf/1/2/3/4"
1351 * twice). */
1352 rc = RTDirCreateFullPath(ValueUnion.psz, fDirMode);
1353 else
1354 rc = RTDirCreate(ValueUnion.psz, fDirMode, 0);
1355 if (RT_FAILURE(rc))
1356 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Could not create directory '%s': %Rra\n",
1357 ValueUnion.psz, rc);
1358 if (fVerbose)
1359 RTMsgInfo("Created directory '%s', mode %#RTfmode\n", ValueUnion.psz, fDirMode);
1360 cDirsCreated++;
1361 break;
1362
1363 default:
1364 return RTGetOptPrintError(ch, &ValueUnion);
1365 }
1366 }
1367 AssertRC(rc);
1368
1369 if (cDirsCreated == 0)
1370 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No directory argument.");
1371
1372 return RTEXITCODE_SUCCESS;
1373}
1374
1375
1376/** @todo Document options! */
1377static char g_paszStatHelp[] =
1378 " VBoxService [--use-toolbox] vbox_stat [<general options>] [<options>]\n"
1379 " <file>...\n\n"
1380 "Display file or file system status.\n\n"
1381 "Options:\n\n"
1382 " [--file-system|-f]\n"
1383 " [--dereference|-L]\n"
1384 " [--terse|-t]\n"
1385 " [--verbose|-v]\n"
1386 "\n";
1387
1388
1389/**
1390 * Main function for tool "vbox_stat".
1391 *
1392 * @return RTEXITCODE.
1393 * @param argc Number of arguments.
1394 * @param argv Pointer to argument array.
1395 */
1396static RTEXITCODE vgsvcToolboxStat(int argc, char **argv)
1397{
1398 static const RTGETOPTDEF s_aOptions[] =
1399 {
1400 { "--file-system", 'f', RTGETOPT_REQ_NOTHING },
1401 { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
1402 { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
1403 { "--terse", 't', RTGETOPT_REQ_NOTHING },
1404 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
1405 };
1406
1407 int ch;
1408 RTGETOPTUNION ValueUnion;
1409 RTGETOPTSTATE GetState;
1410 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1411
1412 int rc = VINF_SUCCESS;
1413 uint32_t fOutputFlags = VBOXSERVICETOOLBOXOUTPUTFLAG_LONG; /* Use long mode by default. */
1414 uint32_t fQueryInfoFlags = RTPATH_F_ON_LINK;
1415
1416 while ( (ch = RTGetOpt(&GetState, &ValueUnion))
1417 && RT_SUCCESS(rc))
1418 {
1419 /* For options that require an argument, ValueUnion has received the value. */
1420 switch (ch)
1421 {
1422 case 'f':
1423 RTMsgError("Sorry, option '%s' is not implemented yet!\n", ValueUnion.pDef->pszLong);
1424 rc = VERR_INVALID_PARAMETER;
1425 break;
1426
1427 case 'L':
1428 fQueryInfoFlags &= ~RTPATH_F_ON_LINK;
1429 fQueryInfoFlags |= RTPATH_F_FOLLOW_LINK;
1430 break;
1431
1432 case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE:
1433 fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE;
1434 break;
1435
1436 case 'h':
1437 vgsvcToolboxShowUsageHeader();
1438 RTPrintf("%s", g_paszStatHelp);
1439 return RTEXITCODE_SUCCESS;
1440
1441 case 'V':
1442 vgsvcToolboxShowVersion();
1443 return RTEXITCODE_SUCCESS;
1444
1445 case VINF_GETOPT_NOT_OPTION:
1446 {
1447 Assert(GetState.iNext);
1448 GetState.iNext--;
1449 break;
1450 }
1451
1452 default:
1453 return RTGetOptPrintError(ch, &ValueUnion);
1454 }
1455
1456 /* All flags / options processed? Bail out here.
1457 * Processing the file / directory list comes down below. */
1458 if (ch == VINF_GETOPT_NOT_OPTION)
1459 break;
1460 }
1461
1462 if (RT_SUCCESS(rc))
1463 {
1464 if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */
1465 {
1466 rc = vgsvcToolboxStrmInit();
1467 if (RT_FAILURE(rc))
1468 RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc);
1469 vgsvcToolboxPrintStrmHeader("vbt_stat", 1 /* Stream version */);
1470 }
1471
1472 VGSVCIDCACHE IdCache;
1473 RT_ZERO(IdCache);
1474
1475 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1476 {
1477 RTFSOBJINFO objInfo;
1478 int rc2 = RTPathQueryInfoEx(ValueUnion.psz, &objInfo, RTFSOBJATTRADD_UNIX, fQueryInfoFlags);
1479 if (RT_FAILURE(rc2))
1480 {
1481 if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
1482 RTMsgError("Cannot stat for '%s': %Rrc\n", ValueUnion.psz, rc2);
1483 }
1484 else
1485 rc2 = vgsvcToolboxPrintFsInfo(ValueUnion.psz, strlen(ValueUnion.psz), fOutputFlags, NULL, &IdCache, &objInfo);
1486
1487 if (RT_SUCCESS(rc))
1488 rc = rc2;
1489 /* Do not break here -- process every element in the list
1490 * and keep (initial) failing rc. */
1491 }
1492
1493 if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */
1494 vgsvcToolboxPrintStrmTermination();
1495
1496 /* At this point the overall result (success/failure) should be in rc. */
1497 }
1498 else
1499 RTMsgError("Failed with rc=%Rrc\n", rc);
1500
1501 if (RT_FAILURE(rc))
1502 {
1503 switch (rc)
1504 {
1505 case VERR_ACCESS_DENIED:
1506 return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_ACCESS_DENIED;
1507
1508 case VERR_FILE_NOT_FOUND:
1509 return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_FILE_NOT_FOUND;
1510
1511 case VERR_PATH_NOT_FOUND:
1512 return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_PATH_NOT_FOUND;
1513
1514 case VERR_NET_PATH_NOT_FOUND:
1515 return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_NET_PATH_NOT_FOUND;
1516
1517 case VERR_INVALID_NAME:
1518 return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_INVALID_NAME;
1519
1520 default:
1521#ifdef DEBUG_andy
1522 AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc));
1523#endif
1524 break;
1525 }
1526
1527 return RTEXITCODE_FAILURE;
1528 }
1529
1530 return RTEXITCODE_SUCCESS;
1531}
1532
1533
1534/**
1535 * Looks up the tool definition entry for the tool give by @a pszTool.
1536 *
1537 * @returns Pointer to the tool definition. NULL if not found.
1538 * @param pszTool The name of the tool.
1539 */
1540static PCVBOXSERVICETOOLBOXTOOL vgsvcToolboxLookUp(const char *pszTool)
1541{
1542 AssertPtrReturn(pszTool, NULL);
1543
1544 /* Do a linear search, since we don't have that much stuff in the table. */
1545 for (unsigned i = 0; i < RT_ELEMENTS(g_aTools); i++)
1546 if (!strcmp(g_aTools[i].pszName, pszTool))
1547 return &g_aTools[i];
1548
1549 return NULL;
1550}
1551
1552
1553/**
1554 * Converts a tool's exit code back to an IPRT error code.
1555 *
1556 * @return Converted IPRT status code.
1557 * @param pszTool Name of the toolbox tool to convert exit code for.
1558 * @param rcExit The tool's exit code to convert.
1559 */
1560int VGSvcToolboxExitCodeConvertToRc(const char *pszTool, RTEXITCODE rcExit)
1561{
1562 AssertPtrReturn(pszTool, VERR_INVALID_POINTER);
1563
1564 PCVBOXSERVICETOOLBOXTOOL pTool = vgsvcToolboxLookUp(pszTool);
1565 if (pTool)
1566 return pTool->pfnExitCodeConvertToRc(rcExit);
1567
1568 AssertMsgFailed(("Tool '%s' not found\n", pszTool));
1569 return VERR_GENERAL_FAILURE; /* Lookup failed, should not happen. */
1570}
1571
1572
1573/**
1574 * Entry point for internal toolbox.
1575 *
1576 * @return True if an internal tool was handled, false if not.
1577 * @param argc Number of arguments.
1578 * @param argv Pointer to argument array.
1579 * @param prcExit Where to store the exit code when an
1580 * internal toolbox command was handled.
1581 */
1582bool VGSvcToolboxMain(int argc, char **argv, RTEXITCODE *prcExit)
1583{
1584
1585 /*
1586 * Check if the file named in argv[0] is one of the toolbox programs.
1587 */
1588 AssertReturn(argc > 0, false);
1589 const char *pszTool = RTPathFilename(argv[0]);
1590 PCVBOXSERVICETOOLBOXTOOL pTool = vgsvcToolboxLookUp(pszTool);
1591 if (!pTool)
1592 {
1593 /*
1594 * For debugging and testing purposes we also allow toolbox program access
1595 * when the first VBoxService argument is --use-toolbox.
1596 */
1597 if (argc < 2 || strcmp(argv[1], "--use-toolbox"))
1598 {
1599 /* We must match vgsvcGstCtrlProcessCreateProcess here and claim
1600 everything starting with "vbox_". */
1601 if (!RTStrStartsWith(pszTool, "vbox_"))
1602 return false;
1603 RTMsgError("Unknown tool: %s\n", pszTool);
1604 *prcExit = RTEXITCODE_SYNTAX;
1605 return true;
1606 }
1607
1608 /* No tool specified? Show toolbox help. */
1609 if (argc < 3)
1610 {
1611 RTMsgError("No tool following --use-toolbox\n");
1612 *prcExit = RTEXITCODE_SYNTAX;
1613 return true;
1614 }
1615
1616 argc -= 2;
1617 argv += 2;
1618 pszTool = argv[0];
1619 pTool = vgsvcToolboxLookUp(pszTool);
1620 if (!pTool)
1621 {
1622 *prcExit = RTEXITCODE_SUCCESS;
1623 if ( !strcmp(pszTool, "-V")
1624 || !strcmp(pszTool, "version"))
1625 vgsvcToolboxShowVersion();
1626 else if ( !strcmp(pszTool, "help")
1627 || !strcmp(pszTool, "--help")
1628 || !strcmp(pszTool, "-h"))
1629 vgsvcToolboxShowUsage();
1630 else
1631 {
1632 RTMsgError("Unknown tool: %s\n", pszTool);
1633 *prcExit = RTEXITCODE_SYNTAX;
1634 }
1635 return true;
1636 }
1637 }
1638
1639 /*
1640 * Invoke the handler.
1641 */
1642 RTMsgSetProgName("VBoxService/%s", pszTool);
1643 AssertPtr(pTool);
1644 *prcExit = pTool->pfnHandler(argc, argv);
1645
1646 return true;
1647}
1648
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette