Hello everyone!
I've expanded the functionality of the partfs translator to work with multiple disks and their partitions. Thus, by running the command: settrans -c partfs /hurd/partfs /root/disk1.img /root/disk2.img /root/disk3.img
The translator directory will have the following directory tree:
partfs
├── 0
│ ├── 1
│ ├── 2
│ └── ...
├── 1
│ ├── 1
│ ├── 2
│ └── ...
├── 2
│ ├── 1
│ ├── 2
│ └── ...
Since the disks are directories, the cd and ls commands work in the translator node.
I also tested mounting, reading, and writing using the commands:
settrans -c ext01 /hurd/ext2fs -w -T typed file:/root/partfs/0/1
and
settrans -c ext1_1 /hurd/ext2fs -w -T typed part:1:file:/root/partfs/1
In all cases, everything worked correctly, although there may be issues that I don't yet understand, so any advice or comments are welcome!

--
Mikhail Karpov
From cfb156a861625d7e6543c49dc9f50c26a061b0bb Mon Sep 17 00:00:00 2001
From: Mikhail Karpov <[email protected]>
Date: Thu, 21 May 2026 14:23:51 +0700
Subject: [PATCH] Adding a partfs translator

---
 Makefile         |   4 +
 partfs/Makefile  |  30 +++
 partfs/netfs.c   | 683 +++++++++++++++++++++++++++++++++++++++++++++++
 partfs/options.c |  95 +++++++
 partfs/options.h |  42 +++
 partfs/partfs.c  | 350 ++++++++++++++++++++++++
 partfs/partfs.h  |  67 +++++
 7 files changed, 1271 insertions(+)
 create mode 100644 partfs/Makefile
 create mode 100644 partfs/netfs.c
 create mode 100644 partfs/options.c
 create mode 100644 partfs/options.h
 create mode 100644 partfs/partfs.c
 create mode 100644 partfs/partfs.h

diff --git a/Makefile b/Makefile
index c51e8c1c..3a1707e7 100644
--- a/Makefile
+++ b/Makefile
@@ -70,6 +70,10 @@ ifeq ($(HAVE_LIBACPICA),yes)
 prog-subdirs += acpi
 endif
 
+ifneq ($(PARTED_LIBS),)
+prog-subdirs += partfs
+endif
+
 # Other directories
 other-subdirs = hurd doc config release include
 
