TARFS 0.1.5
Read-only TAR filesystem for ESP32
Loading...
Searching...
No Matches
inode.c
Go to the documentation of this file.
1/*
2 * TARFS - Immutable (read-only) filesystem for embedded systems.
3 *
4 * Copyright (c) 2026 Viacheslav Logunov
5 * SPDX-License-Identifier: MMIT
6 *
7 * Author:
8 * Viacheslav Logunov <vvb333007@gmail.com>
9 *
10 * Project:
11 * https://github.com/vvb333007/tarfs
12 *
13 * @file inode.c
14 * @brief Inode API implementation
15 */
16
17#include <stddef.h>
18#include <stdint.h>
19#include <stdlib.h>
20#include <stdio.h>
21#include <stdbool.h>
22#include <stdatomic.h>
23#include <string.h>
24#include <time.h>
25#include <unistd.h>
26#include <errno.h>
27
28#include "config.h"
29#include "os.h"
30#include "tar.h"
31#include "fs.h"
32#include "hash.h"
33#include "inode.h"
34
42
43
44
45
51static const char *remove_subpath(const char *path, const char *path_end, const char *subpath) {
52
53 const char *text = path;
54
55 while((uintptr_t)text < (uintptr_t)path_end &&
56 *text &&
57 *subpath &&
58 *text == *subpath) {
59 text++;
60 subpath++;
61 }
62
63 if (*subpath != 0) /* subpath failed: prefix differs */
64 text = path;
65
66 return text;
67}
68
69
78static const char* path_from_pax_header(const char *buf, size_t size, const char *templ) {
79
80 size_t line_start,i = 0, j, digi = 0;
81 const char *line;
82 int templ_len = strlen(templ);
83
84 while (i < size)
85 {
86 /* 1. parse length prefix */
87 size_t len = 0;
88
89 /* read until space */
90 j = i;
91 digi = 0; /* how many digits was in the number */
92
93 while (j < size && buf[j] != ' ')
94 {
95 if (buf[j] < '0' || buf[j] > '9')
96 return NULL;
97 len = len * 10 + (buf[j] - '0');
98 j++;
99 digi++;
100 }
101
102 if (j >= size || buf[j] != ' ')
103 return NULL;
104
105 line_start = j + 1;
106 if (line_start >= size)
107 return NULL;
108
109 if (line_start + len > size)
110 return NULL;
111
112 /* line = buf[line_start .. line_start+len) */
113
114 line = buf + line_start;
115
116 /* 2. check prefix "path=" */
117 if (len >= templ_len && memcmp(line, templ, templ_len) == 0)
118 {
119 /* Check if found key value ends with \r \n or NUL within the buf */
120 bool good_line = false;
121 for (int i=templ_len; i < (len - digi - 1); i++)
122 if (line[i] == 0 || line[i] == '\r' || line[i] == '\n') {
123 good_line = true;
124 break;
125 }
126
127 if (!good_line)
128 log("PAX header is\r\n");
129
130 return good_line ? line + templ_len : NULL; /* value starts here */
131 }
132
133 /* 3. jump to next record */
134 i = line_start + len - digi - 1;
135
136 }
137
138 return NULL;
139}
140
141
142
147static int inode_compare(const struct tarfs_inode *a, const struct tarfs_inode *b) {
148
149 if (a->in_hash < b->in_hash) return -1;
150 if (a->in_hash > b->in_hash) return 1;
151
152 return 0;
153}
154
158static inline void inode_exchange(struct tarfs_inode **a, struct tarfs_inode **b) {
159
160 struct tarfs_inode *t = *a;
161 *a = *b;
162 *b = t;
163}
164
165
170static void inode_siftdown(struct tarfs_inode **v, size_t root, size_t end)
171{
172 while (1) {
173 size_t child = root * 2 + 1;
174
175 if (child >= end)
176 return;
177
178 if (child + 1 < end && inode_compare(v[child], v[child + 1]) < 0)
179 child++;
180
181 if (inode_compare(v[root], v[child]) >= 0)
182 return;
183
184 inode_exchange(&v[root], &v[child]);
185 root = child;
186 }
187}
188
192void inode_sort(struct tarfs_inode **iarr, size_t count) {
193
194 struct tarfs_inode **v = (struct tarfs_inode **)iarr;
195
196 if (count < 2)
197 return;
198
199 for (size_t i = count / 2; i-- > 0;)
200 inode_siftdown(v, i, count);
201
202 for (size_t end = count; end > 1; end--) {
203
204 inode_exchange(&v[0], &v[end - 1]);
205 inode_siftdown(v, 0, end - 1);
206 }
207}
208
209
210
211
212
220
221static struct tarfs_inode *merge(struct tarfs_inode *a, struct tarfs_inode *b) {
222
223 struct tarfs_inode *head = NULL;
224 struct tarfs_inode **tail = &head;
225
226 while (a && b) {
227
228 if (tar_strcmp((char const *)a->in_path, NULL,(char const *)b->in_path) <= 0) {
229 *tail = a;
230 a = a->in_next;
231 } else {
232 *tail = b;
233 b = b->in_next;
234 }
235
236 tail = &(*tail)->in_next;
237 }
238
239 *tail = a ? a : b;
240
241 return head;
242}
243
249
250static struct tarfs_inode *merge_sort(struct tarfs_inode *head) {
251
252 if (!head || !head->in_next)
253 return head;
254
255 /* Find the middle of the list */
256 struct tarfs_inode *slow = head;
257 struct tarfs_inode *fast = head->in_next;
258
259 while (fast && fast->in_next) {
260 slow = slow->in_next;
261 fast = fast->in_next->in_next;
262 }
263
264 struct tarfs_inode *right = slow->in_next;
265 slow->in_next = NULL;
266
267 struct tarfs_inode *left = merge_sort(head);
268 right = merge_sort(right);
269
270 return merge(left, right);
271}
272
279struct tarfs_inode *inode_alphasort(struct tarfs_inode *array, size_t count) {
280
281 if (count == 0 || array == NULL)
282 return NULL;
283
284 /* Turn our array to a linked list */
285 for (size_t i = 0; i + 1 < count; i++)
286 array[i].in_next = &array[i + 1];
287
288 /* final node points to NULL */
289 array[count - 1].in_next = NULL;
290
291 /* Sort */
292 return merge_sort(array);
293}
294
303struct tarfs_inode **inode_alloc(size_t count) {
304
305 char *ptr;
306 struct tarfs_inode *nodes, **index;
307 size_t index_size, nodes_size;
308
309
310 if (count < 1) {
311 errno = EINVAL;
312 return NULL;
313 }
314
315 index_size = count * sizeof(struct tarfs_inode *);
316 nodes_size = count * sizeof(struct tarfs_inode);
317
318 /* Allocate two arrays as a single chunk of memory, better cache locality */
319 if (NULL == (ptr = tarfs_calloc(1, index_size + nodes_size)))
320 return NULL;
321
322
323 index = (struct tarfs_inode **)ptr;
324 nodes = (struct tarfs_inode *)(ptr + index_size);
325
326 /* After creation, all index elements are pointing to the corresponding inode structure:
327 * index[0] --> inode[0], index[10] --> inode[10]
328 */
329 for (size_t i = 0; i < count; i++)
330 index[i] = &nodes[i];
331
332 log("Allocated %u inodes, %u bytes (%u + %u)\r\n",(unsigned int)count,(unsigned int)(index_size + nodes_size),(unsigned int)index_size, (unsigned int)nodes_size);
333 return index;
334}
335
342void inode_free(struct tarfs_inode **index, size_t count, uintptr_t tar_start, size_t tar_length) {
343
344 if (index != NULL) {
345 /* free all paths if there were any */
346 for (int i = 0; i < count; i++) {
347 if (index[i]->in_path != 0) {
348 /* address is from the tar file range or beyond? */
349 if (index[i]->in_path < tar_start || index[i]->in_path >= (tar_start + tar_length)) {
350 tarfs_os_free((void *)(index[i]->in_path));
351 }
352 }
353 }
354 /* free inodes and index arrays (they sit together in one memory allocation )*/
355 tarfs_os_free(index);
356 }
357}
358
364static bool inode_pathcmp(const struct tarfs_inode *inode, const char *src) {
365
366 if (inode == NULL || src == NULL)
367 return NULL;
368
369 //struct tarhdr const *in_vaddr = (struct tarhdr const *)inode->in_vaddr;
370 const char *in_path = (const char *)inode->in_path;
371
372 if (in_path == NULL)
373 return false;
374
375 while(*in_path == *src) {
376
377 if (*src == 0)
378 return true;
379
380 src++;
381 in_path++;
382 }
383
384 return (*src == 0 && (*in_path == '\r' || *in_path == '\n' || *in_path == '\0'));
385}
386
387
388
389
399int inode_lookup(struct tarfs_inode const * const *index, size_t num_inodes, const char *path) {
400
401
402 uint32_t hash;
403 size_t left,
404 right;
405
406 if (index == NULL || path == NULL)
407 return -EFAULT;
408
409 if (num_inodes < 1)
410 return -EINVAL;
411
412 left = 0;
413 right = num_inodes;
414 hash = hash32(HASH32_IV, (uint8_t const *)path, strlen(path));
415
416 while (left < right) {
417
418 size_t mid = left + ((right - left) >> 1);
419 uint32_t mid_hash = index[mid]->in_hash;
420
421 if (hash < mid_hash)
422 right = mid;
423 else if (hash > mid_hash)
424 left = mid + 1;
425 else {
426
427 size_t first;
428
429 /* Alright, walk left, looking for the first inode with the same in_hash. Once found we walk from
430 * the left to the right comparing string literals
431 */
432 first = mid;
433 while (first > 0 && index[first - 1]->in_hash == mid_hash)
434 first--;
435
436
437 /* Scan collision chain, left to right */
438 for (int i = first; i < num_inodes && index[i]->in_hash == hash; i++) {
439 //struct tarhdr *hdr = (struct tarhdr *)index[i]->in_vaddr;
440 if (inode_pathcmp(index[i], path)) {
441 log("found inode#%u, hash=<e8bb5ed2> path='%s'\r\n", i, path);
442 return i;
443 }
444 }
445
446 log("unresolved collision, hash=%08x\r\n", (unsigned int)hash);
447 break;
448 }
449 } /* while left < right */
450 log("hash=<%08x> path='%s' not found\r\n",(unsigned int)hash, path);
451 return -ENOENT;
452}
453
466tart_t inode_getinfo(struct tarfs_inode const * const *index, int idx, size_t *size, time_t *mtime) {
467
468 if (index != NULL) {
469 struct tarfs_inode const *ino = index[idx];
470 struct tarhdr const *hdr;
471
472 if (NULL != (hdr = (struct tarhdr const *)ino->in_dvaddr)) {
473
474 if (size != NULL)
475 *size = tar_octal(hdr->size, sizeof(hdr->size));
476
477 if (mtime != NULL)
478 *mtime = tar_octal(hdr->mtime, sizeof(hdr->mtime));
479
480 return hdr->type;
481 }
482 }
483 return TART_BAD;
484}
485
486/* Return resolved inode type: TART_DIR, TART_FILE or TART_BAD. Links are resolved to their
487 * final destination. To check if inode is a link, use inode_islink() function instead
488 *
489 */
490tart_t inode_type(struct tarfs_inode const *ino) {
491
492 if (ino != NULL) {
493 struct tarhdr const *hdr;
494
495 if (NULL != (hdr = (struct tarhdr const *)ino->in_dvaddr))
496 return hdr->type;
497 }
498 return TART_BAD;
499}
500
501
506tart_t inode_rawtype(struct tarfs_inode const *ino) {
507
508 if (ino != NULL) {
509 struct tarhdr const *hdr;
510
511 if (NULL != (hdr = (struct tarhdr const *)ino->in_vaddr))
512 return hdr->type;
513 }
514 return TART_BAD;
515}
516
517
518/* Check if inode ino is a link or not.
519 *
520 */
521bool inode_islink(struct tarfs_inode const *ino) {
522
523 if (ino == NULL)
524 return false;
525
526 struct tarhdr const *hdr = (struct tarhdr const *)ino->in_vaddr;
527 return hdr->type == TART_SYMLINK || hdr->type == TART_HARDLINK;
528}
529
530
531
532/*
533 *
534 */
535time_t inode_mtime(struct tarfs_fs *fs, int idx, size_t *size) {
536
537 if (fs != NULL && idx >= 0 && idx < fs->fs_nino) {
538
539 struct tarfs_inode const *ino;
540
541 if (NULL != (ino = fs->fs_ino[idx])) {
542
543 struct tarhdr const *hdr;
544
545 if (NULL != (hdr = (struct tarhdr const *)ino->in_dvaddr)) {
546
547 if (size != NULL)
548 *size = tar_octal(hdr->size, sizeof(hdr->size));
549
550 return hdr->type;
551 }
552 }
553 }
554 return TART_BAD;
555}
556
557
558/* Scan through inodes, find all inodes with types 1 and 2 (links)
559 * and resolve them to their final destination (type=0 File or type=5 Directory)
560 */
561int inode_resolve(struct tarfs_inode **index, size_t count) {
562
563 int floating = 0, resolved = 0, attempted = 0;
564
565
566 for (int i = 0; i < count; i++ ) {
567
568 char *link_name = (char *)index[i]->in_next;
569
570 if (link_name != NULL) {
571
572 attempted++;
573
574 index[i]->in_dvaddr = 0;
575
576 int depth = 16;
577 do {
578 int dest = inode_lookup((struct tarfs_inode const * const *)index, count, link_name);
579 /* This ugly two-times lookup is required
580 * if we want to support all kind so Windows links/directory junctions/sym/hardlinks and
581 * all other entry types.
582 * The problem is that TAR utility stores directories with a trailing slash while
583 * PAX-symlinks do not end with "/" even if they point to a directory
584 */
585 if (dest < 0) {
586 int last_byte = strlen(link_name);
587 /* Link name (in_next) is created with tar_strdup1() which guarantees TWO NUL at the end,
588 * so adding a slash at position of the first NUL is completely safe. We modify inode's field here
589 * so subsequent lookups should be fine
590 */
591 link_name[last_byte] = '/';
592 dest = inode_lookup((struct tarfs_inode const * const *)index, count, link_name);
593 if (dest < 0) {
594
595 log("failed to resolve '%s' in two attempts\r\n", link_name);
596 break;
597 }
598 }
599 tart_t type = inode_getinfo((struct tarfs_inode const * const *)index, dest, NULL, NULL);
600 if (type == TART_BAD) {
601 log("can not get info on inode %d\r\n", dest);
602 break;
603 }
604
605 if (type != TART_HARDLINK && type != TART_SYMLINK) {
606#if CONFIG_TARFS_LOG
607 puts("Linked: ");
608 tar_print((const char *)index[i]->in_path, NULL);
609 printf(" --> ");
610 tar_print((const char *)index[dest]->in_path, NULL);
611 puts("");
612#endif
613
614 index[i]->in_dvaddr = index[dest]->in_dvaddr;
615 resolved++;
616 break;
617 }
618
619 link_name = (char *)index[dest]->in_next;
620 log("link to a link, continuing to resolve..\r\n");
621
622 } while(--depth > 0);
623
624 if (index[i]->in_dvaddr == 0) {
625 log("inode #%d is a floating link\r\n",i);
626 floating++;
627 }
628
629 }
630 }
631
632 log("memory cleanup, release unneded memory chunks\r\n");
633
634 for (int i = 0; i < count; i++ ) {
635
636 const char *link_name = (const char *)index[i]->in_next;
637 if (link_name != NULL) {
638 index[i]->in_next = NULL;
639 tarfs_os_free((void *)link_name);
640 }
641 }
642
643 log("%u of %u links were resolved, floating inodes: %u\r\n",resolved, attempted, floating);
644 return 0;
645}
646
647static char const * s_bad_path = "<bad path>";
648
655size_t inode_populate(struct tarfs_inode *inodes,
656 size_t nino,
657 const uint8_t *tar_start,
658 size_t tar_length,
659 const char *link_rebase,
660 const char *root_folder,
661 struct tarfs_stats *st) {
662
663
664
665 uint32_t total_data_size = 0;
666 uint32_t total_headers_size = 0;
667 uint32_t overhead = 0;
668
669 int files = 0, dirs = 0, links = 0, pax_headers = 0, idx = 0, bad_path = 0;
670
671 size_t off = 0;
672 unsigned int hdr_no = 0;
673 unsigned int bad = 0, total_bad = 0, total_badcrc = 0;
674 const char *pax_entry_path = NULL, *pax_entry_link = NULL, *pax_entry_end;
675 uintptr_t tar_end = (uintptr_t )((const uint8_t *)tar_start + tar_length);
676
677
678 /* Basic overhead created by inodes and the inode index + FS descriptor
679 * We use sizeof(struct tarfs_fs) which is a bit smaller than actual
680 * FS: we do not count the size of the fs->mountpoint
681 */
682 overhead = sizeof(struct tarfs_fs) + nino * (sizeof(struct tarfs_inode *) + sizeof(struct tarfs_inode));
683
684 while (off + sizeof(tarhdr_t) <= tar_length) {
685
686 const tarhdr_t *hdr = (const tarhdr_t *)(tar_start + off);
687
688 if (tar_badhdr(hdr)) {
689
690 pax_entry_path = NULL;
691 pax_entry_link = NULL;
692
693 if (!bad) {
694 log("Header #%u is ignored (or NUL-header)\r\n", hdr_no);
695bad_header:
696 log("Scanning from offset %u..\r\n", (unsigned int)off);
697 bad++;
698
699 }
700
701 off += sizeof(tarhdr_t);
702 continue;
703 }
704
705 if (bad) {
706 log("Resuming at offset %u; %u blocks were lost \n", (unsigned int)off, bad);
707 total_bad += bad;
708 bad = 0;
709
710 }
711
712 uint64_t size = tar_octal(hdr->size, sizeof(hdr->size));
713
714 /* Check if size is sane: current pointer + 512 bytes + size must be < tar_end */
715 if (((uintptr_t)(hdr + 1)) + size >= tar_end) {
716 log("Invalid entry size, sector marked as bad\r\n");
717 goto bad_header;
718 }
719
720 total_headers_size += 512;
721
722#if CONFIG_TARFS_INTEGRITY
723 bool bad_crc = false;
724 /* Verify CRC64 only if configured to do so */
725 if (tarfs_integrity(-1) > 0) {
726 bad_crc = tar_baddata(hdr, (size_t)size);
727 if (bad_crc)
728 total_bad++;
729 }
730#endif
731
732
733 /* Only create inodes of type FILE, SYMLINK, HARDLINK and DIRECTORY*/
734 switch(hdr->type) {
735 case TART_AFILE:
736 case TART_CONT:
737 case TART_FILE:
738 files++;
739 break;
740
741 case TART_HARDLINK:
742 case TART_SYMLINK:
743 links++;
744 break;
745
746 case TART_DIR:
747 dirs++;
748 break;
749
750 case TART_PAX_G:
751 case TART_PAX:
752 pax_headers++;
753 total_headers_size += size;
754 goto is_pax;
755
756 default:
757 goto skip_header_and_data;
758 };
759
760 total_data_size += size;
761
762
763
764 /*All checks are done, start populating inode.
765 * inodes[idx].in_path = (uintptr_t)strdup("Hello Hello");
766 */
767
768
769 /* NAME */
770 if (pax_entry_path) {
771 pax_entry_path = remove_subpath(pax_entry_path, pax_entry_end, root_folder);
772 //tar_print(pax_entry_path, pax_entry_end);
773
774 inodes[idx].in_path = (uintptr_t)pax_entry_path; // path_from_pax_header() guarantees that there is a path terminator (\r, \n or \0)
775
776 pax_entry_path = NULL;
777
778 } else {
779
780 /* No PAX override - fallback to prefix/name logic:
781 * If we do have non-empty prefix, then we have the worst case scenario #1, which requires
782 * us to malloc() a linear buffer and reconstruct full path there.
783 */
784 if (hdr->prefix[0]) {
785 char tmp[sizeof(hdr->prefix) + sizeof(hdr->name) + 1 + 1];
786
787 int nlen = tar_strlen(hdr->name, &hdr->name[0] + sizeof(hdr->name));
788 int plen = tar_strlen(hdr->prefix, &hdr->prefix[0] + sizeof(hdr->prefix));
789
790 memcpy(tmp,hdr->prefix,plen);
791 tmp[plen] = '/';
792 memcpy(tmp + plen + 1,hdr->name,nlen);
793 tmp[plen+nlen+1] = '\0';
794
795 const char *t = remove_subpath(tmp, tmp+sizeof(tmp), root_folder);
796
797 inodes[idx].in_path = (uintptr_t)tarfs_strdup(t);
798 overhead += strlen(t);
799
800 } else {
801 /* Entry name is exactly 100 bytes long: this is the worst case scenario #2: that means
802 * we do not have any path terminator in our hdr->name. This is quite rare case so we
803 * simply strdup() this kind of strings
804 */
805 const char *reb = remove_subpath(hdr->name, &hdr->name[0] + sizeof(hdr->name), root_folder);
806
807 if (hdr->name[sizeof(hdr->name) - 1] != 0) {
808
809 inodes[idx].in_path = (uintptr_t )tar_strdup1(reb , &hdr->name[0] + sizeof(hdr->name));
810 overhead += tar_strlen(reb , &hdr->name[0] + sizeof(hdr->name));
811
812 } else {
813 inodes[idx].in_path = (uintptr_t)reb;
814 }
815 //tar_print(reb, (char *)(hdr->name) + sizeof(hdr->name));
816 }
817 }
818
819
820 /* LINK
821 * Temporary use in_next as a pointer to the link name.
822 * We will be freed in link resolution pass (inode_resolve()).
823 * Skipping inode_resolve() step may create memory leaks
824 */
825
826 if (hdr->type == TART_SYMLINK || hdr->type == TART_HARDLINK) {
827
828
829 /* PAX has preference over link_name field */
830 if (pax_entry_link) {
831
832 if (pax_entry_link[0] == '/')
833 pax_entry_link = remove_subpath(pax_entry_link, pax_entry_end, link_rebase);
834 else
835 pax_entry_link = remove_subpath(pax_entry_link, pax_entry_end, root_folder);
836
837 /* Create a duplictate with 1 extra byte at the end. This extra byte MAY be used to add an explicit /
838 * for symlinks pointing to directories
839 */
840 inodes[idx].in_next = (void *)tar_strdup1(pax_entry_link, pax_entry_end);
841
842 pax_entry_link = NULL;
843
844 } else {
845 const char *t = remove_subpath(hdr->link_name, (char *)(hdr->link_name) + sizeof(hdr->link_name), hdr->type == TART_SYMLINK ? link_rebase : root_folder); //XXX: ugly hack
846 /* this memory allocation is temporary. memory will be released in inode_resolve() */
847 inodes[idx].in_next = (void *)tar_strdup1(t, (char *)(hdr->link_name) + sizeof(hdr->link_name));
848 }
849 }
850
851 /* Mark node as invalid if we had problems with in_path (e.g. out of memory on strdup etc).
852 * We do not want to check in_path for validity each time we want to use it.
853 */
854 if (inodes[idx].in_path == 0) {
855 inodes[idx].in_path = (uintptr_t )s_bad_path;
856 inodes[idx].in_hash = 0 /* hash that does not match inode's name. will be rejected at inode_lookup */;
857 bad_path++;
858 } else {
859
860 /* Previous code guarantees that in_path field has a string terminator (NUL, CR or LF)
861 * so it is safe to cal tar_strlen with the second argument set to NULL
862 */
863 int path_len = tar_strlen((const char *)inodes[idx].in_path, NULL);
864 inodes[idx].in_hash = hash32(HASH32_IV, (uint8_t const *)inodes[idx].in_path, path_len);
865 }
866
867 inodes[idx].in_vaddr = (uintptr_t)hdr;
868#if CONFIG_TARFS_INTEGRITY
869 if (bad_crc) {
870 total_badcrc++;
871 inodes[idx].in_dvaddr = 0;
872 log("Inode %d, dropped, hash sum mismatch\r\n", idx);
873
874 } else
875#endif
876 inodes[idx].in_dvaddr = (uintptr_t)hdr;
877
878
879 /* go to the next inode index */
880 idx++;
881
882
883 /**/
884is_pax:
885 if (hdr->type == TART_PAX) {
886 /* both of these can be NULL. that simply means X record will be ignored */
887 pax_entry_path = path_from_pax_header((const char *)(hdr + 1), size, "path=");
888 pax_entry_link = path_from_pax_header((const char *)(hdr + 1), size, "linkpath=");
889 pax_entry_end = ((const char *)(hdr + 1)) + size;
890 }
891skip_header_and_data:
892
893 /* Real size is 512 bytes aligned */
894 off += sizeof(tarhdr_t) + (((size_t)size + 511) & ~511u);
895 hdr_no++;
896 }
897
898 /* NOTE: We are not adding bad to the total_bad on the very last step for a reason:
899
900 * The last two blocks of the TAR archive are NUL-headers. These are treated as BAD DATA
901 * however, it does bit add to total_bad: while() finishes
902 */
903 log("end of file reached\r\n");
904
905 st->badblocks = total_bad;
906 st->files = files;
907 st->links = links;
908 st->dirs = dirs;
909 st->ram = overhead;
910#if CONFIG_TARFS_INTEGRITY
911 st->badcrc = total_badcrc;
912#endif
913
914 return total_data_size;
915}
916
917
918/* Free all inodes and associated data
919 *
920 */
921void inode_unmount(struct tarfs_fs *fs, const void * tar_start, size_t tar_size) {
922
923 if (fs != NULL && fs->fs_ino != NULL) {
924 log("free inodes\r\n");
925 inode_free((struct tarfs_inode **)fs->fs_ino, fs->fs_nino, (uintptr_t )tar_start, tar_size);
926 }
927}
928
929/* Create inodes (filesystem index), perform all sortings, link resolution and etc
930 * to make things faster later
931 */
932int inode_mount(struct tarfs_fs *fs, const unsigned char *buf, size_t size, const char *rebase_link, const char *base_dir) {
933
934
935 int nino;
936
937 fs->fs_vaddr= buf;
938 fs->fs_size = size;
939 fs->fs_dsize= 0;
940 fs->fs_ino = NULL;
941 fs->fs_nino = 0;
942 fs->fs_root = NULL;
943
944
945 // PASS1: count inodes, count all required memory
946 log("PASS1, analyzing..\n");
947 nino = tar_getnino(buf, size);
948
949 log("%u inodes, expected RAM usage: %u bytes of RAM\n",nino, (unsigned int)(sizeof(struct tarfs_fs) + nino * (sizeof(struct tarfs_inode) + sizeof(struct tarfs_inode *))));
950 if (nino < 1)
951 return -1;
952
953 log("filesystem prefix '%s' \n", base_dir);
954
955 struct tarfs_inode **index = inode_alloc( nino );
956 struct tarfs_inode *inodes = (struct tarfs_inode *)(index + nino);
957
958 if (index != NULL) {
959
960 // PASS3: populate inodes
961 log("PASS2, populating inodes..\n");
962 size_t dsize = inode_populate(inodes, nino, buf, size, rebase_link, base_dir, &fs->fs_stats);
963
964
965 /* Sort inode index table (pointers to inodes are sorted by inode's hash value)
966 * so inode_lookup() can be used
967 */
968 log("building binary search index..\n");
969 inode_sort(index, nino);
970
971 /*Resolve symlinks and hardlinks; For inodes which can not be resolved to a valid type5 or type0
972 * tar entries, the corresponding ->in_dvaddr is set to NULL, indicating that this inode has no valid data.
973 * Resolve other dpendencies; Free tmp memory used by linkpath strings. Link path pointer is placed to the in_next
974 * by the code above and is freed by the inode_resolve().
975 *
976 */
977 log("symlinks and hardlinks resolution..\n");
978 inode_resolve(index, nino);
979
980 /*
981 * Perform alphasorting by the in_path; index is not changed, only ->in_next is manipulated
982 * to build an alphasorted list of entries.
983 * root
984 */
985 log("lexigraphical sorting..\n");
986 struct tarfs_inode *root = inode_alphasort(inodes, nino);
987
988 /* PUBLISH */
989
990 fs->fs_ino = (struct tarfs_inode const * const *)index;
991 fs->fs_nino = nino;
992 fs->fs_root = (struct tarfs_inode const * )root;
993 fs->fs_dsize = dsize;
994 fs->fs_mtime = time( NULL );
995
996 if (root != NULL) {
997
998 /* Check if root node is '/' by checking its hash */
999 if (root->in_hash != HASH32_SLASH) {
1000 /* inode_dumppath_sorted(root); */
1001 log("WARN: root directory hash differs from expected %08x != 0x2a0c975e\r\n", (unsigned int )root->in_hash);
1002 }
1003 /* Read root's directory mtime from the tar header
1004 * in_vaddr can not be NULL
1005 */
1006 struct tarhdr const *hdr = (struct tarhdr const *)root->in_vaddr;
1007 if (hdr != NULL) {
1008 time_t mtime = tar_octal(hdr->mtime, sizeof(hdr->mtime));
1009 if (mtime != 0) {
1010 fs->fs_mtime = mtime;
1011 log("mtime is taken from the root entry\r\n");
1012 }
1013 } else {
1014 log("CRITICAL: root->in_vaddr is NULL, this must not happen!\r\n");
1015 }
1016
1017 /* Success! */
1018 return 0;
1019 }
1020
1021 log("WARN: no root inode after alphasort, opendir() is disabled\r\n");
1022 }
1023 /* Return "we have some problems" */
1024 return -1;
1025}
1026
1027
1032void inode_dumphash_sorted(struct tarfs_inode const * const * index, size_t count) {
1033
1034
1035 puts("-- HASH SORTED INODES --");
1036#if CONFIG_TARFS_LOG
1037 for (size_t i = 0; i < count; i++) {
1038 struct tarfs_inode const *inode = (struct tarfs_inode const *)index[i];
1039
1040 printf("<%08x> %c %s path=", (unsigned int)inode->in_hash,
1041 inode_getinfo(index, i, NULL, NULL) ,
1042 inode->in_vaddr != inode->in_dvaddr ? "*" : " ");
1043
1044 tar_print((char const *)inode->in_path, NULL);
1045
1046 puts("");
1047 }
1048#else
1049 puts("Enable CONFIG_TARFS_LOG to use inode_dumphash_sorted()");
1050#endif
1051}
1052
1057void inode_dumppath_sorted(struct tarfs_inode const * root) {
1058
1059
1060 puts("-- ALPHA SORTED INODES --");
1061#if CONFIG_TARFS_LOG
1062 puts("< HASH > *X Absolute path:");
1063 for (size_t i = 0; root != NULL; i++) {
1064
1065 printf("<%08x> %s%s path=",
1066 (unsigned int)root->in_hash,
1067 root->in_vaddr != root->in_dvaddr ? "*" : " ",
1068 root->in_dvaddr == 0 ? "X" : " ");
1069
1070 tar_print((char const *)root->in_path, NULL);
1071
1072 puts("");
1073
1074 root = root->in_next;
1075 }
1076
1077 puts("\r\nLegend: * - Symlink or Hardlink");
1078 puts(" X - Bad (unresolved) hardlink or symlink");
1079#else
1080 puts("Enable CONFIG_TARFS_LOG to use inode_dumppath_sorted()");
1081#endif
1082}
void * tarfs_calloc(size_t count, size_t size)
calloc() based on a memory backend; Memory backend must set errno if there were errors
Definition fs.c:492
char * tarfs_strdup(char const *str)
Definition fs.c:505
int tarfs_integrity(int en)
Enables or disables CRC64 integrity verification for TARFS archives.
Definition fs.c:803
#define log(Format_,...)
Definition fs.h:445
uint32_t hash32(uint32_t prev_hash, const uint8_t *data, size_t len)
Compute FNV-1a hash over a byte buffer.
Definition hash.c:79
#define HASH32_SLASH
Definition hash.h:29
#define HASH32_IV
1) CRC64/ECMA182 algorithm implementation (no tables version, slow but memory-efficient 2) Optimized ...
Definition hash.h:28
static struct tarfs_inode * merge_sort(struct tarfs_inode *head)
Classic merge-sort It is recursive but recursion depth log N, i.e.
Definition inode.c:250
size_t inode_populate(struct tarfs_inode *inodes, size_t nino, const uint8_t *tar_start, size_t tar_length, const char *link_rebase, const char *root_folder, struct tarfs_stats *st)
Populate inodes.
Definition inode.c:655
static const char * path_from_pax_header(const char *buf, size_t size, const char *templ)
Used to parse PAX-Header data section which is key=value format:
Definition inode.c:78
struct tarfs_inode * inode_alphasort(struct tarfs_inode *array, size_t count)
Sort inodes alphabetically: we do not move inodes.
Definition inode.c:279
int inode_resolve(struct tarfs_inode **index, size_t count)
Definition inode.c:561
void inode_dumphash_sorted(struct tarfs_inode const *const *index, size_t count)
Displays inodes sorted by hash.
Definition inode.c:1032
static void inode_siftdown(struct tarfs_inode **v, size_t root, size_t end)
Sift subarray down.
Definition inode.c:170
bool inode_islink(struct tarfs_inode const *ino)
Definition inode.c:521
void inode_dumppath_sorted(struct tarfs_inode const *root)
Displays inodes sorted by path.
Definition inode.c:1057
void inode_sort(struct tarfs_inode **iarr, size_t count)
Sort inode indicies.
Definition inode.c:192
void inode_free(struct tarfs_inode **index, size_t count, uintptr_t tar_start, size_t tar_length)
Free inodes.
Definition inode.c:342
static char const * s_bad_path
Definition inode.c:647
tart_t inode_getinfo(struct tarfs_inode const *const *index, int idx, size_t *size, time_t *mtime)
inode_getinfo() : get inode's Type, Size and Mtime These are not precached and must be calculated eve...
Definition inode.c:466
void inode_unmount(struct tarfs_fs *fs, const void *tar_start, size_t tar_size)
Unmount a TAR image.
Definition inode.c:921
tart_t inode_rawtype(struct tarfs_inode const *ino)
Return raw inode type: TART_HARDLINK, TART_SYMLINK, TART_DIR, TART_FILE or TART_BAD.
Definition inode.c:506
int inode_mount(struct tarfs_fs *fs, const unsigned char *buf, size_t size, const char *rebase_link, const char *base_dir)
Build an inode index for a TAR image.
Definition inode.c:932
static bool inode_pathcmp(const struct tarfs_inode *inode, const char *src)
Check if given inode is exactly given path.
Definition inode.c:364
static struct tarfs_inode * merge(struct tarfs_inode *a, struct tarfs_inode *b)
Alphaberical order sorting routines Inodes once created are "position-immutable": that means,...
Definition inode.c:221
static int inode_compare(const struct tarfs_inode *a, const struct tarfs_inode *b)
Comparator function for our array sortin routine; Compare two inodes.
Definition inode.c:147
static void inode_exchange(struct tarfs_inode **a, struct tarfs_inode **b)
Exchange two node indicies.
Definition inode.c:158
struct tarfs_inode ** inode_alloc(size_t count)
Returns an array of pointers to tarfs_inode structures.
Definition inode.c:303
int inode_lookup(struct tarfs_inode const *const *index, size_t num_inodes, const char *path)
Find an inode that corresponds to given path name.
Definition inode.c:399
time_t inode_mtime(struct tarfs_fs *fs, int idx, size_t *size)
Definition inode.c:535
static const char * remove_subpath(const char *path, const char *path_end, const char *subpath)
Inode is represented by struct tarfs_inode; Every inode contains a pointer to a corresponding tarfile...
Definition inode.c:51
tart_t inode_type(struct tarfs_inode const *ino)
Definition inode.c:490
void tarfs_os_free(void *buffer)
Definition os_esp32.c:207
This descriptor holds all file descriptors opened.
Definition fs.h:94
tarfs_inode_t const * fs_root
Definition fs.h:103
struct tarfs_stats fs_stats
Definition fs.h:110
tarfs_inode_t const *const * fs_ino
Definition fs.h:102
uint32_t fs_nino
Definition fs.h:101
size_t fs_dsize
Definition fs.h:99
time_t fs_mtime
Definition fs.h:107
void const * fs_vaddr
Definition fs.h:98
size_t fs_size
Definition fs.h:100
unsigned int ram
Definition fs.h:73
unsigned int links
Definition fs.h:71
unsigned int dirs
Definition fs.h:72
unsigned int badblocks
Definition fs.h:69
unsigned int files
Definition fs.h:70
For TAR files with modified PADDING field (see tarsum.c TARFS Checksum Utility): The type and meaning...
Definition tar.h:55
const char mtime[12]
Definition tar.h:62
const tart_t type
Definition tar.h:64
const char size[12]
Definition tar.h:61
char * tar_strdup1(const char *s1, const char *s1_end)
Duplicate a TAR string as a regular NUL-terminated C string.
Definition tar.c:171
uint32_t tar_octal(const char *p, size_t max_len)
Definition tar.c:193
bool tar_badhdr(tarhdr_t const *hdr)
Validate a TAR header.
Definition tar.c:249
int tar_getnino(const uint8_t *tar_start, size_t tar_length)
Quick run through the tarfile to count number of inodes we have to create.
Definition tar.c:306
int tar_strcmp(const char *s1, const char *s1_end, const char *s2)
Compare an UTS/CTS to a CTS.
Definition tar.c:48
int tar_strlen(const char *s1, const char *s1_end)
Return the length of a TAR string.
Definition tar.c:131
bool tar_baddata(struct tarhdr const *hdr, size_t size)
Verify CRC64 checksum stored in a TAR archive, if present.
Definition tar.c:529
const char link_name[100]
Definition tar.h:9
const tart_t type
Definition tar.h:8
const char mtime[12]
Definition tar.h:6
tart_t
Definition tar.h:46
const char size[12]
Definition tar.h:5