VirtualBox

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

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

Main/Unattended: bugref:9781. Addressing a todo item in UnattendedScript.cpp

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