Home | History | Annotate | Download | only in Ebl
      1 /** @file
      2   Basic command line parser for EBL (Embedded Boot Loader)
      3 
      4   Copyright (c) 2007, Intel Corporation. All rights reserved.<BR>
      5   Portions copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>
      6   (C) Copyright 2015 Hewlett Packard Enterprise Development LP<BR>
      7 
      8   This program and the accompanying materials
      9   are licensed and made available under the terms and conditions of the BSD License
     10   which accompanies this distribution.  The full text of the license may be found at
     11   http://opensource.org/licenses/bsd-license.php
     12 
     13   THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
     14   WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
     15 
     16 
     17 **/
     18 
     19 #include "Ebl.h"
     20 
     21 // Globals for command history processing
     22 INTN mCmdHistoryEnd     = -1;
     23 INTN mCmdHistoryStart   = -1;
     24 INTN mCmdHistoryCurrent = -1;
     25 CHAR8 mCmdHistory[MAX_CMD_HISTORY][MAX_CMD_LINE];
     26 CHAR8 *mCmdBlank = "";
     27 
     28 // Globals to remember current screen geometry
     29 UINTN gScreenColumns;
     30 UINTN gScreenRows;
     31 
     32 // Global to turn on/off breaking commands with prompts before they scroll the screen
     33 BOOLEAN gPageBreak = TRUE;
     34 
     35 VOID
     36 RingBufferIncrement (
     37   IN  INTN  *Value
     38   )
     39 {
     40   *Value = *Value + 1;
     41 
     42   if (*Value >= MAX_CMD_HISTORY) {
     43     *Value = 0;
     44   }
     45 }
     46 
     47 VOID
     48 RingBufferDecrement (
     49   IN  INTN  *Value
     50   )
     51 {
     52   *Value = *Value - 1;
     53 
     54   if (*Value < 0) {
     55     *Value = MAX_CMD_HISTORY - 1;
     56   }
     57 }
     58 
     59 /**
     60   Save this command in the circular history buffer. Older commands are
     61   overwritten with newer commands.
     62 
     63   @param  Cmd   Command line to archive the history of.
     64 
     65   @return None
     66 
     67 **/
     68 VOID
     69 SetCmdHistory (
     70   IN  CHAR8 *Cmd
     71   )
     72 {
     73   // Don't bother adding empty commands to the list
     74   if (AsciiStrLen(Cmd) != 0) {
     75 
     76     // First entry
     77     if (mCmdHistoryStart == -1) {
     78       mCmdHistoryStart   = 0;
     79       mCmdHistoryEnd     = 0;
     80     } else {
     81       // Record the new command at the next index
     82       RingBufferIncrement(&mCmdHistoryStart);
     83 
     84       // If the next index runs into the end index, shuffle end back by one
     85       if (mCmdHistoryStart == mCmdHistoryEnd) {
     86         RingBufferIncrement(&mCmdHistoryEnd);
     87       }
     88     }
     89 
     90     // Copy the new command line into the ring buffer
     91     AsciiStrnCpyS (&mCmdHistory[mCmdHistoryStart][0], MAX_CMD_LINE, Cmd, MAX_CMD_LINE);
     92   }
     93 
     94   // Reset the command history for the next up arrow press
     95   mCmdHistoryCurrent = mCmdHistoryStart;
     96 }
     97 
     98 
     99 /**
    100   Retreave data from the Command History buffer. Direction maps into up arrow
    101   an down arrow on the command line
    102 
    103   @param  Direction  Command forward or back
    104 
    105   @return The Command history based on the Direction
    106 
    107 **/
    108 CHAR8 *
    109 GetCmdHistory (
    110   IN UINT16   Direction
    111   )
    112 {
    113   CHAR8 *HistoricalCommand = NULL;
    114 
    115   // No history yet?
    116   if (mCmdHistoryCurrent == -1) {
    117     HistoricalCommand = mCmdBlank;
    118     goto Exit;
    119   }
    120 
    121   if (Direction == SCAN_UP) {
    122     HistoricalCommand = &mCmdHistory[mCmdHistoryCurrent][0];
    123 
    124     // if we just echoed the last command, hang out there, don't wrap around
    125     if (mCmdHistoryCurrent == mCmdHistoryEnd) {
    126       goto Exit;
    127     }
    128 
    129     // otherwise, back up by one
    130     RingBufferDecrement(&mCmdHistoryCurrent);
    131 
    132   } else if (Direction == SCAN_DOWN) {
    133 
    134     // if we last echoed the start command, put a blank prompt out
    135     if (mCmdHistoryCurrent == mCmdHistoryStart) {
    136       HistoricalCommand = mCmdBlank;
    137       goto Exit;
    138     }
    139 
    140     // otherwise increment the current pointer and return that command
    141     RingBufferIncrement(&mCmdHistoryCurrent);
    142     RingBufferIncrement(&mCmdHistoryCurrent);
    143 
    144     HistoricalCommand = &mCmdHistory[mCmdHistoryCurrent][0];
    145     RingBufferDecrement(&mCmdHistoryCurrent);
    146   }
    147 
    148 Exit:
    149   return HistoricalCommand;
    150 }
    151 
    152 
    153 /**
    154   Parse the CmdLine and break it up into Argc (arg count) and Argv (array of
    155   pointers to each argument). The Cmd buffer is altered and separators are
    156   converted to string terminators. This allows Argv to point into CmdLine.
    157   A CmdLine can support multiple commands. The next command in the command line
    158   is returned if it exists.
    159 
    160   @param  CmdLine String to parse for a set of commands
    161   @param  Argc    Returns the number of arguments in the CmdLine current command
    162   @param  Argv    Argc pointers to each string in CmdLine
    163 
    164   @return Next Command in the command line or NULL if non exists
    165 **/
    166 CHAR8 *
    167 ParseArguments (
    168   IN  CHAR8  *CmdLine,
    169   OUT UINTN  *Argc,
    170   OUT CHAR8  **Argv
    171   )
    172 {
    173   UINTN   Arg;
    174   CHAR8   *Char;
    175   BOOLEAN LookingForArg;
    176   BOOLEAN InQuote;
    177 
    178   *Argc = 0;
    179   if (AsciiStrLen (CmdLine) == 0) {
    180     return NULL;
    181   }
    182 
    183   // Walk a single command line. A CMD_SEPARATOR allows multiple commands on a single line
    184   InQuote       = FALSE;
    185   LookingForArg = TRUE;
    186   for (Char = CmdLine, Arg = 0; *Char != '\0'; Char++) {
    187     if (!InQuote && *Char == CMD_SEPARATOR) {
    188       break;
    189     }
    190 
    191     // Perform any text conversion here
    192     if (*Char == '\t') {
    193       // TAB to space
    194       *Char = ' ';
    195     }
    196 
    197     if (LookingForArg) {
    198       // Look for the beginning of an Argv[] entry
    199       if (*Char == '"') {
    200         Argv[Arg++] = ++Char;
    201         LookingForArg = FALSE;
    202         InQuote = TRUE;
    203       } else if (*Char != ' ') {
    204         Argv[Arg++] = Char;
    205         LookingForArg = FALSE;
    206       }
    207     } else {
    208       // Looking for the terminator of an Argv[] entry
    209       if (!InQuote && (*Char == ' ')) {
    210         *Char = '\0';
    211         LookingForArg = TRUE;
    212       } else if (!InQuote && (*Char == '"') && (*(Char-1) != '\\')) {
    213         InQuote = TRUE;
    214       } else if (InQuote && (*Char == '"') && (*(Char-1) != '\\')) {
    215         *Char = '\0';
    216         InQuote = FALSE;
    217       }
    218     }
    219   }
    220 
    221   *Argc = Arg;
    222 
    223   if (*Char == CMD_SEPARATOR) {
    224     // Replace the command delimiter with null and return pointer to next command line
    225     *Char = '\0';
    226     return ++Char;
    227   }
    228 
    229   return NULL;
    230 }
    231 
    232 
    233 /**
    234   Return a keypress or optionally timeout if a timeout value was passed in.
    235   An optional callback function is called every second when waiting for a
    236   timeout.
    237 
    238   @param  Key           EFI Key information returned
    239   @param  TimeoutInSec  Number of seconds to wait to timeout
    240   @param  CallBack      Callback called every second during the timeout wait
    241 
    242   @return EFI_SUCCESS  Key was returned
    243   @return EFI_TIMEOUT  If the TimoutInSec expired
    244 
    245 **/
    246 EFI_STATUS
    247 EFIAPI
    248 EblGetCharKey (
    249   IN OUT EFI_INPUT_KEY            *Key,
    250   IN     UINTN                    TimeoutInSec,
    251   IN     EBL_GET_CHAR_CALL_BACK   CallBack   OPTIONAL
    252   )
    253 {
    254   EFI_STATUS    Status;
    255   UINTN         WaitCount;
    256   UINTN         WaitIndex;
    257   EFI_EVENT     WaitList[2];
    258 
    259   WaitCount   = 1;
    260   WaitList[0] = gST->ConIn->WaitForKey;
    261   if (TimeoutInSec != 0) {
    262     // Create a time event for 1 sec duration if we have a timeout
    263     gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &WaitList[1]);
    264     gBS->SetTimer (WaitList[1], TimerPeriodic, EFI_SET_TIMER_TO_SECOND);
    265     WaitCount++;
    266   }
    267 
    268   for (;;) {
    269     Status = gBS->WaitForEvent (WaitCount, WaitList, &WaitIndex);
    270     ASSERT_EFI_ERROR (Status);
    271 
    272     switch (WaitIndex) {
    273     case 0:
    274       // Key event signaled
    275       Status = gST->ConIn->ReadKeyStroke (gST->ConIn, Key);
    276       if (!EFI_ERROR (Status)) {
    277         if (WaitCount == 2) {
    278           gBS->CloseEvent (WaitList[1]);
    279         }
    280         return EFI_SUCCESS;
    281       }
    282       break;
    283 
    284     case 1:
    285       // Periodic 1 sec timer signaled
    286       TimeoutInSec--;
    287       if (CallBack != NULL) {
    288         // Call the users callback function if registered
    289         CallBack (TimeoutInSec);
    290       }
    291       if (TimeoutInSec == 0) {
    292         gBS->CloseEvent (WaitList[1]);
    293         return EFI_TIMEOUT;
    294       }
    295       break;
    296     default:
    297       ASSERT (FALSE);
    298     }
    299   }
    300 }
    301 
    302 
    303 /**
    304   This routine is used prevent command output data from scrolling off the end
    305   of the screen. The global gPageBreak is used to turn on or off this feature.
    306   If the CurrentRow is near the end of the screen pause and print out a prompt
    307   If the use hits Q to quit return TRUE else for any other key return FALSE.
    308   PrefixNewline is used to figure out if a newline is needed before the prompt
    309   string. This depends on the last print done before calling this function.
    310   CurrentRow is updated by one on a call or set back to zero if a prompt is
    311   needed.
    312 
    313   @param  CurrentRow  Used to figure out if its the end of the page and updated
    314   @param  PrefixNewline  Did previous print issue a newline
    315 
    316   @return TRUE if Q was hit to quit, FALSE in all other cases.
    317 
    318 **/
    319 BOOLEAN
    320 EFIAPI
    321 EblAnyKeyToContinueQtoQuit (
    322   IN  UINTN   *CurrentRow,
    323   IN  BOOLEAN PrefixNewline
    324   )
    325 {
    326   EFI_INPUT_KEY     InputKey;
    327 
    328   if (!gPageBreak) {
    329     // global disable for this feature
    330     return FALSE;
    331   }
    332 
    333   if (*CurrentRow >= (gScreenRows - 2)) {
    334     if (PrefixNewline) {
    335       AsciiPrint ("\n");
    336     }
    337     AsciiPrint ("Any key to continue (Q to quit): ");
    338     EblGetCharKey (&InputKey, 0, NULL);
    339     AsciiPrint ("\n");
    340 
    341     // Time to promt to stop the screen. We have to leave space for the prompt string
    342     *CurrentRow = 0;
    343     if (InputKey.UnicodeChar == 'Q' || InputKey.UnicodeChar == 'q') {
    344       return TRUE;
    345     }
    346   } else {
    347     *CurrentRow += 1;
    348   }
    349 
    350   return FALSE;
    351 }
    352 
    353 
    354 /**
    355   Set the text color of the EFI Console. If a zero is passed in reset to
    356   default text/background color.
    357 
    358   @param  Attribute   For text and background color
    359 
    360 **/
    361 VOID
    362 EblSetTextColor (
    363   UINTN   Attribute
    364   )
    365 {
    366   if (Attribute == 0) {
    367     // Set the text color back to default
    368     Attribute = (UINTN)PcdGet32 (PcdEmbeddedDefaultTextColor);
    369   }
    370 
    371   gST->ConOut->SetAttribute (gST->ConOut, Attribute);
    372 }
    373 
    374 
    375 /**
    376   Collect the keyboard input for a cmd line. Carriage Return, New Line, or ESC
    377   terminates the command line. You can edit the command line via left arrow,
    378   delete and backspace and they all back up and erase the command line.
    379   No edit of command line is possible without deletion at this time!
    380   The up arrow and down arrow fill Cmd with information from the history
    381   buffer.
    382 
    383   @param  Cmd         Command line to return
    384   @param  CmdMaxSize  Maximum size of Cmd
    385 
    386   @return The Status of EblGetCharKey()
    387 
    388 **/
    389 EFI_STATUS
    390 GetCmd (
    391   IN OUT  CHAR8   *Cmd,
    392   IN      UINTN   CmdMaxSize
    393   )
    394 {
    395   EFI_STATUS    Status;
    396   UINTN         Index;
    397   UINTN         Index2;
    398   CHAR8         Char;
    399   CHAR8         *History;
    400   EFI_INPUT_KEY Key;
    401 
    402   for (Index = 0; Index < CmdMaxSize - 1;) {
    403     Status = EblGetCharKey (&Key, 0, NULL);
    404     if (EFI_ERROR (Status)) {
    405       Cmd[Index] = '\0';
    406       AsciiPrint ("\n");
    407       return Status;
    408     }
    409 
    410     Char = (CHAR8)Key.UnicodeChar;
    411     if ((Char == '\n') || (Char == '\r') || (Char == 0x7f)) {
    412       Cmd[Index] = '\0';
    413       if (FixedPcdGetBool(PcdEmbeddedShellCharacterEcho) == TRUE) {
    414         AsciiPrint ("\n\r");
    415       }
    416       return EFI_SUCCESS;
    417     } else if ((Char == '\b') || (Key.ScanCode == SCAN_LEFT) || (Key.ScanCode == SCAN_DELETE)){
    418       if (Index != 0) {
    419         Index--;
    420         //
    421         // Update the display
    422         //
    423         AsciiPrint ("\b \b");
    424       }
    425     } else if ((Key.ScanCode == SCAN_UP) || Key.ScanCode == SCAN_DOWN) {
    426       History = GetCmdHistory (Key.ScanCode);
    427       //
    428       // Clear display line
    429       //
    430       for (Index2 = 0; Index2 < Index; Index2++) {
    431         AsciiPrint ("\b \b");
    432       }
    433       AsciiPrint (History);
    434       Index = AsciiStrLen (History);
    435       AsciiStrnCpyS (Cmd, CmdMaxSize, History, CmdMaxSize);
    436     } else {
    437       Cmd[Index++] = Char;
    438       if (FixedPcdGetBool(PcdEmbeddedShellCharacterEcho) == TRUE) {
    439         AsciiPrint ("%c", Char);
    440       }
    441     }
    442   }
    443 
    444   return EFI_SUCCESS;
    445 }
    446 
    447 
    448 /**
    449   Print the boot up banner for the EBL.
    450 **/
    451 VOID
    452 EblPrintStartupBanner (
    453   VOID
    454   )
    455 {
    456   AsciiPrint ("Embedded Boot Loader (");
    457   EblSetTextColor (EFI_YELLOW);
    458   AsciiPrint ("EBL");
    459   EblSetTextColor (0);
    460   AsciiPrint (") prototype. Built at %a on %a\n",__TIME__, __DATE__);
    461   AsciiPrint ("THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN 'AS IS' BASIS,\nWITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\n");
    462   AsciiPrint ("Please send feedback to edk2-devel (at) lists.sourceforge.net\n");
    463 }
    464 
    465 
    466 /**
    467   Send null requests to all removable media block IO devices so the a media add/remove/change
    468   can be detected in real before we execute a command.
    469 
    470   This is mainly due to the fact that the FAT driver does not do this today so you can get stale
    471   dir commands after an SD Card has been removed.
    472 **/
    473 VOID
    474 EblProbeRemovableMedia (
    475   VOID
    476   )
    477 {
    478   UINTN         Index;
    479   UINTN         Max;
    480   EFI_OPEN_FILE *File;
    481 
    482   //
    483   // Probe for media insertion/removal in removable media devices
    484   //
    485   Max = EfiGetDeviceCounts (EfiOpenBlockIo);
    486   if (Max != 0) {
    487     for (Index = 0; Index < Max; Index++) {
    488       File = EfiDeviceOpenByType (EfiOpenBlockIo, Index);
    489       if (File != NULL) {
    490         if (File->FsBlockIoMedia->RemovableMedia) {
    491           // Probe to see if media is present (or not) or media changed
    492           //  this causes the ReinstallProtocolInterface() to fire in the
    493           //  block io driver to update the system about media change events
    494           File->FsBlockIo->ReadBlocks (File->FsBlockIo, File->FsBlockIo->Media->MediaId, (EFI_LBA)0, 0, NULL);
    495         }
    496         EfiClose (File);
    497       }
    498     }
    499   }
    500 }
    501 
    502 
    503 
    504 
    505 /**
    506   Print the prompt for the EBL.
    507 **/
    508 VOID
    509 EblPrompt (
    510   VOID
    511   )
    512 {
    513   EblSetTextColor (EFI_YELLOW);
    514   AsciiPrint ("%a %a",(CHAR8 *)PcdGetPtr (PcdEmbeddedPrompt), EfiGetCwd ());
    515   EblSetTextColor (0);
    516   AsciiPrint ("%a", ">");
    517 }
    518 
    519 
    520 
    521 /**
    522   Parse a command line and execute the commands. The ; separator allows
    523   multiple commands for each command line. Stop processing if one of the
    524   commands returns an error.
    525 
    526   @param  CmdLine          Command Line to process.
    527   @param  MaxCmdLineSize   MaxSize of the Command line
    528 
    529   @return EFI status of the Command
    530 
    531 **/
    532 EFI_STATUS
    533 ProcessCmdLine (
    534   IN CHAR8      *CmdLine,
    535   IN UINTN      MaxCmdLineSize
    536   )
    537 {
    538   EFI_STATUS          Status;
    539   EBL_COMMAND_TABLE   *Cmd;
    540   CHAR8               *Ptr;
    541   UINTN               Argc;
    542   CHAR8               *Argv[MAX_ARGS];
    543 
    544   // Parse the command line. The loop processes commands separated by ;
    545   for (Ptr = CmdLine, Status = EFI_SUCCESS; Ptr != NULL;) {
    546     Ptr = ParseArguments (Ptr, &Argc, Argv);
    547     if (Argc != 0) {
    548       Cmd = EblGetCommand (Argv[0]);
    549       if (Cmd != NULL) {
    550         // Execute the Command!
    551         Status = Cmd->Command (Argc, Argv);
    552         if (Status == EFI_ABORTED) {
    553           // exit command so lets exit
    554           break;
    555         } else if (Status == EFI_TIMEOUT) {
    556           // pause command got input so don't process any more cmd on this cmd line
    557           break;
    558         } else if (EFI_ERROR (Status)) {
    559           AsciiPrint ("%a returned %r error\n", Cmd->Name, Status);
    560           // if any command fails stop processing CmdLine
    561           break;
    562         }
    563       } else {
    564         AsciiPrint ("The command '%a' is not supported.\n", Argv[0]);
    565       }
    566     }
    567   }
    568 
    569   return Status;
    570 }
    571 
    572 
    573 
    574 /**
    575   Embedded Boot Loader (EBL) - A simple EFI command line application for embedded
    576   devices. PcdEmbeddedAutomaticBootCommand is a complied in command line that
    577   gets executed automatically. The ; separator allows multiple commands
    578   for each command line.
    579 
    580   @param  ImageHandle   EFI ImageHandle for this application.
    581   @param  SystemTable   EFI system table
    582 
    583   @return EFI status of the application
    584 
    585 **/
    586 EFI_STATUS
    587 EFIAPI
    588 EdkBootLoaderEntry (
    589   IN EFI_HANDLE                            ImageHandle,
    590   IN EFI_SYSTEM_TABLE                      *SystemTable
    591   )
    592 {
    593   EFI_STATUS  Status;
    594   CHAR8       CmdLine[MAX_CMD_LINE];
    595   CHAR16      *CommandLineVariable = NULL;
    596   CHAR16      *CommandLineVariableName = L"default-cmdline";
    597   UINTN       CommandLineVariableSize = 0;
    598   EFI_GUID    VendorGuid;
    599 
    600   // Initialize tables of commands
    601   EblInitializeCmdTable ();
    602   EblInitializeDeviceCmd ();
    603   EblInitializemdHwDebugCmds ();
    604   EblInitializemdHwIoDebugCmds ();
    605   EblInitializeDirCmd ();
    606   EblInitializeHobCmd ();
    607   EblInitializeScriptCmd ();
    608   EblInitializeExternalCmd ();
    609   EblInitializeNetworkCmd();
    610   EblInitializeVariableCmds ();
    611 
    612   if (gST->ConOut == NULL) {
    613     DEBUG((EFI_D_ERROR,"Error: No Console Output\n"));
    614     return EFI_NOT_READY;
    615   }
    616 
    617   // Disable the 5 minute EFI watchdog time so we don't get automatically reset
    618   gBS->SetWatchdogTimer (0, 0, 0, NULL);
    619 
    620   if (FeaturePcdGet (PcdEmbeddedMacBoot)) {
    621     // A MAC will boot in graphics mode, so turn it back to text here
    622     // This protocol was removed from edk2. It is only an edk thing. We need to make our own copy.
    623     // DisableQuietBoot ();
    624 
    625     // Enable the biggest output screen size possible
    626     gST->ConOut->SetMode (gST->ConOut, (UINTN)gST->ConOut->Mode->MaxMode - 1);
    627 
    628   }
    629 
    630   // Save current screen mode
    631   gST->ConOut->QueryMode (gST->ConOut, gST->ConOut->Mode->Mode, &gScreenColumns, &gScreenRows);
    632 
    633   EblPrintStartupBanner ();
    634 
    635   // Parse command line and handle commands separated by ;
    636   // The loop prints the prompt gets user input and saves history
    637 
    638   // Look for a variable with a default command line, otherwise use the Pcd
    639   ZeroMem(&VendorGuid, sizeof(EFI_GUID));
    640 
    641   Status = gRT->GetVariable(CommandLineVariableName, &VendorGuid, NULL, &CommandLineVariableSize, CommandLineVariable);
    642   if (Status == EFI_BUFFER_TOO_SMALL) {
    643     CommandLineVariable = AllocatePool(CommandLineVariableSize);
    644 
    645     Status = gRT->GetVariable(CommandLineVariableName, &VendorGuid, NULL, &CommandLineVariableSize, CommandLineVariable);
    646     if (!EFI_ERROR(Status)) {
    647       UnicodeStrToAsciiStrS (CommandLineVariable, CmdLine, MAX_CMD_LINE);
    648     }
    649 
    650     FreePool(CommandLineVariable);
    651   }
    652 
    653   if (EFI_ERROR(Status)) {
    654     AsciiStrCpyS (CmdLine, MAX_CMD_LINE, (CHAR8 *)PcdGetPtr (PcdEmbeddedAutomaticBootCommand));
    655   }
    656 
    657   for (;;) {
    658     Status = ProcessCmdLine (CmdLine, MAX_CMD_LINE);
    659     if (Status == EFI_ABORTED) {
    660       // if a command returns EFI_ABORTED then exit the EBL
    661       EblShutdownExternalCmdTable ();
    662       return EFI_SUCCESS;
    663     }
    664 
    665     // get the command line from the user
    666     EblPrompt ();
    667     GetCmd (CmdLine, MAX_CMD_LINE);
    668     SetCmdHistory (CmdLine);
    669 
    670     if (FeaturePcdGet (PcdEmbeddedProbeRemovable)) {
    671       // Probe removable media devices to see if media has been inserted or removed.
    672       EblProbeRemovableMedia ();
    673     }
    674   }
    675 }
    676 
    677 
    678