/** @file Process Capsule On Disk. Copyright (c) 2019, Intel Corporation. All rights reserved.
This program and the accompanying materials are licensed and made available under the terms and conditions of the BSD License which accompanies this distribution. The full text of the license may be found at http://opensource.org/licenses/bsd-license.php THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. **/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include EFI_GUID mCapsuleOnDiskBootOptionGuid = { 0x4CC29BB7, 0x2413, 0x40A2, { 0xB0, 0x6D, 0x25, 0x3E, 0x37, 0x10, 0xF5, 0x32 } }; /** Get shell protocol. @return Pointer to shell protocol. **/ EFI_SHELL_PROTOCOL * GetShellProtocol ( VOID ); /** Get file name from file path. @param FilePath File path. @return Pointer to file name. **/ CHAR16 * GetFileNameFromPath ( CHAR16 *FilePath ) { EFI_STATUS Status; EFI_SHELL_PROTOCOL *ShellProtocol; SHELL_FILE_HANDLE Handle; EFI_FILE_INFO *FileInfo; ShellProtocol = GetShellProtocol (); if (ShellProtocol == NULL) { return NULL; } // // Open file by FileName. // Status = ShellProtocol->OpenFileByName ( FilePath, &Handle, EFI_FILE_MODE_READ ); if (EFI_ERROR (Status)) { return NULL; } // // Get file name from EFI_FILE_INFO. // FileInfo = ShellProtocol->GetFileInfo (Handle); ShellProtocol->CloseFile (Handle); if (FileInfo == NULL) { return NULL; } return FileInfo->FileName; } /** Check if the device path is EFI system Partition. @param DevicePath The ESP device path. @retval TRUE DevicePath is a device path for ESP. @retval FALSE DevicePath is not a device path for ESP. **/ BOOLEAN IsEfiSysPartitionDevicePath ( EFI_DEVICE_PATH_PROTOCOL *DevicePath ) { EFI_STATUS Status; EFI_DEVICE_PATH_PROTOCOL *TempDevicePath; HARDDRIVE_DEVICE_PATH *Hd; EFI_HANDLE Handle; // // Check if the device path contains GPT node // TempDevicePath = DevicePath; while (!IsDevicePathEnd (TempDevicePath)) { if ((DevicePathType (TempDevicePath) == MEDIA_DEVICE_PATH) && (DevicePathSubType (TempDevicePath) == MEDIA_HARDDRIVE_DP)) { Hd = (HARDDRIVE_DEVICE_PATH *)TempDevicePath; if (Hd->MBRType == MBR_TYPE_EFI_PARTITION_TABLE_HEADER) { break; } } TempDevicePath = NextDevicePathNode (TempDevicePath); } if (!IsDevicePathEnd (TempDevicePath)) { // // Search for EFI system partition protocol on full device path in Boot Option // Status = gBS->LocateDevicePath (&gEfiPartTypeSystemPartGuid, &DevicePath, &Handle); return EFI_ERROR (Status) ? FALSE : TRUE; } else { return FALSE; } } /** Dump all EFI System Partition. **/ VOID DumpAllEfiSysPartition ( VOID ) { EFI_HANDLE *SimpleFileSystemHandles; UINTN NumberSimpleFileSystemHandles; UINTN Index; EFI_DEVICE_PATH_PROTOCOL *DevicePath; UINTN NumberEfiSystemPartitions; EFI_SHELL_PROTOCOL *ShellProtocol; NumberEfiSystemPartitions = 0; ShellProtocol = GetShellProtocol (); if (ShellProtocol == NULL) { Print (L"Get Shell Protocol Fail\n");; return ; } Print (L"EFI System Partition list:\n"); gBS->LocateHandleBuffer ( ByProtocol, &gEfiSimpleFileSystemProtocolGuid, NULL, &NumberSimpleFileSystemHandles, &SimpleFileSystemHandles ); for (Index = 0; Index < NumberSimpleFileSystemHandles; Index++) { DevicePath = DevicePathFromHandle (SimpleFileSystemHandles[Index]); if (IsEfiSysPartitionDevicePath (DevicePath)) { NumberEfiSystemPartitions++; Print(L" %s\n %s\n", ShellProtocol->GetMapFromDevicePath (&DevicePath), ConvertDevicePathToText (DevicePath, TRUE, TRUE)); } } if (NumberEfiSystemPartitions == 0) { Print(L" No ESP found.\n"); } } /** Check if capsule is provisioned. @retval TRUE Capsule is provisioned previously. @retval FALSE No capsule is provisioned. **/ BOOLEAN IsCapsuleProvisioned ( VOID ) { EFI_STATUS Status; UINT64 OsIndication; UINTN DataSize; OsIndication = 0; DataSize = sizeof(UINT64); Status = gRT->GetVariable ( L"OsIndications", &gEfiGlobalVariableGuid, NULL, &DataSize, &OsIndication ); if (!EFI_ERROR (Status) && (OsIndication & EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED) != 0) { return TRUE; } return FALSE; } /** Get one active Efi System Partition. @param[out] FsDevicePath The device path of Fs @param[out] Fs The file system within EfiSysPartition @retval EFI_SUCCESS Get file system successfully @retval EFI_NOT_FOUND No valid file system found **/ EFI_STATUS GetEfiSysPartition ( OUT EFI_DEVICE_PATH_PROTOCOL **FsDevicePath, OUT EFI_SIMPLE_FILE_SYSTEM_PROTOCOL **Fs ) { EFI_HANDLE *SimpleFileSystemHandles; UINTN NumberSimpleFileSystemHandles; UINTN Index; EFI_DEVICE_PATH_PROTOCOL *DevicePath; EFI_STATUS Status; Status = gBS->LocateHandleBuffer ( ByProtocol, &gEfiSimpleFileSystemProtocolGuid, NULL, &NumberSimpleFileSystemHandles, &SimpleFileSystemHandles ); if (EFI_ERROR (Status)) { return EFI_NOT_FOUND; } for (Index = 0; Index < NumberSimpleFileSystemHandles; Index++) { DevicePath = DevicePathFromHandle (SimpleFileSystemHandles[Index]); if (IsEfiSysPartitionDevicePath (DevicePath)) { Status = gBS->HandleProtocol (SimpleFileSystemHandles[Index], &gEfiSimpleFileSystemProtocolGuid, (VOID **)Fs); if (!EFI_ERROR (Status)) { *FsDevicePath = DevicePath; return EFI_SUCCESS; } } } return EFI_NOT_FOUND; } /** Check if Active Efi System Partition within GPT is in the device path. @param[in] DevicePath The device path @param[out] FsDevicePath The device path of Fs @param[out] Fs The file system within EfiSysPartition @retval EFI_SUCCESS Get file system successfully @retval EFI_NOT_FOUND No valid file system found @retval others Get file system failed **/ EFI_STATUS GetEfiSysPartitionFromDevPath ( IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, OUT EFI_DEVICE_PATH_PROTOCOL **FsDevicePath, OUT EFI_SIMPLE_FILE_SYSTEM_PROTOCOL **Fs ) { EFI_STATUS Status; EFI_DEVICE_PATH_PROTOCOL *TempDevicePath; HARDDRIVE_DEVICE_PATH *Hd; EFI_HANDLE Handle; // // Check if the device path contains GPT node // TempDevicePath = DevicePath; while (!IsDevicePathEnd (TempDevicePath)) { if ((DevicePathType (TempDevicePath) == MEDIA_DEVICE_PATH) && (DevicePathSubType (TempDevicePath) == MEDIA_HARDDRIVE_DP)) { Hd = (HARDDRIVE_DEVICE_PATH *)TempDevicePath; if (Hd->MBRType == MBR_TYPE_EFI_PARTITION_TABLE_HEADER) { break; } } TempDevicePath = NextDevicePathNode (TempDevicePath); } if (!IsDevicePathEnd (TempDevicePath)) { // // Search for EFI system partition protocol on full device path in Boot Option // Status = gBS->LocateDevicePath (&gEfiPartTypeSystemPartGuid, &DevicePath, &Handle); // // Search for simple file system on this handler // if (!EFI_ERROR (Status)) { Status = gBS->HandleProtocol (Handle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)Fs); if (!EFI_ERROR (Status)) { *FsDevicePath = DevicePathFromHandle (Handle); return EFI_SUCCESS; } } } return EFI_NOT_FOUND; } /** Get SimpleFileSystem from boot option file path. @param[in] DevicePath The file path of boot option @param[out] FullPath The full device path of boot device @param[out] Fs The file system within EfiSysPartition @retval EFI_SUCCESS Get file system successfully @retval EFI_NOT_FOUND No valid file system found @retval others Get file system failed **/ EFI_STATUS EFIAPI GetEfiSysPartitionFromBootOptionFilePath ( IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, OUT EFI_DEVICE_PATH_PROTOCOL **FullPath, OUT EFI_SIMPLE_FILE_SYSTEM_PROTOCOL **Fs ) { EFI_STATUS Status; EFI_DEVICE_PATH_PROTOCOL *CurFullPath; EFI_DEVICE_PATH_PROTOCOL *PreFullPath; EFI_DEVICE_PATH_PROTOCOL *FsFullPath; CurFullPath = NULL; FsFullPath = NULL; // // Try every full device Path generated from bootoption // do { PreFullPath = CurFullPath; CurFullPath = EfiBootManagerGetNextLoadOptionDevicePath (DevicePath, CurFullPath); if (PreFullPath != NULL) { FreePool (PreFullPath); } if (CurFullPath == NULL) { // // No Active EFI system partition is found in BootOption device path // Status = EFI_NOT_FOUND; break; } DEBUG_CODE ( CHAR16 *DevicePathStr; DevicePathStr = ConvertDevicePathToText (CurFullPath, TRUE, TRUE); if (DevicePathStr != NULL){ DEBUG ((DEBUG_INFO, "Full device path %s\n", DevicePathStr)); FreePool (DevicePathStr); } ); Status = GetEfiSysPartitionFromDevPath (CurFullPath, &FsFullPath, Fs); } while (EFI_ERROR (Status)); if (*Fs != NULL) { *FullPath = FsFullPath; return EFI_SUCCESS; } else { return EFI_NOT_FOUND; } } /** Get a valid SimpleFileSystem within EFI system partition. @param[in] Map The FS mapping capsule write to @param[out] BootNext The value of BootNext Variable @param[out] Fs The file system within EfiSysPartition @param[out] UpdateBootNext The flag to indicate whether update BootNext Variable @retval EFI_SUCCESS Get FS successfully @retval EFI_NOT_FOUND No valid FS found @retval others Get FS failed **/ EFI_STATUS EFIAPI GetUpdateFileSystem ( IN CHAR16 *Map, OUT UINT16 *BootNext, OUT EFI_SIMPLE_FILE_SYSTEM_PROTOCOL **Fs, OUT BOOLEAN *UpdateBootNext ) { EFI_STATUS Status; CHAR16 BootOptionName[20]; UINTN Index; CONST EFI_DEVICE_PATH_PROTOCOL *MappedDevicePath; EFI_DEVICE_PATH_PROTOCOL *DevicePath; EFI_DEVICE_PATH_PROTOCOL *FullPath; UINT16 *BootNextData; EFI_BOOT_MANAGER_LOAD_OPTION BootNextOption; EFI_BOOT_MANAGER_LOAD_OPTION *BootOptionBuffer; UINTN BootOptionCount; EFI_SHELL_PROTOCOL *ShellProtocol; EFI_BOOT_MANAGER_LOAD_OPTION NewOption; MappedDevicePath = NULL; BootOptionBuffer = NULL; ShellProtocol = GetShellProtocol (); if (ShellProtocol == NULL) { Print (L"Get Shell Protocol Fail\n");; return EFI_NOT_FOUND; } // // 1. If Fs is not assigned and there are capsule provisioned before, // Get EFI system partition from BootNext. // if (IsCapsuleProvisioned () && Map == NULL) { Status = GetVariable2 ( L"BootNext", &gEfiGlobalVariableGuid, (VOID **)&BootNextData, NULL ); if (EFI_ERROR (Status) || BootNextData == NULL) { Print (L"Get Boot Next Data Fail. Status = %r\n", Status); return EFI_NOT_FOUND; } else { UnicodeSPrint (BootOptionName, sizeof (BootOptionName), L"Boot%04x", *BootNextData); Status = EfiBootManagerVariableToLoadOption (BootOptionName, &BootNextOption); if (!EFI_ERROR (Status)) { DevicePath = BootNextOption.FilePath; Status = GetEfiSysPartitionFromBootOptionFilePath (DevicePath, &FullPath, Fs); if (!EFI_ERROR (Status)) { *UpdateBootNext = FALSE; Print(L"Get EFI system partition from BootNext : %s\n", BootNextOption.Description); Print(L"%s %s\n", ShellProtocol->GetMapFromDevicePath (&FullPath), ConvertDevicePathToText (FullPath, TRUE, TRUE)); return EFI_SUCCESS; } } } } // // Check if Map is valid. // if (Map != NULL) { MappedDevicePath = ShellProtocol->GetDevicePathFromMap (Map); if (MappedDevicePath == NULL) { Print(L"'%s' is not a valid mapping.\n", Map); return EFI_INVALID_PARAMETER; } else if (!IsEfiSysPartitionDevicePath (DuplicateDevicePath (MappedDevicePath))) { Print(L"'%s' is not a EFI System Partition.\n", Map); return EFI_INVALID_PARAMETER; } } // // 2. Get EFI system partition form boot options. // BootOptionBuffer = EfiBootManagerGetLoadOptions (&BootOptionCount, LoadOptionTypeBoot); if ( (BootOptionBuffer == NULL) || (BootOptionCount == 0 && Map == NULL) ) { return EFI_NOT_FOUND; } for (Index = 0; Index < BootOptionCount; Index++) { // // Get the boot option from the link list // DevicePath = BootOptionBuffer[Index].FilePath; // // Skip inactive or legacy boot options // if ((BootOptionBuffer[Index].Attributes & LOAD_OPTION_ACTIVE) == 0 || DevicePathType (DevicePath) == BBS_DEVICE_PATH) { continue; } DEBUG_CODE ( CHAR16 *DevicePathStr; DevicePathStr = ConvertDevicePathToText (DevicePath, TRUE, TRUE); if (DevicePathStr != NULL){ DEBUG ((DEBUG_INFO, "Try BootOption %s\n", DevicePathStr)); FreePool (DevicePathStr); } else { DEBUG ((DEBUG_INFO, "DevicePathToStr failed\n")); } ); Status = GetEfiSysPartitionFromBootOptionFilePath (DevicePath, &FullPath, Fs); if (!EFI_ERROR (Status)) { if (Map == NULL) { *BootNext = (UINT16) BootOptionBuffer[Index].OptionNumber; *UpdateBootNext = TRUE; Print (L"Found EFI system partition on Boot%04x: %s\n", *BootNext, BootOptionBuffer[Index].Description); Print (L"%s %s\n", ShellProtocol->GetMapFromDevicePath (&FullPath), ConvertDevicePathToText (FullPath, TRUE, TRUE)); return EFI_SUCCESS; } if (StrnCmp (Map, ShellProtocol->GetMapFromDevicePath (&FullPath), StrLen (Map)) == 0) { *BootNext = (UINT16) BootOptionBuffer[Index].OptionNumber; *UpdateBootNext = TRUE; Print (L"Found Boot Option on %s : %s\n", Map, BootOptionBuffer[Index].Description); return EFI_SUCCESS; } } } // // 3. If no ESP is found on boot option, try to find a ESP and create boot option for it. // if (Map != NULL) { // // If map is assigned, try to get ESP from mapped Fs. // DevicePath = DuplicateDevicePath (MappedDevicePath); Status = GetEfiSysPartitionFromDevPath (DevicePath, &FullPath, Fs); if (EFI_ERROR (Status)) { Print (L"Error: Cannot get EFI system partiion from '%s' - %r\n", Map, Status); return EFI_NOT_FOUND; } Print (L"Warning: Cannot find Boot Option on '%s'!\n", Map); } else { Status = GetEfiSysPartition (&DevicePath, Fs); if (EFI_ERROR (Status)) { Print (L"Error: Cannot find a EFI system partition!\n"); return EFI_NOT_FOUND; } } Print (L"Create Boot option for capsule on disk:\n"); Status = EfiBootManagerInitializeLoadOption ( &NewOption, LoadOptionNumberUnassigned, LoadOptionTypeBoot, LOAD_OPTION_ACTIVE, L"UEFI Capsule On Disk", DevicePath, (UINT8 *) &mCapsuleOnDiskBootOptionGuid, sizeof(EFI_GUID) ); if (!EFI_ERROR (Status)) { Status = EfiBootManagerAddLoadOptionVariable (&NewOption, (UINTN) -1); { if (!EFI_ERROR (Status)) { *UpdateBootNext = TRUE; *BootNext = (UINT16) NewOption.OptionNumber; Print (L" Boot%04x: %s\n", *BootNext, ConvertDevicePathToText(DevicePath, TRUE, TRUE)); return EFI_SUCCESS; } } } Print (L"ERROR: Cannot create boot option! - %r\n", Status); return EFI_NOT_FOUND; } /** Write files to a given SimpleFileSystem. @param[in] Buffer The buffer array @param[in] BufferSize The buffer size array @param[in] FileName The file name array @param[in] BufferNum The buffer number @param[in] Fs The SimpleFileSystem handle to be written @retval EFI_SUCCESS Write file successfully @retval EFI_NOT_FOUND SFS protocol not found @retval others Write file failed **/ EFI_STATUS WriteUpdateFile ( IN VOID **Buffer, IN UINTN *BufferSize, IN CHAR16 **FileName, IN UINTN BufferNum, IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs ) { EFI_STATUS Status; EFI_FILE *Root; EFI_FILE *FileHandle; EFI_FILE_PROTOCOL *DirHandle; UINT64 FileInfo; VOID *Filebuffer; UINTN FileSize; UINTN Index; DirHandle = NULL; FileHandle = NULL; Index = 0; // // Open Root from SFS // Status = Fs->OpenVolume (Fs, &Root); if (EFI_ERROR (Status)) { Print (L"Cannot open volume. Status = %r\n", Status); return EFI_NOT_FOUND; } // // Ensure that efi and updatecapsule directories exist // Status = Root->Open (Root, &DirHandle, L"\\EFI", EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0); if (EFI_ERROR (Status)) { Status = Root->Open (Root, &DirHandle, L"\\EFI", EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, EFI_FILE_DIRECTORY); if (EFI_ERROR (Status)) { Print(L"Unable to create %s directory\n", L"\\EFI"); return EFI_NOT_FOUND; } } Status = Root->Open (Root, &DirHandle, EFI_CAPSULE_FILE_DIRECTORY, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE , 0); if (EFI_ERROR (Status)) { Status = Root->Open (Root, &DirHandle, EFI_CAPSULE_FILE_DIRECTORY, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, EFI_FILE_DIRECTORY); if (EFI_ERROR (Status)) { Print(L"Unable to create %s directory\n", EFI_CAPSULE_FILE_DIRECTORY); return EFI_NOT_FOUND; } } for (Index = 0; Index < BufferNum; Index++) { FileHandle = NULL; // // Open UpdateCapsule file // Status = DirHandle->Open (DirHandle, &FileHandle, FileName[Index], EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ, 0); if (EFI_ERROR (Status)) { Print (L"Unable to create %s file\n", FileName[Index]); return EFI_NOT_FOUND; } // // Empty the file contents // Status = FileHandleGetSize (FileHandle, &FileInfo); if (EFI_ERROR (Status)) { FileHandleClose (FileHandle); Print (L"Error Reading %s\n", FileName[Index]); return EFI_DEVICE_ERROR; } // // If the file size is already 0, then it has been empty. // if (FileInfo != 0) { // // Set the file size to 0. // FileInfo = 0; Status = FileHandleSetSize (FileHandle, FileInfo); if (EFI_ERROR (Status)) { Print (L"Error Deleting %s\n", FileName[Index]); FileHandleClose (FileHandle); return Status; } } // // Write Filebuffer to file // Filebuffer = Buffer[Index]; FileSize = BufferSize[Index]; Status = FileHandleWrite (FileHandle, &FileSize, Filebuffer); if (EFI_ERROR (Status)) { Print (L"Unable to write Capsule Update to %s, Status = %r\n", FileName[Index], Status); return EFI_NOT_FOUND; } Print (L"Succeed to write %s\n", FileName[Index]); FileHandleClose (FileHandle); } return EFI_SUCCESS; } /** Set capsule status variable. @param[in] SetCap Set or clear the capsule flag. @retval EFI_SUCCESS Succeed to set SetCap variable. @retval others Fail to set the variable. **/ EFI_STATUS SetCapsuleStatusVariable ( BOOLEAN SetCap ) { EFI_STATUS Status; UINT64 OsIndication; UINTN DataSize; OsIndication = 0; DataSize = sizeof(UINT64); Status = gRT->GetVariable ( L"OsIndications", &gEfiGlobalVariableGuid, NULL, &DataSize, &OsIndication ); if (EFI_ERROR (Status)) { OsIndication = 0; } if (SetCap) { OsIndication |= ((UINT64)EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED); } else { OsIndication &= ~((UINT64)EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED); } Status = gRT->SetVariable ( L"OsIndications", &gEfiGlobalVariableGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE, sizeof(UINT64), &OsIndication ); return Status; } /** Process Capsule On Disk. @param[in] CapsuleBuffer An array of pointer to capsule images @param[in] CapsuleBufferSize An array of UINTN to capsule images size @param[in] FilePath An array of capsule images file path @param[in] Map File system mapping string @param[in] CapsuleNum The count of capsule images @retval EFI_SUCCESS Capsule on disk success. @retval others Capsule on disk fail. **/ EFI_STATUS ProcessCapsuleOnDisk ( IN VOID **CapsuleBuffer, IN UINTN *CapsuleBufferSize, IN CHAR16 **FilePath, IN CHAR16 *Map, IN UINTN CapsuleNum ) { EFI_STATUS Status; UINT16 BootNext; EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs; BOOLEAN UpdateBootNext; // // Get a valid file system from boot path // Fs = NULL; Status = GetUpdateFileSystem (Map, &BootNext, &Fs, &UpdateBootNext); if (EFI_ERROR (Status)) { Print (L"CapsuleApp: cannot find a valid file system on boot devies. Status = %r\n", Status); return Status; } // // Copy capsule image to '\efi\UpdateCapsule\' // Status = WriteUpdateFile (CapsuleBuffer, CapsuleBufferSize, FilePath, CapsuleNum, Fs); if (EFI_ERROR (Status)) { Print (L"CapsuleApp: capsule image could not be copied for update.\n"); return Status; } // // Set variable then reset // Status = SetCapsuleStatusVariable (TRUE); if (EFI_ERROR (Status)) { Print (L"CapsuleApp: unable to set OSIndication variable.\n"); return Status; } if (UpdateBootNext) { Status = gRT->SetVariable ( L"BootNext", &gEfiGlobalVariableGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE, sizeof(UINT16), &BootNext ); if (EFI_ERROR (Status)){ Print (L"CapsuleApp: unable to set BootNext variable.\n"); return Status; } } return EFI_SUCCESS; }