mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH 0/6] fs: implement tree and truncate
@ 2025-03-20  5:20 Ahmad Fatoum
  2025-03-20  5:20 ` [PATCH 1/6] fs: use filename_create/filename_lookup instead of open-coding Ahmad Fatoum
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Ahmad Fatoum @ 2025-03-20  5:20 UTC (permalink / raw)
  To: barebox

Turn opendir inside out, so it uses directory fds internally.
This is an important change, because it means that the inode
iterate operation will now operate on a real struct file.

This is a prerequisite for porting 9pfs from Linux as it expects
directories to be openend before iteration.

To aid with future development of file systems, introduce a tree and
a truncate command on top.

Ahmad Fatoum (6):
  fs: use filename_create/filename_lookup instead of open-coding
  fs: implement O_DIRECTORY
  fs: implement opendir in terms of fdopendir
  commands: implement tree command
  commands: add new truncate command
  Documentation: devel: add short section on file systems

 Documentation/devel/background-execution.rst |   2 +
 Documentation/devel/devel.rst                |   1 +
 Documentation/devel/filesystems.rst          |  73 +++++++
 commands/Kconfig                             |  24 +++
 commands/Makefile                            |   2 +
 commands/stat.c                              |   2 +-
 commands/tree.c                              | 159 ++++++++++++++
 commands/truncate.c                          | 100 +++++++++
 fs/fs.c                                      | 207 +++++++------------
 include/dirent.h                             |   3 +-
 test/self/dirfd.c                            |  11 +-
 11 files changed, 438 insertions(+), 146 deletions(-)
 create mode 100644 Documentation/devel/filesystems.rst
 create mode 100644 commands/tree.c
 create mode 100644 commands/truncate.c

-- 
2.39.5




^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 1/6] fs: use filename_create/filename_lookup instead of open-coding
  2025-03-20  5:20 [PATCH 0/6] fs: implement tree and truncate Ahmad Fatoum
@ 2025-03-20  5:20 ` Ahmad Fatoum
  2025-03-20  5:20 ` [PATCH 2/6] fs: implement O_DIRECTORY Ahmad Fatoum
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Ahmad Fatoum @ 2025-03-20  5:20 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

By reusing the existing file lookup code, open can open all directories,
e.g. / and /tmp/., which so far wasn't possible.

We need to be able to open these paths for directory FDs to work as one
would expect.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 fs/fs.c           | 55 ++++++++---------------------------------------
 test/self/dirfd.c |  7 +-----
 2 files changed, 10 insertions(+), 52 deletions(-)

diff --git a/fs/fs.c b/fs/fs.c
index 96ca60341ea4..1766719c567a 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -2543,9 +2543,7 @@ int openat(int dirfd, const char *pathname, int flags)
 	int error = 0;
 	struct inode *inode = NULL;
 	struct dentry *dentry = NULL;
-	struct nameidata nd;
-	const char *s;
-	struct filename *filename;
+	struct path path;
 
 	if (flags & O_TMPFILE) {
 		fsdev = get_fsdevice_by_path(dirfd, pathname);
@@ -2575,57 +2573,22 @@ int openat(int dirfd, const char *pathname, int flags)
 		return file_to_fd(f);
 	}
 
-	filename = getname(pathname);
-	if (IS_ERR(filename))
-		return PTR_ERR(filename);
-
-	set_nameidata(&nd, filename);
-
-	s = path_init(dirfd, &nd, LOOKUP_FOLLOW);
-	if (IS_ERR(s))
-		return PTR_ERR(s);
-
-	while (1) {
-		error = link_path_walk(s, &nd);
-		if (error)
-			break;
-
-		if (!d_is_dir(nd.path.dentry)) {
-			error = -ENOTDIR;
-			break;
-		}
-
-		dentry = __lookup_hash(&nd.last, nd.path.dentry, 0);
-		if (IS_ERR(dentry)) {
-			error = PTR_ERR(dentry);
-			break;
-		}
-
-		if (!d_is_symlink(dentry))
-			break;
-
-		dput(dentry);
-
-		error = lookup_last(&nd);
-		if (error <= 0)
-			break;
-
-		s = trailing_symlink(&nd);
-		if (IS_ERR(s)) {
-			error = PTR_ERR(s);
-			break;
-		}
+	if (flags & O_CREAT) {
+		dentry = filename_create(dirfd, getname(pathname), &path, 0);
+		error = PTR_ERR_OR_ZERO(dentry);
 	}
 
-	terminate_walk(&nd);
-	putname(nd.name);
+	if (!(flags & O_CREAT) || error == -EEXIST) {
+		error = filename_lookup(dirfd, getname(pathname), LOOKUP_FOLLOW, &path);
+		dentry = path.dentry;
+	}
 
 	if (error)
 		goto out1;
 
 	if (d_is_negative(dentry)) {
 		if (flags & O_CREAT) {
-			error = create(nd.path.dentry, dentry);
+			error = create(path.dentry, dentry);
 			if (error)
 				goto out1;
 		} else {
diff --git a/test/self/dirfd.c b/test/self/dirfd.c
index 644ff214fb37..20b54258715a 100644
--- a/test/self/dirfd.c
+++ b/test/self/dirfd.c
@@ -100,13 +100,8 @@ static void test_dirfd(void)
 	int fd;
 
 	fd = open("/", O_PATH);
-	if (expect(fd < 0, false, "open(/, O_PATH) = %d", fd)) {
+	if (expect(fd < 0, false, "open(/, O_PATH) = %d", fd))
 		close(fd);
-	} else {
-		pr_info("\tIgnoring expected failure\n");
-		failed_tests--;
-		skipped_tests++;
-	}
 
 #define B(dot, dotdot, zero, dev) 0b##dev##zero##dotdot##dot
 	/* We do fiften tests for every configuration
-- 
2.39.5




^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 2/6] fs: implement O_DIRECTORY
  2025-03-20  5:20 [PATCH 0/6] fs: implement tree and truncate Ahmad Fatoum
  2025-03-20  5:20 ` [PATCH 1/6] fs: use filename_create/filename_lookup instead of open-coding Ahmad Fatoum
