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