1 | /* $Id: VBoxManageGuestCtrl.cpp 36035 2011-02-21 14:44:15Z vboxsync $ */
|
---|
2 | /** @file
|
---|
3 | * VBoxManage - Implementation of guestcontrol command.
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | * Copyright (C) 2010-2011 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 "VBoxManage.h"
|
---|
23 |
|
---|
24 | #ifndef VBOX_ONLY_DOCS
|
---|
25 |
|
---|
26 | #include <VBox/com/com.h>
|
---|
27 | #include <VBox/com/string.h>
|
---|
28 | #include <VBox/com/array.h>
|
---|
29 | #include <VBox/com/ErrorInfo.h>
|
---|
30 | #include <VBox/com/errorprint.h>
|
---|
31 |
|
---|
32 | #include <VBox/com/VirtualBox.h>
|
---|
33 | #include <VBox/com/EventQueue.h>
|
---|
34 |
|
---|
35 | #include <VBox/HostServices/GuestControlSvc.h> /* for PROC_STS_XXX */
|
---|
36 |
|
---|
37 | #include <iprt/asm.h>
|
---|
38 | #include <iprt/dir.h>
|
---|
39 | #include <iprt/file.h>
|
---|
40 | #include <iprt/isofs.h>
|
---|
41 | #include <iprt/getopt.h>
|
---|
42 | #include <iprt/list.h>
|
---|
43 | #include <iprt/path.h>
|
---|
44 | #include <iprt/thread.h>
|
---|
45 |
|
---|
46 | #ifdef USE_XPCOM_QUEUE
|
---|
47 | # include <sys/select.h>
|
---|
48 | # include <errno.h>
|
---|
49 | #endif
|
---|
50 |
|
---|
51 | #include <signal.h>
|
---|
52 |
|
---|
53 | #ifdef RT_OS_DARWIN
|
---|
54 | # include <CoreFoundation/CFRunLoop.h>
|
---|
55 | #endif
|
---|
56 |
|
---|
57 | using namespace com;
|
---|
58 |
|
---|
59 | /**
|
---|
60 | * IVirtualBoxCallback implementation for handling the GuestControlCallback in
|
---|
61 | * relation to the "guestcontrol * wait" command.
|
---|
62 | */
|
---|
63 | /** @todo */
|
---|
64 |
|
---|
65 | /** Set by the signal handler. */
|
---|
66 | static volatile bool g_fGuestCtrlCanceled = false;
|
---|
67 |
|
---|
68 | /*
|
---|
69 | * Structure holding a directory entry.
|
---|
70 | */
|
---|
71 | typedef struct DIRECTORYENTRY
|
---|
72 | {
|
---|
73 | char *pszSourcePath;
|
---|
74 | char *pszDestPath;
|
---|
75 | RTLISTNODE Node;
|
---|
76 | } DIRECTORYENTRY, *PDIRECTORYENTRY;
|
---|
77 |
|
---|
78 | /**
|
---|
79 | * Special exit codes for returning errors/information of a
|
---|
80 | * started guest process to the command line VBoxManage was started from.
|
---|
81 | * Useful for e.g. scripting.
|
---|
82 | * @todo r=bird: RTEXITCODE is using the RT prefix, that is reserved for IPRT
|
---|
83 | * use only.
|
---|
84 | */
|
---|
85 | enum RTEXITCODE_EXEC
|
---|
86 | {
|
---|
87 | RTEXITCODE_EXEC_SUCCESS = RTEXITCODE_SUCCESS,
|
---|
88 | RTEXITCODE_EXEC_FAILED = 16,
|
---|
89 | RTEXITCODE_EXEC_TERM_SIGNAL = 17,
|
---|
90 | RTEXITCODE_EXEC_TERM_ABEND = 18,
|
---|
91 | RTEXITCODE_EXEC_TIMEOUT = 19,
|
---|
92 | RTEXITCODE_EXEC_CANCELED = 20
|
---|
93 | };
|
---|
94 |
|
---|
95 | /**
|
---|
96 | * RTGetOpt-IDs for the guest execution control command line.
|
---|
97 | * @todo r=bird: RTGETOPTDEF_EXEC is using the RT prefix, that is reserved for
|
---|
98 | * IPRT use only.
|
---|
99 | */
|
---|
100 | enum RTGETOPTDEF_EXEC
|
---|
101 | {
|
---|
102 | RTGETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES = 1000,
|
---|
103 | RTGETOPTDEF_EXEC_OUTPUTFORMAT,
|
---|
104 | RTGETOPTDEF_EXEC_OUTPUTTYPE,
|
---|
105 | RTGETOPTDEF_EXEC_WAITFOREXIT,
|
---|
106 | RTGETOPTDEF_EXEC_WAITFORSTDOUT,
|
---|
107 | RTGETOPTDEF_EXEC_WAITFORSTDERR,
|
---|
108 | RTGETOPTDEF_EXEC_ARGS
|
---|
109 | };
|
---|
110 |
|
---|
111 | #endif /* VBOX_ONLY_DOCS */
|
---|
112 |
|
---|
113 | void usageGuestControl(PRTSTREAM pStrm)
|
---|
114 | {
|
---|
115 | RTStrmPrintf(pStrm,
|
---|
116 | "VBoxManage guestcontrol <vmname>|<uuid> exec[ute]\n"
|
---|
117 | " --image <path to program>\n"
|
---|
118 | " --username <name> --password <password>\n"
|
---|
119 | " [--environment \"<NAME>=<VALUE> [<NAME>=<VALUE>]\"]\n"
|
---|
120 | " [--timeout <msec>] [--verbose]\n"
|
---|
121 | " [--wait-exit] [--wait-stdout] [--wait-stdout]"
|
---|
122 | //" [--output-format=<dos>|<unix>]\n"
|
---|
123 | " [--output-type=<binary>|<text>]\n"
|
---|
124 | " [-- [<argument1>] ... [<argumentN>]\n"
|
---|
125 | /** @todo Add a "--" parameter (has to be last parameter) to directly execute
|
---|
126 | * stuff, e.g. "VBoxManage guestcontrol execute <VMName> --username <> ... -- /bin/rm -Rf /foo". */
|
---|
127 | "\n"
|
---|
128 | " <vmname>|<uuid> copyto|cp\n"
|
---|
129 | " <source on host> <destination on guest>\n"
|
---|
130 | " --username <name> --password <password>\n"
|
---|
131 | " [--dryrun] [--follow] [--recursive] [--verbose]\n"
|
---|
132 | "\n"
|
---|
133 | " <vmname>|<uuid> createdir[ectory]|mkdir|md\n"
|
---|
134 | " <directory to create on guest>\n"
|
---|
135 | " --username <name> --password <password>\n"
|
---|
136 | " [--parents] [--mode <mode>] [--verbose]\n"
|
---|
137 | "\n"
|
---|
138 | " <vmname>|<uuid> updateadditions\n"
|
---|
139 | " [--source <guest additions .ISO>] [--verbose]\n"
|
---|
140 | "\n");
|
---|
141 | }
|
---|
142 |
|
---|
143 | #ifndef VBOX_ONLY_DOCS
|
---|
144 |
|
---|
145 | /**
|
---|
146 | * Signal handler that sets g_fGuestCtrlCanceled.
|
---|
147 | *
|
---|
148 | * This can be executed on any thread in the process, on Windows it may even be
|
---|
149 | * a thread dedicated to delivering this signal. Do not doing anything
|
---|
150 | * unnecessary here.
|
---|
151 | */
|
---|
152 | static void guestCtrlSignalHandler(int iSignal)
|
---|
153 | {
|
---|
154 | NOREF(iSignal);
|
---|
155 | ASMAtomicWriteBool(&g_fGuestCtrlCanceled, true);
|
---|
156 | }
|
---|
157 |
|
---|
158 | /**
|
---|
159 | * Installs a custom signal handler to get notified
|
---|
160 | * whenever the user wants to intercept the program.
|
---|
161 | */
|
---|
162 | static void ctrlSignalHandlerInstall()
|
---|
163 | {
|
---|
164 | signal(SIGINT, guestCtrlSignalHandler);
|
---|
165 | #ifdef SIGBREAK
|
---|
166 | signal(SIGBREAK, guestCtrlSignalHandler);
|
---|
167 | #endif
|
---|
168 | }
|
---|
169 |
|
---|
170 | /**
|
---|
171 | * Uninstalls a previously installed signal handler.
|
---|
172 | */
|
---|
173 | static void ctrlSignalHandlerUninstall()
|
---|
174 | {
|
---|
175 | signal(SIGINT, SIG_DFL);
|
---|
176 | #ifdef SIGBREAK
|
---|
177 | signal(SIGBREAK, SIG_DFL);
|
---|
178 | #endif
|
---|
179 | }
|
---|
180 |
|
---|
181 | /**
|
---|
182 | * Translates a process status to a human readable
|
---|
183 | * string.
|
---|
184 | */
|
---|
185 | static const char *ctrlExecProcessStatusToText(ULONG uStatus)
|
---|
186 | {
|
---|
187 | switch (uStatus)
|
---|
188 | {
|
---|
189 | case guestControl::PROC_STS_STARTED:
|
---|
190 | return "started";
|
---|
191 | case guestControl::PROC_STS_TEN:
|
---|
192 | return "successfully terminated";
|
---|
193 | case guestControl::PROC_STS_TES:
|
---|
194 | return "terminated by signal";
|
---|
195 | case guestControl::PROC_STS_TEA:
|
---|
196 | return "abnormally aborted";
|
---|
197 | case guestControl::PROC_STS_TOK:
|
---|
198 | return "timed out";
|
---|
199 | case guestControl::PROC_STS_TOA:
|
---|
200 | return "timed out, hanging";
|
---|
201 | case guestControl::PROC_STS_DWN:
|
---|
202 | return "killed";
|
---|
203 | case guestControl::PROC_STS_ERROR:
|
---|
204 | return "error";
|
---|
205 | default:
|
---|
206 | break;
|
---|
207 | }
|
---|
208 | return "unknown";
|
---|
209 | }
|
---|
210 |
|
---|
211 | static int ctrlExecProcessStatusToExitCode(ULONG uStatus)
|
---|
212 | {
|
---|
213 | int rc = RTEXITCODE_EXEC_SUCCESS;
|
---|
214 | switch (uStatus)
|
---|
215 | {
|
---|
216 | /** @todo r=bird: Why do you use guestControl status codes here? Is the Main
|
---|
217 | * API not exposing these constants? If so, it's something that
|
---|
218 | * needs fixing. */
|
---|
219 | case guestControl::PROC_STS_STARTED:
|
---|
220 | rc = RTEXITCODE_EXEC_SUCCESS;
|
---|
221 | break;
|
---|
222 | case guestControl::PROC_STS_TEN:
|
---|
223 | /** @todo check the exit code, 0 is success, !0 should be indicated
|
---|
224 | * by a specail VBoxManage exit code. */
|
---|
225 | rc = RTEXITCODE_EXEC_SUCCESS;
|
---|
226 | break;
|
---|
227 | case guestControl::PROC_STS_TES:
|
---|
228 | rc = RTEXITCODE_EXEC_TERM_SIGNAL;
|
---|
229 | break;
|
---|
230 | case guestControl::PROC_STS_TEA:
|
---|
231 | rc = RTEXITCODE_EXEC_TERM_ABEND;
|
---|
232 | break;
|
---|
233 | case guestControl::PROC_STS_TOK:
|
---|
234 | rc = RTEXITCODE_EXEC_TIMEOUT;
|
---|
235 | break;
|
---|
236 | case guestControl::PROC_STS_TOA:
|
---|
237 | rc = RTEXITCODE_EXEC_TIMEOUT;
|
---|
238 | break;
|
---|
239 | case guestControl::PROC_STS_DWN:
|
---|
240 | /* Service/OS is stopping, process was killed, so
|
---|
241 | * not exactly an error of the started process ... */
|
---|
242 | rc = RTEXITCODE_EXEC_SUCCESS; /** @todo r=bird: return failure if you don't know what happend. No false positives, please. */
|
---|
243 | break;
|
---|
244 | case guestControl::PROC_STS_ERROR:
|
---|
245 | rc = RTEXITCODE_EXEC_FAILED;
|
---|
246 | break;
|
---|
247 | default:
|
---|
248 | AssertMsgFailed(("Unknown exit code (%u) from guest process returned!\n", uStatus));
|
---|
249 | break;
|
---|
250 | }
|
---|
251 | return rc;
|
---|
252 | }
|
---|
253 |
|
---|
254 | static int ctrlPrintError(com::ErrorInfo &errorInfo)
|
---|
255 | {
|
---|
256 | if ( errorInfo.isFullAvailable()
|
---|
257 | || errorInfo.isBasicAvailable())
|
---|
258 | {
|
---|
259 | /* If we got a VBOX_E_IPRT error we handle the error in a more gentle way
|
---|
260 | * because it contains more accurate info about what went wrong. */
|
---|
261 | if (errorInfo.getResultCode() == VBOX_E_IPRT_ERROR)
|
---|
262 | RTMsgError("%ls.", errorInfo.getText().raw());
|
---|
263 | else
|
---|
264 | {
|
---|
265 | RTMsgError("Error details:");
|
---|
266 | GluePrintErrorInfo(errorInfo);
|
---|
267 | }
|
---|
268 | return VERR_GENERAL_FAILURE; /** @todo */
|
---|
269 | }
|
---|
270 | AssertMsgFailedReturn(("Object has indicated no error!?\n"),
|
---|
271 | VERR_INVALID_PARAMETER);
|
---|
272 | }
|
---|
273 |
|
---|
274 | static int ctrlPrintError(IUnknown *pObj, const GUID &aIID)
|
---|
275 | {
|
---|
276 | com::ErrorInfo ErrInfo(pObj, aIID);
|
---|
277 | return ctrlPrintError(ErrInfo);
|
---|
278 | }
|
---|
279 |
|
---|
280 |
|
---|
281 | static int ctrlPrintProgressError(ComPtr<IProgress> progress)
|
---|
282 | {
|
---|
283 | int rc;
|
---|
284 | BOOL fCanceled;
|
---|
285 | if ( SUCCEEDED(progress->COMGETTER(Canceled(&fCanceled)))
|
---|
286 | && fCanceled)
|
---|
287 | {
|
---|
288 | rc = VERR_CANCELLED;
|
---|
289 | }
|
---|
290 | else
|
---|
291 | {
|
---|
292 | com::ProgressErrorInfo ErrInfo(progress);
|
---|
293 | rc = ctrlPrintError(ErrInfo);
|
---|
294 | }
|
---|
295 | return rc;
|
---|
296 | }
|
---|
297 |
|
---|
298 | /**
|
---|
299 | * Un-initializes the VM after guest control usage.
|
---|
300 | */
|
---|
301 | static void ctrlUninitVM(HandlerArg *pArg)
|
---|
302 | {
|
---|
303 | AssertPtrReturnVoid(pArg);
|
---|
304 | if (pArg->session)
|
---|
305 | pArg->session->UnlockMachine();
|
---|
306 | }
|
---|
307 |
|
---|
308 | /**
|
---|
309 | * Initializes the VM for IGuest operations.
|
---|
310 | *
|
---|
311 | * That is, checks whether it's up and running, if it can be locked (shared
|
---|
312 | * only) and returns a valid IGuest pointer on success.
|
---|
313 | *
|
---|
314 | * @return IPRT status code.
|
---|
315 | * @param pArg Our command line argument structure.
|
---|
316 | * @param pszNameOrId The VM's name or UUID.
|
---|
317 | * @param pGuest Where to return the IGuest interface pointer.
|
---|
318 | */
|
---|
319 | static int ctrlInitVM(HandlerArg *pArg, const char *pszNameOrId, ComPtr<IGuest> *pGuest)
|
---|
320 | {
|
---|
321 | AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
|
---|
322 | AssertPtrReturn(pszNameOrId, VERR_INVALID_PARAMETER);
|
---|
323 |
|
---|
324 | /* Lookup VM. */
|
---|
325 | ComPtr<IMachine> machine;
|
---|
326 | /* Assume it's an UUID. */
|
---|
327 | HRESULT rc;
|
---|
328 | CHECK_ERROR(pArg->virtualBox, FindMachine(Bstr(pszNameOrId).raw(),
|
---|
329 | machine.asOutParam()));
|
---|
330 | if (FAILED(rc))
|
---|
331 | return VERR_NOT_FOUND;
|
---|
332 |
|
---|
333 | /* Machine is running? */
|
---|
334 | MachineState_T machineState;
|
---|
335 | CHECK_ERROR_RET(machine, COMGETTER(State)(&machineState), 1);
|
---|
336 | if (machineState != MachineState_Running)
|
---|
337 | {
|
---|
338 | RTMsgError("Machine \"%s\" is not running (currently %s)!\n",
|
---|
339 | pszNameOrId, machineStateToName(machineState, false));
|
---|
340 | return VERR_VM_INVALID_VM_STATE;
|
---|
341 | }
|
---|
342 |
|
---|
343 | do
|
---|
344 | {
|
---|
345 | /* Open a session for the VM. */
|
---|
346 | CHECK_ERROR_BREAK(machine, LockMachine(pArg->session, LockType_Shared));
|
---|
347 | /* Get the associated console. */
|
---|
348 | ComPtr<IConsole> console;
|
---|
349 | CHECK_ERROR_BREAK(pArg->session, COMGETTER(Console)(console.asOutParam()));
|
---|
350 | /* ... and session machine. */
|
---|
351 | ComPtr<IMachine> sessionMachine;
|
---|
352 | CHECK_ERROR_BREAK(pArg->session, COMGETTER(Machine)(sessionMachine.asOutParam()));
|
---|
353 | /* Get IGuest interface. */
|
---|
354 | CHECK_ERROR_BREAK(console, COMGETTER(Guest)(pGuest->asOutParam()));
|
---|
355 | } while (0);
|
---|
356 |
|
---|
357 | if (FAILED(rc))
|
---|
358 | ctrlUninitVM(pArg);
|
---|
359 | return SUCCEEDED(rc) ? VINF_SUCCESS : VERR_GENERAL_FAILURE;
|
---|
360 | }
|
---|
361 |
|
---|
362 | /* <Missing docuemntation> */
|
---|
363 | static int handleCtrlExecProgram(ComPtr<IGuest> guest, HandlerArg *pArg)
|
---|
364 | {
|
---|
365 | AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
|
---|
366 |
|
---|
367 | /*
|
---|
368 | * Parse arguments.
|
---|
369 | */
|
---|
370 | if (pArg->argc < 2) /* At least the command we want to execute in the guest should be present :-). */
|
---|
371 | return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
|
---|
372 |
|
---|
373 | static const RTGETOPTDEF s_aOptions[] =
|
---|
374 | {
|
---|
375 | { "--environment", 'e', RTGETOPT_REQ_STRING },
|
---|
376 | { "--flags", 'f', RTGETOPT_REQ_STRING },
|
---|
377 | { "--ignore-operhaned-processes", RTGETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES, RTGETOPT_REQ_NOTHING },
|
---|
378 | { "--image", 'i', RTGETOPT_REQ_STRING },
|
---|
379 | //{ "--output-format", RTGETOPTDEF_EXEC_OUTPUTFORMAT, RTGETOPT_REQ_STRING },
|
---|
380 | { "--output-type", RTGETOPTDEF_EXEC_OUTPUTTYPE, RTGETOPT_REQ_STRING },
|
---|
381 | /** @todo r=bird: output-type and output-format are the same thing.
|
---|
382 | * Please just call it --dos2unix and --unix2dos and
|
---|
383 | * implement it them. (If you insist on flexibility, it would
|
---|
384 | * be --input-type <x> and --output-type <y>, where x and y
|
---|
385 | * could indicate line ending styles (CR/LF/CRLF), encodings
|
---|
386 | * (UTF-8, UTF-16, ++), code pages and plugin provided
|
---|
387 | * conversions. See the conv option of dd and iconv for
|
---|
388 | * inspiration.)
|
---|
389 | *
|
---|
390 | * The default must be no conversion at all, i.e.
|
---|
391 | * --output-type=binary. */
|
---|
392 | { "--password", 'p', RTGETOPT_REQ_STRING },
|
---|
393 | { "--timeout", 't', RTGETOPT_REQ_UINT32 },
|
---|
394 | { "--username", 'u', RTGETOPT_REQ_STRING },
|
---|
395 | { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
|
---|
396 | { "--wait-exit", RTGETOPTDEF_EXEC_WAITFOREXIT, RTGETOPT_REQ_NOTHING },
|
---|
397 | { "--wait-stdout", RTGETOPTDEF_EXEC_WAITFORSTDOUT, RTGETOPT_REQ_NOTHING },
|
---|
398 | { "--wait-stderr", RTGETOPTDEF_EXEC_WAITFORSTDERR, RTGETOPT_REQ_NOTHING },
|
---|
399 | /* @todo r=bird: '--' is interpreted by RTGetOpt() to indicate the
|
---|
400 | * end of arguments, you don't need to handle this specially.
|
---|
401 | * RTGetOpt() will return VINF_GETOPT_NOT_OPTION for anything following
|
---|
402 | * a '--' argument. So, let me give you some other examples for illustrating
|
---|
403 | * what I mean should work now (all the same, userid/pw missing):
|
---|
404 | * VBoxManage guestcontrol myvm exec --image /bin/busybox cp /foo /bar
|
---|
405 | * VBoxManage guestcontrol myvm exec --image /bin/busybox -- cp /foo /bar
|
---|
406 | * VBoxManage guestcontrol myvm exec --image /bin/busybox cp -- /foo /bar
|
---|
407 | * VBoxManage guestcontrol myvm exec --image /bin/busybox cp /foo -- /bar
|
---|
408 | *
|
---|
409 | * Same, but with standard cp (image defaults to /bin/cp):
|
---|
410 | * VBoxManage guestcontrol myvm exec /bin/cp /foo /bar
|
---|
411 | * VBoxManage guestcontrol myvm exec -- /bin/cp /foo /bar
|
---|
412 | * VBoxManage guestcontrol myvm exec /bin/cp -- /foo /bar
|
---|
413 | * VBoxManage guestcontrol myvm exec /bin/cp /foo -- /bar
|
---|
414 | *
|
---|
415 | * The old example:
|
---|
416 | * VBoxManage guestcontrol myvm exec --image /bin/busybox -- ln -s /foo /bar
|
---|
417 | *
|
---|
418 | * As an example of where '--' is used by standard linux utils, try delete a
|
---|
419 | * file named '-f' without specifying the directory:
|
---|
420 | * > -f # creates the file
|
---|
421 | * rm -v -f # does nothing because '-f' is a rm option.
|
---|
422 | * rm -v -- -f # does the trick because '--' tells rm to not read '-f' as an option.
|
---|
423 | */
|
---|
424 | { "--", RTGETOPTDEF_EXEC_ARGS, RTGETOPT_REQ_STRING }
|
---|
425 | };
|
---|
426 |
|
---|
427 | int ch;
|
---|
428 | RTGETOPTUNION ValueUnion;
|
---|
429 | RTGETOPTSTATE GetState;
|
---|
430 | RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
|
---|
431 |
|
---|
432 | Utf8Str Utf8Cmd;
|
---|
433 | uint32_t fFlags = 0;
|
---|
434 | com::SafeArray<IN_BSTR> args;
|
---|
435 | com::SafeArray<IN_BSTR> env;
|
---|
436 | Utf8Str Utf8UserName;
|
---|
437 | Utf8Str Utf8Password;
|
---|
438 | uint32_t cMsTimeout = 0;
|
---|
439 | bool fOutputBinary = false;
|
---|
440 | bool fWaitForExit = false;
|
---|
441 | bool fWaitForStdOut = false;
|
---|
442 | bool fWaitForStdErr = false;
|
---|
443 | bool fVerbose = false;
|
---|
444 |
|
---|
445 | int vrc = VINF_SUCCESS;
|
---|
446 | while ( (ch = RTGetOpt(&GetState, &ValueUnion))
|
---|
447 | && RT_SUCCESS(vrc))
|
---|
448 | {
|
---|
449 | /* For options that require an argument, ValueUnion has received the value. */
|
---|
450 | switch (ch)
|
---|
451 | {
|
---|
452 | case 'e': /* Environment */
|
---|
453 | {
|
---|
454 | char **papszArg;
|
---|
455 | int cArgs;
|
---|
456 |
|
---|
457 | vrc = RTGetOptArgvFromString(&papszArg, &cArgs, ValueUnion.psz, NULL);
|
---|
458 | if (RT_FAILURE(vrc))
|
---|
459 | return errorSyntax(USAGE_GUESTCONTROL, "Failed to parse environment value, rc=%Rrc", vrc);
|
---|
460 | for (int j = 0; j < cArgs; j++)
|
---|
461 | env.push_back(Bstr(papszArg[j]).raw());
|
---|
462 |
|
---|
463 | RTGetOptArgvFree(papszArg);
|
---|
464 | break;
|
---|
465 | }
|
---|
466 |
|
---|
467 | case RTGETOPTDEF_EXEC_IGNOREORPHANEDPROCESSES:
|
---|
468 | fFlags |= ExecuteProcessFlag_IgnoreOrphanedProcesses;
|
---|
469 | break;
|
---|
470 |
|
---|
471 | case 'i':
|
---|
472 | Utf8Cmd = ValueUnion.psz;
|
---|
473 | break;
|
---|
474 |
|
---|
475 | /** @todo Add DOS2UNIX and vice versa handling!
|
---|
476 | case RTGETOPTDEF_EXEC_OUTPUTFORMAT:
|
---|
477 | break;*/
|
---|
478 |
|
---|
479 | case RTGETOPTDEF_EXEC_OUTPUTTYPE:
|
---|
480 | if (!RTStrICmp(ValueUnion.psz, "binary"))
|
---|
481 | fOutputBinary = true;
|
---|
482 | else if (!RTStrICmp(ValueUnion.psz, "text"))
|
---|
483 | fOutputBinary = false;
|
---|
484 | else
|
---|
485 | {
|
---|
486 | AssertPtr(GetState.pDef->pszLong);
|
---|
487 | return errorSyntax(USAGE_GUESTCONTROL, "Unknown value for '%s' specified! Use either 'binary' or 'text'",
|
---|
488 | GetState.pDef->pszLong);
|
---|
489 | }
|
---|
490 | break;
|
---|
491 |
|
---|
492 | /** @todo Add a hidden flag. */
|
---|
493 |
|
---|
494 | case 'p': /* Password */
|
---|
495 | Utf8Password = ValueUnion.psz;
|
---|
496 | break;
|
---|
497 |
|
---|
498 | case 't': /* Timeout */
|
---|
499 | cMsTimeout = ValueUnion.u32;
|
---|
500 | break;
|
---|
501 |
|
---|
502 | case 'u': /* User name */
|
---|
503 | Utf8UserName = ValueUnion.psz;
|
---|
504 | break;
|
---|
505 |
|
---|
506 | case 'v': /* Verbose */
|
---|
507 | fVerbose = true;
|
---|
508 | break;
|
---|
509 |
|
---|
510 | case RTGETOPTDEF_EXEC_WAITFOREXIT:
|
---|
511 | fWaitForExit = true;
|
---|
512 | break;
|
---|
513 |
|
---|
514 | case RTGETOPTDEF_EXEC_WAITFORSTDOUT:
|
---|
515 | fWaitForExit = true;
|
---|
516 | fWaitForStdOut = true;
|
---|
517 | break;
|
---|
518 |
|
---|
519 | case RTGETOPTDEF_EXEC_WAITFORSTDERR:
|
---|
520 | fWaitForExit = true;
|
---|
521 | fWaitForStdErr = true;
|
---|
522 | break;
|
---|
523 |
|
---|
524 | case RTGETOPTDEF_EXEC_ARGS:
|
---|
525 | {
|
---|
526 | /* Push current parameter to vector. */
|
---|
527 | args.push_back(Bstr(ValueUnion.psz).raw());
|
---|
528 |
|
---|
529 | /*
|
---|
530 | * Add all following parameters after this one to our guest process
|
---|
531 | * argument vector.
|
---|
532 | */
|
---|
533 | while ( (ch = RTGetOpt(&GetState, &ValueUnion))
|
---|
534 | && RT_SUCCESS(vrc))
|
---|
535 | {
|
---|
536 | /*
|
---|
537 | * Is this option unknown or not recognized an option? Then just
|
---|
538 | * add the raw string value to our argument vector.
|
---|
539 | */
|
---|
540 | if ( ch == VINF_GETOPT_NOT_OPTION
|
---|
541 | || ch == VERR_GETOPT_UNKNOWN_OPTION)
|
---|
542 | args.push_back(Bstr(ValueUnion.psz).raw());
|
---|
543 | /*
|
---|
544 | * If this is an option/parameter we already defined for our actual
|
---|
545 | * execution command we need to take the pDef->pszLong value instead.
|
---|
546 | */
|
---|
547 | else if (ValueUnion.pDef)
|
---|
548 | args.push_back(Bstr(ValueUnion.pDef->pszLong).raw());
|
---|
549 | else
|
---|
550 | AssertMsgFailed(("Unknown parameter type detected!\n"));
|
---|
551 | }
|
---|
552 | break;
|
---|
553 | }
|
---|
554 |
|
---|
555 | case VINF_GETOPT_NOT_OPTION:
|
---|
556 | if (Utf8Cmd.isEmpty())
|
---|
557 | Utf8Cmd = ValueUnion.psz;
|
---|
558 | else
|
---|
559 | return RTGetOptPrintError(ch, &ValueUnion);
|
---|
560 | break;
|
---|
561 |
|
---|
562 | default:
|
---|
563 | return RTGetOptPrintError(ch, &ValueUnion);
|
---|
564 | }
|
---|
565 | }
|
---|
566 |
|
---|
567 | if (RT_FAILURE(vrc)) /** @todo r=bird: You don't need to check vrc here or in any of the two while conditions above because you are now returning directly in those cases. Drop the checks, this will keep things simpler. */
|
---|
568 | {
|
---|
569 | RTMsgError("Failed to parse argument '%c', rc=%Rrc", ch, vrc);
|
---|
570 | return RTEXITCODE_FAILURE;
|
---|
571 | }
|
---|
572 |
|
---|
573 | if (Utf8Cmd.isEmpty())
|
---|
574 | return errorSyntax(USAGE_GUESTCONTROL, "No command to execute specified!");
|
---|
575 |
|
---|
576 | if (Utf8UserName.isEmpty())
|
---|
577 | return errorSyntax(USAGE_GUESTCONTROL, "No user name specified!");
|
---|
578 |
|
---|
579 | /*
|
---|
580 | * <missing comment indicating that we're done parsing args and started doing something else>
|
---|
581 | */
|
---|
582 | HRESULT rc = S_OK;
|
---|
583 | if (fVerbose)
|
---|
584 | {
|
---|
585 | if (cMsTimeout == 0)
|
---|
586 | RTPrintf("Waiting for guest to start process ...\n");
|
---|
587 | else
|
---|
588 | RTPrintf("Waiting for guest to start process (within %ums)\n", cMsTimeout);
|
---|
589 | }
|
---|
590 |
|
---|
591 | /* Get current time stamp to later calculate rest of timeout left. */
|
---|
592 | uint64_t u64StartMS = RTTimeMilliTS();
|
---|
593 |
|
---|
594 | /* Execute the process. */
|
---|
595 | int rcProc = RTEXITCODE_EXEC_SUCCESS; /** @todo r=bird: Don't initialize this, please, set it explicitly in the various branches. It's easier to see what it's going to be that way. */
|
---|
596 | ComPtr<IProgress> progress;
|
---|
597 | ULONG uPID = 0;
|
---|
598 | rc = guest->ExecuteProcess(Bstr(Utf8Cmd).raw(),
|
---|
599 | fFlags,
|
---|
600 | ComSafeArrayAsInParam(args),
|
---|
601 | ComSafeArrayAsInParam(env),
|
---|
602 | Bstr(Utf8UserName).raw(),
|
---|
603 | Bstr(Utf8Password).raw(),
|
---|
604 | cMsTimeout,
|
---|
605 | &uPID,
|
---|
606 | progress.asOutParam());
|
---|
607 | if (FAILED(rc))
|
---|
608 | vrc = ctrlPrintError(guest, COM_IIDOF(IGuest)); /** @todo return straight away and drop state (e.g. rcProc) confusion. */
|
---|
609 | else
|
---|
610 | {
|
---|
611 | if (fVerbose)
|
---|
612 | RTPrintf("Process '%s' (PID: %u) started\n", Utf8Cmd.c_str(), uPID);
|
---|
613 | if (fWaitForExit)
|
---|
614 | {
|
---|
615 | if (fVerbose)
|
---|
616 | {
|
---|
617 | if (cMsTimeout) /* Wait with a certain timeout. */
|
---|
618 | {
|
---|
619 | /* Calculate timeout value left after process has been started. */
|
---|
620 | uint64_t u64Elapsed = RTTimeMilliTS() - u64StartMS;
|
---|
621 | /* Is timeout still bigger than current difference? */
|
---|
622 | if (cMsTimeout > u64Elapsed)
|
---|
623 | RTPrintf("Waiting for process to exit (%ums left) ...\n", cMsTimeout - u64Elapsed);
|
---|
624 | else
|
---|
625 | RTPrintf("No time left to wait for process!\n"); /** @todo a bit misleading ... */
|
---|
626 | }
|
---|
627 | else /* Wait forever. */
|
---|
628 | RTPrintf("Waiting for process to exit ...\n");
|
---|
629 | }
|
---|
630 |
|
---|
631 | /* Setup signal handling if cancelable. */
|
---|
632 | ASSERT(progress);
|
---|
633 | bool fCanceledAlready = false;
|
---|
634 | BOOL fCancelable;
|
---|
635 | HRESULT hrc = progress->COMGETTER(Cancelable)(&fCancelable);
|
---|
636 | if (FAILED(hrc))
|
---|
637 | fCancelable = FALSE;
|
---|
638 | if (fCancelable)
|
---|
639 | ctrlSignalHandlerInstall();
|
---|
640 |
|
---|
641 | /* Wait for process to exit ... */
|
---|
642 | BOOL fCompleted = FALSE;
|
---|
643 | BOOL fCanceled = FALSE;
|
---|
644 | int cMilliesSleep = 0;
|
---|
645 | while (SUCCEEDED(progress->COMGETTER(Completed(&fCompleted))))
|
---|
646 | {
|
---|
647 | SafeArray<BYTE> aOutputData;
|
---|
648 | ULONG cbOutputData = 0;
|
---|
649 |
|
---|
650 | /*
|
---|
651 | * Some data left to output?
|
---|
652 | */
|
---|
653 | if ( fWaitForStdOut
|
---|
654 | || fWaitForStdErr)
|
---|
655 | {
|
---|
656 | /** @todo r=bird: The timeout argument is bogus in several
|
---|
657 | * ways:
|
---|
658 | * 1. RT_MAX will evaluate the arguments twice, which may
|
---|
659 | * result in different values because RTTimeMilliTS()
|
---|
660 | * returns a higher value the 2nd time. Worst case:
|
---|
661 | * Imagine when RT_MAX calculates the remaining time
|
---|
662 | * out (first expansion) there is say 60 ms left. Then
|
---|
663 | * we're preempted and rescheduled after, say, 120 ms.
|
---|
664 | * We call RTTimeMilliTS() again and ends up with a
|
---|
665 | * value -60 ms, which translate to a UINT32_MAX - 59
|
---|
666 | * ms timeout.
|
---|
667 | *
|
---|
668 | * 2. When the period expires, we will wait forever since
|
---|
669 | * both 0 and -1 mean indefinite timeout with this API,
|
---|
670 | * at least that's one way of reading the main code.
|
---|
671 | *
|
---|
672 | * 3. There is a signed/unsigned ambiguity in the
|
---|
673 | * RT_MAX expression. The left hand side is signed
|
---|
674 | * integer (0), the right side is unsigned 64-bit. From
|
---|
675 | * what I can tell, the compiler will treat this as
|
---|
676 | * unsigned 64-bit and never return 0.
|
---|
677 | */
|
---|
678 | /** @todo r=bird: We must separate stderr and stdout
|
---|
679 | * output, seems bunched together here which
|
---|
680 | * won't do the trick for unix BOFHs. */
|
---|
681 | rc = guest->GetProcessOutput(uPID, 0 /* aFlags */,
|
---|
682 | RT_MAX(0, cMsTimeout - (RTTimeMilliTS() - u64StartMS)) /* Timeout in ms */,
|
---|
683 | _64K, ComSafeArrayAsOutParam(aOutputData));
|
---|
684 | if (FAILED(rc))
|
---|
685 | {
|
---|
686 | vrc = ctrlPrintError(guest, COM_IIDOF(IGuest));
|
---|
687 | cbOutputData = 0;
|
---|
688 | }
|
---|
689 | else
|
---|
690 | {
|
---|
691 | cbOutputData = aOutputData.size();
|
---|
692 | if (cbOutputData > 0)
|
---|
693 | {
|
---|
694 | /** @todo r=bird: Adding exec options to convert unix2dos
|
---|
695 | * and dos2unix. Use a VFS I/O stream filter for doing this, it's a
|
---|
696 | * generic problem and the new VFS APIs will handle it more
|
---|
697 | * transparently. (requires writing dos2unix/unix2dos filters ofc) */
|
---|
698 | if (!fOutputBinary)
|
---|
699 | {
|
---|
700 | /*
|
---|
701 | * If aOutputData is text data from the guest process' stdout or stderr,
|
---|
702 | * it has a platform dependent line ending. So standardize on
|
---|
703 | * Unix style, as RTStrmWrite does the LF -> CR/LF replacement on
|
---|
704 | * Windows. Otherwise we end up with CR/CR/LF on Windows.
|
---|
705 | */
|
---|
706 | ULONG cbOutputDataPrint = cbOutputData;
|
---|
707 | for (BYTE *s = aOutputData.raw(), *d = s;
|
---|
708 | s - aOutputData.raw() < (ssize_t)cbOutputData;
|
---|
709 | s++, d++)
|
---|
710 | {
|
---|
711 | if (*s == '\r')
|
---|
712 | {
|
---|
713 | /* skip over CR, adjust destination */
|
---|
714 | d--;
|
---|
715 | cbOutputDataPrint--;
|
---|
716 | }
|
---|
717 | else if (s != d)
|
---|
718 | *d = *s;
|
---|
719 | }
|
---|
720 | RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputDataPrint);
|
---|
721 | }
|
---|
722 | else /* Just dump all data as we got it ... */
|
---|
723 | RTStrmWrite(g_pStdOut, aOutputData.raw(), cbOutputData);
|
---|
724 | }
|
---|
725 | }
|
---|
726 | }
|
---|
727 |
|
---|
728 | /* No more output data left? */
|
---|
729 | if (cbOutputData <= 0)
|
---|
730 | {
|
---|
731 | /* Only break out from process handling loop if we processed (displayed)
|
---|
732 | * all output data or if there simply never was output data and the process
|
---|
733 | * has been marked as complete. */
|
---|
734 | if (fCompleted)
|
---|
735 | break;
|
---|
736 | }
|
---|
737 |
|
---|
738 | /* Process async cancelation */
|
---|
739 | if (g_fGuestCtrlCanceled && !fCanceledAlready)
|
---|
740 | {
|
---|
741 | hrc = progress->Cancel();
|
---|
742 | if (SUCCEEDED(hrc))
|
---|
743 | fCanceledAlready = TRUE;
|
---|
744 | else
|
---|
745 | g_fGuestCtrlCanceled = false;
|
---|
746 | }
|
---|
747 |
|
---|
748 | /* Progress canceled by Main API? */
|
---|
749 | if ( SUCCEEDED(progress->COMGETTER(Canceled(&fCanceled)))
|
---|
750 | && fCanceled)
|
---|
751 | break;
|
---|
752 |
|
---|
753 | /* Did we run out of time? */
|
---|
754 | if ( cMsTimeout
|
---|
755 | && RTTimeMilliTS() - u64StartMS > cMsTimeout)
|
---|
756 | {
|
---|
757 | progress->Cancel();
|
---|
758 | break;
|
---|
759 | }
|
---|
760 | } /* while */
|
---|
761 |
|
---|
762 | /* Undo signal handling */
|
---|
763 | if (fCancelable)
|
---|
764 | ctrlSignalHandlerUninstall();
|
---|
765 |
|
---|
766 | /* Report status back to the user. */
|
---|
767 | if (fCanceled)
|
---|
768 | {
|
---|
769 | if (fVerbose)
|
---|
770 | RTPrintf("Process execution canceled!\n");
|
---|
771 | rcProc = RTEXITCODE_EXEC_CANCELED;
|
---|
772 | }
|
---|
773 | else if ( fCompleted
|
---|
774 | && SUCCEEDED(rc)) /* The GetProcessOutput rc. */
|
---|
775 | {
|
---|
776 | LONG iRc;
|
---|
777 | CHECK_ERROR_RET(progress, COMGETTER(ResultCode)(&iRc), rc);
|
---|
778 | if (FAILED(iRc))
|
---|
779 | vrc = ctrlPrintProgressError(progress);
|
---|
780 | else
|
---|
781 | {
|
---|
782 | ULONG uRetStatus, uRetExitCode, uRetFlags;
|
---|
783 | rc = guest->GetProcessStatus(uPID, &uRetExitCode, &uRetFlags, &uRetStatus);
|
---|
784 | if (SUCCEEDED(rc) && fVerbose)
|
---|
785 | RTPrintf("Exit code=%u (Status=%u [%s], Flags=%u)\n", uRetExitCode, uRetStatus, ctrlExecProcessStatusToText(uRetStatus), uRetFlags);
|
---|
786 | /** @todo r=bird: This isn't taking uRetExitCode into
|
---|
787 | * account. We MUST give the users a way to indicate
|
---|
788 | * whether the guest program terminated with an 0 or
|
---|
789 | * non-zero exit code. */
|
---|
790 | rcProc = ctrlExecProcessStatusToExitCode(uRetStatus);
|
---|
791 | }
|
---|
792 | }
|
---|
793 | else
|
---|
794 | {
|
---|
795 | if (fVerbose)
|
---|
796 | RTPrintf("Process execution aborted!\n");
|
---|
797 | /** @todo r=bird: Should set rcProc here, this is not a
|
---|
798 | * success branch. */
|
---|
799 | }
|
---|
800 | }
|
---|
801 | }
|
---|
802 |
|
---|
803 | if (RT_FAILURE(vrc) || FAILED(rc))
|
---|
804 | return RTEXITCODE_FAILURE;
|
---|
805 | return rcProc;
|
---|
806 | }
|
---|
807 |
|
---|
808 | /**
|
---|
809 | * Appends a new file/directory entry to a given list.
|
---|
810 | *
|
---|
811 | * @return IPRT status code.
|
---|
812 | * @param pszFileSource Full qualified source path of file to copy (optional).
|
---|
813 | * @param pszFileDest Full qualified destination path (optional).
|
---|
814 | * @param pList Copy list used for insertion.
|
---|
815 | */
|
---|
816 | static int ctrlDirectoryEntryAppend(const char *pszFileSource, const char *pszFileDest,
|
---|
817 | PRTLISTNODE pList)
|
---|
818 | {
|
---|
819 | AssertPtrReturn(pList, VERR_INVALID_POINTER);
|
---|
820 | AssertReturn(pszFileSource || pszFileDest, VERR_INVALID_PARAMETER);
|
---|
821 |
|
---|
822 | PDIRECTORYENTRY pNode = (PDIRECTORYENTRY)RTMemAlloc(sizeof(DIRECTORYENTRY));
|
---|
823 | if (pNode == NULL)
|
---|
824 | return VERR_NO_MEMORY;
|
---|
825 |
|
---|
826 | pNode->pszSourcePath = NULL;
|
---|
827 | pNode->pszDestPath = NULL;
|
---|
828 |
|
---|
829 | if (pszFileSource)
|
---|
830 | {
|
---|
831 | pNode->pszSourcePath = RTStrDup(pszFileSource);
|
---|
832 | AssertPtrReturn(pNode->pszSourcePath, VERR_NO_MEMORY);
|
---|
833 | }
|
---|
834 | if (pszFileDest)
|
---|
835 | {
|
---|
836 | pNode->pszDestPath = RTStrDup(pszFileDest);
|
---|
837 | AssertPtrReturn(pNode->pszDestPath, VERR_NO_MEMORY);
|
---|
838 | }
|
---|
839 |
|
---|
840 | pNode->Node.pPrev = NULL;
|
---|
841 | pNode->Node.pNext = NULL;
|
---|
842 | RTListAppend(pList, &pNode->Node);
|
---|
843 | return VINF_SUCCESS;
|
---|
844 | }
|
---|
845 |
|
---|
846 | /**
|
---|
847 | * Destroys a directory list.
|
---|
848 | *
|
---|
849 | * @param pList Pointer to list to destroy.
|
---|
850 | */
|
---|
851 | static void ctrlDirectoryListDestroy(PRTLISTNODE pList)
|
---|
852 | {
|
---|
853 | AssertPtr(pList);
|
---|
854 |
|
---|
855 | /* Destroy file list. */
|
---|
856 | PDIRECTORYENTRY pNode = RTListGetFirst(pList, DIRECTORYENTRY, Node);
|
---|
857 | while (pNode)
|
---|
858 | {
|
---|
859 | PDIRECTORYENTRY pNext = RTListNodeGetNext(&pNode->Node, DIRECTORYENTRY, Node);
|
---|
860 | bool fLast = RTListNodeIsLast(pList, &pNode->Node);
|
---|
861 |
|
---|
862 | if (pNode->pszSourcePath)
|
---|
863 | RTStrFree(pNode->pszSourcePath);
|
---|
864 | if (pNode->pszDestPath)
|
---|
865 | RTStrFree(pNode->pszDestPath);
|
---|
866 | RTListNodeRemove(&pNode->Node);
|
---|
867 | RTMemFree(pNode);
|
---|
868 |
|
---|
869 | if (fLast)
|
---|
870 | break;
|
---|
871 |
|
---|
872 | pNode = pNext;
|
---|
873 | }
|
---|
874 | }
|
---|
875 |
|
---|
876 |
|
---|
877 | /**
|
---|
878 | * Reads a specified directory (recursively) based on the copy flags
|
---|
879 | * and appends all matching entries to the supplied list.
|
---|
880 | *
|
---|
881 | * @return IPRT status code.
|
---|
882 | * @param pszRootDir Directory to start with. Must end with
|
---|
883 | * a trailing slash and must be absolute.
|
---|
884 | * @param pszSubDir Sub directory part relative to the root
|
---|
885 | * directory; needed for recursion.
|
---|
886 | * @param pszFilter Search filter (e.g. *.pdf).
|
---|
887 | * @param pszDest Destination directory.
|
---|
888 | * @param fFlags Copy flags.
|
---|
889 | * @param pcObjects Where to store the overall objects to
|
---|
890 | * copy found.
|
---|
891 | * @param pList Pointer to the object list to use.
|
---|
892 | */
|
---|
893 | static int ctrlCopyDirectoryRead(const char *pszRootDir, const char *pszSubDir,
|
---|
894 | const char *pszFilter, const char *pszDest,
|
---|
895 | uint32_t fFlags, uint32_t *pcObjects, PRTLISTNODE pList)
|
---|
896 | {
|
---|
897 | AssertPtrReturn(pszRootDir, VERR_INVALID_POINTER);
|
---|
898 | /* Sub directory is optional. */
|
---|
899 | /* Filter directory is optional. */
|
---|
900 | AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
|
---|
901 | AssertPtrReturn(pcObjects, VERR_INVALID_POINTER);
|
---|
902 | AssertPtrReturn(pList, VERR_INVALID_POINTER);
|
---|
903 |
|
---|
904 | PRTDIR pDir = NULL;
|
---|
905 |
|
---|
906 | int rc = VINF_SUCCESS;
|
---|
907 | char szCurDir[RTPATH_MAX];
|
---|
908 | /* Construct current path. */
|
---|
909 | if (RTStrPrintf(szCurDir, sizeof(szCurDir), pszRootDir))
|
---|
910 | {
|
---|
911 | if (pszSubDir != NULL)
|
---|
912 | rc = RTPathAppend(szCurDir, sizeof(szCurDir), pszSubDir);
|
---|
913 | }
|
---|
914 | else
|
---|
915 | rc = VERR_NO_MEMORY;
|
---|
916 |
|
---|
917 | if (RT_SUCCESS(rc))
|
---|
918 | {
|
---|
919 | /* Open directory without a filter - RTDirOpenFiltered unfortunately
|
---|
920 | * cannot handle sub directories so we have to do the filtering ourselves. */
|
---|
921 | rc = RTDirOpen(&pDir, szCurDir);
|
---|
922 | for (;RT_SUCCESS(rc);)
|
---|
923 | {
|
---|
924 | RTDIRENTRY DirEntry;
|
---|
925 | rc = RTDirRead(pDir, &DirEntry, NULL);
|
---|
926 | if (RT_FAILURE(rc))
|
---|
927 | {
|
---|
928 | if (rc == VERR_NO_MORE_FILES)
|
---|
929 | rc = VINF_SUCCESS;
|
---|
930 | break;
|
---|
931 | }
|
---|
932 | switch (DirEntry.enmType)
|
---|
933 | {
|
---|
934 | case RTDIRENTRYTYPE_DIRECTORY:
|
---|
935 | /* Skip "." and ".." entrires. */
|
---|
936 | if ( !strcmp(DirEntry.szName, ".")
|
---|
937 | || !strcmp(DirEntry.szName, ".."))
|
---|
938 | {
|
---|
939 | break;
|
---|
940 | }
|
---|
941 | if (fFlags & CopyFileFlag_Recursive)
|
---|
942 | {
|
---|
943 | char *pszNewSub = NULL;
|
---|
944 | if (pszSubDir)
|
---|
945 | RTStrAPrintf(&pszNewSub, "%s%s/", pszSubDir, DirEntry.szName);
|
---|
946 | else
|
---|
947 | RTStrAPrintf(&pszNewSub, "%s/", DirEntry.szName);
|
---|
948 |
|
---|
949 | if (pszNewSub)
|
---|
950 | {
|
---|
951 | rc = ctrlCopyDirectoryRead(pszRootDir, pszNewSub,
|
---|
952 | pszFilter, pszDest,
|
---|
953 | fFlags, pcObjects, pList);
|
---|
954 | RTStrFree(pszNewSub);
|
---|
955 | }
|
---|
956 | else
|
---|
957 | rc = VERR_NO_MEMORY;
|
---|
958 | }
|
---|
959 | break;
|
---|
960 |
|
---|
961 | case RTDIRENTRYTYPE_SYMLINK:
|
---|
962 | if ( (fFlags & CopyFileFlag_Recursive)
|
---|
963 | && (fFlags & CopyFileFlag_FollowLinks))
|
---|
964 | {
|
---|
965 | /* Fall through to next case is intentional. */
|
---|
966 | }
|
---|
967 | else
|
---|
968 | break;
|
---|
969 |
|
---|
970 | case RTDIRENTRYTYPE_FILE:
|
---|
971 | {
|
---|
972 | bool fProcess = false;
|
---|
973 | if (pszFilter && RTStrSimplePatternMatch(pszFilter, DirEntry.szName))
|
---|
974 | fProcess = true;
|
---|
975 | else if (!pszFilter)
|
---|
976 | fProcess = true;
|
---|
977 |
|
---|
978 | if (fProcess)
|
---|
979 | {
|
---|
980 | char *pszFileSource = NULL;
|
---|
981 | char *pszFileDest = NULL;
|
---|
982 | if (RTStrAPrintf(&pszFileSource, "%s%s%s",
|
---|
983 | pszRootDir, pszSubDir ? pszSubDir : "",
|
---|
984 | DirEntry.szName) >= 0)
|
---|
985 | {
|
---|
986 | if (RTStrAPrintf(&pszFileDest, "%s%s%s",
|
---|
987 | pszDest, pszSubDir ? pszSubDir : "",
|
---|
988 | DirEntry.szName) <= 0)
|
---|
989 | {
|
---|
990 | rc = VERR_NO_MEMORY;
|
---|
991 | }
|
---|
992 | }
|
---|
993 | else
|
---|
994 | rc = VERR_NO_MEMORY;
|
---|
995 |
|
---|
996 | if (RT_SUCCESS(rc))
|
---|
997 | {
|
---|
998 | rc = ctrlDirectoryEntryAppend(pszFileSource, pszFileDest, pList);
|
---|
999 | if (RT_SUCCESS(rc))
|
---|
1000 | *pcObjects = *pcObjects + 1;
|
---|
1001 | }
|
---|
1002 |
|
---|
1003 | if (pszFileSource)
|
---|
1004 | RTStrFree(pszFileSource);
|
---|
1005 | if (pszFileDest)
|
---|
1006 | RTStrFree(pszFileDest);
|
---|
1007 | }
|
---|
1008 | }
|
---|
1009 | break;
|
---|
1010 |
|
---|
1011 | default:
|
---|
1012 | break;
|
---|
1013 | }
|
---|
1014 | if (RT_FAILURE(rc))
|
---|
1015 | break;
|
---|
1016 | }
|
---|
1017 | }
|
---|
1018 |
|
---|
1019 | if (pDir)
|
---|
1020 | RTDirClose(pDir);
|
---|
1021 | return rc;
|
---|
1022 | }
|
---|
1023 |
|
---|
1024 | /**
|
---|
1025 | * Initializes the copy process and builds up an object list
|
---|
1026 | * with all required information to start the actual copy process.
|
---|
1027 | *
|
---|
1028 | * @return IPRT status code.
|
---|
1029 | * @param pszSource Source path on host to use.
|
---|
1030 | * @param pszDest Destination path on guest to use.
|
---|
1031 | * @param fFlags Copy flags.
|
---|
1032 | * @param pcObjects Where to store the count of objects to be copied.
|
---|
1033 | * @param pList Where to store the object list.
|
---|
1034 | */
|
---|
1035 | static int ctrlCopyInit(const char *pszSource, const char *pszDest, uint32_t fFlags,
|
---|
1036 | uint32_t *pcObjects, PRTLISTNODE pList)
|
---|
1037 | {
|
---|
1038 | AssertPtrReturn(pszSource, VERR_INVALID_PARAMETER);
|
---|
1039 | AssertPtrReturn(pszDest, VERR_INVALID_PARAMETER);
|
---|
1040 | AssertPtrReturn(pcObjects, VERR_INVALID_PARAMETER);
|
---|
1041 | AssertPtrReturn(pList, VERR_INVALID_PARAMETER);
|
---|
1042 |
|
---|
1043 | int rc = VINF_SUCCESS;
|
---|
1044 | char *pszSourceAbs = RTPathAbsDup(pszSource);
|
---|
1045 | if (pszSourceAbs)
|
---|
1046 | {
|
---|
1047 | if ( RTPathFilename(pszSourceAbs)
|
---|
1048 | && RTFileExists(pszSourceAbs)) /* We have a single file ... */
|
---|
1049 | {
|
---|
1050 | char *pszDestAbs = RTStrDup(pszDest);
|
---|
1051 | if (pszDestAbs)
|
---|
1052 | {
|
---|
1053 | /* Do we have a trailing slash for the destination?
|
---|
1054 | * Then this is a directory ... */
|
---|
1055 | size_t cch = strlen(pszDestAbs);
|
---|
1056 | if ( cch > 1
|
---|
1057 | && ( RTPATH_IS_SLASH(pszDestAbs[cch - 1])
|
---|
1058 | || RTPATH_IS_SLASH(pszDestAbs[cch - 2])
|
---|
1059 | )
|
---|
1060 | )
|
---|
1061 | {
|
---|
1062 | rc = RTStrAAppend(&pszDestAbs, RTPathFilename(pszSourceAbs));
|
---|
1063 | }
|
---|
1064 | else
|
---|
1065 | {
|
---|
1066 | /* Since the desetination seems not to be a directory,
|
---|
1067 | * we assume that this is the absolute path to the destination
|
---|
1068 | * file -> nothing to do here ... */
|
---|
1069 | }
|
---|
1070 |
|
---|
1071 | if (RT_SUCCESS(rc))
|
---|
1072 | {
|
---|
1073 | RTListInit(pList);
|
---|
1074 | rc = ctrlDirectoryEntryAppend(pszSourceAbs, pszDestAbs, pList);
|
---|
1075 | *pcObjects = 1;
|
---|
1076 | }
|
---|
1077 | RTStrFree(pszDestAbs);
|
---|
1078 | }
|
---|
1079 | else
|
---|
1080 | rc = VERR_NO_MEMORY;
|
---|
1081 | }
|
---|
1082 | else /* ... or a directory. */
|
---|
1083 | {
|
---|
1084 | /* Append trailing slash to absolute directory. */
|
---|
1085 | if (RTDirExists(pszSourceAbs))
|
---|
1086 | RTStrAAppend(&pszSourceAbs, RTPATH_SLASH_STR);
|
---|
1087 |
|
---|
1088 | /* Extract directory filter (e.g. "*.exe"). */
|
---|
1089 | char *pszFilter = RTPathFilename(pszSourceAbs);
|
---|
1090 | char *pszSourceAbsRoot = RTStrDup(pszSourceAbs);
|
---|
1091 | char *pszDestAbs = RTStrDup(pszDest);
|
---|
1092 | if ( pszSourceAbsRoot
|
---|
1093 | && pszDestAbs)
|
---|
1094 | {
|
---|
1095 | if (pszFilter)
|
---|
1096 | {
|
---|
1097 | RTPathStripFilename(pszSourceAbsRoot);
|
---|
1098 | rc = RTStrAAppend(&pszSourceAbsRoot, RTPATH_SLASH_STR);
|
---|
1099 | }
|
---|
1100 | else
|
---|
1101 | {
|
---|
1102 | /*
|
---|
1103 | * If we have more than one file to copy, make sure that we have
|
---|
1104 | * a trailing slash so that we can construct a full path name
|
---|
1105 | * (e.g. "foo.txt" -> "c:/foo/temp.txt") as destination.
|
---|
1106 | */
|
---|
1107 | size_t cch = strlen(pszSourceAbsRoot);
|
---|
1108 | if ( cch > 1
|
---|
1109 | && !RTPATH_IS_SLASH(pszSourceAbsRoot[cch - 1])
|
---|
1110 | && !RTPATH_IS_SLASH(pszSourceAbsRoot[cch - 2]))
|
---|
1111 | {
|
---|
1112 | rc = RTStrAAppend(&pszSourceAbsRoot, RTPATH_SLASH_STR);
|
---|
1113 | }
|
---|
1114 | }
|
---|
1115 |
|
---|
1116 | if (RT_SUCCESS(rc))
|
---|
1117 | {
|
---|
1118 | /*
|
---|
1119 | * Make sure we have a valid destination path. All we can do
|
---|
1120 | * here is to check whether we have a trailing slash -- the rest
|
---|
1121 | * (i.e. path creation, rights etc.) needs to be done inside the guest.
|
---|
1122 | */
|
---|
1123 | size_t cch = strlen(pszDestAbs);
|
---|
1124 | if ( cch > 1
|
---|
1125 | && !RTPATH_IS_SLASH(pszDestAbs[cch - 1])
|
---|
1126 | && !RTPATH_IS_SLASH(pszDestAbs[cch - 2]))
|
---|
1127 | {
|
---|
1128 | rc = RTStrAAppend(&pszDestAbs, RTPATH_SLASH_STR);
|
---|
1129 | }
|
---|
1130 | }
|
---|
1131 |
|
---|
1132 | if (RT_SUCCESS(rc))
|
---|
1133 | {
|
---|
1134 | RTListInit(pList);
|
---|
1135 | rc = ctrlCopyDirectoryRead(pszSourceAbsRoot, NULL /* Sub directory */,
|
---|
1136 | pszFilter, pszDestAbs,
|
---|
1137 | fFlags, pcObjects, pList);
|
---|
1138 | if (RT_SUCCESS(rc) && *pcObjects == 0)
|
---|
1139 | rc = VERR_NOT_FOUND;
|
---|
1140 | }
|
---|
1141 |
|
---|
1142 | if (pszDestAbs)
|
---|
1143 | RTStrFree(pszDestAbs);
|
---|
1144 | if (pszSourceAbsRoot)
|
---|
1145 | RTStrFree(pszSourceAbsRoot);
|
---|
1146 | }
|
---|
1147 | else
|
---|
1148 | rc = VERR_NO_MEMORY;
|
---|
1149 | }
|
---|
1150 | RTStrFree(pszSourceAbs);
|
---|
1151 | }
|
---|
1152 | else
|
---|
1153 | rc = VERR_NO_MEMORY;
|
---|
1154 | return rc;
|
---|
1155 | }
|
---|
1156 |
|
---|
1157 | /**
|
---|
1158 | * Copys a file from host to the guest.
|
---|
1159 | *
|
---|
1160 | * @return IPRT status code.
|
---|
1161 | * @param pGuest IGuest interface pointer.
|
---|
1162 | * @param fVerbose Verbose flag.
|
---|
1163 | * @param pszSource Source path of existing host file to copy.
|
---|
1164 | * @param pszDest Destination path on guest to copy the file to.
|
---|
1165 | * @param pszUserName User name on guest to use for the copy operation.
|
---|
1166 | * @param pszPassword Password of user account.
|
---|
1167 | * @param fFlags Copy flags.
|
---|
1168 | */
|
---|
1169 | static int ctrlCopyFileToGuest(IGuest *pGuest, bool fVerbose, const char *pszSource, const char *pszDest,
|
---|
1170 | const char *pszUserName, const char *pszPassword,
|
---|
1171 | uint32_t fFlags)
|
---|
1172 | {
|
---|
1173 | AssertPtrReturn(pGuest, VERR_INVALID_PARAMETER);
|
---|
1174 | AssertPtrReturn(pszSource, VERR_INVALID_PARAMETER);
|
---|
1175 | AssertPtrReturn(pszDest, VERR_INVALID_PARAMETER);
|
---|
1176 | AssertPtrReturn(pszUserName, VERR_INVALID_PARAMETER);
|
---|
1177 | AssertPtrReturn(pszPassword, VERR_INVALID_PARAMETER);
|
---|
1178 |
|
---|
1179 | int vrc = VINF_SUCCESS;
|
---|
1180 | ComPtr<IProgress> progress;
|
---|
1181 | HRESULT rc = pGuest->CopyToGuest(Bstr(pszSource).raw(), Bstr(pszDest).raw(),
|
---|
1182 | Bstr(pszUserName).raw(), Bstr(pszPassword).raw(),
|
---|
1183 | fFlags, progress.asOutParam());
|
---|
1184 | if (FAILED(rc))
|
---|
1185 | vrc = ctrlPrintError(pGuest, COM_IIDOF(IGuest));
|
---|
1186 | else
|
---|
1187 | {
|
---|
1188 | rc = showProgress(progress);
|
---|
1189 | if (FAILED(rc))
|
---|
1190 | vrc = ctrlPrintProgressError(progress);
|
---|
1191 | }
|
---|
1192 | return vrc;
|
---|
1193 | }
|
---|
1194 |
|
---|
1195 | static int handleCtrlCopyTo(ComPtr<IGuest> guest, HandlerArg *pArg)
|
---|
1196 | {
|
---|
1197 | AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
|
---|
1198 |
|
---|
1199 | /*
|
---|
1200 | * Check the syntax.
|
---|
1201 | */
|
---|
1202 | if (pArg->argc < 2) /* At least the source + destination should be present :-). */
|
---|
1203 | return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
|
---|
1204 |
|
---|
1205 | static const RTGETOPTDEF s_aOptions[] =
|
---|
1206 | {
|
---|
1207 | { "--dryrun", 'd', RTGETOPT_REQ_NOTHING },
|
---|
1208 | { "--follow", 'F', RTGETOPT_REQ_NOTHING },
|
---|
1209 | { "--password", 'p', RTGETOPT_REQ_STRING },
|
---|
1210 | { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
|
---|
1211 | { "--username", 'u', RTGETOPT_REQ_STRING },
|
---|
1212 | { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
|
---|
1213 | };
|
---|
1214 |
|
---|
1215 | int ch;
|
---|
1216 | RTGETOPTUNION ValueUnion;
|
---|
1217 | RTGETOPTSTATE GetState;
|
---|
1218 | RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
|
---|
1219 |
|
---|
1220 | Utf8Str Utf8Source;
|
---|
1221 | Utf8Str Utf8Dest;
|
---|
1222 | Utf8Str Utf8UserName;
|
---|
1223 | Utf8Str Utf8Password;
|
---|
1224 | uint32_t fFlags = CopyFileFlag_None;
|
---|
1225 | bool fVerbose = false;
|
---|
1226 | bool fCopyRecursive = false;
|
---|
1227 | bool fDryRun = false;
|
---|
1228 |
|
---|
1229 | int vrc = VINF_SUCCESS;
|
---|
1230 | uint32_t idxNonOption = 0;
|
---|
1231 | bool fUsageOK = true;
|
---|
1232 | while ( (ch = RTGetOpt(&GetState, &ValueUnion))
|
---|
1233 | && RT_SUCCESS(vrc))
|
---|
1234 | {
|
---|
1235 | /* For options that require an argument, ValueUnion has received the value. */
|
---|
1236 | switch (ch)
|
---|
1237 | {
|
---|
1238 | case 'd': /* Dry run */
|
---|
1239 | fDryRun = true;
|
---|
1240 | break;
|
---|
1241 |
|
---|
1242 | case 'F': /* Follow symlinks */
|
---|
1243 | fFlags |= CopyFileFlag_FollowLinks;
|
---|
1244 | break;
|
---|
1245 |
|
---|
1246 | case 'p': /* Password */
|
---|
1247 | Utf8Password = ValueUnion.psz;
|
---|
1248 | break;
|
---|
1249 |
|
---|
1250 | case 'R': /* Recursive processing */
|
---|
1251 | fFlags |= CopyFileFlag_Recursive;
|
---|
1252 | break;
|
---|
1253 |
|
---|
1254 | case 'u': /* User name */
|
---|
1255 | Utf8UserName = ValueUnion.psz;
|
---|
1256 | break;
|
---|
1257 |
|
---|
1258 | case 'v': /* Verbose */
|
---|
1259 | fVerbose = true;
|
---|
1260 | break;
|
---|
1261 |
|
---|
1262 | case VINF_GETOPT_NOT_OPTION:
|
---|
1263 | {
|
---|
1264 | /* Get the actual source + destination. */
|
---|
1265 | switch (idxNonOption)
|
---|
1266 | {
|
---|
1267 | case 0:
|
---|
1268 | Utf8Source = ValueUnion.psz;
|
---|
1269 | break;
|
---|
1270 |
|
---|
1271 | case 1:
|
---|
1272 | Utf8Dest = ValueUnion.psz;
|
---|
1273 | break;
|
---|
1274 |
|
---|
1275 | default:
|
---|
1276 | /** @todo r=bird: {2..UINT32_MAX-1} goes unnoticed
|
---|
1277 | * to /dev/null? That doesn't make much
|
---|
1278 | * sense... Why not just return with a syntax
|
---|
1279 | * error straight away? */
|
---|
1280 | break;
|
---|
1281 | }
|
---|
1282 | idxNonOption++;
|
---|
1283 | if (idxNonOption == UINT32_MAX)
|
---|
1284 | {
|
---|
1285 | RTMsgError("Too many files specified! Aborting.\n");
|
---|
1286 | vrc = VERR_TOO_MUCH_DATA; /** @todo r=bird: return straight away, drop the RT_SUCCESS(vrc) from the while condition. Keep things simple. */
|
---|
1287 | }
|
---|
1288 | break;
|
---|
1289 | }
|
---|
1290 |
|
---|
1291 | default:
|
---|
1292 | return RTGetOptPrintError(ch, &ValueUnion);
|
---|
1293 | }
|
---|
1294 | }
|
---|
1295 |
|
---|
1296 | if (!fUsageOK) /** @todo r=bird: fUsageOK - never set to false, just drop it. Keep the state simple. */
|
---|
1297 | return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
|
---|
1298 |
|
---|
1299 | if (Utf8Source.isEmpty())
|
---|
1300 | return errorSyntax(USAGE_GUESTCONTROL,
|
---|
1301 | "No source specified!");
|
---|
1302 |
|
---|
1303 | if (Utf8Dest.isEmpty())
|
---|
1304 | return errorSyntax(USAGE_GUESTCONTROL,
|
---|
1305 | "No destination specified!");
|
---|
1306 |
|
---|
1307 | if (Utf8UserName.isEmpty())
|
---|
1308 | return errorSyntax(USAGE_GUESTCONTROL,
|
---|
1309 | "No user name specified!");
|
---|
1310 |
|
---|
1311 | /*
|
---|
1312 | * Done parsing arguments, do stuff.
|
---|
1313 | */
|
---|
1314 | HRESULT rc = S_OK;
|
---|
1315 | if (fVerbose)
|
---|
1316 | {
|
---|
1317 | if (fDryRun)
|
---|
1318 | RTPrintf("Dry run - no files copied!\n");
|
---|
1319 | RTPrintf("Gathering file information ...\n");
|
---|
1320 | }
|
---|
1321 |
|
---|
1322 | RTLISTNODE listToCopy;
|
---|
1323 | uint32_t cObjects = 0;
|
---|
1324 | vrc = ctrlCopyInit(Utf8Source.c_str(), Utf8Dest.c_str(), fFlags,
|
---|
1325 | &cObjects, &listToCopy);
|
---|
1326 | if (RT_FAILURE(vrc))
|
---|
1327 | {
|
---|
1328 | switch (vrc)
|
---|
1329 | {
|
---|
1330 | case VERR_NOT_FOUND:
|
---|
1331 | RTMsgError("No files to copy found!\n");
|
---|
1332 | break;
|
---|
1333 |
|
---|
1334 | case VERR_FILE_NOT_FOUND:
|
---|
1335 | RTMsgError("Source path \"%s\" not found!\n", Utf8Source.c_str());
|
---|
1336 | break;
|
---|
1337 |
|
---|
1338 | default:
|
---|
1339 | RTMsgError("Failed to initialize, rc=%Rrc\n", vrc);
|
---|
1340 | break;
|
---|
1341 | }
|
---|
1342 | }
|
---|
1343 | else
|
---|
1344 | {
|
---|
1345 | PDIRECTORYENTRY pNode;
|
---|
1346 | if (RT_SUCCESS(vrc))
|
---|
1347 | {
|
---|
1348 | if (fVerbose)
|
---|
1349 | {
|
---|
1350 | if (fCopyRecursive)
|
---|
1351 | RTPrintf("Recursively copying \"%s\" to \"%s\" (%u file(s)) ...\n",
|
---|
1352 | Utf8Source.c_str(), Utf8Dest.c_str(), cObjects);
|
---|
1353 | else
|
---|
1354 | RTPrintf("Copying \"%s\" to \"%s\" (%u file(s)) ...\n",
|
---|
1355 | Utf8Source.c_str(), Utf8Dest.c_str(), cObjects);
|
---|
1356 | }
|
---|
1357 |
|
---|
1358 | uint32_t iObject = 1;
|
---|
1359 | RTListForEach(&listToCopy, pNode, DIRECTORYENTRY, Node)
|
---|
1360 | {
|
---|
1361 | if (!fDryRun)
|
---|
1362 | {
|
---|
1363 | if (fVerbose)
|
---|
1364 | RTPrintf("Copying \"%s\" to \"%s\" (%u/%u) ...\n",
|
---|
1365 | pNode->pszSourcePath, pNode->pszDestPath, iObject, cObjects);
|
---|
1366 | /* Finally copy the desired file (if no dry run selected). */
|
---|
1367 | if (!fDryRun)
|
---|
1368 | vrc = ctrlCopyFileToGuest(guest, fVerbose, pNode->pszSourcePath, pNode->pszDestPath,
|
---|
1369 | Utf8UserName.c_str(), Utf8Password.c_str(), fFlags);
|
---|
1370 | }
|
---|
1371 | if (RT_FAILURE(vrc))
|
---|
1372 | break;
|
---|
1373 | iObject++;
|
---|
1374 | }
|
---|
1375 | if (RT_SUCCESS(vrc) && fVerbose)
|
---|
1376 | RTPrintf("Copy operation successful!\n");
|
---|
1377 | }
|
---|
1378 | ctrlDirectoryListDestroy(&listToCopy);
|
---|
1379 | }
|
---|
1380 |
|
---|
1381 | if (RT_FAILURE(vrc))
|
---|
1382 | rc = VBOX_E_IPRT_ERROR;
|
---|
1383 | return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
|
---|
1384 | }
|
---|
1385 |
|
---|
1386 | static int handleCtrlCreateDirectory(ComPtr<IGuest> guest, HandlerArg *pArg)
|
---|
1387 | {
|
---|
1388 | AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
|
---|
1389 |
|
---|
1390 | /*
|
---|
1391 | * Check the syntax. We can deduce the correct syntax from the number of
|
---|
1392 | * arguments.
|
---|
1393 | */
|
---|
1394 | if (pArg->argc < 2) /* At least the directory we want to create should be present :-). */
|
---|
1395 | return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
|
---|
1396 |
|
---|
1397 | static const RTGETOPTDEF s_aOptions[] =
|
---|
1398 | {
|
---|
1399 | { "--mode", 'm', RTGETOPT_REQ_UINT32 },
|
---|
1400 | { "--parents", 'P', RTGETOPT_REQ_NOTHING },
|
---|
1401 | { "--password", 'p', RTGETOPT_REQ_STRING },
|
---|
1402 | { "--username", 'u', RTGETOPT_REQ_STRING },
|
---|
1403 | { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
|
---|
1404 | };
|
---|
1405 |
|
---|
1406 | int ch;
|
---|
1407 | RTGETOPTUNION ValueUnion;
|
---|
1408 | RTGETOPTSTATE GetState;
|
---|
1409 | RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
|
---|
1410 |
|
---|
1411 | Utf8Str Utf8UserName;
|
---|
1412 | Utf8Str Utf8Password;
|
---|
1413 | uint32_t fFlags = CreateDirectoryFlag_None;
|
---|
1414 | uint32_t uMode = 0;
|
---|
1415 | bool fVerbose = false;
|
---|
1416 |
|
---|
1417 | RTLISTNODE listDirs;
|
---|
1418 | uint32_t cDirs = 0;
|
---|
1419 | RTListInit(&listDirs);
|
---|
1420 |
|
---|
1421 | int vrc = VINF_SUCCESS;
|
---|
1422 | bool fUsageOK = true;
|
---|
1423 | while ( (ch = RTGetOpt(&GetState, &ValueUnion))
|
---|
1424 | && RT_SUCCESS(vrc))
|
---|
1425 | {
|
---|
1426 | /* For options that require an argument, ValueUnion has received the value. */
|
---|
1427 | switch (ch)
|
---|
1428 | {
|
---|
1429 | case 'm': /* Mode */
|
---|
1430 | uMode = ValueUnion.u32;
|
---|
1431 | break;
|
---|
1432 |
|
---|
1433 | case 'P': /* Create parents */
|
---|
1434 | fFlags |= CreateDirectoryFlag_Parents;
|
---|
1435 | break;
|
---|
1436 |
|
---|
1437 | case 'p': /* Password */
|
---|
1438 | Utf8Password = ValueUnion.psz;
|
---|
1439 | break;
|
---|
1440 |
|
---|
1441 | case 'u': /* User name */
|
---|
1442 | Utf8UserName = ValueUnion.psz;
|
---|
1443 | break;
|
---|
1444 |
|
---|
1445 | case 'v': /* Verbose */
|
---|
1446 | fVerbose = true;
|
---|
1447 | break;
|
---|
1448 |
|
---|
1449 | case VINF_GETOPT_NOT_OPTION:
|
---|
1450 | {
|
---|
1451 | vrc = ctrlDirectoryEntryAppend(NULL, /* No source given */
|
---|
1452 | ValueUnion.psz, /* Destination */
|
---|
1453 | &listDirs);
|
---|
1454 | if (RT_SUCCESS(vrc))
|
---|
1455 | {
|
---|
1456 | cDirs++;
|
---|
1457 | if (cDirs == UINT32_MAX)
|
---|
1458 | {
|
---|
1459 | RTMsgError("Too many directories specified! Aborting.\n");
|
---|
1460 | vrc = VERR_TOO_MUCH_DATA;
|
---|
1461 | }
|
---|
1462 | }
|
---|
1463 | break;
|
---|
1464 | }
|
---|
1465 |
|
---|
1466 | default:
|
---|
1467 | return RTGetOptPrintError(ch, &ValueUnion);
|
---|
1468 | }
|
---|
1469 | }
|
---|
1470 |
|
---|
1471 | if (!fUsageOK)
|
---|
1472 | return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
|
---|
1473 |
|
---|
1474 | if (!cDirs)
|
---|
1475 | return errorSyntax(USAGE_GUESTCONTROL,
|
---|
1476 | "No directory to create specified!");
|
---|
1477 |
|
---|
1478 | if (Utf8UserName.isEmpty())
|
---|
1479 | return errorSyntax(USAGE_GUESTCONTROL,
|
---|
1480 | "No user name specified!");
|
---|
1481 |
|
---|
1482 | HRESULT rc = S_OK;
|
---|
1483 | if (fVerbose && cDirs > 1)
|
---|
1484 | RTPrintf("Creating %u directories ...\n", cDirs);
|
---|
1485 |
|
---|
1486 | PDIRECTORYENTRY pNode;
|
---|
1487 | RTListForEach(&listDirs, pNode, DIRECTORYENTRY, Node)
|
---|
1488 | {
|
---|
1489 | if (fVerbose)
|
---|
1490 | RTPrintf("Creating directory \"%s\" ...\n", pNode->pszDestPath);
|
---|
1491 |
|
---|
1492 | ComPtr<IProgress> progress;
|
---|
1493 | rc = guest->CreateDirectory(Bstr(pNode->pszDestPath).raw(),
|
---|
1494 | Bstr(Utf8UserName).raw(), Bstr(Utf8Password).raw(),
|
---|
1495 | uMode, fFlags, progress.asOutParam());
|
---|
1496 | if (FAILED(rc))
|
---|
1497 | {
|
---|
1498 | ctrlPrintError(guest, COM_IIDOF(IGuest)); /* (return code ignored, save original rc) */
|
---|
1499 | break;
|
---|
1500 | }
|
---|
1501 | }
|
---|
1502 | ctrlDirectoryListDestroy(&listDirs);
|
---|
1503 | return SUCCEEDED(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
|
---|
1504 | }
|
---|
1505 |
|
---|
1506 | static int handleCtrlUpdateAdditions(ComPtr<IGuest> guest, HandlerArg *pArg)
|
---|
1507 | {
|
---|
1508 | AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
|
---|
1509 |
|
---|
1510 | /*
|
---|
1511 | * Check the syntax. We can deduce the correct syntax from the number of
|
---|
1512 | * arguments.
|
---|
1513 | */
|
---|
1514 | if (pArg->argc < 1) /* At least the VM name should be present :-). */ /** @todo r=bird: This is no longer correct. Ditto for all other handlers. */
|
---|
1515 | return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
|
---|
1516 |
|
---|
1517 | Utf8Str Utf8Source;
|
---|
1518 | bool fVerbose = false;
|
---|
1519 |
|
---|
1520 | static const RTGETOPTDEF s_aOptions[] =
|
---|
1521 | {
|
---|
1522 | { "--source", 's', RTGETOPT_REQ_STRING },
|
---|
1523 | { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
|
---|
1524 | };
|
---|
1525 |
|
---|
1526 | int ch;
|
---|
1527 | RTGETOPTUNION ValueUnion;
|
---|
1528 | RTGETOPTSTATE GetState;
|
---|
1529 | RTGetOptInit(&GetState, pArg->argc, pArg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
|
---|
1530 |
|
---|
1531 | int vrc = VINF_SUCCESS;
|
---|
1532 | while ( (ch = RTGetOpt(&GetState, &ValueUnion))
|
---|
1533 | && RT_SUCCESS(vrc))
|
---|
1534 | {
|
---|
1535 | switch (ch)
|
---|
1536 | {
|
---|
1537 | case 's':
|
---|
1538 | Utf8Source = ValueUnion.psz;
|
---|
1539 | break;
|
---|
1540 |
|
---|
1541 | case 'v':
|
---|
1542 | fVerbose = true;
|
---|
1543 | break;
|
---|
1544 |
|
---|
1545 | default:
|
---|
1546 | return RTGetOptPrintError(ch, &ValueUnion);
|
---|
1547 | }
|
---|
1548 | }
|
---|
1549 |
|
---|
1550 | if (fVerbose)
|
---|
1551 | RTPrintf("Updating Guest Additions ...\n");
|
---|
1552 |
|
---|
1553 | #ifdef DEBUG_andy
|
---|
1554 | if (Utf8Source.isEmpty())
|
---|
1555 | Utf8Source = "c:\\Downloads\\VBoxGuestAdditions-r67158.iso";
|
---|
1556 | #endif
|
---|
1557 |
|
---|
1558 | /* Determine source if not set yet. */
|
---|
1559 | if (Utf8Source.isEmpty())
|
---|
1560 | {
|
---|
1561 | char strTemp[RTPATH_MAX];
|
---|
1562 | vrc = RTPathAppPrivateNoArch(strTemp, sizeof(strTemp));
|
---|
1563 | AssertRC(vrc);
|
---|
1564 | Utf8Str Utf8Src1 = Utf8Str(strTemp).append("/VBoxGuestAdditions.iso");
|
---|
1565 |
|
---|
1566 | vrc = RTPathExecDir(strTemp, sizeof(strTemp));
|
---|
1567 | AssertRC(vrc);
|
---|
1568 | Utf8Str Utf8Src2 = Utf8Str(strTemp).append("/additions/VBoxGuestAdditions.iso");
|
---|
1569 |
|
---|
1570 | /* Check the standard image locations */
|
---|
1571 | if (RTFileExists(Utf8Src1.c_str()))
|
---|
1572 | Utf8Source = Utf8Src1;
|
---|
1573 | else if (RTFileExists(Utf8Src2.c_str()))
|
---|
1574 | Utf8Source = Utf8Src2;
|
---|
1575 | else
|
---|
1576 | {
|
---|
1577 | RTMsgError("Source could not be determined! Please use --source to specify a valid source.\n");
|
---|
1578 | vrc = VERR_FILE_NOT_FOUND;
|
---|
1579 | }
|
---|
1580 | }
|
---|
1581 | else if (!RTFileExists(Utf8Source.c_str()))
|
---|
1582 | {
|
---|
1583 | RTMsgError("Source \"%s\" does not exist!\n", Utf8Source.c_str());
|
---|
1584 | vrc = VERR_FILE_NOT_FOUND;
|
---|
1585 | }
|
---|
1586 |
|
---|
1587 | if (RT_SUCCESS(vrc))
|
---|
1588 | {
|
---|
1589 | if (fVerbose)
|
---|
1590 | RTPrintf("Using source: %s\n", Utf8Source.c_str());
|
---|
1591 |
|
---|
1592 | HRESULT rc = S_OK;
|
---|
1593 | ComPtr<IProgress> progress;
|
---|
1594 | CHECK_ERROR(guest, UpdateGuestAdditions(Bstr(Utf8Source).raw(),
|
---|
1595 | /* Wait for whole update process to complete. */
|
---|
1596 | AdditionsUpdateFlag_None,
|
---|
1597 | progress.asOutParam()));
|
---|
1598 | if (FAILED(rc))
|
---|
1599 | vrc = ctrlPrintError(guest, COM_IIDOF(IGuest));
|
---|
1600 | else
|
---|
1601 | {
|
---|
1602 | rc = showProgress(progress);
|
---|
1603 | if (FAILED(rc))
|
---|
1604 | vrc = ctrlPrintProgressError(progress);
|
---|
1605 | else if (fVerbose)
|
---|
1606 | RTPrintf("Guest Additions update successful.\n");
|
---|
1607 | }
|
---|
1608 | }
|
---|
1609 |
|
---|
1610 | return RT_SUCCESS(vrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
|
---|
1611 | }
|
---|
1612 |
|
---|
1613 | /**
|
---|
1614 | * Access the guest control store.
|
---|
1615 | *
|
---|
1616 | * @returns program exit code.
|
---|
1617 | * @note see the command line API description for parameters
|
---|
1618 | */
|
---|
1619 | int handleGuestControl(HandlerArg *pArg)
|
---|
1620 | {
|
---|
1621 | AssertPtrReturn(pArg, VERR_INVALID_PARAMETER);
|
---|
1622 |
|
---|
1623 | /* At least the VM name + sub command needs to be specified. */
|
---|
1624 | if (pArg->argc <= 2)
|
---|
1625 | return errorSyntax(USAGE_GUESTCONTROL, "Incorrect parameters");
|
---|
1626 |
|
---|
1627 | HandlerArg arg = *pArg;
|
---|
1628 | arg.argc = pArg->argc - 2; /* Skip VM name and sub command. */
|
---|
1629 | arg.argv = pArg->argv + 2; /* Same here. */
|
---|
1630 |
|
---|
1631 | ComPtr<IGuest> guest;
|
---|
1632 | int vrc = ctrlInitVM(pArg, pArg->argv[0] /* VM Name */, &guest);
|
---|
1633 | if (RT_SUCCESS(vrc))
|
---|
1634 | {
|
---|
1635 | int rcExit;
|
---|
1636 | if ( !strcmp(pArg->argv[1], "exec")
|
---|
1637 | || !strcmp(pArg->argv[1], "execute"))
|
---|
1638 | {
|
---|
1639 | rcExit = handleCtrlExecProgram(guest, &arg);
|
---|
1640 | }
|
---|
1641 | else if ( !strcmp(pArg->argv[1], "copyto")
|
---|
1642 | || !strcmp(pArg->argv[1], "cp"))
|
---|
1643 | {
|
---|
1644 | rcExit = handleCtrlCopyTo(guest, &arg);
|
---|
1645 | }
|
---|
1646 | else if ( !strcmp(pArg->argv[1], "createdirectory")
|
---|
1647 | || !strcmp(pArg->argv[1], "createdir")
|
---|
1648 | || !strcmp(pArg->argv[1], "mkdir")
|
---|
1649 | || !strcmp(pArg->argv[1], "md"))
|
---|
1650 | {
|
---|
1651 | rcExit = handleCtrlCreateDirectory(guest, &arg);
|
---|
1652 | }
|
---|
1653 | else if ( !strcmp(pArg->argv[1], "updateadditions")
|
---|
1654 | || !strcmp(pArg->argv[1], "updateadds"))
|
---|
1655 | {
|
---|
1656 | rcExit = handleCtrlUpdateAdditions(guest, &arg);
|
---|
1657 | }
|
---|
1658 | else
|
---|
1659 | rcExit = errorSyntax(USAGE_GUESTCONTROL, "No sub command specified!");
|
---|
1660 |
|
---|
1661 | ctrlUninitVM(pArg);
|
---|
1662 | return rcExit;
|
---|
1663 | }
|
---|
1664 | return RTEXITCODE_FAILURE;
|
---|
1665 | }
|
---|
1666 |
|
---|
1667 | #endif /* !VBOX_ONLY_DOCS */
|
---|
1668 |
|
---|