Hello, I haven't looked at the details, but the description looks similar to the existing unionfs?
Samuel Mikhail Karpov, le mar. 29 sept. 2026 18:32:07 +0700, a ecrit: > Hello everyone! > > I am pleased to present the overlayfs translator my attempt to implement the > GNU/Linux overlayfs for GNU/Hurd. I probably don't need to explain what > overlayfs does; I will simply note that, in addition to the --lowerdir and > --upperdir flags, this translator implements --metacopy and --index. Just > like in the GNU/Linux version, these allow avoiding unnecessary copying from > the lower directory to the upper directory and enable hardlink tracking > during copy operations. The translator currently works quite well (in my > opinion), and I would love to hear your thoughts on it. > > I would also like to figure out the chauthor mechanism; I haven't found a > "standard" way to invoke the chauthor function unlike calls such as lchown, > lchmod, or chflags. Consequently, chauthor currently works only for files > via the file_chauthor call. > > I’d like to know if any additional functions need to be implemented or if > the current ones should be reworked. > There are also tests for overlayfs available at > https://github.com/amir73il/unionmount-testsuite; would it be possible to > try porting these tests to GNU/Hurd? > > Finally, when I first started learning libnetfs to implement partfs, there > weren't many simple, comprehensive examples available for beginners. The > overlayfs translator is both simple (relying only on libnetfs and standard > POSIX functions) and complete, so it could serve as a great example for > newcomers. > > That’s all for now. I look forward to hearing your opinions, comments, and > feedback! > > Thanks, > > -- > Mikhail Karpov > From dd58ac67fd30aeee6a50c99ad422226b2fdf270e Mon Sep 17 00:00:00 2001 > From: Mikhail Karpov <[email protected]> > Date: Tue, 29 Sep 2026 17:33:46 +0700 > Subject: [PATCH] Adding a overlayfs translator > > --- > Makefile | 2 +- > overlayfs/Makefile | 29 + > overlayfs/netfs.c | 1950 +++++++++++++++++++++++++++++++++++++++++ > overlayfs/options.c | 185 ++++ > overlayfs/options.h | 28 + > overlayfs/overlayfs.c | 151 ++++ > overlayfs/overlayfs.h | 125 +++ > overlayfs/rbtree.c | 331 +++++++ > overlayfs/rbtree.h | 30 + > overlayfs/unite.c | 453 ++++++++++ > 10 files changed, 3283 insertions(+), 1 deletion(-) > create mode 100644 overlayfs/Makefile > create mode 100644 overlayfs/netfs.c > create mode 100644 overlayfs/options.c > create mode 100644 overlayfs/options.h > create mode 100644 overlayfs/overlayfs.c > create mode 100644 overlayfs/overlayfs.h > create mode 100644 overlayfs/rbtree.c > create mode 100644 overlayfs/rbtree.h > create mode 100644 overlayfs/unite.c > > diff --git a/Makefile b/Makefile > index c51e8c1c..a7b16989 100644 > --- a/Makefile > +++ b/Makefile > @@ -36,7 +36,7 @@ lib-subdirs = libshouldbeinlibc libihash libiohelp libports > \ > > # Hurd programs > prog-subdirs = auth proc exec term \ > - ext2fs isofs tmpfs fatfs \ > + ext2fs isofs tmpfs fatfs overlayfs \ > storeio pflocal pfinet defpager mach-defpager \ > login daemons boot console \ > hostmux usermux ftpfs trans \ > diff --git a/overlayfs/Makefile b/overlayfs/Makefile > new file mode 100644 > index 00000000..9ddd8597 > --- /dev/null > +++ b/overlayfs/Makefile > @@ -0,0 +1,29 @@ > +# Copyright (C) 2026 Free Software Foundation > +# > +# This file is part of the GNU Hurd. > +# > +# The GNU Hurd is free software; you can redistribute it and/or > +# modify it under the terms of the GNU General Public License as > +# published by the Free Software Foundation; either version 2, or (at > +# your option) any later version. > +# > +# The GNU Hurd is distributed in the hope that it will be useful, but > +# WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > +# General Public License for more details. > + > +# You should have received a copy of the GNU General Public License > +# along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. > + > +dir := overlayfs > +makemode := server > +target = overlayfs > + > +#CFLAGS += -DDEBUG > + > +SRCS = netfs.c options.c overlayfs.c rbtree.c unite.c > +OBJS = $(SRCS:.c=.o) > +HURDLIBS = fshelp iohelp netfs ports store shouldbeinlibc > +LDLIBS = -lpthread > + > +include ../Makeconf > diff --git a/overlayfs/netfs.c b/overlayfs/netfs.c > new file mode 100644 > index 00000000..0bd94f53 > --- /dev/null > +++ b/overlayfs/netfs.c > @@ -0,0 +1,1950 @@ > +/* Libnetfs callbacks for node operations. > + Copyright (C) 2026 Free Software Foundation > + > + This file is part of the GNU Hurd. > + > + The GNU Hurd is free software; you can redistribute it and/or > + modify it under the terms of the GNU General Public License as > + published by the Free Software Foundation; either version 2, or (at > + your option) any later version. > + > + The GNU Hurd is distributed in the hope that it will be useful, but > + WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ > + > +#include <dirent.h> > +#include <fcntl.h> > +#include <stdlib.h> > +#include <string.h> > +#include <sys/mman.h> > +#include <sys/stat.h> > + > +#include "overlayfs.h" > + > +static inline error_t > +add_node_to_array (struct node ***array, size_t *size, struct node *node) > +{ > + struct node **new_array = realloc (*array, (*size + 1) > + * sizeof (struct node *)); > + if (!new_array) > + return errno; > + > + *array = new_array; > + (*array)[*size] = node; > + *size += 1; > + return 0; > +} > + > +static inline void > +remove_node_from_array (struct node ***array, size_t *size, struct node > *node) > +{ > + for (size_t i = 0; i < *size; ++i) > + if ((*array)[i] == node) > + { > + if (i == *size - 1) > + { > + if (i == 0) > + { > + free (*array); > + *array = NULL; > + *size = 0; > + return; > + } > + } > + else > + (*array)[i] = (*array)[*size - 1]; > + > + break; > + } > + > + struct node **new_array = realloc (*array, (*size - 1) > + * sizeof (struct node *)); > + if (!new_array) > + /* Since new_array is less than array, realloc will most likely not > + return any error, but if it does, we simply don't change the value > + of array. */ > + debug ("realloc from remove_node_from_array return error: %d\n", errno); > + else > + *array = new_array; > + > + *size -= 1; > +} > + > +static inline error_t > +concat_paths (char **path, const char *path1, const char *path2) > +{ > + *path = malloc (strlen (path1) + strlen (path2) + 2); > + if (!*path) > + return errno; > + > + strcpy (*path, path1); > + strcat (*path, "/"); > + strcat (*path, path2); > + return 0; > +} > + > +static void > +remove_node_from_dir (struct node *dir, struct node *node) > +{ > + remove_node_from_array (&dir->nn->entries, &dir->nn->entries_size, node); > + netfs_nrele (dir); > +} > + > +static error_t > +create_dir_in_upperdir (struct node *node, struct node *dir, > + const char *dir_name) > +{ > + debug ("create_dir_in_upperdir (node: %p, dir: %p, dir_name: %s\n):\n", > + node, dir, dir_name); > + > + char *path; > + char *new_dir_name = NULL; > + error_t err; > + > + if (dir->nn->num_dir == 0) > + { > + err = concat_paths (&path, dir->nn->path, dir_name); > + if (err) > + return err; > + } > + else > + { > + size_t end_lowerdir = > + strlen (overlayfs.lowerdirs[dir->nn->num_dir - 1]); > + char *needs_dir = &dir->nn->path[end_lowerdir]; > + path = malloc (strlen (overlayfs.upperdir) + strlen (needs_dir) > + + strlen (dir_name) + 2); > + if (!path) > + return errno; > + > + strcpy (path, overlayfs.upperdir); > + strcat (path, needs_dir); > + strcat (path, "/"); > + strcat (path, dir_name); > + } > + > + if (strcmp (node->nn->name, dir_name) != 0) > + { > + new_dir_name = strdup (dir_name); > + if (!new_dir_name) > + { > + err = errno; > + free (path); > + return err; > + } > + } > + > + struct node **list = NULL; > + size_t list_size = 0; > + > + struct node *it_dir = dir; > + while (1) > + { > + if (it_dir->nn->num_dir == 0) > + break; > + > + err = add_node_to_array (&list, &list_size, it_dir); > + if (err) > + goto return_error; > + > + it_dir = it_dir->nn->dir; > + } > + > + strcpy (path, it_dir->nn->path); > + strcat (path, "/"); > + > + size_t i = list_size - 1; > + while (list) > + { > + strcat (path, list[i]->nn->name); > + > + err = mkdir (path, 0777); > + if (err && errno != EEXIST) > + { > + err = errno; > + goto return_error; > + } > + > + char *new_path = strdup (path); > + if (!new_path) > + { > + err = errno; > + goto return_error; > + } > + > + free (list[i]->nn->path); > + list[i]->nn->path = new_path; > + list[i]->nn->num_dir = 0; > + > + strcat (path, "/"); > + > + if (i == 0) > + break; > + > + --i; > + } > + > + free (list); > + > + strcat (path, dir_name); > + > + err = mkdir (path, 0777); > + if (err && errno != EEXIST) > + { > + err = errno; > + goto return_error; > + } > + > + debug ("here\n"); > + > + if (node->nn->dir != dir) > + { > + err = add_node_to_array (&dir->nn->entries, &dir->nn->entries_size, > + node); > + if (err) > + goto return_error; > + > + remove_node_from_dir (node->nn->dir, node); > + node->nn->dir = dir; > + netfs_nref (dir); > + } > + > + free (node->nn->path); > + node->nn->path = path; > + if (new_dir_name) > + { > + free (node->nn->name); > + node->nn->name = new_dir_name; > + } > + > + node->nn->num_dir = 0; > + return 0; > + > + return_error: > + free (list); > + free (path); > + free (new_dir_name); > + return err; > +} > + > +static void > +remove_node_from_hardlinks (struct list_hardlinks *list, struct node *node) > +{ > + pthread_mutex_lock (&list->lock); > + remove_node_from_array (&list->hardlinks, &list->size, node); > + pthread_mutex_unlock (&list->lock); > +} > + > +static inline error_t > +copy_stat (char *name, struct stat *stat) > +{ > + error_t err = chown (name, stat->st_uid, stat->st_gid); > + if (err) > + return errno; > + > + err = chflags (name, stat->st_flags); > + if (err) > + return errno; > + > + struct timespec times[2] = { > + stat->st_atim, > + stat->st_mtim > + }; > + > + err = utimensat (AT_FDCWD, name, times, 0); > + if (err) > + return errno; > + > + // file_t file = file_name_lookup (name, O_RDWR, 0); > + // if (file == MACH_PORT_NULL) > + // return errno; > + > + // err = file_chauthor (file, stat->st_author); > + // mach_port_deallocate (mach_task_self (), file); > + return err; > +} > + > +static error_t > +copy_file_to_upperdir (struct node *node, struct node *dir, > + const char *file_name) > +{ > + debug ("copy_file_to_upperdir (node: %p, dir: %p, file_name: %s):\n", node, > + dir, file_name); > + error_t err; > + if (dir->nn->num_dir != 0) > + { > + err = create_dir_in_upperdir (dir, dir->nn->dir, dir->nn->name); > + if (err) > + return err; > + } > + > + char *new_file_name = NULL; > + if (strcmp (node->nn->name, file_name) != 0) > + { > + new_file_name = strdup (file_name); > + if (!new_file_name) > + return errno; > + } > + > + char *path; > + err = concat_paths (&path, dir->nn->path, file_name); > + if (err) > + goto free_new_name; > + > + if (node->nn->symlink_path) > + { > + err = symlink (node->nn->symlink_path, path); > + if (err) > + { > + err = errno; > + goto free_path; > + } > + } > + else > + { > + int dest = open (path, O_WRONLY | O_CREAT | O_SYNC, > + node->nn_stat.st_mode); > + if (dest == -1) > + { > + err = errno; > + goto free_path; > + } > + > + if (node->nn_stat.st_size > 0) > + { > + file_t file = file_name_lookup (node->nn->path, O_READ, 0); > + if (file == MACH_PORT_NULL) > + { > + err = errno; > + close (dest); > + goto free_path; > + } > + > + char *buf; > + mach_msg_type_number_t buf_len = 0; > + err = io_read (file, &buf, &buf_len, 0, node->nn_stat.st_size); > + mach_port_deallocate (mach_task_self (), file); > + if (err) > + { > + close (dest); > + goto free_path; > + } > + > + ssize_t bytes_written = write (dest, buf, buf_len); > + munmap (buf, node->nn_stat.st_size); > + if (bytes_written == -1) > + { > + err = errno; > + close (dest); > + goto free_path; > + } > + } > + > + close (dest); > + } > + > + if (overlayfs.metacopy) > + { > + err = copy_stat (path, &node->nn_stat); > + if (err) > + goto free_path; > + } > + > + if (node->nn->dir != dir) > + { > + err = add_node_to_array (&dir->nn->entries, &dir->nn->entries_size, > + node); > + if (err) > + goto free_path; > + > + remove_node_from_dir (node->nn->dir, node); > + node->nn->dir = dir; > + netfs_nref (dir); > + } > + > + free (node->nn->path); > + node->nn->path = path; > + node->nn->num_dir = 0; > + > + if (new_file_name) > + { > + free (node->nn->name); > + node->nn->name = new_file_name; > + } > + > + for (size_t i = 0; i < node->nn->list_hardlinks->size; ++i) > + { > + struct node *hard_node = node->nn->list_hardlinks->hardlinks[i]; > + if (overlayfs.index && hard_node->nn->num_dir != 0 && hard_node != > node) > + { > + pthread_mutex_lock (&hard_node->lock); > + if (hard_node->nn->dir->nn->num_dir != 0) > + { > + err = create_dir_in_upperdir (hard_node->nn->dir, > + hard_node->nn->dir->nn->dir, > + hard_node->nn->dir->nn->name); > + if (err) > + { > + pthread_mutex_unlock (&hard_node->lock); > + return err; > + } > + } > + > + char *new_hard_path; > + err = concat_paths (&new_hard_path, hard_node->nn->dir->nn->path, > + hard_node->nn->name); > + if (err) > + { > + pthread_mutex_unlock (&hard_node->lock); > + return err; > + } > + > + err = link (path, new_hard_path); > + if (err) > + { > + err = errno; > + pthread_mutex_unlock (&hard_node->lock); > + return err; > + } > + > + free (hard_node->nn->path); > + hard_node->nn->path = new_hard_path; > + hard_node->nn->num_dir = 0; > + pthread_mutex_unlock (&hard_node->lock); > + } > + else if (hard_node->nn->num_dir == 0 && hard_node != node) > + { > + pthread_mutex_lock (&hard_node->lock); > + err = unlink (hard_node->nn->path); > + if (err) > + { > + err = errno; > + pthread_mutex_unlock (&hard_node->lock); > + return err; > + } > + > + err = link (path, hard_node->nn->path); > + if (err) > + { > + err = errno; > + pthread_mutex_unlock (&hard_node->lock); > + return err; > + } > + > + pthread_mutex_unlock (&hard_node->lock); > + } > + } > + > + return 0; > + > + free_path: > + free (path); > + > + free_new_name: > + free (new_file_name); > + return err; > +} > + > +static error_t > +copy_node_to_upperdir (struct node *node, struct node *dir, > + const char *node_name) > +{ > + if (!S_ISDIR (node->nn_stat.st_mode)) > + return copy_file_to_upperdir (node, dir, node_name); > + > + return create_dir_in_upperdir (node, dir, node_name); > +} > + > +error_t > +create_node (struct node **node, struct node *dir, > + struct list_hardlinks *list_hardlinks, size_t num_dir, > + char *path, char *symlink_path, const char *name, > + struct stat stat) > +{ > + error_t err; > + struct netnode *netnode = calloc (1, sizeof (struct netnode)); > + if (!netnode) > + { > + err = errno; > + goto create_node_exit; > + } > + > + struct node *new_node = netfs_make_node (netnode); > + if (!new_node) > + { > + err = errno; > + goto free_netnode; > + } > + > + new_node->nn_stat = stat; > + new_node->nn_translated = stat.st_mode; > + new_node->next = NULL; > + new_node->prevp = NULL; > + > + new_node->nn->dir = dir; > + new_node->nn->num_dir = num_dir; > + new_node->nn->dirent_type = IFTODT (stat.st_mode); > + > + if (path) > + { > + new_node->nn->path = strdup (path); > + if (!new_node->nn->path) > + { > + err = errno; > + goto free_new_node; > + } > + } > + > + if (symlink_path) > + { > + new_node->nn->symlink_path = strdup (symlink_path); > + if (!new_node->nn->symlink_path) > + { > + err = errno; > + goto free_path; > + } > + } > + > + if (name) > + { > + new_node->nn->name = strdup (name); > + if (!new_node->nn->name) > + { > + err = errno; > + goto free_symlink_path; > + } > + } > + > + if (!list_hardlinks && num_dir != 0 && !S_ISDIR (stat.st_mode)) > + { > + debug ("create new list_hardlinks\n"); > + new_node->nn->list_hardlinks = malloc (sizeof (struct list_hardlinks)); > + if (!new_node->nn->list_hardlinks) > + { > + err = errno; > + goto free_name; > + } > + > + new_node->nn->list_hardlinks->hardlinks = NULL; > + new_node->nn->list_hardlinks->size = 0; > + err = add_node_to_array (&new_node->nn->list_hardlinks->hardlinks, > + &new_node->nn->list_hardlinks->size, > + new_node); > + if (err) > + { > + free (new_node->nn->list_hardlinks); > + goto free_name; > + } > + > + pthread_mutex_init (&new_node->nn->list_hardlinks->lock, NULL); > + } > + else if (list_hardlinks) > + { > + debug ("add new hard link\n"); > + pthread_mutex_lock (&list_hardlinks->lock); > + err = add_node_to_array (&list_hardlinks->hardlinks, > + &list_hardlinks->size, new_node); > + pthread_mutex_unlock (&list_hardlinks->lock); > + if (err) > + goto free_name; > + > + new_node->nn->list_hardlinks = list_hardlinks; > + } > + > + if (dir) > + { > + err = add_node_to_array (&dir->nn->entries, &dir->nn->entries_size, > + new_node); > + if (err) > + goto free_hardlinks; > + > + netfs_nref (dir); > + } > + > + *node = new_node; > + debug ("create_node (dir: %p, num_dir: %zu, path: %s, name: %s):\n", > + dir, num_dir, path, name); > + debug ("*node: %p\n", *node); > + debug ("create_node return: 0\n"); > + return 0; > + > + free_hardlinks: > + if (new_node->nn->list_hardlinks) > + { > + remove_node_from_hardlinks (new_node->nn->list_hardlinks, new_node); > + if (!new_node->nn->list_hardlinks->hardlinks) > + free (new_node->nn->list_hardlinks); > + } > + > + free_name: > + free (new_node->nn->name); > + > + free_symlink_path: > + free (new_node->nn->symlink_path); > + > + free_path: > + free (new_node->nn->path); > + > + free_new_node: > + free (new_node); > + > + free_netnode: > + free (netnode); > + > + create_node_exit: > + *node = NULL; > + debug ("create_node (dir: %p, num_dir: %zu, path: %s, name: %s):\n", > + dir, num_dir, path, name); > + debug ("*node: %p\n", *node); > + debug ("create_node return: %d\n", err); > + return err; > +} > + > +struct node * > +get_node (struct node *dir, const char *name) > +{ > + struct node *node; > + for (size_t i = 0; i < dir->nn->entries_size; ++i) > + { > + node = dir->nn->entries[i]; > + > + if (strcmp (name, node->nn->name) == 0) > + return node; > + } > + > + return NULL; > +} > + > +/* The user must define this function. Make sure that NP->nn_stat is > + filled with the most current information. CRED identifies the user > + responsible for the operation. NP is locked. */ > +error_t > +netfs_validate_stat (struct node *np, struct iouser *cred) > +{ > + debug ("netfs_validate_stat np: %p\n", np); > + > + if (np->nn->num_dir == 0 && np->nn->path) > + { > + ino_t inode = np->nn_stat.st_ino; > + dev_t device = np->nn_stat.st_dev; > + > + struct stat st; > + error_t err = lstat (np->nn->path, &st); > + if (err) > + return errno; > + > + np->nn_stat = st; > + np->nn_stat.st_ino = inode; > + np->nn_stat.st_dev = device; > + np->nn_translated = st.st_mode; > + } > + > + if (S_ISDIR (np->nn_stat.st_mode)) > + { > + np->nn_stat.st_size = 0; > + np->nn_stat.st_size += DIRENT_LEN (strlen (".")); > + np->nn_stat.st_size += DIRENT_LEN (strlen ("..")); > + > + for (size_t i = 0; i < np->nn->entries_size; ++i) > + np->nn_stat.st_size += > + DIRENT_LEN (strlen (np->nn->entries[i]->nn->name)); > + } > + > + return 0; > +} > + > +/* The user must define this function. This should attempt a chmod > + call for the user specified by CRED on locked node NP, to change > + the owner to UID and the group to GID. */ > +error_t > +netfs_attempt_chown (struct iouser *cred, struct node *np, uid_t uid, > + uid_t gid) > +{ > + debug ("netfs_attempt_chown (cred: %p, np: %p, uid: %d, gid: %d):\n", > + cred, np, uid, gid); > + > + if (!overlayfs.upperdir) > + return EROFS; > + > + if (overlayfs.metacopy && np->nn->num_dir != 0) > + { > + np->nn_stat.st_uid = uid; > + np->nn_stat.st_gid = gid; > + return 0; > + } > + > + error_t err; > + if (np->nn->num_dir != 0) > + { > + err = copy_node_to_upperdir (np, np->nn->dir, np->nn->name); > + if (err) > + return err; > + } > + > + err = lchown (np->nn->path, uid, gid); > + if (err) > + err = errno; > + > + return err; > +} > + > +/* The user must define this function. This should attempt a chauthor > + call for the user specified by CRED on locked node NP, thereby > + changing the author to AUTHOR. */ > +error_t > +netfs_attempt_chauthor (struct iouser *cred, struct node *np, uid_t author) > +{ > + debug ("netfs_attempt_chauthor (cred: %p, np: %p, author: %d):\n", > + cred, np, author); > + > + if (!overlayfs.upperdir) > + return EROFS; > + > + if (!S_ISDIR (np->nn_stat.st_mode)) > + { > + if (overlayfs.metacopy && np->nn->num_dir != 0) > + { > + np->nn_stat.st_author = author; > + return 0; > + } > + > + error_t err; > + if (np->nn->num_dir != 0) > + { > + err = copy_node_to_upperdir (np, np->nn->dir, np->nn->name); > + if (err) > + return err; > + } > + > + file_t file = file_name_lookup (np->nn->path, O_RDWR, 0); > + if (file == MACH_PORT_NULL) > + return errno; > + > + err = file_chauthor (file, author); > + mach_port_deallocate (mach_task_self (), file); > + return err; > + } > + > + return EOPNOTSUPP; > +} > + > +/* The user must define this function. This should attempt a chmod > + call for the user specified by CRED on locked node NODE, to change > + the mode to MODE. Unlike the normal Unix and Hurd meaning of > + chmod, this function is also used to attempt to change files into > + other types. If such a transition is attempted which is > + impossible, then return EOPNOTSUPP. */ > +error_t > +netfs_attempt_chmod (struct iouser *cred, struct node *np, mode_t mode) > +{ > + debug ("netfs_attempt_chmod (cred: %p, np: %p, mode: %d):\n", cred, np, > + mode); > + > + if (!overlayfs.upperdir) > + return EROFS; > + > + if (overlayfs.metacopy && np->nn->num_dir != 0) > + { > + np->nn_stat.st_mode = mode; > + return 0; > + } > + > + error_t err; > + if (np->nn->num_dir != 0) > + { > + err = copy_node_to_upperdir (np, np->nn->dir, np->nn->name); > + if (err) > + return err; > + } > + > + err = lchmod (np->nn->path, mode); > + if (err) > + err = errno; > + > + return err; > +} > + > +/* The user must define this function. Attempt to turn locked node NP > + (user CRED) into a symlink with target NAME. */ > +error_t > +netfs_attempt_mksymlink (struct iouser *cred, struct node *np, > + const char *name) > +{ > + debug ("netfs_attempt_mksymlink (cred: %p, np: %p, name: %s):\n", > + cred, np, name); > + > + np->nn->symlink_path = strdup (name); > + if (!np->nn->symlink_path) > + return errno; > + > + return 0; > +} > + > +/* The user must define this function. Attempt to turn NODE (user > + CRED) into a device. TYPE is either S_IFBLK or S_IFCHR. NP is > + locked. */ > +error_t > +netfs_attempt_mkdev (struct iouser *cred, struct node *np, mode_t type, > + dev_t indexes) > +{ > + debug ("netfs_attempt_mkdev (cred: %p, np: %p, type: %d):\n", > + cred, np, type); > + return EOPNOTSUPP; > +} > + > +/* The user must define this function. This should attempt a chflags > + call for the user specified by CRED on locked node NP, to change > + the flags to FLAGS. */ > +error_t > +netfs_attempt_chflags (struct iouser *cred, struct node *np, int flags) > +{ > + debug ("netfs_attempt_chflags (cred: %p, np: %p, flags: %d):\n", > + cred, np, flags); > + > + if (!overlayfs.upperdir) > + return EROFS; > + > + if (overlayfs.metacopy && np->nn->num_dir != 0) > + { > + np->nn_stat.st_flags = flags; > + return 0; > + } > + > + error_t err; > + if (np->nn->num_dir != 0) > + { > + err = copy_node_to_upperdir (np, np->nn->dir, np->nn->name); > + if (err) > + return err; > + } > + > + err = chflags (np->nn->path, flags); > + if (err) > + err = errno; > + > + return err; > +} > + > +/* The user must define this function. This should attempt a utimes > + call for the user specified by CRED on locked node NP, to change > + the atime to ATIME and the mtime to MTIME. If ATIME or MTIME is > + null, then do not change it. */ > +error_t > +netfs_attempt_utimes (struct iouser *cred, struct node *np, > + struct timespec *atime, struct timespec *mtime) > +{ > + debug ("netfs_attempt_utimes (cred: %p, np: %p, atime: %p, mtime: %p):\n", > + cred, np, atime, mtime); > + > + if (!overlayfs.upperdir) > + return EROFS; > + > + struct timespec times[2]; > + if (atime) > + times[0] = *atime; > + else > + { > + times[0].tv_sec = 0; > + times[0].tv_nsec = UTIME_OMIT; > + } > + > + if (mtime) > + times[1] = *mtime; > + else > + { > + times[1].tv_sec = 0; > + times[1].tv_nsec = UTIME_OMIT; > + } > + > + if (overlayfs.metacopy && np->nn->num_dir != 0) > + { > + np->nn_stat.st_atim = times[0]; > + np->nn_stat.st_mtim = times[1]; > + return 0; > + } > + > + error_t err; > + if (np->nn->num_dir != 0) > + { > + err = copy_node_to_upperdir (np, np->nn->dir, np->nn->name); > + if (err) > + return err; > + } > + > + if (np->nn->symlink_path) > + err = utimensat (AT_FDCWD, np->nn->path, times, AT_SYMLINK_NOFOLLOW); > + else > + err = utimensat (AT_FDCWD, np->nn->path, times, 0); > + > + if (err) > + return errno; > + > + return 0; > +} > + > +/* The user must define this function. This should attempt to set the > + size of the locked file NP (for user CRED) to SIZE bytes long. */ > +error_t > +netfs_attempt_set_size (struct iouser *cred, struct node *np, loff_t size) > +{ > + debug ("netfs_attempt_set_size (cred: %p, np: %p):\n", cred, np); > + > + if (!overlayfs.upperdir) > + return EROFS; > + > + if (overlayfs.metacopy && np->nn->num_dir != 0) > + { > + np->nn_stat.st_size = size; > + return 0; > + } > + > + error_t err; > + if (np->nn->num_dir != 0) > + { > + err = copy_file_to_upperdir (np, np->nn->dir, np->nn->name); > + if (err) > + return err; > + } > + > + err = truncate (np->nn->path, size); > + if (err) > + err = errno; > + > + return 0; > +} > + > +/* The user must define this function. This should attempt to fetch > + filesystem status information for the remote filesystem, for the > + user CRED. NP is locked. */ > +error_t > +netfs_attempt_statfs (struct iouser *cred, struct node *np, > + fsys_statfsbuf_t *st) > +{ > + debug ("netfs_attempt_statfs (cred: %p, np: %p, st: %p):\n", cred, np, st); > + return EOPNOTSUPP; > +} > + > +/* The user must define this function. This should sync the locked > + file NP completely to disk, for the user CRED. If WAIT is set, > + return only after the sync is completely finished. */ > +error_t > +netfs_attempt_sync (struct iouser *cred, struct node *np, int wait) > +{ > + debug ("netfs_attempt_sync (cred: %p, np: %p, wait: %d):\n", cred, np, > + wait); > + return 0; > +} > + > +/* The user must define this function. This should sync the entire > + remote filesystem. If WAIT is set, return only after the sync is > + completely finished. */ > +error_t > +netfs_attempt_syncfs (struct iouser *cred, int wait) > +{ > + debug ("netfs_attempt_syncfs (cred: %p, wait: %d):\n", cred, wait); > + return 0; > +} > + > +/* The user must define this function. Lookup NAME in DIR (which is > + locked) for USER; set *NP to the found name upon return. If the > + name was not found, then return ENOENT. On any error, clear *NP. > + (*NP, if found, should be locked and a reference to it generated. > + This call should unlock DIR no matter what.) */ > +error_t > +netfs_attempt_lookup (struct iouser *user, struct node *dir, > + const char *name, struct node **np) > +{ > + debug ("netfs_attempt_lookup: dir: %p\n", dir); > + > + if (!S_ISDIR (dir->nn_stat.st_mode)) > + { > + *np = NULL; > + pthread_mutex_unlock (&dir->lock); > + return ENOTDIR; > + } > + > + if (*name == '\0' || strcmp (name, ".") == 0) > + { > + *np = dir; > + netfs_nref (*np); > + return 0; > + } > + > + if (strcmp (name, "..") == 0) > + { > + *np = dir->nn->dir; > + netfs_nref (*np); > + pthread_mutex_unlock (&dir->lock); > + pthread_mutex_lock (&(*np)->lock); > + return 0; > + } > + > + *np = get_node (dir, name); > + if (*np) > + { > + netfs_nref (*np); > + pthread_mutex_unlock (&dir->lock); > + pthread_mutex_lock (&(*np)->lock); > + return 0; > + } > + > + pthread_mutex_unlock (&dir->lock); > + return ENOENT; > +} > + > +/* The user must define this function. Delete NAME in DIR (which is > + locked) for USER. */ > +error_t > +netfs_attempt_unlink (struct iouser *user, struct node *dir, const char > *name) > +{ > + debug ("netfs_attempt_unlink (user: %p, dir: %p, name: %s):\n", user, dir, > + name); > + > + if (!overlayfs.upperdir) > + return EROFS; > + > + struct node *node = get_node (dir, name); > + if (!node) > + return ENOENT; > + > + if (node->nn->num_dir == 0) > + { > + error_t err = unlink (node->nn->path); > + if (err) > + return errno; > + } > + > + remove_node_from_dir (dir, node); > + netfs_nrele (node); > + return 0; > +} > + > +static error_t > +prepare_todir (struct node *todir, struct node *node, > + const char *toname, int excl) > +{ > + if (S_ISDIR (node->nn_stat.st_mode)) > + { > + struct node *it_dir = todir; > + while (1) > + { > + if (it_dir == netfs_root_node) > + break; > + > + if (it_dir == node) > + return EPERM; > + > + it_dir = it_dir->nn->dir; > + } > + } > + > + struct node *replace_node = get_node (todir, toname); > + if (!replace_node) > + return 0; > + > + if (excl) > + return EEXIST; > + > + /* If we are here, it means we want to move a directory or file into a > + directory where a directory or file with the same name already exists. > + In this case, we need to ensure that node and replace_node are both > files > + or both directories. If node and replace_node are directories, we also > + need to ensure that replace_node is empty. */ > + > + if ((!S_ISDIR (node->nn_stat.st_mode) > + && S_ISDIR (replace_node->nn_stat.st_mode)) > + || (S_ISDIR (node->nn_stat.st_mode) > + && !S_ISDIR (replace_node->nn_stat.st_mode))) > + return EPERM; > + > + if (S_ISDIR (replace_node->nn_stat.st_mode) && replace_node->nn->entries) > + return ENOTEMPTY; > + > + error_t err; > + if (S_ISDIR (replace_node->nn_stat.st_mode)) > + err = rmdir (replace_node->nn->path); > + else > + err = unlink (replace_node->nn->path); > + > + if (err) > + return errno; > + > + remove_node_from_dir (todir, replace_node); > + netfs_nrele (replace_node); > + return 0; > +} > + > +static error_t > +rename_fromdir_node (struct node *node, char *new_path, char *new_name) > +{ > + error_t err = rename (node->nn->path, new_path); > + if (err) > + return errno; > + > + free (node->nn->path); > + node->nn->path = new_path; > + > + if (new_name) > + { > + free (node->nn->name); > + node->nn->name = new_name; > + } > + > + return 0; > +} > + > +static error_t > +change_node_paths (struct node *dir) > +{ > + struct node **list = malloc (sizeof (struct node *)); > + if (!list) > + return errno; > + > + error_t err; > + list[0] = dir; > + size_t list_size = 1; > + > + for (size_t i = 0; i < list_size; ++i) > + for (size_t j = 0; j < list[i]->nn->entries_size; ++j) > + { > + struct node *node = list[i]->nn->entries[j]; > + if (node->nn->entries) > + { > + err = add_node_to_array (&list, &list_size, node); > + if (err) > + { > + free (list); > + return err; > + } > + } > + > + if (node->nn->num_dir == 0) > + { > + char *new_path; > + err = concat_paths (&new_path, list[i]->nn->path, > node->nn->name); > + if (err) > + { > + free (list); > + return err; > + } > + > + free (node->nn->path); > + node->nn->path = new_path; > + } > + } > + > + free (list); > + return 0; > +} > + > +/* The user must define this function. Attempt to rename the > + directory FROMDIR to TODIR. Note that neither of the specific nodes > + are locked. */ > +error_t > +netfs_attempt_rename (struct iouser *user, struct node *fromdir, > + const char *fromname, struct node *todir, > + const char *toname, int excl) > +{ > + debug ("netfs_attempt_rename (user: %p, fromdir: %p, fromname: %s," > + " todir: %p, toname: %s, excl: %d):\n", user, fromdir, fromname, > + todir, toname, excl); > + > + if (!overlayfs.upperdir) > + return EROFS; > + > + char *real_fromname = (char *) fromname; > + int need_free_real_fromname = 0; > + if (fromname[strlen (fromname) - 1] == '/') > + { > + char *buf = calloc (1, strlen (fromname)); > + if (!buf) > + return errno; > + > + size_t pos = 0; > + while (fromname[pos] != '/') > + { > + buf[pos] = fromname[pos]; > + ++pos; > + } > + > + real_fromname = buf; > + if (!S_ISDIR (get_node (fromdir, real_fromname)->nn_stat.st_mode)) > + { > + free (real_fromname); > + return ENOTDIR; > + } > + > + need_free_real_fromname = 1; > + } > + else if (strcmp (fromname, ".") == 0) > + { > + if (!S_ISDIR (fromdir->nn_stat.st_mode)) > + return ENOTDIR; > + > + return EBUSY; > + } > + > + pthread_mutex_lock (&fromdir->lock); > + struct node *node = get_node (fromdir, real_fromname); > + error_t err; > + if (!node) > + { > + err = ENOENT; > + pthread_mutex_unlock (&fromdir->lock); > + goto free_real_fromname; > + } > + > + /* Before performing the rename/move operation, we must check toname. > + Let's execute the command: > + $ mv file foo > + This results in a call to netfs_attempt_rename with the arguments: > + fromdir: 0x123, fromname: "file", todir: 0x123, and toname: "foo". > + First, we must determine whether foo exists and, if so, what type of > + object it is. If foo does not exist or is a file, we must rename file > to > + foo; if foo is a directory, we must move file into foo. Note that in > the > + latter case, real_todir will not hold the todir value (which points to > + the directory containing both file and foo); instead, we must store a > + pointer to foo in real_todir ourselves. Similarly, real_toname will not > + hold toname (the string "foo"), but rather fromname (the string "file"). > + Other similar mv calls are possible, resulting in different invocations > + of netfs_attempt_rename: > + $ mv file foo/ > + In this case, toname is "foo/", and todir holds a pointer to the > + directory containing file and foo. We must first verify that foo is a > + directory; if it is, we need to move file into foo. To do this, > + real_toname must be set to fromname, and real_todir must point to foo. > + $ mv file foo/. > + In this case, toname is ".", and todir holds a pointer to the directory > + foo. Consequently, we need to move file into foo, which simply requires > + storing the fromname value in real_toname. */ > + > + char *real_toname = (char *) toname; > + struct node *real_todir = todir; > + > + if (toname[strlen (toname) - 1] == '/') > + { > + char *buf = calloc (1, strlen (toname)); > + if (!buf) > + { > + err = errno; > + pthread_mutex_unlock (&fromdir->lock); > + goto free_real_fromname; > + } > + > + for (size_t pos = 0; 1; ++pos) > + { > + if (toname[pos] == '/') > + break; > + > + buf[pos] = toname[pos]; > + } > + > + struct node *new_todir = get_node (todir, buf); > + free (buf); > + if (!new_todir || !S_ISDIR (new_todir->nn_stat.st_mode)) > + { > + err = EPERM; > + pthread_mutex_unlock (&fromdir->lock); > + goto free_real_fromname; > + } > + > + real_todir = new_todir; > + real_toname = (char *) real_fromname; > + } > + else if (strcmp (toname, ".") == 0) > + { > + if (!S_ISDIR (todir->nn_stat.st_mode)) > + { > + err = EPERM; > + pthread_mutex_unlock (&fromdir->lock); > + goto free_real_fromname; > + } > + > + real_toname = (char *) real_fromname; > + } > + > + debug ("real_todir: %p\n", real_todir); > + debug ("real_toname: %s\n", real_toname); > + > + if (fromdir == real_todir && strcmp (real_fromname, real_toname) == 0) > + { > + err = EPERM; > + pthread_mutex_unlock (&fromdir->lock); > + goto free_real_fromname; > + } > + > + char *new_path = NULL; > + char *new_name = NULL; > + > + if (node->nn->num_dir == 0) > + { > + /* Since node is in upperdir, we don't need to copy it; we can simply > + use the rename function. */ > + err = concat_paths (&new_path, real_todir->nn->path, real_toname); > + if (err) > + { > + pthread_mutex_unlock (&fromdir->lock); > + goto free_real_fromname; > + } > + > + if (fromdir == real_todir) > + { > + /* This is the simplest case; we simply need to rename node. */ > + char *new_name = strdup (real_toname); > + if (!new_name) > + { > + err = errno; > + goto unlock_fromdir; > + } > + > + err = prepare_todir (fromdir, node, real_toname, excl); > + if (err) > + goto unlock_fromdir; > + > + err = rename_fromdir_node (node, new_path, new_name); > + if (err) > + goto unlock_fromdir; > + } > + else > + { > + /* This is where we move a file between different directories. > + We should rename node if necessary, as well as remove it from > + fromdir and add it to real_todir. */ > + pthread_mutex_lock (&real_todir->lock); > + if (real_todir->nn->num_dir != 0) > + { > + err = create_dir_in_upperdir (real_todir, real_todir->nn->dir, > + real_todir->nn->name); > + if (err) > + goto unlock_real_todir; > + } > + > + if (strcmp (real_fromname, real_toname) != 0) > + { > + new_name = strdup (real_toname); > + if (!new_name) > + { > + err = errno; > + goto unlock_real_todir; > + } > + } > + > + err = prepare_todir (real_todir, node, real_toname, excl); > + if (err) > + goto unlock_real_todir; > + > + err = rename_fromdir_node (node, new_path, new_name); > + if (err) > + goto unlock_real_todir; > + > + err = add_node_to_array (&real_todir->nn->entries, > + &real_todir->nn->entries_size, node); > + if (err) > + goto unlock_real_todir; > + > + node->nn->dir = real_todir; > + remove_node_from_dir (fromdir, node); > + netfs_nref (real_todir); > + pthread_mutex_unlock (&real_todir->lock); > + } > + > + if (node->nn->entries) > + { > + err = change_node_paths (node); > + if (err) > + { > + pthread_mutex_unlock (&fromdir->lock); > + goto free_real_fromname; > + } > + } > + } > + else > + { > + /* Since node is in the lowerdir directory, we need to copy it to the > + upperdir directory. */ > + if (fromdir != real_todir) > + pthread_mutex_lock (&real_todir->lock); > + > + err = prepare_todir (real_todir, node, real_toname, excl); > + if (err) > + { > + if (fromdir != real_todir) > + goto unlock_real_todir; > + > + goto unlock_fromdir; > + } > + > + err = copy_node_to_upperdir (node, real_todir, real_toname); > + > + if (fromdir != real_todir) > + pthread_mutex_unlock (&real_todir->lock); > + } > + > + pthread_mutex_unlock (&fromdir->lock); > + if (need_free_real_fromname) > + free (real_fromname); > + > + return err; > + > + unlock_real_todir: > + pthread_mutex_unlock (&real_todir->lock); > + > + unlock_fromdir: > + pthread_mutex_unlock (&fromdir->lock); > + free (new_path); > + free (new_name); > + > + free_real_fromname: > + if (need_free_real_fromname) > + free (real_fromname); > + > + return err; > +} > + > +/* The user must define this function. Attempt to create a new > + directory named NAME in DIR (which is locked) for USER with mode > + MODE. */ > +error_t > +netfs_attempt_mkdir (struct iouser *user, struct node *dir, const char *name, > + mode_t mode) > +{ > + debug ("netfs_attempt_mkdir (user: %p, dir: %p, name: %s, mode: %d):\n", > + user, dir, name, mode); > + > + if (!overlayfs.upperdir) > + return EROFS; > + > + if (get_node (dir, name)) > + return EEXIST; > + > + error_t err; > + if (dir->nn->num_dir != 0) > + { > + err = create_dir_in_upperdir (dir, dir->nn->dir, dir->nn->name); > + if (err) > + return err; > + } > + > + char *path; > + err = concat_paths (&path, dir->nn->path, name); > + if (err) > + return err; > + > + err = mkdir (path, 0777); > + if (err) > + { > + err = errno; > + free (path); > + return err; > + } > + > + struct stat st; > + err = stat (path, &st); > + if (err) > + { > + err = errno; > + free (path); > + return err; > + } > + > + struct node *node; > + err = create_node (&node, dir, NULL, 0, path, NULL, name, st); > + free (path); > + return err; > +} > + > +/* The user must define this function. Attempt to remove directory > + named NAME in DIR (which is locked) for USER. */ > +error_t > +netfs_attempt_rmdir (struct iouser *user, struct node *dir, const char *name) > +{ > + debug ("netfs_attempt_rmdir (user: %p, dir: %p, name: %s):\n", user, dir, > + name); > + > + if (!overlayfs.upperdir) > + return EROFS; > + > + struct node *inner_dir = get_node (dir, name); > + if (!inner_dir) > + return ENOENT; > + > + if (inner_dir->nn->entries) > + return ENOTEMPTY; > + > + if (inner_dir->nn->num_dir == 0) > + { > + error_t err = rmdir (inner_dir->nn->path); > + if (err) > + return errno; > + } > + > + remove_node_from_dir (dir, inner_dir); > + netfs_nrele (inner_dir); > + return 0; > +} > + > +/* The user must define this function. Create a link in DIR with name > + NAME to FILE for USER. Note that neither DIR nor FILE are > + locked. If EXCL is set, do not delete the target. Return EEXIST if > + NAME is already found in DIR. */ > +error_t > +netfs_attempt_link (struct iouser *user, struct node *dir, struct node *file, > + const char *name, int excl) > +{ > + debug ("netfs_attempt_link (user: %p, dir: %p, file: %p, name: %s," > + " excl: %d):\n", user, dir, file, name, excl); > + > + if (!overlayfs.upperdir) > + return EROFS; > + > + pthread_mutex_lock (&dir->lock); > + struct node *node = get_node (dir, name); > + if (node) > + { > + pthread_mutex_unlock (&dir->lock); > + return EEXIST; > + } > + > + error_t err; > + if (dir->nn->num_dir != 0) > + { > + err = create_dir_in_upperdir (dir, dir->nn->dir, dir->nn->name); > + if (err) > + { > + pthread_mutex_unlock (&dir->lock); > + return err; > + } > + } > + > + char *path; > + err = concat_paths (&path, dir->nn->path, name); > + if (err) > + { > + pthread_mutex_unlock (&dir->lock); > + return err; > + } > + > + pthread_mutex_lock (&file->lock); > + > + if (!file->nn->path) > + err = symlink (file->nn->symlink_path, path); > + else if (file->nn->symlink_path) > + err = linkat (AT_FDCWD, file->nn->path, AT_FDCWD, path, 0); > + else > + err = link (file->nn->path, path); > + > + if (err) > + { > + err = errno; > + goto out; > + } > + > + struct stat st; > + err = lstat (path, &st); > + if (err) > + { > + err = errno; > + goto out; > + } > + > + struct list_hardlinks *list = NULL; > + if (file->nn->num_dir != 0) > + list = file->nn->list_hardlinks; > + > + err = create_node (&node, dir, list, 0, path, file->nn->symlink_path, name, > + st); > + > + out: > + pthread_mutex_unlock (&file->lock); > + pthread_mutex_unlock (&dir->lock); > + free (path); > + return err; > +} > + > +/* The user must define this function. Attempt to create an anonymous > + file related to DIR (which is locked) for USER with MODE. Set *NP > + to the returned file upon success. No matter what, unlock DIR. */ > +error_t > +netfs_attempt_mkfile (struct iouser *user, struct node *dir, mode_t mode, > + struct node **np) > +{ > + debug ("netfs_attempt_mkfile (user: %p, dir: %p, mode: %d):\n", user, dir, > + mode); > + > + if (!overlayfs.upperdir) > + { > + pthread_mutex_unlock (&dir->lock); > + return EROFS; > + } > + > + error_t err = fshelp_access (&dir->nn_stat, S_IWRITE, user); > + if (err) > + { > + pthread_mutex_unlock (&dir->lock); > + return err; > + } > + > + struct netnode *netnode = calloc (1, sizeof (struct netnode)); > + if (!netnode) > + { > + err = errno; > + pthread_mutex_unlock (&dir->lock); > + return err; > + } > + > + *np = netfs_make_node (netnode); > + if (!*np) > + { > + err = errno; > + free (netnode); > + pthread_mutex_unlock (&dir->lock); > + return err; > + } > + > + (*np)->nn_stat.st_mode = mode; > + debug ("*np: %p\n", *np); > + > + pthread_mutex_unlock (&dir->lock); > + pthread_mutex_lock (&(*np)->lock); > + return 0; > +} > + > +/* The user must define this function. Attempt to create a file named > + NAME in DIR (which is locked) for USER with MODE. Set *NP to the > + new node upon return. On any error, clear *NP. *NP should be > + locked on success; no matter what, unlock DIR before returning. */ > +error_t > +netfs_attempt_create_file (struct iouser *user, struct node *dir, > + const char *name, mode_t mode, struct node **np) > +{ > + debug ("netfs_attempt_create_file (user: %p, dir: %p, name: %s," > + " mode: %d):\n", user, dir, name, mode); > + > + error_t err; > + if (!overlayfs.upperdir) > + { > + err = EROFS; > + goto return_error; > + } > + > + err = fshelp_access (&dir->nn_stat, S_IWRITE, user); > + if (err) > + goto return_error; > + > + if (dir->nn->num_dir != 0) > + { > + err = create_dir_in_upperdir (dir, dir->nn->dir, dir->nn->name); > + if (err) > + goto return_error; > + } > + > + char *path; > + err = concat_paths (&path, dir->nn->path, name); > + if (err) > + goto return_error; > + > + int file = open (path, O_CREAT | O_EXCL, mode); > + if (file == -1) > + goto free_path; > + > + close (file); > + > + struct stat st; > + err = stat (path, &st); > + if (err) > + goto free_path; > + > + err = create_node (np, dir, NULL, 0, path, NULL, name, st); > + if (!err) > + { > + netfs_nref (*np); > + pthread_mutex_lock (&(*np)->lock); > + } > + > + free (path); > + pthread_mutex_unlock (&dir->lock); > + return err; > + > + free_path: > + err = errno; > + free (path); > + > + return_error: > + *np = NULL; > + pthread_mutex_unlock (&dir->lock); > + return err; > +} > + > +/* The user must define this function. Read the contents of locked > + node NP (a symlink), for USER, into BUF. */ > +error_t > +netfs_attempt_readlink (struct iouser *user, struct node *np, char *buf) > +{ > + debug ("netfs_attempt_readlink (user: %p, np: %p):\n", user, np); > + > + memcpy (buf, np->nn->symlink_path, np->nn_stat.st_size); > + return 0; > +} > + > +/* The user must define this function. Locked node NP is being opened > + by USER, with FLAGS. NEWNODE is nonzero if we just created this > + node. Return an error if we should not permit the open to complete > + because of a permission restriction. */ > +error_t > +netfs_check_open_permissions (struct iouser *user, struct node *np, > + int flags, int newnode) > +{ > + error_t err = 0; > + > + if (!err && flags & O_READ) > + err = fshelp_access (&np->nn_stat, S_IREAD, user); > + > + if (!err && flags & O_WRITE) > + err = fshelp_access (&np->nn_stat, S_IWRITE, user); > + > + if (!err && flags & O_EXEC) > + err = fshelp_access (&np->nn_stat, S_IEXEC, user); > + > + return err; > +} > + > +/* The user must define this function. Read from the locked file NP > + for user CRED starting at OFFSET and continuing for up to *LEN > + bytes. Put the data at DATA. Set *LEN to the amount successfully > + read upon return. */ > +error_t > +netfs_attempt_read (struct iouser *cred, struct node *np, loff_t offset, > + size_t *len, void *data) > +{ > + debug ("netfs_attempt_read (cred: %p, np: %p):\n", cred, np); > + file_t file = file_name_lookup (np->nn->path, O_READ, 0); > + if (file == MACH_PORT_NULL) > + return errno; > + > + char *buf = data; > + mach_msg_type_number_t size = *len; > + error_t err = io_read (file, &buf, &size, offset, size); > + mach_port_deallocate (mach_task_self (), file); > + if (err) > + return err; > + > + *len = size; > + if (buf != data) > + { > + memcpy (data, buf, *len); > + munmap (buf, *len); > + } > + > + return 0; > +} > + > +/* The user must define this function. Write to the locked file NP > + for user CRED starting at OFSET and continuing for up to *LEN bytes > + from DATA. Set *LEN to the amount successfully written upon > + return. */ > +error_t > +netfs_attempt_write (struct iouser *cred, struct node *np, loff_t offset, > + size_t *len, const void *data) > +{ > + debug ("netfs_attempt_write (cred: %p, np: %p):\n", cred, np); > + if (!overlayfs.upperdir) > + return EROFS; > + > + error_t err; > + if (np->nn->num_dir != 0) > + { > + err = copy_file_to_upperdir (np, np->nn->dir, np->nn->name); > + if (err) > + return err; > + } > + > + file_t file = file_name_lookup (np->nn->path, O_WRITE, 0); > + if (file == MACH_PORT_NULL) > + return errno; > + > + err = io_write (file, data, *len, offset, len); > + mach_port_deallocate (mach_task_self (), file); > + return err; > +} > + > +/* The user must define this function. Return the valid access > + types (bitwise OR of O_READ, O_WRITE, and O_EXEC) in *TYPES for > + locked file NP and user CRED. */ > +error_t > +netfs_report_access (struct iouser *cred, struct node *np, int *types) > +{ > + debug ("netfs_report_access (cred: %p, np: %p):\n", cred, np); > + file_t file = file_name_lookup (np->nn->path, O_RDWR, 0); > + if (file == MACH_PORT_NULL) > + return errno; > + > + error_t err = file_check_access (file, types); > + mach_port_deallocate (mach_task_self (), file); > + return err; > +} > + > +/* The user must define this function. Create a new user from the > + specified UID and GID arrays. */ > +struct iouser * > +netfs_make_user (uid_t *uids, int nuids, uid_t *gids, int ngids) > +{ > + debug ("netfs_make_user (uids: %p, nuids: %d, gids: %p, ngids: %d):\n", > + uids, nuids, gids, ngids); > + return NULL; > +} > + > +/* The user must define this function. Node NP has no more references; > + free all its associated storage. */ > +void > +netfs_node_norefs (struct node *np) > +{ > + debug ("netfs_node_norefs (np: %p, np->nn->path %s):\n", np, np->nn->path); > + /* We don't need to free memory for entries because if node is a file then > + entries is NULL, and if it is a directory then its entries are already > + freed because you can only remove an empty directory. */ > + > + if (np->nn->list_hardlinks) > + { > + remove_node_from_hardlinks (np->nn->list_hardlinks, np); > + if (!np->nn->list_hardlinks->hardlinks) > + free (np->nn->list_hardlinks); > + } > + > + free (np->nn->path); > + free (np->nn->symlink_path); > + free (np->nn->name); > + free (np->nn); > + free (np); > +} > + > +static inline int > +bump_size (size_t *size, int *count, const char *name, const int nentries, > + const vm_size_t buffsize) > +{ > + if (nentries == -1 || *count < nentries) > + { > + size_t new_size = *size + DIRENT_LEN (strlen (name)); > + if (buffsize > 0 && new_size > buffsize) > + return 0; > + > + *size = new_size; > + *count += 1; > + return 1; > + } > + > + return 0; > +} > + > +static inline int > +add_dir_entry (char **data, const char *name, const ino_t fileno, > + const int type, int *count, const int nentries, size_t *size) > +{ > + if (nentries == -1 || *count < nentries) > + { > + size_t namlen = strlen (name); > + size_t sz = DIRENT_LEN (namlen); > + > + if (sz > *size) > + return 0; > + > + *size -= sz; > + > + struct dirent hdr; > + hdr.d_fileno = fileno; > + hdr.d_reclen = sz; > + hdr.d_type = type; > + hdr.d_namlen = namlen; > + > + memcpy (*data, &hdr, DIRENT_NAME_OFFS); > + strcpy (*data + DIRENT_NAME_OFFS, name); > + > + *data += sz; > + *count += 1; > + return 1; > + } > + > + return 0; > +} > + > +/* The user must define this function. Fill the array *DATA of size > + BUFSIZE with up to NENTRIES dirents from DIR (which is locked) > + starting with entry ENTRY for user CRED. The number of entries in > + the array is stored in *AMT and the number of bytes in *DATACNT. > + If the supplied buffer is not large enough to hold the data, it > + should be grown. */ > +error_t > +netfs_get_dirents (struct iouser *cred, struct node *dir, int entry, > + int nentries, char **data, mach_msg_type_number_t > *datacnt, > + vm_size_t bufsize, int *amt) > +{ > + debug ("netfs_get_dirents (cred: %p, dir: %p):\n", cred, dir); > + > + if (!S_ISDIR (dir->nn_stat.st_mode)) > + return ENOTDIR; > + > + if (dir->nn->entries_size + 2 <= entry) > + { > + *datacnt = 0; > + *amt = 0; > + *data = NULL; > + return 0; > + } > + > + int count = 0; > + size_t size = 0; > + > + if (entry == 0) > + bump_size (&size, &count, ".", nentries, bufsize); > + if (entry <= 1) > + bump_size (&size, &count, "..", nentries, bufsize); > + > + struct node *current_node; > + for (size_t i = 0; i < dir->nn->entries_size; ++i) > + { > + current_node = dir->nn->entries[i]; > + > + if (!bump_size (&size, &count, current_node->nn->name, nentries, > + bufsize)) > + break; > + } > + > + void *new_data = mmap (0, size, PROT_READ | PROT_WRITE, MAP_ANON, 0, 0); > + if (new_data == MAP_FAILED) > + return errno; > + > + *data = (char *) new_data; > + *datacnt = size; > + *amt = count; > + > + count = 0; > + char *ptr_data = *data; > + > + if (entry == 0) > + add_dir_entry (&ptr_data, ".", dir->nn_stat.st_ino, DT_DIR, &count, > + nentries, &size); > + if (entry <= 1) > + { > + if (dir == netfs_root_node) > + add_dir_entry (&ptr_data, "..", 1, DT_DIR, &count, nentries, &size); > + else > + add_dir_entry (&ptr_data, "..", dir->nn->dir->nn_stat.st_ino, DT_DIR, > + &count, nentries, &size); > + } > + > + for (size_t i = 0; i < dir->nn->entries_size; ++i) > + { > + current_node = dir->nn->entries[i]; > + if (current_node->nn->name) > + if (!add_dir_entry (&ptr_data, current_node->nn->name, > + current_node->nn_stat.st_ino, > + current_node->nn->dirent_type, &count, nentries, > + &size)) > + break; > + } > + > + return 0; > +} > diff --git a/overlayfs/options.c b/overlayfs/options.c > new file mode 100644 > index 00000000..914f720d > --- /dev/null > +++ b/overlayfs/options.c > @@ -0,0 +1,185 @@ > +/* Command-line option parsing. > + Copyright (C) 2026 Free Software Foundation > + > + This file is part of the GNU Hurd. > + > + The GNU Hurd is free software; you can redistribute it and/or > + modify it under the terms of the GNU General Public License as > + published by the Free Software Foundation; either version 2, or (at > + your option) any later version. > + > + The GNU Hurd is distributed in the hope that it will be useful, but > + WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ > + > +#include <error.h> > +#include <stdlib.h> > +#include <string.h> > +#include <unistd.h> > + > +#include "options.h" > +#include "overlayfs.h" > + > +#define LOWERDIR -1 > +#define UPPERDIR -2 > +#define INDEX -3 > +#define METACOPY -4 > + > +static char * > +remove_extra_slashes (char *str) > +{ > + size_t str_len = strlen (str); > + char *buf = calloc (1, str_len + 1); > + if (!buf) > + error (1, errno, "Failed to allocate memory for buf"); > + > + size_t str_pos = 0; > + size_t buf_pos = 0; > + while (str_pos < str_len) > + { > + if (str[str_pos] == '/') > + { > + if (str_pos + 1 < str_len && str[str_pos + 1] == '/') > + { > + ++str_pos; > + continue; > + } > + else if (str_pos + 1 == str_len) > + break; > + } > + > + buf[buf_pos] = str[str_pos]; > + ++str_pos; > + ++buf_pos; > + } > + > + return buf; > +} > + > +static inline int > +get_value (struct argp_state *state, char *arg) > +{ > + int value = 0; > + if (strcmp (arg, "on") == 0) > + value = 1; > + else if (strcmp (arg, "off") == 0) > + value = 0; > + else > + argp_error (state, "Unrecognized argument: %s", arg); > + > + return value; > +} > + > +const struct argp_option options[] = > +{ > +#ifdef DEBUG > + {"debug", 'd', "FILE", 0, "Enable debug and write debug" > + " statements to FILE. The FILE must be located outside the translator" > + " directory.", 0}, > +#endif > + {"lowerdir", LOWERDIR, "DIR", 0, "DIR represents the path to a" > + " directory. DIR can be a colon-separated list of directories. The" > + " lowerdir can be only read-only.", 0}, > + {"upperdir", UPPERDIR, "DIR", 0, "DIR represents the path to a" > + " directory. The upperdir may be read and write.", 0}, > + {"index", INDEX, "on|off", 0, "When this feature is enabled, copying" > + " a file from lowerdir to upperdir causes all hard links to the file to > be" > + " updated to point to the new file in upperdir.", 0}, > + {"metacopy", METACOPY, "on|off", 0, "When this feature is enabled," > + " overlayfs will not copy the file from lowerdir to the upperdir during" > + " operations that modify only metadata. Instead, overlayfs will store the" > + " changes within its own structure until a write operation to the file" > + " occurs.", 0}, > + {0} > +}; > + > +error_t > +parse_opt (int key, char *arg, struct argp_state *state) > +{ > + switch (key) > + { > +#ifdef DEBUG > + case 'd': > + debug_file_name = arg; > + break; > +#endif > + > + case LOWERDIR: > + { > + char *start = arg; > + char *dir_sep; > + > + do > + { > + size_t len = (overlayfs.lowerdirs_count + 1) * sizeof (char *); > + char **buf = realloc (overlayfs.lowerdirs, len); > + if (!buf) > + argp_failure (state, 1, errno, "Failed to reallocate memory > for" > + " lowerdirs"); > + > + overlayfs.lowerdirs = buf; > + dir_sep = strchr (start, ':'); > + if (dir_sep) > + { > + char *tmp_str = strndup (start, dir_sep - start); > + if (!tmp_str) > + argp_failure (state, 1, errno, "Failed to reallocate > memory" > + " for tmp_str"); > + > + start = dir_sep + 1; > + overlayfs.lowerdirs[overlayfs.lowerdirs_count] = > + remove_extra_slashes (tmp_str); > + > + free (tmp_str); > + } > + else > + overlayfs.lowerdirs[overlayfs.lowerdirs_count] > + = remove_extra_slashes (start); > + > + ++overlayfs.lowerdirs_count; > + } > + while (dir_sep); > + break; > + } > + > + case UPPERDIR: > + overlayfs.upperdir = remove_extra_slashes (arg); > + break; > + > + case INDEX: > + overlayfs.index = get_value (state, arg); > + break; > + > + case METACOPY: > + overlayfs.metacopy = get_value (state, arg); > + break; > + > + case ARGP_KEY_END: > + if (!overlayfs.lowerdirs) > + argp_error (state, "The lowerdir option is not specified."); > + break; > + > + case ARGP_KEY_INIT: > +#ifdef DEBUG > + debug_file_name = NULL; > +#endif > + overlayfs.upperdir = NULL; > + overlayfs.lowerdirs = NULL; > + overlayfs.lowerdirs_count = 0; > + overlayfs.metacopy = 0; > + overlayfs.index = 0; > + break; > + > + case ARGP_KEY_SUCCESS: > + break; > + > + default: > + return ARGP_ERR_UNKNOWN; > + } > + > + return 0; > +} > diff --git a/overlayfs/options.h b/overlayfs/options.h > new file mode 100644 > index 00000000..9c2cf855 > --- /dev/null > +++ b/overlayfs/options.h > @@ -0,0 +1,28 @@ > +/* Definitions for parsing command-line arguments. > + Copyright (C) 2026 Free Software Foundation > + > + This file is part of the GNU Hurd. > + > + The GNU Hurd is free software; you can redistribute it and/or > + modify it under the terms of the GNU General Public License as > + published by the Free Software Foundation; either version 2, or (at > + your option) any later version. > + > + The GNU Hurd is distributed in the hope that it will be useful, but > + WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ > + > +#ifndef OVERLAYFS_OPTIONS_H > +#define OVERLAYFS_OPTIONS_H > + > +#include <argp.h> > + > +extern const struct argp_option options[]; > + > +error_t parse_opt (int key, char *arg, struct argp_state *state); > + > +#endif /* OVERLAYFS_OPTIONS_H */ > diff --git a/overlayfs/overlayfs.c b/overlayfs/overlayfs.c > new file mode 100644 > index 00000000..a3db0ff8 > --- /dev/null > +++ b/overlayfs/overlayfs.c > @@ -0,0 +1,151 @@ > +/* Main entry point for the overlayfs translator. > + Copyright (C) 2026 Free Software Foundation > + > + This file is part of the GNU Hurd. > + > + The GNU Hurd is free software; you can redistribute it and/or > + modify it under the terms of the GNU General Public License as > + published by the Free Software Foundation; either version 2, or (at > + your option) any later version. > + > + The GNU Hurd is distributed in the hope that it will be useful, but > + WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ > + > +#include <dirent.h> > +#include <error.h> > +#include <version.h> > + > +#include "options.h" > +#include "overlayfs.h" > + > +#ifdef DEBUG > +char *debug_file_name = NULL; > +FILE *debug_file = NULL; > +pthread_mutex_t debug_lock = PTHREAD_MUTEX_INITIALIZER; > +#endif > + > +const char *argp_program_version = STANDARD_HURD_VERSION (overlayfs); > +static const char argp_doc[] = "OVERLAYFS-DOC"; > +static const char doc[] = "A translator representing an implementation of a" > + " union-mount file system."; > + > +char *netfs_server_name = "overlayfs"; > +char *netfs_server_version = HURD_VERSION; > +int netfs_maxsymlinks = 1024; /* arbitrary */ > + > +struct overlayfs overlayfs; > + > +static error_t > +create_overlafs (io_statbuf_t underlying_stat) > +{ > + debug ("create_overlafs ():\n"); > + error_t err = maptime_map (1, 0, &overlayfs.current_time); > + if (err) > + { > + err = maptime_map (0, 0, &overlayfs.current_time); > + if (err) > + { > + debug ("maptime_map return err: %d\n", err); > + return err; > + } > + } > + > + underlying_stat.st_nlink = 2; > + underlying_stat.st_mode = > + S_IFDIR | (underlying_stat.st_mode & ~S_IFMT & ~S_ITRANS); > + > + err = create_node (&netfs_root_node, NULL, NULL, 0, overlayfs.upperdir, > + NULL, NULL, underlying_stat); > + if (err) > + { > + debug ("create_node return err: %d\n", err); > + return err; > + } > + > + fshelp_touch (&netfs_root_node->nn_stat, > + TOUCH_ATIME | TOUCH_CTIME | TOUCH_MTIME, > + overlayfs.current_time); > + > + err = init_unite_dir (); > + if (err) > + { > + debug ("init_unite_dir return error: %d\n", err); > + return err; > + } > + > + if (overlayfs.upperdir) > + { > + err = merge_unite_dir (overlayfs.upperdir, 0); > + if (err) > + { > + debug ("merge_unite_dir (overlayfs.upperdir: %s) return error:" > + " %d\n", overlayfs.upperdir, err); > + return err; > + } > + } > + > + for (size_t i = 0; i < overlayfs.lowerdirs_count; ++i) > + { > + err = merge_unite_dir (overlayfs.lowerdirs[i], i + 1); > + if (err) > + { > + debug ("merge_unite_dir (overlayfs.lowerdirs[%zu]: %s) return" > + " error: %d\n", i, overlayfs.lowerdirs[i], err); > + return err; > + } > + } > + > + destroy_unite_dir (); > + return 0; > +} > + > +int > +main (int argc, char **argv) > +{ > + struct argp argp = {options, parse_opt, argp_doc, doc}; > + argp_parse (&argp, argc, argv, 0, 0, 0); > + > + mach_port_t bootstrap; > + task_get_bootstrap_port (mach_task_self (), &bootstrap); > + if (bootstrap == MACH_PORT_NULL) > + error (1, 0, "Must be started as a translator"); > + > + netfs_init (); > + > + mach_port_t underlying_node = netfs_startup (bootstrap, O_READ); > + io_statbuf_t underlying_stat; > + > + error_t err = io_stat (underlying_node, &underlying_stat); > + if (err) > + error (1, err, "Cannot stat underlying_node"); > + > +#ifdef DEBUG > + if (debug_file_name) > + { > + debug_file = fopen (debug_file_name, "a"); > + setbuf (debug_file, NULL); > + pthread_mutex_init (&debug_lock, NULL); > + } > +#endif > + > + debug ("\n---------------start main---------------\n"); > + debug ("upperdir: %s\n", overlayfs.upperdir); > + for (size_t i = 0; i < overlayfs.lowerdirs_count; ++i) > + debug ("lowerdir[%zu]: %s\n", i, overlayfs.lowerdirs[i]); > + > + debug ("index: %s\n", overlayfs.index ? "on" : "off"); > + debug ("metacopy: %s\n", overlayfs.metacopy ? "on" : "off"); > + > + err = create_overlafs (underlying_stat); > + if (err) > + error (1, err, "Cannot create overlayfs"); > + > + debug ("netfs_server_loop ()...\n"); > + netfs_server_loop (); > + return 0; > +} > diff --git a/overlayfs/overlayfs.h b/overlayfs/overlayfs.h > new file mode 100644 > index 00000000..f2bc63f0 > --- /dev/null > +++ b/overlayfs/overlayfs.h > @@ -0,0 +1,125 @@ > +/* Definitions for the overlayfs translator. > + Copyright (C) 2026 Free Software Foundation > + > + This file is part of the GNU Hurd. > + > + The GNU Hurd is free software; you can redistribute it and/or > + modify it under the terms of the GNU General Public License as > + published by the Free Software Foundation; either version 2, or (at > + your option) any later version. > + > + The GNU Hurd is distributed in the hope that it will be useful, but > + WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ > + > +#ifndef OVERLAYFS_OVERLAYFS_H > +#define OVERLAYFS_OVERLAYFS_H > + > +#include <hurd/netfs.h> > +#include <pthread.h> > +#include <stdio.h> > +#include <stddef.h> > +#include <unistd.h> > +#include <sys/stat.h> > + > +/* Returned directory entries are aligned to blocks this many bytes long. > + Must be a power of two. */ > +#define DIRENT_ALIGN 4 > +#define DIRENT_NAME_OFFS offsetof (struct dirent, d_name) > + > +/* Length is structure before the name + the name + '\0', all > + padded to a four-byte alignment. */ > +#define DIRENT_LEN(name_len) \ > + ((DIRENT_NAME_OFFS + (name_len) + 1 + (DIRENT_ALIGN - 1)) \ > + & ~(DIRENT_ALIGN - 1)) > + > +struct list_hardlinks > +{ > + pthread_mutex_t lock; > + struct node **hardlinks; > + size_t size; > +}; > + > +struct netnode > +{ > + /* If this node is a directory, the entries contain pointers to the nodes > + in that directory. */ > + struct node **entries; > + > + /* The node pointer to the directory in which this node is located. */ > + struct node *dir; > + > + /* If the node is a file or symlink located in lowerdir, then > list_hardlinks > + contains pointers to other nodes that are hard links to this file or > + symlink. */ > + struct list_hardlinks *list_hardlinks; > + > + /* The full path along with the file, symlink or directory name of this > node > + in a third-party FS. */ > + char *path; > + > + /* If this node is a symlink, the symlink_path is string from readlink. */ > + char *symlink_path; > + > + /* The name of this node. */ > + char *name; > + > + /* If this node is a directory, entries_size contains the number of nodes > + in that directory. */ > + size_t entries_size; > + > + /* This number indicates the directory in the third-party FS where this > + node's file, symlink or directory is located. For upperdir, this number > + is 0, lowerdir1 is 1, lowerdir2 is 2, and so on. */ > + size_t num_dir; > + > + /* File type for `d_type' from struct dirent. */ > + int dirent_type; > +}; > + > +struct overlayfs > +{ > + char *upperdir; > + char **lowerdirs; > + volatile struct mapped_time_value *current_time; > + size_t lowerdirs_count; > + int index; > + int metacopy; > +}; > + > +extern struct overlayfs overlayfs; > + > +error_t create_node (struct node **node, struct node *dir, > + struct list_hardlinks *list_hardlinks, size_t num_dir, > + char *path, char *symlink_path, const char *name, > + struct stat stat); > +struct node *get_node (struct node *dir, const char *name); > + > +error_t init_unite_dir (void); > +void destroy_unite_dir (void); > +error_t merge_unite_dir (char *str, size_t num_dir); > + > +#ifdef DEBUG > +extern char *debug_file_name; > +extern FILE *debug_file; > +extern pthread_mutex_t debug_lock; > +# define debug(format, ...) \ > + do \ > + { \ > + if (debug_file) \ > + { \ > + pthread_mutex_lock (&debug_lock); \ > + fprintf (debug_file, format, ## __VA_ARGS__); \ > + pthread_mutex_unlock (&debug_lock); \ > + } \ > + } \ > + while (0) > +#else > +# define debug(format, ...) do {} while (0) > +#endif > + > +#endif /* OVERLAYFS_OVERLAYFS_H */ > diff --git a/overlayfs/rbtree.c b/overlayfs/rbtree.c > new file mode 100644 > index 00000000..7190ec0f > --- /dev/null > +++ b/overlayfs/rbtree.c > @@ -0,0 +1,331 @@ > +/* Implementation of a red-black tree. > + Copyright (C) 2026 Free Software Foundation > + > + This file is part of the GNU Hurd. > + > + The GNU Hurd is free software; you can redistribute it and/or > + modify it under the terms of the GNU General Public License as > + published by the Free Software Foundation; either version 2, or (at > + your option) any later version. > + > + The GNU Hurd is distributed in the hope that it will be useful, but > + WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ > + > +#include "rbtree.h" > + > +enum color > +{ > + RED, > + BLACK > +}; > + > +enum direction > +{ > + LEFT, > + RIGHT > +}; > + > +struct node_data > +{ > + struct list_hardlinks *list; > + dev_t device; > +}; > + > +struct leaf > +{ > + struct leaf *parent; > + struct leaf *left; > + struct leaf *right; > + struct node_data *node_data; > + size_t node_data_size; > + ino_t key; > + enum color color; > +}; > + > +struct leaf *root_leaf; > + > +static error_t > +set_data_for_leaf (struct leaf *parent, int is_left, ino_t inode, > + dev_t device, enum color color, > + struct list_hardlinks *list) > +{ > + struct leaf *leaf = malloc (sizeof (struct leaf)); > + if (!leaf) > + return errno; > + > + leaf->parent = parent; > + leaf->left = NULL; > + leaf->right = NULL; > + leaf->node_data = malloc (sizeof (struct node_data)); > + if (!leaf->node_data) > + { > + error_t err = errno; > + free (leaf); > + return err; > + } > + > + leaf->node_data[0].list = list; > + leaf->node_data[0].device = device; > + leaf->node_data_size = 1; > + leaf->key = inode; > + leaf->color = color; > + > + if (parent) > + { > + if (is_left) > + parent->left = leaf; > + else > + parent->right = leaf; > + } > + else > + root_leaf = leaf; > + > + return 0; > +} > + > +static void > +get_leaf_and_parent (struct leaf **leaf, struct leaf **parent, int *is_left, > + ino_t inode) > +{ > + if (!root_leaf) > + { > + *leaf = NULL; > + return; > + } > + > + struct leaf *return_leaf = root_leaf; > + while (1) > + { > + if (return_leaf->key == inode) > + { > + *leaf = return_leaf; > + *parent = return_leaf->parent; > + return; > + } > + else if (inode < return_leaf->key) > + { > + *is_left = 1; > + > + if (!return_leaf->left) > + { > + *leaf = return_leaf->left; > + *parent = return_leaf; > + return; > + } > + > + return_leaf = return_leaf->left; > + } > + else > + { > + *is_left = 0; > + > + if (!return_leaf->right) > + { > + *leaf = return_leaf->right; > + *parent = return_leaf; > + return; > + } > + > + return_leaf = return_leaf->right; > + } > + } > +} > + > +static struct leaf * > +get_grandparent (struct leaf *leaf) > +{ > + if (leaf && leaf->parent) > + return leaf->parent->parent; > + > + return NULL; > +} > + > +static struct leaf * > +get_uncle (struct leaf *leaf) > +{ > + struct leaf *grandparent = get_grandparent (leaf); > + if (!grandparent) > + return NULL; > + > + if (leaf->parent == grandparent->left) > + return grandparent->right; > + > + return grandparent->left; > +} > + > +static void > +rotate (struct leaf *input_leaf, enum direction direction) > +{ > + struct leaf *leaf; > + if (direction == LEFT) > + leaf = input_leaf->right; > + else > + leaf = input_leaf->left; > + > + leaf->parent = input_leaf->parent; > + if (input_leaf->parent) > + { > + if (input_leaf->parent->left == input_leaf) > + input_leaf->parent->left = leaf; > + else > + input_leaf->parent->right = leaf; > + } > + > + if (direction == LEFT) > + { > + input_leaf->right = leaf->left; > + if (leaf->left) > + leaf->left->parent = input_leaf; > + > + input_leaf->parent = leaf; > + leaf->left = input_leaf; > + } > + else > + { > + input_leaf->left = leaf->right; > + if (leaf->right) > + leaf->right->parent = input_leaf; > + > + input_leaf->parent = leaf; > + leaf->right = input_leaf; > + } > +} > + > +static void > +free_leaf (struct leaf *leaf) > +{ > + if (leaf->left) > + free_leaf (leaf->left); > + > + if (leaf->right) > + free_leaf (leaf->right); > + > + free (leaf->node_data); > + free (leaf); > +} > + > +void > +init_rbtree (void) > +{ > + root_leaf = NULL; > +} > + > +void > +free_rbtree (void) > +{ > + if (root_leaf) > + { > + free_leaf (root_leaf); > + root_leaf = NULL; > + } > +} > + > +error_t > +insert_item_to_rbtree (ino_t inode, dev_t device, > + struct list_hardlinks *list) > +{ > + if (!root_leaf) > + return set_data_for_leaf (NULL, 0, inode, device, BLACK, list); > + > + struct leaf *leaf; > + struct leaf *parent; > + int is_left; > + get_leaf_and_parent (&leaf, &parent, &is_left, inode); > + if (leaf) > + { > + struct node_data *new_node_data = > + realloc (leaf->node_data, (leaf->node_data_size + 1) > + * sizeof (struct node_data)); > + if (!new_node_data) > + return errno; > + > + leaf->node_data = new_node_data; > + leaf->node_data[leaf->node_data_size].list = list; > + leaf->node_data[leaf->node_data_size].device = device; > + ++leaf->node_data_size; > + return 0; > + } > + > + error_t err = set_data_for_leaf (parent, is_left, inode, device, RED, > list); > + if (err) > + return err; > + > + if (is_left) > + leaf = parent->left; > + else > + leaf = parent->right; > + > + do > + { > + if (!leaf->parent) > + { > + root_leaf = leaf; > + leaf->color = BLACK; > + break; > + } > + > + if (leaf->parent->color == BLACK) > + break; > + > + struct leaf *uncle_leaf = get_uncle (leaf); > + if (uncle_leaf && uncle_leaf->color == RED) > + { > + leaf->parent->color = BLACK; > + uncle_leaf->color = BLACK; > + leaf = get_grandparent (leaf); > + leaf->color = RED; > + continue; > + } > + > + struct leaf *grandparent_leaf = get_grandparent (leaf); > + if (leaf == leaf->parent->right > + && leaf->parent == grandparent_leaf->left) > + { > + rotate (leaf->parent, LEFT); > + leaf = leaf->left; > + grandparent_leaf = get_grandparent (leaf); > + } > + else if (leaf == leaf->parent->left > + && leaf->parent == grandparent_leaf->right) > + { > + rotate (leaf->parent, RIGHT); > + leaf = leaf->right; > + grandparent_leaf = get_grandparent (leaf); > + } > + > + leaf->parent->color = BLACK; > + grandparent_leaf->color = RED; > + if (leaf == leaf->parent->left > + && leaf->parent == grandparent_leaf->left) > + rotate (grandparent_leaf, RIGHT); > + else > + rotate (grandparent_leaf, LEFT); > + > + leaf = leaf->parent; > + } > + while (1); > + > + return 0; > +} > + > +struct list_hardlinks * > +get_item_from_rbtree (ino_t inode, dev_t device) > +{ > + struct leaf *leaf; > + struct leaf *parent; > + int is_left; > + get_leaf_and_parent (&leaf, &parent, &is_left, inode); > + if (!leaf) > + return NULL; > + > + for (size_t i = 0; i < leaf->node_data_size; ++i) > + if (leaf->node_data[i].device == device) > + return leaf->node_data[i].list; > + > + return NULL; > +} > diff --git a/overlayfs/rbtree.h b/overlayfs/rbtree.h > new file mode 100644 > index 00000000..2f866f37 > --- /dev/null > +++ b/overlayfs/rbtree.h > @@ -0,0 +1,30 @@ > +/* Definitions for a red-black tree. > + Copyright (C) 2026 Free Software Foundation > + > + This file is part of the GNU Hurd. > + > + The GNU Hurd is free software; you can redistribute it and/or > + modify it under the terms of the GNU General Public License as > + published by the Free Software Foundation; either version 2, or (at > + your option) any later version. > + > + The GNU Hurd is distributed in the hope that it will be useful, but > + WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ > + > +#ifndef RED_BLACK_TREE_H > +#define RED_BLACK_TREE_H > + > +#include "overlayfs.h" > + > +void init_rbtree (void); > +void free_rbtree (void); > +error_t insert_item_to_rbtree (ino_t inode, dev_t device, > + struct list_hardlinks *list); > +struct list_hardlinks *get_item_from_rbtree (ino_t inode, dev_t device); > + > +#endif /* RED_BLACK_TREE_H */ > diff --git a/overlayfs/unite.c b/overlayfs/unite.c > new file mode 100644 > index 00000000..17ca7fe6 > --- /dev/null > +++ b/overlayfs/unite.c > @@ -0,0 +1,453 @@ > +/* Reading and merging directories. > + Copyright (C) 2026 Free Software Foundation > + > + This file is part of the GNU Hurd. > + > + The GNU Hurd is free software; you can redistribute it and/or > + modify it under the terms of the GNU General Public License as > + published by the Free Software Foundation; either version 2, or (at > + your option) any later version. > + > + The GNU Hurd is distributed in the hope that it will be useful, but > + WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ > + > +#include <dirent.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <unistd.h> > +#include <sys/stat.h> > + > +#include "overlayfs.h" > +#include "rbtree.h" > + > +#define NUM_PTHREADS 4 > +#define MAX_SYMLINK_PATH 512 > + > +struct pool > +{ > + pthread_cond_t cond; > + pthread_cond_t finish_cond; > + pthread_mutex_t lock; > + pthread_t *pthreads; > + size_t running; > + int stop; > +}; > + > +struct patch_string > +{ > + char *str; > + size_t size; > + size_t last_dir_size; > + size_t max_size; > +}; > + > +DIR **dirs; > +struct node **dirs_nodes; > +struct patch_string *scanned_paths; > +size_t capacity; > +size_t size; > +size_t current; > +error_t global_err; > + > +size_t m_num_dir; > + > +struct pool *pool; > + > +static inline error_t > +copy_str (struct patch_string *path, const char *str) > +{ > + size_t len = strlen (str); > + if (path->max_size < len) > + { > + char *new_str = realloc (path->str, len + 1); > + if (!new_str) > + return errno; > + > + path->str = new_str; > + path->max_size = len; > + } > + > + strcpy (path->str, str); > + path->size = len; > + path->last_dir_size = len; > + return 0; > +} > + > +static inline error_t > +copy_path (struct patch_string *path1, const struct patch_string *path2) > +{ > + if (path1->max_size < path2->size) > + { > + char *new_str = realloc (path1->str, path2->size + 1); > + if (!new_str) > + return errno; > + > + path1->str = new_str; > + path1->max_size = path2->size; > + } > + > + strcpy (path1->str, path2->str); > + path1->size = path2->size; > + path1->last_dir_size = path2->size; > + return 0; > +} > + > +static inline error_t > +concat_str (struct patch_string *path, const char *str) > +{ > + size_t len = path->last_dir_size + strlen (str) + 1; /* for / */ > + if (path->max_size < len) > + { > + char *new_str = realloc (path->str, len + 1); > + if (!new_str) > + return errno; > + > + path->str = new_str; > + path->max_size = len; > + } > + > + strcpy (&path->str[path->last_dir_size], "/"); > + strcat (path->str, str); > + path->size = len; > + return 0; > +} > + > +static void > +read_dir (size_t pos) > +{ > + char symlink_buf[MAX_SYMLINK_PATH]; > + struct dirent *dirent; > + error_t err; > + while (1) > + { > + dirent = readdir (dirs[pos]); > + if (!dirent) > + { > + closedir (dirs[pos]); > + > + dirs_nodes[pos]->nn_stat.st_size = 0; > + dirs_nodes[pos]->nn_stat.st_size += DIRENT_LEN (strlen (".")); > + dirs_nodes[pos]->nn_stat.st_size += DIRENT_LEN (strlen ("..")); > + > + for (size_t i = 0; i < dirs_nodes[pos]->nn->entries_size; ++i) > + dirs_nodes[pos]->nn_stat.st_size += > + DIRENT_LEN (strlen > (dirs_nodes[pos]->nn->entries[i]->nn->name)); > + > + return; > + } > + > + if (strcmp (dirent->d_name, ".") == 0 > + || strcmp (dirent->d_name, "..") == 0) > + continue; > + > + err = concat_str (&scanned_paths[pos], dirent->d_name); > + if (err) > + goto break_return; > + > + struct node *node = get_node (dirs_nodes[pos], dirent->d_name); > + if (!node) > + { > + struct stat st; > + err = lstat (scanned_paths[pos].str, &st); > + if (err) > + { > + err = errno; > + goto break_return; > + } > + > + char *symlink_path = NULL; > + if (S_ISLNK (st.st_mode)) > + { > + ssize_t size = readlink (scanned_paths[pos].str, symlink_buf, > + MAX_SYMLINK_PATH); > + if (size == -1) > + goto break_unlock; > + > + symlink_buf[size] = '\0'; > + symlink_path = symlink_buf; > + } > + > + pthread_mutex_lock (&pool->lock); > + if (overlayfs.index && !S_ISDIR (st.st_mode)) > + { > + struct list_hardlinks *list = get_item_from_rbtree (st.st_ino, > + st.st_dev); > + > + err = create_node (&node, dirs_nodes[pos], list, m_num_dir, > + scanned_paths[pos].str, symlink_path, > + dirent->d_name, st); > + if (err) > + goto break_unlock; > + > + if (!list) > + err = insert_item_to_rbtree (st.st_ino, st.st_dev, > + node->nn->list_hardlinks); > + } > + else > + err = create_node (&node, dirs_nodes[pos], NULL, m_num_dir, > + scanned_paths[pos].str, symlink_path, > + dirent->d_name, st); > + > + pthread_mutex_unlock (&pool->lock); > + if (err) > + goto break_return; > + } > + > + if (S_ISDIR (node->nn_stat.st_mode)) > + { > + pthread_mutex_lock (&pool->lock); > + DIR *new_dir = opendir (scanned_paths[pos].str); > + if (!new_dir) > + { > + err = errno; > + goto break_unlock; > + } > + > + if (size == capacity) > + { > + size_t len = size + 1; > + > + DIR **new_dirs = realloc (dirs, len * sizeof (DIR *)); > + if (!new_dirs) > + { > + err = errno; > + closedir (new_dir); > + goto break_unlock; > + } > + > + dirs = new_dirs; > + > + struct node **new_dirs_nodes = > + realloc (dirs_nodes, len * sizeof (struct node *)); > + if (!new_dirs_nodes) > + { > + err = errno; > + closedir (new_dir); > + goto break_unlock; > + } > + > + dirs_nodes = new_dirs_nodes; > + > + struct patch_string *new_scanned_paths = > + realloc (scanned_paths, len * sizeof (struct patch_string)); > + if (!new_scanned_paths) > + { > + err = errno; > + closedir (new_dir); > + goto break_unlock; > + } > + > + scanned_paths = new_scanned_paths; > + scanned_paths[size].str = NULL; > + scanned_paths[size].max_size = 0; > + > + ++capacity; > + } > + > + dirs[size] = new_dir; > + dirs_nodes[size] = node; > + err = copy_path (&scanned_paths[size], &scanned_paths[pos]); > + if (err) > + { > + closedir (new_dir); > + goto break_unlock; > + } > + > + ++size; > + pthread_cond_signal (&pool->cond); > + pthread_mutex_unlock (&pool->lock); > + } > + } > + > + break_unlock: > + pthread_mutex_unlock (&pool->lock); > + > + break_return: > + closedir (dirs[pos]); > + global_err = err; > +} > + > +static void * > +thread_loop (void *arg) > +{ > + struct pool *pool = (struct pool *) arg; > + size_t pos; > + > + while (1) > + { > + pthread_mutex_lock (&pool->lock); > + while (!pool->stop && current == size) > + pthread_cond_wait (&pool->cond, &pool->lock); > + > + if (pool->stop && current == size) > + { > + pthread_mutex_unlock (&pool->lock); > + break; > + } > + > + pos = current; > + ++current; > + ++pool->running; > + pthread_mutex_unlock (&pool->lock); > + > + read_dir (pos); > + > + pthread_mutex_lock (&pool->lock); > + --pool->running; > + if (pool->running == 0 && current == size) > + pthread_cond_signal (&pool->finish_cond); > + > + pthread_mutex_unlock(&pool->lock); > + } > + > + return NULL; > +} > + > +error_t > +init_unite_dir (void) > +{ > + init_rbtree (); > + > + error_t err; > + pool = malloc (sizeof (struct pool)); > + if (!pool) > + { > + err = errno; > + goto init_update_dir_exit; > + } > + > + pool->pthreads = malloc (NUM_PTHREADS * sizeof (pthread_t)); > + if (!pool->pthreads) > + { > + err = errno; > + goto free_pool; > + } > + > + scanned_paths = malloc (sizeof (struct patch_string)); > + if (!scanned_paths) > + { > + err = errno; > + goto free_pool_pthreads; > + } > + > + scanned_paths[0].str = NULL; > + scanned_paths[0].max_size = 0; > + > + dirs = malloc (sizeof (DIR *)); > + if (!dirs) > + { > + err = errno; > + goto free_scanned_paths; > + } > + > + dirs_nodes = malloc (sizeof (struct node *)); > + if (!dirs_nodes) > + { > + err = errno; > + goto free_dirs; > + } > + > + dirs_nodes[0] = netfs_root_node; > + > + pthread_cond_init (&pool->cond, NULL); > + pthread_cond_init (&pool->finish_cond, NULL); > + pthread_mutex_init (&pool->lock, NULL); > + pool->running = 0; > + pool->stop = 0; > + > + current = 0; > + size = 0; > + capacity = 1; > + global_err = 0; > + > + for (size_t i = 0; i < NUM_PTHREADS; ++i) > + { > + err = pthread_create (&pool->pthreads[i], NULL, thread_loop, pool); > + if (err) > + goto free_dirs_nodes; > + } > + > + return 0; > + > + free_dirs_nodes: > + free (dirs_nodes); > + > + free_dirs: > + free (dirs); > + > + free_scanned_paths: > + free (scanned_paths); > + > + free_pool_pthreads: > + free (pool->pthreads); > + > + free_pool: > + free (pool); > + > + init_update_dir_exit: > + return err; > +} > + > +void > +destroy_unite_dir (void) > +{ > + pthread_mutex_lock (&pool->lock); > + pool->stop = 1; > + pthread_cond_broadcast (&pool->cond); > + pthread_mutex_unlock (&pool->lock); > + > + for (size_t i = 0; i < NUM_PTHREADS; ++i) > + pthread_join (pool->pthreads[i], NULL); > + > + free (pool->pthreads); > + > + pthread_cond_destroy (&pool->cond); > + pthread_cond_destroy (&pool->finish_cond); > + pthread_mutex_destroy (&pool->lock); > + > + free (pool); > + > + free_rbtree (); > + > + for (size_t i = 0; i < capacity; ++i) > + free (scanned_paths[i].str); > + > + free (scanned_paths); > + free (dirs_nodes); > + free (dirs); > +} > + > +error_t > +merge_unite_dir (char *str, size_t num_dir) > +{ > + error_t err = copy_str (&scanned_paths[0], str); > + if (err) > + return err; > + > + dirs[0] = opendir (str); > + if (!dirs[0]) > + return errno; > + > + global_err = 0; > + current = 0; > + size = 1; > + > + m_num_dir = num_dir; > + > + pthread_mutex_lock (&pool->lock); > + pthread_cond_signal (&pool->cond); > + pthread_mutex_unlock (&pool->lock); > + > + pthread_mutex_lock (&pool->lock); > + while (!(current == size && pool->running == 0)) > + pthread_cond_wait (&pool->finish_cond, &pool->lock); > + > + pthread_mutex_unlock (&pool->lock); > + return global_err; > +} > -- > 2.43.0 > -- Samuel Pour un père, autant mourir que de faire plein de calculs et pas s'occuper de son fils -+- y sur #ens-mim - sombres histoires de zombies -+-
