/* erofs.c - Enhanced Read-Only File System */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2024 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
GRUB_MOD_LICENSE ("GPLv3+");
#define EROFS_SUPER_OFFSET 1024
#define EROFS_MAGIC 0xE0F5E1E2
#define EROFS_ISLOTBITS 5
#define EROFS_FEATURE_INCOMPAT_CHUNKED_FILE 0x00000004
#define EROFS_ALL_FEATURE_INCOMPAT EROFS_FEATURE_INCOMPAT_CHUNKED_FILE
struct grub_erofs_super
{
grub_uint32_t magic;
grub_uint32_t checksum;
grub_uint32_t feature_compat;
grub_uint8_t log2_blksz;
grub_uint8_t sb_extslots;
grub_uint16_t root_nid;
grub_uint64_t inos;
grub_uint64_t build_time;
grub_uint32_t build_time_nsec;
grub_uint32_t blocks;
grub_uint32_t meta_blkaddr;
grub_uint32_t xattr_blkaddr;
grub_packed_guid_t uuid;
grub_uint8_t volume_name[16];
grub_uint32_t feature_incompat;
union
{
grub_uint16_t available_compr_algs;
grub_uint16_t lz4_max_distance;
} GRUB_PACKED u1;
grub_uint16_t extra_devices;
grub_uint16_t devt_slotoff;
grub_uint8_t log2_dirblksz;
grub_uint8_t xattr_prefix_count;
grub_uint32_t xattr_prefix_start;
grub_uint64_t packed_nid;
grub_uint8_t reserved2[24];
} GRUB_PACKED;
#define EROFS_INODE_LAYOUT_COMPACT 0
#define EROFS_INODE_LAYOUT_EXTENDED 1
#define EROFS_INODE_FLAT_PLAIN 0
#define EROFS_INODE_COMPRESSED_FULL 1
#define EROFS_INODE_FLAT_INLINE 2
#define EROFS_INODE_COMPRESSED_COMPACT 3
#define EROFS_INODE_CHUNK_BASED 4
#define EROFS_I_VERSION_MASKS 0x01
#define EROFS_I_DATALAYOUT_MASKS 0x07
#define EROFS_I_VERSION_BIT 0
#define EROFS_I_DATALAYOUT_BIT 1
struct grub_erofs_inode_chunk_info
{
grub_uint16_t format;
grub_uint16_t reserved;
} GRUB_PACKED;
#define EROFS_CHUNK_FORMAT_BLKBITS_MASK 0x001F
#define EROFS_CHUNK_FORMAT_INDEXES 0x0020
#define EROFS_BLOCK_MAP_ENTRY_SIZE 4
#define EROFS_MAP_MAPPED 0x02
#define EROFS_NULL_ADDR 1
#define EROFS_NAME_LEN 255
#define EROFS_PATH_LEN 4096
#define EROFS_MIN_LOG2_BLOCK_SIZE 9
#define EROFS_MAX_LOG2_BLOCK_SIZE 16
struct grub_erofs_inode_chunk_index
{
grub_uint16_t advise;
grub_uint16_t device_id;
grub_uint32_t blkaddr;
};
union grub_erofs_inode_i_u
{
grub_uint32_t compressed_blocks;
grub_uint32_t raw_blkaddr;
grub_uint32_t rdev;
struct grub_erofs_inode_chunk_info c;
};
struct grub_erofs_inode_compact
{
grub_uint16_t i_format;
grub_uint16_t i_xattr_icount;
grub_uint16_t i_mode;
grub_uint16_t i_nlink;
grub_uint32_t i_size;
grub_uint32_t i_reserved;
union grub_erofs_inode_i_u i_u;
grub_uint32_t i_ino;
grub_uint16_t i_uid;
grub_uint16_t i_gid;
grub_uint32_t i_reserved2;
} GRUB_PACKED;
struct grub_erofs_inode_extended
{
grub_uint16_t i_format;
grub_uint16_t i_xattr_icount;
grub_uint16_t i_mode;
grub_uint16_t i_reserved;
grub_uint64_t i_size;
union grub_erofs_inode_i_u i_u;
grub_uint32_t i_ino;
grub_uint32_t i_uid;
grub_uint32_t i_gid;
grub_uint64_t i_mtime;
grub_uint32_t i_mtime_nsec;
grub_uint32_t i_nlink;
grub_uint8_t i_reserved2[16];
} GRUB_PACKED;
union grub_erofs_inode
{
struct grub_erofs_inode_compact c;
struct grub_erofs_inode_extended e;
} GRUB_PACKED;
#define EROFS_FT_UNKNOWN 0
#define EROFS_FT_REG_FILE 1
#define EROFS_FT_DIR 2
#define EROFS_FT_CHRDEV 3
#define EROFS_FT_BLKDEV 4
#define EROFS_FT_FIFO 5
#define EROFS_FT_SOCK 6
#define EROFS_FT_SYMLINK 7
struct grub_erofs_dirent
{
grub_uint64_t nid;
grub_uint16_t nameoff;
grub_uint8_t file_type;
grub_uint8_t reserved;
} GRUB_PACKED;
struct grub_erofs_map_blocks
{
grub_uint64_t m_pa; /* physical address */
grub_uint64_t m_la; /* logical address */
grub_uint64_t m_plen; /* physical length */
grub_uint64_t m_llen; /* logical length */
grub_uint32_t m_flags;
};
struct grub_erofs_xattr_ibody_header
{
grub_uint32_t h_reserved;
grub_uint8_t h_shared_count;
grub_uint8_t h_reserved2[7];
grub_uint32_t h_shared_xattrs[0];
};
struct grub_fshelp_node
{
struct grub_erofs_data *data;
union grub_erofs_inode inode;
grub_uint64_t ino;
grub_uint8_t inode_type;
grub_uint8_t inode_datalayout;
/* If the inode has been read into memory? */
bool inode_loaded;
};
struct grub_erofs_data
{
grub_disk_t disk;
struct grub_erofs_super sb;
struct grub_fshelp_node inode;
};
#define erofs_blocksz(data) (((grub_uint32_t) 1) << data->sb.log2_blksz)
static grub_size_t
grub_erofs_strnlen (const char *s, grub_size_t n)
{
const char *p = s;
if (n == 0)
return 0;
while (n-- && *p)
p++;
return p - s;
}
static grub_uint64_t
erofs_iloc (grub_fshelp_node_t node)
{
struct grub_erofs_super *sb = &node->data->sb;
return ((grub_uint64_t) grub_le_to_cpu32 (sb->meta_blkaddr) << sb->log2_blksz) +
(node->ino << EROFS_ISLOTBITS);
}
static grub_err_t
erofs_read_inode (struct grub_erofs_data *data, grub_fshelp_node_t node)
{
union grub_erofs_inode *di;
grub_err_t err;
grub_uint16_t i_format;
grub_uint64_t addr = erofs_iloc (node);
di = (union grub_erofs_inode *) &node->inode;
err = grub_disk_read (data->disk, addr >> GRUB_DISK_SECTOR_BITS,
addr & (GRUB_DISK_SECTOR_SIZE - 1),
sizeof (struct grub_erofs_inode_compact), &di->c);
if (err != GRUB_ERR_NONE)
return err;
i_format = grub_le_to_cpu16 (di->c.i_format);
node->inode_type = (i_format >> EROFS_I_VERSION_BIT) & EROFS_I_VERSION_MASKS;
node->inode_datalayout = (i_format >> EROFS_I_DATALAYOUT_BIT) & EROFS_I_DATALAYOUT_MASKS;
switch (node->inode_type)
{
case EROFS_INODE_LAYOUT_EXTENDED:
addr += sizeof (struct grub_erofs_inode_compact);
err = grub_disk_read (data->disk, addr >> GRUB_DISK_SECTOR_BITS,
addr & (GRUB_DISK_SECTOR_SIZE - 1),
sizeof (struct grub_erofs_inode_extended) - sizeof (struct grub_erofs_inode_compact),
(grub_uint8_t *) di + sizeof (struct grub_erofs_inode_compact));
if (err != GRUB_ERR_NONE)
return err;
break;
case EROFS_INODE_LAYOUT_COMPACT:
break;
default:
return grub_error (GRUB_ERR_BAD_FS, "invalid type %u @ inode %" PRIuGRUB_UINT64_T,
node->inode_type, node->ino);
}
node->inode_loaded = true;
return 0;
}
static grub_uint64_t
erofs_inode_size (grub_fshelp_node_t node)
{
return node->inode_type == EROFS_INODE_LAYOUT_COMPACT
? sizeof (struct grub_erofs_inode_compact)
: sizeof (struct grub_erofs_inode_extended);
}
static grub_uint64_t
erofs_inode_file_size (grub_fshelp_node_t node)
{
union grub_erofs_inode *di = (union grub_erofs_inode *) &node->inode;
return node->inode_type == EROFS_INODE_LAYOUT_COMPACT
? grub_le_to_cpu32 (di->c.i_size)
: grub_le_to_cpu64 (di->e.i_size);
}
static grub_uint32_t
erofs_inode_xattr_ibody_size (grub_fshelp_node_t node)
{
grub_uint16_t cnt = grub_le_to_cpu16 (node->inode.e.i_xattr_icount);
if (cnt == 0)
return 0;
return sizeof (struct grub_erofs_xattr_ibody_header) + ((cnt - 1) * sizeof (grub_uint32_t));
}
static grub_uint64_t
erofs_inode_mtime (grub_fshelp_node_t node)
{
return node->inode_type == EROFS_INODE_LAYOUT_COMPACT
? grub_le_to_cpu64 (node->data->sb.build_time)
: grub_le_to_cpu64 (node->inode.e.i_mtime);
}
static grub_err_t
erofs_map_blocks_flatmode (grub_fshelp_node_t node,
struct grub_erofs_map_blocks *map)
{
grub_uint64_t nblocks, lastblk, file_size;
bool tailendpacking = (node->inode_datalayout == EROFS_INODE_FLAT_INLINE);
grub_uint64_t blocksz = erofs_blocksz (node->data);
/* `file_size` is checked by caller and cannot be zero, hence nblocks > 0. */
file_size = erofs_inode_file_size (node);
if (grub_add (file_size, blocksz - 1, &nblocks))
return grub_error (GRUB_ERR_OUT_OF_RANGE, "nblocks overflow");
nblocks >>= node->data->sb.log2_blksz;
lastblk = nblocks - tailendpacking;
map->m_flags = EROFS_MAP_MAPPED;
/* No overflow as (lastblk <= nblocks) && (nblocks * blocksz <= UINT64_MAX - blocksz + 1). */
if (map->m_la < (lastblk * blocksz))
{
if (grub_mul ((grub_uint64_t) grub_le_to_cpu32 (node->inode.e.i_u.raw_blkaddr), blocksz, &map->m_pa) ||
grub_add (map->m_pa, map->m_la, &map->m_pa))
return grub_error (GRUB_ERR_OUT_OF_RANGE, "m_pa overflow");
if (grub_sub (lastblk * blocksz, map->m_la, &map->m_plen))
return grub_error (GRUB_ERR_OUT_OF_RANGE, "m_plen underflow");
}
else if (tailendpacking)
{
if (grub_add (erofs_iloc (node), erofs_inode_size (node), &map->m_pa) ||
grub_add (map->m_pa, erofs_inode_xattr_ibody_size (node), &map->m_pa) ||
grub_add (map->m_pa, map->m_la & (blocksz - 1), &map->m_pa))
return grub_error (GRUB_ERR_OUT_OF_RANGE, "m_pa overflow when handling tailpacking");
if (grub_sub (file_size, map->m_la, &map->m_plen))
return grub_error (GRUB_ERR_OUT_OF_RANGE, "m_plen overflow when handling tailpacking");
/* No overflow as map->m_plen <= UINT64_MAX - blocksz + 1. */
if (((map->m_pa & (blocksz - 1)) + map->m_plen) > blocksz)
return grub_error (GRUB_ERR_BAD_FS,
"inline data cross block boundary @ inode %" PRIuGRUB_UINT64_T,
node->ino);
}
else
return grub_error (GRUB_ERR_BAD_FS,
"invalid map->m_la=%" PRIuGRUB_UINT64_T
" @ inode %" PRIuGRUB_UINT64_T,
map->m_la, node->ino);
map->m_llen = map->m_plen;
return GRUB_ERR_NONE;
}
static grub_err_t
erofs_map_blocks_chunkmode (grub_fshelp_node_t node,
struct grub_erofs_map_blocks *map)
{
grub_uint16_t chunk_format = grub_le_to_cpu16 (node->inode.e.i_u.c.format);
grub_uint64_t unit, pos, chunknr, blkaddr;
grub_uint8_t chunkbits;
grub_err_t err;
if (chunk_format & EROFS_CHUNK_FORMAT_INDEXES)
unit = sizeof (struct grub_erofs_inode_chunk_index);
else
unit = EROFS_BLOCK_MAP_ENTRY_SIZE;
chunkbits = node->data->sb.log2_blksz + (chunk_format & EROFS_CHUNK_FORMAT_BLKBITS_MASK);
if (chunkbits > 63)
return grub_error (GRUB_ERR_BAD_FS, "invalid chunkbits %u @ inode %" PRIuGRUB_UINT64_T,
chunkbits, node->ino);
chunknr = map->m_la >> chunkbits;
if (grub_add (erofs_iloc (node), erofs_inode_size (node), &pos))
return grub_error (GRUB_ERR_OUT_OF_RANGE, "chunkmap position overflow when adding inode size");
if (grub_add (pos, erofs_inode_xattr_ibody_size (node), &pos))
return grub_error (GRUB_ERR_OUT_OF_RANGE, "chunkmap position overflow when adding xattr size");
if (ALIGN_UP_OVF (pos, unit, &pos))
return grub_error (GRUB_ERR_OUT_OF_RANGE, "position overflow when seeking at the start of chunkmap");
/* No overflow for multiplication as chunkbits >= 9 and sizeof(unit) <= 8. */
if (grub_add (pos, chunknr * unit, &pos))
return grub_error (GRUB_ERR_OUT_OF_RANGE, "chunkmap position overflow when finding the specific chunk");
map->m_la = chunknr << chunkbits;
if (grub_sub (erofs_inode_file_size (node), map->m_la, &map->m_plen))
return grub_error (GRUB_ERR_OUT_OF_RANGE, "m_plen underflow");
map->m_plen = grub_min (((grub_uint64_t) 1) << chunkbits,
ALIGN_UP (map->m_plen, erofs_blocksz (node->data)));
if (chunk_format & EROFS_CHUNK_FORMAT_INDEXES)
{
struct grub_erofs_inode_chunk_index idx;
err = grub_disk_read (node->data->disk, pos >> GRUB_DISK_SECTOR_BITS,
pos & (GRUB_DISK_SECTOR_SIZE - 1), unit, &idx);
if (err != GRUB_ERR_NONE)
return err;
blkaddr = grub_le_to_cpu32 (idx.blkaddr);
}
else
{
grub_uint32_t blkaddr_le;
err = grub_disk_read (node->data->disk, pos >> GRUB_DISK_SECTOR_BITS,
pos & (GRUB_DISK_SECTOR_SIZE - 1), unit, &blkaddr_le);
if (err != GRUB_ERR_NONE)
return err;
blkaddr = grub_le_to_cpu32 (blkaddr_le);
}
if (blkaddr == EROFS_NULL_ADDR)
{
map->m_pa = 0;
map->m_flags = 0;
}
else
{
map->m_pa = blkaddr << node->data->sb.log2_blksz;
map->m_flags = EROFS_MAP_MAPPED;
}
map->m_llen = map->m_plen;
return GRUB_ERR_NONE;
}
static grub_err_t
erofs_map_blocks (grub_fshelp_node_t node, struct grub_erofs_map_blocks *map)
{
if (map->m_la >= erofs_inode_file_size (node))
{
map->m_llen = map->m_plen = 0;
map->m_pa = 0;
map->m_flags = 0;
return GRUB_ERR_NONE;
}
if (node->inode_datalayout != EROFS_INODE_CHUNK_BASED)
return erofs_map_blocks_flatmode (node, map);
else
return erofs_map_blocks_chunkmode (node, map);
}
static grub_err_t
erofs_read_raw_data (grub_fshelp_node_t node, grub_uint8_t *buf, grub_uint64_t size,
grub_uint64_t offset, grub_uint64_t *bytes)
{
struct grub_erofs_map_blocks map = {0};
grub_uint64_t cur;
grub_err_t err;
if (bytes)
*bytes = 0;
if (node->inode_loaded == false)
{
err = erofs_read_inode (node->data, node);
if (err != GRUB_ERR_NONE)
return err;
}
cur = offset;
while (cur < offset + size)
{
grub_uint8_t *const estart = buf + cur - offset;
grub_uint64_t eend, moff = 0;
map.m_la = cur;
err = erofs_map_blocks (node, &map);
if (err != GRUB_ERR_NONE)
return err;
if (grub_add(map.m_la, map.m_llen, &eend))
return grub_error (GRUB_ERR_OUT_OF_RANGE, "eend overflow");
eend = grub_min (eend, offset + size);
if (!(map.m_flags & EROFS_MAP_MAPPED))
{
if (!map.m_llen)
{
/* Reached EOF. */
grub_memset (estart, 0, offset + size - cur);
cur = offset + size;
continue;
}
/* It's a hole. */
grub_memset (estart, 0, eend - cur);
if (bytes)
*bytes += eend - cur;
cur = eend;
continue;
}
if (cur > map.m_la)
{
moff = cur - map.m_la;
map.m_la = cur;
}
err = grub_disk_read (node->data->disk,
(map.m_pa + moff) >> GRUB_DISK_SECTOR_BITS,
(map.m_pa + moff) & (GRUB_DISK_SECTOR_SIZE - 1),
eend - map.m_la, estart);
if (err != GRUB_ERR_NONE)
return err;
if (bytes)
*bytes += eend - map.m_la;
cur = eend;
}
return GRUB_ERR_NONE;
}
static int
erofs_iterate_dir (grub_fshelp_node_t dir, grub_fshelp_iterate_dir_hook_t hook,
void *hook_data)
{
grub_uint64_t offset = 0, file_size;
grub_uint32_t blocksz = erofs_blocksz (dir->data);
grub_uint8_t *buf;
grub_err_t err;
if (dir->inode_loaded == false)
{
err = erofs_read_inode (dir->data, dir);
if (err != GRUB_ERR_NONE)
return 0;
}
file_size = erofs_inode_file_size (dir);
buf = grub_malloc (blocksz);
if (buf == NULL)
return 0;
while (offset < file_size)
{
grub_uint64_t maxsize = grub_min (blocksz, file_size - offset);
struct grub_erofs_dirent *de = (void *) buf, *end;
grub_uint16_t nameoff;
err = erofs_read_raw_data (dir, buf, maxsize, offset, NULL);
if (err != GRUB_ERR_NONE)
goto not_found;
nameoff = grub_le_to_cpu16 (de->nameoff);
if (nameoff < sizeof (struct grub_erofs_dirent) || nameoff >= maxsize)
{
grub_error (GRUB_ERR_BAD_FS,
"invalid nameoff %u @ inode %" PRIuGRUB_UINT64_T,
nameoff, dir->ino);
goto not_found;
}
end = (struct grub_erofs_dirent *) ((grub_uint8_t *) de + nameoff);
while (de < end)
{
struct grub_fshelp_node *fdiro;
enum grub_fshelp_filetype type;
char filename[EROFS_NAME_LEN + 1];
grub_size_t de_namelen;
const char *de_name;
fdiro = grub_malloc (sizeof (struct grub_fshelp_node));
if (fdiro == NULL)
goto not_found;
fdiro->data = dir->data;
fdiro->ino = grub_le_to_cpu64 (de->nid);
fdiro->inode_loaded = false;
nameoff = grub_le_to_cpu16 (de->nameoff);
if (nameoff < sizeof (struct grub_erofs_dirent) || nameoff >= maxsize)
{
grub_error (GRUB_ERR_BAD_FS,
"invalid nameoff %u @ inode %" PRIuGRUB_UINT64_T,
nameoff, dir->ino);
grub_free (fdiro);
goto not_found;
}
de_name = (char *) buf + nameoff;
if (de + 1 >= end)
de_namelen = grub_erofs_strnlen (de_name, maxsize - nameoff);
else
{
if (grub_sub (grub_le_to_cpu16 (de[1].nameoff), nameoff, &de_namelen))
{
grub_error (GRUB_ERR_OUT_OF_RANGE, "de_namelen underflow");
grub_free (fdiro);
goto not_found;
}
}
if (nameoff + de_namelen > maxsize || de_namelen > EROFS_NAME_LEN)
{
grub_error (GRUB_ERR_BAD_FS,
"invalid de_namelen %" PRIuGRUB_SIZE
" @ inode %" PRIuGRUB_UINT64_T,
de_namelen, dir->ino);
grub_free (fdiro);
goto not_found;
}
grub_memcpy (filename, de_name, de_namelen);
filename[de_namelen] = '\0';
switch (grub_le_to_cpu16 (de->file_type))
{
case EROFS_FT_REG_FILE:
case EROFS_FT_BLKDEV:
case EROFS_FT_CHRDEV:
case EROFS_FT_FIFO:
case EROFS_FT_SOCK:
type = GRUB_FSHELP_REG;
break;
case EROFS_FT_DIR:
type = GRUB_FSHELP_DIR;
break;
case EROFS_FT_SYMLINK:
type = GRUB_FSHELP_SYMLINK;
break;
case EROFS_FT_UNKNOWN:
default:
type = GRUB_FSHELP_UNKNOWN;
}
if (hook (filename, type, fdiro, hook_data))
{
grub_free (buf);
return 1;
}
++de;
}
offset += maxsize;
}
not_found:
grub_free (buf);
return 0;
}
static char *
erofs_read_symlink (grub_fshelp_node_t node)
{
char *symlink;
grub_size_t sz, lsz;
grub_err_t err;
if (node->inode_loaded == false)
{
err = erofs_read_inode (node->data, node);
if (err != GRUB_ERR_NONE)
return NULL;
}
sz = erofs_inode_file_size (node);
if (sz >= EROFS_PATH_LEN)
{
grub_error (GRUB_ERR_BAD_FS,
"symlink too long @ inode %" PRIuGRUB_UINT64_T, node->ino);
return NULL;
}
if (grub_add (sz, 1, &lsz))
{
grub_error (GRUB_ERR_OUT_OF_RANGE, N_("symlink size overflow"));
return NULL;
}
symlink = grub_malloc (lsz);
if (symlink == NULL)
return NULL;
err = erofs_read_raw_data (node, (grub_uint8_t *) symlink, sz, 0, NULL);
if (err != GRUB_ERR_NONE)
{
grub_free (symlink);
return NULL;
}
symlink[sz] = '\0';
return symlink;
}
static struct grub_erofs_data *
erofs_mount (grub_disk_t disk, bool read_root)
{
struct grub_erofs_super sb;
grub_err_t err;
struct grub_erofs_data *data;
grub_uint32_t feature;
err = grub_disk_read (disk, EROFS_SUPER_OFFSET >> GRUB_DISK_SECTOR_BITS, 0,
sizeof (sb), &sb);
if (err != GRUB_ERR_NONE)
return NULL;
if (sb.magic != grub_cpu_to_le32_compile_time (EROFS_MAGIC) ||
grub_le_to_cpu32 (sb.log2_blksz) < EROFS_MIN_LOG2_BLOCK_SIZE ||
grub_le_to_cpu32 (sb.log2_blksz) > EROFS_MAX_LOG2_BLOCK_SIZE)
{
grub_error (GRUB_ERR_BAD_FS, "not a valid erofs filesystem");
return NULL;
}
feature = grub_le_to_cpu32 (sb.feature_incompat);
if (feature & ~EROFS_ALL_FEATURE_INCOMPAT)
{
grub_error (GRUB_ERR_BAD_FS, "unsupported features: 0x%x",
feature & ~EROFS_ALL_FEATURE_INCOMPAT);
return NULL;
}
data = grub_malloc (sizeof (*data));
if (data == NULL)
return NULL;
data->disk = disk;
data->sb = sb;
if (read_root)
{
data->inode.data = data;
data->inode.ino = grub_le_to_cpu16 (sb.root_nid);
err = erofs_read_inode (data, &data->inode);
if (err != GRUB_ERR_NONE)
{
grub_free (data);
return NULL;
}
}
return data;
}
/* Context for grub_erofs_dir. */
struct grub_erofs_dir_ctx
{
grub_fs_dir_hook_t hook;
void *hook_data;
struct grub_erofs_data *data;
};
/* Helper for grub_erofs_dir. */
static int
erofs_dir_iter (const char *filename, enum grub_fshelp_filetype filetype,
grub_fshelp_node_t node, void *data)
{
struct grub_erofs_dir_ctx *ctx = data;
struct grub_dirhook_info info = {0};
grub_err_t err;
if (node->inode_loaded == false)
{
err = erofs_read_inode (ctx->data, node);
if (err != GRUB_ERR_NONE)
return 0;
}
if (node->inode_loaded == true)
{
info.mtimeset = 1;
info.mtime = erofs_inode_mtime (node);
}
info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
grub_free (node);
return ctx->hook (filename, &info, ctx->hook_data);
}
static grub_err_t
grub_erofs_dir (grub_device_t device, const char *path, grub_fs_dir_hook_t hook,
void *hook_data)
{
grub_fshelp_node_t fdiro = NULL;
grub_err_t err;
struct grub_erofs_dir_ctx ctx = {
.hook = hook,
.hook_data = hook_data
};
ctx.data = erofs_mount (device->disk, true);
if (ctx.data == NULL)
goto fail;
err = grub_fshelp_find_file (path, &ctx.data->inode, &fdiro, erofs_iterate_dir,
erofs_read_symlink, GRUB_FSHELP_DIR);
if (err != GRUB_ERR_NONE)
goto fail;
erofs_iterate_dir (fdiro, erofs_dir_iter, &ctx);
fail:
if (fdiro != &ctx.data->inode)
grub_free (fdiro);
grub_free (ctx.data);
return grub_errno;
}
static grub_err_t
grub_erofs_open (grub_file_t file, const char *name)
{
struct grub_erofs_data *data;
struct grub_fshelp_node *fdiro = NULL;
grub_err_t err;
data = erofs_mount (file->device->disk, true);
if (data == NULL)
{
err = grub_errno;
goto fail;
}
err = grub_fshelp_find_file (name, &data->inode, &fdiro, erofs_iterate_dir,
erofs_read_symlink, GRUB_FSHELP_REG);
if (err != GRUB_ERR_NONE)
goto fail;
if (fdiro->inode_loaded == false)
{
err = erofs_read_inode (data, fdiro);
if (err != GRUB_ERR_NONE)
goto fail;
}
grub_memcpy (&data->inode, fdiro, sizeof (*fdiro));
grub_free (fdiro);
file->data = data;
file->size = erofs_inode_file_size (&data->inode);
return GRUB_ERR_NONE;
fail:
if (fdiro != &data->inode)
grub_free (fdiro);
grub_free (data);
return err;
}
static grub_ssize_t
grub_erofs_read (grub_file_t file, char *buf, grub_size_t len)
{
struct grub_erofs_data *data = file->data;
struct grub_fshelp_node *inode = &data->inode;
grub_off_t off = file->offset;
grub_uint64_t ret = 0, file_size;
grub_err_t err;
if (inode->inode_loaded == false)
{
err = erofs_read_inode (data, inode);
if (err != GRUB_ERR_NONE)
return -1;
}
file_size = erofs_inode_file_size (inode);
if (off > file_size)
{
grub_error (GRUB_ERR_IO, "read past EOF @ inode %" PRIuGRUB_UINT64_T, inode->ino);
return -1;
}
if (off == file_size)
return 0;
if (off + len > file_size)
len = file_size - off;
err = erofs_read_raw_data (inode, (grub_uint8_t *) buf, len, off, &ret);
if (err != GRUB_ERR_NONE)
return -1;
return ret;
}
static grub_err_t
grub_erofs_close (grub_file_t file)
{
grub_free (file->data);
return GRUB_ERR_NONE;
}
static grub_err_t
grub_erofs_uuid (grub_device_t device, char **uuid)
{
struct grub_erofs_data *data;
data = erofs_mount (device->disk, false);
if (data == NULL)
{
*uuid = NULL;
return grub_errno;
}
*uuid = grub_xasprintf ("%pG", &data->sb.uuid);
grub_free (data);
return GRUB_ERR_NONE;
}
static grub_err_t
grub_erofs_label (grub_device_t device, char **label)
{
struct grub_erofs_data *data;
data = erofs_mount (device->disk, false);
if (data == NULL)
{
*label = NULL;
return grub_errno;
}
*label = grub_strndup ((char *) data->sb.volume_name, sizeof (data->sb.volume_name));
grub_free (data);
if (*label == NULL)
return grub_errno;
return GRUB_ERR_NONE;
}
static grub_err_t
grub_erofs_mtime (grub_device_t device, grub_int64_t *tm)
{
struct grub_erofs_data *data;
data = erofs_mount (device->disk, false);
if (data == NULL)
{
*tm = 0;
return grub_errno;
}
*tm = grub_le_to_cpu64 (data->sb.build_time);
grub_free (data);
return GRUB_ERR_NONE;
}
static struct grub_fs grub_erofs_fs = {
.name = "erofs",
.fs_dir = grub_erofs_dir,
.fs_open = grub_erofs_open,
.fs_read = grub_erofs_read,
.fs_close = grub_erofs_close,
.fs_uuid = grub_erofs_uuid,
.fs_label = grub_erofs_label,
.fs_mtime = grub_erofs_mtime,
#ifdef GRUB_UTIL
.reserved_first_sector = 1,
.blocklist_install = 0,
#endif
.next = 0,
};
GRUB_MOD_INIT (erofs)
{
grub_erofs_fs.mod = mod;
grub_fs_register (&grub_erofs_fs);
}
GRUB_MOD_FINI (erofs)
{
grub_fs_unregister (&grub_erofs_fs);
}