123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708 |
- /** @file
- Directory related routines
- Copyright (c) 2021 - 2023 Pedro Falcato All rights reserved.
- SPDX-License-Identifier: BSD-2-Clause-Patent
- **/
- #include "Ext4Dxe.h"
- #include <Library/BaseUcs2Utf8Lib.h>
- /**
- Retrieves the filename of the directory entry and converts it to UTF-16/UCS-2
- @param[in] Entry Pointer to a EXT4_DIR_ENTRY.
- @param[out] Ucs2FileName Pointer to an array of CHAR16's, of size EXT4_NAME_MAX + 1.
- @retval EFI_SUCCESS The filename was successfully retrieved and converted to UCS2.
- @retval EFI_INVALID_PARAMETER The filename is not valid UTF-8.
- @retval !EFI_SUCCESS Failure.
- **/
- EFI_STATUS
- Ext4GetUcs2DirentName (
- IN EXT4_DIR_ENTRY *Entry,
- OUT CHAR16 Ucs2FileName[EXT4_NAME_MAX + 1]
- )
- {
- CHAR8 Utf8NameBuf[EXT4_NAME_MAX + 1];
- UINT16 *Str;
- UINT8 Index;
- EFI_STATUS Status;
- for (Index = 0; Index < Entry->name_len; ++Index) {
- if (Entry->name[Index] == '\0') {
- return EFI_INVALID_PARAMETER;
- }
- Utf8NameBuf[Index] = Entry->name[Index];
- }
- Utf8NameBuf[Entry->name_len] = '\0';
- // Unfortunately, BaseUcs2Utf8Lib doesn't have a convert-buffer-to-buffer-like
- // function. Therefore, we need to allocate from the pool (inside UTF8StrToUCS2),
- // copy it to our out buffer (Ucs2FileName) and free.
- Status = UTF8StrToUCS2 (Utf8NameBuf, &Str);
- if (EFI_ERROR (Status)) {
- return Status;
- }
- Status = StrCpyS (Ucs2FileName, EXT4_NAME_MAX + 1, Str);
- FreePool (Str);
- return Status;
- }
- /**
- Validates a directory entry.
- @param[in] Dirent Pointer to the directory entry.
- @retval TRUE Valid directory entry.
- FALSE Invalid directory entry.
- **/
- STATIC
- BOOLEAN
- Ext4ValidDirent (
- IN CONST EXT4_DIR_ENTRY *Dirent
- )
- {
- UINTN RequiredSize;
- RequiredSize = Dirent->name_len + EXT4_MIN_DIR_ENTRY_LEN;
- if (Dirent->rec_len < RequiredSize) {
- DEBUG ((DEBUG_ERROR, "[ext4] dirent size %lu too small (compared to %lu)\n", Dirent->rec_len, RequiredSize));
- return FALSE;
- }
- // Dirent sizes need to be 4 byte aligned
- if ((Dirent->rec_len % 4) != 0) {
- return FALSE;
- }
- return TRUE;
- }
- /**
- Retrieves a directory entry.
- @param[in] Directory Pointer to the opened directory.
- @param[in] NameUnicode Pointer to the UCS-2 formatted filename.
- @param[in] Partition Pointer to the ext4 partition.
- @param[out] Result Pointer to the destination directory entry.
- @return The result of the operation.
- **/
- EFI_STATUS
- Ext4RetrieveDirent (
- IN EXT4_FILE *Directory,
- IN CONST CHAR16 *Name,
- IN EXT4_PARTITION *Partition,
- OUT EXT4_DIR_ENTRY *Result
- )
- {
- EFI_STATUS Status;
- CHAR8 *Buf;
- UINT64 Off;
- EXT4_INODE *Inode;
- UINT64 DirInoSize;
- UINT32 BlockRemainder;
- UINTN Length;
- EXT4_DIR_ENTRY *Entry;
- UINTN RemainingBlock;
- CHAR16 DirentUcs2Name[EXT4_NAME_MAX + 1];
- UINTN ToCopy;
- UINTN BlockOffset;
- Buf = AllocatePool (Partition->BlockSize);
- if (Buf == NULL) {
- return EFI_OUT_OF_RESOURCES;
- }
- Off = 0;
- Inode = Directory->Inode;
- DirInoSize = EXT4_INODE_SIZE (Inode);
- DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);
- if (BlockRemainder != 0) {
- // Directory inodes need to have block aligned sizes
- Status = EFI_VOLUME_CORRUPTED;
- goto Out;
- }
- while (Off < DirInoSize) {
- Length = Partition->BlockSize;
- Status = Ext4Read (Partition, Directory, Buf, Off, &Length);
- if (Status != EFI_SUCCESS) {
- goto Out;
- }
- for (BlockOffset = 0; BlockOffset < Partition->BlockSize; ) {
- Entry = (EXT4_DIR_ENTRY *)(Buf + BlockOffset);
- RemainingBlock = Partition->BlockSize - BlockOffset;
- // Check if the minimum directory entry fits inside [BlockOffset, EndOfBlock]
- if (RemainingBlock < EXT4_MIN_DIR_ENTRY_LEN) {
- Status = EFI_VOLUME_CORRUPTED;
- goto Out;
- }
- if (!Ext4ValidDirent (Entry)) {
- Status = EFI_VOLUME_CORRUPTED;
- goto Out;
- }
- if ((Entry->name_len > RemainingBlock) || (Entry->rec_len > RemainingBlock)) {
- // Corrupted filesystem
- Status = EFI_VOLUME_CORRUPTED;
- goto Out;
- }
- // Unused entry
- if (Entry->inode == 0) {
- BlockOffset += Entry->rec_len;
- continue;
- }
- Status = Ext4GetUcs2DirentName (Entry, DirentUcs2Name);
- /* In theory, this should never fail.
- * In reality, it's quite possible that it can fail, considering filenames in
- * Linux (and probably other nixes) are just null-terminated bags of bytes, and don't
- * need to form valid ASCII/UTF-8 sequences.
- */
- if (EFI_ERROR (Status)) {
- if (Status == EFI_INVALID_PARAMETER) {
- // If we error out due to a bad UTF-8 sequence (see Ext4GetUcs2DirentName), skip this entry.
- // I'm not sure if this is correct behaviour, but I don't think there's a precedent here.
- BlockOffset += Entry->rec_len;
- continue;
- }
- // Other sorts of errors should just error out.
- FreePool (Buf);
- return Status;
- }
- if ((Entry->name_len == StrLen (Name)) &&
- !Ext4StrCmpInsensitive (DirentUcs2Name, (CHAR16 *)Name))
- {
- ToCopy = MIN (Entry->rec_len, sizeof (EXT4_DIR_ENTRY));
- CopyMem (Result, Entry, ToCopy);
- Status = EFI_SUCCESS;
- goto Out;
- }
- BlockOffset += Entry->rec_len;
- }
- Off += Partition->BlockSize;
- }
- Status = EFI_NOT_FOUND;
- Out:
- FreePool (Buf);
- return Status;
- }
- /**
- Opens a file using a directory entry.
- @param[in] Partition Pointer to the ext4 partition.
- @param[in] OpenMode Mode in which the file is supposed to be open.
- @param[out] OutFile Pointer to the newly opened file.
- @param[in] Entry Directory entry to be used.
- @param[in] Directory Pointer to the opened directory.
- @retval EFI_STATUS Result of the operation
- **/
- EFI_STATUS
- Ext4OpenDirent (
- IN EXT4_PARTITION *Partition,
- IN UINT64 OpenMode,
- OUT EXT4_FILE **OutFile,
- IN EXT4_DIR_ENTRY *Entry,
- IN EXT4_FILE *Directory
- )
- {
- EFI_STATUS Status;
- CHAR16 FileName[EXT4_NAME_MAX + 1];
- EXT4_FILE *File;
- File = AllocateZeroPool (sizeof (EXT4_FILE));
- if (File == NULL) {
- Status = EFI_OUT_OF_RESOURCES;
- goto Error;
- }
- Status = Ext4GetUcs2DirentName (Entry, FileName);
- if (EFI_ERROR (Status)) {
- goto Error;
- }
- if (StrCmp (FileName, L".") == 0) {
- // We're using the parent directory's dentry
- File->Dentry = Directory->Dentry;
- ASSERT (File->Dentry != NULL);
- Ext4RefDentry (File->Dentry);
- } else if (StrCmp (FileName, L"..") == 0) {
- // Using the parent's parent's dentry
- File->Dentry = Directory->Dentry->Parent;
- if (!File->Dentry) {
- // Someone tried .. on root, so direct them to /
- // This is an illegal EFI Open() but is possible to hit from a variety of internal code
- File->Dentry = Directory->Dentry;
- }
- Ext4RefDentry (File->Dentry);
- } else {
- File->Dentry = Ext4CreateDentry (FileName, Directory->Dentry);
- if (File->Dentry == NULL) {
- Status = EFI_OUT_OF_RESOURCES;
- goto Error;
- }
- }
- Status = Ext4InitExtentsMap (File);
- if (EFI_ERROR (Status)) {
- goto Error;
- }
- File->InodeNum = Entry->inode;
- Ext4SetupFile (File, Partition);
- Status = Ext4ReadInode (Partition, Entry->inode, &File->Inode);
- if (EFI_ERROR (Status)) {
- goto Error;
- }
- *OutFile = File;
- InsertTailList (&Partition->OpenFiles, &File->OpenFilesListNode);
- return EFI_SUCCESS;
- Error:
- if (File != NULL) {
- if (File->Dentry != NULL) {
- Ext4UnrefDentry (File->Dentry);
- }
- if (File->ExtentsMap != NULL) {
- OrderedCollectionUninit (File->ExtentsMap);
- }
- FreePool (File);
- }
- return Status;
- }
- /**
- Opens a file.
- @param[in] Directory Pointer to the opened directory.
- @param[in] Name Pointer to the UCS-2 formatted filename.
- @param[in] Partition Pointer to the ext4 partition.
- @param[in] OpenMode Mode in which the file is supposed to be open.
- @param[out] OutFile Pointer to the newly opened file.
- @return Result of the operation.
- **/
- EFI_STATUS
- Ext4OpenFile (
- IN EXT4_FILE *Directory,
- IN CONST CHAR16 *Name,
- IN EXT4_PARTITION *Partition,
- IN UINT64 OpenMode,
- OUT EXT4_FILE **OutFile
- )
- {
- EXT4_DIR_ENTRY Entry;
- EFI_STATUS Status;
- Status = Ext4RetrieveDirent (Directory, Name, Partition, &Entry);
- if (EFI_ERROR (Status)) {
- return Status;
- }
- // EFI requires us to error out on ".." opens for the root directory
- if (Entry.inode == Directory->InodeNum) {
- return EFI_NOT_FOUND;
- }
- return Ext4OpenDirent (Partition, OpenMode, OutFile, &Entry, Directory);
- }
- /**
- Open the root directory on a volume.
- @param[in] This A pointer to the volume to open the root directory.
- @param[out] Root A pointer to the location to return the opened file handle for the
- root directory.
- @retval EFI_SUCCESS The device was opened.
- @retval EFI_UNSUPPORTED This volume does not support the requested file system type.
- @retval EFI_NO_MEDIA The device has no medium.
- @retval EFI_DEVICE_ERROR The device reported an error.
- @retval EFI_VOLUME_CORRUPTED The file system structures are corrupted.
- @retval EFI_ACCESS_DENIED The service denied access to the file.
- @retval EFI_OUT_OF_RESOURCES The volume was not opened due to lack of resources.
- @retval EFI_MEDIA_CHANGED The device has a different medium in it or the medium is no
- longer supported. Any existing file handles for this volume are
- no longer valid. To access the files on the new medium, the
- volume must be reopened with OpenVolume().
- **/
- EFI_STATUS
- EFIAPI
- Ext4OpenVolume (
- IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *This,
- OUT EFI_FILE_PROTOCOL **Root
- )
- {
- EXT4_INODE *RootInode;
- EFI_STATUS Status;
- EXT4_FILE *RootDir;
- EXT4_PARTITION *Partition;
- Partition = (EXT4_PARTITION *)This;
- Status = Ext4ReadInode (Partition, EXT4_ROOT_INODE_NR, &RootInode);
- if (EFI_ERROR (Status)) {
- DEBUG ((DEBUG_ERROR, "[ext4] Could not open root inode - error %r\n", Status));
- return Status;
- }
- RootDir = AllocateZeroPool (sizeof (EXT4_FILE));
- if (RootDir == NULL) {
- FreePool (RootInode);
- return EFI_OUT_OF_RESOURCES;
- }
- RootDir->Inode = RootInode;
- RootDir->InodeNum = EXT4_ROOT_INODE_NR;
- Status = Ext4InitExtentsMap (RootDir);
- if (EFI_ERROR (Status)) {
- FreePool (RootInode);
- FreePool (RootDir);
- return EFI_OUT_OF_RESOURCES;
- }
- Ext4SetupFile (RootDir, Partition);
- *Root = &RootDir->Protocol;
- InsertTailList (&Partition->OpenFiles, &RootDir->OpenFilesListNode);
- ASSERT (Partition->RootDentry != NULL);
- RootDir->Dentry = Partition->RootDentry;
- Ext4RefDentry (RootDir->Dentry);
- return EFI_SUCCESS;
- }
- /**
- Reads a directory entry.
- @param[in] Partition Pointer to the ext4 partition.
- @param[in] File Pointer to the open directory.
- @param[out] Buffer Pointer to the output buffer.
- @param[in] Offset Initial directory position.
- @param[in out] OutLength Pointer to a UINTN that contains the length of the buffer,
- and the length of the actual EFI_FILE_INFO after the call.
- @return Result of the operation.
- **/
- EFI_STATUS
- Ext4ReadDir (
- IN EXT4_PARTITION *Partition,
- IN EXT4_FILE *File,
- OUT VOID *Buffer,
- IN UINT64 Offset,
- IN OUT UINTN *OutLength
- )
- {
- EXT4_INODE *DirIno;
- EFI_STATUS Status;
- UINT64 DirInoSize;
- UINTN Len;
- UINT32 BlockRemainder;
- EXT4_DIR_ENTRY Entry;
- EXT4_FILE *TempFile;
- BOOLEAN ShouldSkip;
- BOOLEAN IsDotOrDotDot;
- CHAR16 DirentUcs2Name[EXT4_NAME_MAX + 1];
- DirIno = File->Inode;
- Status = EFI_SUCCESS;
- DirInoSize = EXT4_INODE_SIZE (DirIno);
- DivU64x32Remainder (DirInoSize, Partition->BlockSize, &BlockRemainder);
- if (BlockRemainder != 0) {
- // Directory inodes need to have block aligned sizes
- return EFI_VOLUME_CORRUPTED;
- }
- while (TRUE) {
- TempFile = NULL;
- // We (try to) read the maximum size of a directory entry at a time
- // Note that we don't need to read any padding that may exist after it.
- Len = sizeof (Entry);
- Status = Ext4Read (Partition, File, &Entry, Offset, &Len);
- if (EFI_ERROR (Status)) {
- goto Out;
- }
- if (Len == 0) {
- *OutLength = 0;
- Status = EFI_SUCCESS;
- goto Out;
- }
- if (Len < EXT4_MIN_DIR_ENTRY_LEN) {
- Status = EFI_VOLUME_CORRUPTED;
- goto Out;
- }
- // Invalid directory entry length
- if (!Ext4ValidDirent (&Entry)) {
- DEBUG ((DEBUG_ERROR, "[ext4] Invalid dirent at offset %lu\n", Offset));
- Status = EFI_VOLUME_CORRUPTED;
- goto Out;
- }
- // Check if the entire dir entry length fits in Len
- if (Len < (UINTN)(EXT4_MIN_DIR_ENTRY_LEN + Entry.name_len)) {
- Status = EFI_VOLUME_CORRUPTED;
- goto Out;
- }
- // We don't care about passing . or .. entries to the caller of ReadDir(),
- // since they're generally useless entries *and* may break things if too
- // many callers assume FAT32.
- // Entry.name_len may be 0 if it's a nameless entry, like an unused entry
- // or a checksum at the end of the directory block.
- // memcmp (and CompareMem) return 0 when the passed length is 0.
- // We must bound name_len as > 0 and <= 2 to avoid any out-of-bounds accesses or bad detection of
- // "." and "..".
- IsDotOrDotDot = Entry.name_len > 0 && Entry.name_len <= 2 &&
- CompareMem (Entry.name, "..", Entry.name_len) == 0;
- // When inode = 0, it's unused. When name_len == 0, it's a nameless entry
- // (which we should not expose to ReadDir).
- ShouldSkip = Entry.inode == 0 || Entry.name_len == 0 || IsDotOrDotDot;
- if (ShouldSkip) {
- Offset += Entry.rec_len;
- continue;
- }
- // Test if the dirent is valid utf-8. This is already done inside Ext4OpenDirent but EFI_INVALID_PARAMETER
- // has the danger of its meaning being overloaded in many places, so we can't skip according to that.
- // So test outside of it, explicitly.
- Status = Ext4GetUcs2DirentName (&Entry, DirentUcs2Name);
- if (EFI_ERROR (Status)) {
- if (Status == EFI_INVALID_PARAMETER) {
- // Bad UTF-8, skip.
- Offset += Entry.rec_len;
- continue;
- }
- goto Out;
- }
- Status = Ext4OpenDirent (Partition, EFI_FILE_MODE_READ, &TempFile, &Entry, File);
- if (EFI_ERROR (Status)) {
- goto Out;
- }
- Status = Ext4GetFileInfo (TempFile, Buffer, OutLength);
- if (!EFI_ERROR (Status)) {
- File->Position = Offset + Entry.rec_len;
- }
- Ext4CloseInternal (TempFile);
- goto Out;
- }
- Status = EFI_SUCCESS;
- Out:
- return Status;
- }
- /**
- Removes a dentry from the other's list.
- @param[in out] Parent Pointer to the parent EXT4_DENTRY.
- @param[in out] ToBeRemoved Pointer to the child EXT4_DENTRY.
- **/
- STATIC
- VOID
- Ext4RemoveDentry (
- IN OUT EXT4_DENTRY *Parent,
- IN OUT EXT4_DENTRY *ToBeRemoved
- )
- {
- ASSERT (IsNodeInList (&ToBeRemoved->ListNode, &Parent->Children));
- RemoveEntryList (&ToBeRemoved->ListNode);
- }
- /**
- Adds a dentry to the other's list.
- The dentry that is added to the other one's list gets ->Parent set to Parent,
- and the parent gets its reference count incremented.
- @param[in out] Parent Pointer to the parent EXT4_DENTRY.
- @param[in out] ToBeAdded Pointer to the child EXT4_DENTRY.
- **/
- STATIC
- VOID
- Ext4AddDentry (
- IN OUT EXT4_DENTRY *Parent,
- IN OUT EXT4_DENTRY *ToBeAdded
- )
- {
- ToBeAdded->Parent = Parent;
- InsertTailList (&Parent->Children, &ToBeAdded->ListNode);
- Ext4RefDentry (Parent);
- }
- /**
- Creates a new dentry object.
- @param[in] Name Name of the dentry.
- @param[in out opt] Parent Parent dentry, if it's not NULL.
- @return The new allocated and initialised dentry.
- The ref count will be set to 1.
- **/
- EXT4_DENTRY *
- Ext4CreateDentry (
- IN CONST CHAR16 *Name,
- IN OUT EXT4_DENTRY *Parent OPTIONAL
- )
- {
- EXT4_DENTRY *Dentry;
- EFI_STATUS Status;
- Dentry = AllocateZeroPool (sizeof (EXT4_DENTRY));
- if (Dentry == NULL) {
- return NULL;
- }
- Dentry->RefCount = 1;
- // This StrCpyS should not fail.
- Status = StrCpyS (Dentry->Name, ARRAY_SIZE (Dentry->Name), Name);
- ASSERT_EFI_ERROR (Status);
- InitializeListHead (&Dentry->Children);
- if (Parent != NULL) {
- Ext4AddDentry (Parent, Dentry);
- }
- DEBUG ((DEBUG_FS, "[ext4] Created dentry %s\n", Name));
- return Dentry;
- }
- /**
- Increments the ref count of the dentry.
- @param[in out] Dentry Pointer to a valid EXT4_DENTRY.
- **/
- VOID
- Ext4RefDentry (
- IN OUT EXT4_DENTRY *Dentry
- )
- {
- UINTN OldRef;
- OldRef = Dentry->RefCount;
- Dentry->RefCount++;
- // I'm not sure if this (Refcount overflow) is a valid concern,
- // but it's better to be safe than sorry.
- ASSERT (OldRef < Dentry->RefCount);
- }
- /**
- Deletes the dentry.
- @param[in out] Dentry Pointer to a valid EXT4_DENTRY.
- **/
- STATIC
- VOID
- Ext4DeleteDentry (
- IN OUT EXT4_DENTRY *Dentry
- )
- {
- if (Dentry->Parent) {
- Ext4RemoveDentry (Dentry->Parent, Dentry);
- Ext4UnrefDentry (Dentry->Parent);
- }
- DEBUG ((DEBUG_FS, "[ext4] Deleted dentry %s\n", Dentry->Name));
- FreePool (Dentry);
- }
- /**
- Decrements the ref count of the dentry.
- If the ref count is 0, it's destroyed.
- @param[in out] Dentry Pointer to a valid EXT4_DENTRY.
- @retval True if it was destroyed, false if it's alive.
- **/
- BOOLEAN
- Ext4UnrefDentry (
- IN OUT EXT4_DENTRY *Dentry
- )
- {
- Dentry->RefCount--;
- if (Dentry->RefCount == 0) {
- Ext4DeleteDentry (Dentry);
- return TRUE;
- }
- return FALSE;
- }
|