xref: /aosp_15_r20/external/erofs-utils/lib/dir.c (revision 33b1fccf6a0fada2c2875d400ed01119b7676ee5)
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