VirtualBox

source: vbox/trunk/src/VBox/Additions/os2/VBoxOs2AdditionsInstall.cpp

Last change on this file was 98103, checked in by vboxsync, 16 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 68.7 KB
Line 
1/** $Id: VBoxOs2AdditionsInstall.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * VBoxOs2AdditionsInstall - Barebone OS/2 Guest Additions Installer.
4 */
5
6/*
7 * Copyright (C) 2022-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define INCL_BASE
33#include <os2.h>
34#include <VBox/version.h>
35
36#include <string.h>
37#include <iprt/ctype.h>
38#include <iprt/path.h>
39
40
41/*********************************************************************************************************************************
42* Defined Constants And Macros *
43*********************************************************************************************************************************/
44#define SKIP_CONFIG_SYS 0x01
45#define SKIP_STARTUP_CMD 0x02
46#define SKIP_SERVICE 0x04
47#define SKIP_SHARED_FOLDERS 0x08
48#define SKIP_GRAPHICS 0x10
49#define SKIP_MOUSE 0x20
50#define SKIP_LIBC_DLLS 0x40
51
52/** NIL HFILE value. */
53#define MY_NIL_HFILE (~(HFILE)0)
54
55#if defined(DOXYGEN_RUNNING)
56/** Enabled extra debug output in the matching functions. */
57# define DEBUG_MATCHING
58#endif
59
60
61/*********************************************************************************************************************************
62* Structures and Typedefs *
63*********************************************************************************************************************************/
64typedef struct FILEEDITOR
65{
66 size_t cbOrg;
67 char *pszOrg;
68 size_t cbNew;
69 size_t cbNewAlloc;
70 char *pszNew;
71 bool fAppendEof;
72 bool fOverflowed;
73 size_t cBogusChars;
74} FILEEDITOR;
75
76
77/*********************************************************************************************************************************
78* Global Variables *
79*********************************************************************************************************************************/
80/** Where the files to install (default: same dir as this program). */
81static CHAR g_szSrcPath[CCHMAXPATH];
82/** The length of g_szSrcPath, including a trailing slash. */
83static size_t g_cchSrcPath = 0;
84/** The boot drive path, i.e. where Config.kmk & Startup.cmd lives. */
85static CHAR g_szBootDrivePath[CCHMAXPATH] = "C:\\";
86/** The size of the bootdrive path, including a trailing slash. */
87static size_t g_cchBootDrivePath = sizeof("C:\\") - 1;
88/** Where to install the guest additions files. */
89static CHAR g_szDstPath[CCHMAXPATH] = "C:\\VBoxAdd\\";
90/** The length of g_szDstPath, including a trailing slash. */
91static size_t g_cchDstPath = sizeof("C:\\VBoxAdd\\") - 1;
92/** Mask of SKIP_XXX flags of components/tasks to skip. */
93static uint8_t g_fSkipMask = 0;
94/** Verbose or quiet. */
95static bool g_fVerbose = true;
96/** Whether this is a real run (true) or just a trial. */
97static bool g_fRealRun = false;
98
99/** The standard output handle. */
100static HFILE const g_hStdOut = (HFILE)1;
101/** The standard error handle. */
102static HFILE const g_hStdErr = (HFILE)2;
103
104
105/** File editor for Config.sys. */
106static FILEEDITOR g_ConfigSys;
107/** File editor for Startup.cmd. */
108static FILEEDITOR g_StartupCmd;
109
110
111/*********************************************************************************************************************************
112* Messaging. *
113*********************************************************************************************************************************/
114
115static void DoWriteNStr(HFILE hFile, const char *psz, size_t cch)
116{
117 ULONG cbIgnore;
118 while (DosWrite(hFile, (void *)psz, cch, &cbIgnore) == ERROR_INTERRUPT)
119 ;
120}
121
122
123static void DoWriteStr(HFILE hFile, const char *psz)
124{
125 DoWriteNStr(hFile, psz, strlen(psz));
126}
127
128
129/** Writes a variable number of strings to @a hFile, stopping at NULL. */
130static void WriteStrings(HFILE hFile, ...)
131{
132 va_list va;
133 va_start(va, hFile);
134 for (;;)
135 {
136 const char *psz = va_arg(va, const char *);
137 if (psz)
138 DoWriteStr(hFile, psz);
139 else
140 break;
141 }
142 va_end(va);
143}
144
145
146/** Writes a variable number of length/strings pairs to @a hFile, stopping at
147 * 0/NULL. */
148static void WriteNStrings(HFILE hFile, ...)
149{
150 va_list va;
151 va_start(va, hFile);
152 for (;;)
153 {
154 const char *psz = va_arg(va, const char *);
155 int cch = va_arg(va, int);
156 if (psz)
157 {
158 if (cch < 0)
159 DoWriteStr(hFile, psz);
160 else
161 DoWriteNStr(hFile, psz, cch);
162 }
163 else
164 break;
165 }
166 va_end(va);
167}
168
169
170static RTEXITCODE ErrorNStrings(const char *pszMsg, ssize_t cchMsg, ...)
171{
172 DoWriteNStr(g_hStdErr, RT_STR_TUPLE("VBoxOs2AdditionsInstall: error: "));
173 va_list va;
174 va_start(va, cchMsg);
175 do
176 {
177 if (cchMsg < 0)
178 DoWriteStr(g_hStdErr, pszMsg);
179 else
180 DoWriteNStr(g_hStdErr, pszMsg, cchMsg);
181 pszMsg = va_arg(va, const char *);
182 cchMsg = va_arg(va, int);
183 } while (pszMsg != NULL);
184 va_end(va);
185 DoWriteNStr(g_hStdErr, RT_STR_TUPLE("\r\n"));
186 return RTEXITCODE_FAILURE;
187}
188
189
190static char *MyNumToString(char *pszBuf, unsigned uNum)
191{
192 char *pszRet = pszBuf;
193
194 /* Convert to decimal and inverted digit order: */
195 char szTmp[32];
196 unsigned off = 0;
197 do
198 {
199 szTmp[off++] = uNum % 10 + '0';
200 uNum /= 10;
201 } while (uNum);
202
203 /* Copy it out to the destination buffer in the right order and add a terminator: */
204 while (off-- > 0)
205 *pszBuf++ = szTmp[off];
206 *pszBuf = '\0';
207
208 return pszRet;
209}
210
211
212static void DoWriteNumber(HFILE hFile, unsigned uNum)
213{
214 char szTmp[32];
215 MyNumToString(szTmp, uNum);
216 DoWriteStr(hFile, szTmp);
217}
218
219
220static RTEXITCODE ApiErrorN(APIRET rc, unsigned cMsgs, ...)
221{
222 DoWriteNStr(g_hStdErr, RT_STR_TUPLE("VBoxOs2AdditionsInstall: error: "));
223 va_list va;
224 va_start(va, cMsgs);
225 while (cMsgs-- > 0)
226 {
227 const char *pszMsg = va_arg(va, const char *);
228 DoWriteStr(g_hStdErr, pszMsg);
229 }
230 va_end(va);
231 DoWriteNStr(g_hStdErr, RT_STR_TUPLE(": "));
232 DoWriteNumber(g_hStdErr, rc);
233 DoWriteNStr(g_hStdErr, RT_STR_TUPLE("\r\n"));
234 return RTEXITCODE_FAILURE;
235}
236
237
238DECLINLINE(RTEXITCODE) ApiError(const char *pszMsg, APIRET rc)
239{
240 return ApiErrorN(rc, 1, pszMsg);
241}
242
243
244static RTEXITCODE SyntaxError(const char *pszMsg, const char *pszArg)
245{
246 DoWriteNStr(g_hStdErr, RT_STR_TUPLE("VBoxOs2AdditionsInstall: syntax error: "));
247 DoWriteNStr(g_hStdErr, pszMsg, strlen(pszMsg));
248 DoWriteNStr(g_hStdErr, RT_STR_TUPLE("\r\n"));
249 return RTEXITCODE_SYNTAX;
250}
251
252
253/*********************************************************************************************************************************
254* Editor. *
255*********************************************************************************************************************************/
256
257/**
258 * Reads a file into the editor.
259 */
260static RTEXITCODE EditorReadInFile(FILEEDITOR *pEditor, const char *pszFilename, size_t cbExtraEdit, bool fMustExist)
261{
262 FILESTATUS3 FileSts;
263
264 if (g_fVerbose)
265 WriteStrings(g_hStdOut, "info: Preparing \"", pszFilename, "\" modifications...\r\n", NULL);
266
267 /*
268 * Open the file.
269 */
270 HFILE hFile = MY_NIL_HFILE;
271 ULONG uAction = ~0U;
272 APIRET rc = DosOpen(pszFilename, &hFile, &uAction, 0, FILE_NORMAL,
273 OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW,
274 OPEN_ACCESS_READONLY | OPEN_SHARE_DENYWRITE | OPEN_FLAGS_SEQUENTIAL | OPEN_FLAGS_NOINHERIT,
275 NULL /*pEaOp2*/);
276 if (rc == ERROR_OPEN_FAILED)
277 rc = DosQueryPathInfo(pszFilename, FIL_STANDARD, &FileSts, sizeof(FileSts));
278 if (rc == ERROR_FILE_NOT_FOUND)
279 hFile = MY_NIL_HFILE;
280 else if (rc != NO_ERROR)
281 return ApiErrorN(rc, 3, "DosOpen(\"", pszFilename, "\",READONLY)");
282
283 /*
284 * Get it's size and check that it's sane.
285 */
286 if (hFile != MY_NIL_HFILE)
287 {
288 rc = DosQueryFileInfo(hFile, FIL_STANDARD, &FileSts, sizeof(FileSts));
289 if (rc != NO_ERROR)
290 return ApiErrorN(rc, 3, "DosQueryFileInfo(\"", pszFilename, "\",FIL_STANDARD,,)");
291
292 if (FileSts.cbFile > _2M)
293 return ApiErrorN(rc, FileSts.cbFile, "File \"", pszFilename, "\" is too large");
294 }
295 else
296 FileSts.cbFile = 0;
297
298 /*
299 * Allocate buffers.
300 */
301 PVOID pvAlloc = NULL;
302 size_t cbAlloc = FileSts.cbFile * 2 + cbExtraEdit + 16;
303 rc = DosAllocMem(&pvAlloc, cbAlloc, PAG_COMMIT | PAG_WRITE | PAG_READ);
304 if (rc != NO_ERROR)
305 return ApiError("DosAllocMem", rc);
306
307 memset(pvAlloc, 0, cbAlloc);
308 pEditor->cbOrg = FileSts.cbFile;
309 pEditor->pszOrg = (char *)pvAlloc;
310 pEditor->pszNew = (char *)pvAlloc + FileSts.cbFile + 1;
311 pEditor->cbNew = 0;
312 pEditor->cbNewAlloc = cbAlloc - FileSts.cbFile - 2;
313 pEditor->fAppendEof = false;
314 pEditor->fOverflowed = false;
315 pEditor->cBogusChars = 0;
316
317 /*
318 * Read in the file content.
319 */
320 if (hFile != MY_NIL_HFILE)
321 {
322 ULONG cbRead = 0;
323 rc = DosRead(hFile, pEditor->pszOrg, FileSts.cbFile, &cbRead);
324 if (rc != NO_ERROR)
325 return ApiErrorN(rc, 3, "DosRead(\"", pszFilename, "\")");
326 if (cbRead != FileSts.cbFile)
327 return ApiErrorN(cbRead < FileSts.cbFile ? ERROR_MORE_DATA : ERROR_BUFFER_OVERFLOW,
328 3, "DosRead(\"", pszFilename, "\")");
329 DosClose(hFile);
330
331 /*
332 * Check for EOF/SUB character.
333 */
334 char *pchEof = (char *)memchr(pEditor->pszOrg, 0x1a, FileSts.cbFile);
335 if (pchEof)
336 {
337 size_t offEof = pchEof - pEditor->pszOrg;
338 for (size_t off = offEof + 1; off < FileSts.cbFile; off++)
339 if (!RT_C_IS_SPACE(pEditor->pszOrg[off]))
340 return ErrorNStrings(RT_STR_TUPLE("Refusing to modify \""), pszFilename, -1,
341 RT_STR_TUPLE("\" because of EOF character followed by text!"));
342 pEditor->cbOrg = offEof;
343 pEditor->fAppendEof = true;
344 }
345 }
346
347 return RTEXITCODE_SUCCESS;
348}
349
350
351/**
352 * Writes out a modified file, backing up the original.
353 */
354static RTEXITCODE EditorWriteOutFile(FILEEDITOR *pEditor, const char *pszFilename)
355{
356 if (g_fVerbose)
357 WriteStrings(g_hStdOut, "info: Writing out \"", pszFilename, "\" modifications...\r\n", NULL);
358
359 /*
360 * Skip if no change was made.
361 */
362 if ( pEditor->cbNew == 0
363 || ( pEditor->cbNew == pEditor->cbOrg
364 && memcmp(pEditor->pszNew, pEditor->pszOrg, pEditor->cbNew) == 0))
365 {
366 WriteStrings(g_hStdOut, "info: No changes to \"", pszFilename, "\".\r\n", NULL);
367 return RTEXITCODE_SUCCESS;
368 }
369
370 /*
371 * Back up the original.
372 * ASSUMES that the input is CCHMAXPATH or less.
373 */
374 if (pEditor->cbOrg != 0)
375 {
376 CHAR szBackup[CCHMAXPATH + 16];
377 size_t const cchFilename = strlen(pszFilename);
378 memcpy(szBackup, pszFilename, cchFilename + 1);
379 char *pszExt = (char *)memrchr(szBackup, '.', cchFilename);
380 if (!pszExt || strchr(pszExt, '\\') || strchr(pszExt, '/'))
381 pszExt = &szBackup[cchFilename];
382 strcpy(pszExt, ".BAK");
383 for (unsigned short i = 0; ; i++)
384 {
385 HFILE hFile = MY_NIL_HFILE;
386 ULONG uAction = ~0U;
387 APIRET rc = DosOpen(szBackup, &hFile, &uAction, 0, FILE_NORMAL,
388 OPEN_ACTION_FAIL_IF_EXISTS | OPEN_ACTION_CREATE_IF_NEW,
389 OPEN_ACCESS_WRITEONLY | OPEN_SHARE_DENYWRITE | OPEN_FLAGS_SEQUENTIAL | OPEN_FLAGS_NOINHERIT,
390 NULL /*pEaOp2*/);
391 if (rc == NO_ERROR)
392 {
393 ULONG cbWritten = 0;
394 do
395 rc = DosWrite(hFile, pEditor->pszOrg, pEditor->cbOrg + (pEditor->fAppendEof ? 1 : 0), &cbWritten);
396 while (rc == ERROR_INTERRUPT);
397 DosClose(hFile);
398 if (rc != NO_ERROR)
399 return ApiErrorN(rc, 5, "Failed backing up \"", pszFilename, "\" as \"", szBackup, "\"");
400 break;
401 }
402
403 /* try next extension variation */
404 if (i >= 1000)
405 return ApiErrorN(rc, 5, "Failed backing up \"", pszFilename, "\" as \"", szBackup, "\"");
406 if (i >= 100)
407 pszExt[1] = '0' + (i / 100);
408 if (i >= 10)
409 pszExt[2] = '0' + (i / 100 % 10);
410 pszExt[3] = '0' + (i % 10);
411 }
412 }
413
414 /*
415 * Write out the new copy.
416 */
417 HFILE hFile = MY_NIL_HFILE;
418 ULONG uAction = ~0U;
419 APIRET rc = DosOpen(pszFilename, &hFile, &uAction, 0, FILE_NORMAL,
420 OPEN_ACTION_REPLACE_IF_EXISTS | OPEN_ACTION_CREATE_IF_NEW,
421 OPEN_ACCESS_WRITEONLY | OPEN_SHARE_DENYWRITE | OPEN_FLAGS_SEQUENTIAL | OPEN_FLAGS_NOINHERIT,
422 NULL /*pEaOp2*/);
423 if (rc != NO_ERROR)
424 return ApiErrorN(rc, 3, "Opening \"", pszFilename, "\" for writing");
425
426 ULONG cbToWrite = pEditor->cbNew;
427 if (pEditor->fAppendEof)
428 {
429 pEditor->pszNew[cbToWrite] = 0x1a; /* replacing terminator */
430 cbToWrite++;
431 }
432
433 ULONG cbWritten = 0;
434 do
435 rc = DosWrite(hFile, pEditor->pszNew, cbToWrite, &cbWritten);
436 while (rc == ERROR_INTERRUPT);
437 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
438 if (rc != NO_ERROR)
439 rcExit = ApiErrorN(rc, 3, "Failed writing \"", pszFilename, "\"");
440 else if (cbWritten != cbToWrite)
441 {
442 char szNum1[32], szNum2[32];
443 rcExit = ErrorNStrings(RT_STR_TUPLE("Failed writing \""), pszFilename, -1, RT_STR_TUPLE("\" - incomplete write: "),
444 MyNumToString(szNum1, cbWritten), -1, RT_STR_TUPLE(" written, requested "),
445 MyNumToString(szNum2, cbToWrite), NULL, 0);
446 }
447
448 rc = DosClose(hFile);
449 if (rc != NO_ERROR)
450 rcExit = ApiErrorN(rc, 3, "Failed closing \"", pszFilename, "\"");
451
452 pEditor->pszNew[pEditor->cbNew - 1] = '\0'; /* replacing EOF */
453
454 return rcExit;
455}
456
457
458/**
459 * Gets the next line.
460 *
461 * @returns The offset to pass to the next EditorGetLine call.
462 * @retval 0 if no further lines in the input file.
463 *
464 * @param pEditor Pointer to the editor.
465 * @param offSrc The current source offset. Initialize to zero before
466 * first calls and pass return value for subsequent calls
467 * @param ppchLine Where to return the pointer to the start of the line.
468 * @param pcchLine Where to return the length of the line (sans EOL).
469 */
470static size_t EditorGetLine(FILEEDITOR *pEditor, size_t offSrc, const char **ppchLine, size_t *pcchLine)
471{
472 if (offSrc < pEditor->cbOrg)
473 {
474 const char *pchLine = &pEditor->pszOrg[offSrc];
475 *ppchLine = pchLine;
476
477 size_t cchMax = pEditor->cbOrg - offSrc;
478 const char *pchCr = (const char *)memchr(pchLine, '\r', cchMax);
479 const char *pchNl = (const char *)memchr(pchLine, '\n', pchCr ? pchCr - pchLine : cchMax);
480 size_t cchLine;
481 size_t cchEol;
482 if (pchCr && !pchNl)
483 {
484 cchLine = pchCr - pchLine;
485 cchEol = 1 + (pchCr[1] == '\n');
486 }
487 else if (pchNl)
488 {
489 cchLine = pchNl - pchLine;
490 cchEol = 1;
491 }
492 else
493 {
494 cchLine = cchMax;
495 cchEol = 0;
496 }
497 *pcchLine = cchLine;
498 return offSrc + cchLine + cchEol;
499 }
500
501 *ppchLine = "";
502 *pcchLine = 0;
503 return 0;
504}
505
506
507/**
508 * Checks that a string doesn't contain any funny control characters.
509 *
510 * These bogus characters are counted and EditorCheckState should be called to
511 * check these after editing has completed.
512 */
513static void EditorCheckString(FILEEDITOR *pEditor, const char *pchString, size_t cchString, const char *pszCaller)
514{
515 for (size_t off = 0; off < cchString; off++)
516 {
517 if ( RT_C_IS_CNTRL(pchString[off])
518 && pchString[off] != '\t')
519 {
520 static char s_szHex[] = "0123456789abcdef";
521 char szDigits[3] = { s_szHex[pchString[off] >> 4], s_szHex[pchString[off] & 0xf], '\0' };
522 ErrorNStrings(pszCaller, -1, RT_STR_TUPLE(": Bogus control character in "),
523 pEditor == &g_ConfigSys ? "Config.sys: " : "Startup.cmd: ", -1, szDigits, 2, NULL, 0);
524 pEditor->cBogusChars++;
525 }
526 }
527}
528
529
530/**
531 * Adds a line to the output buffer.
532 *
533 * A CRLF is appended automatically.
534 *
535 * @returns true on success, false on overflow (error displayed and sets
536 * fOverflowed on the editor).
537 *
538 * @param pEditor Pointer to the editor.
539 * @param pchLine Pointer to the line string.
540 * @param cchLine The length of the line (sans newline).
541 */
542static bool EditorPutLine(FILEEDITOR *pEditor, const char *pchLine, size_t cchLine)
543{
544 EditorCheckString(pEditor, pchLine, cchLine, "EditorPutLine");
545
546 size_t offNew = pEditor->cbNew;
547 if (offNew + cchLine + 2 < pEditor->cbNewAlloc)
548 {
549 char *pszNew = pEditor->pszNew;
550 memcpy(&pszNew[offNew], pchLine, cchLine);
551 offNew += cchLine;
552 pszNew[offNew++] = '\r';
553 pszNew[offNew++] = '\n';
554 pszNew[offNew] = '\0';
555 pEditor->cbNew = offNew;
556 return true;
557 }
558 pEditor->fOverflowed = true;
559 return false;
560}
561
562
563/**
564 * Writes a string to the output buffer.
565 *
566 * @returns true on success, false on overflow (error displayed and sets
567 * fOverflowed on the editor).
568 *
569 * @param pEditor Pointer to the editor.
570 * @param pchString Pointer to the string.
571 * @param cchString The length of the string.
572 */
573static bool EditorPutStringN(FILEEDITOR *pEditor, const char *pchString, size_t cchString)
574{
575 EditorCheckString(pEditor, pchString, cchString, "EditorPutStringN");
576
577 size_t offNew = pEditor->cbNew;
578 if (offNew + cchString < pEditor->cbNewAlloc)
579 {
580 char *pszNew = pEditor->pszNew;
581 memcpy(&pszNew[offNew], pchString, cchString);
582 offNew += cchString;
583 pszNew[offNew] = '\0';
584 pEditor->cbNew = offNew;
585 return true;
586 }
587 pEditor->fOverflowed = true;
588 return false;
589}
590
591
592/**
593 * Checks the editor state and makes the editing was successful.
594 */
595static RTEXITCODE EditorCheckState(FILEEDITOR *pEditor, const char *pszFilename)
596{
597 if (pEditor->fOverflowed)
598 return ErrorNStrings(RT_STR_TUPLE("Editor overflowed while modifying \""), pszFilename, -1, RT_STR_TUPLE("\""));
599 if (pEditor->cBogusChars > 0)
600 return ErrorNStrings(RT_STR_TUPLE("Editing failed because \""), pszFilename, -1,
601 RT_STR_TUPLE("\" contains bogus control characters (see above)"));
602 return RTEXITCODE_SUCCESS;
603}
604
605
606/**
607 * Simplistic case-insensitive memory compare function.
608 */
609static int MyMemICmp(void const *pv1, void const *pv2, size_t cb)
610{
611 char const *pch1 = (const char *)pv1;
612 char const *pch2 = (const char *)pv2;
613 while (cb-- > 0)
614 {
615 char ch1 = *pch1++;
616 char ch2 = *pch2++;
617 if ( ch1 != ch2
618 && RT_C_TO_UPPER(ch1) != RT_C_TO_UPPER(ch2))
619 return (int)ch1 - (int)ch2;
620 }
621 return 0;
622}
623
624
625/**
626 * Matches a word deliminated by space of @a chAltSep.
627 *
628 * @returns true if matched, false if not.
629 * @param pchLine The line we're working on.
630 * @param poff The current line offset. Updated on match.
631 * @param cchLine The current line length.
632 * @param pszWord The word to match with.
633 * @param cchWord The length of the word to match.
634 * @param chAltSep Alternative word separator, optional.
635 */
636static bool MatchWord(const char *pchLine, size_t *poff, size_t cchLine, const char *pszWord, size_t cchWord, char chAltSep = ' ')
637{
638 size_t off = *poff;
639 pchLine += off;
640 cchLine -= off;
641 if (cchWord <= cchLine)
642 if (MyMemICmp(pchLine, pszWord, cchWord) == 0)
643 if ( cchWord == cchLine
644 || RT_C_IS_BLANK(pchLine[cchWord])
645 || pchLine[cchWord] == chAltSep)
646 {
647 *poff += cchWord;
648 return true;
649 }
650 return false;
651}
652
653
654/**
655 * Checks if the path @a pchLine[@a off] ends with @a pszFilename, ignoring case.
656 *
657 * @returns true if filename found, false if not.
658 * @param pchLine The line we're working on.
659 * @param off The current line offset where the image path of
660 * a DEVICE or IFS statement starts.
661 * @param cchLine The current line length.
662 * @param pszFilename The filename (no path) to match with, all upper
663 * cased.
664 * @param cchFilename The length of the filename to match with.
665 */
666static bool MatchOnlyFilename(const char *pchLine, size_t off, size_t cchLine, const char *pszFilename, size_t cchFilename)
667{
668 pchLine += off;
669 cchLine -= off;
670
671 /*
672 * Skip ahead in pchString till we get to the filename.
673 */
674 size_t offFilename = 0;
675 size_t offCur = 0;
676 if ( cchLine > 2
677 && pchLine[1] == ':'
678 && RT_C_IS_ALPHA(pchLine[0]))
679 offCur += 2;
680 while (offCur < cchLine)
681 {
682 char ch = pchLine[offCur];
683 if (RTPATH_IS_SLASH(pchLine[offCur]))
684 offFilename = offCur + 1;
685 else if (RT_C_IS_BLANK(ch))
686 break;
687 offCur++;
688 }
689 size_t const cchLeftFilename = offCur - offFilename;
690#ifdef DEBUG_MATCHING
691 WriteNStrings(g_hStdOut, RT_STR_TUPLE("debug: MatchOnlyFilename: '"), &pchLine[offFilename], cchLeftFilename,
692 RT_STR_TUPLE("' vs '"), pszFilename, cchFilename, RT_STR_TUPLE("'\r\n"), NULL, 0);
693#endif
694
695 /*
696 * Check if the length matches.
697 */
698 if (cchLeftFilename != cchFilename)
699 return false;
700
701 /*
702 * Check if the filenames matches (ASSUMES right side is uppercased).
703 */
704 pchLine += offFilename;
705 while (cchFilename-- > 0)
706 {
707 if (RT_C_TO_UPPER(*pchLine) != *pszFilename)
708 return false;
709 pchLine++;
710 pszFilename++;
711 }
712#ifdef DEBUG_MATCHING
713 WriteStrings(g_hStdOut, "debug: MatchOnlyFilename: -> true\r\n", NULL);
714#endif
715 return true;
716}
717
718
719static bool MatchPath(const char *pchPath1, size_t cchPath1, const char *pchPath2, size_t cchPath2)
720{
721#ifdef DEBUG_MATCHING
722 WriteNStrings(g_hStdOut, RT_STR_TUPLE("debug: MatchPath: '"), pchPath1, cchPath1,
723 RT_STR_TUPLE("' vs '"), pchPath2, cchPath2, RT_STR_TUPLE("'\r\n"), NULL, 0);
724#endif
725
726 while (cchPath1 > 0 && cchPath2 > 0)
727 {
728 char const ch1 = *pchPath1++;
729 cchPath1--;
730 char const ch2 = *pchPath2++;
731 cchPath2--;
732
733 /* Slashes are special as it generally doesn't matter how many are in
734 a row, at least no on decent systems. */
735 if (RTPATH_IS_SLASH(ch1))
736 {
737 if (!RTPATH_IS_SLASH(ch2))
738 return false;
739 while (cchPath1 > 0 && RTPATH_IS_SLASH(*pchPath1))
740 pchPath1++, cchPath1--;
741 while (cchPath2 > 0 && RTPATH_IS_SLASH(*pchPath2))
742 pchPath2++, cchPath2--;
743 }
744 /* Just uppercase before comparing to save space. */
745 else if (RT_C_TO_UPPER(ch1) != RT_C_TO_UPPER(ch2))
746 return false;
747 }
748
749 /* Ignore trailing slashes before reaching a conclusion. */
750 while (cchPath1 > 0 && RTPATH_IS_SLASH(*pchPath1))
751 pchPath1++, cchPath1--;
752 while (cchPath2 > 0 && RTPATH_IS_SLASH(*pchPath2))
753 pchPath2++, cchPath2--;
754
755#ifdef DEBUG_MATCHING
756 if (cchPath1 == 0 && cchPath2 == 0)
757 WriteStrings(g_hStdOut, "debug: MatchPath: -> true\r\n", NULL);
758#endif
759 return cchPath1 == 0 && cchPath2 == 0;
760}
761
762
763/*********************************************************************************************************************************
764* Installation Steps. *
765*********************************************************************************************************************************/
766
767/**
768 * Checks that the necessary GRADD components are present.
769 */
770static RTEXITCODE CheckForGradd(void)
771{
772 strcpy(&g_szBootDrivePath[g_cchBootDrivePath], "OS2\\DLL\\GENGRADD.DLL");
773 FILESTATUS3 FileSts;
774 APIRET rc = DosQueryPathInfo(g_szBootDrivePath, FIL_STANDARD, &FileSts, sizeof(FileSts));
775 if (rc != NO_ERROR)
776 return ApiErrorN(rc, 3, "DosQueryPathInfo(\"", g_szBootDrivePath, "\",,,) [installed gengradd?] ");
777
778 /* Note! GRADD precense in Config.sys is checked below while modifying it. */
779 return RTEXITCODE_SUCCESS;
780}
781
782
783/** Adds DEVICE=[path]\\VBoxGuest.sys to the modified Config.sys. */
784static bool ConfigSysAddVBoxGuest(void)
785{
786 EditorPutStringN(&g_ConfigSys, RT_STR_TUPLE("DEVICE="));
787 EditorPutStringN(&g_ConfigSys, g_szDstPath, g_cchDstPath);
788 EditorPutLine(&g_ConfigSys, RT_STR_TUPLE("VBoxGuest.sys"));
789 return true;
790}
791
792
793/** Adds IFS=[path]\\VBoxSF.IFS to the modified Config.sys. */
794static bool ConfigSysAddVBoxSF(void)
795{
796 EditorPutStringN(&g_ConfigSys, RT_STR_TUPLE("IFS="));
797 EditorPutStringN(&g_ConfigSys, g_szDstPath, g_cchDstPath);
798 EditorPutLine(&g_ConfigSys, RT_STR_TUPLE("VBoxSF.ifs"));
799 return true;
800}
801
802
803/** Adds DEVICE=[path]\\VBoxMouse.sys to the modified Config.sys. */
804static bool ConfigSysAddVBoxMouse(void)
805{
806 EditorPutStringN(&g_ConfigSys, RT_STR_TUPLE("DEVICE="));
807 EditorPutStringN(&g_ConfigSys, g_szDstPath, g_cchDstPath);
808 EditorPutLine(&g_ConfigSys, RT_STR_TUPLE("VBoxMouse.sys"));
809 return true;
810}
811
812
813/**
814 * Strips leading and trailing spaces and commas from the given substring.
815 *
816 * This is for GRADD_CHAINS and friends.
817 */
818static size_t StripGraddList(const char **ppch, size_t cch)
819{
820 const char *pch = *ppch;
821 while ( cch > 0
822 && ( RT_C_IS_BLANK(pch[0])
823 || pch[0] == ',') )
824 cch--, pch++;
825 *ppch = pch;
826
827 while ( cch > 0
828 && ( RT_C_IS_BLANK(pch[cch - 1])
829 || pch[cch - 1] == ',') )
830 cch--;
831 return cch;
832}
833
834
835/**
836 * Prepares the config.sys modifications.
837 */
838static RTEXITCODE PrepareConfigSys(void)
839{
840 if (g_fSkipMask & SKIP_CONFIG_SYS)
841 return RTEXITCODE_SUCCESS;
842
843 strcpy(&g_szBootDrivePath[g_cchBootDrivePath], "CONFIG.SYS");
844 RTEXITCODE rcExit = EditorReadInFile(&g_ConfigSys, g_szBootDrivePath, 4096, true /*fMustExist*/);
845 if (rcExit != RTEXITCODE_SUCCESS)
846 return rcExit;
847
848 /*
849 * Figure out which IFS we should place ourselves after by examining the
850 * destination path's file system, assuming HPFS if we cannot figure it out.
851 */
852 const char *pszAfterIfs = "HPFS.IFS";
853 size_t cchAfterIfs = sizeof("HPFS.IFS") - 1;
854
855 union
856 {
857 FSQBUFFER2 FsQBuf;
858 uint8_t abPadding[1024];
859 } u;
860 memset(&u, 0, sizeof(u));
861 ULONG cbBuf = sizeof(u) - 8 /* for adding .IFS */;
862
863 char szDrv[4];
864 szDrv[0] = g_szDstPath[0];
865 szDrv[1] = g_szDstPath[1];
866 szDrv[2] = '\0';
867
868 APIRET rc = DosQueryFSAttach(szDrv, 0 /*iOrdinal*/, FSAIL_QUERYNAME, &u.FsQBuf, &cbBuf);
869 if ( rc == NO_ERROR
870 || (rc == ERROR_BUFFER_OVERFLOW && u.FsQBuf.cbFSDName > 2 && u.FsQBuf.cbFSDName <= 7))
871 {
872 char *pszFsdName = (char *)&u.FsQBuf.szName[u.FsQBuf.cbName + 1];
873 if ( RT_C_IS_ALNUM(pszFsdName[0])
874 && RT_C_IS_ALNUM(pszFsdName[1])
875 && pszFsdName[u.FsQBuf.cbFSDName] == '\0')
876 {
877 /* MatchOnlyFilename requires it to be all uppercase (should be the case already). */
878 size_t off = u.FsQBuf.cbFSDName;
879 while (off-- > 0)
880 pszFsdName[off] = RT_C_TO_UPPER(pszFsdName[off]);
881
882 /* Add the IFS suffix. */
883 strcpy(&pszFsdName[u.FsQBuf.cbFSDName], ".IFS");
884 pszAfterIfs = pszFsdName;
885 cchAfterIfs = u.FsQBuf.cbFSDName + sizeof(".IFS") - 1;
886
887 if (g_fVerbose)
888 WriteStrings(g_hStdOut, "info: Found \"IFS=", pszFsdName, "\" for ", pszFsdName, "\r\n", NULL);
889 }
890 else
891 {
892 pszFsdName[10] = '\0';
893 ApiErrorN(ERROR_INVALID_NAME, 5, "Bogus FSD name \"", pszFsdName, "\" for ", szDrv, " - assuming HPFS");
894 }
895 }
896 else
897 ApiErrorN(rc, 3, "DosQueryFSAttach(", szDrv, ") - assuming HPFS");
898
899 /*
900 * Do a scan to locate where to insert ourselves and such.
901 */
902 char szLineNo[32];
903 char szNumBuf[32];
904 bool fInsertedGuest = false;
905 bool fInsertedMouse = RT_BOOL(g_fSkipMask & SKIP_MOUSE);
906 bool fPendingMouse = false;
907 bool fInsertedIfs = RT_BOOL(g_fSkipMask & SKIP_SHARED_FOLDERS);
908 unsigned cPathsFound = 0;
909 const char *pchGraddChains = "C1";
910 size_t cchGraddChains = sizeof("C1") - 1;
911 const char *pchGraddChain1 = NULL;
912 size_t cchGraddChain1 = NULL;
913 unsigned iLine = 0;
914 size_t offSrc = 0;
915 const char *pchLine;
916 size_t cchLine;
917 while ((offSrc = EditorGetLine(&g_ConfigSys, offSrc, &pchLine, &cchLine)) != 0)
918 {
919 iLine++;
920
921 size_t off = 0;
922#define SKIP_BLANKS() \
923 while (off < cchLine && RT_C_IS_BLANK(pchLine[off])) \
924 off++
925
926 bool fDone = false;
927 SKIP_BLANKS();
928
929 /*
930 * Add the destination directory to the PATH.
931 * If there are multiple SET PATH statements, we add ourselves to all of them.
932 */
933 if (MatchWord(pchLine, &off, cchLine, RT_STR_TUPLE("SET")))
934 {
935 SKIP_BLANKS();
936 if (MatchWord(pchLine, &off, cchLine, RT_STR_TUPLE("PATH"), '='))
937 {
938 SKIP_BLANKS();
939 if (cchLine > off && pchLine[off] == '=')
940 {
941 off++;
942 SKIP_BLANKS();
943
944 if (g_fVerbose)
945 WriteStrings(g_hStdOut, "info: Config.sys line ", MyNumToString(szLineNo, iLine), ": SET PATH\r\n", NULL);
946
947 /* Strip trailing spaces and semicolons. */
948 while (cchLine > off && (RT_C_IS_BLANK(pchLine[cchLine - 1]) || pchLine[cchLine - 1] == ';'))
949 cchLine--;
950
951 /* Remove any previous entries of the destination directory. */
952 unsigned iElement = 0;
953 char chLast = 0;
954 uint32_t cchWritten = 0;
955 while (off < cchLine)
956 {
957 iElement++;
958 const char *pszElement = &pchLine[off];
959 const char *pszSemiColon = (const char *)memchr(&pchLine[off], ';', cchLine - off);
960 size_t cchElement = (pszSemiColon ? pszSemiColon : &pchLine[cchLine]) - pszElement;
961 if (MatchPath(pszElement, cchElement, g_szDstPath, g_cchDstPath - (g_cchDstPath > 3 ? 1 : 0)))
962 {
963 if (g_fVerbose)
964 WriteNStrings(g_hStdOut, RT_STR_TUPLE("info: Config.sys line "),
965 MyNumToString(szLineNo, iLine), -1, RT_STR_TUPLE(": Removing PATH element #"),
966 MyNumToString(szNumBuf, iElement), -1, RT_STR_TUPLE(" \""), pszElement, cchElement,
967 RT_STR_TUPLE("\"\r\n"), NULL, 0);
968 EditorPutStringN(&g_ConfigSys, &pchLine[cchWritten], off - cchWritten);
969 chLast = pchLine[off - 1];
970 cchWritten = off + cchElement + (chLast == ';');
971 }
972 off += cchElement + 1;
973 }
974
975 /* Write out the rest of the line and append the destination directory to it. */
976 if (cchLine > cchWritten)
977 {
978 EditorPutStringN(&g_ConfigSys, &pchLine[cchWritten], cchLine - cchWritten);
979 chLast = pchLine[cchLine - 1];
980 }
981 if (chLast != ';')
982 EditorPutStringN(&g_ConfigSys, RT_STR_TUPLE(";"));
983 EditorPutStringN(&g_ConfigSys, g_szDstPath, g_cchDstPath - (g_cchDstPath > 3 ? 1 : 0));
984 EditorPutLine(&g_ConfigSys, RT_STR_TUPLE(";"));
985 fDone = true;
986
987 cPathsFound += 1;
988 }
989 }
990 /*
991 * Look for the GRADD_CHAINS variable.
992 *
993 * It is a comma separated list of chains (other env.vars.), however
994 * we can only deal with a single element. This shouldn't be an issue
995 * as GRADD_CHAINS is standardized by COMGRADD.DSP to the value C1, so
996 * other values can only be done by users or special drivers.
997 */
998 else if (MatchWord(pchLine, &off, cchLine, RT_STR_TUPLE("GRADD_CHAINS"), '='))
999 {
1000 SKIP_BLANKS();
1001 if (cchLine > off && pchLine[off] == '=')
1002 {
1003 off++;
1004
1005 const char *pchNew = &pchLine[off];
1006 size_t cchNew = StripGraddList(&pchNew, cchLine - off);
1007
1008 const char *pszComma = (const char *)memchr(pchNew, ',', cchNew);
1009 if (pszComma)
1010 {
1011 cchNew = StripGraddList(&pchNew, pchNew - pszComma);
1012 WriteStrings(g_hStdOut, "warning: Config.sys line ", MyNumToString(szLineNo, iLine),
1013 "GRADD_CHAINS contains more than one element. Ignoring all but the first.\r\n", NULL);
1014 }
1015
1016 /* If it differs from the default "C1" / previous value, we must
1017 restart the search for the primary chain environment variable.
1018 This means that chains values other than "C1" must come after
1019 the GRADD_CHAINS statement, since we're not doing an extra pass. */
1020 if ( cchGraddChains != cchNew
1021 || MyMemICmp(pchNew, pchGraddChains, cchNew) == 0)
1022 {
1023 pchGraddChains = pchNew;
1024 cchGraddChains = cchNew;
1025 pchGraddChain1 = NULL;
1026 cchGraddChain1 = 0;
1027 }
1028
1029 if (g_fVerbose)
1030 WriteNStrings(g_hStdOut, RT_STR_TUPLE("info: Config.sys line "), MyNumToString(szLineNo, iLine), - 1,
1031 RT_STR_TUPLE(": SET GRADD_CHAINS="), &pchLine[off], cchLine - off,
1032 RT_STR_TUPLE("\r\n"), NULL, 0);
1033 }
1034 }
1035 /*
1036 * Look for the chains listed by GRADD_CHAINS.
1037 */
1038 else if (MatchWord(pchLine, &off, cchLine, pchGraddChains, cchGraddChains, '='))
1039 {
1040 SKIP_BLANKS();
1041 if (cchLine > off && pchLine[off] == '=')
1042 {
1043 off++;
1044 SKIP_BLANKS();
1045
1046 /* Just save it, we'll validate it after processing everything. */
1047 pchGraddChain1 = &pchLine[off];
1048 cchGraddChain1 = StripGraddList(&pchGraddChain1, cchLine - off);
1049
1050 if (g_fVerbose)
1051 WriteNStrings(g_hStdOut, RT_STR_TUPLE("info: Config.sys line "), MyNumToString(szLineNo, iLine), - 1,
1052 RT_STR_TUPLE(": Found GRADD chain "), pchGraddChains, cchGraddChains,
1053 RT_STR_TUPLE(" with value: "), pchGraddChain1, cchGraddChain1,
1054 RT_STR_TUPLE("\r\n"), NULL, 0);
1055 }
1056 }
1057 }
1058 /*
1059 * Look for that IFS that should be loaded before we can load our drivers.
1060 */
1061 else if (MatchWord(pchLine, &off, cchLine, RT_STR_TUPLE("IFS"), '='))
1062 {
1063 SKIP_BLANKS();
1064 if (cchLine > off && pchLine[off] == '=')
1065 {
1066 off++;
1067 SKIP_BLANKS();
1068 if (MatchOnlyFilename(pchLine, off, cchLine, pszAfterIfs, cchAfterIfs))
1069 {
1070 if (g_fVerbose)
1071 WriteStrings(g_hStdOut, "info: Config.sys line ", MyNumToString(szLineNo, iLine),
1072 ": Found IFS=", pszAfterIfs, "\r\n", NULL);
1073 EditorPutLine(&g_ConfigSys, pchLine, cchLine);
1074 fDone = true;
1075
1076 if (!fInsertedGuest)
1077 fInsertedGuest = ConfigSysAddVBoxGuest();
1078 if (!fInsertedIfs)
1079 fInsertedIfs = ConfigSysAddVBoxSF();
1080 if (fPendingMouse && !fInsertedMouse)
1081 fInsertedMouse = ConfigSysAddVBoxMouse();
1082 }
1083 /* Remove old VBoxSF.IFS lines */
1084 else if ( !(g_fSkipMask & SKIP_SHARED_FOLDERS)
1085 && ( MatchOnlyFilename(pchLine, off, cchLine, RT_STR_TUPLE("VBOXSF.IFS"))
1086 || MatchOnlyFilename(pchLine, off, cchLine, RT_STR_TUPLE("VBOXFS.IFS")) ) )
1087 {
1088 if (g_fVerbose)
1089 WriteStrings(g_hStdOut, "info: Config.sys line ", MyNumToString(szLineNo, iLine),
1090 ": Removing old VBoxSF.ifs statement\r\n", NULL);
1091 fDone = true;
1092 }
1093 }
1094 }
1095 /*
1096 * Look for the mouse driver we need to comment out / existing VBoxMouse.sys,
1097 * as well as older VBoxGuest.sys statements we should remove.
1098 */
1099 else if (MatchWord(pchLine, &off, cchLine, RT_STR_TUPLE("DEVICE"), '='))
1100 {
1101 SKIP_BLANKS();
1102 if (cchLine > off && pchLine[off] == '=')
1103 {
1104 off++;
1105 SKIP_BLANKS();
1106 if ( !(g_fSkipMask & SKIP_MOUSE)
1107 && MatchOnlyFilename(pchLine, off, cchLine, RT_STR_TUPLE("MOUSE.SYS")))
1108 {
1109 if (g_fVerbose)
1110 WriteStrings(g_hStdOut, "info: Config.sys line ", MyNumToString(szLineNo, iLine),
1111 ": Found DEVICE=<path>\\MOUSE.SYS\r\n", NULL);
1112 EditorPutStringN(&g_ConfigSys, RT_STR_TUPLE("REM "));
1113 EditorPutLine(&g_ConfigSys, pchLine, cchLine);
1114 fDone = true;
1115
1116 if (!fInsertedMouse)
1117 {
1118 if (fInsertedGuest) /* means we've found the IFS and can access the destination dir */
1119 fInsertedMouse = ConfigSysAddVBoxMouse();
1120 else
1121 fPendingMouse = true;
1122 }
1123 }
1124 /* Remove or replace old VBoxMouse.IFS lines */
1125 else if ( !(g_fSkipMask & SKIP_MOUSE)
1126 && MatchOnlyFilename(pchLine, off, cchLine, RT_STR_TUPLE("VBOXMOUSE.SYS")))
1127 {
1128 if (g_fVerbose)
1129 WriteStrings(g_hStdOut, "info: Config.sys line ", MyNumToString(szLineNo, iLine), ": ",
1130 fInsertedMouse || !fInsertedGuest ? "Removing" : "Replacing",
1131 " old VBoxMouse.sys statement\r\n", NULL);
1132 if (!fInsertedMouse)
1133 {
1134 if (fInsertedGuest) /* means we've found the IFS and can access the destination dir */
1135 fInsertedMouse = ConfigSysAddVBoxMouse();
1136 else
1137 fPendingMouse = true;
1138 }
1139 fDone = true;
1140 }
1141 /* Remove old VBoxGuest.sys lines. */
1142 else if (MatchOnlyFilename(pchLine, off, cchLine, RT_STR_TUPLE("VBOXGUEST.SYS")))
1143 {
1144 if (g_fVerbose)
1145 WriteStrings(g_hStdOut, "info: Config.sys line ", MyNumToString(szLineNo, iLine),
1146 ": Removing old VBoxGuest.sys statement\r\n", NULL);
1147 fDone = true;
1148 }
1149 }
1150 }
1151#undef SKIP_BLANKS
1152
1153 /*
1154 * Output the current line if we didn't already do so above.
1155 */
1156 if (!fDone)
1157 EditorPutLine(&g_ConfigSys, pchLine, cchLine);
1158 }
1159
1160 /*
1161 * If we've still got pending stuff, add it now at the end.
1162 */
1163 if (!fInsertedGuest)
1164 fInsertedGuest = ConfigSysAddVBoxGuest();
1165 if (!fInsertedIfs)
1166 fInsertedIfs = ConfigSysAddVBoxSF();
1167 if (!fInsertedMouse)
1168 fInsertedMouse = ConfigSysAddVBoxMouse();
1169
1170 if (!cPathsFound)
1171 WriteStrings(g_hStdErr, "warning: Found no SET PATH statement in Config.sys.\r\n", NULL);
1172
1173 /*
1174 * If we're installing the graphics driver, check that GENGRADD is in the
1175 * primary GRADD chain.
1176 */
1177 if (!(g_fSkipMask & SKIP_GRAPHICS))
1178 {
1179 if (cchGraddChain1 > 0 && cchGraddChains > 0)
1180 {
1181 int idxGenGradd = -1;
1182 for (size_t off = 0, idx = 0; off < cchGraddChain1;)
1183 {
1184 const char *psz = &pchGraddChain1[off];
1185 size_t cch = cchGraddChain1 - off;
1186 const char *pszComma = (const char *)memchr(psz, ',', cchGraddChain1 - off);
1187 if (!pszComma)
1188 off += cch;
1189 else
1190 {
1191 cch = pszComma - psz;
1192 off += cch + 1;
1193 }
1194 while (cch > 0 && RT_C_IS_BLANK(*psz))
1195 cch--, psz++;
1196 while (cch > 0 && RT_C_IS_BLANK(psz[cch - 1]))
1197 cch--;
1198 if ( cch == sizeof("GENGRADD") - 1
1199 && MyMemICmp(psz, RT_STR_TUPLE("GENGRADD")) == 0)
1200 {
1201 idxGenGradd = idx;
1202 break;
1203 }
1204 idx += cch != 0;
1205 }
1206 if (idxGenGradd < 0)
1207 return ErrorNStrings(RT_STR_TUPLE("Primary GRADD chain \""), pchGraddChains, cchGraddChains,
1208 RT_STR_TUPLE("="), pchGraddChain1, cchGraddChain1,
1209 RT_STR_TUPLE("\" does not contain a GENGRADD entry."), NULL, 0);
1210 if (idxGenGradd != 0)
1211 return ErrorNStrings(RT_STR_TUPLE("GENGRADD is not the first entry in the primary GRADD chain \""),
1212 pchGraddChains, cchGraddChains, RT_STR_TUPLE("="), pchGraddChain1, cchGraddChain1, NULL, 0);
1213 }
1214 else if (cchGraddChains > 0)
1215 return ErrorNStrings(RT_STR_TUPLE("Primary GRADD chain \""), pchGraddChains, cchGraddChains,
1216 RT_STR_TUPLE("\" not found (only searched after SET GRADD_CHAINS)."), NULL, 0);
1217 else
1218 return ErrorNStrings(RT_STR_TUPLE("No SET GRADD_CHAINS statement found in Config.sys"), NULL, 0);
1219 }
1220
1221 return EditorCheckState(&g_ConfigSys, g_szBootDrivePath);
1222}
1223
1224
1225/** Puts the line starting VBoxService to Startup.cmd. */
1226static void StartupCmdPutLine(const char *pszLineNo)
1227{
1228 if (g_fVerbose)
1229 WriteStrings(g_hStdOut, "info: Starting VBoxService at line ", pszLineNo, " of Startup.cmd\r\n", NULL);
1230 EditorPutStringN(&g_StartupCmd, g_szDstPath, g_cchDstPath);
1231 EditorPutLine(&g_StartupCmd, RT_STR_TUPLE("VBoxService.exe"));
1232}
1233
1234
1235/**
1236 * Prepares the startup.cmd modifications.
1237 */
1238static RTEXITCODE PrepareStartupCmd(void)
1239{
1240 if (g_fSkipMask & SKIP_STARTUP_CMD)
1241 return RTEXITCODE_SUCCESS;
1242
1243 strcpy(&g_szBootDrivePath[g_cchBootDrivePath], "STARTUP.CMD");
1244 RTEXITCODE rcExit = EditorReadInFile(&g_StartupCmd, g_szBootDrivePath, 1024, false /*fMustExist*/);
1245 if (rcExit != RTEXITCODE_SUCCESS)
1246 return rcExit;
1247
1248 /*
1249 * Scan startup.cmd and see if there is an [@]ECHO OFF without anything other
1250 * than REM statements preceding it. If there is we'll insert ourselves after
1251 * that, otherwise we'll just jump in at the top.
1252 */
1253 unsigned iInsertBeforeLine = 0;
1254 unsigned iLine = 0;
1255 size_t offSrc = 0;
1256 const char *pchLine;
1257 size_t cchLine;
1258 while ((offSrc = EditorGetLine(&g_StartupCmd, offSrc, &pchLine, &cchLine)) != 0)
1259 {
1260 iLine++;
1261
1262 size_t off = 0;
1263#define SKIP_BLANKS() \
1264 while (off < cchLine && RT_C_IS_BLANK(pchLine[off])) \
1265 off++
1266 SKIP_BLANKS();
1267 if (off < cchLine && pchLine[off] == '@')
1268 {
1269 off++;
1270 SKIP_BLANKS();
1271 }
1272 if (MatchWord(pchLine, &off, cchLine, RT_STR_TUPLE("ECHO")))
1273 {
1274 SKIP_BLANKS();
1275
1276 if (MatchWord(pchLine, &off, cchLine, RT_STR_TUPLE("OFF")))
1277 {
1278 iInsertBeforeLine = iLine + 1;
1279 break;
1280 }
1281 }
1282 else if (MatchWord(pchLine, &off, cchLine, RT_STR_TUPLE("REM")))
1283 { /* skip */ }
1284 else
1285 break;
1286 }
1287
1288 /*
1289 * Make the modifications.
1290 */
1291 if (iInsertBeforeLine == 0) /* Necessary to do this outside the loop in case startup.cmd is empty or non-existing. */
1292 StartupCmdPutLine("1");
1293
1294 offSrc = iLine = 0;
1295 while ((offSrc = EditorGetLine(&g_StartupCmd, offSrc, &pchLine, &cchLine)) != 0)
1296 {
1297 char szLineNo[32];
1298 iLine++;
1299 if (iLine == iInsertBeforeLine)
1300 StartupCmdPutLine(MyNumToString(szLineNo, iLine));
1301
1302 /*
1303 * Filter out old VBoxService lines. To be on the safe side we skip
1304 * past DETACH, CALL, and START before checking for VBoxService.
1305 */
1306 size_t off = 0;
1307 SKIP_BLANKS();
1308 if (off < cchLine && pchLine[off] == '@')
1309 {
1310 off++;
1311 SKIP_BLANKS();
1312 }
1313
1314 if (MatchWord(pchLine, &off, cchLine, RT_STR_TUPLE("DETACH")))
1315 SKIP_BLANKS();
1316
1317 if (MatchWord(pchLine, &off, cchLine, RT_STR_TUPLE("CALL")))
1318 SKIP_BLANKS();
1319
1320 if (MatchWord(pchLine, &off, cchLine, RT_STR_TUPLE("START")))
1321 SKIP_BLANKS();
1322
1323 if ( MatchOnlyFilename(pchLine, off, cchLine, RT_STR_TUPLE("VBOXSERVICE.EXE"))
1324 || MatchOnlyFilename(pchLine, off, cchLine, RT_STR_TUPLE("VBOXSERVICE")) )
1325 {
1326 if (g_fVerbose)
1327 WriteStrings(g_hStdOut, "info: Removing old VBoxService statement on line ",
1328 MyNumToString(szLineNo, iLine), "\r\n", NULL);
1329 }
1330 else
1331 EditorPutLine(&g_StartupCmd, pchLine, cchLine);
1332
1333#undef SKIP_BLANKS
1334 }
1335
1336 return EditorCheckState(&g_StartupCmd, g_szBootDrivePath);
1337}
1338
1339
1340/**
1341 * Tells the loader to cache all the pages in @a pszFile and close it, so that
1342 * we can modify or replace it.
1343 */
1344static void CacheLdrFile(const char *pszFile)
1345{
1346 if (g_fVerbose)
1347 DoWriteNStr(g_hStdOut, RT_STR_TUPLE("info: Sharing violation - applying DosReplaceModule...\r\n"));
1348
1349 APIRET rc = DosReplaceModule(pszFile, NULL, NULL);
1350 if (rc != NO_ERROR)
1351 ApiErrorN(rc, 3, "DosReplaceModule(\"", pszFile, "\",,)");
1352}
1353
1354
1355/**
1356 * Worker for CopyFiles that handles one copying operation.
1357 */
1358static RTEXITCODE CopyOneFile(const char *pszSrc, const char *pszDst)
1359{
1360 FILESTATUS3 FileSts;
1361 if (g_fVerbose)
1362 WriteStrings(g_hStdOut, "info: Copying \"", pszSrc, "\" to \"", pszDst, "\"...\r\n", NULL);
1363
1364 if (g_fRealRun)
1365 {
1366 /* Make sure the destination file isn't read-only before attempting to copying it. */
1367 APIRET rc = DosQueryPathInfo(pszDst, FIL_STANDARD, &FileSts, sizeof(FileSts));
1368 if (rc == NO_ERROR && (FileSts.attrFile & FILE_READONLY))
1369 {
1370 FileSts.attrFile &= ~FILE_READONLY;
1371
1372 /* Don't update the timestamps: */
1373 *(USHORT *)&FileSts.fdateCreation = 0;
1374 *(USHORT *)&FileSts.ftimeCreation = 0;
1375 *(USHORT *)&FileSts.fdateLastAccess = 0;
1376 *(USHORT *)&FileSts.ftimeLastAccess = 0;
1377 *(USHORT *)&FileSts.fdateLastWrite = 0;
1378 *(USHORT *)&FileSts.ftimeLastWrite = 0;
1379
1380 rc = DosSetPathInfo(pszDst, FIL_STANDARD, &FileSts, sizeof(FileSts), 0 /*fOptions*/);
1381 if (rc == ERROR_SHARING_VIOLATION)
1382 {
1383 CacheLdrFile(pszDst);
1384 rc = DosSetPathInfo(pszDst, FIL_STANDARD, &FileSts, sizeof(FileSts), 0 /*fOptions*/);
1385 }
1386
1387 if (rc != NO_ERROR)
1388 ApiErrorN(rc, 3, "DosSetPathInfo(\"", pszDst, "\",~READONLY,)");
1389 }
1390
1391 /* Do the copying. */
1392 rc = DosCopy(pszSrc, pszDst, DCPY_EXISTING);
1393 if (rc == NO_ERROR)
1394 return RTEXITCODE_SUCCESS;
1395 if (rc != ERROR_SHARING_VIOLATION)
1396 return ApiErrorN(rc, 3, "Failed copying to \"", pszDst, "\"");
1397
1398 CacheLdrFile(pszDst);
1399 rc = DosCopy(pszSrc, pszDst, DCPY_EXISTING);
1400 if (rc == NO_ERROR)
1401 return RTEXITCODE_SUCCESS;
1402 return ApiErrorN(rc, 3, "Failed copying to \"", pszDst, "\"");
1403 }
1404 /*
1405 * Dry run: just check that the source file exists.
1406 */
1407 else
1408 {
1409 APIRET rc = DosQueryPathInfo(pszSrc, FIL_STANDARD, &FileSts, sizeof(FileSts));
1410 if (rc == NO_ERROR)
1411 return RTEXITCODE_SUCCESS;
1412 return ApiErrorN(rc, 3, "DosQueryPathInfo failed on \"", pszSrc, "\"");
1413 }
1414}
1415
1416
1417/**
1418 * Copies the GA files.
1419 */
1420static RTEXITCODE CopyFiles(void)
1421{
1422 if (g_fRealRun)
1423 {
1424 /*
1425 * Create the install directory. We do this from the root up as that is
1426 * a nice feature and saves us dealing with trailing slash troubles.
1427 */
1428 char *psz = g_szDstPath;
1429 if (psz[1] == ':' && RTPATH_IS_SLASH(psz[2]))
1430 psz += 3;
1431 else if (psz[1] == ':')
1432 psz += 2;
1433 else
1434 return ApiError("Unexpected condition", __LINE__);
1435
1436 for (;;)
1437 {
1438 char ch;
1439 while ((ch = *psz) != '\0' && !RTPATH_IS_SLASH(ch))
1440 psz++;
1441 if (ch != '\0')
1442 *psz = '\0';
1443 APIRET rc = DosMkDir(g_szDstPath, 0);
1444 if (rc != NO_ERROR && rc != ERROR_ACCESS_DENIED /*HPFS*/ && rc != ERROR_ALREADY_EXISTS /*what one would expect*/)
1445 return ApiErrorN(rc, 3, "DosMkDir(\"", g_szDstPath, "\")");
1446 if (ch == '\0')
1447 break;
1448 *psz++ = ch;
1449 while ((ch = *psz) != '\0' && RTPATH_IS_SLASH(ch))
1450 psz++;
1451 if (ch == '\0')
1452 break;
1453 }
1454 }
1455
1456 /*
1457 * Start copying files. We copy all files into the directory regardless
1458 * of whether they will be referenced by config.sys, startup.cmd or whatever.
1459 */
1460 static struct
1461 {
1462 const char *pszFile;
1463 const char *pszAltDst;
1464 uint8_t fSkipMask;
1465 } const s_aFiles[] =
1466 {
1467 { "VBoxService.exe", NULL, 0 }, /* first as likely to be running */
1468 { "VBoxControl.exe", NULL, 0 },
1469 { "VBoxReplaceDll.exe", NULL, 0 },
1470 { "gengradd.dll", "OS2\\DLL\\gengradd.dll", SKIP_GRAPHICS },
1471 { "libc06.dll", "OS2\\DLL\\libc06.dll", SKIP_LIBC_DLLS },
1472 { "libc061.dll", "OS2\\DLL\\libc061.dll", SKIP_LIBC_DLLS },
1473 { "libc062.dll", "OS2\\DLL\\libc062.dll", SKIP_LIBC_DLLS },
1474 { "libc063.dll", "OS2\\DLL\\libc063.dll", SKIP_LIBC_DLLS },
1475 { "libc064.dll", "OS2\\DLL\\libc064.dll", SKIP_LIBC_DLLS },
1476 { "libc065.dll", "OS2\\DLL\\libc065.dll", SKIP_LIBC_DLLS },
1477 { "libc066.dll", "OS2\\DLL\\libc066.dll", SKIP_LIBC_DLLS },
1478 { "VBoxGuest.sys", NULL, 0 },
1479 { "VBoxSF.ifs", NULL, 0 },
1480 { "vboxmouse.sys", NULL, 0 },
1481 { "readme.txt", NULL, 0 },
1482 };
1483
1484 RTEXITCODE rcExit;
1485 for (size_t i = 0; i < RT_ELEMENTS(s_aFiles); i++)
1486 {
1487 /* Always copy files to the destination folder. */
1488 strcpy(&g_szSrcPath[g_cchSrcPath], s_aFiles[i].pszFile);
1489 strcpy(&g_szDstPath[g_cchDstPath], s_aFiles[i].pszFile);
1490 RTEXITCODE rcExit2 = CopyOneFile(g_szSrcPath, g_szDstPath);
1491 if (rcExit2 != RTEXITCODE_SUCCESS)
1492 rcExit = rcExit2;
1493
1494 /* Additional install location and this not being skipped? */
1495 if ( s_aFiles[i].pszAltDst
1496 && !(s_aFiles[i].fSkipMask & g_fSkipMask) /* ASSUMES one skip bit per file */)
1497 {
1498 strcpy(&g_szBootDrivePath[g_cchBootDrivePath], s_aFiles[i].pszAltDst);
1499
1500 rcExit2 = CopyOneFile(g_szSrcPath, g_szBootDrivePath);
1501 if (rcExit2 != RTEXITCODE_SUCCESS)
1502 rcExit = rcExit2;
1503 }
1504 }
1505
1506 return rcExit;
1507}
1508
1509
1510/**
1511 * Writes out the modified config.sys.
1512 */
1513static RTEXITCODE WriteConfigSys(void)
1514{
1515 if (g_fSkipMask & SKIP_CONFIG_SYS)
1516 return RTEXITCODE_SUCCESS;
1517 strcpy(&g_szBootDrivePath[g_cchBootDrivePath], "CONFIG.SYS");
1518 return EditorWriteOutFile(&g_ConfigSys, g_szBootDrivePath);
1519}
1520
1521
1522/**
1523 * Writes out the modified startup.cmd.
1524 */
1525static RTEXITCODE WriteStartupCmd(void)
1526{
1527 if (g_fSkipMask & SKIP_CONFIG_SYS)
1528 return RTEXITCODE_SUCCESS;
1529 strcpy(&g_szBootDrivePath[g_cchBootDrivePath], "STARTUP.CMD");
1530 return EditorWriteOutFile(&g_StartupCmd, g_szBootDrivePath);
1531}
1532
1533
1534/*********************************************************************************************************************************
1535* Option parsing and such. *
1536*********************************************************************************************************************************/
1537
1538static RTEXITCODE ShowUsage(void)
1539{
1540 static const char g_szUsage[] =
1541 VBOX_PRODUCT " OS/2 Additions Installer " VBOX_VERSION_STRING "\r\n"
1542 "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\r\n"
1543 "\r\n"
1544 "This is a very barebone OS/2 guest additions installer which main purpose is\r\n"
1545 "to help with unattended installation. Do not expect it to handle complicated\r\n"
1546 "situations like upgrades and similar. It also does not understand arguments\r\n"
1547 "that are placed in double quotes.\r\n"
1548 "\r\n"
1549 "Usage: VBoxIs2AdditionsInstall.exe [options]\r\n"
1550 " or VBoxIs2AdditionsInstall.exe <-h|-?|--help>\r\n"
1551 " or VBoxIs2AdditionsInstall.exe <-v|--version>\r\n"
1552 "\r\n"
1553 "Options:\r\n"
1554 " -i, --do-install / -z, --dry-run\r\n"
1555 " Controls whether to do a real install or not. Default: --dry-run\r\n"
1556 " -s<path>, --source[=]<path>\r\n"
1557 " Specifies where the files to install are. Default: Same as installer\r\n"
1558 " -d<path>, --destination[=]<path>\r\n"
1559 " Specifies where to install all the VBox OS/2 additions files.\r\n"
1560 " Default: C:\VBoxAdd (C is replaced by actual boot drive)\r\n"
1561 " -b<path>, --boot-drive[=]<path>\r\n"
1562 " Specifies the boot drive. Default: C: (C is replaced by the actual one)\r\n"
1563 " -F, --no-shared-folders / -f, --shared-folders (default)\r\n"
1564 " Controls whether to put the shared folders IFS in Config.sys.\r\n"
1565 " -G, --no-graphics / -g, --graphics (default)\r\n"
1566 " Controls whether to replace OS2\\DLL\\GENGRADD.DLL with the VBox version.\r\n"
1567 " -M, --no-mouse / -m, --mouse (default)\r\n"
1568 " Controls whether to add the VBox mouse driver to Config.sys and disable\r\n"
1569 " the regular OS/2 one.\r\n"
1570 " -S, --no-service / -s, --service (default)\r\n"
1571 " Controls whether to add starting VBoxService from Startup.cmd.\r\n"
1572 " -T, --no-startup-cmd / -t, --startup-cmd (default)\r\n"
1573 " Controls whether to modify Startup.cmd.\r\n"
1574 " -C, --no-config-sys / -c, --config-sys (default)\r\n"
1575 " Controls whether to modify Config.sys.\r\n"
1576 " -L, --no-libc-dlls / -l, --libc-dlls (default)\r\n"
1577 " Controls whether copy the kLibC DLLs to OS2\\DLLS.\r\n"
1578 " -q, --quiet / -V, --verbose (default)\r\n"
1579 " Controls the installer noise level.\r\n"
1580 "\r\n"
1581 "Exit Codes:\r\n"
1582 " 0 - Success. Reboot required.\r\n"
1583 " 1 - Failure.\r\n"
1584 " 2 - Syntax error.\r\n"
1585 ;
1586 DoWriteNStr(g_hStdOut, RT_STR_TUPLE(g_szUsage));
1587 return RTEXITCODE_SUCCESS;
1588}
1589
1590
1591static RTEXITCODE ShowVersion(void)
1592{
1593 DoWriteNStr(g_hStdOut, RT_STR_TUPLE(VBOX_VERSION_STRING " r"));
1594
1595 const char *pszRev = "$Rev: 98103 $";
1596 while (!RT_C_IS_DIGIT(*pszRev))
1597 pszRev++;
1598 size_t cchRev = 1;
1599 while (RT_C_IS_DIGIT(pszRev[cchRev]))
1600 cchRev++;
1601 DoWriteNStr(g_hStdOut, pszRev, cchRev);
1602
1603 DoWriteNStr(g_hStdOut, RT_STR_TUPLE("\r\n"));
1604 return RTEXITCODE_SUCCESS;
1605}
1606
1607
1608static bool MatchOptWord(PSZ *ppsz, const char *pszWord, size_t cchWord, bool fTakeValue = false)
1609{
1610 PSZ psz = *ppsz;
1611 if (strncmp(psz, pszWord, cchWord) == 0)
1612 {
1613 psz += cchWord;
1614 CHAR ch = *psz;
1615 if (ch == '\0')
1616 {
1617 /* No extra complaining needed when fTakeValue is true, as values must be non-empty strings. */
1618 *ppsz = psz;
1619 return true;
1620 }
1621 if (RT_C_IS_SPACE(ch))
1622 {
1623 if (fTakeValue)
1624 do
1625 {
1626 ch = *++psz;
1627 } while (RT_C_IS_SPACE(ch));
1628 *ppsz = psz;
1629 return true;
1630 }
1631 if (fTakeValue && (ch == ':' || ch == '='))
1632 {
1633 *ppsz = psz + 1;
1634 return true;
1635 }
1636 }
1637 return false;
1638}
1639
1640
1641static PSZ GetOptValue(PSZ psz, const char *pszOption, char *pszValue, size_t cbValue)
1642{
1643 PSZ const pszStart = psz;
1644 CHAR ch = *psz;
1645 if (ch != '\0' && RT_C_IS_SPACE(ch))
1646 {
1647 do
1648 ch = *++psz;
1649 while (ch != '\0' && !RT_C_IS_SPACE(ch));
1650
1651 size_t const cchSrc = psz - pszStart;
1652 if (cchSrc < cbValue)
1653 {
1654 memcpy(pszValue, pszStart, cchSrc);
1655 pszValue[cchSrc] = '\0';
1656 return psz; /* Do not skip space or we won't get out of the inner option loop! */
1657 }
1658 SyntaxError("Argument value too long", pszOption);
1659 }
1660 else
1661 SyntaxError("Argument value cannot be empty", pszOption);
1662 return NULL;
1663}
1664
1665
1666static PSZ GetOptPath(PSZ psz, const char *pszOption, char *pszValue, size_t cbValue, size_t cchScratch, size_t *pcchValue)
1667{
1668 psz = GetOptValue(psz, pszOption, pszValue, cbValue - cchScratch);
1669 if (psz)
1670 {
1671 /* Only accept drive letters for now. This could be a UNC path too for CID servers ;-) */
1672 if ( !RT_C_IS_ALPHA(pszValue[0])
1673 || pszValue[1] != ':'
1674 || (pszValue[2] != '\0' && pszValue[2] != '\\' && pszValue[2] != '/'))
1675 SyntaxError("The path must be absolute", pszOption);
1676
1677 *pcchValue = RTPathEnsureTrailingSeparator(pszValue, cbValue);
1678 if (*pcchValue == 0)
1679 SyntaxError("RTPathEnsureTrailingSeparator overflowed", pszValue);
1680 }
1681 return psz;
1682}
1683
1684
1685
1686/**
1687 * This is the main entrypoint of the executable (no CRT).
1688 *
1689 * @note Considered doing a main() wrapper by means of RTGetOptArgvFromString,
1690 * but the dependencies are bad and we definitely need a half working heap
1691 * for that. Maybe later.
1692 */
1693extern "C" int __cdecl VBoxOs2AdditionsInstallMain(HMODULE hmodExe, ULONG ulReserved, PSZ pszzEnv, PSZ pszzCmdLine)
1694{
1695 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1696
1697 /*
1698 * Correct defaults.
1699 */
1700 ULONG ulBootDrv = 0x80;
1701 DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, &ulBootDrv, sizeof(ulBootDrv));
1702 g_szBootDrivePath[0] = g_szDstPath[0] = 'A' + ulBootDrv - 1;
1703
1704 /*
1705 * Parse parameters, skipping the first argv[0] one.
1706 */
1707 PSZ pszArgs = &pszzCmdLine[strlen(pszzCmdLine) + 1];
1708 CHAR ch;
1709 while ((ch = *pszArgs++) != '\0')
1710 {
1711 if (RT_C_IS_SPACE(ch))
1712 continue;
1713 if (ch != '-')
1714 return SyntaxError("Non-option argument", pszArgs - 1);
1715 ch = *pszArgs++;
1716 if (ch == '-')
1717 {
1718 if (!pszArgs[0])
1719 break;
1720 if ( MatchOptWord(&pszArgs, RT_STR_TUPLE("boot"), true)
1721 || MatchOptWord(&pszArgs, RT_STR_TUPLE("boot-drive"), true))
1722 ch = 'b';
1723 else if ( MatchOptWord(&pszArgs, RT_STR_TUPLE("dst"), true)
1724 || MatchOptWord(&pszArgs, RT_STR_TUPLE("destination"), true) )
1725 ch = 'd';
1726 else if ( MatchOptWord(&pszArgs, RT_STR_TUPLE("src"), true)
1727 || MatchOptWord(&pszArgs, RT_STR_TUPLE("source"), true))
1728 ch = 's';
1729 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("do-install")))
1730 ch = 'i';
1731 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("dry-run")))
1732 ch = 'z';
1733 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("shared-folders")))
1734 ch = 'f';
1735 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("no-shared-folders")))
1736 ch = 'F';
1737 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("graphics")))
1738 ch = 'g';
1739 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("no-graphics")))
1740 ch = 'G';
1741 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("mouse")))
1742 ch = 'm';
1743 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("no-mouse")))
1744 ch = 'M';
1745 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("service")))
1746 ch = 's';
1747 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("no-service")))
1748 ch = 'S';
1749 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("startup-cmd")))
1750 ch = 't';
1751 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("no-startup-cmd")))
1752 ch = 'T';
1753 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("config-sys")))
1754 ch = 'c';
1755 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("no-config-sys")))
1756 ch = 'C';
1757 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("libc-dlls")))
1758 ch = 'l';
1759 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("no-libc-dlls")))
1760 ch = 'L';
1761 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("quiet")))
1762 ch = 'q';
1763 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("verbose")))
1764 ch = 'V';
1765 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("help")))
1766 ch = 'h';
1767 else if (MatchOptWord(&pszArgs, RT_STR_TUPLE("version")))
1768 ch = 'v';
1769 else
1770 return SyntaxError("Unknown option", pszArgs - 2);
1771 }
1772
1773 for (;;)
1774 {
1775 switch (ch)
1776 {
1777 case '-':
1778 while ((ch = *pszArgs) != '\0' && RT_C_IS_SPACE(ch))
1779 pszArgs++;
1780 if (ch == '\0')
1781 break;
1782 return SyntaxError("Non-option argument", pszArgs);
1783
1784 case 'b':
1785 pszArgs = GetOptPath(pszArgs, "--boot-drive / -b",
1786 g_szBootDrivePath, sizeof(g_szBootDrivePath), 64, &g_cchBootDrivePath);
1787 if (!pszArgs)
1788 return RTEXITCODE_SYNTAX;
1789 break;
1790
1791 case 'd':
1792 pszArgs = GetOptPath(pszArgs, "--destination / -d", g_szDstPath, sizeof(g_szDstPath), 32, &g_cchDstPath);
1793 if (!pszArgs)
1794 return RTEXITCODE_SYNTAX;
1795 break;
1796
1797 case 's':
1798 pszArgs = GetOptPath(pszArgs, "--source / -s", g_szSrcPath, sizeof(g_szSrcPath), 32, &g_cchSrcPath);
1799 if (!pszArgs)
1800 return RTEXITCODE_SYNTAX;
1801 break;
1802
1803 case 'i':
1804 g_fRealRun = true;
1805 break;
1806
1807 case 'z':
1808 g_fRealRun = false;
1809 break;
1810
1811#define SKIP_OPT_CASES(a_chSkip, a_chDontSkip, a_fFlag) \
1812 case a_chDontSkip: g_fSkipMask &= ~(a_fFlag); break; \
1813 case a_chSkip: g_fSkipMask |= (a_fFlag); break
1814 SKIP_OPT_CASES('F', 'f', SKIP_SHARED_FOLDERS);
1815 SKIP_OPT_CASES('G', 'g', SKIP_GRAPHICS);
1816 SKIP_OPT_CASES('M', 'm', SKIP_MOUSE);
1817 SKIP_OPT_CASES('E', 'e', SKIP_SERVICE);
1818 SKIP_OPT_CASES('U', 'u', SKIP_STARTUP_CMD);
1819 SKIP_OPT_CASES('C', 'c', SKIP_CONFIG_SYS);
1820 SKIP_OPT_CASES('L', 'l', SKIP_LIBC_DLLS);
1821#undef SKIP_OPT_CASES
1822
1823 case 'q':
1824 g_fVerbose = false;
1825 break;
1826
1827 case 'V':
1828 g_fVerbose = true;
1829 break;
1830
1831 case 'h':
1832 case '?':
1833 return ShowUsage();
1834 case 'v':
1835 return ShowVersion();
1836
1837 default:
1838 return SyntaxError("Unknown option", pszArgs - 2);
1839 }
1840
1841 ch = *pszArgs;
1842 if (RT_C_IS_SPACE(ch) || ch == '\0')
1843 break;
1844 pszArgs++;
1845 }
1846 }
1847
1848 if (g_szSrcPath[0] == '\0')
1849 {
1850 APIRET rc = DosQueryModuleName(hmodExe, sizeof(g_szSrcPath), g_szSrcPath);
1851 if (rc != NO_ERROR)
1852 return ApiError("DosQueryModuleName", rc);
1853 RTPathStripFilename(g_szSrcPath);
1854 g_cchSrcPath = RTPathEnsureTrailingSeparator(g_szSrcPath, sizeof(g_szSrcPath));
1855 if (g_cchSrcPath == 0)
1856 return ApiError("RTPathEnsureTrailingSeparator", ERROR_BUFFER_OVERFLOW);
1857 }
1858
1859 /*
1860 * Do the installation.
1861 */
1862 rcExit = CheckForGradd();
1863 if (rcExit == RTEXITCODE_SUCCESS)
1864 rcExit = PrepareConfigSys();
1865 if (rcExit == RTEXITCODE_SUCCESS)
1866 rcExit = PrepareStartupCmd();
1867 if (rcExit == RTEXITCODE_SUCCESS)
1868 rcExit = CopyFiles();
1869 if (g_fRealRun)
1870 {
1871 if (rcExit == RTEXITCODE_SUCCESS)
1872 rcExit = WriteConfigSys();
1873 if (rcExit == RTEXITCODE_SUCCESS)
1874 rcExit = WriteStartupCmd();
1875
1876 /*
1877 * Status summary.
1878 */
1879 if (rcExit == RTEXITCODE_SUCCESS)
1880 WriteStrings(g_hStdOut, "info: Installation successful\r\n", NULL);
1881 else
1882 WriteStrings(g_hStdErr, "info: Installation failed!\r\n", NULL);
1883 }
1884 else if (rcExit == RTEXITCODE_SUCCESS)
1885 WriteStrings(g_hStdOut, "info: Trial run successful\r\n", NULL);
1886 else
1887 WriteStrings(g_hStdErr, "info: Trial run failed!\r\n", NULL);
1888 return rcExit;
1889}
1890
1891
1892#if 0 /* Better off with the assembly file here. */
1893/*
1894 * Define the stack.
1895 *
1896 * This \#pragma data_seg(STACK,STACK) thing seems to work a little better for
1897 * 32-bit OS2 binaries than 16-bit. Wlink still thinks it needs to allocate
1898 * zero bytes in the file for the abStack variable, but it doesn't looks like
1899 * both the starting ESP and stack size fields in the LX header are correct.
1900 *
1901 * The problem seems to be that wlink will write anything backed by
1902 * LEDATA32/LIDATA32 even it's all zeros. The C compiler emits either of those
1903 * here, and boom the whole BSS is written to the file.
1904 *
1905 * For 16-bit (see os2_util.c) it would find the stack, but either put the
1906 * correct obj:SP or stack size fields in the NE header, never both and the
1907 * resulting EXE would either not start or crash immediately.
1908 */
1909#pragma data_seg("STACK", "STACK")
1910static uint64_t abStack[4096];
1911#endif
1912
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use