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