/*++ Copyright (c) 1991 Microsoft Corporation Module Name: parsboot.c Abstract: Parses the boot.ini file, displays a menu, and provides a kernel path and name to be passed to osloader. Author: John Vert (jvert) 22-Jul-1991 Revision History: --*/ #include "bldrx86.h" #include "msg.h" #include "ntdddisk.h" #include #include #include #define MAX_SELECTIONS 10 #define MAX_TITLE_LENGTH 71 #define WIN95_DOS 1 #define DOS_WIN95 2 typedef struct _MENU_OPTION { PCHAR Title; PCHAR Path; BOOLEAN EnableDebug; ULONG MaxMemory; PCHAR LoadOptions; int ForcedScsiOrdinal; int Win95; BOOLEAN HeadlessRedirect; } MENU_OPTION, *PMENU_OPTION; PCHAR pDefSwitches = NULL; int ForcedScsiOrdinal = -1; CHAR szDebug[] = "unsupporteddebug"; CHAR BlankLine[] = " \r"; // // global to hold the user's last // selection from the advanced boot menu. // LONG AdvancedBoot = -1; #define DEBUG_LOAD_OPTION_LENGTH 60 CHAR DebugLoadOptions[DEBUG_LOAD_OPTION_LENGTH]; // // Defines for options for redirecting to a headless terminal // #define COM1_19_2 "com1at19200" #define COM2_19_2 "com2at19200" // // Private function prototypes // VOID BlpRebootDOS( IN PCHAR BootSectorImage OPTIONAL, IN PCHAR LoadOptions OPTIONAL ); PCHAR BlpNextLine( IN PCHAR String ); VOID BlpTranslateDosToArc( IN PCHAR DosName, OUT PCHAR ArcName ); ULONG BlpPresentMenu( IN PMENU_OPTION MenuOptions, IN ULONG NumberSelections, IN ULONG Default, IN LONG Timeout ); PCHAR * BlpFileToLines( IN PCHAR File, OUT PULONG LineCount ); PCHAR * BlpFindSection( IN PCHAR SectionName, IN PCHAR *BootFile, IN ULONG BootFileLines, OUT PULONG NumberLines ); VOID BlpRenameWin95Files( IN ULONG DriveId, IN ULONG Type ); VOID BlParseOsOptions ( IN PMENU_OPTION MenuOption, IN PCHAR pCurrent ); ULONG BlGetAdvancedBootID( LONG BootOption ); PCHAR BlSelectKernel( IN ULONG DriveId, IN PCHAR BootFile, OUT PCHAR *LoadOptions, IN BOOLEAN UseTimeOut ) /*++ Routine Description: Parses the boot.txt file and determines the fully-qualified name of the kernel to be booted. Arguments: BootFile - Pointer to the beginning of the loaded boot.txt file Debugger - Returns the enable/disable state of the kernel debugger UseTimeOut - Supplies whether the boot menu should time out or not. Return Value: Pointer to the name of a kernel to boot. --*/ { PCHAR *MbLines = NULL; PCHAR *OsLines = NULL; PCHAR *FileLines; #if DBG PCHAR *DebugLines = NULL; ULONG DebugLineCount = 0; #endif ULONG FileLineCount; ULONG OsLineCount = 0; ULONG MbLineCount = 0; PCHAR pCurrent; MENU_OPTION MenuOption[MAX_SELECTIONS+1]; ULONG NumberSystems=0; ULONG i; LONG Timeout; ULONG Selection; ULONG DefaultSelection=0; static CHAR Kernel[128]; CHAR DosName[3]; PCHAR DefaultOldPath="C:\\winnt"; PCHAR WinntDir = DefaultOldPath + 2; PCHAR DefaultNewPath="C:\\windows\\"; CHAR DefaultPathBuffer[128] = {0}; PCHAR DefaultPath = DefaultPathBuffer; PCHAR DefaultTitle=BlFindMessage(BL_DEFAULT_TITLE); ULONG DirId; // // Check to see if "winnt" directory exists on the boot // device. If it does not exist then make the default path point // to "windows" directory // if (BlOpen(DriveId, WinntDir, ArcOpenDirectory, &DirId) != ESUCCESS) { strcpy(DefaultPath, DefaultNewPath); } else { BlClose(DirId); strcpy(DefaultPath, DefaultOldPath); strcat(DefaultPath, "\\"); } *LoadOptions = NULL; if (*BootFile == '\0') { // // No boot.ini file, so we boot the default. // BlPrint(BlFindMessage(BL_INVALID_BOOT_INI),DefaultPath); MenuOption[0].Path = DefaultPath; MenuOption[0].Title = DefaultTitle; MenuOption[0].MaxMemory = 0; MenuOption[0].LoadOptions = NULL; MenuOption[0].Win95 = 0; NumberSystems = 1; DefaultSelection = 0; MbLineCount = 0; OsLineCount = 0; MenuOption[0].EnableDebug = FALSE; #if DBG DebugLineCount = 0; #endif } else { FileLines = BlpFileToLines(BootFile, &FileLineCount); MbLines = BlpFindSection("[boot loader]", FileLines, FileLineCount, &MbLineCount); if (MbLines==NULL) { MbLines = BlpFindSection("[flexboot]", FileLines, FileLineCount, &MbLineCount); if (MbLines==NULL) { MbLines = BlpFindSection("[multiboot]", FileLines, FileLineCount, &MbLineCount); } } OsLines = BlpFindSection("[operating systems]", FileLines, FileLineCount, &OsLineCount); if (OsLineCount == 0) { if (BlBootingFromNet) { return NULL; } BlPrint(BlFindMessage(BL_INVALID_BOOT_INI),DefaultPath); MenuOption[0].Path = DefaultPath; MenuOption[0].Title = DefaultTitle; MenuOption[0].MaxMemory = 0; MenuOption[0].LoadOptions = NULL; MenuOption[0].Win95 = 0; MenuOption[0].HeadlessRedirect = FALSE; NumberSystems = 1; DefaultSelection = 0; } #if DBG DebugLines = BlpFindSection("[debug]", FileLines, FileLineCount, &DebugLineCount); #endif } // // Set default timeout value // if (UseTimeOut) { Timeout = 0; } else { Timeout = -1; } // // Before we look through the [boot loader] section, initialize // our headless redirection information so that the default is // to not redirect. // RtlZeroMemory( &LoaderRedirectionInformation, sizeof(HEADLESS_LOADER_BLOCK) ); BlTerminalConnected = FALSE; // // Parse the [boot loader] section // for (i=0; iForcedScsiOrdinal = -1; MenuOption->MaxMemory = 0; MenuOption->LoadOptions = NULL; MenuOption->Win95 = 0; MenuOption->EnableDebug = FALSE; MenuOption->HeadlessRedirect = FALSE; // If there are no switches specified for this line, use the DefSwitches if ((strchr(pCurrent,'/') == NULL) && (pDefSwitches)) { pCurrent = pDefSwitches; } // // Convert to all one case // _strupr(pCurrent); // // Look for a scsi(x) ordinal to use for opens on scsi ARC paths. // This spec must immediately follow the title and is not part // of the load options. // p = strstr(pCurrent,"/SCSIORDINAL:"); if(p) { MenuOption->ForcedScsiOrdinal = atoi(p + sizeof("/SCSIORDINAL:") - 1); } // // If there is a REDIRECT parameter after the description, then // we need to pass this to the osloader. // p = strstr(pCurrent,"/REDIRECT"); if(p) { MenuOption->HeadlessRedirect = TRUE; } // // If there is a DEBUG parameter after the description, then // we need to pass the DEBUG option to the osloader. // if (strchr(pCurrent,'/') != NULL) { pCurrent = strchr(pCurrent+1,'/'); MenuOption->LoadOptions = pCurrent; if (pCurrent != NULL) { p = strstr(pCurrent,"/MAXMEM"); if (p) { MenuOption->MaxMemory = atoi(p+8); } if (strstr(pCurrent, "/WIN95DOS")) { MenuOption->Win95 = WIN95_DOS; } else if (strstr(pCurrent, "/WIN95")) { MenuOption->Win95 = DOS_WIN95; } // // As long as /nodebug or /crashdebug is specified, this is NO debug system // If /NODEBUG is not specified, and either one of the // DEBUG or BAUDRATE is specified, this is debug system. // if ((strstr(pCurrent, "NODEBUG") == NULL) && (strstr(pCurrent, "CRASHDEBUG") == NULL)) { if (strstr(pCurrent, "DEBUG") || strstr(pCurrent, "BAUDRATE")) { if (_stricmp(MenuOption->Path, "C:\\")) { MenuOption->EnableDebug = TRUE; } } } } } } PCHAR * BlpFileToLines( IN PCHAR File, OUT PULONG LineCount ) /*++ Routine Description: This routine converts the loaded BOOT.INI file into an array of pointers to NULL-terminated ASCII strings. Arguments: File - supplies a pointer to the in-memory image of the BOOT.INI file. This will be converted in place by turning CR/LF pairs into null terminators. LineCount - Returns the number of lines in the BOOT.INI file. Return Value: A pointer to an array of pointers to ASCIIZ strings. The array will have LineCount elements. NULL if the function did not succeed for some reason. --*/ { ULONG Line; PCHAR *LineArray; PCHAR p; PCHAR Space; p = File; // // First count the number of lines in the file so we know how large // an array to allocate. // *LineCount=1; while (*p != '\0') { p=strchr(p, '\n'); if (p==NULL) { break; } ++p; // // See if there's any text following the CR/LF. // if (*p=='\0') { break; } *LineCount += 1; } LineArray = BlAllocateHeap(*LineCount * sizeof(PCHAR)); // // Now step through the file again, replacing CR/LF with \0\0 and // filling in the array of pointers. // p=File; for (Line=0; Line < *LineCount; Line++) { LineArray[Line] = p; p=strchr(p, '\r'); if (p != NULL) { *p = '\0'; ++p; if (*p=='\n') { *p = '\0'; ++p; } } else { p=strchr(LineArray[Line], '\n'); if (p != NULL) { *p = '\0'; ++p; } } // // remove trailing white space // Space = LineArray[Line] + strlen(LineArray[Line])-1; while ((*Space == ' ') || (*Space == '\t')) { *Space = '\0'; --Space; } } return(LineArray); } PCHAR * BlpFindSection( IN PCHAR SectionName, IN PCHAR *BootFile, IN ULONG BootFileLines, OUT PULONG NumberLines ) /*++ Routine Description: Finds a section ([multiboot], [operating systems], etc) in the boot.ini file and returns a pointer to its first line. The search will be case-insensitive. Arguments: SectionName - Supplies the name of the section. No brackets. BootFile - Supplies the array of pointers to lines of the ini file. BootFileLines - Supplies the number of lines in the ini file. NumberLines - Returns the number of lines in the section. Return Value: Pointer to an array of ASCIIZ strings, one entry per line. NULL, if the section was not found. --*/ { ULONG cnt; ULONG StartLine; for (cnt=0; cnt MAX_TITLE_LENGTH ) { MenuOption[i].Title[MAX_TITLE_LENGTH - 1] = '\0'; } if (MenuOption[i].EnableDebug == TRUE || MenuOption[i].Win95 != 0 || _stricmp(MenuOption[i].Path, "C:\\") == 0) { AllowNewOptions = TRUE; } } Selection = Default; CurrentTime = StartTime = GET_COUNTER(); EndTime = StartTime + (Timeout * 182) / 10; pDebug = szDebug; DebugSelect = NULL; ResetDisplay = TRUE; Moved = TRUE; BlankLineDrawn = FALSE; do { if (ResetDisplay) { ARC_DISPLAY_ATTRIBUTES_OFF(); ARC_DISPLAY_CLEAR(); // ARC_DISPLAY_POSITION_CURSOR(0, 0); // BlPrint(OsLoaderVersion); ARC_DISPLAY_POSITION_CURSOR(0, 23); if (AdvancedBoot != -1) { ARC_DISPLAY_SET_COLOR("1;34"); // high-intensity red BlPrint(BlGetAdvancedBootDisplayString(AdvancedBoot)); ARC_DISPLAY_ATTRIBUTES_OFF(); } else { ARC_DISPLAY_CLEAR_TO_EOL(); } ARC_DISPLAY_POSITION_CURSOR(0, 21); BlPrint(AdvancedBootMessage); ARC_DISPLAY_POSITION_CURSOR(0, 2); BlPrint(SelectOs); ResetDisplay = FALSE; ARC_DISPLAY_POSITION_CURSOR(0, 5+NumberSelections-1); BlPrint(MoveHighlight); } if(Moved) { for (i=0; i DEBUG_LOAD_OPTION_LENGTH-1) { i = DEBUG_LOAD_OPTION_LENGTH-1; } memcpy (DebugLoadOptions, MenuOption[Selection].LoadOptions, i); } BlPrint ( DebugSelect, MenuOption[Selection].Title, MenuOption[Selection].Path, DebugLoadOptions ); } Moved = FALSE; } if (!DebugSelect) { if (Timeout != -1) { LastTime = CurrentTime; CurrentTime = GET_COUNTER(); // // deal with wraparound at midnight // We can't do it the easy way because there are not exactly // 18.2 * 60 * 60 * 24 tics/day. (just approximately) // if (CurrentTime < StartTime) { if (BiasTime == 0) { BiasTime = LastTime + 1; } CurrentTime += BiasTime; } SecondsLeft = ((LONG)(EndTime - CurrentTime) * 10) / 182; if (SecondsLeft < 0) { // // Note that if the user hits the PAUSE key, the counter stops // and, as a result, SecondsLeft can become < 0. // SecondsLeft = 0; } if (SecondsLeft != LastSecondsLeft) { ARC_DISPLAY_POSITION_CURSOR(0, 5+NumberSelections-1); BlPrint(MoveHighlight); BlPrint(TimeoutCountdown); BlPrint(" %d \n",SecondsLeft); LastSecondsLeft = SecondsLeft; } } else if (!BlankLineDrawn) { BlankLineDrawn = TRUE; ARC_DISPLAY_POSITION_CURSOR(0, 5+NumberSelections-1); BlPrint(MoveHighlight); BlPrint(BlankLine); } } // // Poll for a key. // Key = BlGetKey(); if (Key) { // // Any key stops timeout // Timeout = -1; // // Check for debug string // if ((UCHAR) Key == *pDebug) { pDebug++; if (!*pDebug) { Moved = TRUE; DebugSelect = BlFindMessage(BL_DEBUG_SELECT_OS); SelectOs = DebugSelect; } } else { pDebug = szDebug; } } #if defined(ENABLE_LOADER_DEBUG) || DBG // // for debugging only. // lets you break into the debugger // with the F10 key. // if (Key == F10_KEY) { extern LOGICAL BdDebuggerEnabled; if (BdDebuggerEnabled == TRUE) { DbgBreakPoint(); } } #endif // // check for advanced boot options // if (Key == F8_KEY || Key == F5_KEY) { AdvancedBoot = BlDoAdvancedBoot( BL_ADVANCEDBOOT_TITLE, AdvancedBoot, FALSE, 0 ); #if 0 if ((AdvancedBoot != -1) && (BlGetAdvancedBootID(AdvancedBoot) == BL_MSG_BOOT_NORMALLY)) { AdvancedBoot = -1; // break; // the current selection need to be booted (normally) } #endif if ((AdvancedBoot != -1) && (BlGetAdvancedBootID(AdvancedBoot) == BL_MSG_REBOOT)) { BlClearScreen(); ArcReboot(); } ResetDisplay = TRUE; Moved = TRUE; } else // // Check for selection // if ( (Key==UP_ARROW) || (Key==DOWN_ARROW) || (Key==HOME_KEY) || (Key==END_KEY) ) { Moved = TRUE; ARC_DISPLAY_POSITION_CURSOR(0, 5+Selection); ARC_DISPLAY_ATTRIBUTES_OFF(); BlPrint( " %s", MenuOption[Selection].Title); if (Key==DOWN_ARROW) { Selection = (Selection+1) % NumberSelections; } else if (Key==UP_ARROW) { Selection = (Selection == 0) ? (NumberSelections-1) : (Selection - 1); } else if (Key==HOME_KEY) { Selection = 0; } else if (Key==END_KEY) { Selection = NumberSelections-1; } } } while ( ((Key&(ULONG)0xff) != ENTER_KEY) && ((CurrentTime < EndTime) || (Timeout == -1)) ); // // If debugging, prompt the user for new load options // if (DebugSelect && AllowNewOptions) { ARC_DISPLAY_CLEAR(); ARC_DISPLAY_POSITION_CURSOR(0, 2); ARC_DISPLAY_CLEAR_TO_EOD(); BlPrint ( DebugSelect, MenuOption[Selection].Title, MenuOption[Selection].Path, DebugLoadOptions ); BlInputString ( BL_DEBUG_NEW_OPTIONS, 0, 7, (PUCHAR)DebugLoadOptions, DEBUG_LOAD_OPTION_LENGTH - 1 ); BlParseOsOptions ( &MenuOption[Selection], DebugLoadOptions ); } return(Selection); } ARC_STATUS BlpRenameWin95SystemFile( IN ULONG DriveId, IN ULONG Type, IN PCHAR FileName, IN PCHAR Ext, IN PCHAR NewName ) /*++ Routine Description: Renames a file from one name to another. Arguments: DriveId - Open drive identifier Type - WIN95_DOS or DOS_WIN95 FileName - Base file name Ext - Base extension NewName - Non-NULL value causes an override of a generated name Return Value: Arc status of the failed opperation or E_SUCCESS. --*/ { ARC_STATUS Status; ULONG FileId; ULONG FileIdCur; CHAR Fname[16]; CHAR FnameCur[16]; CHAR FnameNew[16]; if (Type == WIN95_DOS) { sprintf( Fname, "%s.dos", FileName ); } else { if (NewName) { strcpy( Fname, NewName ); } else { sprintf( Fname, "%s.w40", FileName ); } } Status = BlOpen( DriveId, Fname, ArcOpenReadOnly, &FileId ); if (Status != ESUCCESS) { return Status; } sprintf( FnameCur, "%s.%s", FileName, Ext ); Status = BlOpen( DriveId, FnameCur, ArcOpenReadOnly, &FileIdCur ); if (Status != ESUCCESS) { BlClose( FileId ); return Status; } if (Type == WIN95_DOS) { if (NewName) { strcpy( FnameNew, NewName ); } else { sprintf( FnameNew, "%s.w40", FileName ); } } else { sprintf( FnameNew, "%s.dos", FileName ); } Status = BlRename( FileIdCur, FnameNew ); BlClose( FileIdCur ); if (Status != ESUCCESS) { BlClose( FileId ); return Status; } Status = BlRename( FileId, FnameCur ); BlClose( FileId ); return Status; } VOID BlpRenameWin95Files( IN ULONG DriveId, IN ULONG Type ) /*++ Routine Description: Renames all Windows 95 system files from either their Win95 DOS names to their Win95 name or the reverse. Arguments: DriveId - Open drive identifier Type - 1=dos to win95, 2=win95 to dos Return Value: None. --*/ { BlpRenameWin95SystemFile( DriveId, Type, "command", "com", NULL ); BlpRenameWin95SystemFile( DriveId, Type, "msdos", "sys", NULL ); BlpRenameWin95SystemFile( DriveId, Type, "io", "sys", "winboot.sys" ); BlpRenameWin95SystemFile( DriveId, Type, "autoexec", "bat", NULL ); BlpRenameWin95SystemFile( DriveId, Type, "config", "sys", NULL ); } ULONG BlGetAdvancedBootOption( VOID ) { return AdvancedBoot; }