1 /*
2  * Copyright (c) 2019 Peter Bigot Consulting, LLC
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /* Basic littlefs operations:
8  * * create
9  * * write
10  * * stat
11  * * read
12  * * seek
13  * * tell
14  * * truncate
15  * * unlink
16  * * sync
17  */
18 
19 #include <string.h>
20 #include <zephyr/ztest.h>
21 #include "testfs_tests.h"
22 #include "testfs_lfs.h"
23 #include <lfs.h>
24 
25 #include <zephyr/fs/littlefs.h>
26 
27 #define HELLO "hello"
28 #define GOODBYE "goodbye"
29 
mount(struct fs_mount_t * mp)30 static int mount(struct fs_mount_t *mp)
31 {
32 	TC_PRINT("mounting %s\n", mp->mnt_point);
33 
34 	zassert_equal(fs_mount(mp), 0,
35 		      "mount failed");
36 
37 	return TC_PASS;
38 }
39 
clear_partition(struct fs_mount_t * mp)40 static int clear_partition(struct fs_mount_t *mp)
41 {
42 	TC_PRINT("clearing partition %s\n", mp->mnt_point);
43 
44 	zassert_equal(testfs_lfs_wipe_partition(mp),
45 		      TC_PASS,
46 		      "failed to wipe partition");
47 
48 	return TC_PASS;
49 }
50 
clean_statvfs(const struct fs_mount_t * mp)51 static int clean_statvfs(const struct fs_mount_t *mp)
52 {
53 	struct fs_statvfs stat;
54 
55 	TC_PRINT("checking clean statvfs of %s\n", mp->mnt_point);
56 
57 	zassert_equal(fs_statvfs(mp->mnt_point, &stat), 0,
58 		      "statvfs failed");
59 
60 	TC_PRINT("%s: bsize %lu ; frsize %lu ; blocks %lu ; bfree %lu\n",
61 		 mp->mnt_point,
62 		 stat.f_bsize, stat.f_frsize, stat.f_blocks, stat.f_bfree);
63 	zassert_equal(stat.f_bsize, 16,
64 		      "bsize fail");
65 	zassert_equal(stat.f_frsize, 4096,
66 		      "frsize fail");
67 	zassert_equal(stat.f_blocks, 16,
68 		      "blocks fail");
69 	zassert_equal(stat.f_bfree, stat.f_blocks - 2U,
70 		      "bfree fail");
71 
72 	return TC_PASS;
73 }
74 
create_write_hello(const struct fs_mount_t * mp)75 static int create_write_hello(const struct fs_mount_t *mp)
76 {
77 	struct testfs_path path;
78 	struct fs_file_t file;
79 
80 	fs_file_t_init(&file);
81 	TC_PRINT("creating and writing file\n");
82 
83 	zassert_equal(fs_open(&file,
84 			      testfs_path_init(&path, mp,
85 					       HELLO,
86 					       TESTFS_PATH_END),
87 			      FS_O_CREATE | FS_O_RDWR),
88 		      0,
89 		      "open hello failed");
90 
91 	struct fs_dirent stat;
92 
93 	zassert_equal(fs_stat(path.path, &stat),
94 		      0,
95 		      "stat new hello failed");
96 
97 	zassert_equal(stat.type, FS_DIR_ENTRY_FILE,
98 		      "stat new hello not file");
99 	zassert_equal(strcmp(stat.name, HELLO), 0,
100 		      "stat new hello not hello");
101 	zassert_equal(stat.size, 0,
102 		      "stat new hello not empty");
103 
104 	zassert_equal(testfs_write_incrementing(&file, 0, TESTFS_BUFFER_SIZE),
105 		      TESTFS_BUFFER_SIZE,
106 		      "write constant failed");
107 
108 	zassert_equal(fs_stat(path.path, &stat),
109 		      0,
110 		      "stat written hello failed");
111 
112 	zassert_equal(stat.type, FS_DIR_ENTRY_FILE,
113 		      "stat written hello not file");
114 	zassert_equal(strcmp(stat.name, HELLO), 0,
115 		      "stat written hello not hello");
116 
117 	/* Anomalous behavior requiring upstream response */
118 	if (true) {
119 		/* VARIATION POINT: littlefs does not update the file size of
120 		 * an open file.  See upstream issue #250.
121 		 */
122 		zassert_equal(stat.size, 0,
123 			      "stat written hello bad size");
124 	}
125 
126 	zassert_equal(fs_close(&file), 0,
127 		      "close hello failed");
128 
129 	zassert_equal(fs_stat(path.path, &stat),
130 		      0,
131 		      "stat closed hello failed");
132 
133 	zassert_equal(stat.type, FS_DIR_ENTRY_FILE,
134 		      "stat closed hello not file");
135 	zassert_equal(strcmp(stat.name, HELLO), 0,
136 		      "stat closed hello not hello");
137 	zassert_equal(stat.size, TESTFS_BUFFER_SIZE,
138 		      "stat closed hello badsize");
139 
140 	return TC_PASS;
141 }
142 
verify_hello(const struct fs_mount_t * mp)143 static int verify_hello(const struct fs_mount_t *mp)
144 {
145 	struct testfs_path path;
146 	struct fs_file_t file;
147 
148 	fs_file_t_init(&file);
149 	TC_PRINT("opening and verifying file\n");
150 
151 	zassert_equal(fs_open(&file,
152 			      testfs_path_init(&path, mp,
153 					       HELLO,
154 					       TESTFS_PATH_END),
155 			      FS_O_CREATE | FS_O_RDWR),
156 		      0,
157 		      "verify hello open failed");
158 
159 	zassert_equal(fs_tell(&file), 0U,
160 		      "verify hello open tell failed");
161 
162 	zassert_equal(testfs_verify_incrementing(&file, 0, TESTFS_BUFFER_SIZE),
163 		      TESTFS_BUFFER_SIZE,
164 		      "verify hello at start failed");
165 
166 	zassert_equal(fs_tell(&file), TESTFS_BUFFER_SIZE,
167 		      "verify hello read tell failed");
168 
169 	zassert_equal(fs_close(&file), 0,
170 		      "verify close hello failed");
171 
172 	return TC_PASS;
173 }
174 
seek_within_hello(const struct fs_mount_t * mp)175 static int seek_within_hello(const struct fs_mount_t *mp)
176 {
177 	struct testfs_path path;
178 	struct fs_file_t file;
179 
180 	fs_file_t_init(&file);
181 	TC_PRINT("seek and tell in file\n");
182 
183 	zassert_equal(fs_open(&file,
184 			      testfs_path_init(&path, mp,
185 					       HELLO,
186 					       TESTFS_PATH_END),
187 			      FS_O_CREATE | FS_O_RDWR),
188 		      0,
189 		      "verify hello open failed");
190 
191 	zassert_equal(fs_tell(&file), 0U,
192 		      "verify hello open tell failed");
193 
194 	struct fs_dirent stat;
195 
196 	zassert_equal(fs_stat(path.path, &stat),
197 		      0,
198 		      "stat old hello failed");
199 	zassert_equal(stat.size, TESTFS_BUFFER_SIZE,
200 		      "stat old hello bad size");
201 
202 	off_t pos = stat.size / 4;
203 
204 	zassert_equal(fs_seek(&file, pos, FS_SEEK_SET),
205 		      0,
206 		      "verify hello seek near mid failed");
207 
208 	zassert_equal(fs_tell(&file), pos,
209 		      "verify hello tell near mid failed");
210 
211 	zassert_equal(testfs_verify_incrementing(&file, pos, TESTFS_BUFFER_SIZE),
212 		      TESTFS_BUFFER_SIZE - pos,
213 		      "verify hello at middle failed");
214 
215 	zassert_equal(fs_tell(&file), stat.size,
216 		      "verify hello read middle tell failed");
217 
218 	zassert_equal(fs_seek(&file, -stat.size, FS_SEEK_CUR),
219 		      0,
220 		      "verify hello seek back from cur failed");
221 
222 	zassert_equal(fs_tell(&file), 0U,
223 		      "verify hello tell back from cur failed");
224 
225 	zassert_equal(fs_seek(&file, -pos, FS_SEEK_END),
226 		      0,
227 		      "verify hello seek from end failed");
228 
229 	zassert_equal(fs_tell(&file), stat.size - pos,
230 		      "verify hello tell from end failed");
231 
232 	zassert_equal(testfs_verify_incrementing(&file, stat.size - pos,
233 						 TESTFS_BUFFER_SIZE),
234 		      pos,
235 		      "verify hello at post middle failed");
236 
237 	zassert_equal(fs_close(&file), 0,
238 		      "verify close hello failed");
239 
240 	return TC_PASS;
241 }
242 
truncate_hello(const struct fs_mount_t * mp)243 static int truncate_hello(const struct fs_mount_t *mp)
244 {
245 	struct testfs_path path;
246 	struct fs_file_t file;
247 
248 	fs_file_t_init(&file);
249 	TC_PRINT("truncate in file\n");
250 
251 	zassert_equal(fs_open(&file,
252 			      testfs_path_init(&path, mp,
253 					       HELLO,
254 					       TESTFS_PATH_END),
255 			      FS_O_CREATE | FS_O_RDWR),
256 		      0,
257 		      "verify hello open failed");
258 
259 	struct fs_dirent stat;
260 
261 	zassert_equal(fs_stat(path.path, &stat),
262 		      0,
263 		      "stat old hello failed");
264 	zassert_equal(stat.size, TESTFS_BUFFER_SIZE,
265 		      "stat old hello bad size");
266 
267 	off_t pos = 3 * stat.size / 4;
268 
269 	zassert_equal(fs_tell(&file), 0U,
270 		      "truncate initial tell failed");
271 
272 	zassert_equal(fs_truncate(&file, pos),
273 		      0,
274 		      "truncate 3/4 failed");
275 
276 	zassert_equal(fs_tell(&file), 0U,
277 		      "truncate post tell failed");
278 
279 	zassert_equal(fs_stat(path.path, &stat),
280 		      0,
281 		      "stat open 3/4 failed");
282 
283 	/* Anomalous behavior requiring upstream response */
284 	if (true) {
285 		/* VARIATION POINT: littlefs does not update the file size of
286 		 * an open file.  See upstream issue #250.
287 		 */
288 		zassert_equal(stat.size, TESTFS_BUFFER_SIZE,
289 			      "stat open 3/4 bad size");
290 	}
291 
292 	zassert_equal(testfs_verify_incrementing(&file, 0, 64),
293 		      48,
294 		      "post truncate content unexpected");
295 
296 	zassert_equal(fs_close(&file), 0,
297 		      "post truncate close failed");
298 
299 	/* After close size is correct. */
300 	zassert_equal(fs_stat(path.path, &stat),
301 		      0,
302 		      "stat closed truncated failed");
303 	zassert_equal(stat.size, pos,
304 		      "stat closed truncated bad size");
305 
306 	return TC_PASS;
307 }
308 
unlink_hello(const struct fs_mount_t * mp)309 static int unlink_hello(const struct fs_mount_t *mp)
310 {
311 	struct testfs_path path;
312 
313 	TC_PRINT("unlink hello\n");
314 
315 	testfs_path_init(&path, mp,
316 			 HELLO,
317 			 TESTFS_PATH_END);
318 
319 	struct fs_dirent stat;
320 
321 	zassert_equal(fs_stat(path.path, &stat),
322 		      0,
323 		      "stat existing hello failed");
324 	zassert_equal(fs_unlink(path.path),
325 		      0,
326 		      "unlink hello failed");
327 	zassert_equal(fs_stat(path.path, &stat),
328 		      -ENOENT,
329 		      "stat existing hello failed");
330 
331 	return TC_PASS;
332 }
333 
sync_goodbye(const struct fs_mount_t * mp)334 static int sync_goodbye(const struct fs_mount_t *mp)
335 {
336 	struct testfs_path path;
337 	struct fs_file_t file;
338 
339 	fs_file_t_init(&file);
340 	TC_PRINT("sync goodbye\n");
341 
342 	zassert_equal(fs_open(&file,
343 			      testfs_path_init(&path, mp,
344 					       GOODBYE,
345 					       TESTFS_PATH_END),
346 			      FS_O_CREATE | FS_O_RDWR),
347 		      0,
348 		      "sync goodbye failed");
349 
350 	struct fs_dirent stat;
351 
352 	zassert_equal(fs_stat(path.path, &stat),
353 		      0,
354 		      "stat existing hello failed");
355 	zassert_equal(stat.size, 0,
356 		      "stat new goodbye not empty");
357 
358 	zassert_equal(testfs_write_incrementing(&file, 0, TESTFS_BUFFER_SIZE),
359 		      TESTFS_BUFFER_SIZE,
360 		      "write goodbye failed");
361 
362 	zassert_equal(fs_tell(&file), TESTFS_BUFFER_SIZE,
363 		      "tell goodbye failed");
364 
365 	if (true) {
366 		/* Upstream issue #250 */
367 		zassert_equal(stat.size, 0,
368 			      "stat new goodbye not empty");
369 	}
370 
371 	zassert_equal(fs_sync(&file), 0,
372 		      "sync goodbye failed");
373 
374 	zassert_equal(fs_tell(&file), TESTFS_BUFFER_SIZE,
375 		      "tell synced moved");
376 
377 	zassert_equal(fs_stat(path.path, &stat),
378 		      0,
379 		      "stat existing hello failed");
380 	printk("sync size %u\n", (uint32_t)stat.size);
381 
382 	zassert_equal(stat.size, TESTFS_BUFFER_SIZE,
383 		      "stat synced goodbye not correct");
384 
385 	zassert_equal(fs_close(&file), 0,
386 		      "post sync close failed");
387 
388 	/* After close size is correct. */
389 	zassert_equal(fs_stat(path.path, &stat),
390 		      0,
391 		      "stat sync failed");
392 	zassert_equal(stat.size, TESTFS_BUFFER_SIZE,
393 		      "stat sync bad size");
394 
395 	return TC_PASS;
396 }
397 
verify_goodbye(const struct fs_mount_t * mp)398 static int verify_goodbye(const struct fs_mount_t *mp)
399 {
400 	struct testfs_path path;
401 	struct fs_file_t file;
402 
403 	fs_file_t_init(&file);
404 	TC_PRINT("verify goodbye\n");
405 
406 	zassert_equal(fs_open(&file,
407 			      testfs_path_init(&path, mp,
408 					       GOODBYE,
409 					       TESTFS_PATH_END),
410 			      FS_O_CREATE | FS_O_RDWR),
411 		      0,
412 		      "verify goodbye failed");
413 
414 	zassert_equal(testfs_verify_incrementing(&file, 0, TESTFS_BUFFER_SIZE),
415 		      TESTFS_BUFFER_SIZE,
416 		      "write goodbye failed");
417 
418 	zassert_equal(fs_close(&file), 0,
419 		      "post sync close failed");
420 
421 	return TC_PASS;
422 }
423 
check_medium(void)424 static int check_medium(void)
425 {
426 	struct fs_mount_t *mp = &testfs_medium_mnt;
427 	struct fs_statvfs stat;
428 
429 	zassert_equal(clear_partition(mp), TC_PASS,
430 		      "clear partition failed");
431 
432 	zassert_equal(fs_mount(mp), 0,
433 		      "medium mount failed");
434 
435 	zassert_equal(fs_statvfs(mp->mnt_point, &stat), 0,
436 		      "statvfs failed");
437 
438 	TC_PRINT("%s: bsize %lu ; frsize %lu ; blocks %lu ; bfree %lu\n",
439 		 mp->mnt_point,
440 		 stat.f_bsize, stat.f_frsize, stat.f_blocks, stat.f_bfree);
441 	zassert_equal(stat.f_bsize, MEDIUM_IO_SIZE,
442 		      "bsize fail");
443 	zassert_equal(stat.f_frsize, 4096,
444 		      "frsize fail");
445 	zassert_equal(stat.f_blocks, 240,
446 		      "blocks fail");
447 	zassert_equal(stat.f_bfree, stat.f_blocks - 2U,
448 		      "bfree fail");
449 
450 	zassert_equal(fs_unmount(mp), 0,
451 		      "medium unmount failed");
452 
453 	return TC_PASS;
454 }
455 
check_large(void)456 static int check_large(void)
457 {
458 	struct fs_mount_t *mp = &testfs_large_mnt;
459 	struct fs_statvfs stat;
460 
461 	zassert_equal(clear_partition(mp), TC_PASS,
462 		      "clear partition failed");
463 
464 	zassert_equal(fs_mount(mp), 0,
465 		      "large mount failed");
466 
467 	zassert_equal(fs_statvfs(mp->mnt_point, &stat), 0,
468 		      "statvfs failed");
469 
470 	TC_PRINT("%s: bsize %lu ; frsize %lu ; blocks %lu ; bfree %lu\n",
471 		 mp->mnt_point,
472 		 stat.f_bsize, stat.f_frsize, stat.f_blocks, stat.f_bfree);
473 	zassert_equal(stat.f_bsize, LARGE_IO_SIZE,
474 		      "bsize fail");
475 	zassert_equal(stat.f_frsize, 32768,
476 		      "frsize fail");
477 	zassert_equal(stat.f_blocks, 96,
478 		      "blocks fail");
479 	zassert_equal(stat.f_bfree, stat.f_blocks - 2U,
480 		      "bfree fail");
481 
482 	zassert_equal(fs_unmount(mp), 0,
483 		      "large unmount failed");
484 
485 	return TC_PASS;
486 }
487 
num_files(struct fs_mount_t * mp)488 static int num_files(struct fs_mount_t *mp)
489 {
490 	struct testfs_path path;
491 	char name[2] = { 0 };
492 	const char *pstr;
493 	struct fs_file_t files[CONFIG_FS_LITTLEFS_NUM_FILES];
494 	size_t fi = 0;
495 	int rc;
496 
497 	memset(files, 0, sizeof(files));
498 
499 	TC_PRINT("CONFIG_FS_LITTLEFS_NUM_FILES=%u\n", CONFIG_FS_LITTLEFS_NUM_FILES);
500 	while (fi < ARRAY_SIZE(files)) {
501 		struct fs_file_t *const file = &files[fi];
502 
503 		name[0] = 'A' + fi;
504 		pstr = testfs_path_init(&path, mp,
505 					name,
506 					TESTFS_PATH_END);
507 
508 		TC_PRINT("opening %s\n", pstr);
509 		rc = fs_open(file, pstr, FS_O_CREATE | FS_O_RDWR);
510 		zassert_equal(rc, 0, "open %s failed: %d", pstr, rc);
511 
512 		rc = testfs_write_incrementing(file, 0, TESTFS_BUFFER_SIZE);
513 		zassert_equal(rc, TESTFS_BUFFER_SIZE, "write %s failed: %d", pstr, rc);
514 
515 		++fi;
516 	}
517 
518 	while (fi-- != 0)  {
519 		struct fs_file_t *const file = &files[fi];
520 
521 		name[0] = 'A' + fi;
522 		pstr = testfs_path_init(&path, mp,
523 					name,
524 					TESTFS_PATH_END);
525 
526 		TC_PRINT("Close and unlink %s\n", pstr);
527 
528 		rc = fs_close(file);
529 		zassert_equal(rc, 0, "close %s failed: %d", pstr, rc);
530 
531 		rc = fs_unlink(pstr);
532 		zassert_equal(rc, 0, "unlink %s failed: %d", pstr, rc);
533 	}
534 
535 	return TC_PASS;
536 }
537 
num_dirs(struct fs_mount_t * mp)538 static int num_dirs(struct fs_mount_t *mp)
539 {
540 	struct testfs_path path;
541 	char name[3] = "Dx";
542 	const char *pstr;
543 	struct fs_dir_t dirs[CONFIG_FS_LITTLEFS_NUM_DIRS];
544 	size_t di = 0;
545 	int rc;
546 
547 	memset(dirs, 0, sizeof(dirs));
548 
549 	TC_PRINT("CONFIG_FS_LITTLEFS_NUM_DIRS=%u\n", CONFIG_FS_LITTLEFS_NUM_DIRS);
550 	while (di < ARRAY_SIZE(dirs)) {
551 		struct fs_dir_t *const dir = &dirs[di];
552 
553 		name[1] = 'A' + di;
554 		pstr = testfs_path_init(&path, mp,
555 					name,
556 					TESTFS_PATH_END);
557 
558 		TC_PRINT("making and opening directory %s\n", pstr);
559 		rc = fs_mkdir(pstr);
560 		zassert_equal(rc, 0, "mkdir %s failed: %d", pstr, rc);
561 
562 		rc = fs_opendir(dir, pstr);
563 		zassert_equal(rc, 0, "opendir %s failed: %d", name, rc);
564 
565 		++di;
566 	}
567 
568 	while (di-- != 0)  {
569 		struct fs_dir_t *const dir = &dirs[di];
570 
571 		name[1] = 'A' + di;
572 		pstr = testfs_path_init(&path, mp,
573 					name,
574 					TESTFS_PATH_END);
575 
576 		TC_PRINT("Close and rmdir %s\n", pstr);
577 
578 		rc = fs_closedir(dir);
579 		zassert_equal(rc, 0, "closedir %s failed: %d", name, rc);
580 
581 		rc = fs_unlink(pstr);
582 		zassert_equal(rc, 0, "unlink %s failed: %d", name, rc);
583 	}
584 
585 	return TC_PASS;
586 }
587 
ZTEST(littlefs,test_lfs_basic)588 ZTEST(littlefs, test_lfs_basic)
589 {
590 	struct fs_mount_t *mp = &testfs_small_mnt;
591 
592 	zassert_equal(clear_partition(mp), TC_PASS,
593 		      "clear partition failed");
594 
595 	zassert_equal(mount(mp), TC_PASS,
596 		      "clean mount failed");
597 
598 	zassert_equal(clean_statvfs(mp), TC_PASS,
599 		      "clean statvfs failed");
600 
601 	zassert_equal(create_write_hello(mp), TC_PASS,
602 		      "write hello failed");
603 
604 	zassert_equal(verify_hello(mp), TC_PASS,
605 		      "verify hello failed");
606 
607 	zassert_equal(seek_within_hello(mp), TC_PASS,
608 		      "seek within hello failed");
609 
610 	zassert_equal(truncate_hello(mp), TC_PASS,
611 		      "truncate hello failed");
612 
613 	zassert_equal(unlink_hello(mp), TC_PASS,
614 		      "unlink hello failed");
615 
616 	zassert_equal(sync_goodbye(mp), TC_PASS,
617 		      "sync goodbye failed");
618 
619 	zassert_equal(num_files(mp), TC_PASS,
620 		      "num_files failed");
621 
622 	zassert_equal(num_dirs(mp), TC_PASS,
623 		      "num_dirs failed");
624 
625 	TC_PRINT("unmounting %s\n", mp->mnt_point);
626 	zassert_equal(fs_unmount(mp), 0,
627 		      "unmount small failed");
628 
629 	k_sleep(K_MSEC(100));   /* flush log messages */
630 	TC_PRINT("checking double unmount diagnoses\n");
631 	zassert_equal(fs_unmount(mp), -EINVAL,
632 		      "unmount unmounted failed");
633 
634 	zassert_equal(mount(mp), TC_PASS,
635 		      "remount failed");
636 
637 	zassert_equal(verify_goodbye(mp), TC_PASS,
638 		      "verify goodbye failed");
639 
640 	zassert_equal(fs_unmount(mp), 0,
641 		      "unmount2 small failed");
642 
643 	if (IS_ENABLED(CONFIG_APP_TEST_CUSTOM)) {
644 		zassert_equal(check_medium(), TC_PASS,
645 			      "check medium failed");
646 
647 		zassert_equal(check_large(), TC_PASS,
648 			      "check large failed");
649 	}
650 }
651