X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;ds=sidebyside;f=lib%2Fefi_loader%2Fefi_file.c;h=182d735e6bc28cadf6a83823cdb5d344020480e0;hb=e6023be41e94eb2bab4c93d343ac3deddfe12c7b;hp=52a4e7438e33c2d6dfddda22debdde81f1a82a34;hpb=2a92080d8c4463f2ddbf4e3110c25ab55ef6ca3f;p=oweals%2Fu-boot.git diff --git a/lib/efi_loader/efi_file.c b/lib/efi_loader/efi_file.c index 52a4e7438e..182d735e6b 100644 --- a/lib/efi_loader/efi_file.c +++ b/lib/efi_loader/efi_file.c @@ -1,17 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * EFI utils * * Copyright (c) 2017 Rob Clark - * - * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include +#include #include +/* GUID for file system information */ +const efi_guid_t efi_file_system_info_guid = EFI_FILE_SYSTEM_INFO_GUID; + struct file_system { struct efi_simple_file_system_protocol base; struct efi_device_path *dp; @@ -49,11 +52,18 @@ static int set_blk_dev(struct file_handle *fh) return fs_set_blk_dev_with_part(fh->fs->desc, fh->fs->part); } +/** + * is_dir() - check if file handle points to directory + * + * We assume that set_blk_dev(fh) has been called already. + * + * @fh: file handle + * Return: true if file handle points to a directory + */ static int is_dir(struct file_handle *fh) { struct fs_dir_stream *dirs; - set_blk_dev(fh); dirs = fs_opendir(fh->path); if (!dirs) return 0; @@ -124,11 +134,41 @@ static int sanitize_path(char *path) return 0; } -/* NOTE: despite what you would expect, 'file_name' is actually a path. - * With windoze style backlashes, ofc. +/** + * efi_create_file() - create file or directory + * + * @fh: file handle + * @attributes: attributes for newly created file + * Returns: 0 for success + */ +static int efi_create_file(struct file_handle *fh, u64 attributes) +{ + loff_t actwrite; + void *buffer = &actwrite; + + if (attributes & EFI_FILE_DIRECTORY) + return fs_mkdir(fh->path); + else + return fs_write(fh->path, map_to_sysmem(buffer), 0, 0, + &actwrite); +} + +/** + * file_open() - open a file handle + * + * @fs: file system + * @parent: directory relative to which the file is to be opened + * @file_name: path of the file to be opened. '\', '.', or '..' may + * be used as modifiers. A leading backslash indicates an + * absolute path. + * @mode: bit mask indicating the access mode (read, write, + * create) + * @attributes: attributes for newly created file + * Returns: handle to the opened file or NULL */ static struct efi_file_handle *file_open(struct file_system *fs, - struct file_handle *parent, s16 *file_name, u64 mode) + struct file_handle *parent, u16 *file_name, u64 mode, + u64 attributes) { struct file_handle *fh; char f0[MAX_UTF8_PER_UTF16] = {0}; @@ -136,8 +176,8 @@ static struct efi_file_handle *file_open(struct file_system *fs, int flen = 0; if (file_name) { - utf16_to_utf8((u8 *)f0, (u16 *)file_name, 1); - flen = utf16_strlen((u16 *)file_name); + utf16_to_utf8((u8 *)f0, file_name, 1); + flen = u16_strlen(file_name); } /* we could have a parent, but also an absolute path: */ @@ -155,6 +195,7 @@ static struct efi_file_handle *file_open(struct file_system *fs, if (parent) { char *p = fh->path; + int exists; if (plen > 0) { strcpy(p, parent->path); @@ -162,7 +203,7 @@ static struct efi_file_handle *file_open(struct file_system *fs, *p++ = '/'; } - utf16_to_utf8((u8 *)p, (u16 *)file_name, flen); + utf16_to_utf8((u8 *)p, file_name, flen); if (sanitize_path(fh->path)) goto error; @@ -171,9 +212,17 @@ static struct efi_file_handle *file_open(struct file_system *fs, if (set_blk_dev(fh)) goto error; - if (!((mode & EFI_FILE_MODE_CREATE) || fs_exists(fh->path))) + exists = fs_exists(fh->path); + /* fs_exists() calls fs_close(), so open file system again */ + if (set_blk_dev(fh)) goto error; + if (!exists) { + if (!(mode & EFI_FILE_MODE_CREATE) || + efi_create_file(fh, attributes)) + goto error; + } + /* figure out if file is a directory: */ fh->isdir = is_dir(fh); } else { @@ -190,18 +239,51 @@ error: static efi_status_t EFIAPI efi_file_open(struct efi_file_handle *file, struct efi_file_handle **new_handle, - s16 *file_name, u64 open_mode, u64 attributes) + u16 *file_name, u64 open_mode, u64 attributes) { struct file_handle *fh = to_fh(file); + efi_status_t ret; - EFI_ENTRY("%p, %p, \"%ls\", %llx, %llu", file, new_handle, file_name, - open_mode, attributes); + EFI_ENTRY("%p, %p, \"%ls\", %llx, %llu", file, new_handle, + file_name, open_mode, attributes); - *new_handle = file_open(fh->fs, fh, file_name, open_mode); - if (!*new_handle) - return EFI_EXIT(EFI_NOT_FOUND); + /* Check parameters */ + if (!file || !new_handle || !file_name) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (open_mode != EFI_FILE_MODE_READ && + open_mode != (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE) && + open_mode != (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | + EFI_FILE_MODE_CREATE)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + /* + * The UEFI spec requires that attributes are only set in create mode. + * The SCT does not care about this and sets EFI_FILE_DIRECTORY in + * read mode. EDK2 does not check that attributes are zero if not in + * create mode. + * + * So here we only check attributes in create mode and do not check + * that they are zero otherwise. + */ + if ((open_mode & EFI_FILE_MODE_CREATE) && + (attributes & (EFI_FILE_READ_ONLY | ~EFI_FILE_VALID_ATTR))) { + ret = EFI_INVALID_PARAMETER; + goto out; + } - return EFI_EXIT(EFI_SUCCESS); + /* Open file */ + *new_handle = file_open(fh->fs, fh, file_name, open_mode, attributes); + if (*new_handle) { + EFI_PRINT("file handle %p\n", *new_handle); + ret = EFI_SUCCESS; + } else { + ret = EFI_NOT_FOUND; + } +out: + return EFI_EXIT(ret); } static efi_status_t file_close(struct file_handle *fh) @@ -221,9 +303,21 @@ static efi_status_t EFIAPI efi_file_close(struct efi_file_handle *file) static efi_status_t EFIAPI efi_file_delete(struct efi_file_handle *file) { struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + EFI_ENTRY("%p", file); + + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + if (fs_unlink(fh->path)) + ret = EFI_DEVICE_ERROR; file_close(fh); - return EFI_EXIT(EFI_WARN_DELETE_FAILURE); + +error: + return EFI_EXIT(ret); } static efi_status_t file_read(struct file_handle *fh, u64 *buffer_size, @@ -231,7 +325,7 @@ static efi_status_t file_read(struct file_handle *fh, u64 *buffer_size, { loff_t actread; - if (fs_read(fh->path, (ulong)buffer, fh->offset, + if (fs_read(fh->path, map_to_sysmem(buffer), fh->offset, *buffer_size, &actread)) return EFI_DEVICE_ERROR; @@ -306,7 +400,7 @@ static efi_status_t dir_read(struct file_handle *fh, u64 *buffer_size, if (dent->type == FS_DT_DIR) info->attribute |= EFI_FILE_DIRECTORY; - ascii2unicode((u16 *)info->file_name, dent->name); + ascii2unicode(info->file_name, dent->name); fh->offset++; @@ -314,29 +408,41 @@ static efi_status_t dir_read(struct file_handle *fh, u64 *buffer_size, } static efi_status_t EFIAPI efi_file_read(struct efi_file_handle *file, - u64 *buffer_size, void *buffer) + efi_uintn_t *buffer_size, void *buffer) { struct file_handle *fh = to_fh(file); efi_status_t ret = EFI_SUCCESS; + u64 bs; EFI_ENTRY("%p, %p, %p", file, buffer_size, buffer); + if (!buffer_size || !buffer) { + ret = EFI_INVALID_PARAMETER; + goto error; + } + if (set_blk_dev(fh)) { ret = EFI_DEVICE_ERROR; goto error; } + bs = *buffer_size; if (fh->isdir) - ret = dir_read(fh, buffer_size, buffer); + ret = dir_read(fh, &bs, buffer); else - ret = file_read(fh, buffer_size, buffer); + ret = file_read(fh, &bs, buffer); + if (bs <= SIZE_MAX) + *buffer_size = bs; + else + *buffer_size = SIZE_MAX; error: return EFI_EXIT(ret); } static efi_status_t EFIAPI efi_file_write(struct efi_file_handle *file, - u64 *buffer_size, void *buffer) + efi_uintn_t *buffer_size, + void *buffer) { struct file_handle *fh = to_fh(file); efi_status_t ret = EFI_SUCCESS; @@ -349,7 +455,7 @@ static efi_status_t EFIAPI efi_file_write(struct efi_file_handle *file, goto error; } - if (fs_write(fh->path, (ulong)buffer, fh->offset, *buffer_size, + if (fs_write(fh->path, map_to_sysmem(buffer), fh->offset, *buffer_size, &actwrite)) { ret = EFI_DEVICE_ERROR; goto error; @@ -362,17 +468,46 @@ error: return EFI_EXIT(ret); } +/** + * efi_file_getpos() - get current position in file + * + * This function implements the GetPosition service of the EFI file protocol. + * See the UEFI spec for details. + * + * @file: file handle + * @pos: pointer to file position + * Return: status code + */ static efi_status_t EFIAPI efi_file_getpos(struct efi_file_handle *file, - u64 *pos) + u64 *pos) { + efi_status_t ret = EFI_SUCCESS; struct file_handle *fh = to_fh(file); + EFI_ENTRY("%p, %p", file, pos); + + if (fh->isdir) { + ret = EFI_UNSUPPORTED; + goto out; + } + *pos = fh->offset; - return EFI_EXIT(EFI_SUCCESS); +out: + return EFI_EXIT(ret); } +/** + * efi_file_setpos() - set current position in file + * + * This function implements the SetPosition service of the EFI file protocol. + * See the UEFI spec for details. + * + * @file: file handle + * @pos: new file position + * Return: status code + */ static efi_status_t EFIAPI efi_file_setpos(struct efi_file_handle *file, - u64 pos) + u64 pos) { struct file_handle *fh = to_fh(file); efi_status_t ret = EFI_SUCCESS; @@ -411,12 +546,14 @@ error: } static efi_status_t EFIAPI efi_file_getinfo(struct efi_file_handle *file, - efi_guid_t *info_type, u64 *buffer_size, void *buffer) + const efi_guid_t *info_type, + efi_uintn_t *buffer_size, + void *buffer) { struct file_handle *fh = to_fh(file); efi_status_t ret = EFI_SUCCESS; - EFI_ENTRY("%p, %p, %p, %p", file, info_type, buffer_size, buffer); + EFI_ENTRY("%p, %pUl, %p, %p", file, info_type, buffer_size, buffer); if (!guidcmp(info_type, &efi_file_info_guid)) { struct efi_file_info *info = buffer; @@ -451,7 +588,42 @@ static efi_status_t EFIAPI efi_file_getinfo(struct efi_file_handle *file, if (fh->isdir) info->attribute |= EFI_FILE_DIRECTORY; - ascii2unicode((u16 *)info->file_name, filename); + ascii2unicode(info->file_name, filename); + } else if (!guidcmp(info_type, &efi_file_system_info_guid)) { + struct efi_file_system_info *info = buffer; + disk_partition_t part; + efi_uintn_t required_size; + int r; + + if (fh->fs->part >= 1) + r = part_get_info(fh->fs->desc, fh->fs->part, &part); + else + r = part_get_info_whole_disk(fh->fs->desc, &part); + if (r < 0) { + ret = EFI_DEVICE_ERROR; + goto error; + } + required_size = sizeof(info) + 2 * + (strlen((const char *)part.name) + 1); + if (*buffer_size < required_size) { + *buffer_size = required_size; + ret = EFI_BUFFER_TOO_SMALL; + goto error; + } + + memset(info, 0, required_size); + + info->size = required_size; + info->read_only = true; + info->volume_size = part.size * part.blksz; + info->free_space = 0; + info->block_size = part.blksz; + /* + * TODO: The volume label is not available in U-Boot. + * Use the partition name as substitute. + */ + ascii2unicode((u16 *)info->volume_label, + (const char *)part.name); } else { ret = EFI_UNSUPPORTED; } @@ -461,10 +633,76 @@ error: } static efi_status_t EFIAPI efi_file_setinfo(struct efi_file_handle *file, - efi_guid_t *info_type, u64 buffer_size, void *buffer) + const efi_guid_t *info_type, + efi_uintn_t buffer_size, + void *buffer) { - EFI_ENTRY("%p, %p, %llu, %p", file, info_type, buffer_size, buffer); - return EFI_EXIT(EFI_UNSUPPORTED); + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_UNSUPPORTED; + + EFI_ENTRY("%p, %pUl, %zu, %p", file, info_type, buffer_size, buffer); + + if (!guidcmp(info_type, &efi_file_info_guid)) { + struct efi_file_info *info = (struct efi_file_info *)buffer; + char *filename = basename(fh); + char *new_file_name, *pos; + loff_t file_size; + + if (buffer_size < sizeof(struct efi_file_info)) { + ret = EFI_BAD_BUFFER_SIZE; + goto out; + } + /* We cannot change the directory attribute */ + if (!fh->isdir != !(info->attribute & EFI_FILE_DIRECTORY)) { + ret = EFI_ACCESS_DENIED; + goto out; + } + /* Check for renaming */ + new_file_name = malloc(utf16_utf8_strlen(info->file_name)); + if (!new_file_name) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + pos = new_file_name; + utf16_utf8_strcpy(&pos, info->file_name); + if (strcmp(new_file_name, filename)) { + /* TODO: we do not support renaming */ + EFI_PRINT("Renaming not supported\n"); + free(new_file_name); + ret = EFI_ACCESS_DENIED; + goto out; + } + free(new_file_name); + /* Check for truncation */ + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + if (fs_size(fh->path, &file_size)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + if (file_size != info->file_size) { + /* TODO: we do not support truncation */ + EFI_PRINT("Truncation not supported\n"); + ret = EFI_ACCESS_DENIED; + goto out; + } + /* + * We do not care for the other attributes + * TODO: Support read only + */ + ret = EFI_SUCCESS; + } else if (!guidcmp(info_type, &efi_file_system_info_guid)) { + if (buffer_size < sizeof(struct efi_file_system_info)) { + ret = EFI_BAD_BUFFER_SIZE; + goto out; + } + } else { + ret = EFI_UNSUPPORTED; + } +out: + return EFI_EXIT(ret); } static efi_status_t EFIAPI efi_file_flush(struct efi_file_handle *file) @@ -474,6 +712,10 @@ static efi_status_t EFIAPI efi_file_flush(struct efi_file_handle *file) } static const struct efi_file_handle efi_file_handle_protocol = { + /* + * TODO: We currently only support EFI file protocol revision 0x00010000 + * while UEFI specs 2.4 - 2.7 prescribe revision 0x00020000. + */ .rev = EFI_FILE_PROTOCOL_REVISION, .open = efi_file_open, .close = efi_file_close, @@ -487,6 +729,12 @@ static const struct efi_file_handle efi_file_handle_protocol = { .flush = efi_file_flush, }; +/** + * efi_file_from_path() - open file via device path + * + * @fp: device path + * @return: EFI_FILE_PROTOCOL for the file or NULL + */ struct efi_file_handle *efi_file_from_path(struct efi_device_path *fp) { struct efi_simple_file_system_protocol *v; @@ -501,10 +749,14 @@ struct efi_file_handle *efi_file_from_path(struct efi_device_path *fp) if (ret != EFI_SUCCESS) return NULL; - /* skip over device-path nodes before the file path: */ + /* Skip over device-path nodes before the file path. */ while (fp && !EFI_DP_TYPE(fp, MEDIA_DEVICE, FILE_PATH)) fp = efi_dp_next(fp); + /* + * Step through the nodes of the directory path until the actual file + * node is reached which is the final node in the device path. + */ while (fp) { struct efi_device_path_file_path *fdp = container_of(fp, struct efi_device_path_file_path, dp); @@ -516,7 +768,7 @@ struct efi_file_handle *efi_file_from_path(struct efi_device_path *fp) return NULL; } - EFI_CALL(ret = f->open(f, &f2, (s16 *)fdp->str, + EFI_CALL(ret = f->open(f, &f2, fdp->str, EFI_FILE_MODE_READ, 0)); if (ret != EFI_SUCCESS) return NULL; @@ -538,7 +790,7 @@ efi_open_volume(struct efi_simple_file_system_protocol *this, EFI_ENTRY("%p, %p", this, root); - *root = file_open(fs, NULL, NULL, 0); + *root = file_open(fs, NULL, NULL, 0, 0); return EFI_EXIT(EFI_SUCCESS); }