fs/erofs: add erofs filesystem support
This patch mainly deals with uncompressed files.
Signed-off-by: Huang Jianan <jnhuang95@gmail.com>
diff --git a/fs/erofs/fs.c b/fs/erofs/fs.c
new file mode 100644
index 0000000..8926975
--- /dev/null
+++ b/fs/erofs/fs.c
@@ -0,0 +1,267 @@
+// 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, blknr_to_addr(start),
+ blknr_to_addr(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_BLKSIZ];
+ 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_BLKSIZ,
+ 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_BLKSIZ) {
+ 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 = blknr_to_addr(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;
+}