March 31, 2026 at 1:12 PM, "Mikhail Karpov via Bug reports for the GNU Hurd" 
<[email protected] 
mailto:[email protected]?to=%22Mikhail%20Karpov%20via%20Bug%20reports%20for%20the%20GNU%20Hurd%22%20%3Cbug-hurd%40gnu.org%3E
 > wrote:



> 
> 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. :)

You are new to operating system development and this is your first 
contribution!?

WOW!  I've been trying to write code for the Hurd for waaaay too long.  
libtrivfs
still confuses the heck out of me!

Congrats on a great first contribution.  Best of luck in getting it polished up
and committed!

Joshua Branson
hurdos.com
gnucode.org

> 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/> 
> http://www.gnu.org/licenses/%3E .
> +
> +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/> 
> http://www.gnu.org/licenses/%3E . */
> +
> +#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/> 
> http://www.gnu.org/licenses/%3E . */
> +
> +#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/> 
> http://www.gnu.org/licenses/%3E . */
> +
> +#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/> 
> http://www.gnu.org/licenses/%3E . */
> +
> +#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/> 
> http://www.gnu.org/licenses/%3E . */
> +
> +#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