1 // SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
2 #include <stdlib.h>
3 #include <sys/stat.h>
4 #include "erofs/print.h"
5 #include "erofs/dir.h"
6
7 /* filename should not have a '/' in the name string */
erofs_validate_filename(const char * dname,int size)8 static bool erofs_validate_filename(const char *dname, int size)
9 {
10 char *name = (char *)dname;
11
12 while (name - dname < size && *name != '\0') {
13 if (*name == '/')
14 return false;
15 ++name;
16 }
17 return true;
18 }
19
traverse_dirents(struct erofs_dir_context * ctx,void * dentry_blk,unsigned int lblk,unsigned int next_nameoff,unsigned int maxsize,bool fsck)20 static int traverse_dirents(struct erofs_dir_context *ctx,
21 void *dentry_blk, unsigned int lblk,
22 unsigned int next_nameoff, unsigned int maxsize,
23 bool fsck)
24 {
25 struct erofs_sb_info *sbi = ctx->dir->sbi;
26 struct erofs_dirent *de = dentry_blk;
27 const struct erofs_dirent *end = dentry_blk + next_nameoff;
28 const char *prev_name = NULL;
29 const char *errmsg;
30 unsigned int prev_namelen = 0;
31 int ret = 0;
32 bool silent = false;
33
34 while (de < end) {
35 const char *de_name;
36 unsigned int de_namelen;
37 unsigned int nameoff;
38
39 nameoff = le16_to_cpu(de->nameoff);
40 de_name = (char *)dentry_blk + nameoff;
41
42 /* the last dirent check */
43 if (de + 1 >= end)
44 de_namelen = strnlen(de_name, maxsize - nameoff);
45 else
46 de_namelen = le16_to_cpu(de[1].nameoff) - nameoff;
47
48 ctx->de_nid = le64_to_cpu(de->nid);
49 erofs_dbg("traversed nid (%llu)", ctx->de_nid | 0ULL);
50
51 ret = -EFSCORRUPTED;
52 /* corrupted entry check */
53 if (nameoff != next_nameoff) {
54 errmsg = "bogus dirent nameoff";
55 break;
56 }
57
58 if (nameoff + de_namelen > maxsize || !de_namelen ||
59 de_namelen > EROFS_NAME_LEN) {
60 errmsg = "bogus dirent namelen";
61 break;
62 }
63
64 if (fsck && prev_name) {
65 int cmp = strncmp(prev_name, de_name,
66 min(prev_namelen, de_namelen));
67
68 if (cmp > 0 || (cmp == 0 &&
69 prev_namelen >= de_namelen)) {
70 errmsg = "wrong dirent name order";
71 break;
72 }
73 }
74
75 if (fsck && de->file_type >= EROFS_FT_MAX) {
76 errmsg = "invalid file type %u";
77 break;
78 }
79
80 ctx->dname = de_name;
81 ctx->de_namelen = de_namelen;
82 ctx->de_ftype = de->file_type;
83 ctx->dot_dotdot = is_dot_dotdot_len(de_name, de_namelen);
84 if (ctx->dot_dotdot) {
85 switch (de_namelen) {
86 case 2:
87 if (fsck &&
88 (ctx->flags & EROFS_READDIR_DOTDOT_FOUND)) {
89 errmsg = "duplicated `..' dirent";
90 goto out;
91 }
92 ctx->flags |= EROFS_READDIR_DOTDOT_FOUND;
93 if (sbi->root_nid == ctx->dir->nid) {
94 ctx->pnid = sbi->root_nid;
95 ctx->flags |= EROFS_READDIR_VALID_PNID;
96 }
97 if (fsck &&
98 (ctx->flags & EROFS_READDIR_VALID_PNID) &&
99 ctx->de_nid != ctx->pnid) {
100 errmsg = "corrupted `..' dirent";
101 goto out;
102 }
103 break;
104 case 1:
105 if (fsck &&
106 (ctx->flags & EROFS_READDIR_DOT_FOUND)) {
107 errmsg = "duplicated `.' dirent";
108 goto out;
109 }
110
111 ctx->flags |= EROFS_READDIR_DOT_FOUND;
112 if (fsck && ctx->de_nid != ctx->dir->nid) {
113 errmsg = "corrupted `.' dirent";
114 goto out;
115 }
116 break;
117 }
118 } else if (fsck &&
119 !erofs_validate_filename(de_name, de_namelen)) {
120 errmsg = "corrupted dirent with illegal filename";
121 goto out;
122 }
123 ret = ctx->cb(ctx);
124 if (ret) {
125 silent = true;
126 break;
127 }
128 prev_name = de_name;
129 prev_namelen = de_namelen;
130 next_nameoff += de_namelen;
131 ++de;
132 }
133 out:
134 if (ret && !silent)
135 erofs_err("%s @ nid %llu, lblk %u, index %lu",
136 errmsg, ctx->dir->nid | 0ULL, lblk,
137 (de - (struct erofs_dirent *)dentry_blk) | 0UL);
138 return ret;
139 }
140
erofs_iterate_dir(struct erofs_dir_context * ctx,bool fsck)141 int erofs_iterate_dir(struct erofs_dir_context *ctx, bool fsck)
142 {
143 struct erofs_inode *dir = ctx->dir;
144 struct erofs_sb_info *sbi = dir->sbi;
145 int err = 0;
146 erofs_off_t pos;
147 char buf[EROFS_MAX_BLOCK_SIZE];
148
149 if (!S_ISDIR(dir->i_mode))
150 return -ENOTDIR;
151
152 ctx->flags &= ~EROFS_READDIR_ALL_SPECIAL_FOUND;
153 pos = 0;
154 while (pos < dir->i_size) {
155 erofs_blk_t lblk = erofs_blknr(sbi, pos);
156 erofs_off_t maxsize = min_t(erofs_off_t,
157 dir->i_size - pos, erofs_blksiz(sbi));
158 const struct erofs_dirent *de = (const void *)buf;
159 unsigned int nameoff;
160
161 err = erofs_pread(dir, buf, maxsize, pos);
162 if (err) {
163 erofs_err("I/O error occurred when reading dirents @ nid %llu, lblk %u: %d",
164 dir->nid | 0ULL, lblk, err);
165 return err;
166 }
167
168 nameoff = le16_to_cpu(de->nameoff);
169 if (nameoff < sizeof(struct erofs_dirent) ||
170 nameoff >= erofs_blksiz(sbi)) {
171 erofs_err("invalid de[0].nameoff %u @ nid %llu, lblk %u",
172 nameoff, dir->nid | 0ULL, lblk);
173 return -EFSCORRUPTED;
174 }
175 err = traverse_dirents(ctx, buf, lblk, nameoff, maxsize, fsck);
176 if (err)
177 break;
178 pos += maxsize;
179 }
180
181 if (fsck && (ctx->flags & EROFS_READDIR_ALL_SPECIAL_FOUND) !=
182 EROFS_READDIR_ALL_SPECIAL_FOUND) {
183 erofs_err("`.' or `..' dirent is missing @ nid %llu",
184 dir->nid | 0ULL);
185 return -EFSCORRUPTED;
186 }
187 return err;
188 }
189
190 #define EROFS_PATHNAME_FOUND 1
191
192 struct erofs_get_pathname_context {
193 struct erofs_dir_context ctx;
194 erofs_nid_t target_nid;
195 char *buf;
196 size_t size;
197 size_t pos;
198 };
199
erofs_get_pathname_iter(struct erofs_dir_context * ctx)200 static int erofs_get_pathname_iter(struct erofs_dir_context *ctx)
201 {
202 int ret;
203 struct erofs_get_pathname_context *pathctx = (void *)ctx;
204 const char *dname = ctx->dname;
205 size_t len = ctx->de_namelen;
206 size_t pos = pathctx->pos;
207
208 if (ctx->dot_dotdot)
209 return 0;
210
211 if (ctx->de_nid == pathctx->target_nid) {
212 if (pos + len + 2 > pathctx->size) {
213 erofs_err("get_pathname buffer not large enough: len %zd, size %zd",
214 pos + len + 2, pathctx->size);
215 return -ERANGE;
216 }
217
218 pathctx->buf[pos++] = '/';
219 strncpy(pathctx->buf + pos, dname, len);
220 pathctx->buf[pos + len] = '\0';
221 return EROFS_PATHNAME_FOUND;
222 }
223
224 if (ctx->de_ftype == EROFS_FT_DIR || ctx->de_ftype == EROFS_FT_UNKNOWN) {
225 struct erofs_inode dir = {
226 .sbi = ctx->dir->sbi,
227 .nid = ctx->de_nid
228 };
229
230 ret = erofs_read_inode_from_disk(&dir);
231 if (ret) {
232 erofs_err("read inode failed @ nid %llu", dir.nid | 0ULL);
233 return ret;
234 }
235
236 if (S_ISDIR(dir.i_mode)) {
237 struct erofs_get_pathname_context nctx = {
238 .ctx.flags = 0,
239 .ctx.dir = &dir,
240 .ctx.cb = erofs_get_pathname_iter,
241 .target_nid = pathctx->target_nid,
242 .buf = pathctx->buf,
243 .size = pathctx->size,
244 .pos = pos + len + 1,
245 };
246 ret = erofs_iterate_dir(&nctx.ctx, false);
247 if (ret == EROFS_PATHNAME_FOUND) {
248 pathctx->buf[pos++] = '/';
249 strncpy(pathctx->buf + pos, dname, len);
250 }
251 return ret;
252 } else if (ctx->de_ftype == EROFS_FT_DIR) {
253 erofs_err("i_mode and file_type are inconsistent @ nid %llu",
254 dir.nid | 0ULL);
255 }
256 }
257 return 0;
258 }
259
erofs_get_pathname(struct erofs_sb_info * sbi,erofs_nid_t nid,char * buf,size_t size)260 int erofs_get_pathname(struct erofs_sb_info *sbi, erofs_nid_t nid,
261 char *buf, size_t size)
262 {
263 int ret;
264 struct erofs_inode root = {
265 .sbi = sbi,
266 .nid = sbi->root_nid,
267 };
268 struct erofs_get_pathname_context pathctx = {
269 .ctx.flags = 0,
270 .ctx.dir = &root,
271 .ctx.cb = erofs_get_pathname_iter,
272 .target_nid = nid,
273 .buf = buf,
274 .size = size,
275 .pos = 0,
276 };
277
278 if (nid == root.nid) {
279 if (size < 2) {
280 erofs_err("get_pathname buffer not large enough: len 2, size %zd",
281 size);
282 return -ERANGE;
283 }
284
285 buf[0] = '/';
286 buf[1] = '\0';
287 return 0;
288 }
289
290 ret = erofs_read_inode_from_disk(&root);
291 if (ret) {
292 erofs_err("read inode failed @ nid %llu", root.nid | 0ULL);
293 return ret;
294 }
295
296 ret = erofs_iterate_dir(&pathctx.ctx, false);
297 if (ret == EROFS_PATHNAME_FOUND)
298 return 0;
299 if (!ret)
300 return -ENOENT;
301 return ret;
302 }
303