]> code.delx.au - refind/blobdiff - refind/config.c
Can now specify complete paths, optionally including volumes, in
[refind] / refind / config.c
index 30b3c1ff94ac8806854bf0cab68781bdff7cc089..c56c54f1332d3e14fd028a24397cd02754ac4e9d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * refit/config.c
+ * refind/config.c
  * Configuration file functions
  *
  * Copyright (c) 2006 Christoph Pfisterer
@@ -60,6 +60,9 @@
 #define ENCODING_UTF8       (1)
 #define ENCODING_UTF16_LE   (2)
 
+#define GetTime ST->RuntimeServices->GetTime
+#define LAST_MINUTE 1439 /* Last minute of a day */
+
 static REFIT_MENU_ENTRY MenuEntryReturn   = { L"Return to Main Menu", TAG_RETURN, 0, 0, 0, NULL, NULL, NULL };
 
 //
@@ -305,8 +308,12 @@ VOID FreeTokenLine(IN OUT CHAR16 ***TokenList, IN OUT UINTN *TokenCount)
 // handle a parameter with a single integer argument
 static VOID HandleInt(IN CHAR16 **TokenList, IN UINTN TokenCount, OUT UINTN *Value)
 {
-    if (TokenCount == 2)
-       *Value = Atoi(TokenList[1]);
+    if (TokenCount == 2) {
+       if (StriCmp(TokenList[1], L"-1") == 0)
+          *Value = -1;
+       else
+          *Value = Atoi(TokenList[1]);
+    }
 }
 
 // handle a parameter with a single string argument
@@ -317,22 +324,91 @@ static VOID HandleString(IN CHAR16 **TokenList, IN UINTN TokenCount, OUT CHAR16
    } // if
 } // static VOID HandleString()
 
-// Handle a parameter with a series of string arguments, to be added to a comma-delimited
-// list. Passes each token through the CleanUpPathNameSlashes() function to ensure
-// consistency in subsequent comparisons of filenames.
+// Handle a parameter with a series of string arguments, to replace or be added to a
+// comma-delimited list. Passes each token through the CleanUpPathNameSlashes() function
+// to ensure consistency in subsequent comparisons of filenames. If the first
+// non-keyword token is "+", the list is added to the existing target string; otherwise,
+// the tokens replace the current string.
 static VOID HandleStrings(IN CHAR16 **TokenList, IN UINTN TokenCount, OUT CHAR16 **Target) {
    UINTN i;
+   BOOLEAN AddMode = FALSE;
 
-   if (*Target != NULL) {
+   if ((TokenCount > 2) && (StriCmp(TokenList[1], L"+") == 0)) {
+      AddMode = TRUE;
+   }
+
+   if ((*Target != NULL) && !AddMode) {
       FreePool(*Target);
       *Target = NULL;
    } // if
    for (i = 1; i < TokenCount; i++) {
-      CleanUpPathNameSlashes(TokenList[i]);
-      MergeStrings(Target, TokenList[i], L',');
-   }
+      if ((i != 1) || !AddMode) {
+         CleanUpPathNameSlashes(TokenList[i]);
+         MergeStrings(Target, TokenList[i], L',');
+      } // if
+   } // for
 } // static VOID HandleStrings()
 
