// SPDX-License-Identifier: GPL-2.0 /* * linux/drivers/staging/erofs/dir.c * * Copyright (C) 2017-2018 HUAWEI, Inc. * http://www.huawei.com/ * Created by Gao Xiang * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the Linux * distribution for more details. */ #include "internal.h" static const unsigned char erofs_filetype_table[EROFS_FT_MAX] = { [EROFS_FT_UNKNOWN] = DT_UNKNOWN, [EROFS_FT_REG_FILE] = DT_REG, [EROFS_FT_DIR] = DT_DIR, [EROFS_FT_CHRDEV] = DT_CHR, [EROFS_FT_BLKDEV] = DT_BLK, [EROFS_FT_FIFO] = DT_FIFO, [EROFS_FT_SOCK] = DT_SOCK, [EROFS_FT_SYMLINK] = DT_LNK, }; static void debug_one_dentry(unsigned char d_type, const char *de_name, unsigned int de_namelen) { #ifdef CONFIG_EROFS_FS_DEBUG /* since the on-disk name could not have the trailing '\0' */ unsigned char dbg_namebuf[EROFS_NAME_LEN + 1]; memcpy(dbg_namebuf, de_name, de_namelen); dbg_namebuf[de_namelen] = '\0'; debugln("found dirent %s de_len %u d_type %d", dbg_namebuf, de_namelen, d_type); #endif } static int erofs_fill_dentries(struct dir_context *ctx, void *dentry_blk, unsigned int *ofs, unsigned int nameoff, unsigned int maxsize) { struct erofs_dirent *de = dentry_blk; const struct erofs_dirent *end = dentry_blk + nameoff; de = dentry_blk + *ofs; while (de < end) { const char *de_name; unsigned int de_namelen; unsigned char d_type; if (de->file_type < EROFS_FT_MAX) d_type = erofs_filetype_table[de->file_type]; else d_type = DT_UNKNOWN; 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 (unlikely(nameoff + de_namelen > maxsize || de_namelen > EROFS_NAME_LEN)) { DBG_BUGON(1); return -EIO; } debug_one_dentry(d_type, de_name, de_namelen); if (!dir_emit(ctx, de_name, de_namelen, le64_to_cpu(de->nid), d_type)) /* stopped by some reason */ return 1; ++de; *ofs += sizeof(struct erofs_dirent); } *ofs = maxsize; return 0; } static int erofs_readdir(struct file *f, struct dir_context *ctx) { struct inode *dir = file_inode(f); struct address_space *mapping = dir->i_mapping; const size_t dirsize = i_size_read(dir); unsigned int i = ctx->pos / EROFS_BLKSIZ; unsigned int ofs = ctx->pos % EROFS_BLKSIZ; int err = 0; bool initial = true; while (ctx->pos < dirsize) { struct page *dentry_page; struct erofs_dirent *de; unsigned int nameoff, maxsize; dentry_page = read_mapping_page(mapping, i, NULL); if (IS_ERR(dentry_page)) continue; de = (struct erofs_dirent *)kmap(dentry_page); nameoff = le16_to_cpu(de->nameoff); if (unlikely(nameoff < sizeof(struct erofs_dirent) || nameoff >= PAGE_SIZE)) { errln("%s, invalid de[0].nameoff %u", __func__, nameoff); err = -EIO; goto skip_this; } maxsize = min_t(unsigned int, dirsize - ctx->pos + ofs, PAGE_SIZE); /* search dirents at the arbitrary position */ if (unlikely(initial)) { initial = false; ofs = roundup(ofs, sizeof(struct erofs_dirent)); if (unlikely(ofs >= nameoff)) goto skip_this; } err = erofs_fill_dentries(ctx, de, &ofs, nameoff, maxsize); skip_this: kunmap(dentry_page); put_page(dentry_page); ctx->pos = blknr_to_addr(i) + ofs; if (unlikely(err)) break; ++i; ofs = 0; } return err < 0 ? err : 0; } const struct file_operations erofs_dir_fops = { .llseek = generic_file_llseek, .read = generic_read_dir, .iterate_shared = erofs_readdir, };