VirtualBox

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

Last change on this file since 44824 was 43877, checked in by vboxsync, 12 years ago

build fix

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