[PATCH] Adding a partfs translator.
Mikhail Karpov <[email protected]>
| Newsgroups | gmane.os.hurd.bugs |
|---|---|
| Message-ID | <[email protected]> |
Hello everyone! I've expanded the functionality of the partfs translator to work with multiple disks and their partitions. Thus, by running the command: settrans -c partfs /hurd/partfs /root/disk1.img /root/disk2.img /root/disk3.img The translator directory will have the following directory tree: partfs ├── 0 │ ├── 1 │ ├── 2 │ └── ... ├── 1 │ ├── 1 │ ├── 2 │ └── ... ├── 2 │ ├── 1 │ ├── 2 │ └── ... Since the disks are directories, the cd and ls commands work in the translator node. I also tested mounting, reading, and writing using the commands: settrans -c ext01 /hurd/ext2fs -w -T typed file:/root/partfs/0/1 and settrans -c ext1_1 /hurd/ext2fs -w -T typed part:1:file:/root/partfs/1 In all cases, everything worked correctly, although there may be issues that I don't yet understand, so any advice or comments are welcome! -- Mikhail Karpov
0001-Adding-a-partfs-translator.patch
(text/x-patch, 36.3 KB)
From cfb156a861625d7e6543c49dc9f50c26a061b0bb Mon Sep 17 00:00:00 2001 From: Mikhail Karpov <[email protected]> Date: Thu, 21 May 2026 14:23:51 +0700 Subject: [PATCH] Adding a partfs translator --- Makefile | 4 + partfs/Makefile | 30 +++ partfs/netfs.c | 683 +++++++++++++++++++++++++++++++++++++++++++++++ partfs/options.c | 95 +++++++ partfs/options.h | 42 +++ partfs/partfs.c | 350 ++++++++++++++++++++++++ partfs/partfs.h | 67 +++++ 7 files changed, 1271 insertions(+) create mode 100644 partfs/Makefile create mode 100644 partfs/netfs.c create mode 100644 partfs/options.c create mode 100644 partfs/options.h create mode 100644 partfs/partfs.c create mode 100644 partfs/partfs.h diff --git a/Makefile b/Makefile index c51e8c1c..3a1707e7 100644 --- a/Makefile +++ b/Makefile @@ -70,6 +70,10 @@ ifeq ($(HAVE_LIBACPICA),yes) prog-subdirs += acpi endif +ifneq ($(PARTED_LIBS),) +prog-subdirs += partfs +endif + # Other directories other-subdirs = hurd doc config release include diff --git a/partfs/Makefile b/partfs/Makefile new file mode 100644 index 00000000..2ca1dfe8 --- /dev/null +++ b/partfs/Makefile @@ -0,0 +1,30 @@ +# Copyright (C) 2026 Free Software Foundation +# Written by Mikhail Karpov. +# +# This file is part of the GNU Hurd. +# +# The GNU Hurd is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2, or (at +# your option) any later version. +# +# The GNU Hurd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. + +dir := partfs +makemode := server +target = partfs + +#CFLAGS += -DDEBUG +SRCS = netfs.c options.c partfs.c + +OBJS = $(SRCS:.c=.o) +HURDLIBS = fshelp iohelp netfs ports shouldbeinlibc store +LDLIBS = -lparted -lpthread + +include ../Makeconf diff --git a/partfs/netfs.c b/partfs/netfs.c new file mode 100644 index 00000000..66a30129 --- /dev/null +++ b/partfs/netfs.c @@ -0,0 +1,683 @@ +/* Copyright (C) 2026 Free Software Foundation + Written by Mikhail Karpov. + + This file is part of the GNU Hurd. + + The GNU Hurd is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + The GNU Hurd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ + +#include <argp.h> +#include <dirent.h> +#include <pthread.h> +#include <sys/mman.h> + +#include "partfs.h" + +error_t +netfs_validate_stat (struct node *np, struct iouser *cred) +{ + return 0; +} + +error_t +netfs_attempt_chown (struct iouser *cred, struct node *np, uid_t uid, + uid_t gid) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_chauthor (struct iouser *cred, struct node *np, uid_t author) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_chmod (struct iouser *cred, struct node *np, mode_t mode) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_mksymlink (struct iouser *cred, struct node *np, + const char *name) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_mkdev (struct iouser *cred, struct node *np, mode_t type, + dev_t indexes) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_chflags (struct iouser *cred, struct node *np, int flags) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_utimes (struct iouser *cred, struct node *np, + struct timespec *atime, struct timespec *mtime) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_set_size (struct iouser *cred, struct node *np, loff_t size) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_statfs (struct iouser *cred, struct node *np, + fsys_statfsbuf_t *st) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_sync (struct iouser *cred, struct node *np, int wait) +{ + return 0; +} + +error_t +netfs_attempt_syncfs (struct iouser *cred, int wait) +{ + return 0; +} + +error_t +netfs_attempt_lookup (struct iouser *user, struct node *dir, + const char *name, struct node **np) +{ + debug ("netfs_attempt_lookup (user: %p, dir: %p, name: %s, np: %p)\n", + user, dir, name, np); + pthread_mutex_unlock (&dir->lock); + + if (!dir->nn->entries) + { + debug ("!dir->nn->entries\n"); + *np = NULL; + debug ("netfs_attempt_lookup end with ENOTDIR\n"); + return ENOTDIR; + } + + if (*name == '\0' || strcmp (name, ".") == 0) + { + debug ("*name == '\\0' || strcmp (name, \".\") == 0\n"); + *np = dir; + pthread_mutex_lock (&dir->lock); + netfs_nref (*np); + pthread_mutex_unlock (&dir->lock); + debug ("netfs_attempt_lookup end with 0\n"); + return 0; + } + else if (strcmp (name, "..") == 0) + { + debug ("strcmp (name, \"..\") == 0\n"); + *np = netfs_root_node; + pthread_mutex_lock (&dir->lock); + netfs_nref (*np); + pthread_mutex_unlock (&dir->lock); + debug ("netfs_attempt_lookup end with 0\n"); + return 0; + } + + struct node *current_node = NULL; + for (size_t i = 0; i < dir->nn->entries_size; ++i) + { + current_node = dir->nn->entries[i]; + + if (strcmp (name, current_node->nn->name) == 0) + break; + } + + if (current_node) + { + pthread_mutex_lock (&dir->lock); + *np = current_node; + netfs_nref (*np); + pthread_mutex_unlock (&dir->lock); + debug ("current_node->nn->name: %s\n", current_node->nn->name); + debug ("netfs_attempt_lookup end with 0\n"); + return 0; + } + + *np = NULL; + debug ("netfs_attempt_lookup end with ENOENT\n"); + return ENOENT; +} + +error_t +netfs_attempt_unlink (struct iouser *user, struct node *dir, const char *name) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_rename (struct iouser *user, struct node *fromdir, + const char *fromname, struct node *todir, + const char *toname, int excl) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_mkdir (struct iouser *user, struct node *dir, const char *name, + mode_t mode) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_rmdir (struct iouser *user, struct node *dir, const char *name) +{ + return EOPNOTSUPP; +} + +error_t +netfs_attempt_link (struct iouser *user, struct node *dir, struct node *file, + const char *name, int excl) +{ + return EOPNOTSUPP; +} + +/* We don't use this function, but we need to unlock the dir. */ +error_t +netfs_attempt_mkfile (struct iouser *user, struct node *dir, mode_t mode, + struct node **np) +{ + pthread_mutex_unlock (&dir->lock); + return EOPNOTSUPP; +} + +/* We don't use this function, but we need to unlock the dir. */ +error_t +netfs_attempt_create_file (struct iouser *user, struct node *dir, + const char *name, mode_t mode, struct node **np) +{ + pthread_mutex_unlock (&dir->lock); + return EOPNOTSUPP; +} + +error_t +netfs_attempt_readlink (struct iouser *user, struct node *np, char *buf) +{ + return EOPNOTSUPP; +} + +error_t +netfs_check_open_permissions (struct iouser *user, struct node *np, + int flags, int newnode) +{ + error_t err = 0; + + if (!err && (flags & O_READ)) + err = fshelp_access (&np->nn_stat, S_IREAD, user); + if (!err && (flags & O_WRITE)) + err = fshelp_access (&np->nn_stat, S_IWRITE, user); + if (!err && (flags & O_EXEC)) + err = fshelp_access (&np->nn_stat, S_IEXEC, user); + + return err; +} + +/* We don't use this function, but it has to be defined. */ +error_t +netfs_attempt_read (struct iouser *cred, struct node *np, loff_t offset, + size_t *len, void *data) +{ + return EOPNOTSUPP; +} + +/* We don't use this function, but it has to be defined. */ +error_t +netfs_attempt_write (struct iouser *cred, struct node *np, loff_t offset, + size_t *len, const void *data) +{ + return EOPNOTSUPP; +} + +error_t +netfs_report_access (struct iouser *cred, struct node *np, int *types) +{ + return EOPNOTSUPP; +} + +struct iouser * +netfs_make_user (uid_t *uids, int nuids, uid_t *gids, int ngids) +{ + return NULL; +} + +void +netfs_node_norefs (struct node *np) +{ + return; +} + +/* Returned directory entries are aligned to blocks this many bytes long. + Must be a power of two. */ +#define DIRENT_ALIGN 4 +#define DIRENT_NAME_OFFS offsetof (struct dirent, d_name) + +/* Length is structure before the name + the name + '\0', all + padded to a four-byte alignment. */ +#define DIRENT_LEN(name_len) \ + ((DIRENT_NAME_OFFS + (name_len) + 1 + (DIRENT_ALIGN - 1)) \ + & ~(DIRENT_ALIGN - 1)) + +static inline int +bump_size (size_t *size, int *count, const char *name, const int nentries, + const vm_size_t buffsize) +{ + if (nentries == -1 || *count < nentries) + { + size_t new_size = *size + DIRENT_LEN (strlen (name)); + if (buffsize > 0 && new_size > buffsize) + return 0; + + *size = new_size; + *count += 1; + return 1; + } + + return 0; +} + +static inline int +add_dir_entry (char **data, const char *name, const ino_t fileno, + const int type, int *count, const int nentries, size_t *size) +{ + if (nentries == -1 || *count < nentries) + { + size_t namlen = strlen (name); + size_t sz = DIRENT_LEN (namlen); + + if (sz > *size) + return 0; + + *size -= sz; + + struct dirent hdr; + hdr.d_fileno = fileno; + hdr.d_reclen = sz; + hdr.d_type = type; + hdr.d_namlen = namlen; + + memcpy (*data, &hdr, DIRENT_NAME_OFFS); + strcpy (*data + DIRENT_NAME_OFFS, name); + + *data += sz; + *count += 1; + + return 1; + } + + return 0; +} + +error_t +netfs_get_dirents (struct iouser *cred, struct node *dir, int entry, + int nentries, char **data, mach_msg_type_number_t *datacnt, + vm_size_t bufsize, int *amt) +{ + debug ("netfs_get_dirents (cred: %p, dir: %p, entry: %d, nentries: %d, " + "datacnt: %u, bufsize: %u, amt: %d)\n", cred, dir, entry, nentries, + *datacnt, bufsize, *amt); + + if (!dir->nn->entries) + { + debug ("!dir->nn->entries\n"); + debug ("netfs_attempt_lookup end with ENOTDIR\n"); + return ENOTDIR; + } + + if (dir->nn->entries_size + 2 <= entry) + { + debug ("dir->nn->entries_size + 2 <= entry\n"); + *datacnt = 0; + *amt = 0; + *data = NULL; + debug ("netfs_get_dirents end with 0\n"); + return 0; + } + + int count = 0; + size_t size = 0; + + if (entry == 0) + bump_size (&size, &count, ".", nentries, bufsize); + if (entry <= 1) + bump_size (&size, &count, "..", nentries, bufsize); + + struct node *current_node; + for (size_t i = 0; i < dir->nn->entries_size; ++i) + { + current_node = dir->nn->entries[i]; + bump_size (&size, &count, current_node->nn->name, nentries, bufsize); + } + + *data = mmap (0, size, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + + if ((void *) *data == (void *) -1) + return errno; + + *datacnt = size; + *amt = count; + + count = 0; + char *ptr_data = *data; + + if (entry == 0) + { + debug ("entry == 0\n"); + add_dir_entry (&ptr_data, ".", dir->nn_stat.st_ino, DT_DIR, &count, + nentries, &size); + } + if (entry <= 1) + { + debug ("entry <= 1\n"); + add_dir_entry (&ptr_data, "..", 2, DT_DIR, &count, nentries, &size); + } + + debug ("Fill in the real directory entries\n"); + int dirent_type; + if (dir->nn->entries_size > 0) + dirent_type = DT_DIR; + else + if (dir->nn->store->block_size == 1) + dirent_type = DT_CHR; + else + dirent_type = DT_BLK; + + for (size_t i = 0; i < dir->nn->entries_size; ++i) + { + current_node = dir->nn->entries[i]; + add_dir_entry (&ptr_data, current_node->nn->name, + current_node->nn_stat.st_ino, dirent_type, &count, + nentries, &size); + } + + debug ("netfs_get_dirents end with 0\n"); + return 0; +} + +static inline error_t +check_offset_and_len (loff_t offset, store_offset_t store_size, size_t *len) +{ + if (offset < 0 || offset > store_size) + return EINVAL; + + if (offset + *len > store_size) + *len = store_size - offset; + + return 0; +} + +static error_t +attempt_read (struct netnode *netnode, loff_t offset, size_t *len, + vm_size_t *amount, void **data) +{ + debug ("attempt_read (netnode: %p, offset: %lld, len: %zu, amount: %zu)\n", + netnode, offset, *len, *amount); + + struct store *store = netnode->store; + + if (store->size > 0 && offset == store->size) + { + debug ("store->size > 0 && offset == store->size\n"); + *len = 0; + debug ("attempt_read end with 0\n"); + return 0; + } + + error_t err = check_offset_and_len (offset, store->size, len); + if (err) + { + debug ("err in check_offset_and_len\n"); + debug ("attempt_read end with err: %d\n", err); + return err; + } + + off_t addr = offset >> store->log2_block_size; + pthread_rwlock_rdlock (&netnode->io_lock); + err = store_read (store, addr, *len, data, amount); + pthread_rwlock_unlock (&netnode->io_lock); + + debug ("attempt_read end with err: %d\n", err); + return err; +} + +kern_return_t +netfs_S_io_read (struct protid *user, data_t *data, + mach_msg_type_number_t *datalen, off_t offset, + vm_size_t amount) +{ + debug ("netfs_S_io_read:\n"); + + if (!user) + { + debug ("!user\n"); + debug ("netfs_S_io_read end with EOPNOTSUPP\n"); + return EOPNOTSUPP; + } + + struct node *node = user->po->np; + pthread_mutex_lock (&user->po->np->lock); + + if ((user->po->openstat & O_READ) == 0) + { + debug ("(user->po->openstat & O_READ) == 0\n"); + pthread_mutex_unlock (&node->lock); + debug ("netfs_S_io_read end with EBADF"); + return EBADF; + } + + int alloced = 0; + size_t data_size = *datalen; + if (amount > data_size) + { + alloced = 1; + *data = mmap (0, amount, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + } + data_size = amount; + + off_t start = (offset == -1 ? user->po->filepointer : offset); + error_t err; + err = attempt_read (node->nn, start, &data_size, &amount, (void **)data); + + if (offset == -1 && !err) + user->po->filepointer += data_size; + + pthread_mutex_unlock (&node->lock); + + if (err && alloced) + munmap (*data, amount); + + if (!err && alloced && (round_page (data_size) < round_page (amount))) + munmap (*data + round_page (data_size), + round_page (amount) - round_page (data_size)); + + *datalen = data_size; + debug ("netfs_S_io_read end with err: %d\n", err); + return err; +} + +static error_t +attempt_write (struct netnode *netnode, loff_t offset, size_t len, + vm_size_t *amount, const void *data) +{ + debug ("attempt_write (netnode: %p, offset: %lld, len: %zu amount: %zu):\n", + netnode, offset, len, *amount); + + struct store *store = netnode->store; + + error_t err = check_offset_and_len (offset, store->size, &len); + if (err) + { + debug ("err in check_offset_and_len\n"); + debug ("attempt_write end with err: %d\n", err); + return err; + } + + off_t addr = offset >> store->log2_block_size; + pthread_rwlock_rdlock (&netnode->io_lock); + err = store_write (store, addr, data, len, amount); + pthread_rwlock_unlock (&netnode->io_lock); + + debug ("attempt_write end with err: %d\n", err); + return err; +} + +kern_return_t +netfs_S_io_write (struct protid *user, const_data_t data, + mach_msg_type_number_t datalen, off_t offset, + vm_size_t *amount) +{ + debug ("netfs_S_io_write:\n"); + + if (!user) + { + debug ("!user\n"); + debug ("netfs_S_io_write end with EOPNOTSUPP\n"); + return EOPNOTSUPP; + } + + if ((user->po->openstat & O_WRITE) == 0) + { + debug ("(user->po->openstat & O_WRITE) == 0\n"); + debug ("netfs_S_io_write end with EBADF\n"); + return EBADF; + } + + *amount = datalen; + + struct node *np = user->po->np; + pthread_mutex_lock (&np->lock); + + off_t start = offset; + error_t err; + if (start == -1) + { + if (user->po->openstat & O_APPEND) + { + err = netfs_validate_stat (np, user->user); + if (err) + { + pthread_mutex_unlock (&np->lock); + debug ("err in netfs_validate_stat\n"); + debug ("netfs_S_io_write end with err: %d\n", err); + return err; + } + user->po->filepointer = np->nn_stat.st_size; + } + start = user->po->filepointer; + } + + err = attempt_write (np->nn, start, datalen, amount, (const void *)data); + if (offset == -1 && !err) + user->po->filepointer += *amount; + pthread_mutex_unlock (&np->lock); + + debug ("netfs_S_io_write end with err: %d\n", err); + return err; +} + +static inline int +is_privileged (const struct idvec *uids) +{ + return idvec_contains (uids, 0) || idvec_contains (uids, getuid ()); +} + +error_t +netfs_file_get_storage_info (struct iouser *cred, struct node *np, + mach_port_t **ports, + mach_msg_type_name_t *ports_type, + mach_msg_type_number_t *num_ports, + int **ints, + mach_msg_type_number_t *num_ints, + off_t **offsets, + mach_msg_type_number_t *num_offsets, + char **data, + mach_msg_type_number_t *data_len) +{ + debug ("netfs_file_get_storage_info:\n"); + *ports_type = MACH_MSG_TYPE_COPY_SEND; + + if (!cred) + { + debug ("!cred\n"); + debug ("netfs_file_get_storage_info end with EOPNOTSUPP\n"); + return EOPNOTSUPP; + } + + struct store *store = np->nn->store; + if (partfs.enforced && !(store->flags & STORE_ENFORCED)) + { + debug ("partfs.enforced && !(store->flags & STORE_ENFORCED)\n"); + size_t name_len = (store->name ? strlen (store->name) + 1 : 0); + *num_ports = 0; + int i = 0; + (*ints)[i++] = STORAGE_OTHER; + (*ints)[i++] = store->flags; + (*ints)[i++] = store->block_size; + (*ints)[i++] = 1; + (*ints)[i++] = name_len; + (*ints)[i++] = 0; + *num_ints = i; + i = 0; + (*offsets)[i++] = 0; + (*offsets)[i++] = store->size; + *num_offsets = i; + if (store->name) + memcpy (*data, store->name, name_len); + *data_len = name_len; + debug ("netfs_file_get_storage_info end with 0\n"); + return 0; + } + + error_t err; + if (!is_privileged (cred->uids) + && !store_is_securely_returnable (store, np->nn_stat.st_mode)) + { + debug ("!is_privileged (cred->uids)...\n"); + struct store *clone; + err = store_clone (store, &clone); + if (err) + { + debug ("err in store_clone\n"); + debug ("netfs_file_get_storage_info end with err: %d\n", err); + return err; + } + + err = store_set_flags (clone, STORE_INACTIVE); + if (err == EINVAL) + err = EACCES; + else + err = store_return (clone, ports, num_ports, ints, num_ints, + offsets, num_offsets, data, data_len); + + store_free (clone); + } + else + err = store_return (store, ports, num_ports, ints, num_ints, + offsets, num_offsets, data, data_len); + + debug ("netfs_file_get_storage_info end with err: %d\n", err); + return err; +} diff --git a/partfs/options.c b/partfs/options.c new file mode 100644 index 00000000..adc780cd --- /dev/null +++ b/partfs/options.c @@ -0,0 +1,95 @@ +/* Copyright (C) 2026 Free Software Foundation + Written by Mikhail Karpov. + + This file is part of the GNU Hurd. + + The GNU Hurd is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + The GNU Hurd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ + +#include <error.h> +#include <string.h> +#include <unistd.h> + +#include "options.h" + +const struct argp_option options[] = +{ + {"readonly", 'r', 0, 0, "Disallow writing."}, + {"writable", 'w', 0, 0, "Allow writing (default)."}, + {"enforced", 'e', 0, 0, "Never reveal underlying devices, even to" + " root."}, + {"no-file-io", 'F', 0, 0, "Never perform io via plain file io RPCs."}, +#ifdef DEBUG + {"debug", 'd', "FILE", 0, "Enable debug and write debug statements to" + " FILE. The FILE must be located outside the translator directory."}, +#endif + {0} +}; + +error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + struct arguments *arguments = state->input; + + switch (key) + { + case 'r': arguments->readonly = 1; break; + case 'w': arguments->readonly = 0; break; + case 'e': arguments->enforced = 1; break; + case 'F': arguments->no_fileio = 1; break; +#ifdef DEBUG + case 'd': arguments->debug_file_name = arg; break; +#endif + case ARGP_KEY_ARG: + if (arguments->device_names_count == 0) + { + arguments->device_names = malloc (sizeof (char *)); + if (arguments->device_names == NULL) + error (1, 0, "Not enough memory to allocate for hd name"); + } + else + { + size_t len = (arguments->device_names_count + 1) * sizeof (char *); + char **tmp = realloc (arguments->device_names, len); + if (tmp == NULL) + error (1, 0, "Not enough memory to allocate for hd name"); + + arguments->device_names = tmp; + } + + arguments->device_names[arguments->device_names_count] = arg; + ++arguments->device_names_count; + break; + case ARGP_KEY_END: + if (!arguments->device_names) + error (1, 0, "No disk image file specified\n"); + break; + case ARGP_KEY_INIT: + arguments->readonly = 0; + arguments->enforced = 0; + arguments->no_fileio = 0; +#ifdef DEBUG + arguments->debug_file_name = NULL; +#endif + arguments->device_names = NULL; + arguments->device_names_count = 0; + break; + case ARGP_KEY_SUCCESS: + case ARGP_KEY_ERROR: + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} diff --git a/partfs/options.h b/partfs/options.h new file mode 100644 index 00000000..05058727 --- /dev/null +++ b/partfs/options.h @@ -0,0 +1,42 @@ +/* Copyright (C) 2026 Free Software Foundation + Written by Mikhail Karpov. + + This file is part of the GNU Hurd. + + The GNU Hurd is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + The GNU Hurd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef PARTFS_OPTIONS_H +#define PARTFS_OPTIONS_H + +#include <argp.h> +#include <stdlib.h> + +struct arguments +{ +#ifdef DEBUG + char *debug_file_name; +#endif + char **device_names; + size_t device_names_count; + + int readonly; + int enforced; + int no_fileio; +}; + +extern const struct argp_option options[]; + +error_t parse_opt (int key, char *arg, struct argp_state *state); + +#endif /* PARTFS_OPTIONS_H */ diff --git a/partfs/partfs.c b/partfs/partfs.c new file mode 100644 index 00000000..01531f05 --- /dev/null +++ b/partfs/partfs.c @@ -0,0 +1,350 @@ +/* Copyright (C) 2026 Free Software Foundation + Written by Mikhail Karpov. + + This file is part of the GNU Hurd. + + The GNU Hurd is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + The GNU Hurd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ + +#include <error.h> +#include <fcntl.h> +#include <parted/parted.h> +#include <pthread.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include <version.h> + +#include "options.h" +#include "partfs.h" + +char *netfs_server_name = "partfs"; +char *netfs_server_version = HURD_VERSION; +int netfs_maxsymlinks = 0; /* arbitrary */ + +const char *argp_program_version = STANDARD_HURD_VERSION (partfs); + +struct partfs partfs; + +#ifdef DEBUG +FILE *debug_file = NULL; +pthread_mutex_t debug_lock = PTHREAD_MUTEX_INITIALIZER; + +static void +print_node_info (const char *node_name, const struct node *node) +{ + debug ("%s: %p, name: %s, store: %p, entries: %p," + " entries_size: %zu\n", node_name, node, node->nn->name, + node->nn->store, node->nn->entries, node->nn->entries_size); + + if (node->nn->entries_size > 0) + { + for (size_t i = 0; i < node->nn->entries_size; ++i) + debug ("entries[%zu]: %p ", i, node->nn->entries[i]); + debug ("\n"); + } +} + +static void +print_partfs_node_info (void) +{ + debug ("\n------------partfs node info------------\n"); + print_node_info ("netfs_root_node", netfs_root_node); + + char node_name[128]; + struct node *device, *part; + for (size_t i = 0; i < netfs_root_node->nn->entries_size; ++i) + { + debug ("\n"); + snprintf (node_name, sizeof (node_name), "device%zu", i); + device = netfs_root_node->nn->entries[i]; + print_node_info (node_name, device); + + for (size_t j = 0; j < device->nn->entries_size; ++j) + { + snprintf (node_name, sizeof (node_name), "part%zu", j + 1); + part = device->nn->entries[j]; + print_node_info (node_name, part); + } + } + debug ("\n"); +} +#endif /* DEBUG */ + +static error_t +set_last_partition_num (const char *device_name, size_t *last_partition_num) +{ + ped_exception_fetch_all (); + PedDevice *device = ped_device_get (device_name); + if (!device || !ped_device_open (device)) + { + debug ("!device || !ped_device_open (device)\n"); + debug ("set_last_partition_num end with err: 1\n"); + return 1; + } + + PedDisk *disk = ped_disk_new (device); + if (!disk) + { + debug ("!disk\n"); + if (!ped_device_close (device)) + debug ("!ped_device_close (device)\n"); + debug ("set_last_partition_num end with err: 1\n"); + return 1; + } + + error_t err = 0; + *last_partition_num = ped_disk_get_last_partition_num (disk); + if (*last_partition_num < 0) + { + debug ("*last_partition_num < 0\n"); + err = 1; + } + + ped_disk_destroy (disk); + if (!ped_device_close (device)) + debug ("!ped_device_close (device)\n"); + ped_exception_leave_all (); + + return err; +} + +static inline char * +create_node_name (const size_t num) +{ + char buffer[20]; + snprintf (buffer, sizeof (buffer), "%zu", num); + + return strdup (buffer); +} + +static error_t +create_node (struct node **node, char *name, struct node *dir, + size_t entries_size, struct store *store) +{ + debug ("create_node:\n"); + struct netnode *netnode = malloc (sizeof (struct netnode)); + if (!netnode) + { + debug ("!netnode\n"); + debug ("create_node end with ENOMEM\n"); + return ENOMEM; + } + + struct node *new_node = netfs_make_node (netnode); + if (!new_node) + { + debug ("!new_node\n"); + free (netnode); + debug ("create_node end with ENOMEM\n"); + return ENOMEM; + } + + static ino_t id = 1; + io_statbuf_t statbuf = { + .st_fstype = FSTYPE_MISC, + .st_fsid = partfs.pid, + .st_dev = partfs.pid, + .st_rdev = partfs.pid, + .st_uid = partfs.uid, + .st_author = partfs.uid, + .st_gid = partfs.gid, + .st_mode = partfs.mode, + .st_ino = id++, + .st_nlink = 1, + .st_blksize = 1, + .st_blocks = 1, + .st_gen = 0 + }; + new_node->nn_stat = statbuf; + new_node->next = NULL; + new_node->prevp = NULL; + pthread_rwlock_init (&new_node->nn->io_lock, NULL); + new_node->nn->name = name; + new_node->nn->store = store; + + new_node->nn->entries_size = entries_size; + if (entries_size == 0) + new_node->nn->entries = NULL; + else + { + new_node->nn_stat.st_mode |= S_IFDIR; + new_node->nn->entries = malloc (entries_size * sizeof (struct node *)); + if (!new_node->nn->entries) + { + debug ("!new_node->nn->entries\n"); + free (netnode); + free (new_node); + debug ("create_node end with ENOMEM\n"); + return ENOMEM; + } + } + + if (dir) + { + netfs_nref (dir); + new_node->nn_stat.st_size = store->size; + + if (store->block_size == 1 && entries_size == 0) + new_node->nn_stat.st_mode |= S_IFCHR; + else if (store->block_size > 1) + { + if (entries_size == 0) + new_node->nn_stat.st_mode |= S_IFBLK; + new_node->nn_stat.st_blksize = store->block_size; + } + } + else + new_node->nn_stat.st_size = 0; + + fshelp_touch (&new_node->nn_stat, TOUCH_ATIME|TOUCH_CTIME|TOUCH_MTIME, + partfs.current_time); + + *node = new_node; + + debug ("new_node: %p\n", new_node); + debug ("new_node->name: %s\n", new_node->nn->name); + debug ("new_node->store: %p\n", new_node->nn->store); + debug ("create_node end with 0\n"); + return 0; +} + +static error_t +create_partfs (const struct arguments *arguments) +{ + debug ("create_partfs:\n"); + + error_t err = maptime_map (0, 0, &partfs.current_time); + if (err) + return err; + + partfs.pid = getpid (); + partfs.uid = getuid (); + partfs.gid = getgid (); + + if (arguments->readonly) + partfs.mode = 0444; + else + partfs.mode = 0644; + + partfs.enforced = arguments->enforced; + + err = create_node (&netfs_root_node, NULL, NULL, + arguments->device_names_count, NULL); + if (err) + return err; + debug ("netfs_root_node: %p\n", netfs_root_node); + + netfs_root_node->nn_stat.st_nlink = 2; + + struct node **device, **part; + struct store *source, *store; + int store_flags = ((arguments->readonly ? STORE_READONLY : 0) + | (arguments->no_fileio ? STORE_NO_FILEIO : 0)); + for (size_t i = 0; i < arguments->device_names_count; ++i) + { + device = &netfs_root_node->nn->entries[i]; + + err = store_file_open (arguments->device_names[i], store_flags, + &source); + if (err) + return err; + + size_t last_partition_num; + err = set_last_partition_num (arguments->device_names[i], + &last_partition_num); + if (err) + return err; + + err = create_node (device, create_node_name (i), netfs_root_node, + last_partition_num, source); + if (err) + return err; + + for (size_t j = 1; j <= last_partition_num; ++j) + { + part = &(*device)->nn->entries[j - 1]; + err = store_file_open (arguments->device_names[i], store_flags, + &source); + if (err) + return err; + + err = store_part_create (source, j, store_flags, &store); + if (err) + return err; + + err = create_node (part, create_node_name (j), *device, 0, store); + if (err) + return err; + } + } + + debug ("create_partfs end\n"); + return 0; +} + +static const char argp_doc[] = "PARTFS-DOC"; +static const char doc[] = + "A translator for obtaining disk partitions using parted."; + +int +main (int argc, char *argv[]) +{ + struct arguments arguments; + struct argp argp = {options, parse_opt, argp_doc, doc}; + argp_parse (&argp, argc, argv, 0, 0, &arguments); + + mach_port_t bootstrap; + task_get_bootstrap_port (mach_task_self (), &bootstrap); + + netfs_init (); + + mach_port_t underlying_node = netfs_startup (bootstrap, O_READ); + io_statbuf_t underlying_stat; + + error_t err = io_stat (underlying_node, &underlying_stat); + if (err) + error (1, err, "Cannot stat underlying node"); + +#ifdef DEBUG + if (arguments.debug_file_name) + { + debug_file = fopen (arguments.debug_file_name, "a"); + setbuf (debug_file, NULL); + } +#endif + + debug ("\n---------------start main---------------\n"); + for (size_t i = 0; i < arguments.device_names_count; ++i) + debug ("device_name: %s\n", arguments.device_names[i]); + + err = create_partfs (&arguments); + if (err) + error (1, err, "Cannot creare partfs"); + free (arguments.device_names); + +#ifdef DEBUG + print_partfs_node_info (); +#endif + + netfs_root_node->nn_stat = underlying_stat; + netfs_root_node->nn_stat.st_mode = + S_IFDIR | (underlying_stat.st_mode & ~S_IFMT & ~S_ITRANS); + + debug ("netfs_server_loop()...\n"); + netfs_server_loop (); + + return 0; +} diff --git a/partfs/partfs.h b/partfs/partfs.h new file mode 100644 index 00000000..63d7fab6 --- /dev/null +++ b/partfs/partfs.h @@ -0,0 +1,67 @@ +/* Copyright (C) 2026 Free Software Foundation + Written by Mikhail Karpov. + + This file is part of the GNU Hurd. + + The GNU Hurd is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + The GNU Hurd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef PARTFS_PARTFS_H +#define PARTFS_PARTFS_H + +#include <hurd/store.h> +#include <hurd/netfs.h> +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +struct netnode +{ + pthread_rwlock_t io_lock; + char *name; + struct store *store; + struct node **entries; + size_t entries_size; +}; + +struct partfs +{ + volatile struct mapped_time_value *current_time; + pid_t pid; + uid_t uid; + gid_t gid; + mode_t mode; + int enforced; +}; + +extern struct partfs partfs; + +#ifdef DEBUG +extern FILE *debug_file; +extern pthread_mutex_t debug_lock; +# define debug(format, ...) \ + do \ + { \ + if (debug_file) \ + { \ + pthread_mutex_lock (&debug_lock); \ + fprintf (debug_file, format, ## __VA_ARGS__); \ + pthread_mutex_unlock (&debug_lock); \ + } \ + } \ + while (0) +#else +# define debug(format, ...) do {} while (0) +#endif + +#endif /* PARTFS_PARTFS_H */ -- 2.43.0