VirtualBox

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

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

Add/os2: Some simplifications to the installer.

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

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