1 /*
2 * Copyright (c) 2019 Peter Bigot Consulting, LLC
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <string.h>
8 #include <errno.h>
9 #include <zephyr/sys/__assert.h>
10 #include <zephyr/ztest.h>
11 #include "test_fs_util.h"
12
path_vextend(struct testfs_path * pp,va_list ap)13 static const char *path_vextend(struct testfs_path *pp,
14 va_list ap)
15 {
16 const char *ep = va_arg(ap, const char *);
17 const char *endp = pp->path + sizeof(pp->path);
18
19 while (ep) {
20 size_t len = strlen(ep);
21 char *eos = pp->eos;
22 size_t rem = (endp - eos);
23
24 if (strcmp(ep, "..") == 0) {
25 char *sp = strrchr(pp->path, '/');
26
27 if (sp == pp->path) {
28 eos = sp + 1;
29 } else {
30 eos = sp;
31 }
32 } else if ((1 + len + 1) < rem) { /* /, ep, EOS */
33 *eos = '/';
34 ++eos;
35 memcpy(eos, ep, len);
36 eos += len;
37 } else {
38 break;
39 }
40 *eos = 0;
41 pp->eos = eos;
42
43 ep = va_arg(ap, const char *);
44 }
45 return pp->path;
46 }
47
testfs_path_init(struct testfs_path * pp,const struct fs_mount_t * mp,...)48 const char *testfs_path_init(struct testfs_path *pp,
49 const struct fs_mount_t *mp,
50 ...)
51 {
52 va_list ap;
53
54 if (mp == NULL) {
55 pp->path[0] = '/';
56 pp->eos = pp->path + 1;
57 } else {
58 size_t len = strlen(mp->mnt_point);
59
60 __ASSERT('/' == mp->mnt_point[0], "relative mount point");
61 if ((len + 1) >= sizeof(pp->path)) {
62 len = sizeof(pp->path) - 1;
63 }
64 strncpy(pp->path, mp->mnt_point, len);
65 pp->eos = pp->path + len;
66 }
67 *pp->eos = '\0';
68
69 va_start(ap, mp);
70 path_vextend(pp, ap);
71 va_end(ap);
72 return pp->path;
73 }
74
testfs_path_extend(struct testfs_path * pp,...)75 const char *testfs_path_extend(struct testfs_path *pp,
76 ...)
77 {
78 va_list ap;
79
80 va_start(ap, pp);
81 path_vextend(pp, ap);
82 va_end(ap);
83
84 return pp->path;
85 }
86
testfs_write_constant(struct fs_file_t * fp,uint8_t value,unsigned int len)87 int testfs_write_constant(struct fs_file_t *fp,
88 uint8_t value,
89 unsigned int len)
90 {
91 uint8_t buffer[TESTFS_BUFFER_SIZE];
92 unsigned int rem = len;
93
94 memset(buffer, value, sizeof(buffer));
95
96 while (rem > 0) {
97 unsigned int count = sizeof(buffer);
98
99 if (count > rem) {
100 count = rem;
101 }
102
103 ssize_t rc = fs_write(fp, buffer, count);
104
105 if (rc < 0) {
106 return rc;
107 }
108
109 rem -= count;
110 }
111
112 return len;
113 }
114
testfs_verify_constant(struct fs_file_t * fp,uint8_t value,unsigned int len)115 int testfs_verify_constant(struct fs_file_t *fp,
116 uint8_t value,
117 unsigned int len)
118 {
119 uint8_t buffer[TESTFS_BUFFER_SIZE];
120 unsigned int match = 0;
121
122 while (len > 0) {
123 unsigned int count = sizeof(buffer);
124
125 if (count > len) {
126 count = len;
127 }
128
129 int rc = fs_read(fp, buffer, count);
130
131 if (rc < 0) {
132 return rc;
133 }
134
135 if (rc > count) {
136 return -EIO;
137 }
138
139 for (unsigned int i = 0; i < rc; ++i) {
140 if (value != buffer[i]) {
141 break;
142 }
143 ++match;
144 }
145
146 if (rc < count) {
147 break;
148 }
149
150 len -= count;
151 }
152 return match;
153 }
154
testfs_write_incrementing(struct fs_file_t * fp,uint8_t value,unsigned int len)155 int testfs_write_incrementing(struct fs_file_t *fp,
156 uint8_t value,
157 unsigned int len)
158 {
159 uint8_t buffer[TESTFS_BUFFER_SIZE];
160 unsigned int rem = len;
161
162 while (rem > 0) {
163 unsigned int count = sizeof(buffer);
164
165 if (count > rem) {
166 count = rem;
167 }
168
169 for (unsigned int i = 0; i < count; ++i) {
170 buffer[i] = value++;
171 }
172
173 ssize_t rc = fs_write(fp, buffer, count);
174
175 if (rc < 0) {
176 return rc;
177 }
178
179 rem -= count;
180 }
181
182 return len;
183 }
184
testfs_verify_incrementing(struct fs_file_t * fp,uint8_t value,unsigned int len)185 int testfs_verify_incrementing(struct fs_file_t *fp,
186 uint8_t value,
187 unsigned int len)
188 {
189 uint8_t buffer[TESTFS_BUFFER_SIZE];
190 unsigned int match = 0;
191
192 while (len > 0) {
193 unsigned int count = sizeof(buffer);
194
195 if (count > len) {
196 count = len;
197 }
198
199 int rc = fs_read(fp, buffer, count);
200
201 if (rc < 0) {
202 return rc;
203 }
204
205 if (rc > count) {
206 return -EIO;
207 }
208
209 for (unsigned int i = 0; i < rc; ++i) {
210 if (value++ != buffer[i]) {
211 break;
212 }
213 ++match;
214 }
215
216 if (rc < count) {
217 break;
218 }
219
220 len -= count;
221 }
222 return match;
223 }
224
testfs_build(struct testfs_path * root,const struct testfs_bcmd * cp)225 int testfs_build(struct testfs_path *root,
226 const struct testfs_bcmd *cp)
227 {
228 int rc;
229
230 while (!TESTFS_BCMD_IS_END(cp)) {
231
232 if (TESTFS_BCMD_IS_FILE(cp)) {
233 struct fs_file_t file;
234
235 fs_file_t_init(&file);
236 rc = fs_open(&file,
237 testfs_path_extend(root,
238 cp->name,
239 TESTFS_PATH_END),
240 FS_O_CREATE | FS_O_RDWR);
241 TC_PRINT("create at %s with %u from 0x%02x: %d\n",
242 root->path, cp->size, cp->value, rc);
243 if (rc == 0) {
244 rc = testfs_write_incrementing(&file, cp->value, cp->size);
245 (void)fs_close(&file);
246 }
247 testfs_path_extend(root, "..", TESTFS_PATH_END);
248
249 if (rc < 0) {
250 TC_PRINT("FAILED create/write %s: %d\n",
251 root->path, rc);
252 return rc;
253 }
254
255 } else if (TESTFS_BCMD_IS_ENTER_DIR(cp)) {
256 testfs_path_extend(root,
257 cp->name,
258 TESTFS_PATH_END);
259 rc = fs_mkdir(root->path);
260 TC_PRINT("mkdir %s: %d\n", root->path, rc);
261 if (rc < 0) {
262 return rc;
263 }
264 } else if (TESTFS_BCMD_IS_EXIT_DIR(cp)) {
265 TC_PRINT("exit directory %s\n", root->path);
266 testfs_path_extend(root, "..", TESTFS_PATH_END);
267 } else {
268 TC_PRINT("ERROR: unexpected build command");
269 return -EINVAL;
270 }
271 ++cp;
272 }
273 return 0;
274 }
275
276 /* Check the found entry against a probably match. Recurse into
277 * matched directories.
278 *
279 * Sets the matched field of *cp if all tests pass.
280 *
281 * Return a negative error, or a nonnegative count of foreign
282 * files.
283 */
check_layout_entry(struct testfs_path * pp,const struct fs_dirent * statp,struct testfs_bcmd * cp,struct testfs_bcmd * ecp)284 static int check_layout_entry(struct testfs_path *pp,
285 const struct fs_dirent *statp,
286 struct testfs_bcmd *cp,
287 struct testfs_bcmd *ecp)
288 {
289 int rc = 0;
290 unsigned int foreign = 0;
291
292 /* Create the full path */
293 testfs_path_extend(pp, statp->name,
294 TESTFS_PATH_END);
295
296 /* Also check file content */
297 if (statp->type == FS_DIR_ENTRY_FILE) {
298 struct fs_file_t file;
299
300 fs_file_t_init(&file);
301 rc = fs_open(&file, pp->path, FS_O_CREATE | FS_O_RDWR);
302 if (rc < 0) {
303 TC_PRINT("%s: content check open failed: %d\n",
304 pp->path, rc);
305 rc = -ENOENT;
306 goto out;
307 }
308 rc = testfs_verify_incrementing(&file, cp->value, cp->size);
309 if (rc != cp->size) {
310 TC_PRINT("%s: content check failed: %d\n",
311 pp->path, rc);
312 if (rc >= 0) {
313 rc = -EIO;
314 }
315 goto out;
316 }
317 rc = fs_close(&file);
318 if (rc != 0) {
319 TC_PRINT("%s: content check close failed: %d\n",
320 pp->path, rc);
321 }
322 } else if (statp->type == FS_DIR_ENTRY_DIR) {
323 rc = testfs_bcmd_verify_layout(pp,
324 cp + 1,
325 testfs_bcmd_exitdir(cp, ecp));
326 if (rc >= 0) {
327 foreign = rc;
328 }
329 }
330
331 out:
332 testfs_path_extend(pp, "..",
333 TESTFS_PATH_END);
334
335 if (rc >= 0) {
336 cp->matched = true;
337 rc = foreign;
338 }
339
340 return rc;
341 }
342
testfs_bcmd_verify_layout(struct testfs_path * pp,struct testfs_bcmd * scp,struct testfs_bcmd * ecp)343 int testfs_bcmd_verify_layout(struct testfs_path *pp,
344 struct testfs_bcmd *scp,
345 struct testfs_bcmd *ecp)
346 {
347 struct fs_dir_t dir;
348 unsigned int count = 0;
349 unsigned int foreign = 0;
350 struct testfs_bcmd *cp = scp;
351
352 while (cp < ecp) {
353 cp->matched = false;
354 ++cp;
355 }
356
357 fs_dir_t_init(&dir);
358
359 int rc = fs_opendir(&dir, pp->path);
360
361 if (rc != 0) {
362 TC_PRINT("%s: opendir failed: %d\n", pp->path, rc);
363 if (rc > 0) {
364 rc = -EIO;
365 }
366 return rc;
367 }
368
369 TC_PRINT("check %s for %zu entries\n", pp->path, ecp - scp);
370 while (rc >= 0) {
371 struct fs_dirent stat;
372
373 rc = fs_readdir(&dir, &stat);
374 if (rc != 0) {
375 TC_PRINT("readdir failed: %d", rc);
376 rc = -EIO;
377 break;
378 }
379
380 if (stat.name[0] == '\0') {
381 break;
382 }
383
384 ++count;
385
386 cp = testfs_bcmd_find(&stat, scp, ecp);
387
388 bool dotdir = ((stat.type == FS_DIR_ENTRY_DIR)
389 && ((strcmp(stat.name, ".") == 0)
390 || (strcmp(stat.name, "..") == 0)));
391
392 TC_PRINT("%s %s%s%s %zu\n", pp->path,
393 stat.name,
394 (stat.type == FS_DIR_ENTRY_FILE) ? "" : "/",
395 dotdir ? " SYNTHESIZED"
396 : (cp == NULL) ? " FOREIGN"
397 : "",
398 stat.size);
399
400 if (dotdir) {
401 zassert_true(false,
402 "special directories observed");
403 } else if (cp != NULL) {
404 rc = check_layout_entry(pp, &stat, cp, ecp);
405 if (rc > 0) {
406 foreign += rc;
407 }
408 } else {
409 foreign += 1;
410 }
411 }
412 TC_PRINT("%s found %u entries, %u foreign\n", pp->path, count, foreign);
413
414 int rc2 = fs_closedir(&dir);
415
416 if (rc2 != 0) {
417 TC_PRINT("%s: closedir failed: %d\n",
418 pp->path, rc2);
419 if (rc >= 0) {
420 rc = (rc2 >= 0) ? -EIO : rc2;
421 }
422 }
423
424 if (rc >= 0) {
425 rc = foreign;
426 }
427
428 return rc;
429 }
430
testfs_bcmd_exitdir(struct testfs_bcmd * cp,struct testfs_bcmd * ecp)431 struct testfs_bcmd *testfs_bcmd_exitdir(struct testfs_bcmd *cp,
432 struct testfs_bcmd *ecp)
433 {
434 unsigned int level = 1;
435
436 /* Skip to the paired EXIT_DIR */
437 while ((level > 0) && (++cp < ecp)) {
438 if (TESTFS_BCMD_IS_ENTER_DIR(cp)) {
439 ++level;
440 } else if (TESTFS_BCMD_IS_EXIT_DIR(cp)) {
441 --level;
442 }
443 }
444 return cp;
445 }
446
testfs_bcmd_find(struct fs_dirent * statp,struct testfs_bcmd * scp,struct testfs_bcmd * ecp)447 struct testfs_bcmd *testfs_bcmd_find(struct fs_dirent *statp,
448 struct testfs_bcmd *scp,
449 struct testfs_bcmd *ecp)
450 {
451 struct testfs_bcmd *cp = scp;
452
453 while (cp < ecp) {
454 if ((cp->type == statp->type)
455 && (cp->name != NULL)
456 && (strcmp(cp->name, statp->name) == 0)
457 && (cp->size == statp->size)) {
458 return cp;
459 }
460
461 if (TESTFS_BCMD_IS_ENTER_DIR(cp)) {
462 cp = testfs_bcmd_exitdir(cp, ecp);
463 }
464 ++cp;
465 }
466
467 return NULL;
468 }
469