TARFS 0.1.5
Read-only TAR filesystem for ESP32
Loading...
Searching...
No Matches
dir.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
14#include <stdint.h>
15#include <stdlib.h>
16#include <stdio.h>
17#include <stdbool.h>
18#include <stdatomic.h>
19#include <string.h>
20
21#include <unistd.h>
22#include <dirent.h>
23#include <sys/errno.h>
24#include <sys/fcntl.h>
25#include <sys/dirent.h>
26
27#include "config.h"
28#include "os.h"
29#include "fs.h"
30#include "inode.h"
31#include "tar.h"
32#include "dir.h"
33
34
35/* How readdir() works:
36 *
37 * Inodes are stored in two independent orderings:
38 *
39 * 1. Hash-sorted index used for fast pathname lookup (fs->fs_ino array)
40 * 2. Lexicographically sorted list of full pathnames, used by readdir() (linked list via inode->in_next ).
41 *
42 * Example:
43 *
44 * /dir3/dir33/ <-- opendir() / open()
45 * /dir3/dir33/file33.txt <-- direct child readdir()
46 * /dir3/dir33/file44.txt <-- direct child readdir()
47 * /dir3/dir33/file55/ <-- direct child readdir()
48 * /dir3/dir33/file55/jjjj <-- indirect child skip
49 * /dir3/dir33/file55/ssss <-- indirect child skip
50 * /dir3/dir33/file66/ <-- direct child readdir()
51 * /dir3/dir33/file55.txt <-- direct child readdir()
52 * /dir3/dir33/file77.txt <-- direct child readdir()
53 * /dir3/dir44/ <-- end of this directory, readdir() returns NULL
54 * /dir3/dir44/test
55 *
56 * Because all pathnames are lexicographically sorted, every direct child of a
57 * directory immediately follows its own inode in the sorted list.
58 *
59 * opendir() returns the index of the directory inode. readdir() then starts
60 * scanning from the next entry (idx + 1), returning only direct children while
61 * skipping descendants located in subdirectories (e.g. file55/jjjj). Scanning
62 * stops as soon as the pathname no longer belongs to the opened directory
63 * (e.g. "/dir3/dir44/").
64 */
65
66
67
71struct tarfs_dir {
72
73 DIR di_dir;
74 struct dirent di_ent;
75 size_t di_off;
76 int di_fd;
77 char *di_prefix;
78 struct tarfs_inode const *di_ino;
79 struct tarfs_inode const *di_cino;
80
81};
82
83/* remove subpath component of the path */
84static const char *remove_subpath(const char *path, const char *subpath) {
85
86 const char *text = path;
87
88 while(*text && *text != '\r' && *text != '\n' &&
89 *subpath && *subpath != '\r' && *subpath != '\n' &&
90 *text == *subpath) {
91 text++;
92 subpath++;
93 }
94
95 if (*subpath != 0 && *subpath != '\r' && *subpath != '\n') /* subpath failed: prefix differs */
96 text = path;
97
98 return text;
99}
100
101
102/* fd sanity check.
103 * we check fds which are passed to us by VFS layer
104 * for being in our range [0 .. TARFS_MAX_FDS]. Check if that fd is alive (file is opened)
105 */
106static bool is_sanefd(struct tarfs_fs *fs, int fd) {
107
108 return (fd >= 0) &&
109 (fd < TARFS_MAX_FDS) &&
110 ((atomic_load_explicit(&fs->fs_usedfd, memory_order_relaxed) & (1u << fd)) != 0);
111}
112
113
114
119static int is_direct_child(const char *path, const char *prefix) {
120
121 const char *tail;
122 const char *slash;
123 size_t plen;
124
125 plen = strlen(prefix);
126
127 if (tar_strncmp(path, prefix, plen) != 0)
128 return -1;
129
130 tail = path + plen;
131
132 /* the dir itself */
133 if (*tail == '\0' || *tail == '\r' || *tail == '\n')
134 return 1;
135
136
137 /* the dir itself when prefix does not end with '/' */
138 if (*tail == '/') {
139
140 tail++;
141 if (*tail == '\0' || *tail == '\r' || *tail == '\n')
142 return 1;
143 } else
144 /* we only tolerate / and NUL here. Any other character just mean that paths are different */
145 return -1;
146
147 slash = strchr(tail, '/');
148
149 /* файл */
150 if (slash == NULL)
151 return 1;
152
153 /* каталог первого уровня */
154 return slash[1] == '\0' || slash[1] == '\r' || slash[1] == '\n';
155}
156
165
166DIR* tard_fdopendir(void* ctx, int fd) {
167
168 struct tarfs_dir *dir;
169
170 /* opendir(), fdopendir() and open() all increase the refcounter */
171 struct tarfs_fs *fs = tarfs_getfs_addref((int)(uintptr_t)ctx);
172 if (fs == NULL) {
173
174 log("filesystem %d has gone, fd == %d is dead\r\n", (int)(uintptr_t)ctx, fd);
175
176 errno = ENODEV;
177 return NULL;
178 }
179
180 /* Check if fd is still alive */
181 if (!is_sanefd(fs, fd)) {
182
183 log("fd == %d is dead\r\n", fd);
184
185 tarfs_unref(fs);
186 errno = EBADF;
187 return NULL;
188 }
189
190 /* Allocate DIR and populate it*/
191 dir = tarfs_calloc(1, sizeof(struct tarfs_dir));
192 if (dir != NULL) {
193
194 struct tarfs_fp *fp = &fs->fs_fd[fd];
195
196 dir->di_off = 0; /* Current directory position (0 = before first entry) */
197 dir->di_fd = fd; /* Underlying directory file descriptor */
198 dir->di_ino = fs->fs_ino[fp->fp_idx]; /* Directory inode */
199 dir->di_cino = dir->di_ino; /* Current inode used by readdir()/seekdir() */
200
201
202 /* Root directory prefix. We need it to find direct children */
203 dir->di_prefix = tar_strdup1((const char *)dir->di_ino->in_path, NULL);
204 if (dir->di_prefix != NULL) {
205
206 int nlen = strlen(dir->di_prefix);
207
208 if (dir->di_prefix[nlen - 1] == '/')
209 dir->di_prefix[nlen - 1] = '\0';
210
211 log("prefix: '%s' opened, fd=%d\r\n", dir->di_prefix, fd);
212 /* Success! */
213 return (DIR*)dir;
214 }
215 tarfs_os_free(dir);
216 }
217
218 tarfs_unref(fs);
219 errno = ENOMEM;
220
221 return NULL;
222}
223
224
232
233DIR* tard_opendir(void* ctx, const char* name) {
234
235 if (name != NULL) {
236
237 int fd;
238 DIR *d;
239
240 if ((fd = tarf_open(ctx, name, O_DIRECTORY|O_RDONLY, 0)) >= 0) {
241 if ((d = tard_fdopendir(ctx, fd)) != NULL)
242 return d;
243 tarf_close(ctx, fd);
244 }
245 /* tarf_open() sets errno */
246 log("failed to open()/fdopendir() '%s'\r\n", name);
247 } else
248 errno = EFAULT;
249
250 return NULL;
251}
252
253
260int tard_closedir(void* ctx, DIR* pdir) {
261
262 struct tarfs_fs *fs = tarfs_getfs((int)(uintptr_t)ctx);
263
264 if (pdir) {
265
266 struct tarfs_dir *dir = (struct tarfs_dir *)pdir;
267
268 log("closing dir fd=%d\r\n", dir->di_fd);
269
270 tarf_close(ctx, dir->di_fd);
271
272 if (dir->di_prefix != NULL)
273 tarfs_os_free((void *)dir->di_prefix);
274
275 tarfs_os_free(dir);
276 tarfs_unref(fs);
277
278 return 0;
279 }
280
281 errno = EFAULT;
282 return -1;
283}
284
285
293struct dirent* tard_readdir(void* ctx, DIR* pdir) {
294
295 struct tarfs_dir *dir = (struct tarfs_dir *)pdir;
296 struct tarfs_inode const *cur;
297
298 ctx = ctx;
299 cur = dir->di_cino;
300
301 while (cur->in_next != NULL) {
302
303 cur = cur->in_next;
304
305 int x = is_direct_child((char const *)cur->in_path, dir->di_prefix);
306
307 if (x < 0) {
308 log("end of directory '%s' reached\r\n", dir->di_prefix);
309 return NULL;
310 }
311
312 if (x > 0) {
313
314 const char *p = remove_subpath((char const *)cur->in_path, dir->di_prefix);
315
316 if (*p == '/') /* normally yes */
317 p++;
318
319 int len = tar_strlen(p, NULL);
320 if (len < sizeof(dir->di_ent.d_name)) {
321
322 memcpy(dir->di_ent.d_name, p, len);
323 dir->di_ent.d_name[len] = '\0';
324
325 switch(inode_type(cur)) {
326 case TART_DIR: dir->di_ent.d_type = DT_DIR; break;
327 case TART_FILE:
328 case TART_AFILE:
329 case TART_CONT: dir->di_ent.d_type = DT_REG; break;
330 default: dir->di_ent.d_type = DT_UNKNOWN; break;
331 }
332
333 /* TODO: inode number. */
334 dir->di_ent.d_ino = 0;
335
336
337 dir->di_cino = cur;
338 dir->di_off++;
339
340 return &dir->di_ent;
341 } else
342 errno = ENAMETOOLONG;
343 }
344 }
345
346 log("end of alphalist is reached\r\n");
347 return NULL;
348}
349
350
357long tard_telldir(void* ctx, DIR* pdir) {
358
359 struct tarfs_dir * dir = (struct tarfs_dir *)pdir;
360 ctx = ctx;
361 return dir->di_off;
362}
363
364
371void tard_seekdir(void* ctx, DIR* pdir, long offset) {
372
373 struct tarfs_dir * dir = (struct tarfs_dir *)pdir;
374
375 /* We cant setp backwards - we are using single-linked list
376 * so instead we perform full rewind() and then simply do 'offset' numbers of readdir() calls
377 */
378 if (dir->di_off > offset) {
379 dir->di_off = 0;
380 dir->di_cino = dir->di_ino;
381 }
382
383 while(dir->di_off < offset) {
384 if (NULL == tard_readdir(ctx, pdir)) {
385 log("offset %ld is not reachable, stopped at offset %u\r\n", offset, (unsigned int)dir->di_off);
386 break;
387 }
388 }
389}
390
397int tard_dirfd(void* ctx, DIR *pdir) {
398
399 ctx = ctx;
400
401 if (pdir != NULL) {
402
403 struct tarfs_dir * dir = (struct tarfs_dir *)pdir;
404 return dir->di_fd;
405 }
406 errno = EFAULT;
407 return -1;
408}
409
long tard_telldir(void *ctx, DIR *pdir)
Return the current directory position.
Definition dir.c:357
static bool is_sanefd(struct tarfs_fs *fs, int fd)
Definition dir.c:106
int tard_closedir(void *ctx, DIR *pdir)
Close a directory stream.
Definition dir.c:260
DIR * tard_fdopendir(void *ctx, int fd)
Associate an open directory file descriptor with a directory stream.
Definition dir.c:166
struct dirent * tard_readdir(void *ctx, DIR *pdir)
Read the next directory entry.
Definition dir.c:293
int tard_dirfd(void *ctx, DIR *pdir)
The function tard_dirfd() returns the file descriptor associated with the directory stream pdir.
Definition dir.c:397
DIR * tard_opendir(void *ctx, const char *name)
Open a directory for reading.
Definition dir.c:233
static const char * remove_subpath(const char *path, const char *subpath)
Definition dir.c:84
static int is_direct_child(const char *path, const char *prefix)
Check if 'path' is direct child of 'prefix'; Prefix MUST NOT have '/' at the end.
Definition dir.c:119
void tard_seekdir(void *ctx, DIR *pdir, long offset)
Reposition a directory stream.
Definition dir.c:371
int tarf_close(void *ctx, int fd)
Definition file.c:445
int tarf_open(void *ctx, const char *path0, int flags, int mode)
Open a TARFS file or directory.
Definition file.c:252
int tarfs_unref(struct tarfs_fs *fs)
Definition fs.c:260
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
struct tarfs_fs * tarfs_getfs(int i)
Lockless, not thread safe, does not increase refcounters.
Definition fs.c:54
struct tarfs_fs * tarfs_getfs_addref(int i)
Thread safe, increases refcounter, uses mutex!
Definition fs.c:70
#define log(Format_,...)
Definition fs.h:445
#define TARFS_MAX_FDS
Definition fs.h:30
tart_t inode_type(struct tarfs_inode const *ino)
Definition inode.c:490
void tarfs_os_free(void *buffer)
Definition os_esp32.c:207
TARFS File API: tarf_open(), tarf_close(), tarf_read(), tarf_pread(), tarf_lseek(),...
Definition file.h:63
int fp_idx
Definition file.h:68
This descriptor holds all file descriptors opened.
Definition fs.h:94
struct tarfs_fp fs_fd[16]
Definition fs.h:106
tarfs_inode_t const *const * fs_ino
Definition fs.h:102
int tar_strncmp(const char *s1, const char *s2, size_t len)
Definition tar.c:90
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
int tar_strlen(const char *s1, const char *s1_end)
Return the length of a TAR string.
Definition tar.c:131
const char name[100]
Definition tar.h:1
const char prefix[155]
Definition tar.h:16