@ 2025-03-20  5:20 ` Ahmad Fatoum
  2025-03-20  5:20 ` [PATCH 3/6] fs: implement opendir in terms of fdopendir Ahmad Fatoum
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Ahmad Fatoum @ 2025-03-20  5:20 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

We already had O_DIRECTORY defined, but only used it as one of the
bits for O_TMPFILE.

Fix the O_TMPFILE branch, so it checks both bits and implement
O_DIRECTORY, so it causes an error when opening a file that's not
a directory.

This is useful for use with openat.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 commands/stat.c   |  2 +-
 fs/fs.c           | 32 ++++++++++++++++++++++----------
 test/self/dirfd.c |  6 +++---
 3 files changed, 26 insertions(+), 14 deletions(-)

diff --git a/commands/stat.c b/commands/stat.c
index 10662005cdda..8a96bf77b0f1 100644
--- a/commands/stat.c
+++ b/commands/stat.c
@@ -26,7 +26,7 @@ static int do_stat(int argc, char *argv[])
 			extra_flags |= O_CHROOT;
 			fallthrough;
 		case 'c':
-			dirfd = open(optarg, O_PATH | extra_flags);
+			dirfd = open(optarg, O_PATH | O_DIRECTORY | extra_flags);
 			if (dirfd < 0)
 				return dirfd;
 			break;
