| // SPDX-License-Identifier: GPL-2.0+ |
| #include "internal.h" |
| #include <fs_internal.h> |
| |
| struct erofs_sb_info sbi; |
| |
| static struct erofs_ctxt { |
| struct disk_partition cur_part_info; |
| struct blk_desc *cur_dev; |
| } ctxt; |
| |
| int erofs_dev_read(int device_id, void *buf, u64 offset, size_t len) |
| { |
| lbaint_t sect = offset >> ctxt.cur_dev->log2blksz; |
| int off = offset & (ctxt.cur_dev->blksz - 1); |
| |
| if (!ctxt.cur_dev) |
| return -EIO; |
| |
| if (fs_devread(ctxt.cur_dev, &ctxt.cur_part_info, sect, |
| off, len, buf)) |
| return 0; |
| return -EIO; |
| } |
| |
| int erofs_blk_read(void *buf, erofs_blk_t start, u32 nblocks) |
| { |
| return erofs_dev_read(0, buf, erofs_pos(start), |
| erofs_pos(nblocks)); |
| } |
| |
| int erofs_probe(struct blk_desc *fs_dev_desc, |
| struct disk_partition *fs_partition) |
| { |
| int ret; |
| |
| ctxt.cur_dev = fs_dev_desc; |
| ctxt.cur_part_info = *fs_partition; |
| |
| ret = erofs_read_superblock(); |
| if (ret) |
| goto error; |
| |
| return 0; |
| error: |
| ctxt.cur_dev = NULL; |
| return ret; |
| } |
| |
| struct erofs_dir_stream { |
| struct fs_dir_stream fs_dirs; |
| struct fs_dirent dirent; |
| |
| struct erofs_inode inode; |
| char dblk[EROFS_MAX_BLOCK_SIZE]; |
| unsigned int maxsize, de_end; |
| erofs_off_t pos; |
| }; |
| |
| static int erofs_readlink(struct erofs_inode *vi) |
| { |
| size_t len = vi->i_size; |
| char *target; |
| int err; |
| |
| target = malloc(len + 1); |
| if (!target) |
| return -ENOMEM; |
| target[len] = '\0'; |
| |
| err = erofs_pread(vi, target, len, 0); |
| if (err) |
| goto err_out; |
| |
| err = erofs_ilookup(target, vi); |
| if (err) |
| goto err_out; |
| |
| err_out: |
| free(target); |
| return err; |
| } |
| |
| int erofs_opendir(const char *filename, struct fs_dir_stream **dirsp) |
| { |
| struct erofs_dir_stream *dirs; |
| int err; |
| |
| dirs = calloc(1, sizeof(*dirs)); |
| if (!dirs) |
| return -ENOMEM; |
| |
| err = erofs_ilookup(filename, &dirs->inode); |
| if (err) |
| goto err_out; |
| |
| if (S_ISLNK(dirs->inode.i_mode)) { |
| err = erofs_readlink(&dirs->inode); |
| if (err) |
| goto err_out; |
| } |
| |
| if (!S_ISDIR(dirs->inode.i_mode)) { |
| err = -ENOTDIR; |
| goto err_out; |
| } |
| *dirsp = (struct fs_dir_stream *)dirs; |
| return 0; |
| err_out: |
| free(dirs); |
| return err; |
| } |
| |
| int erofs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp) |
| { |
| struct erofs_dir_stream *dirs = (struct erofs_dir_stream *)fs_dirs; |
| struct fs_dirent *dent = &dirs->dirent; |
| erofs_off_t pos = dirs->pos; |
| unsigned int nameoff, de_namelen; |
| struct erofs_dirent *de; |
| char *de_name; |
| int err; |
| |
| if (pos >= dirs->inode.i_size) |
| return 1; |
| |
| if (!dirs->maxsize) { |
| dirs->maxsize = min_t(unsigned int, EROFS_MAX_BLOCK_SIZE, |
| dirs->inode.i_size - pos); |
| |
| err = erofs_pread(&dirs->inode, dirs->dblk, |
| dirs->maxsize, pos); |
| if (err) |
| return err; |
| |
| de = (struct erofs_dirent *)dirs->dblk; |
| dirs->de_end = le16_to_cpu(de->nameoff); |
| if (dirs->de_end < sizeof(struct erofs_dirent) || |
| dirs->de_end >= EROFS_MAX_BLOCK_SIZE) { |
| erofs_err("invalid de[0].nameoff %u @ nid %llu", |
| dirs->de_end, de->nid | 0ULL); |
| return -EFSCORRUPTED; |
| } |
| } |
| |
| de = (struct erofs_dirent *)(dirs->dblk + erofs_blkoff(pos)); |
| nameoff = le16_to_cpu(de->nameoff); |
| de_name = (char *)dirs->dblk + nameoff; |
| |
| /* the last dirent in the block? */ |
| if (de + 1 >= (struct erofs_dirent *)(dirs->dblk + dirs->de_end)) |
| de_namelen = strnlen(de_name, dirs->maxsize - nameoff); |
| else |
| de_namelen = le16_to_cpu(de[1].nameoff) - nameoff; |
| |
| /* a corrupted entry is found */ |
| if (nameoff + de_namelen > dirs->maxsize || |
| de_namelen > EROFS_NAME_LEN) { |
| erofs_err("bogus dirent @ nid %llu", de->nid | 0ULL); |
| DBG_BUGON(1); |
| return -EFSCORRUPTED; |
| } |
| |
| memcpy(dent->name, de_name, de_namelen); |
| dent->name[de_namelen] = '\0'; |
| |
| if (de->file_type == EROFS_FT_DIR) { |
| dent->type = FS_DT_DIR; |
| } else if (de->file_type == EROFS_FT_SYMLINK) { |
| dent->type = FS_DT_LNK; |
| } else { |
| struct erofs_inode vi; |
| |
| dent->type = FS_DT_REG; |
| vi.nid = de->nid; |
| |
| err = erofs_read_inode_from_disk(&vi); |
| if (err) |
| return err; |
| dent->size = vi.i_size; |
| } |
| *dentp = dent; |
| |
| pos += sizeof(*de); |
| if (erofs_blkoff(pos) >= dirs->de_end) { |
| pos = erofs_pos(erofs_blknr(pos) + 1); |
| dirs->maxsize = 0; |
| } |
| dirs->pos = pos; |
| return 0; |
| } |
| |
| void erofs_closedir(struct fs_dir_stream *fs_dirs) |
| { |
| free(fs_dirs); |
| } |
| |
| int erofs_exists(const char *filename) |
| { |
| struct erofs_inode vi; |
| int err; |
| |
| err = erofs_ilookup(filename, &vi); |
| return err == 0; |
| } |
| |
| int erofs_size(const char *filename, loff_t *size) |
| { |
| struct erofs_inode vi; |
| int err; |
| |
| err = erofs_ilookup(filename, &vi); |
| if (err) |
| return err; |
| *size = vi.i_size; |
| return 0; |
| } |
| |
| int erofs_read(const char *filename, void *buf, loff_t offset, loff_t len, |
| loff_t *actread) |
| { |
| struct erofs_inode vi; |
| int err; |
| |
| err = erofs_ilookup(filename, &vi); |
| if (err) |
| return err; |
| |
| if (S_ISLNK(vi.i_mode)) { |
| err = erofs_readlink(&vi); |
| if (err) |
| return err; |
| } |
| |
| if (!len) |
| len = vi.i_size; |
| |
| err = erofs_pread(&vi, buf, len, offset); |
| if (err) { |
| *actread = 0; |
| return err; |
| } |
| |
| if (offset >= vi.i_size) |
| *actread = 0; |
| else if (offset + len > vi.i_size) |
| *actread = vi.i_size - offset; |
| else |
| *actread = len; |
| return 0; |
| } |
| |
| void erofs_close(void) |
| { |
| ctxt.cur_dev = NULL; |
| } |
| |
| int erofs_uuid(char *uuid_str) |
| { |
| if (IS_ENABLED(CONFIG_LIB_UUID)) { |
| if (ctxt.cur_dev) |
| uuid_bin_to_str(sbi.uuid, uuid_str, |
| UUID_STR_FORMAT_STD); |
| return 0; |
| } |
| return -ENOSYS; |
| } |