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 -+-

Reply via email to