1 // SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
2 #include "erofs/print.h"
3 #include "erofs/dir.h"
4 #include <stdlib.h>
5 #include <sys/stat.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 ((dir->i_mode & S_IFMT) != S_IFDIR)
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 >= PAGE_SIZE) {
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