VirtualBox

source: vbox/trunk/src/VBox/Main/GuestImpl.cpp@ 28098

Last change on this file since 28098 was 28051, checked in by vboxsync, 15 years ago

Another attempt to initialize the balloon correct during startup

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.7 KB
Line 
1/* $Id: GuestImpl.cpp 28051 2010-04-07 14:09:14Z vboxsync $ */
2
3/** @file
4 *
5 * VirtualBox COM class implementation
6 */
7
8/*
9 * Copyright (C) 2006-2008 Sun Microsystems, Inc.
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 *
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
20 * Clara, CA 95054 USA or visit http://www.sun.com if you need
21 * additional information or have any questions.
22 */
23
24#include "GuestImpl.h"
25
26#include "Global.h"
27#include "ConsoleImpl.h"
28#include "VMMDev.h"
29
30#include "AutoCaller.h"
31#include "Logging.h"
32
33#include <VBox/VMMDev.h>
34#ifdef VBOX_WITH_GUEST_CONTROL
35# include <VBox/HostServices/GuestControlSvc.h>
36# include <VBox/com/array.h>
37#endif
38#include <iprt/cpp/utils.h>
39#include <iprt/getopt.h>
40#include <VBox/pgm.h>
41
42// defines
43/////////////////////////////////////////////////////////////////////////////
44
45// constructor / destructor
46/////////////////////////////////////////////////////////////////////////////
47
48DEFINE_EMPTY_CTOR_DTOR (Guest)
49
50HRESULT Guest::FinalConstruct()
51{
52 return S_OK;
53}
54
55void Guest::FinalRelease()
56{
57 uninit ();
58}
59
60// public methods only for internal purposes
61/////////////////////////////////////////////////////////////////////////////
62
63/**
64 * Initializes the guest object.
65 */
66HRESULT Guest::init (Console *aParent)
67{
68 LogFlowThisFunc(("aParent=%p\n", aParent));
69
70 ComAssertRet(aParent, E_INVALIDARG);
71
72 /* Enclose the state transition NotReady->InInit->Ready */
73 AutoInitSpan autoInitSpan(this);
74 AssertReturn(autoInitSpan.isOk(), E_FAIL);
75
76 unconst(mParent) = aParent;
77
78 /* mData.mAdditionsActive is FALSE */
79
80 /* Confirm a successful initialization when it's the case */
81 autoInitSpan.setSucceeded();
82
83 ULONG aMemoryBalloonSize;
84 HRESULT ret = mParent->machine()->COMGETTER(MemoryBalloonSize)(&aMemoryBalloonSize);
85 if (ret == S_OK)
86 mMemoryBalloonSize = aMemoryBalloonSize;
87 else
88 mMemoryBalloonSize = 0; /* Default is no ballooning */
89
90 mStatUpdateInterval = 0; /* Default is not to report guest statistics at all */
91
92 /* Clear statistics. */
93 for (unsigned i = 0 ; i < GUESTSTATTYPE_MAX; i++)
94 mCurrentGuestStat[i] = 0;
95
96 return S_OK;
97}
98
99/**
100 * Uninitializes the instance and sets the ready flag to FALSE.
101 * Called either from FinalRelease() or by the parent when it gets destroyed.
102 */
103void Guest::uninit()
104{
105 LogFlowThisFunc(("\n"));
106
107 /* Enclose the state transition Ready->InUninit->NotReady */
108 AutoUninitSpan autoUninitSpan(this);
109 if (autoUninitSpan.uninitDone())
110 return;
111
112 unconst(mParent) = NULL;
113}
114
115// IGuest properties
116/////////////////////////////////////////////////////////////////////////////
117
118STDMETHODIMP Guest::COMGETTER(OSTypeId) (BSTR *aOSTypeId)
119{
120 CheckComArgOutPointerValid(aOSTypeId);
121
122 AutoCaller autoCaller(this);
123 if (FAILED(autoCaller.rc())) return autoCaller.rc();
124
125 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
126
127 // redirect the call to IMachine if no additions are installed
128 if (mData.mAdditionsVersion.isEmpty())
129 return mParent->machine()->COMGETTER(OSTypeId)(aOSTypeId);
130
131 mData.mOSTypeId.cloneTo(aOSTypeId);
132
133 return S_OK;
134}
135
136STDMETHODIMP Guest::COMGETTER(AdditionsActive) (BOOL *aAdditionsActive)
137{
138 CheckComArgOutPointerValid(aAdditionsActive);
139
140 AutoCaller autoCaller(this);
141 if (FAILED(autoCaller.rc())) return autoCaller.rc();
142
143 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
144
145 *aAdditionsActive = mData.mAdditionsActive;
146
147 return S_OK;
148}
149
150STDMETHODIMP Guest::COMGETTER(AdditionsVersion) (BSTR *aAdditionsVersion)
151{
152 CheckComArgOutPointerValid(aAdditionsVersion);
153
154 AutoCaller autoCaller(this);
155 if (FAILED(autoCaller.rc())) return autoCaller.rc();
156
157 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
158
159 mData.mAdditionsVersion.cloneTo(aAdditionsVersion);
160
161 return S_OK;
162}
163
164STDMETHODIMP Guest::COMGETTER(SupportsSeamless) (BOOL *aSupportsSeamless)
165{
166 CheckComArgOutPointerValid(aSupportsSeamless);
167
168 AutoCaller autoCaller(this);
169 if (FAILED(autoCaller.rc())) return autoCaller.rc();
170
171 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
172
173 *aSupportsSeamless = mData.mSupportsSeamless;
174
175 return S_OK;
176}
177
178STDMETHODIMP Guest::COMGETTER(SupportsGraphics) (BOOL *aSupportsGraphics)
179{
180 CheckComArgOutPointerValid(aSupportsGraphics);
181
182 AutoCaller autoCaller(this);
183 if (FAILED(autoCaller.rc())) return autoCaller.rc();
184
185 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
186
187 *aSupportsGraphics = mData.mSupportsGraphics;
188
189 return S_OK;
190}
191
192STDMETHODIMP Guest::COMGETTER(MemoryBalloonSize) (ULONG *aMemoryBalloonSize)
193{
194 CheckComArgOutPointerValid(aMemoryBalloonSize);
195
196 AutoCaller autoCaller(this);
197 if (FAILED(autoCaller.rc())) return autoCaller.rc();
198
199 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
200
201 *aMemoryBalloonSize = mMemoryBalloonSize;
202
203 return S_OK;
204}
205
206STDMETHODIMP Guest::COMSETTER(MemoryBalloonSize) (ULONG aMemoryBalloonSize)
207{
208 AutoCaller autoCaller(this);
209 if (FAILED(autoCaller.rc())) return autoCaller.rc();
210
211 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
212
213 HRESULT ret = mParent->machine()->COMSETTER(MemoryBalloonSize)(aMemoryBalloonSize);
214 if (ret == S_OK)
215 {
216 mMemoryBalloonSize = aMemoryBalloonSize;
217 /* forward the information to the VMM device */
218 VMMDev *vmmDev = mParent->getVMMDev();
219 if (vmmDev)
220 vmmDev->getVMMDevPort()->pfnSetMemoryBalloon(vmmDev->getVMMDevPort(), aMemoryBalloonSize);
221 }
222
223 return ret;
224}
225
226STDMETHODIMP Guest::COMGETTER(StatisticsUpdateInterval)(ULONG *aUpdateInterval)
227{
228 CheckComArgOutPointerValid(aUpdateInterval);
229
230 AutoCaller autoCaller(this);
231 if (FAILED(autoCaller.rc())) return autoCaller.rc();
232
233 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
234
235 *aUpdateInterval = mStatUpdateInterval;
236 return S_OK;
237}
238
239STDMETHODIMP Guest::COMSETTER(StatisticsUpdateInterval)(ULONG aUpdateInterval)
240{
241 AutoCaller autoCaller(this);
242 if (FAILED(autoCaller.rc())) return autoCaller.rc();
243
244 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
245
246 mStatUpdateInterval = aUpdateInterval;
247 /* forward the information to the VMM device */
248 VMMDev *vmmDev = mParent->getVMMDev();
249 if (vmmDev)
250 vmmDev->getVMMDevPort()->pfnSetStatisticsInterval(vmmDev->getVMMDevPort(), aUpdateInterval);
251
252 return S_OK;
253}
254
255STDMETHODIMP Guest::InternalGetStatistics(ULONG *aCpuUser, ULONG *aCpuKernel, ULONG *aCpuIdle,
256 ULONG *aMemTotal, ULONG *aMemFree, ULONG *aMemBalloon,
257 ULONG *aMemCache, ULONG *aPageTotal,
258 ULONG *aMemAllocTotal, ULONG *aMemFreeTotal, ULONG *aMemBalloonTotal)
259{
260 CheckComArgOutPointerValid(aCpuUser);
261 CheckComArgOutPointerValid(aCpuKernel);
262 CheckComArgOutPointerValid(aCpuIdle);
263 CheckComArgOutPointerValid(aMemTotal);
264 CheckComArgOutPointerValid(aMemFree);
265 CheckComArgOutPointerValid(aMemBalloon);
266 CheckComArgOutPointerValid(aMemCache);
267 CheckComArgOutPointerValid(aPageTotal);
268
269 AutoCaller autoCaller(this);
270 if (FAILED(autoCaller.rc())) return autoCaller.rc();
271
272 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
273
274 *aCpuUser = mCurrentGuestStat[GUESTSTATTYPE_CPUUSER] * (_4K/_1K); /* page (4K) -> 1 KB units */
275 *aCpuKernel = mCurrentGuestStat[GUESTSTATTYPE_CPUKERNEL] * (_4K/_1K);
276 *aCpuIdle = mCurrentGuestStat[GUESTSTATTYPE_CPUIDLE] * (_4K/_1K);
277 *aMemTotal = mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL] * (_4K/_1K);
278 *aMemFree = mCurrentGuestStat[GUESTSTATTYPE_MEMFREE] * (_4K/_1K);
279 *aMemBalloon = mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K);
280 *aMemCache = mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE] * (_4K/_1K);
281 *aPageTotal = mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL] * (_4K/_1K);
282
283 Console::SafeVMPtr pVM (mParent);
284 if (pVM.isOk())
285 {
286 uint64_t uFreeTotal, uAllocTotal, uBalloonedTotal;
287 *aMemFreeTotal = 0;
288 int rc = PGMR3QueryVMMMemoryStats(pVM.raw(), &uAllocTotal, &uFreeTotal, &uBalloonedTotal);
289 AssertRC(rc);
290 if (rc == VINF_SUCCESS)
291 {
292 *aMemAllocTotal = (ULONG)(uAllocTotal / _1K); /* bytes -> KB */
293 *aMemFreeTotal = (ULONG)(uFreeTotal / _1K);
294 *aMemBalloonTotal = (ULONG)(uBalloonedTotal / _1K);
295 }
296 }
297 else
298 *aMemFreeTotal = 0;
299
300 return S_OK;
301}
302
303HRESULT Guest::SetStatistic(ULONG aCpuId, GUESTSTATTYPE enmType, ULONG aVal)
304{
305 AutoCaller autoCaller(this);
306 if (FAILED(autoCaller.rc())) return autoCaller.rc();
307
308 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
309
310 if (enmType >= GUESTSTATTYPE_MAX)
311 return E_INVALIDARG;
312
313 mCurrentGuestStat[enmType] = aVal;
314 return S_OK;
315}
316
317STDMETHODIMP Guest::SetCredentials(IN_BSTR aUserName, IN_BSTR aPassword,
318 IN_BSTR aDomain, BOOL aAllowInteractiveLogon)
319{
320 AutoCaller autoCaller(this);
321 if (FAILED(autoCaller.rc())) return autoCaller.rc();
322
323 /* forward the information to the VMM device */
324 VMMDev *vmmDev = mParent->getVMMDev();
325 if (vmmDev)
326 {
327 uint32_t u32Flags = VMMDEV_SETCREDENTIALS_GUESTLOGON;
328 if (!aAllowInteractiveLogon)
329 u32Flags = VMMDEV_SETCREDENTIALS_NOLOCALLOGON;
330
331 vmmDev->getVMMDevPort()->pfnSetCredentials(vmmDev->getVMMDevPort(),
332 Utf8Str(aUserName).raw(), Utf8Str(aPassword).raw(),
333 Utf8Str(aDomain).raw(), u32Flags);
334 return S_OK;
335 }
336
337 return setError(VBOX_E_VM_ERROR,
338 tr("VMM device is not available (is the VM running?)"));
339}
340
341#ifdef VBOX_WITH_GUEST_CONTROL
342/**
343 * Creates the argument list as an array used for executing a program.
344 *
345 * @returns VBox status code.
346 *
347 * @todo
348 *
349 * @todo Respect spaces when quoting for arguments, e.g. "c:\\program files\\".
350 * @todo Handle empty ("") argguments.
351 */
352int Guest::prepareExecuteArgs(const char *pszArgs, void **ppvList, uint32_t *pcbList, uint32_t *pcArgs)
353{
354 char **ppaArg;
355 int iArgs;
356 int rc = RTGetOptArgvFromString(&ppaArg, &iArgs, pszArgs, NULL);
357 if (RT_SUCCESS(rc))
358 {
359 char *pszTemp = NULL;
360 *pcbList = 0;
361 for (int i=0; i<iArgs; i++)
362 {
363 if (i > 0) /* Insert space as delimiter. */
364 rc = RTStrAAppendN(&pszTemp, " ", 1);
365
366 if (RT_FAILURE(rc))
367 break;
368 else
369 {
370 rc = RTStrAAppendN(&pszTemp, ppaArg[i], strlen(ppaArg[i]));
371 if (RT_FAILURE(rc))
372 break;
373 }
374 }
375 RTGetOptArgvFree(ppaArg);
376 if (RT_SUCCESS(rc))
377 {
378 *ppvList = pszTemp;
379 *pcArgs = iArgs;
380 if (pszTemp)
381 *pcbList = strlen(pszTemp) + 1; /* Include zero termination. */
382 }
383 else
384 RTStrFree(pszTemp);
385 }
386 return rc;
387}
388
389/**
390 * Appends environment variables to the environment block. Each var=value pair is separated
391 * by NULL (\0) sequence. The whole block will be stored in one blob and disassembled on the
392 * guest side later to fit into the HGCM param structure.
393 *
394 * @returns VBox status code.
395 *
396 * @todo
397 *
398 */
399int Guest::prepareExecuteEnv(const char *pszEnv, void **ppvList, uint32_t *pcbList, uint32_t *pcEnv)
400{
401 int rc = VINF_SUCCESS;
402 uint32_t cbLen = strlen(pszEnv);
403 if (*ppvList)
404 {
405 uint32_t cbNewLen = *pcbList + cbLen + 1; /* Include zero termination. */
406 char *pvTmp = (char*)RTMemRealloc(*ppvList, cbNewLen);
407 if (NULL == pvTmp)
408 {
409 rc = VERR_NO_MEMORY;
410 }
411 else
412 {
413 memcpy(pvTmp + *pcbList, pszEnv, cbLen);
414 pvTmp[cbNewLen - 1] = '\0'; /* Add zero termination. */
415 *ppvList = (void**)pvTmp;
416 }
417 }
418 else
419 {
420 char *pcTmp;
421 if (RTStrAPrintf(&pcTmp, "%s", pszEnv) > 0)
422 {
423 *ppvList = (void**)pcTmp;
424 /* Reset counters. */
425 *pcEnv = 0;
426 *pcbList = 0;
427 }
428 }
429 if (RT_SUCCESS(rc))
430 {
431 *pcbList += cbLen + 1; /* Include zero termination. */
432 *pcEnv += 1; /* Increase env pairs count. */
433 }
434 return rc;
435}
436#endif /* VBOX_WITH_GUEST_CONTROL */
437
438STDMETHODIMP Guest::ExecuteProcess(IN_BSTR aCommand, ULONG aFlags,
439 ComSafeArrayIn(IN_BSTR, aArguments), ComSafeArrayIn(IN_BSTR, aEnvironment),
440 IN_BSTR aStdIn, IN_BSTR aStdOut, IN_BSTR aStdErr,
441 IN_BSTR aUserName, IN_BSTR aPassword,
442 ULONG aTimeoutMS, ULONG *aPID, IProgress **aProgress)
443{
444#ifndef VBOX_WITH_GUEST_CONTROL
445 ReturnComNotImplemented();
446#else /* VBOX_WITH_GUEST_CONTROL */
447 using namespace guestControl;
448
449 CheckComArgStrNotEmptyOrNull(aCommand);
450 CheckComArgOutPointerValid(aProgress);
451 CheckComArgOutPointerValid(aPID);
452 /* Flags are not supported at the moment. */
453 if (aFlags != 0)
454 return E_INVALIDARG;
455
456 HRESULT rc = S_OK;
457
458 try
459 {
460 AutoCaller autoCaller(this);
461 if (FAILED(autoCaller.rc())) return autoCaller.rc();
462
463 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
464
465 /* Just be on the safe side when calling another process. */
466 alock.leave();
467
468 HRESULT rc = E_UNEXPECTED;
469 using namespace guestControl;
470
471 int vrc = VINF_SUCCESS;
472 Utf8Str Utf8Command(aCommand);
473
474 /* Prepare arguments. */
475 com::SafeArray<IN_BSTR> args(ComSafeArrayInArg(aArguments));
476 uint32_t uNumArgs = args.size();
477 char **papszArgv = NULL;
478 if(uNumArgs > 0)
479 {
480 papszArgv = (char**)RTMemAlloc(sizeof(char*) * (uNumArgs + 1));
481 AssertPtr(papszArgv);
482 for (unsigned i = 0; RT_SUCCESS(vrc) && i < uNumArgs; i++)
483 vrc = RTStrAPrintf(&papszArgv[i], "%s", Utf8Str(args[i]).raw());
484 papszArgv[uNumArgs] = NULL;
485 }
486
487 if (RT_SUCCESS(vrc))
488 {
489 char *pszArgs = NULL;
490 if (uNumArgs > 0)
491 vrc = RTGetOptArgvToString(&pszArgs, papszArgv, 0);
492 if (RT_SUCCESS(vrc))
493 {
494 uint32_t cbArgs = pszArgs ? strlen(pszArgs) + 1 : 0; /* Include terminating zero. */
495
496 /* Prepare environment. */
497 com::SafeArray<IN_BSTR> env(ComSafeArrayInArg(aEnvironment));
498
499 void *pvEnv = NULL;
500 uint32_t uNumEnv = 0;
501 uint32_t cbEnv = 0;
502
503 for (unsigned i = 0; i < env.size(); i++)
504 {
505 vrc = prepareExecuteEnv(Utf8Str(env[i]).raw(), &pvEnv, &cbEnv, &uNumEnv);
506 if (RT_FAILURE(vrc))
507 break;
508 }
509
510 if (RT_SUCCESS(vrc))
511 {
512 Utf8Str Utf8StdIn(aStdIn);
513 Utf8Str Utf8StdOut(aStdOut);
514 Utf8Str Utf8StdErr(aStdErr);
515 Utf8Str Utf8UserName(aUserName);
516 Utf8Str Utf8Password(aPassword);
517
518 VBOXHGCMSVCPARM paParms[14];
519 int i = 0;
520 paParms[i++].setPointer((void*)Utf8Command.raw(), (uint32_t)strlen(Utf8Command.raw()) + 1);
521 paParms[i++].setUInt32(aFlags);
522 paParms[i++].setUInt32(uNumArgs);
523 paParms[i++].setPointer((void*)pszArgs, cbArgs);
524 paParms[i++].setUInt32(uNumEnv);
525 paParms[i++].setUInt32(cbEnv);
526 paParms[i++].setPointer((void*)pvEnv, cbEnv);
527 paParms[i++].setPointer((void*)Utf8StdIn.raw(), (uint32_t)strlen(Utf8StdIn.raw()) + 1);
528 paParms[i++].setPointer((void*)Utf8StdOut.raw(), (uint32_t)strlen(Utf8StdOut.raw()) + 1);
529 paParms[i++].setPointer((void*)Utf8StdErr.raw(), (uint32_t)strlen(Utf8StdErr.raw()) + 1);
530 paParms[i++].setPointer((void*)Utf8UserName.raw(), (uint32_t)strlen(Utf8UserName.raw()) + 1);
531 paParms[i++].setPointer((void*)Utf8Password.raw(), (uint32_t)strlen(Utf8Password.raw()) + 1);
532 paParms[i++].setUInt32(aTimeoutMS);
533
534 /* Forward the information to the VMM device. */
535 AssertPtr(mParent);
536 VMMDev *vmmDev = mParent->getVMMDev();
537 if (vmmDev)
538 {
539 LogFlow(("Guest::ExecuteProgram: numParms=%d\n", i));
540 vrc = vmmDev->hgcmHostCall("VBoxGuestControlSvc", HOST_EXEC_CMD,
541 i, paParms);
542 /** @todo Get the PID. */
543 }
544 RTMemFree(pvEnv);
545 }
546 RTStrFree(pszArgs);
547 }
548 if (RT_SUCCESS(vrc))
549 rc = S_OK;
550 else
551 rc = setError(E_UNEXPECTED,
552 tr("The service call failed with the error %Rrc"),
553 vrc);
554
555 for (unsigned i = 0; i < uNumArgs; i++)
556 RTMemFree(papszArgv[i]);
557 RTMemFree(papszArgv);
558 }
559 }
560 catch (std::bad_alloc &)
561 {
562 rc = E_OUTOFMEMORY;
563 };
564
565 return rc;
566#endif /* VBOX_WITH_GUEST_CONTROL */
567}
568
569// public methods only for internal purposes
570/////////////////////////////////////////////////////////////////////////////
571
572void Guest::setAdditionsVersion(Bstr aVersion, VBOXOSTYPE aOsType)
573{
574 AutoCaller autoCaller(this);
575 AssertComRCReturnVoid (autoCaller.rc());
576
577 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
578
579 mData.mAdditionsVersion = aVersion;
580 mData.mAdditionsActive = !aVersion.isEmpty();
581 /* Older Additions didn't have this finer grained capability bit,
582 * so enable it by default. Newer Additions will disable it immediately
583 * if relevant. */
584 mData.mSupportsGraphics = mData.mAdditionsActive;
585
586 mData.mOSTypeId = Global::OSTypeId (aOsType);
587}
588
589void Guest::setSupportsSeamless (BOOL aSupportsSeamless)
590{
591 AutoCaller autoCaller(this);
592 AssertComRCReturnVoid (autoCaller.rc());
593
594 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
595
596 mData.mSupportsSeamless = aSupportsSeamless;
597}
598
599void Guest::setSupportsGraphics (BOOL aSupportsGraphics)
600{
601 AutoCaller autoCaller(this);
602 AssertComRCReturnVoid (autoCaller.rc());
603
604 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
605
606 mData.mSupportsGraphics = aSupportsGraphics;
607}
608/* vi: set tabstop=4 shiftwidth=4 expandtab: */
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