diff options
Diffstat (limited to 'fs/erofs/namei.c')
-rw-r--r-- | fs/erofs/namei.c | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/fs/erofs/namei.c b/fs/erofs/namei.c new file mode 100644 index 0000000000..f1195a09ea --- /dev/null +++ b/fs/erofs/namei.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include "internal.h" + +int erofs_read_inode_from_disk(struct erofs_inode *vi) +{ + int ret, ifmt; + char buf[sizeof(struct erofs_inode_extended)]; + struct erofs_inode_compact *dic; + struct erofs_inode_extended *die; + const erofs_off_t inode_loc = iloc(vi->nid); + + ret = erofs_dev_read(0, buf, inode_loc, sizeof(*dic)); + if (ret < 0) + return -EIO; + + dic = (struct erofs_inode_compact *)buf; + ifmt = le16_to_cpu(dic->i_format); + + vi->datalayout = erofs_inode_datalayout(ifmt); + if (vi->datalayout >= EROFS_INODE_DATALAYOUT_MAX) { + erofs_err("unsupported datalayout %u of nid %llu", + vi->datalayout, vi->nid | 0ULL); + return -EOPNOTSUPP; + } + switch (erofs_inode_version(ifmt)) { + case EROFS_INODE_LAYOUT_EXTENDED: + vi->inode_isize = sizeof(struct erofs_inode_extended); + + ret = erofs_dev_read(0, buf + sizeof(*dic), inode_loc + sizeof(*dic), + sizeof(*die) - sizeof(*dic)); + if (ret < 0) + return -EIO; + + die = (struct erofs_inode_extended *)buf; + vi->xattr_isize = erofs_xattr_ibody_size(die->i_xattr_icount); + vi->i_mode = le16_to_cpu(die->i_mode); + + switch (vi->i_mode & S_IFMT) { + case S_IFREG: + case S_IFDIR: + case S_IFLNK: + vi->u.i_blkaddr = le32_to_cpu(die->i_u.raw_blkaddr); + break; + case S_IFCHR: + case S_IFBLK: + vi->u.i_rdev = 0; + break; + case S_IFIFO: + case S_IFSOCK: + vi->u.i_rdev = 0; + break; + default: + goto bogusimode; + } + + vi->i_uid = le32_to_cpu(die->i_uid); + vi->i_gid = le32_to_cpu(die->i_gid); + vi->i_nlink = le32_to_cpu(die->i_nlink); + + vi->i_ctime = le64_to_cpu(die->i_ctime); + vi->i_ctime_nsec = le64_to_cpu(die->i_ctime_nsec); + vi->i_size = le64_to_cpu(die->i_size); + if (vi->datalayout == EROFS_INODE_CHUNK_BASED) + /* fill chunked inode summary info */ + vi->u.chunkformat = le16_to_cpu(die->i_u.c.format); + break; + case EROFS_INODE_LAYOUT_COMPACT: + vi->inode_isize = sizeof(struct erofs_inode_compact); + vi->xattr_isize = erofs_xattr_ibody_size(dic->i_xattr_icount); + vi->i_mode = le16_to_cpu(dic->i_mode); + + switch (vi->i_mode & S_IFMT) { + case S_IFREG: + case S_IFDIR: + case S_IFLNK: + vi->u.i_blkaddr = le32_to_cpu(dic->i_u.raw_blkaddr); + break; + case S_IFCHR: + case S_IFBLK: + vi->u.i_rdev = 0; + break; + case S_IFIFO: + case S_IFSOCK: + vi->u.i_rdev = 0; + break; + default: + goto bogusimode; + } + + vi->i_uid = le16_to_cpu(dic->i_uid); + vi->i_gid = le16_to_cpu(dic->i_gid); + vi->i_nlink = le16_to_cpu(dic->i_nlink); + + vi->i_ctime = sbi.build_time; + vi->i_ctime_nsec = sbi.build_time_nsec; + + vi->i_size = le32_to_cpu(dic->i_size); + if (vi->datalayout == EROFS_INODE_CHUNK_BASED) + vi->u.chunkformat = le16_to_cpu(dic->i_u.c.format); + break; + default: + erofs_err("unsupported on-disk inode version %u of nid %llu", + erofs_inode_version(ifmt), vi->nid | 0ULL); + return -EOPNOTSUPP; + } + + vi->flags = 0; + if (vi->datalayout == EROFS_INODE_CHUNK_BASED) { + if (vi->u.chunkformat & ~EROFS_CHUNK_FORMAT_ALL) { + erofs_err("unsupported chunk format %x of nid %llu", + vi->u.chunkformat, vi->nid | 0ULL); + return -EOPNOTSUPP; + } + vi->u.chunkbits = LOG_BLOCK_SIZE + + (vi->u.chunkformat & EROFS_CHUNK_FORMAT_BLKBITS_MASK); + } else if (erofs_inode_is_data_compressed(vi->datalayout)) + return -EOPNOTSUPP; + return 0; +bogusimode: + erofs_err("bogus i_mode (%o) @ nid %llu", vi->i_mode, vi->nid | 0ULL); + return -EFSCORRUPTED; +} + +struct erofs_dirent *find_target_dirent(erofs_nid_t pnid, + void *dentry_blk, + const char *name, unsigned int len, + unsigned int nameoff, + unsigned int maxsize) +{ + struct erofs_dirent *de = dentry_blk; + const struct erofs_dirent *end = dentry_blk + nameoff; + + while (de < end) { + const char *de_name; + unsigned int de_namelen; + + nameoff = le16_to_cpu(de->nameoff); + de_name = (char *)dentry_blk + nameoff; + + /* the last dirent in the block? */ + if (de + 1 >= end) + de_namelen = strnlen(de_name, maxsize - nameoff); + else + de_namelen = le16_to_cpu(de[1].nameoff) - nameoff; + + /* a corrupted entry is found */ + if (nameoff + de_namelen > maxsize || + de_namelen > EROFS_NAME_LEN) { + erofs_err("bogus dirent @ nid %llu", pnid | 0ULL); + DBG_BUGON(1); + return ERR_PTR(-EFSCORRUPTED); + } + + if (len == de_namelen && !memcmp(de_name, name, de_namelen)) + return de; + ++de; + } + return NULL; +} + +struct nameidata { + erofs_nid_t nid; + unsigned int ftype; +}; + +int erofs_namei(struct nameidata *nd, + const char *name, unsigned int len) +{ + erofs_nid_t nid = nd->nid; + int ret; + char buf[EROFS_BLKSIZ]; + struct erofs_inode vi = { .nid = nid }; + erofs_off_t offset; + + ret = erofs_read_inode_from_disk(&vi); + if (ret) + return ret; + + offset = 0; + while (offset < vi.i_size) { + erofs_off_t maxsize = min_t(erofs_off_t, + vi.i_size - offset, EROFS_BLKSIZ); + struct erofs_dirent *de = (void *)buf; + unsigned int nameoff; + + ret = erofs_pread(&vi, buf, maxsize, offset); + if (ret) + return ret; + + nameoff = le16_to_cpu(de->nameoff); + if (nameoff < sizeof(struct erofs_dirent) || + nameoff >= PAGE_SIZE) { + erofs_err("invalid de[0].nameoff %u @ nid %llu", + nameoff, nid | 0ULL); + return -EFSCORRUPTED; + } + + de = find_target_dirent(nid, buf, name, len, + nameoff, maxsize); + if (IS_ERR(de)) + return PTR_ERR(de); + + if (de) { + nd->nid = le64_to_cpu(de->nid); + return 0; + } + offset += maxsize; + } + return -ENOENT; +} + +static int link_path_walk(const char *name, struct nameidata *nd) +{ + nd->nid = sbi.root_nid; + + while (*name == '/') + name++; + + /* At this point we know we have a real path component. */ + while (*name != '\0') { + const char *p = name; + int ret; + + do { + ++p; + } while (*p != '\0' && *p != '/'); + + DBG_BUGON(p <= name); + ret = erofs_namei(nd, name, p - name); + if (ret) + return ret; + + name = p; + /* Skip until no more slashes. */ + for (name = p; *name == '/'; ++name) + ; + } + return 0; +} + +int erofs_ilookup(const char *path, struct erofs_inode *vi) +{ + int ret; + struct nameidata nd; + + ret = link_path_walk(path, &nd); + if (ret) + return ret; + + vi->nid = nd.nid; + return erofs_read_inode_from_disk(vi); +} |