[PATCH] Adding a partfs translator.

Mikhail Karpov <[email protected]>
Newsgroups gmane.os.hurd.bugs
Message-ID <[email protected]>
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
0001-Adding-a-partfs-translator.patch (text/x-patch, 36.3 KB)
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.