Hello everyone!
I wrote a partfs translator, and I think it works quite well, although perhaps
I'm just not experienced enough to spot any errors, as I'm new to operating
system development. :)
I wrote this translator based on the code of storeio, unionfs, and xmlfs.
The translator receives a disk image and displays the disk partitions as
separate files in a directory. So, if you pass a disk image with three
partitions, the translator directory will contain the files device0p1,
device0p2, and device0p3.
Any advice or comments are welcome!
Mikhail

Signed-off-by: Mikhail Karpov <[email protected]>
---
 Makefile         |   4 +
 partfs/Makefile  |  30 +++
 partfs/netfs.c   | 582 +++++++++++++++++++++++++++++++++++++++++++++++
 partfs/options.c |  79 +++++++
 partfs/options.h |  37 +++
 partfs/partfs.c  | 253 ++++++++++++++++++++
 partfs/partfs.h  |  66 ++++++
 7 files changed, 1051 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..c708a2c9
--- /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..2b2f4f43
--- /dev/null
+++ b/partfs/netfs.c
@@ -0,0 +1,582 @@
+/* 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");
+      debug ("netfs_attempt_lookup end with ENOTDIR\n");
+      return ENOTDIR;
+    }
+
+  if (*name == '\0' || strcmp (name, ".") == 0)
+    {
+      *np = dir;
+      pthread_mutex_lock (&dir->lock);
+      netfs_nref (dir);
+      pthread_mutex_unlock (&dir->lock);
+      debug ("*name == '\\0' || strcmp (name, \".\") == 0\n");
+      debug ("netfs_attempt_lookup end with 0\n");
+      return 0;
+    }
+
+  struct node *current_node = NULL;
+  for (size_t i = 0; i < partfs.last_partition_num; ++i)
+    {
+      current_node = netfs_root_node->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;
+    }
+
+  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)
+    {
+      struct dirent hdr;
+      size_t namlen = strlen (name);
+      size_t sz = DIRENT_LEN (namlen);
+
+      if (sz > *size)
+        return 0;
+
+      *size -= sz;
+
+      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 (partfs.last_partition_num + 2 <= entry)
+    {
+      *datacnt = 0;
+      *amt = 0;
+      *data = NULL;
+      debug ("partfs.last_partition_num + 2 <= entry\n");
+      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 < partfs.last_partition_num; ++i)
+    {
+      current_node = netfs_root_node->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);
+
+  error_t err = 0;
+  if ((void *) *data == (void *) -1)
+    err = errno;
+
+  if (!err)
+    {
+      *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");
+      for (size_t i = 0; i < partfs.last_partition_num; ++i)
+        {
+          current_node = netfs_root_node->nn->entries[i];
+          add_dir_entry (&ptr_data, current_node->nn->name,
+                         current_node->nn_stat.st_ino, DT_REG, &count,
+                         nentries, &size);
+        }
+    }
+
+  debug ("netfs_get_dirents final end with err = %d\n", err);
+  return err;
+}
+
+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)
+    {
+      *len = 0;
+      debug ("if (store->size > 0 && offset == store->size)\n");
+      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)
+    {
+      pthread_mutex_unlock (&node->lock);
+      debug ("(user->po->openstat & O_READ) == 0\n");
+      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;
+}
diff --git a/partfs/options.c b/partfs/options.c
new file mode 100644
index 00000000..43e8f6c4
--- /dev/null
+++ b/partfs/options.c
@@ -0,0 +1,79 @@
+/* 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[] =
+{
+#ifdef DEBUG
+  {"debug",              'd', "FILE", 0,
+    "Enable debug and write debug statements to FILE. The FILE must be "
+    "located outside the translator directory."},
+#endif
+  {"device-name",        'n', "NAME", 0, "Set the device NAME for files "
+    "provided by the translator."},
+  {0}
+};
+
+error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+  struct arguments *arguments = state->input;
+
+  switch (key)
+    {
+#ifdef DEBUG
+    case 'd':
+      arguments->debug_file_name = arg;
+      break;
+#endif
+    case 'n':
+      arguments->device_name = arg;
+      break;
+    case ARGP_KEY_ARG:
+      if (state->arg_num == 0)
+        arguments->source_file_name = arg;
+      else
+        return ARGP_ERR_UNKNOWN;
+      break;
+    case ARGP_KEY_END:
+      if (!arguments->source_file_name)
+        error (1, 0, "No disk image file specified\n");
+      if (!arguments->device_name)
+        arguments->device_name = "device0";
+      break;
+    case ARGP_KEY_INIT:
+#ifdef DEBUG
+      arguments->debug_file_name = NULL;
+#endif
+      arguments->device_name = NULL;
+      arguments->source_file_name = NULL;
+      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..240ef17f
--- /dev/null
+++ b/partfs/options.h
@@ -0,0 +1,37 @@
+/* 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>
+
+struct arguments
+{
+#ifdef DEBUG
+  char *debug_file_name;
+#endif
+  char *device_name;
+  char *source_file_name;
+};
+
+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..7346c016
--- /dev/null
+++ b/partfs/partfs.c
@@ -0,0 +1,253 @@
+/* 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;
+
+static error_t
+set_last_partition_num (const char *source_file_name,
+                        size_t *last_partition_num)
+{
+  ped_exception_fetch_all ();
+  PedDevice *device = ped_device_get (source_file_name);
+  if (!device || !ped_device_open (device))
+    return 1;
+
+  PedDisk *disk = ped_disk_new (device);
+  if (!disk)
+    {
+      ped_device_close (device);
+      return 1;
+    }
+
+  error_t err = 0;
+  *last_partition_num = ped_disk_get_last_partition_num (disk);
+  if (*last_partition_num < 0)
+    err = 1;
+
+  ped_disk_destroy (disk);
+  ped_device_close (device);
+
+  return err;
+}
+
+static inline char *
+create_node_name (const char *device_name, unsigned long part_num)
+{
+  if (!device_name)
+    return NULL;
+
+  char buffer[300];
+  memset (buffer, 0, sizeof (buffer));
+  strcat (buffer, device_name);
+  strcat (buffer, "p");
+  char num_buffer[20];
+  memset (num_buffer, 0, sizeof (num_buffer));
+  snprintf (num_buffer, sizeof (num_buffer), "%lu", part_num);
+  strcat (buffer, num_buffer);
+
+  return strdup (buffer);
+}
+
+static error_t
+create_node (struct node **node, const struct partfs *partfs,
+             struct node *dir, const char *device_name,
+             const unsigned long part_num, struct store *store)
+{
+  debug ("create_node:\n");
+  struct netnode *netnode = malloc (sizeof (struct netnode));
+  if (!netnode)
+    return ENOMEM;
+
+  struct node *new_node = netfs_make_node (netnode);
+  if (!new_node)
+    {
+      free (netnode);
+      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 = create_node_name (device_name, part_num);
+  new_node->nn->store = store;
+  new_node->nn->entries = NULL;
+
+  if (dir)
+    {
+      netfs_nref (dir);
+      new_node->nn_stat.st_size = store->size;
+
+      if (store->block_size == 1)
+        new_node->nn_stat.st_mode |= S_IFCHR;
+      else if (store->block_size > 1)
+        {
+          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 ("create_node end\n");
+  return 0;
+}
+
+static error_t
+create_partfs (const struct arguments *arguments, struct partfs *partfs)
+{
+  debug ("create_partfs:\n");
+  error_t err = set_last_partition_num(arguments->source_file_name,
+                                       &partfs->last_partition_num);
+  if (err)
+    return err;
+
+  partfs->pid = getpid();
+  partfs->uid = getuid();
+  partfs->gid = getgid();
+  err = maptime_map (0, 0, &partfs->current_time);
+  if (err)
+    return err;
+
+  partfs->mode = 0644;
+
+  netfs_root_node = NULL;
+  err = create_node (&netfs_root_node, partfs, NULL, NULL, 0, NULL);
+  if (err)
+    return err;
+
+  netfs_root_node->nn_stat.st_nlink = 2;
+  netfs_root_node->nn->entries = malloc (partfs->last_partition_num
+                                         * sizeof (struct node *));
+  debug ("netfs_root_node: %p\n", netfs_root_node);
+
+  struct store *source, *store;
+  for (size_t i = 1; i <= partfs->last_partition_num; ++i)
+    {
+      err = store_file_open (arguments->source_file_name, 0, &source);
+      if (err)
+        return err;
+
+      err = store_part_create (source, i, 0, &store);
+      if (err)
+        return err;
+
+      create_node (&netfs_root_node->nn->entries[i - 1], partfs,
+                   netfs_root_node, arguments->device_name, i, store);
+    }
+
+  debug ("create_partfs end\n");
+  return 0;
+}
+
+#ifdef DEBUG
+FILE *debug_file = NULL;
+pthread_mutex_t debug_lock = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+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, "w");
+      setbuf (debug_file, NULL);
+    }
+#endif
+
+  debug ("\n---------------start main---------------\n");
+  debug ("device_name: %s\n", arguments.device_name);
+  debug ("source_file_name: %s\n", arguments.source_file_name);
+
+  err = create_partfs (&arguments, &partfs);
+  if (err)
+    error (1, err, "Cannot creare partfs");
+
+  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..8d18723b
--- /dev/null
+++ b/partfs/partfs.h
@@ -0,0 +1,66 @@
+/* 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
+{
+  char *name;
+  struct store *store;
+  pthread_rwlock_t io_lock;
+  struct node **entries;
+};
+
+struct partfs
+{
+  pid_t pid;
+  uid_t uid;
+  gid_t gid;
+  volatile struct mapped_time_value *current_time;
+  size_t last_partition_num;
+  mode_t mode;
+};
+
+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