diff --git a/fs/fs.c b/fs/fs.c
index 1766719c567a..fd5818c36c20 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -337,16 +337,23 @@ static void put_file(struct file *f)
 
 static struct file *fd_to_file(int fd, bool o_path_ok)
 {
-	if (fd < 0 || fd >= MAX_FILES || !files[fd].fsdev) {
-		errno = EBADF;
-		return ERR_PTR(-errno);
-	}
-	if (!o_path_ok && (files[fd].f_flags & O_PATH)) {
-		errno = EINVAL;
-		return ERR_PTR(-errno);
+	int err = -EBADF;
+	unsigned flimits;
+
+	if (fd < 0 || fd >= MAX_FILES || !files[fd].fsdev)
+		goto err;
+
+	flimits = files[fd].f_flags & (O_PATH | O_DIRECTORY);
+	if (!o_path_ok && flimits) {
+		if (flimits & O_DIRECTORY)
+			err = -EBADF;
+		goto err;
 	}
 
 	return &files[fd];
+err:
+	errno = -err;
+	return ERR_PTR(err);
 }
 
 static int create(struct dentry *dir, struct dentry *dentry)
@@ -2545,7 +2552,7 @@ int openat(int dirfd, const char *pathname, int flags)
 	struct dentry *dentry = NULL;
 	struct path path;
 
-	if (flags & O_TMPFILE) {
+	if ((flags & O_TMPFILE) == O_TMPFILE) {
 		fsdev = get_fsdevice_by_path(dirfd, pathname);
 		if (!fsdev) {
 			errno = ENOENT;
@@ -2596,11 +2603,16 @@ int openat(int dirfd, const char *pathname, int flags)
 			error = -ENOENT;
 			goto out1;
 		}
-	} else if (!(flags & O_PATH)) {
-		if (d_is_dir(dentry) && !dentry_is_tftp(dentry)) {
+	} else if (d_is_dir(dentry)) {
+		if (!(flags & (O_PATH | O_DIRECTORY)) && !dentry_is_tftp(dentry)) {
 			error = -EISDIR;
 			goto out1;
 		}
+
+		flags |= O_DIRECTORY;
+	} else if (flags & O_DIRECTORY) {
+		error = -ENOTDIR;
+		goto out1;
 	}
 
 	inode = d_inode(dentry);
diff --git a/test/self/dirfd.c b/test/self/dirfd.c
index 20b54258715a..1f9b375e84ff 100644
--- a/test/self/dirfd.c
+++ b/test/self/dirfd.c
@@ -99,8 +99,8 @@ static void test_dirfd(void)
 {
 	int fd;
 
-	fd = open("/", O_PATH);
-	if (expect(fd < 0, false, "open(/, O_PATH) = %d", fd))
+	fd = open("/", O_PATH | O_DIRECTORY);
+	if (expect(fd < 0, false, "open(/, O_PATH | O_DIRECTORY) = %d", fd))
 		close(fd);
 
 #define B(dot, dotdot, zero, dev) 0b##dev##zero##dotdot##dot
@@ -119,7 +119,7 @@ static void test_dirfd(void)
 	do_test_dirfd("AT_FDCWD", AT_FDCWD,
 		      B(110,110,000,111), B(111,110,111,000),
 		      B(110,110,000,111), B(110,110,000,111));
-	do_test_dirfd("/dev", open("/dev", O_PATH),
+	do_test_dirfd("/dev", open("/dev", O_PATH | O_DIRECTORY),
 		      B(111,110,111,000), B(111,110,111,000),
 		      B(110,110,000,111), B(110,110,000,111));
 	do_test_dirfd("/dev O_CHROOT", open("/dev", O_PATH | O_CHROOT),
-- 
2.39.5




^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 3/6] fs: implement opendir in terms of fdopendir
  2025-03-20  5:20 [PATCH 0/6] fs: implement tree and truncate Ahmad Fatoum
  2025-03-20  5:20 ` [PATCH 1/6] fs: use filename_create/filename_lookup instead of open-coding Ahmad Fatoum
  2025-03-20  5:20 ` [PATCH 2/6] fs: implement O_DIRECTORY Ahmad Fatoum
@ 2025-03-20  5:20 ` Ahmad Fatoum
  2025-03-20  5:20 ` [PATCH 4/6] commands: implement tree command Ahmad Fatoum
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Ahmad Fatoum @ 2025-03-20  5:20 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

While struct filep (FILE) and struct file have been merged, we still
have two different ways the files are created: Regularly in open and
irregularly in an incomplete manner inside opendir.

This breaks file systems that expect the struct file passed to iterate
to have been opened before.

To allow porting such filesystems more easily, let's go all in on
directory FDs and implement opendir using it.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 fs/fs.c          | 114 +++++++++++++++--------------------------------
 include/dirent.h |   2 -
 2 files changed, 35 insertions(+), 81 deletions(-)

diff --git a/fs/fs.c b/fs/fs.c
index fd5818c36c20..0c698981227f 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -2660,20 +2660,6 @@ int openat(int dirfd, const char *pathname, int flags)
 }
 EXPORT_SYMBOL(openat);
 
-static const char *fd_getpath(int fd)
-{
-	struct file *f;
-
-	if (fd < 0)
-		return ERR_PTR(errno_set(fd));
-
-	f = fd_to_file(fd, true);
-	if (IS_ERR(f))
-		return ERR_CAST(f);
-
-	return f->path;
-}
-
 int unlinkat(int dirfd, const char *pathname, int flags)
 {
 	int ret;
@@ -2758,107 +2744,77 @@ static void __release_dir(DIR *d)
 static int __opendir(DIR *d)
 {
 	int ret;
-	struct file file = {};
-	struct path *path = &d->path;
-	struct dentry *dir = path->dentry;
+	struct file *file = fd_to_file(d->fd, true);
 	struct readdir_callback rd = {
 		.ctx = {
 			.actor = fillonedir,
 		},
 	};
 
-	file.f_path.dentry = dir;
-	file.f_inode = d_inode(dir);
-	file.f_op = dir->d_inode->i_fop;
-
 	INIT_LIST_HEAD(&d->entries);
 	rd.dir = d;
 
-	ret = file.f_op->iterate(&file, &rd.ctx);
+	ret = file->f_inode->i_fop->iterate(file, &rd.ctx);
 	if (ret)
 		__release_dir(d);
 
 	return ret;
 }
 
-DIR *opendir(const char *pathname)
-{
-	int ret;
-	struct dentry *dir;
-	struct inode *inode;
-	DIR *d;
-	struct path path = {};
-
-	ret = filename_lookup(AT_FDCWD, getname(pathname),
-			      LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path);
-	if (ret)
-		goto out;
-
-	dir = path.dentry;
-
-	if (d_is_negative(dir)) {
-		ret = -ENOENT;
-		goto out_put;
-	}
-
-	inode = d_inode(dir);
-
-	if (!S_ISDIR(inode->i_mode)) {
-		ret = -ENOTDIR;
-		goto out_put;
-	}
-
-	d = xzalloc(sizeof(*d));
-	d->path = path;
-	d->fd = -ENOENT;
-
-	ret = __opendir(d);
-	if (ret)
-		goto out_free;
-
-	return d;
-
-out_free:
-	free(d);
-out_put:
-	path_put(&path);
-out:
-	errno_set(ret);
-
-	return NULL;
-}
-EXPORT_SYMBOL(opendir);
-
 DIR *fdopendir(int fd)
 {
-	const char *path;
+	struct stat st;
 	DIR *dir;
+	int ret;
 
-	path = fd_getpath(fd);
-	if (IS_ERR(path))
+	ret = fstat(fd, &st);
+	if (ret)
 		return NULL;
 
-	dir = opendir(path);
-	if (!dir)
-		return NULL;
+	if (!S_ISDIR(st.st_mode)) {
+		ret = -ENOTDIR;
+		goto err;
+	}
+
+	dir = xzalloc(sizeof(*dir));
 
 	/* we intentionally don't increment the reference count,
 	 * as POSIX specifies that fd ownership is transferred
 	 */
 	dir->fd = fd;
+
+	ret = __opendir(dir);
+	if (ret)
+		goto err;
+
 	return dir;
+err:
+	errno_set(ret);
+	return NULL;
 }
 EXPORT_SYMBOL(fdopendir);
 
+DIR *opendir(const char *pathname)
+{
+	int fd;
+
+	fd = open(pathname, O_DIRECTORY);
+	if (fd < 0) {
+		errno_set(fd);
+		return NULL;
+	}
+
+	return fdopendir(fd);
+}
+EXPORT_SYMBOL(opendir);
+
 int closedir(DIR *dir)
 {
 	if (!dir)
 		return errno_set(-EBADF);
 
-	path_put(&dir->path);
 	__release_dir(dir);
-	if (dir->fd >= 0)
-		close(dir->fd);
+	close(dir->fd);
 	free(dir);
 
 	return 0;
diff --git a/include/dirent.h b/include/dirent.h
index 4f7ff2a5f91f..f74541d83d26 100644
--- a/include/dirent.h
+++ b/include/dirent.h
@@ -3,7 +3,6 @@
 #define __DIRENT_H
 
 #include <linux/list.h>
-#include <linux/path.h>
 
 struct dirent {
 	char d_name[256];
@@ -15,7 +14,6 @@ typedef struct dir {
 	struct dirent d;
 	void *priv; /* private data for the fs driver */
 	int fd;
-	struct path path;
 	struct list_head entries;
 } DIR;
 
-- 
2.39.5




^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 4/6] commands: implement tree command
  2025-03-20  5:20 [PATCH 0/6] fs: implement tree and truncate Ahmad Fatoum
                   ` (2 preceding siblings ...)
  2025-03-20  5:20 ` [PATCH 3/6] fs: implement opendir in terms of fdopendir Ahmad Fatoum
@ 2025-03-20  5:20 ` Ahmad Fatoum
  2025-03-20  5:20 ` [PATCH 5/6] commands: add new truncate command Ahmad Fatoum
  2025-03-20  5:20 ` [PATCH 6/6] Documentation: devel: add short section on file systems Ahmad Fatoum
  5 siblings, 0 replies; 7+ messages in thread
From: Ahmad Fatoum @ 2025-03-20  5:20 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

This is a very simple tree command inspired by busybox. We already have
ls -R for recursive directory listings, but tree uses indentation to
make it easier to see the directory structure.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 commands/Kconfig  |   9 +++
 commands/Makefile |   1 +
 commands/tree.c   | 159 ++++++++++++++++++++++++++++++++++++++++++++++
 fs/fs.c           |   6 ++
 include/dirent.h  |   1 +
 5 files changed, 176 insertions(+)
 create mode 100644 commands/tree.c

diff --git a/commands/Kconfig b/commands/Kconfig
index fe459fa862f5..8a7a6e94f025 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -1021,6 +1021,15 @@ config CMD_LS
 		  -C	column format (opposite of long format)
 		  -R	list subdirectories recursively
 
+config CMD_TREE
+	tristate
+	default y
+	prompt "tree"
+	help
+	  list contents of directories in a tree-like format.
+
+	  Usage: tree [FILEDIR...]
+
 config CMD_STAT
 	tristate
 	prompt "stat"
diff --git a/commands/Makefile b/commands/Makefile
index e152e4148b04..fa7de443cc13 100644
--- a/commands/Makefile
+++ b/commands/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_CMD_POWEROFF)	+= poweroff.o
 obj-$(CONFIG_CMD_GO)		+= go.o
 obj-$(CONFIG_CMD_PARTITION)	+= partition.o
 obj-$(CONFIG_CMD_LS)		+= ls.o
+obj-$(CONFIG_CMD_TREE)		+= tree.o
 obj-$(CONFIG_CMD_STAT)		+= stat.o
 obj-$(CONFIG_CMD_CD)		+= cd.o
 obj-$(CONFIG_CMD_PWD)		+= pwd.o
diff --git a/commands/tree.c b/commands/tree.c
new file mode 100644
index 000000000000..9b660bb35584
--- /dev/null
+++ b/commands/tree.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-FileCopyrightText: © 2022 Roger Knecht <rknecht@pm.me>
+// SPDX-FileCopyrightText: © 2025 Ahmad Fatoum
+
+#include <stdio.h>
+#include <command.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#define TREE_GAP	"    "
+#define TREE_BAR	"|   "
+#define TREE_MID	"|-- "
+#define TREE_END	"`-- "
+
+struct tree_buf {
+	char buf[80];
+};
+
+struct tree_iter {
+	struct tree_buf prefix;
+	unsigned ndirs;
+	unsigned nfiles;
+};
+
+static int tree_prefix_append(struct tree_buf *prefix, int pos, const char *suffix)
+{
+	int strsz = strlen(suffix);
+
+	if (pos + strsz + 1 < sizeof(prefix->buf))
+		memcpy(prefix->buf + pos, suffix, strsz + 1);
+
+	return pos + strsz;
+}
+
+static int tree(int dirfd, const char *path, int prefix_pos, struct tree_iter *iter)
+{
+	DIR *dir;
+	struct dirent *d;
+	int total, ret = 0;
+
+	if (ctrlc())
+		return -EINTR;
+
+	dirfd = openat(dirfd, path, O_DIRECTORY);
+	if (dirfd < 0)
+		return -errno;
+
+	printf("%s\n", path);
+
+	dir = fdopendir(dirfd);
+	if (!dir) {
+		ret = -errno;
+		goto out_close;
+	}
+
+	iter->ndirs++;
+
+	total = countdir(dir);
+	if (total < 0) {
+		ret = -errno;
+		goto out_close;
+	}
+
+	for (int i = 1; (d = readdir(dir)); i++) {
+		struct stat st;
+		int pos;
+
+		if (d->d_name[0] == '.')
+			continue;
+
+		ret = lstatat(dirfd, d->d_name, &st);
+		if (ret)
+			st.st_mode = S_IFREG;
+
+		if (i == total)
+			pos = tree_prefix_append(&iter->prefix, prefix_pos, TREE_END);
+		else
+			pos = tree_prefix_append(&iter->prefix, prefix_pos, TREE_MID);
+
+		printf("%*s", pos, iter->prefix.buf);
+
+		switch (st.st_mode & S_IFMT) {
+			char realname[PATH_MAX];
+		case S_IFLNK:
+			realname[PATH_MAX - 1] = '\0';
+
+			printf("%s", d->d_name);
+			if (readlinkat(dirfd, d->d_name, realname, PATH_MAX - 1) >= 0)
+				printf(" -> %s", realname);
+			printf("\n");
+
+			if (statat(dirfd, d->d_name, &st) || !S_ISDIR(st.st_mode))
+				iter->nfiles++;
+			else
+				iter->ndirs++;
+
+			break;
+		case S_IFDIR:
+			if (i == total)
+				pos = tree_prefix_append(&iter->prefix, prefix_pos, TREE_GAP);
+			else
+				pos = tree_prefix_append(&iter->prefix, prefix_pos, TREE_BAR);
+
+			ret = tree(dirfd, d->d_name, pos, iter);
+			if (ret)
+				goto out_closedir;
+			break;
+		default:
+			printf("%s\n", d->d_name);
+			iter->nfiles++;
+		}
+	}
+
+out_closedir:
+	closedir(dir);
+out_close:
+	close(dirfd);
+	return ret;
+}
+
+static int do_tree(int argc, char *argv[])
+{
+	const char *cwd, **args;
+	struct tree_iter iter;
+	int ret, exitcode = 0;
+
+	if (argc > 1) {
+		args = (const char **)argv + 1;
+		argc--;
+	} else {
+		cwd = getcwd();
+		args = &cwd;
+	}
+
+	iter.nfiles = iter.ndirs = 0;
+
+	for (int i = 0; i < argc; i++) {
+		iter.prefix.buf[0] = 0;
+		ret = tree(AT_FDCWD, args[i], 0, &iter);
+		if (ret) {
+			printf("%s  [error opening dir: %pe]\n", args[i], ERR_PTR(ret));
+			exitcode = 1;
+		}
+	}
+
+	printf("\n%u directories, %u files\n", iter.ndirs, iter.nfiles);
+
+	return exitcode;
+}
+
+BAREBOX_CMD_START(tree)
+	.cmd		= do_tree,
+	BAREBOX_CMD_DESC("list contents of directories in a tree-like format.")
+	BAREBOX_CMD_OPTS("[FILEDIR...]")
+	BAREBOX_CMD_GROUP(CMD_GRP_FILE)
+BAREBOX_CMD_END
diff --git a/fs/fs.c b/fs/fs.c
index 0c698981227f..3e5eb6dc8479 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -1062,6 +1062,12 @@ int unreaddir(DIR *dir, const struct dirent *d)
 }
 EXPORT_SYMBOL(unreaddir);
 
+int countdir(DIR *dir)
+{
+	return list_count_nodes(&dir->entries);
+}
+EXPORT_SYMBOL(countdir);
+
 struct dirent *readdir(DIR *dir)
 {
 	struct readdir_entry *entry;
diff --git a/include/dirent.h b/include/dirent.h
index f74541d83d26..b6806aa24799 100644
--- a/include/dirent.h
+++ b/include/dirent.h
@@ -22,6 +22,7 @@ DIR *fdopendir(int fd);
 struct dirent *readdir(DIR *dir);
 int unreaddir(DIR *dir, const struct dirent *d);
 int rewinddir(DIR *dir);
+int countdir(DIR *dir);
 int closedir(DIR *dir);
 
 #endif /* __DIRENT_H */
-- 
2.39.5




^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 5/6] commands: add new truncate command
  2025-03-20  5:20 [PATCH 0/6] fs: implement tree and truncate Ahmad Fatoum
                   ` (3 preceding siblings ...)
  2025-03-20  5:20 ` [PATCH 4/6] commands: implement tree command Ahmad Fatoum
@ 2025-03-20  5:20 ` Ahmad Fatoum
  2025-03-20  5:20 ` [PATCH 6/6] Documentation: devel: add short section on file systems Ahmad Fatoum
  5 siblings, 0 replies; 7+ messages in thread
From: Ahmad Fatoum @ 2025-03-20  5:20 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

The truncate command offers an easy way to exercise a file system's
truncate operation.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 commands/Kconfig    |  15 +++++++
 commands/Makefile   |   1 +
 commands/truncate.c | 100 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 116 insertions(+)
 create mode 100644 commands/truncate.c

diff --git a/commands/Kconfig b/commands/Kconfig
index 8a7a6e94f025..8c05b8eff606 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -1904,6 +1904,21 @@ config CMD_DETECT
 		  -e	bail out if one device fails to detect
 		  -a	detect all devices
 
+config CMD_TRUNCATE
+	tristate
+	prompt "truncate"
+	help
+
+	truncate [-c] -s [+]SIZE FILE...
+
+	truncate truncates files to a given size. If a file does
+	not exist, it is created unless told otherwise.
+	Options:
+	-c		Do not create files
+	-s [+]SIZE	truncate file to SIZE, or increase by +SIZE
+
+	This command is mainly useful for VFS development.
+
 config CMD_SYNC
 	tristate
 	prompt "sync"
diff --git a/commands/Makefile b/commands/Makefile
index fa7de443cc13..36ef59b38759 100644
--- a/commands/Makefile
+++ b/commands/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_CMD_FINDMNT)	+= findmnt.o
 obj-$(CONFIG_CMD_CRC)		+= crc.o
 obj-$(CONFIG_CMD_CLEAR)		+= clear.o
 obj-$(CONFIG_CMD_TEST)		+= test.o
+obj-$(CONFIG_CMD_TRUNCATE)	+= truncate.o
 obj-$(CONFIG_CMD_SYNC)		+= sync.o
 obj-$(CONFIG_CMD_FLASH)		+= flash.o
 obj-$(CONFIG_CMD_MEMINFO)	+= meminfo.o
diff --git a/commands/truncate.c b/commands/truncate.c
new file mode 100644
index 000000000000..5c6d74257da1
--- /dev/null
+++ b/commands/truncate.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015 by Ari Sundholm <ari@tuxera.com>
+ */
+
+#include <common.h>
+#include <command.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <fs.h>
+
+static int do_truncate(int argc, char *argv[])
+{
+	int flags = O_CREAT | O_WRONLY;
+	int opt, modify = 0, ret = 0;
+	const char *size_str;
+	off_t size = -1;
+
+	while((opt = getopt(argc, argv, "cs:")) > 0) {
+		switch(opt) {
+		case 'c':
+			flags &= ~O_CREAT;
+			break;
+		case 's':
+			size_str = optarg;
+			if (!size_str)
+				return COMMAND_ERROR_USAGE;
+			switch (*size_str) {
+			case '+':
+				modify = 1;
+				size_str++;
+				break;
+			case '-':
+				return COMMAND_ERROR_USAGE;
+			}
+			size = strtoull_suffix(size_str, NULL, 10);
+			break;
+		default:
+			return COMMAND_ERROR_USAGE;
+		}
+	}
+
+	argv += optind;
+	argc -= optind;
+
+	if (size == -1 || argc == 0)
+		return COMMAND_ERROR_USAGE;
+
+	for (int i = 0; i < argc; i++) {
+		int fd = open(argv[i], flags, 0666);
+		if (fd < 0) {
+			if (errno != ENOENT || !(flags & O_CREAT)) {
+				perror("open");
+				ret = 1;
+			}
+			/* else: ENOENT && OPT_NOCREATE:
+			 * do not report error, exitcode is also 0.
+			 */
+			continue;
+		}
+
+		if (modify) {
+			struct stat st;
+
+			if (fstat(fd, &st) == -1) {
+				perror("fstat");
+				ret = 1;
+				goto close;
+			}
+
+			size = st.st_size + modify * size;
+		}
+
+		if (ftruncate(fd, size) == -1) {
+			perror("truncate");
+			ret = 1;
+		}
+close:
+		close(fd);
+	}
+
+	return ret;
+}
+
+BAREBOX_CMD_HELP_START(truncate)
+BAREBOX_CMD_HELP_TEXT("truncate truncates files to a given size. If a file does")
+BAREBOX_CMD_HELP_TEXT("not exist, it is created unless told otherwise.")
+BAREBOX_CMD_HELP_TEXT("")
+BAREBOX_CMD_HELP_TEXT("Options:")
+BAREBOX_CMD_HELP_OPT ("-c",     "Do not create files")
+BAREBOX_CMD_HELP_OPT ("-s [+]SIZE", "truncate file to SIZE, or increase by +SIZE")
+BAREBOX_CMD_HELP_END
+
+BAREBOX_CMD_START(truncate)
+	.cmd		= do_truncate,
+	BAREBOX_CMD_DESC("truncate files to size")
+	BAREBOX_CMD_OPTS("[-c] -s [+]SIZE FILE...")
+	BAREBOX_CMD_GROUP(CMD_GRP_HWMANIP)
+	BAREBOX_CMD_HELP(cmd_truncate_help)
+BAREBOX_CMD_END
-- 
2.39.5




^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 6/6] Documentation: devel: add short section on file systems
  2025-03-20  5:20 [PATCH 0/6] fs: implement tree and truncate Ahmad Fatoum
                   ` (4 preceding siblings ...)
  2025-03-20  5:20 ` [PATCH 5/6] commands: add new truncate command Ahmad Fatoum
@ 2025-03-20  5:20 ` Ahmad Fatoum
  5 siblings, 0 replies; 7+ messages in thread
From: Ahmad Fatoum @ 2025-03-20  5:20 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

barebox has a lot of commands that can be used to exercise different
aspects of a file system. Add some documentation for that.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 Documentation/devel/background-execution.rst |  2 +
 Documentation/devel/devel.rst                |  1 +
 Documentation/devel/filesystems.rst          | 73 ++++++++++++++++++++
 3 files changed, 76 insertions(+)
 create mode 100644 Documentation/devel/filesystems.rst

diff --git a/Documentation/devel/background-execution.rst b/Documentation/devel/background-execution.rst
index cf8675b9aa0e..e1e350785298 100644
--- a/Documentation/devel/background-execution.rst
+++ b/Documentation/devel/background-execution.rst
@@ -1,3 +1,5 @@
+.. _background_execution:
+
 Background execution in barebox
 ===============================
 
diff --git a/Documentation/devel/devel.rst b/Documentation/devel/devel.rst
index 39070074ca1b..3e9d44218334 100644
--- a/Documentation/devel/devel.rst
+++ b/Documentation/devel/devel.rst
@@ -9,6 +9,7 @@ Contents:
    :maxdepth: 2
 
    porting
+   filesystems
    background-execution
    project-ideas
 
diff --git a/Documentation/devel/filesystems.rst b/Documentation/devel/filesystems.rst
new file mode 100644
index 000000000000..454e736c701f
--- /dev/null
+++ b/Documentation/devel/filesystems.rst
@@ -0,0 +1,73 @@
+File system development in barebox
+==================================
+
+The barebox file system support is based heavily on Linux.
+
+Access to all file systems goes through the virtual file system
+layer, which provides uniform access to all mounted file systems
+under the same root.
+
+As power-fail safe writing of file system couldn't be guaranteed,
+most file systems supported by barebox are read-only.
+Safe writing is possible, however, via the :ref:`state_framework`.
+
+For an up-to-date listing of writable filesystems, refer to the
+``CONFIG_FS_WRITABLE`` Kconfig symbol.
+
+Testing File systems
+--------------------
+
+Nearly all file system operations have commands that directly exercise them:
+
++--------------------------+-----------------------------------------+
+| Command                  | Operations                              |
++==========================+=========================================+
+| :ref:`command_cat`       | ``open``, ``close``, ``read``           |
++--------------------------+-----------------------------------------+
+| :ref:`command_echo`      | ``create``, ``write``                   |
++--------------------------+-----------------------------------------+
+| :ref:`command_sync`      | ``flush``                               |
++--------------------------+-----------------------------------------+
+| :ref:`command_erase`     | ``erase``                               |
++--------------------------+-----------------------------------------+
+| :ref:`command_protect`   | ``protect``                             |
++--------------------------+-----------------------------------------+
+| :ref:`command_md`        | ``lseek``, ``memmap``                   |
++--------------------------+-----------------------------------------+
+| :ref:`command_rm`        | ``unlink``                              |
++--------------------------+-----------------------------------------+
+| :ref:`command_mkdir`     | ``mkdir``                               |
++--------------------------+-----------------------------------------+
+| :ref:`command_rmdir`     | ``rmdir``                               |
++--------------------------+-----------------------------------------+
+| :ref:`command_ln`        | ``symlink``                             |
++--------------------------+-----------------------------------------+
+| :ref:`command_readlink`  | ``readlink``                            |
++--------------------------+-----------------------------------------+
+| | :ref:`command_ls`      |  ``opendir``, ``readdir``, ``closedir`` |
+| | :ref:`command_tree`    |                                         |
++--------------------------+-----------------------------------------+
+| :ref:`command_stat`      | ``stat``                                |
++--------------------------+-----------------------------------------+
+| :ref:`command_truncate`  | ``truncate``                            |
++--------------------------+-----------------------------------------+
+
+This leaves two specialized operations that can't be easily tested
+via the shell:
+
+- ``discard_range``: Advise that a range need not be preserved
+- ``ioctl``: Issue device-specific output and input control commands
+
+Unused metadata
+---------------
+
+barebox currently ignores ownership and permission information
+inside file systems as well as special nodes like FIFOs or
+sockets. When porting file systems, these parts can be omitted.
+
+Background execution
+--------------------
+
+Outside command context (i.e. in
+:ref:`pollers and secondary barebox threads <background_execution>`),
+virtual file system access is only permitted with ramfs.
-- 
2.39.5




^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2025-03-20  5:20 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-03-20  5:20 [PATCH 0/6] fs: implement tree and truncate Ahmad Fatoum
2025-03-20  5:20 ` [PATCH 1/6] fs: use filename_create/filename_lookup instead of open-coding Ahmad Fatoum
2025-03-20  5:20 ` [PATCH 2/6] fs: implement O_DIRECTORY Ahmad Fatoum
2025-03-20  5:20 ` [PATCH 3/6] fs: implement opendir in terms of fdopendir Ahmad Fatoum
2025-03-20  5:20 ` [PATCH 4/6] commands: implement tree command Ahmad Fatoum
2025-03-20  5:20 ` [PATCH 5/6] commands: add new truncate command Ahmad Fatoum
2025-03-20  5:20 ` [PATCH 6/6] Documentation: devel: add short section on file systems Ahmad Fatoum

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox