diff options
Diffstat (limited to 'fs/erofs/dir.c')
-rw-r--r-- | fs/erofs/dir.c | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/fs/erofs/dir.c b/fs/erofs/dir.c new file mode 100644 index 000000000000..1976e60e5174 --- /dev/null +++ b/fs/erofs/dir.c | |||
@@ -0,0 +1,139 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0-only | ||
2 | /* | ||
3 | * Copyright (C) 2017-2018 HUAWEI, Inc. | ||
4 | * http://www.huawei.com/ | ||
5 | * Created by Gao Xiang <gaoxiang25@huawei.com> | ||
6 | */ | ||
7 | #include "internal.h" | ||
8 | |||
9 | static void debug_one_dentry(unsigned char d_type, const char *de_name, | ||
10 | unsigned int de_namelen) | ||
11 | { | ||
12 | #ifdef CONFIG_EROFS_FS_DEBUG | ||
13 | /* since the on-disk name could not have the trailing '\0' */ | ||
14 | unsigned char dbg_namebuf[EROFS_NAME_LEN + 1]; | ||
15 | |||
16 | memcpy(dbg_namebuf, de_name, de_namelen); | ||
17 | dbg_namebuf[de_namelen] = '\0'; | ||
18 | |||
19 | debugln("found dirent %s de_len %u d_type %d", dbg_namebuf, | ||
20 | de_namelen, d_type); | ||
21 | #endif | ||
22 | } | ||
23 | |||
24 | static int erofs_fill_dentries(struct inode *dir, struct dir_context *ctx, | ||
25 | void *dentry_blk, unsigned int *ofs, | ||
26 | unsigned int nameoff, unsigned int maxsize) | ||
27 | { | ||
28 | struct erofs_dirent *de = dentry_blk + *ofs; | ||
29 | const struct erofs_dirent *end = dentry_blk + nameoff; | ||
30 | |||
31 | while (de < end) { | ||
32 | const char *de_name; | ||
33 | unsigned int de_namelen; | ||
34 | unsigned char d_type; | ||
35 | |||
36 | d_type = fs_ftype_to_dtype(de->file_type); | ||
37 | |||
38 | nameoff = le16_to_cpu(de->nameoff); | ||
39 | de_name = (char *)dentry_blk + nameoff; | ||
40 | |||
41 | /* the last dirent in the block? */ | ||
42 | if (de + 1 >= end) | ||
43 | de_namelen = strnlen(de_name, maxsize - nameoff); | ||
44 | else | ||
45 | de_namelen = le16_to_cpu(de[1].nameoff) - nameoff; | ||
46 | |||
47 | /* a corrupted entry is found */ | ||
48 | if (unlikely(nameoff + de_namelen > maxsize || | ||
49 | de_namelen > EROFS_NAME_LEN)) { | ||
50 | errln("bogus dirent @ nid %llu", EROFS_V(dir)->nid); | ||
51 | DBG_BUGON(1); | ||
52 | return -EFSCORRUPTED; | ||
53 | } | ||
54 | |||
55 | debug_one_dentry(d_type, de_name, de_namelen); | ||
56 | if (!dir_emit(ctx, de_name, de_namelen, | ||
57 | le64_to_cpu(de->nid), d_type)) | ||
58 | /* stopped by some reason */ | ||
59 | return 1; | ||
60 | ++de; | ||
61 | *ofs += sizeof(struct erofs_dirent); | ||
62 | } | ||
63 | *ofs = maxsize; | ||
64 | return 0; | ||
65 | } | ||
66 | |||
67 | static int erofs_readdir(struct file *f, struct dir_context *ctx) | ||
68 | { | ||
69 | struct inode *dir = file_inode(f); | ||
70 | struct address_space *mapping = dir->i_mapping; | ||
71 | const size_t dirsize = i_size_read(dir); | ||
72 | unsigned int i = ctx->pos / EROFS_BLKSIZ; | ||
73 | unsigned int ofs = ctx->pos % EROFS_BLKSIZ; | ||
74 | int err = 0; | ||
75 | bool initial = true; | ||
76 | |||
77 | while (ctx->pos < dirsize) { | ||
78 | struct page *dentry_page; | ||
79 | struct erofs_dirent *de; | ||
80 | unsigned int nameoff, maxsize; | ||
81 | |||
82 | dentry_page = read_mapping_page(mapping, i, NULL); | ||
83 | if (dentry_page == ERR_PTR(-ENOMEM)) { | ||
84 | err = -ENOMEM; | ||
85 | break; | ||
86 | } else if (IS_ERR(dentry_page)) { | ||
87 | errln("fail to readdir of logical block %u of nid %llu", | ||
88 | i, EROFS_V(dir)->nid); | ||
89 | err = -EFSCORRUPTED; | ||
90 | break; | ||
91 | } | ||
92 | |||
93 | de = (struct erofs_dirent *)kmap(dentry_page); | ||
94 | |||
95 | nameoff = le16_to_cpu(de->nameoff); | ||
96 | |||
97 | if (unlikely(nameoff < sizeof(struct erofs_dirent) || | ||
98 | nameoff >= PAGE_SIZE)) { | ||
99 | errln("%s, invalid de[0].nameoff %u @ nid %llu", | ||
100 | __func__, nameoff, EROFS_V(dir)->nid); | ||
101 | err = -EFSCORRUPTED; | ||
102 | goto skip_this; | ||
103 | } | ||
104 | |||
105 | maxsize = min_t(unsigned int, | ||
106 | dirsize - ctx->pos + ofs, PAGE_SIZE); | ||
107 | |||
108 | /* search dirents at the arbitrary position */ | ||
109 | if (unlikely(initial)) { | ||
110 | initial = false; | ||
111 | |||
112 | ofs = roundup(ofs, sizeof(struct erofs_dirent)); | ||
113 | if (unlikely(ofs >= nameoff)) | ||
114 | goto skip_this; | ||
115 | } | ||
116 | |||
117 | err = erofs_fill_dentries(dir, ctx, de, &ofs, | ||
118 | nameoff, maxsize); | ||
119 | skip_this: | ||
120 | kunmap(dentry_page); | ||
121 | |||
122 | put_page(dentry_page); | ||
123 | |||
124 | ctx->pos = blknr_to_addr(i) + ofs; | ||
125 | |||
126 | if (unlikely(err)) | ||
127 | break; | ||
128 | ++i; | ||
129 | ofs = 0; | ||
130 | } | ||
131 | return err < 0 ? err : 0; | ||
132 | } | ||
133 | |||
134 | const struct file_operations erofs_dir_fops = { | ||
135 | .llseek = generic_file_llseek, | ||
136 | .read = generic_read_dir, | ||
137 | .iterate_shared = erofs_readdir, | ||
138 | }; | ||
139 | |||