VirtualBox

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

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

Add/os2: Made the installer start in a --dry-run mode and require a -i/--do-install parameter before it actually does anything. Fixed sharing violation issue clearing the readonly bit.

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

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