VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/UnattendedScript.cpp@ 93948

Last change on this file since 93948 was 93920, checked in by vboxsync, 3 years ago

scm fix.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 42.1 KB
Line 
1/* $Id: UnattendedScript.cpp 93920 2022-02-24 14:00:53Z vboxsync $ */
2/** @file
3 * Classes for reading/parsing/saving scripts for unattended installation.
4 */
5
6/*
7 * Copyright (C) 2006-2022 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#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
23#include "LoggingNew.h"
24#include "VirtualBoxBase.h"
25#include "AutoCaller.h"
26#include <VBox/com/ErrorInfo.h>
27
28#include "UnattendedScript.h"
29#include "UnattendedImpl.h"
30
31#include <iprt/err.h>
32
33#include <iprt/ctype.h>
34#include <iprt/file.h>
35#include <iprt/vfs.h>
36#include <iprt/getopt.h>
37#include <iprt/path.h>
38
39using namespace std;
40
41#ifdef VBOX_WITH_UNATTENDED
42
43
44/*********************************************************************************************************************************
45* Defined Constants And Macros *
46*********************************************************************************************************************************/
47static const char g_szPrefix[] = "@@VBOX_";
48static const char g_szPrefixInsert[] = "@@VBOX_INSERT";
49static const char g_szPrefixInsertXxx[] = "@@VBOX_INSERT_";
50static const char g_szPrefixInsertExpr[] = "@@VBOX_INSERT[";
51static const char g_szPrefixCond[] = "@@VBOX_COND";
52static const char g_szPrefixCondXxx[] = "@@VBOX_COND_";
53static const char g_szPrefixCondExpr[] = "@@VBOX_COND[";
54static const char g_szPrefixCondElse[] = "@@VBOX_COND_ELSE@@";
55static const char g_szPrefixCondEnd[] = "@@VBOX_COND_END@@";
56static const char g_szPrefixSplitter[] = "@@VBOX_SPLITTER";
57
58
59/*********************************************************************************************************************************
60* Static Functions *
61*********************************************************************************************************************************/
62
63/**
64 * Searches comparison operators'<', '<=', '>', '>=', or '=' in string.
65 *
66 * @returns true if detected, false if not.
67 * @param pszComparisonOperator Comparison operators string array. Assumed to be of size 3,
68 * @param pachPlaceholder The string array in which we search the comparison operators,
69 * @param startPos The position with in the pachPlaceholder from where the seach starts. Required to be smaller then
70 * sizeof(pachPlaceholder) - 3
71 */
72
73static bool detectComparisonOperator(char *pszComparisonOperator, const char *pachPlaceholder, size_t &startPos)
74{
75 memset(pszComparisonOperator, '\0', 3);
76 /* Search for '>', '<', '>=', '<=', '='. */
77 if (pachPlaceholder[startPos] == '<')
78 {
79 pszComparisonOperator[0] = '<';
80 ++startPos;
81 if (pachPlaceholder[startPos] == '=')
82 {
83 pszComparisonOperator[1] = '=';
84 ++startPos;
85 }
86 return true;
87 }
88 if (pachPlaceholder[startPos] == '>')
89 {
90 pszComparisonOperator[0] = '>';
91 ++startPos;
92 if (pachPlaceholder[startPos] == '=')
93 {
94 pszComparisonOperator[1] = '=';
95 ++startPos;
96 }
97 return true;
98 }
99 else if (pachPlaceholder[startPos] == '=')
100 {
101 pszComparisonOperator[0] = '=';
102 ++startPos;
103 return true;
104 }
105 return false;
106}
107
108
109/*********************************************************************************************************************************
110* UnattendedScriptTemplate Implementation *
111*********************************************************************************************************************************/
112
113UnattendedScriptTemplate::UnattendedScriptTemplate(Unattended *pUnattended, const char *pszDefaultTemplateFilename,
114 const char *pszDefaultFilename)
115 : BaseTextScript(pUnattended, pszDefaultTemplateFilename, pszDefaultFilename), mpUnattended(pUnattended)
116{
117}
118
119HRESULT UnattendedScriptTemplate::saveToString(Utf8Str &rStrDst)
120{
121 RTEXPREVAL hEvaluator = NIL_RTEXPREVAL;
122 int vrc = RTExprEvalCreate(&hEvaluator, 0, "unattended", this, UnattendedScriptTemplate::queryVariableForExpr);
123 AssertRCReturn(vrc, mpSetError->setErrorVrc(vrc));
124
125 struct
126 {
127 bool fSavedOutputting;
128 } aConds[8];
129 unsigned cConds = 0;
130 bool fOutputting = true;
131 HRESULT hrc = E_FAIL;
132 size_t offTemplate = 0;
133 size_t cchTemplate = mStrScriptFullContent.length();
134 rStrDst.setNull();
135 for (;;)
136 {
137 /*
138 * Find the next placeholder and add any text before it to the output.
139 */
140 size_t offPlaceholder = mStrScriptFullContent.find(g_szPrefix, offTemplate);
141 size_t cchToCopy = offPlaceholder != RTCString::npos ? offPlaceholder - offTemplate : cchTemplate - offTemplate;
142 if (cchToCopy > 0)
143 {
144 if (fOutputting)
145 {
146 try
147 {
148 rStrDst.append(mStrScriptFullContent, offTemplate , cchToCopy);
149 }
150 catch (std::bad_alloc &)
151 {
152 hrc = E_OUTOFMEMORY;
153 break;
154 }
155 }
156 offTemplate += cchToCopy;
157 }
158
159 /*
160 * Process placeholder.
161 */
162 if (offPlaceholder != RTCString::npos)
163 {
164 /*
165 * First we must find the end of the placeholder string.
166 */
167 size_t const cchMaxPlaceholder = RT_MIN(cchTemplate - offPlaceholder, _1K);
168 const char *pszPlaceholder = mStrScriptFullContent.c_str() + offPlaceholder;
169 size_t cchPlaceholder = sizeof(g_szPrefix) - 1;
170 char ch;
171 while ( cchPlaceholder < cchMaxPlaceholder
172 && (ch = pszPlaceholder[cchPlaceholder]) != '\0'
173 && (RT_C_IS_PRINT(ch) || RT_C_IS_SPACE(ch))
174 && ch != '@')
175 cchPlaceholder++;
176
177 if ( offPlaceholder + cchPlaceholder < cchTemplate
178 && pszPlaceholder[cchPlaceholder] == '@')
179 {
180 cchPlaceholder++;
181 if ( offPlaceholder + cchPlaceholder < cchTemplate
182 && pszPlaceholder[cchPlaceholder] == '@')
183 cchPlaceholder++;
184 }
185
186 if ( pszPlaceholder[cchPlaceholder - 1] != '@'
187 || pszPlaceholder[cchPlaceholder - 2] != '@'
188 || ( strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixInsert)) != 0
189 && strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCond)) != 0
190 && strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixSplitter)) != 0 ) )
191 {
192 hrc = mpSetError->setError(E_FAIL, tr("Malformed or too long template placeholder '%.*s'"),
193 cchPlaceholder, pszPlaceholder);
194 break;
195 }
196
197 offTemplate += cchPlaceholder;
198
199 /*
200 * @@VBOX_INSERT_XXX@@:
201 */
202 if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixInsertXxx)) == 0)
203 {
204 /*
205 * Get the placeholder value and add it to the output.
206 */
207 RTCString strValue;
208 hrc = getReplacement(pszPlaceholder, cchPlaceholder, fOutputting, strValue);
209 if (SUCCEEDED(hrc))
210 {
211 if (fOutputting)
212 {
213 try
214 {
215 rStrDst.append(strValue);
216 }
217 catch (std::bad_alloc &)
218 {
219 hrc = E_OUTOFMEMORY;
220 break;
221 }
222 }
223 }
224 else
225 break;
226 }
227 /*
228 * @@VBOX_INSERT[expr]@@:
229 * @@VBOX_INSERT[expr]SH@@:
230 * @@VBOX_INSERT[expr]ELEMENT@@:
231 * @@VBOX_INSERT[expr]ATTRIB_DQ@@:
232 */
233 else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixInsertExpr)) == 0)
234 {
235 /*
236 * Get the placeholder value and add it to the output.
237 */
238 char *pszValue = NULL;
239 hrc = getReplacementForExpr(hEvaluator, pszPlaceholder, cchPlaceholder, fOutputting, &pszValue);
240 if (SUCCEEDED(hrc))
241 {
242 if (fOutputting && pszValue)
243 {
244 try
245 {
246 rStrDst.append(pszValue);
247 }
248 catch (std::bad_alloc &)
249 {
250 hrc = E_OUTOFMEMORY;
251 break;
252 }
253 }
254 RTStrFree(pszValue);
255 }
256 else
257 break;
258 }
259 /*
260 * @@VBOX_COND_END@@: Pop one item of the conditional stack.
261 */
262 else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondEnd)) == 0)
263 {
264 if (cConds > 0)
265 {
266 cConds--;
267 fOutputting = aConds[cConds].fSavedOutputting;
268 }
269 else
270 {
271 hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
272 tr("%s without @@VBOX_COND_XXX@@ at offset %zu (%#zx)"),
273 g_szPrefixCondEnd, offPlaceholder, offPlaceholder);
274 break;
275 }
276 }
277 /*
278 * @@VBOX_COND_ELSE@@: Flip the output setting of the current condition.
279 */
280 else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondElse)) == 0)
281 {
282 if (cConds > 0)
283 fOutputting = !fOutputting;
284 else
285 {
286 hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
287 tr("%s without @@VBOX_COND_XXX@@ at offset %zu (%#zx)"),
288 g_szPrefixCondElse, offPlaceholder, offPlaceholder);
289 break;
290 }
291 }
292 /*
293 * @@VBOX_COND_XXX@@: Push the previous outputting state and combine it with the
294 * one from the condition.
295 */
296 else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondXxx)) == 0)
297 {
298 if (cConds + 1 < RT_ELEMENTS(aConds))
299 {
300 aConds[cConds].fSavedOutputting = fOutputting;
301 bool fNewOutputting = fOutputting;
302 hrc = getConditional(pszPlaceholder, cchPlaceholder, &fNewOutputting);
303 if (SUCCEEDED(hrc))
304 fOutputting = fOutputting && fNewOutputting;
305 else
306 break;
307 cConds++;
308 }
309 else
310 {
311 hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
312 tr("Too deep conditional nesting at offset %zu (%#zx)"),
313 offPlaceholder, offPlaceholder);
314 break;
315 }
316 }
317 /*
318 * @@VBOX_COND[expr]@@: Push the previous outputting state and combine it with the
319 * one from the condition.
320 */
321 else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondExpr)) == 0)
322 {
323 if (cConds + 1 < RT_ELEMENTS(aConds))
324 {
325 aConds[cConds].fSavedOutputting = fOutputting;
326 bool fNewOutputting = fOutputting;
327 hrc = resolveConditionalExpr(hEvaluator, pszPlaceholder, cchPlaceholder, &fNewOutputting);
328 if (SUCCEEDED(hrc))
329 fOutputting = fOutputting && fNewOutputting;
330 else
331 break;
332 cConds++;
333 }
334 else
335 {
336 hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
337 tr("Too deep conditional nesting at offset %zu (%#zx)"),
338 offPlaceholder, offPlaceholder);
339 break;
340 }
341 }
342 /*
343 * @@VBOX_SPLITTER_START/END[filename]@@: Ignored in this pass.
344 */
345 else
346 {
347 Assert(strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixSplitter)) == 0);
348 if (fOutputting)
349 {
350 try
351 {
352 rStrDst.append(pszPlaceholder, cchPlaceholder);
353 }
354 catch (std::bad_alloc &)
355 {
356 hrc = E_OUTOFMEMORY;
357 break;
358 }
359 }
360 }
361 }
362
363 /*
364 * Done?
365 */
366 if (offTemplate >= cchTemplate)
367 {
368 if (cConds == 0)
369 {
370 RTExprEvalRelease(hEvaluator);
371 return S_OK;
372 }
373 if (cConds == 1)
374 hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Missing @@VBOX_COND_END@@"));
375 else
376 hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Missing %u @@VBOX_COND_END@@"), cConds);
377 break;
378 }
379 }
380
381 /* failed */
382 rStrDst.setNull();
383 RTExprEvalRelease(hEvaluator);
384 return hrc;
385}
386
387HRESULT UnattendedScriptTemplate::getReplacement(const char *pachPlaceholder, size_t cchPlaceholder,
388 bool fOutputting, RTCString &rValue)
389{
390 /*
391 * Check for an escaping suffix. Drop the '@@'.
392 */
393 kEvalEscaping_T enmEscaping;
394#define PLACEHOLDER_ENDS_WITH(a_szSuffix) \
395 ( cchPlaceholder > sizeof(a_szSuffix) - 1U \
396 && memcmp(&pachPlaceholder[cchPlaceholder - sizeof(a_szSuffix) + 1U], a_szSuffix, sizeof(a_szSuffix) - 1U) == 0)
397 if (PLACEHOLDER_ENDS_WITH("_SH@@"))
398 {
399 cchPlaceholder -= 3 + 2;
400 enmEscaping = kValueEscaping_Bourne;
401 }
402 else if (PLACEHOLDER_ENDS_WITH("_ELEMENT@@"))
403 {
404 cchPlaceholder -= 8 + 2;
405 enmEscaping = kValueEscaping_XML_Element;
406 }
407 else if (PLACEHOLDER_ENDS_WITH("_ATTRIB_DQ@@"))
408 {
409 cchPlaceholder -= 10 + 2;
410 enmEscaping = kValueEscaping_XML_Attribute_Double_Quotes;
411 }
412 else
413 {
414 Assert(PLACEHOLDER_ENDS_WITH("@@"));
415 cchPlaceholder -= 2;
416 enmEscaping = kValueEscaping_None;
417 }
418#undef PLACEHOLDER_ENDS_WITH
419
420 /*
421 * Resolve and escape the value.
422 */
423 HRESULT hrc;
424 try
425 {
426 Utf8Str strTmp;
427 const char *pszReadOnlyValue = NULL;
428 int vrc = queryVariable(pachPlaceholder + sizeof(g_szPrefixInsertXxx) - 1,
429 cchPlaceholder - sizeof(g_szPrefixInsertXxx) + 1,
430 strTmp, fOutputting ? &pszReadOnlyValue : NULL);
431 if (RT_SUCCESS(vrc))
432 {
433 if (fOutputting)
434 {
435 Assert(pszReadOnlyValue != NULL);
436 switch (enmEscaping)
437 {
438 case kValueEscaping_None:
439 rValue = pszReadOnlyValue;
440 return S_OK;
441
442 case kValueEscaping_Bourne:
443 case kValueEscaping_XML_Element:
444 case kValueEscaping_XML_Attribute_Double_Quotes:
445 {
446 switch (enmEscaping)
447 {
448 case kValueEscaping_Bourne:
449 {
450 const char * const papszArgs[2] = { pszReadOnlyValue, NULL };
451 char *pszEscaped = NULL;
452 vrc = RTGetOptArgvToString(&pszEscaped, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
453 if (RT_SUCCESS(vrc))
454 {
455 try
456 {
457 rValue = pszEscaped;
458 RTStrFree(pszEscaped);
459 return S_OK;
460 }
461 catch (std::bad_alloc &)
462 {
463 hrc = E_OUTOFMEMORY;
464 }
465 RTStrFree(pszEscaped);
466 }
467 else
468 hrc = mpSetError->setErrorVrc(vrc);
469 break;
470 }
471
472 case kValueEscaping_XML_Element:
473 rValue.printf("%RMes", pszReadOnlyValue);
474 return S_OK;
475
476 case kValueEscaping_XML_Attribute_Double_Quotes:
477 {
478 RTCString strTmp2;
479 strTmp2.printf("%RMas", pszReadOnlyValue);
480 rValue = RTCString(strTmp2, 1, strTmp2.length() - 2);
481 return S_OK;
482 }
483
484 default:
485 hrc = E_FAIL;
486 break;
487 }
488 break;
489 }
490
491 default:
492 AssertFailedStmt(hrc = E_FAIL);
493 break;
494 }
495 }
496 else
497 hrc = S_OK;
498 }
499 else
500 hrc = E_FAIL;
501 }
502 catch (std::bad_alloc &)
503 {
504 hrc = E_OUTOFMEMORY;
505 }
506 rValue.setNull();
507 return hrc;
508}
509
510HRESULT UnattendedScriptTemplate::getReplacementForExpr(RTEXPREVAL hEvaluator, const char *pachPlaceholder, size_t cchPlaceholder,
511 bool fOutputting, char **ppszValue) RT_NOEXCEPT
512{
513 /*
514 * Process the tail of the placeholder to figure out the escaping rules.
515 *
516 * @@VBOX_INSERT[expr]@@:
517 * @@VBOX_INSERT[expr]SH@@:
518 * @@VBOX_INSERT[expr]ELEMENT@@:
519 * @@VBOX_INSERT[expr]ATTRIB_DQ@@:
520 */
521 kEvalEscaping_T enmEscaping;
522#define PLACEHOLDER_ENDS_WITH(a_szSuffix) \
523 ( cchPlaceholder > sizeof(a_szSuffix) - 1U \
524 && memcmp(&pachPlaceholder[cchPlaceholder - sizeof(a_szSuffix) + 1U], a_szSuffix, sizeof(a_szSuffix) - 1U) == 0)
525 if (PLACEHOLDER_ENDS_WITH("]SH@@"))
526 {
527 cchPlaceholder -= sizeof("]SH@@") - 1;
528 enmEscaping = kValueEscaping_Bourne;
529 }
530 else if (PLACEHOLDER_ENDS_WITH("]ELEMENT@@"))
531 {
532 cchPlaceholder -= sizeof("]ELEMENT@@") - 1;
533 enmEscaping = kValueEscaping_XML_Element;
534 }
535 else if (PLACEHOLDER_ENDS_WITH("]ATTRIB_DQ@@"))
536 {
537 cchPlaceholder -= sizeof("]ATTRIB_DQ@@") - 1;
538 enmEscaping = kValueEscaping_XML_Attribute_Double_Quotes;
539 }
540 else if (PLACEHOLDER_ENDS_WITH("]@@"))
541 {
542 cchPlaceholder -= sizeof("]@@") - 1;
543 enmEscaping = kValueEscaping_None;
544 }
545 else
546 return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_INSERT[expr]@@: Missing ']' (%.*s)"),
547 cchPlaceholder, pachPlaceholder);
548#undef PLACEHOLDER_ENDS_WITH
549
550 /* The placeholder prefix length. The expression is from cchPrefix to cchPlaceholder. */
551 size_t const cchPrefix = sizeof(g_szPrefixInsertExpr) - 1;
552 Assert(pachPlaceholder[cchPrefix - 1] == '[');
553
554 /*
555 * Evaluate the expression. We do this regardless of fOutput for now.
556 */
557 RTERRINFOSTATIC ErrInfo;
558 char *pszValue = NULL;
559 int vrc = RTExprEvalToString(hEvaluator, &pachPlaceholder[cchPrefix], cchPlaceholder - cchPrefix, &pszValue,
560 RTErrInfoInitStatic(&ErrInfo));
561 LogFlowFunc(("RTExprEvalToString(%.*s) -> %Rrc pszValue=%s\n",
562 cchPlaceholder - cchPrefix, &pachPlaceholder[cchPrefix], vrc, pszValue));
563 if (RT_SUCCESS(vrc))
564 {
565 if (fOutputting)
566 {
567 switch (enmEscaping)
568 {
569 case kValueEscaping_None:
570 *ppszValue = pszValue;
571 pszValue = NULL;
572 break;
573
574 case kValueEscaping_Bourne:
575 {
576 const char * const papszArgs[2] = { pszValue, NULL };
577 vrc = RTGetOptArgvToString(ppszValue, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
578 break;
579 }
580
581 case kValueEscaping_XML_Element:
582 vrc = RTStrAPrintf(ppszValue, "%RMes", pszValue);
583 break;
584
585 case kValueEscaping_XML_Attribute_Double_Quotes:
586 vrc = RTStrAPrintf(ppszValue, "%RMas", pszValue);
587 if (RT_SUCCESS(vrc))
588 {
589 /* drop the quotes */
590 char *pszRet = *ppszValue;
591 size_t const cchRet = strlen(pszRet) - 2;
592 memmove(pszRet, &pszRet[1], cchRet);
593 pszRet[cchRet] = '\0';
594 }
595 break;
596
597 default:
598 AssertFailedStmt(vrc = VERR_IPE_NOT_REACHED_DEFAULT_CASE);
599 break;
600 }
601 RTStrFree(pszValue);
602 if (RT_FAILURE(vrc))
603 return mpSetError->setErrorVrc(vrc);
604 }
605 else
606 {
607 *ppszValue = NULL;
608 RTStrFree(pszValue);
609 }
610 }
611 else
612 return mpSetError->setErrorBoth(E_FAIL, vrc, tr("Expression evaluation error for '%.*s': %#RTeic"),
613 cchPlaceholder, pachPlaceholder, &ErrInfo.Core);
614 return S_OK;
615}
616
617HRESULT UnattendedScriptTemplate::resolveConditionalExpr(RTEXPREVAL hEvaluator, const char *pachPlaceholder,
618 size_t cchPlaceholder, bool *pfOutputting) RT_NOEXCEPT
619{
620 /*
621 * Check the placeholder tail: @@VBOX_COND[expr]@@
622 */
623 static const char s_szTail[] = "]@@";
624 if (memcmp(&pachPlaceholder[cchPlaceholder - sizeof(s_szTail) + 1], RT_STR_TUPLE(s_szTail)) != 0)
625 return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_COND[expr]@@: Missing ']' (%.*s)"),
626 cchPlaceholder, pachPlaceholder);
627 Assert(pachPlaceholder[sizeof(g_szPrefixCondExpr) - 2 ] == '[');
628
629 /*
630 * Evaluate the expression.
631 */
632 RTERRINFOSTATIC ErrInfo;
633 const char * const pchExpr = &pachPlaceholder[sizeof(g_szPrefixCondExpr) - 1];
634 size_t const cchExpr = cchPlaceholder - sizeof(g_szPrefixCondExpr) + 1 - sizeof(s_szTail) + 1;
635 int vrc = RTExprEvalToBool(hEvaluator, pchExpr, cchExpr, pfOutputting, RTErrInfoInitStatic(&ErrInfo));
636 LogFlowFunc(("RTExprEvalToBool(%.*s) -> %Rrc *pfOutputting=%s\n", cchExpr, pchExpr, vrc, *pfOutputting));
637 if (RT_SUCCESS(vrc))
638 return S_OK;
639 return mpSetError->setErrorBoth(E_FAIL, vrc, tr("Expression evaluation error for '%.*s': %#RTeic"),
640 cchPlaceholder, pachPlaceholder, &ErrInfo.Core);
641}
642
643/*static */ DECLCALLBACK(int)
644UnattendedScriptTemplate::queryVariableForExpr(const char *pchName, size_t cchName, void *pvUser, char **ppszValue) RT_NOEXCEPT
645{
646 UnattendedScriptTemplate *pThis = (UnattendedScriptTemplate *)pvUser;
647 int vrc;
648 try
649 {
650 const char *pszReadOnlyValue = NULL;
651 Utf8Str strTmp;
652 vrc = pThis->queryVariable(pchName, cchName, strTmp, ppszValue ? &pszReadOnlyValue : NULL);
653 if (ppszValue)
654 {
655 if (RT_SUCCESS(vrc))
656 vrc = RTStrDupEx(ppszValue, pszReadOnlyValue);
657 else
658 *ppszValue = NULL;
659 }
660 }
661 catch (std::bad_alloc &)
662 {
663 vrc = VERR_NO_MEMORY;
664 *ppszValue = NULL;
665 }
666 return vrc;
667}
668
669int UnattendedScriptTemplate::queryVariable(const char *pchName, size_t cchName, Utf8Str &rstrTmp, const char **ppszValue)
670{
671#define IS_MATCH(a_szMatch) \
672 (cchName == sizeof(a_szMatch) - 1U && memcmp(pchName, a_szMatch, sizeof(a_szMatch) - 1U) == 0)
673
674 const char *pszValue;
675
676 /*
677 * Variables
678 */
679 if (IS_MATCH("USER_LOGIN"))
680 pszValue = mpUnattended->i_getUser().c_str();
681 else if (IS_MATCH("USER_PASSWORD"))
682 pszValue = mpUnattended->i_getPassword().c_str();
683 else if (IS_MATCH("ROOT_PASSWORD"))
684 pszValue = mpUnattended->i_getPassword().c_str();
685 else if (IS_MATCH("USER_FULL_NAME"))
686 pszValue = mpUnattended->i_getFullUserName().c_str();
687 else if (IS_MATCH("PRODUCT_KEY"))
688 pszValue = mpUnattended->i_getProductKey().c_str();
689 else if (IS_MATCH("POST_INSTALL_COMMAND"))
690 pszValue = mpUnattended->i_getPostInstallCommand().c_str();
691 else if (IS_MATCH("AUXILIARY_INSTALL_DIR"))
692 pszValue = mpUnattended->i_getAuxiliaryInstallDir().c_str();
693 else if (IS_MATCH("IMAGE_INDEX"))
694 pszValue = rstrTmp.printf("%u", mpUnattended->i_getImageIndex()).c_str();
695 else if (IS_MATCH("OS_ARCH"))
696 pszValue = mpUnattended->i_isGuestOs64Bit() ? "amd64" : "x86";
697 else if (IS_MATCH("OS_ARCH2"))
698 pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "x86";
699 else if (IS_MATCH("OS_ARCH3"))
700 pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "i386";
701 else if (IS_MATCH("OS_ARCH4"))
702 pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "i486";
703 else if (IS_MATCH("OS_ARCH6"))
704 pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "i686";
705 else if (IS_MATCH("GUEST_OS_VERSION"))
706 pszValue = mpUnattended->i_getDetectedOSVersion().c_str();
707 else if (IS_MATCH("GUEST_OS_MAJOR_VERSION"))
708 {
709 Utf8Str const &rstrOsVer = mpUnattended->i_getDetectedOSVersion();
710 size_t offDot = rstrOsVer.find('.');
711 if (offDot > 0 && offDot != Utf8Str::npos)
712 pszValue = rstrTmp.assign(rstrOsVer, 0, offDot).c_str(); /* caller catches std::bad_alloc */
713 else if (!ppszValue)
714 return VERR_NOT_FOUND;
715 else
716 {
717 mpSetError->setErrorBoth(E_FAIL, VERR_NO_DATA, tr("Unknown guest OS major version '%s'"), rstrOsVer.c_str());
718 return VERR_NO_DATA;
719 }
720 }
721 else if (IS_MATCH("TIME_ZONE_UX"))
722 pszValue = mpUnattended->i_getTimeZoneInfo()
723 ? mpUnattended->i_getTimeZoneInfo()->pszUnixName : mpUnattended->i_getTimeZone().c_str();
724 else if (IS_MATCH("TIME_ZONE_WIN_NAME"))
725 {
726 PCRTTIMEZONEINFO pInfo = mpUnattended->i_getTimeZoneInfo();
727 if (pInfo)
728 pszValue = pInfo->pszWindowsName ? pInfo->pszWindowsName : "GMT";
729 else
730 pszValue = mpUnattended->i_getTimeZone().c_str();
731 }
732 else if (IS_MATCH("TIME_ZONE_WIN_INDEX"))
733 {
734 PCRTTIMEZONEINFO pInfo = mpUnattended->i_getTimeZoneInfo();
735 if (pInfo)
736 pszValue = rstrTmp.printf("%u", pInfo->idxWindows ? pInfo->idxWindows : 85 /*GMT*/).c_str();
737 else
738 pszValue = mpUnattended->i_getTimeZone().c_str();
739 }
740 else if (IS_MATCH("LOCALE"))
741 pszValue = mpUnattended->i_getLocale().c_str();
742 else if (IS_MATCH("DASH_LOCALE"))
743 {
744 Assert(mpUnattended->i_getLocale()[2] == '_');
745 pszValue = rstrTmp.assign(mpUnattended->i_getLocale()).replace(2, 1, "-").c_str();
746 }
747 else if (IS_MATCH("LANGUAGE"))
748 pszValue = mpUnattended->i_getLanguage().c_str();
749 else if (IS_MATCH("COUNTRY"))
750 pszValue = mpUnattended->i_getCountry().c_str();
751 else if (IS_MATCH("HOSTNAME_FQDN"))
752 pszValue = mpUnattended->i_getHostname().c_str();
753 else if (IS_MATCH("HOSTNAME_WITHOUT_DOMAIN"))
754 pszValue = rstrTmp.assign(mpUnattended->i_getHostname(), 0, mpUnattended->i_getHostname().find(".")).c_str();
755 else if (IS_MATCH("HOSTNAME_WITHOUT_DOMAIN_MAX_15"))
756 pszValue = rstrTmp.assign(mpUnattended->i_getHostname(), 0, RT_MIN(mpUnattended->i_getHostname().find("."), 15)).c_str();
757 else if (IS_MATCH("HOSTNAME_DOMAIN"))
758 pszValue = rstrTmp.assign(mpUnattended->i_getHostname(), mpUnattended->i_getHostname().find(".") + 1).c_str();
759 else if (IS_MATCH("PROXY"))
760 pszValue = mpUnattended->i_getProxy().c_str();
761 /*
762 * Indicator variables.
763 */
764 else if (IS_MATCH("IS_INSTALLING_ADDITIONS"))
765 pszValue = mpUnattended->i_getInstallGuestAdditions() ? "1" : "0";
766 else if (IS_MATCH("IS_USER_LOGIN_ADMINISTRATOR"))
767 pszValue = mpUnattended->i_getUser().compare("Administrator", RTCString::CaseInsensitive) == 0 ? "1" : "0";
768 else if (IS_MATCH("IS_INSTALLING_TEST_EXEC_SERVICE"))
769 pszValue = mpUnattended->i_getInstallTestExecService() ? "1" : "0";
770 else if (IS_MATCH("HAS_POST_INSTALL_COMMAND"))
771 pszValue = mpUnattended->i_getPostInstallCommand().isNotEmpty() ? "1" : "0";
772 else if (IS_MATCH("HAS_PRODUCT_KEY"))
773 pszValue = mpUnattended->i_getProductKey().isNotEmpty() ? "1" : "0";
774 else if (IS_MATCH("IS_MINIMAL_INSTALLATION"))
775 pszValue = mpUnattended->i_isMinimalInstallation() ? "1" : "0";
776 else if (IS_MATCH("IS_FIRMWARE_UEFI"))
777 pszValue = mpUnattended->i_isFirmwareEFI() ? "1" : "0";
778 else if (IS_MATCH("IS_RTC_USING_UTC"))
779 pszValue = mpUnattended->i_isRtcUsingUtc() ? "1" : "0";
780 else if (IS_MATCH("HAS_PROXY"))
781 pszValue = mpUnattended->i_getProxy().isNotEmpty() ? "1" : "0";
782 /*
783 * Unknown variable.
784 */
785 else if (!ppszValue)
786 return VERR_NOT_FOUND;
787 else
788 {
789 mpSetError->setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("Unknown variable '%.*s'"), cchName, pchName);
790 return VERR_NO_DATA;
791 }
792 if (ppszValue)
793 *ppszValue = pszValue;
794 return VINF_SUCCESS;
795}
796
797HRESULT UnattendedScriptTemplate::getConditional(const char *pachPlaceholder, size_t cchPlaceholder, bool *pfOutputting)
798{
799#define IS_PLACEHOLDER_MATCH(a_szMatch) \
800 ( cchPlaceholder == sizeof("@@VBOX_COND_" a_szMatch "@@") - 1U \
801 && memcmp(pachPlaceholder, "@@VBOX_COND_" a_szMatch "@@", sizeof("@@VBOX_COND_" a_szMatch "@@") - 1U) == 0)
802#define IS_PLACEHOLDER_PARTIALLY_MATCH(a_szMatch) \
803 (memcmp(pachPlaceholder, "@@VBOX_COND_" a_szMatch, sizeof("@@VBOX_COND_" a_szMatch) - 1U) == 0)
804
805 /* Install Guest Additions: */
806 if (IS_PLACEHOLDER_MATCH("IS_INSTALLING_ADDITIONS"))
807 *pfOutputting = mpUnattended->i_getInstallGuestAdditions();
808 else if (IS_PLACEHOLDER_MATCH("IS_NOT_INSTALLING_ADDITIONS"))
809 *pfOutputting = !mpUnattended->i_getInstallGuestAdditions();
810 /* User == Administrator: */
811 else if (IS_PLACEHOLDER_MATCH("IS_USER_LOGIN_ADMINISTRATOR"))
812 *pfOutputting = mpUnattended->i_getUser().compare("Administrator", RTCString::CaseInsensitive) == 0;
813 else if (IS_PLACEHOLDER_MATCH("IS_USER_LOGIN_NOT_ADMINISTRATOR"))
814 *pfOutputting = mpUnattended->i_getUser().compare("Administrator", RTCString::CaseInsensitive) != 0;
815 /* Install TXS: */
816 else if (IS_PLACEHOLDER_MATCH("IS_INSTALLING_TEST_EXEC_SERVICE"))
817 *pfOutputting = mpUnattended->i_getInstallTestExecService();
818 else if (IS_PLACEHOLDER_MATCH("IS_NOT_INSTALLING_TEST_EXEC_SERVICE"))
819 *pfOutputting = !mpUnattended->i_getInstallTestExecService();
820 /* Post install command: */
821 else if (IS_PLACEHOLDER_MATCH("HAS_POST_INSTALL_COMMAND"))
822 *pfOutputting = mpUnattended->i_getPostInstallCommand().isNotEmpty();
823 else if (IS_PLACEHOLDER_MATCH("HAS_NO_POST_INSTALL_COMMAND"))
824 *pfOutputting = mpUnattended->i_getPostInstallCommand().isEmpty();
825 /* Product key: */
826 else if (IS_PLACEHOLDER_MATCH("HAS_PRODUCT_KEY"))
827 *pfOutputting = mpUnattended->i_getProductKey().isNotEmpty();
828 else if (IS_PLACEHOLDER_MATCH("HAS_NO_PRODUCT_KEY"))
829 *pfOutputting = mpUnattended->i_getProductKey().isEmpty();
830 /* Minimal installation: */
831 else if (IS_PLACEHOLDER_MATCH("IS_MINIMAL_INSTALLATION"))
832 *pfOutputting = mpUnattended->i_isMinimalInstallation();
833 else if (IS_PLACEHOLDER_MATCH("IS_NOT_MINIMAL_INSTALLATION"))
834 *pfOutputting = !mpUnattended->i_isMinimalInstallation();
835 /* Is firmware UEFI: */
836 else if (IS_PLACEHOLDER_MATCH("IS_FIRMWARE_UEFI"))
837 *pfOutputting = mpUnattended->i_isFirmwareEFI();
838 else if (IS_PLACEHOLDER_MATCH("IS_NOT_FIRMWARE_UEFI"))
839 *pfOutputting = !mpUnattended->i_isFirmwareEFI();
840 /* Is RTC using UTC (i.e. set to UTC time on startup): */
841 else if (IS_PLACEHOLDER_MATCH("IS_RTC_USING_UTC"))
842 *pfOutputting = mpUnattended->i_isRtcUsingUtc();
843 else if (IS_PLACEHOLDER_MATCH("IS_NOT_RTC_USING_UTC"))
844 *pfOutputting = !mpUnattended->i_isRtcUsingUtc();
845 else if (IS_PLACEHOLDER_MATCH("HAS_PROXY"))
846 *pfOutputting = mpUnattended->i_getProxy().isNotEmpty();
847 else if (IS_PLACEHOLDER_PARTIALLY_MATCH("GUEST_VERSION["))
848 {
849 /* Check the passed string against format @@VBOX_COND_GUEST_VERSION[>8.04.0]@@. Allowed comparison operators are:
850 * '<', '<=', '>', '>=', or '=' in string. No spaces are allowed in anywhere of the expr. */
851 static const char s_szTail[] = "]@@";
852 size_t endLength = sizeof(s_szTail) - 1;
853 if (memcmp(&pachPlaceholder[cchPlaceholder - endLength], RT_STR_TUPLE(s_szTail)) != 0)
854 {
855 *pfOutputting = false;
856 return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_COND_GUEST_VERSION[expr]@@: Missing ']' (%.*s)"),
857 cchPlaceholder, pachPlaceholder);
858 }
859 size_t startPos = sizeof("@@VBOX_COND_GUEST_VERSION[") - 1;
860 size_t endPos = cchPlaceholder - endLength;
861 if (startPos >= endPos)
862 {
863 *pfOutputting = false;
864 return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_COND_GUEST_VERSION[expr]@@: Missing expr (%.*s)"),
865 cchPlaceholder, pachPlaceholder);
866 }
867 /* Parse for the comparison operator. Assuming the expression starts with one of the allowed operators. */
868 char pszComparisonOperator[3];
869 if (!detectComparisonOperator(pszComparisonOperator, pachPlaceholder, startPos))
870 {
871 *pfOutputting = false;
872 return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_COND_GUEST_VERSION[expr]@@: Only space, '>', '>=', '<', <=', and '=' are allowed at the start. (%.*s)"),
873 cchPlaceholder, pachPlaceholder);
874 }
875 if (startPos >= endPos)
876 {
877 *pfOutputting = false;
878 return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_COND_GUEST_VERSION[expr]@@: No version string found. (%.*s)"),
879 cchPlaceholder, pachPlaceholder);
880 }
881 /* Check if the version string includes any character other than '.' and digits. */
882 for (size_t i = startPos; i < endPos; ++i)
883 {
884 if ( (pachPlaceholder[i] < '0' || pachPlaceholder[i] > '9') && pachPlaceholder[i] != '.')
885 {
886 *pfOutputting = false;
887 return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_COND_GUEST_VERSION[expr]@@: Version string must be consist of only digits and '.', and no spaces. (%.*s)"),
888 cchPlaceholder, pachPlaceholder);
889 }
890 }
891
892 RTCString strRequiredOSVersion(pachPlaceholder, startPos, endPos - startPos);
893 RTCString strDetectedOSVersion = mpUnattended->i_getDetectedOSVersion();
894 int res = RTStrVersionCompare(strDetectedOSVersion.c_str(), strRequiredOSVersion.c_str());
895
896 if ( res == 0
897 && ( pszComparisonOperator[0] == '='
898 || memcmp(pszComparisonOperator, ">=", 2) == 0
899 || memcmp(pszComparisonOperator, "<=", 2) == 0))
900 *pfOutputting = true;
901 else if (res < 0 && pszComparisonOperator[0] == '<')
902 *pfOutputting = true;
903 else if (res > 0 && pszComparisonOperator[0] == '>')
904 *pfOutputting = true;
905 else
906 *pfOutputting = false;
907 }
908 else
909 return mpSetError->setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("Unknown conditional placeholder '%.*s'"),
910 cchPlaceholder, pachPlaceholder);
911 return S_OK;
912#undef IS_PLACEHOLDER_MATCH
913}
914
915#endif /* VBOX_WITH_UNATTENDED */
916#if 0 /* Keeping this a reference */
917
918
919/*********************************************************************************************************************************
920* UnattendedSUSEXMLScript Implementation *
921*********************************************************************************************************************************/
922
923HRESULT UnattendedSUSEXMLScript::parse()
924{
925 HRESULT hrc = UnattendedXMLScript::parse();
926 if (SUCCEEDED(hrc))
927 {
928 /*
929 * Check that we've got the right root element type.
930 */
931 const xml::ElementNode *pelmRoot = mDoc.getRootElement();
932 if ( pelmRoot
933 && strcmp(pelmRoot->getName(), "profile") == 0)
934 {
935 /*
936 * Work thought the sections.
937 */
938 try
939 {
940 LoopThruSections(pelmRoot);
941 hrc = S_OK;
942 }
943 catch (std::bad_alloc &)
944 {
945 hrc = E_OUTOFMEMORY;
946 }
947 }
948 else if (pelmRoot)
949 hrc = mpSetError->setError(E_FAIL, tr("XML document root element is '%s' instead of 'profile'"),
950 pelmRoot->getName());
951 else
952 hrc = mpSetError->setError(E_FAIL, tr("Missing XML root element"));
953 }
954 return hrc;
955}
956
957HRESULT UnattendedSUSEXMLScript::setFieldInElement(xml::ElementNode *pElement, const DataId enmDataId, const Utf8Str &rStrValue)
958{
959 /*
960 * Don't set empty values.
961 */
962 if (rStrValue.isEmpty())
963 {
964 Utf8Str strProbableValue;
965 try
966 {
967 strProbableValue = createProbableValue(enmDataId, pElement);
968 }
969 catch (std::bad_alloc &)
970 {
971 return E_OUTOFMEMORY;
972 }
973 return UnattendedXMLScript::setFieldInElement(pElement, enmDataId, strProbableValue);
974 }
975 return UnattendedXMLScript::setFieldInElement(pElement, enmDataId, rStrValue);
976}
977
978HRESULT UnattendedSUSEXMLScript::LoopThruSections(const xml::ElementNode *pelmRoot)
979{
980 xml::NodesLoop loopChildren(*pelmRoot);
981 const xml::ElementNode *pelmOuterLoop;
982 while ((pelmOuterLoop = loopChildren.forAllNodes()) != NULL)
983 {
984 const char *pcszElemName = pelmOuterLoop->getName();
985 if (!strcmp(pcszElemName, "users"))
986 {
987 xml::NodesLoop loopUsers(*pelmOuterLoop);
988 const xml::ElementNode *pelmUser;
989 while ((pelmUser = loopUsers.forAllNodes()) != NULL)
990 {
991 HRESULT hrc = HandleUserAccountsSection(pelmUser);
992 if (FAILED(hrc))
993 return hrc;
994 }
995 }
996 }
997 return S_OK;
998}
999
1000HRESULT UnattendedSUSEXMLScript::HandleUserAccountsSection(const xml::ElementNode *pelmSection)
1001{
1002 xml::NodesLoop loopUser(*pelmSection);
1003
1004 const xml::ElementNode *pelmCur;
1005 while ((pelmCur = loopUser.forAllNodes()) != NULL)
1006 {
1007 const char *pszValue = pelmCur->getValue();
1008#ifdef LOG_ENABLED
1009 if (!RTStrCmp(pelmCur->getName(), "uid"))
1010 LogRelFunc(("UnattendedSUSEXMLScript::HandleUserAccountsSection profile/users/%s/%s = %s\n",
1011 pelmSection->getName(), pelmCur->getName(), pszValue));
1012#endif
1013
1014 if (!RTStrCmp(pszValue, "$homedir"))
1015 mNodesForCorrectionMap.insert(make_pair(USERHOMEDIR_ID, pelmCur));
1016
1017 if (!RTStrCmp(pszValue, "$user"))
1018 mNodesForCorrectionMap.insert(make_pair(USERNAME_ID, pelmCur));
1019
1020 if (!RTStrCmp(pszValue, "$password"))
1021 mNodesForCorrectionMap.insert(make_pair(USERPASSWORD_ID, pelmCur));
1022 }
1023 return S_OK;
1024}
1025
1026Utf8Str UnattendedSUSEXMLScript::createProbableValue(const DataId enmDataId, const xml::ElementNode *pCurElem)
1027{
1028 const xml::ElementNode *pElem = pCurElem;
1029
1030 switch (enmDataId)
1031 {
1032 case USERHOMEDIR_ID:
1033// if ((pElem = pElem->findChildElement("home")))
1034// {
1035 return createProbableUserHomeDir(pElem);
1036// }
1037 break;
1038 default:
1039 break;
1040 }
1041
1042 return Utf8Str::Empty;
1043}
1044
1045Utf8Str UnattendedSUSEXMLScript::createProbableUserHomeDir(const xml::ElementNode *pCurElem)
1046{
1047 Utf8Str strCalcValue;
1048 const xml::ElementNode *pElem = pCurElem->findNextSibilingElement("username");
1049 if (pElem)
1050 {
1051 const char *pszValue = pElem->getValue();
1052 strCalcValue = "/home/";
1053 strCalcValue.append(pszValue);
1054 }
1055
1056 return strCalcValue;
1057}
1058#endif /* just for reference */
Note: See TracBrowser for help on using the repository browser.

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