diff --git a/partfs/Makefile b/partfs/Makefile
new file mode 100644
index 00000000..2ca1dfe8
--- /dev/null
+++ b/partfs/Makefile
@@ -0,0 +1,30 @@
+#   Copyright (C) 2026 Free Software Foundation
+#   Written by Mikhail Karpov.
+#
+#   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 := partfs
+makemode := server
+target = partfs
+
+#CFLAGS += -DDEBUG
+SRCS = netfs.c options.c partfs.c
+
+OBJS = $(SRCS:.c=.o)
+HURDLIBS = fshelp iohelp netfs ports shouldbeinlibc store
+LDLIBS = -lparted -lpthread
+
+include ../Makeconf
diff --git a/partfs/netfs.c b/partfs/netfs.c
new file mode 100644
index 00000000..66a30129
--- /dev/null
+++ b/partfs/netfs.c
@@ -0,0 +1,683 @@
+/* Copyright (C) 2026 Free Software Foundation
+   Written by Mikhail Karpov.
+
+   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 <argp.h>
+#include <dirent.h>
+#include <pthread.h>
+#include <sys/mman.h>
+
+#include "partfs.h"
+
+error_t
+netfs_validate_stat (struct node *np, struct iouser *cred)
+{
+  return 0;
+}
+
+error_t
+netfs_attempt_chown (struct iouser *cred, struct node *np, uid_t uid,
+                     uid_t gid)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_chauthor (struct iouser *cred, struct node *np, uid_t author)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_chmod (struct iouser *cred, struct node *np, mode_t mode)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_mksymlink (struct iouser *cred, struct node *np,
+                         const char *name)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_mkdev (struct iouser *cred, struct node *np, mode_t type,
+                     dev_t indexes)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_chflags (struct iouser *cred, struct node *np, int flags)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_utimes (struct iouser *cred, struct node *np,
+                      struct timespec *atime, struct timespec *mtime)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_set_size (struct iouser *cred, struct node *np, loff_t size)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_statfs (struct iouser *cred, struct node *np,
+                      fsys_statfsbuf_t *st)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_sync (struct iouser *cred, struct node *np, int wait)
+{
+  return 0;
+}
+
+error_t
+netfs_attempt_syncfs (struct iouser *cred, int wait)
+{
+  return 0;
+}
+
+error_t
+netfs_attempt_lookup (struct iouser *user, struct node *dir,
+                      const char *name, struct node **np)
+{
+  debug ("netfs_attempt_lookup (user: %p, dir: %p, name: %s, np: %p)\n",
+         user, dir, name, np);
+  pthread_mutex_unlock (&dir->lock);
+
+  if (!dir->nn->entries)
+    {
+      debug ("!dir->nn->entries\n");
+      *np = NULL;
+      debug ("netfs_attempt_lookup end with ENOTDIR\n");
+      return ENOTDIR;
+    }
+
+  if (*name == '\0' || strcmp (name, ".") == 0)
+    {
+      debug ("*name == '\\0' || strcmp (name, \".\") == 0\n");
+      *np = dir;
+      pthread_mutex_lock (&dir->lock);
+      netfs_nref (*np);
+      pthread_mutex_unlock (&dir->lock);
+      debug ("netfs_attempt_lookup end with 0\n");
+      return 0;
+    }
+  else if (strcmp (name, "..") == 0)
+    {
+      debug ("strcmp (name, \"..\") == 0\n");
+      *np = netfs_root_node;
+      pthread_mutex_lock (&dir->lock);
+      netfs_nref (*np);
+      pthread_mutex_unlock (&dir->lock);
+      debug ("netfs_attempt_lookup end with 0\n");
+      return 0;
+    }
+
+  struct node *current_node = NULL;
+  for (size_t i = 0; i < dir->nn->entries_size; ++i)
+    {
+      current_node = dir->nn->entries[i];
+
+      if (strcmp (name, current_node->nn->name) == 0)
+        break;
+    }
+
+  if (current_node)
+    {
+      pthread_mutex_lock (&dir->lock);
+      *np = current_node;
+      netfs_nref (*np);
+      pthread_mutex_unlock (&dir->lock);
+      debug ("current_node->nn->name: %s\n", current_node->nn->name);
+      debug ("netfs_attempt_lookup end with 0\n");
+      return 0;
+    }
+
+  *np = NULL;
+  debug ("netfs_attempt_lookup end with ENOENT\n");
+  return ENOENT;
+}
+
+error_t
+netfs_attempt_unlink (struct iouser *user, struct node *dir, const char *name)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_rename (struct iouser *user, struct node *fromdir,
+                      const char *fromname, struct node *todir,
+                      const char *toname, int excl)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_mkdir (struct iouser *user, struct node *dir, const char *name,
+                     mode_t mode)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_rmdir (struct iouser *user, struct node *dir, const char *name)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_link (struct iouser *user, struct node *dir, struct node *file,
+                    const char *name, int excl)
+{
+  return EOPNOTSUPP;
+}
+
+/* We don't use this function, but we need to unlock the dir.  */
+error_t
+netfs_attempt_mkfile (struct iouser *user, struct node *dir, mode_t mode,
+                      struct node **np)
+{
+  pthread_mutex_unlock (&dir->lock);
+  return EOPNOTSUPP;
+}
+
+/* We don't use this function, but we need to unlock the dir.  */
+error_t
+netfs_attempt_create_file (struct iouser *user, struct node *dir,
+                           const char *name, mode_t mode, struct node **np)
+{
+  pthread_mutex_unlock (&dir->lock);
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_readlink (struct iouser *user, struct node *np, char *buf)
+{
+  return EOPNOTSUPP;
+}
+
+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;
+}
+
+/* We don't use this function, but it has to be defined.  */
+error_t
+netfs_attempt_read (struct iouser *cred, struct node *np, loff_t offset,
+                    size_t *len, void *data)
+{
+  return EOPNOTSUPP;
+}
+
+/* We don't use this function, but it has to be defined.  */
+error_t
+netfs_attempt_write (struct iouser *cred, struct node *np, loff_t offset,
+                     size_t *len, const void *data)
+{
+  return EOPNOTSUPP;
+}
+
+error_t
+netfs_report_access (struct iouser *cred, struct node *np, int *types)
+{
+  return EOPNOTSUPP;
+}
+
+struct iouser *
+netfs_make_user (uid_t *uids, int nuids, uid_t *gids, int ngids)
+{
+  return NULL;
+}
+
+void
+netfs_node_norefs (struct node *np)
+{
+  return;
+}
+
+/* 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))
+
+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;
+}
+
+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, entry: %d, nentries: %d, "
+         "datacnt: %u, bufsize: %u, amt: %d)\n", cred, dir, entry, nentries,
+         *datacnt, bufsize, *amt);
+
+  if (!dir->nn->entries)
+    {
+      debug ("!dir->nn->entries\n");
+      debug ("netfs_attempt_lookup end with ENOTDIR\n");
+      return ENOTDIR;
+    }
+
+  if (dir->nn->entries_size + 2 <= entry)
+    {
+      debug ("dir->nn->entries_size + 2 <= entry\n");
+      *datacnt = 0;
+      *amt = 0;
+      *data = NULL;
+      debug ("netfs_get_dirents end with 0\n");
+      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];
+      bump_size (&size, &count, current_node->nn->name, nentries, bufsize);
+    }
+
+  *data = mmap (0, size, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0);
+
+  if ((void *) *data == (void *) -1)
+    return errno;
+
+  *datacnt = size;
+  *amt = count;
+
+  count = 0;
+  char *ptr_data = *data;
+
+  if (entry == 0)
+    {
+      debug ("entry == 0\n");
+      add_dir_entry (&ptr_data, ".", dir->nn_stat.st_ino, DT_DIR, &count,
+                     nentries, &size);
+    }
+  if (entry <= 1)
+    {
+      debug ("entry <= 1\n");
+      add_dir_entry (&ptr_data, "..", 2, DT_DIR, &count, nentries, &size);
+    }
+
+  debug ("Fill in the real directory entries\n");
+  int dirent_type;
+  if (dir->nn->entries_size > 0)
+    dirent_type = DT_DIR;
+  else
+    if (dir->nn->store->block_size == 1)
+      dirent_type = DT_CHR;
+    else
+      dirent_type = DT_BLK;
+
+  for (size_t i = 0; i < dir->nn->entries_size; ++i)
+    {
+      current_node = dir->nn->entries[i];
+      add_dir_entry (&ptr_data, current_node->nn->name,
+                     current_node->nn_stat.st_ino, dirent_type, &count,
+                     nentries, &size);
+    }
+
+  debug ("netfs_get_dirents end with 0\n");
+  return 0;
+}
+
+static inline error_t
+check_offset_and_len (loff_t offset, store_offset_t store_size, size_t *len)
+{
+  if (offset < 0 || offset > store_size)
+    return EINVAL;
+
+  if (offset + *len > store_size)
+    *len = store_size - offset;
+
+  return 0;
+}
+
+static error_t
+attempt_read (struct netnode *netnode, loff_t offset, size_t *len,
+              vm_size_t *amount, void **data)
+{
+  debug ("attempt_read (netnode: %p, offset: %lld, len: %zu, amount: %zu)\n",
+         netnode, offset, *len, *amount);
+
+  struct store *store = netnode->store;
+
+  if (store->size > 0 && offset == store->size)
+    {
+      debug ("store->size > 0 && offset == store->size\n");
+      *len = 0;
+      debug ("attempt_read end with 0\n");
+      return 0;
+    }
+
+  error_t err = check_offset_and_len (offset, store->size, len);
+  if (err)
+    {
+      debug ("err in check_offset_and_len\n");
+      debug ("attempt_read end with err: %d\n", err);
+      return err;
+    }
+
+  off_t addr = offset >> store->log2_block_size;
+  pthread_rwlock_rdlock (&netnode->io_lock);
+  err = store_read (store, addr, *len, data, amount);
+  pthread_rwlock_unlock (&netnode->io_lock);
+
+  debug ("attempt_read end with err: %d\n", err);
+  return err;
+}
+
+kern_return_t
+netfs_S_io_read (struct protid *user, data_t *data,
+                 mach_msg_type_number_t *datalen, off_t offset,
+                 vm_size_t amount)
+{
+  debug ("netfs_S_io_read:\n");
+
+  if (!user)
+    {
+      debug ("!user\n");
+      debug ("netfs_S_io_read end with EOPNOTSUPP\n");
+      return EOPNOTSUPP;
+    }
+
+  struct node *node = user->po->np;
+  pthread_mutex_lock (&user->po->np->lock);
+
+  if ((user->po->openstat & O_READ) == 0)
+    {
+      debug ("(user->po->openstat & O_READ) == 0\n");
+      pthread_mutex_unlock (&node->lock);
+      debug ("netfs_S_io_read end with EBADF");
+      return EBADF;
+    }
+
+  int alloced = 0;
+  size_t data_size = *datalen;
+  if (amount > data_size)
+    {
+      alloced = 1;
+      *data = mmap (0, amount, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0);
+    }
+  data_size = amount;
+
+  off_t start = (offset == -1 ? user->po->filepointer : offset);
+  error_t err;
+  err = attempt_read (node->nn, start, &data_size, &amount, (void **)data);
+
+  if (offset == -1 && !err)
+    user->po->filepointer += data_size;
+
+  pthread_mutex_unlock (&node->lock);
+
+  if (err && alloced)
+    munmap (*data, amount);
+
+  if (!err && alloced && (round_page (data_size) < round_page (amount)))
+    munmap (*data + round_page (data_size),
+            round_page (amount) - round_page (data_size));
+
+  *datalen = data_size;
+  debug ("netfs_S_io_read end with err: %d\n", err);
+  return err;
+}
+
+static error_t
+attempt_write (struct netnode *netnode, loff_t offset, size_t len,
+               vm_size_t *amount, const void *data)
+{
+  debug ("attempt_write (netnode: %p, offset: %lld, len: %zu amount: %zu):\n",
+         netnode, offset, len, *amount);
+
+  struct store *store = netnode->store;
+
+  error_t err = check_offset_and_len (offset, store->size, &len);
+  if (err)
+    {
+      debug ("err in check_offset_and_len\n");
+      debug ("attempt_write end with err: %d\n", err);
+      return err;
+    }
+
+  off_t addr = offset >> store->log2_block_size;
+  pthread_rwlock_rdlock (&netnode->io_lock);
+  err = store_write (store, addr, data, len, amount);
+  pthread_rwlock_unlock (&netnode->io_lock);
+
+  debug ("attempt_write end with err: %d\n", err);
+  return err;
+}
+
+kern_return_t
+netfs_S_io_write (struct protid *user, const_data_t data,
+                  mach_msg_type_number_t datalen, off_t offset,
+                  vm_size_t *amount)
+{
+  debug ("netfs_S_io_write:\n");
+
+  if (!user)
+    {
+      debug ("!user\n");
+      debug ("netfs_S_io_write end with EOPNOTSUPP\n");
+      return EOPNOTSUPP;
+    }
+
+  if ((user->po->openstat & O_WRITE) == 0)
+    {
+      debug ("(user->po->openstat & O_WRITE) == 0\n");
+      debug ("netfs_S_io_write end with EBADF\n");
+      return EBADF;
+    }
+
+  *amount = datalen;
+
+  struct node *np = user->po->np;
+  pthread_mutex_lock (&np->lock);
+
+  off_t start = offset;
+  error_t err;
+  if (start == -1)
+    {
+      if (user->po->openstat & O_APPEND)
+        {
+          err = netfs_validate_stat (np, user->user);
+          if (err)
+            {
+              pthread_mutex_unlock (&np->lock);
+              debug ("err in netfs_validate_stat\n");
+              debug ("netfs_S_io_write end with err: %d\n", err);
+              return err;
+            }
+          user->po->filepointer = np->nn_stat.st_size;
+        }
+      start = user->po->filepointer;
+    }
+
+  err = attempt_write (np->nn, start, datalen, amount, (const void *)data);
+  if (offset == -1 && !err)
+    user->po->filepointer += *amount;
+  pthread_mutex_unlock (&np->lock);
+
+  debug ("netfs_S_io_write end with err: %d\n", err);
+  return err;
+}
+
+static inline int
+is_privileged (const struct idvec *uids)
+{
+  return idvec_contains (uids, 0) || idvec_contains (uids, getuid ());
+}
+
+error_t
+netfs_file_get_storage_info (struct iouser *cred, struct node *np,
+                             mach_port_t **ports,
+                             mach_msg_type_name_t *ports_type,
+                             mach_msg_type_number_t *num_ports,
+                             int **ints,
+                             mach_msg_type_number_t *num_ints,
+                             off_t **offsets,
+                             mach_msg_type_number_t *num_offsets,
+                             char **data,
+                             mach_msg_type_number_t *data_len)
+{
+  debug ("netfs_file_get_storage_info:\n");
+  *ports_type = MACH_MSG_TYPE_COPY_SEND;
+
+  if (!cred)
+    {
+      debug ("!cred\n");
+      debug ("netfs_file_get_storage_info end with EOPNOTSUPP\n");
+      return EOPNOTSUPP;
+    }
+
+  struct store *store = np->nn->store;
+  if (partfs.enforced && !(store->flags & STORE_ENFORCED))
+    {
+      debug ("partfs.enforced && !(store->flags & STORE_ENFORCED)\n");
+      size_t name_len = (store->name ? strlen (store->name) + 1 : 0);
+      *num_ports = 0;
+      int i = 0;
+      (*ints)[i++] = STORAGE_OTHER;
+      (*ints)[i++] = store->flags;
+      (*ints)[i++] = store->block_size;
+      (*ints)[i++] = 1;
+      (*ints)[i++] = name_len;
+      (*ints)[i++] = 0;
+      *num_ints = i;
+      i = 0;
+      (*offsets)[i++] = 0;
+      (*offsets)[i++] = store->size;
+      *num_offsets = i;
+      if (store->name)
+        memcpy (*data, store->name, name_len);
+      *data_len = name_len;
+      debug ("netfs_file_get_storage_info end with 0\n");
+      return 0;
+    }
+
+  error_t err;
+  if (!is_privileged (cred->uids)
+      && !store_is_securely_returnable (store, np->nn_stat.st_mode))
+    {
+      debug ("!is_privileged (cred->uids)...\n");
+      struct store *clone;
+      err = store_clone (store, &clone);
+      if (err)
+        {
+          debug ("err in store_clone\n");
+          debug ("netfs_file_get_storage_info end with err: %d\n", err);
+          return err;
+        }
+
+      err = store_set_flags (clone, STORE_INACTIVE);
+      if (err == EINVAL)
+        err = EACCES;
+      else
+        err = store_return (clone, ports, num_ports, ints, num_ints,
+                            offsets, num_offsets, data, data_len);
+
+      store_free (clone);
+    }
+  else
+    err = store_return (store, ports, num_ports, ints, num_ints,
+                        offsets, num_offsets, data, data_len);
+
+  debug ("netfs_file_get_storage_info end with err: %d\n", err);
+  return err;
+}
diff --git a/partfs/options.c b/partfs/options.c
new file mode 100644
index 00000000..adc780cd
--- /dev/null
+++ b/partfs/options.c
@@ -0,0 +1,95 @@
+/* Copyright (C) 2026 Free Software Foundation
+   Written by Mikhail Karpov.
+
+   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 <string.h>
+#include <unistd.h>
+
+#include "options.h"
+
+const struct argp_option options[] =
+{
+  {"readonly",   'r',      0, 0, "Disallow writing."},
+  {"writable",   'w',      0, 0, "Allow writing (default)."},
+  {"enforced",   'e',      0, 0, "Never reveal underlying devices, even to"
+    " root."},
+  {"no-file-io", 'F',      0, 0, "Never perform io via plain file io RPCs."},
+#ifdef DEBUG
+  {"debug",      'd', "FILE", 0, "Enable debug and write debug statements to"
+    " FILE.  The FILE must be located outside the translator directory."},
+#endif
+  {0}
+};
+
+error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+  struct arguments *arguments = state->input;
+
+  switch (key)
+    {
+    case 'r': arguments->readonly = 1; break;
+    case 'w': arguments->readonly = 0; break;
+    case 'e': arguments->enforced = 1; break;
+    case 'F': arguments->no_fileio = 1; break;
+#ifdef DEBUG
+    case 'd': arguments->debug_file_name = arg; break;
+#endif
+    case ARGP_KEY_ARG:
+      if (arguments->device_names_count == 0)
+        {
+          arguments->device_names = malloc (sizeof (char *));
+          if (arguments->device_names == NULL)
+            error (1, 0, "Not enough memory to allocate for hd name");
+        }
+      else
+        {
+          size_t len = (arguments->device_names_count + 1) * sizeof (char *);
+          char **tmp = realloc (arguments->device_names, len);
+          if (tmp == NULL)
+            error (1, 0, "Not enough memory to allocate for hd name");
+
+          arguments->device_names = tmp;
+        }
+
+      arguments->device_names[arguments->device_names_count] = arg;
+      ++arguments->device_names_count;
+      break;
+    case ARGP_KEY_END:
+      if (!arguments->device_names)
+        error (1, 0, "No disk image file specified\n");
+      break;
+    case ARGP_KEY_INIT:
+      arguments->readonly = 0;
+      arguments->enforced = 0;
+      arguments->no_fileio = 0;
+#ifdef DEBUG
+      arguments->debug_file_name = NULL;
+#endif
+      arguments->device_names = NULL;
+      arguments->device_names_count = 0;
+      break;
+    case ARGP_KEY_SUCCESS:
+    case ARGP_KEY_ERROR:
+      break;
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+
+  return 0;
+}
diff --git a/partfs/options.h b/partfs/options.h
new file mode 100644
index 00000000..05058727
--- /dev/null
+++ b/partfs/options.h
@@ -0,0 +1,42 @@
+/* Copyright (C) 2026 Free Software Foundation
+   Written by Mikhail Karpov.
+
+   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 PARTFS_OPTIONS_H
+#define PARTFS_OPTIONS_H
+
+#include <argp.h>
+#include <stdlib.h>
+
+struct arguments
+{
+#ifdef DEBUG
+  char *debug_file_name;
+#endif
+  char **device_names;
+  size_t device_names_count;
+
+  int readonly;
+  int enforced;
+  int no_fileio;
+};
+
+extern const struct argp_option options[];
+
+error_t parse_opt (int key, char *arg, struct argp_state *state);
+
+#endif /* PARTFS_OPTIONS_H */
diff --git a/partfs/partfs.c b/partfs/partfs.c
new file mode 100644
index 00000000..01531f05
--- /dev/null
+++ b/partfs/partfs.c
@@ -0,0 +1,350 @@
+/* Copyright (C) 2026 Free Software Foundation
+   Written by Mikhail Karpov.
+
+   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 <fcntl.h>
+#include <parted/parted.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <version.h>
+
+#include "options.h"
+#include "partfs.h"
+
+char *netfs_server_name = "partfs";
+char *netfs_server_version = HURD_VERSION;
+int netfs_maxsymlinks = 0; /* arbitrary */
+
+const char *argp_program_version = STANDARD_HURD_VERSION (partfs);
+
+struct partfs partfs;
+
+#ifdef DEBUG
+FILE *debug_file = NULL;
+pthread_mutex_t debug_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static void
+print_node_info (const char *node_name, const struct node *node)
+{
+  debug ("%s: %p, name: %s, store: %p, entries: %p,"
+         " entries_size: %zu\n", node_name, node, node->nn->name,
+         node->nn->store, node->nn->entries, node->nn->entries_size);
+
+  if (node->nn->entries_size > 0)
+    {
+      for (size_t i = 0; i < node->nn->entries_size; ++i)
+        debug ("entries[%zu]: %p ", i, node->nn->entries[i]);
+      debug ("\n");
+    }
+}
+
+static void
+print_partfs_node_info (void)
+{
+  debug ("\n------------partfs node info------------\n");
+  print_node_info ("netfs_root_node", netfs_root_node);
+
+  char node_name[128];
+  struct node *device, *part;
+  for (size_t i = 0; i < netfs_root_node->nn->entries_size; ++i)
+    {
+      debug ("\n");
+      snprintf (node_name, sizeof (node_name), "device%zu", i);
+      device = netfs_root_node->nn->entries[i];
+      print_node_info (node_name, device);
+
+      for (size_t j = 0; j < device->nn->entries_size; ++j)
+        {
+          snprintf (node_name, sizeof (node_name), "part%zu", j + 1);
+          part = device->nn->entries[j];
+          print_node_info (node_name, part);
+        }
+    }
+  debug ("\n");
+}
+#endif /* DEBUG */
+
+static error_t
+set_last_partition_num (const char *device_name, size_t *last_partition_num)
+{
+  ped_exception_fetch_all ();
+  PedDevice *device = ped_device_get (device_name);
+  if (!device || !ped_device_open (device))
+    {
+      debug ("!device || !ped_device_open (device)\n");
+      debug ("set_last_partition_num end with err: 1\n");
+      return 1;
+    }
+
+  PedDisk *disk = ped_disk_new (device);
+  if (!disk)
+    {
+      debug ("!disk\n");
+      if (!ped_device_close (device))
+        debug ("!ped_device_close (device)\n");
+      debug ("set_last_partition_num end with err: 1\n");
+      return 1;
+    }
+
+  error_t err = 0;
+  *last_partition_num = ped_disk_get_last_partition_num (disk);
+  if (*last_partition_num < 0)
+    {
+      debug ("*last_partition_num < 0\n");
+      err = 1;
+    }
+
+  ped_disk_destroy (disk);
+  if (!ped_device_close (device))
+    debug ("!ped_device_close (device)\n");
+  ped_exception_leave_all ();
+
+  return err;
+}
+
+static inline char *
+create_node_name (const size_t num)
+{
+  char buffer[20];
+  snprintf (buffer, sizeof (buffer), "%zu", num);
+
+  return strdup (buffer);
+}
+
+static error_t
+create_node (struct node **node, char *name, struct node *dir,
+             size_t entries_size, struct store *store)
+{
+  debug ("create_node:\n");
+  struct netnode *netnode = malloc (sizeof (struct netnode));
+  if (!netnode)
+    {
+      debug ("!netnode\n");
+      debug ("create_node end with ENOMEM\n");
+      return ENOMEM;
+    }
+
+  struct node *new_node = netfs_make_node (netnode);
+  if (!new_node)
+    {
+      debug ("!new_node\n");
+      free (netnode);
+      debug ("create_node end with ENOMEM\n");
+      return ENOMEM;
+    }
+
+  static ino_t id = 1;
+  io_statbuf_t statbuf = {
+    .st_fstype = FSTYPE_MISC,
+    .st_fsid = partfs.pid,
+    .st_dev = partfs.pid,
+    .st_rdev = partfs.pid,
+    .st_uid = partfs.uid,
+    .st_author = partfs.uid,
+    .st_gid = partfs.gid,
+    .st_mode = partfs.mode,
+    .st_ino = id++,
+    .st_nlink = 1,
+    .st_blksize = 1,
+    .st_blocks = 1,
+    .st_gen = 0
+  };
+  new_node->nn_stat = statbuf;
+  new_node->next = NULL;
+  new_node->prevp = NULL;
+  pthread_rwlock_init (&new_node->nn->io_lock, NULL);
+  new_node->nn->name = name;
+  new_node->nn->store = store;
+
+  new_node->nn->entries_size = entries_size;
+  if (entries_size == 0)
+    new_node->nn->entries = NULL;
+  else
+    {
+      new_node->nn_stat.st_mode |= S_IFDIR;
+      new_node->nn->entries = malloc (entries_size * sizeof (struct node *));
+      if (!new_node->nn->entries)
+        {
+          debug ("!new_node->nn->entries\n");
+          free (netnode);
+          free (new_node);
+          debug ("create_node end with ENOMEM\n");
+          return ENOMEM;
+        }
+    }
+
+  if (dir)
+    {
+      netfs_nref (dir);
+      new_node->nn_stat.st_size = store->size;
+
+      if (store->block_size == 1 && entries_size == 0)
+        new_node->nn_stat.st_mode |= S_IFCHR;
+      else if (store->block_size > 1)
+        {
+          if (entries_size == 0)
+            new_node->nn_stat.st_mode |= S_IFBLK;
+          new_node->nn_stat.st_blksize = store->block_size;
+        }
+    }
+  else
+    new_node->nn_stat.st_size = 0;
+
+  fshelp_touch (&new_node->nn_stat, TOUCH_ATIME|TOUCH_CTIME|TOUCH_MTIME,
+                partfs.current_time);
+
+  *node = new_node;
+
+  debug ("new_node: %p\n", new_node);
+  debug ("new_node->name: %s\n", new_node->nn->name);
+  debug ("new_node->store: %p\n", new_node->nn->store);
+  debug ("create_node end with 0\n");
+  return 0;
+}
+
+static error_t
+create_partfs (const struct arguments *arguments)
+{
+  debug ("create_partfs:\n");
+
+  error_t err = maptime_map (0, 0, &partfs.current_time);
+  if (err)
+    return err;
+
+  partfs.pid = getpid ();
+  partfs.uid = getuid ();
+  partfs.gid = getgid ();
+
+  if (arguments->readonly)
+    partfs.mode = 0444;
+  else
+    partfs.mode = 0644;
+
+  partfs.enforced = arguments->enforced;
+
+  err = create_node (&netfs_root_node, NULL, NULL,
+                     arguments->device_names_count, NULL);
+  if (err)
+    return err;
+  debug ("netfs_root_node: %p\n", netfs_root_node);
+
+  netfs_root_node->nn_stat.st_nlink = 2;
+
+  struct node **device, **part;
+  struct store *source, *store;
+  int store_flags = ((arguments->readonly ? STORE_READONLY : 0)
+                     | (arguments->no_fileio ? STORE_NO_FILEIO : 0));
+  for (size_t i = 0; i < arguments->device_names_count; ++i)
+    {
+      device = &netfs_root_node->nn->entries[i];
+
+      err = store_file_open (arguments->device_names[i], store_flags,
+                             &source);
+      if (err)
+        return err;
+
+      size_t last_partition_num;
+      err = set_last_partition_num (arguments->device_names[i],
+                                    &last_partition_num);
+      if (err)
+        return err;
+
+      err = create_node (device, create_node_name (i), netfs_root_node,
+                         last_partition_num, source);
+      if (err)
+        return err;
+
+      for (size_t j = 1; j <= last_partition_num; ++j)
+        {
+          part = &(*device)->nn->entries[j - 1];
+          err = store_file_open (arguments->device_names[i], store_flags,
+                                 &source);
+          if (err)
+            return err;
+
+          err = store_part_create (source, j, store_flags, &store);
+          if (err)
+            return err;
+
+          err = create_node (part, create_node_name (j), *device, 0, store);
+          if (err)
+            return err;
+        }
+    }
+
+  debug ("create_partfs end\n");
+  return 0;
+}
+
+static const char argp_doc[] = "PARTFS-DOC";
+static const char doc[] =
+  "A translator for obtaining disk partitions using parted.";
+
+int
+main (int argc, char *argv[])
+{
+  struct arguments arguments;
+  struct argp argp = {options, parse_opt, argp_doc, doc};
+  argp_parse (&argp, argc, argv, 0, 0, &arguments);
+
+  mach_port_t bootstrap;
+  task_get_bootstrap_port (mach_task_self (), &bootstrap);
+
+  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 (arguments.debug_file_name)
+    {
+      debug_file = fopen (arguments.debug_file_name, "a");
+      setbuf (debug_file, NULL);
+    }
+#endif
+
+  debug ("\n---------------start main---------------\n");
+  for (size_t i = 0; i < arguments.device_names_count; ++i)
+    debug ("device_name: %s\n", arguments.device_names[i]);
+
+  err = create_partfs (&arguments);
+  if (err)
+    error (1, err, "Cannot creare partfs");
+  free (arguments.device_names);
+
+#ifdef DEBUG
+  print_partfs_node_info ();
+#endif
+
+  netfs_root_node->nn_stat = underlying_stat;
+  netfs_root_node->nn_stat.st_mode =
+    S_IFDIR | (underlying_stat.st_mode & ~S_IFMT & ~S_ITRANS);
+
+  debug ("netfs_server_loop()...\n");
+  netfs_server_loop ();
+
+  return 0;
+}
diff --git a/partfs/partfs.h b/partfs/partfs.h
new file mode 100644
index 00000000..63d7fab6
--- /dev/null
+++ b/partfs/partfs.h
@@ -0,0 +1,67 @@
+/* Copyright (C) 2026 Free Software Foundation
+   Written by Mikhail Karpov.
+
+   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 PARTFS_PARTFS_H
+#define PARTFS_PARTFS_H
+
+#include <hurd/store.h>
+#include <hurd/netfs.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <unistd.h>
+
+struct netnode
+{
+  pthread_rwlock_t io_lock;
+  char *name;
+  struct store *store;
+  struct node **entries;
+  size_t entries_size;
+};
+
+struct partfs
+{
+  volatile struct mapped_time_value *current_time;
+  pid_t pid;
+  uid_t uid;
+  gid_t gid;
+  mode_t mode;
+  int enforced;
+};
+
+extern struct partfs partfs;
+
+#ifdef DEBUG
+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 /* PARTFS_PARTFS_H */
-- 
2.43.0

Reply via email to