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