/* GRUB uses a callback for each directory entry, whereas EFI uses repeated * firmware generated calls to FileReadDir() to get the info for each entry, * so we have to reconcile the twos. For now, we'll re-issue a call to GRUB * dir(), and run through all the entries (to find the one we * are interested in) multiple times. Maybe later we'll try to optimize this * by building a one-off chained list of entries that we can parse... */ static INT32 DirHook(const CHAR8 *name, const GRUB_DIRHOOK_INFO *DirInfo, VOID *Data) { EFI_STATUS Status; EFI_FILE_INFO *Info = (EFI_FILE_INFO *) Data; INT64 *Index = (INT64 *) &Info->FileSize; CHAR8 *filename = (CHAR8 *) Info->PhysicalSize; EFI_TIME Time = { 1970, 01, 01, 00, 00, 00, 0, 0, 0, 0, 0}; // Eliminate '.' or '..' if ((name[0] == '.') && ((name[1] == 0) || ((name[1] == '.') && (name[2] == 0)))) return 0; /* Ignore any entry that doesn't match our index */ if ((*Index)-- != 0) return 0; strcpya(filename, name); Status = Utf8ToUtf16NoAlloc(filename, Info->FileName, (INTN)(Info->Size - sizeof(EFI_FILE_INFO))); if (EFI_ERROR(Status)) { if (Status != EFI_BUFFER_TOO_SMALL) PrintStatusError(Status, L"Could not convert directory entry to UTF-8"); return (INT32) Status; } /* The Info struct size already accounts for the extra NUL */ Info->Size = sizeof(*Info) + StrLen(Info->FileName) * sizeof(CHAR16); // Oh, and of course GRUB uses a 32 bit signed mtime value (seriously, wtf guys?!?) if (DirInfo->MtimeSet) GrubTimeToEfiTime(DirInfo->Mtime, &Time); CopyMem(&Info->CreateTime, &Time, sizeof(Time)); CopyMem(&Info->LastAccessTime, &Time, sizeof(Time)); CopyMem(&Info->ModificationTime, &Time, sizeof(Time)); Info->Attribute = EFI_FILE_READ_ONLY; if (DirInfo->Dir) Info->Attribute |= EFI_FILE_DIRECTORY; return 0; }
/** * Open file * * @v This File handle * @ret new New file handle * @v Name File name * @v Mode File mode * @v Attributes File attributes (for newly-created files) * @ret Status EFI status code */ static EFI_STATUS EFIAPI FileOpen(EFI_FILE_HANDLE This, EFI_FILE_HANDLE *New, CHAR16 *Name, UINT64 Mode, UINT64 Attributes) { EFI_STATUS Status; EFI_GRUB_FILE *File = _CR(This, EFI_GRUB_FILE, EfiFile); EFI_GRUB_FILE *NewFile; // TODO: Use dynamic buffers? char path[MAX_PATH], clean_path[MAX_PATH], *dirname; INTN i, len; BOOLEAN AbsolutePath = (*Name == L'\\'); PrintInfo(L"Open(%llx%s, \"%s\")\n", (UINT64) This, IS_ROOT(File)?L" <ROOT>":L"", Name); /* Fail unless opening read-only */ if (Mode != EFI_FILE_MODE_READ) { PrintWarning(L"File '%s' can only be opened in read-only mode\n", Name); return EFI_WRITE_PROTECTED; } /* Additional failures */ if ((StrCmp(Name, L"..") == 0) && IS_ROOT(File)) { PrintInfo(L"Trying to open <ROOT>'s parent\n"); return EFI_NOT_FOUND; } /* See if we're trying to reopen current (which the EFI Shell insists on doing) */ if ((*Name == 0) || (StrCmp(Name, L".") == 0)) { PrintInfo(L" Reopening %s\n", IS_ROOT(File)?L"<ROOT>":FileName(File)); File->RefCount++; *New = This; PrintInfo(L" RET: %llx\n", (UINT64) *New); return EFI_SUCCESS; } /* If we have an absolute path, don't bother completing with the parent */ if (AbsolutePath) { len = 0; } else { strcpya(path, File->path); len = strlena(path); /* Add delimiter if needed */ if ((len == 0) || (path[len-1] != '/')) path[len++] = '/'; } /* Copy the rest of the path (converted to UTF-8) */ Status = Utf16ToUtf8NoAlloc(Name, &path[len], sizeof(path) - len); if (EFI_ERROR(Status)) { PrintStatusError(Status, L"Could not convert path to UTF-8"); return Status; } /* Convert the delimiters */ for (i = strlena(path) - 1 ; i >= len; i--) { if (path[i] == '\\') path[i] = '/'; } /* We only want to handle with absolute paths */ clean_path[0] = '/'; /* Find out if we're dealing with root by removing the junk */ CopyPathRelative(&clean_path[1], path, MAX_PATH - 1); if (clean_path[1] == 0) { /* We're dealing with the root */ PrintInfo(L" Reopening <ROOT>\n"); *New = &File->FileSystem->RootFile->EfiFile; /* Must make sure that DirIndex is reset too (NB: no concurrent access!) */ File->FileSystem->RootFile->DirIndex = 0; PrintInfo(L" RET: %llx\n", (UINT64) *New); return EFI_SUCCESS; } // TODO: eventually we should seek for already opened files and increase RefCount */ /* Allocate and initialise an instance of a file */ Status = GrubCreateFile(&NewFile, File->FileSystem); if (EFI_ERROR(Status)) { PrintStatusError(Status, L"Could not instantiate file"); return Status; } NewFile->path = AllocatePool(strlena(clean_path)+1); if (NewFile->path == NULL) { GrubDestroyFile(NewFile); PrintError(L"Could not instantiate path\n"); return EFI_OUT_OF_RESOURCES; } strcpya(NewFile->path, clean_path); /* Isolate the basename and dirname */ // TODO: would be nicer to have those if path.c for (i = strlena(clean_path) - 1; i >= 0; i--) { if (clean_path[i] == '/') { clean_path[i] = 0; break; } } dirname = (i <= 0) ? "/" : clean_path; NewFile->basename = &NewFile->path[i+1]; /* Find if we're working with a directory and fill the grub timestamp */ Status = GrubDir(NewFile, dirname, InfoHook, (VOID *) NewFile); if (EFI_ERROR(Status)) { if (Status != EFI_NOT_FOUND) PrintStatusError(Status, L"Could not get file attributes for '%s'", Name); FreePool(NewFile->path); GrubDestroyFile(NewFile); return Status; } /* Finally we can call on GRUB open() if it's a regular file */ if (!NewFile->IsDir) { Status = GrubOpen(NewFile); if (EFI_ERROR(Status)) { if (Status != EFI_NOT_FOUND) PrintStatusError(Status, L"Could not open file '%s'", Name); FreePool(NewFile->path); GrubDestroyFile(NewFile); return Status; } } NewFile->RefCount++; *New = &NewFile->EfiFile; PrintInfo(L" RET: %llx\n", (UINT64) *New); return EFI_SUCCESS; }
/** * Read directory entry * * @v file EFI file * @v Len Length to read * @v Data Data buffer * @ret Status EFI status code */ static EFI_STATUS FileReadDir(EFI_GRUB_FILE *File, UINTN *Len, VOID *Data) { EFI_FILE_INFO *Info = (EFI_FILE_INFO *) Data; EFI_STATUS Status; /* We temporarily repurpose the FileSize as a *signed* entry index */ INT64 *Index = (INT64 *) &Info->FileSize; /* And PhysicalSize as a pointer to our filename */ CHAR8 **basename = (CHAR8 **) &Info->PhysicalSize; CHAR8 path[MAX_PATH]; EFI_GRUB_FILE *TmpFile = NULL; INTN len; /* Unless we can fit our maximum size, forget it */ if (*Len < MINIMUM_INFO_LENGTH) { *Len = MINIMUM_INFO_LENGTH; return EFI_BUFFER_TOO_SMALL; } /* Populate our Info template */ ZeroMem(Data, *Len); Info->Size = *Len; *Index = File->DirIndex; strcpya(path, File->path); len = strlena(path); if (path[len-1] != '/') path[len++] = '/'; *basename = &path[len]; /* Invoke GRUB's directory listing */ Status = GrubDir(File, File->path, DirHook, Data); if (*Index >= 0) { /* No more entries */ *Len = 0; return EFI_SUCCESS; } if (EFI_ERROR(Status)) { PrintStatusError(Status, L"Directory listing failed"); return Status; } /* Our Index/FileSize must be reset */ Info->FileSize = 0; Info->PhysicalSize = 0; /* For regular files, we still need to fill the size */ if (!(Info->Attribute & EFI_FILE_DIRECTORY)) { /* Open the file and read its size */ Status = GrubCreateFile(&TmpFile, File->FileSystem); if (EFI_ERROR(Status)) { PrintStatusError(Status, L"Unable to create temporary file"); return Status; } TmpFile->path = path; Status = GrubOpen(TmpFile); if (EFI_ERROR(Status)) { // TODO: EFI_NO_MAPPING is returned for links... PrintStatusError(Status, L"Unable to obtain the size of '%s'", Info->FileName); /* Non fatal error */ } else { Info->FileSize = GrubGetFileSize(TmpFile); Info->PhysicalSize = GrubGetFileSize(TmpFile); GrubClose(TmpFile); } GrubDestroyFile(TmpFile); } *Len = (UINTN) Info->Size; /* Advance to the next entry */ File->DirIndex++; // PrintInfo(L" Entry[%d]: '%s' %s\n", File->DirIndex-1, Info->FileName, // (Info->Attribute&EFI_FILE_DIRECTORY)?L"<DIR>":L""); return EFI_SUCCESS; }
CHAR8 * strcata(IN CHAR8 *dst,IN CHAR8 *src) { return strcpya(dst+strlena(dst), src); }