You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
5.9 KiB
265 lines
5.9 KiB
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* erofs-utils/lib/namei.c
|
|
*
|
|
* Created by Li Guifu <blucerlee@gmail.com>
|
|
*/
|
|
#include <linux/kdev_t.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/sysmacros.h>
|
|
|
|
#include "erofs/print.h"
|
|
#include "erofs/io.h"
|
|
|
|
static dev_t erofs_new_decode_dev(u32 dev)
|
|
{
|
|
const unsigned int major = (dev & 0xfff00) >> 8;
|
|
const unsigned int minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
|
|
|
|
return makedev(major, minor);
|
|
}
|
|
|
|
static 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 = dev_read(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 = dev_read(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 =
|
|
erofs_new_decode_dev(le32_to_cpu(die->i_u.rdev));
|
|
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);
|
|
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 =
|
|
erofs_new_decode_dev(le32_to_cpu(dic->i_u.rdev));
|
|
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);
|
|
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 (erofs_inode_is_data_compressed(vi->datalayout))
|
|
z_erofs_fill_inode(vi);
|
|
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);
|
|
}
|
|
|