+// Convert TimeString (in "HH:MM" format) to a pure-minute format. Values should be
+// in the range from 0 (for 00:00, or midnight) to 1439 (for 23:59; aka LAST_MINUTE).
+// Any value outside that range denotes an error in the specification. Note that if
+// the input is a number that includes no colon, this function will return the original
+// number in UINTN form.
+static UINTN HandleTime(IN CHAR16 *TimeString) {
+   UINTN Hour = 0, Minute = 0, TimeLength, i = 0;
+
+   TimeLength = StrLen(TimeString);
+   while (i < TimeLength) {
+      if (TimeString[i] == L':') {
+         Hour = Minute;
+         Minute = 0;
+      } // if
+      if ((TimeString[i] >= L'0') && (TimeString[i] <= '9')) {
+         Minute *= 10;
+         Minute += (TimeString[i] - L'0');
+      } // if
+      i++;
+   } // while
+   return (Hour * 60 + Minute);
+} // BOOLEAN HandleTime()
+
+// Sets the default boot loader IF the current time is within the bounds
+// defined by the third and fourth tokens in the TokenList.
+static VOID SetDefaultByTime(IN CHAR16 **TokenList, OUT CHAR16 **Default) {
+   EFI_STATUS            Status;
+   EFI_TIME              CurrentTime;
+   UINTN                 StartTime = LAST_MINUTE + 1, EndTime = LAST_MINUTE + 1, Now;
+   BOOLEAN               SetIt = FALSE;
+
+   StartTime = HandleTime(TokenList[2]);
+   EndTime = HandleTime(TokenList[3]);
+
+   if ((StartTime <= LAST_MINUTE) && (EndTime <= LAST_MINUTE)) {
+      Status = refit_call2_wrapper(GetTime, &CurrentTime, NULL);
+      if (Status != EFI_SUCCESS)
+         return;
+      Now = CurrentTime.Hour * 60 + CurrentTime.Minute;
+
+      if (Now > LAST_MINUTE) { // Shouldn't happen; just being paranoid
+         Print(L"Warning: Impossible system time: %d:%d\n", CurrentTime.Hour, CurrentTime.Minute);
+         return;
+      } // if impossible time
+
+      if (StartTime < EndTime) { // Time range does NOT cross midnight
+         if ((Now >= StartTime) && (Now <= EndTime))
+            SetIt = TRUE;
+      } else { // Time range DOES cross midnight
+         if ((Now >= StartTime) && (Now <= EndTime))
+            SetIt = TRUE;
+      } // if/else time range crosses midnight
+
+      if (SetIt) {
+         MyFreePool(*Default);
+         *Default = StrDuplicate(TokenList[1]);
+      } // if (SetIt)
+   } // if ((StartTime <= LAST_MINUTE) && (EndTime <= LAST_MINUTE))
+} // VOID SetDefaultByTime()
+
 // read config file
 VOID ReadConfig(CHAR16 *FileName)
 {
@@ -340,30 +416,32 @@ VOID ReadConfig(CHAR16 *FileName)
     REFIT_FILE      File;
     CHAR16          **TokenList;
     CHAR16          *FlagName;
-    CHAR16          *SelfPath = NULL;
+    CHAR16          *TempStr = NULL;
     UINTN           TokenCount, i;
 
     // Set a few defaults only if we're loading the default file.
-    if (StriCmp(FileName, CONFIG_FILE_NAME) == 0) {
+    if (StriCmp(FileName, GlobalConfig.ConfigFilename) == 0) {
        MyFreePool(GlobalConfig.AlsoScan);
        GlobalConfig.AlsoScan = StrDuplicate(ALSO_SCAN_DIRS);
        MyFreePool(GlobalConfig.DontScanDirs);
        if (SelfVolume) {
           if (SelfVolume->VolName) {
-             SelfPath = SelfVolume->VolName ? StrDuplicate(SelfVolume->VolName) : NULL;
+             TempStr = SelfVolume->VolName ? StrDuplicate(SelfVolume->VolName) : NULL;
           } else {
-             SelfPath = AllocateZeroPool(256 * sizeof(CHAR16));
-             if (SelfPath != NULL)
-                SPrint(SelfPath, 255, L"fs%d", SelfVolume->VolNumber);
+             TempStr = AllocateZeroPool(256 * sizeof(CHAR16));
+             if (TempStr != NULL)
+                SPrint(TempStr, 255, L"fs%d", SelfVolume->VolNumber);
           } // if/else
        }
-       MergeStrings(&SelfPath, SelfDirPath, L':');
-       GlobalConfig.DontScanDirs = SelfPath;
+       MergeStrings(&TempStr, SelfDirPath, L':');
+       MergeStrings(&TempStr, MEMTEST_LOCATIONS, L',');
+       GlobalConfig.DontScanDirs = TempStr;
        MyFreePool(GlobalConfig.DontScanFiles);
        GlobalConfig.DontScanFiles = StrDuplicate(DONT_SCAN_FILES);
        MergeStrings(&(GlobalConfig.DontScanFiles), MOK_NAMES, L',');
        MyFreePool(GlobalConfig.DontScanVolumes);
        GlobalConfig.DontScanVolumes = StrDuplicate(DONT_SCAN_VOLUMES);
+       GlobalConfig.WindowsRecoveryFiles = StrDuplicate(WINDOWS_RECOVERY_FILES);
     } // if
 
     if (!FileExists(SelfDir, FileName)) {
@@ -440,6 +518,9 @@ VOID ReadConfig(CHAR16 *FileName)
         } else if ((StriCmp(TokenList[0], L"don't_scan_files") == 0) || (StriCmp(TokenList[0], L"dont_scan_files") == 0)) {
            HandleStrings(TokenList, TokenCount, &(GlobalConfig.DontScanFiles));
 
+        } else if (StriCmp(TokenList[0], L"windows_recovery_files") == 0) {
+           HandleStrings(TokenList, TokenCount, &(GlobalConfig.WindowsRecoveryFiles));
+
         } else if (StriCmp(TokenList[0], L"scan_driver_dirs") == 0) {
             HandleStrings(TokenList, TokenCount, &(GlobalConfig.DriverDirs));
 
@@ -461,10 +542,14 @@ VOID ReadConfig(CHAR16 *FileName)
                    GlobalConfig.ShowTools[i - 1] = TAG_SHUTDOWN;
                 } else if (StriCmp(FlagName, L"apple_recovery") == 0) {
                    GlobalConfig.ShowTools[i - 1] = TAG_APPLE_RECOVERY;
+                } else if (StriCmp(FlagName, L"windows_recovery") == 0) {
+                   GlobalConfig.ShowTools[i - 1] = TAG_WINDOWS_RECOVERY;
                 } else if (StriCmp(FlagName, L"mok_tool") == 0) {
                    GlobalConfig.ShowTools[i - 1] = TAG_MOK_TOOL;
                 } else if (StriCmp(FlagName, L"firmware") == 0) {
                    GlobalConfig.ShowTools[i - 1] = TAG_FIRMWARE;
+                } else if ((StriCmp(FlagName, L"memtest86") == 0) || (StriCmp(FlagName, L"memtest") == 0)) {
+                   GlobalConfig.ShowTools[i - 1] = TAG_MEMTEST;
                 } else {
                    Print(L" unknown showtools flag: '%s'\n", FlagName);
                 }
@@ -480,7 +565,11 @@ VOID ReadConfig(CHAR16 *FileName)
            HandleString(TokenList, TokenCount, &(GlobalConfig.SelectionBigFileName));
 
         } else if (StriCmp(TokenList[0], L"default_selection") == 0) {
-           HandleString(TokenList, TokenCount, &(GlobalConfig.DefaultSelection));
+           if (TokenCount == 4) {
+              SetDefaultByTime(TokenList, &(GlobalConfig.DefaultSelection));
+           } else {
+              HandleString(TokenList, TokenCount, &(GlobalConfig.DefaultSelection));
+           }
 
         } else if (StriCmp(TokenList[0], L"textonly") == 0) {
            if ((TokenCount >= 2) && (StriCmp(TokenList[1], L"0") == 0)) {
@@ -503,7 +592,8 @@ VOID ReadConfig(CHAR16 *FileName)
            HandleInt(TokenList, TokenCount, &(GlobalConfig.ScreensaverTime));
 
         } else if (StriCmp(TokenList[0], L"use_graphics_for") == 0) {
-           GlobalConfig.GraphicsFor = 0;
+           if ((TokenCount == 2) || ((TokenCount > 2) && (StriCmp(TokenList[1], L"+") != 0)))
+              GlobalConfig.GraphicsFor = 0;
            for (i = 1; i < TokenCount; i++) {
               if (StriCmp(TokenList[i], L"osx") == 0) {
                  GlobalConfig.GraphicsFor |= GRAPHICS_FOR_OSX;
@@ -531,7 +621,8 @@ VOID ReadConfig(CHAR16 *FileName)
         } else if (StriCmp(TokenList[0], L"max_tags") == 0) {
            HandleInt(TokenList, TokenCount, &(GlobalConfig.MaxTags));
 
-        } else if ((StriCmp(TokenList[0], L"include") == 0) && (TokenCount == 2) && (StriCmp(FileName, CONFIG_FILE_NAME) == 0)) {
+        } else if ((StriCmp(TokenList[0], L"include") == 0) && (TokenCount == 2) &&
+                   (StriCmp(FileName, GlobalConfig.ConfigFilename) == 0)) {
            if (StriCmp(TokenList[1], FileName) != 0) {
               ReadConfig(TokenList[1]);
            }
@@ -540,6 +631,8 @@ VOID ReadConfig(CHAR16 *FileName)
 
         FreeTokenLine(&TokenList, &TokenCount);
     }
+    if ((GlobalConfig.DontScanFiles) && (GlobalConfig.WindowsRecoveryFiles))
+       MergeStrings(&(GlobalConfig.DontScanFiles), GlobalConfig.WindowsRecoveryFiles, L',');
     MyFreePool(File.Buffer);
 } /* VOID ReadConfig() */
 
@@ -770,7 +863,8 @@ VOID ScanUserConfigured(CHAR16 *FileName)
             } // if/else
             MyFreePool(Title);
 
-         } else if ((StriCmp(TokenList[0], L"include") == 0) && (TokenCount == 2) && (StriCmp(FileName, CONFIG_FILE_NAME) == 0)) {
+         } else if ((StriCmp(TokenList[0], L"include") == 0) && (TokenCount == 2) &&
+                    (StriCmp(FileName, GlobalConfig.ConfigFilename) == 0)) {
             if (StriCmp(TokenList[1], FileName) != 0) {
                ScanUserConfigured(TokenList[1]);
             }
@@ -781,6 +875,65 @@ VOID ScanUserConfigured(CHAR16 *FileName)
    } // if()
 } // VOID ScanUserConfigured()
 
