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