+// Create an options file based on /etc/fstab. The resulting file has two options
+// lines, one of which boots the system with "ro root={rootfs}" and the other of
+// which boots the system with "ro root={rootfs} single", where "{rootfs}" is the
+// filesystem identifier associated with the "/" line in /etc/fstab.
+static REFIT_FILE * GenerateOptionsFromEtcFstab(REFIT_VOLUME *Volume) {
+   UINTN        TokenCount, i;
+   REFIT_FILE   *Options = NULL, *Fstab = NULL;
+   EFI_STATUS   Status;
+   CHAR16       **TokenList, *Line, Root[100];
+
+   if (FileExists(Volume->RootDir, L"\\etc\\fstab")) {
+      Options = AllocateZeroPool(sizeof(REFIT_FILE));
+      Fstab = AllocateZeroPool(sizeof(REFIT_FILE));
+      Status = ReadFile(Volume->RootDir, L"\\etc\\fstab", Fstab, &i);
+      if (CheckError(Status, L"while reading /etc/fstab")) {
+         if (Options != NULL)
+            FreePool(Options);
+         if (Fstab != NULL)
+            FreePool(Fstab);
+         Options = NULL;
+         Fstab = NULL;
+      } else { // File read; locate root fs and create entries
+         Options->Encoding = ENCODING_UTF16_LE;
+         while ((TokenCount = ReadTokenLine(Fstab, &TokenList)) > 0) {
+            if (TokenCount > 2) {
+               Root[0] = '\0';
+               if (StriCmp(TokenList[1], L"\\") == 0) {
+                  SPrint(Root, 99, L"%s", TokenList[0]);
+               } else if (StriCmp(TokenList[2], L"\\") == 0) {
+                  SPrint(Root, 99, L"%s=%s", TokenList[0], TokenList[1]);
+               } // if/elseif/elseif
+               if (Root[0] != L'\0') {
+                  for (i = 0; i < StrLen(Root); i++)
+                     if (Root[i] == '\\')
+                        Root[i] = '/';
+                  Line = PoolPrint(L"\"Boot with normal options\"    \"ro root=%s\"\n", Root);
+                  MergeStrings((CHAR16 **) &(Options->Buffer), Line, 0);
+                  MyFreePool(Line);
+                  Line = PoolPrint(L"\"Boot into single-user mode\"  \"ro root=%s single\"\n", Root);
+                  MergeStrings((CHAR16**) &(Options->Buffer), Line, 0);
+                  Options->BufferSize = StrLen((CHAR16*) Options->Buffer) * sizeof(CHAR16);
+               } // if
+            } // if
+            FreeTokenLine(&TokenList, &TokenCount);
+         } // while
+
+         Options->Current8Ptr  = (CHAR8 *)Options->Buffer;
+         Options->End8Ptr      = Options->Current8Ptr + Options->BufferSize;
+         Options->Current16Ptr = (CHAR16 *)Options->Buffer;
+         Options->End16Ptr     = Options->Current16Ptr + (Options->BufferSize >> 1);
+
+         MyFreePool(Fstab->Buffer);
+         MyFreePool(Fstab);
+      } // if/else file read error
+   } // if /etc/fstab exists
+   return Options;
+} // GenerateOptionsFromEtcFstab()
+
+
 // Read a Linux kernel options file for a Linux boot loader into memory. The LoaderPath
 // and Volume variables identify the location of the options file, but not its name --
 // you pass this function the filename of the Linux kernel, initial RAM disk, or other
@@ -791,12 +944,15 @@ VOID ScanUserConfigured(CHAR16 *FileName)
 // kernel developers decided to use that name for a similar purpose, but with a
 // different file format. Thus, I'm migrating rEFInd to use the name refind_linux.conf,
 // but I want a migration period in which both names are used.
+// If a rEFInd options file can't be found, try to generate minimal options from
+// /etc/fstab on the same volume as the kernel. This typically works only if the
+// kernel is being read from the Linux root filesystem.
 //
 // The return value is a pointer to the REFIT_FILE handle for the file, or NULL if
 // it wasn't found.
 REFIT_FILE * ReadLinuxOptionsFile(IN CHAR16 *LoaderPath, IN REFIT_VOLUME *Volume) {
    CHAR16       *OptionsFilename, *FullFilename;
-   BOOLEAN      GoOn = TRUE;
+   BOOLEAN      GoOn = TRUE, FileFound = FALSE;
    UINTN        i = 0, size;
    REFIT_FILE   *File = NULL;
    EFI_STATUS   Status;
@@ -809,17 +965,20 @@ REFIT_FILE * ReadLinuxOptionsFile(IN CHAR16 *LoaderPath, IN REFIT_VOLUME *Volume
          if (FileExists(Volume->RootDir, FullFilename)) {
             File = AllocateZeroPool(sizeof(REFIT_FILE));
             Status = ReadFile(Volume->RootDir, FullFilename, File, &size);
-            GoOn = FALSE;
             if (CheckError(Status, L"while loading the Linux options file")) {
                if (File != NULL)
                   FreePool(File);
                File = NULL;
-               GoOn = TRUE;
-            } // if error
+            } else {
+               GoOn = FALSE;
+               FileFound = TRUE;
+            } // if/else error
          } // if file exists
       } else { // a filename string is NULL
          GoOn = FALSE;
       } // if/else
+      if (!FileFound)
+         File = GenerateOptionsFromEtcFstab(Volume);
       MyFreePool(OptionsFilename);
       MyFreePool(FullFilename);
       OptionsFilename = FullFilename = NULL;