Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 67 additions & 1 deletion src/portal/portal_devfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include <linux/file.h> // Required for fput()
#include <linux/poll.h> // Required for poll_wait()/poll_table_struct/POLL* (issue #77)
#include <linux/wait.h> // Required for wait_queue_head_t/wake_up_interruptible (issue #77)
#include <linux/timer.h> // Required for timer_list/timer_setup/mod_timer (periodic poll heartbeat)
#include <linux/jiffies.h> // Required for msecs_to_jiffies() (periodic poll heartbeat)
#include <linux/shmem_fs.h> // Required for shmem_kernel_file_setup()
#include <linux/vmalloc.h> // Required for kvzalloc/vzalloc()
#include <linux/blkdev.h>
Expand Down Expand Up @@ -40,11 +42,16 @@ struct portal_devfs_create_req {
int support_mmap;
int is_block;
int logical_block_size;
int major;
int major;
int minor;
struct igloo_dev_ops ops;
int replace;
int parent_id;
// periodic poll heartbeat: if > 0, arm a self-rearming kernel timer that
// marks this node readable every poll_interval_ms and wakes poll_wq. Lets a
// poll()/epoll(timeout=-1) main loop advance at a fixed cadence (models a
// hardware heartbeat, e.g. a watchdog device). 0 = disabled.
int poll_interval_ms;
};

// Internal: Track created devices
Expand Down Expand Up @@ -79,6 +86,16 @@ struct portal_devfs_entry {
unsigned int (*python_poll)(struct file *, struct poll_table_struct *);
ssize_t (*python_write)(struct file *, const char __user *, size_t, loff_t *);

// periodic poll heartbeat: when poll_interval_ms > 0 a self-rearming timer
// sets poll_ready and wakes poll_wq every interval; the poll proxy then
// reports POLLIN once per tick (edge-consumed via atomic_xchg) and "not
// ready" otherwise. Models a device that delivers a periodic hardware event
// (e.g. a watchdog): neither always-ready (which spins an epoll(-1) loop)
// nor never-ready (which deadlocks it), but a fixed cadence.
struct timer_list poll_timer;
atomic_t poll_ready;
unsigned int poll_interval_ms;

char *name;
};
// Internal: Track created directories to reconstruct paths
Expand Down Expand Up @@ -302,6 +319,26 @@ static int igloo_devfs_proxy_mmap(struct file *file, struct vm_area_struct *vma)
return ret;
}

/*
* periodic poll heartbeat timer: mark the node readable and wake any poller
* parked on poll_wq, then re-arm. Driven by poll_interval_ms; see the poll
* proxy below for how the tick is consumed. timer_setup()/from_timer() landed
* in 4.14; older kernels use setup_timer() with the entry pointer in ->data.
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
static void igloo_devfs_poll_timer_fn(struct timer_list *t)
{
struct portal_devfs_entry *pe = from_timer(pe, t, poll_timer);
#else
static void igloo_devfs_poll_timer_fn(unsigned long data)
{
struct portal_devfs_entry *pe = (struct portal_devfs_entry *)data;
#endif
atomic_set(&pe->poll_ready, 1);
wake_up_interruptible(&pe->poll_wq);
mod_timer(&pe->poll_timer, jiffies + msecs_to_jiffies(pe->poll_interval_ms));
}

/*
* poll proxy (issue #77): register the opener on the per-device wait queue so a
* "not ready" poll can actually block and be woken later, then defer the
Expand All @@ -324,6 +361,19 @@ static unsigned int igloo_devfs_proxy_poll(struct file *file, struct poll_table_

poll_wait(file, &pe->poll_wq, pt);

if (pe->poll_interval_ms > 0) {
/* Periodic heartbeat node (e.g. a watchdog device). The timer
* sets poll_ready once per interval; report POLLIN for exactly one
* poll() per tick (atomic_xchg consumes it) and "not ready" otherwise,
* so an epoll(timeout=-1) main loop wakes at a fixed cadence and parks
* in between rather than spinning (always-ready) or deadlocking
* (never-ready). Deliberately not POLLOUT: a constant writable mask
* would re-spin a loop that also watches EPOLLOUT. */
if (atomic_xchg(&pe->poll_ready, 0))
return POLLIN | POLLRDNORM;
return 0;
}

if (pe->python_poll)
return pe->python_poll(file, pt);
/* No data-aware poll modeled -> legacy always-ready behavior. */
Expand Down Expand Up @@ -683,6 +733,10 @@ void handle_op_devfs_create_device(portal_region *mem_region)
pe->python_poll = req->ops.poll;
pe->python_write = req->ops.write;
init_waitqueue_head(&pe->poll_wq);
// periodic poll heartbeat cadence (0 = disabled); the timer itself is armed
// only on the char-device path (block devices have no poll proxy).
pe->poll_interval_ms = req->poll_interval_ms;
atomic_set(&pe->poll_ready, 0);
pe->is_block = req->is_block;
pe->mode = req->mode ? req->mode : 0666;

Expand Down Expand Up @@ -825,6 +879,18 @@ void handle_op_devfs_create_device(portal_region *mem_region)
if (!dynamic_chrdev_region) unregister_chrdev_region(devt, 1);
goto fail_alloc;
}

// Arm the periodic poll heartbeat now the entry is fully live. Never
// cancelled: devfs entries persist for the module's lifetime (there is
// no per-entry teardown), matching this timer's self-rearming lifetime.
if (pe->poll_interval_ms > 0) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
timer_setup(&pe->poll_timer, igloo_devfs_poll_timer_fn, 0);
#else
setup_timer(&pe->poll_timer, igloo_devfs_poll_timer_fn, (unsigned long)pe);
#endif
mod_timer(&pe->poll_timer, jiffies + msecs_to_jiffies(pe->poll_interval_ms));
}
}

/* Track it */
Expand Down
39 changes: 34 additions & 5 deletions src/portal/portal_sysctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ static struct ctl_table *igloo_find_sysctl_leaf(const char *dir_path, const char
// is the operation that panics on old kernels (see the guard in
// handle_op_sysctl_create_file), so we only ever do it under a parent we know
// is real.
static bool igloo_sysctl_dir_exists(const char *dir_path)
static bool __maybe_unused igloo_sysctl_dir_exists(const char *dir_path)
{
struct igloo_ctl_table_root *sysctl_root;
struct igloo_ctl_dir *curr_dir = NULL;
Expand Down Expand Up @@ -354,6 +354,30 @@ static bool igloo_sysctl_dir_exists(const char *dir_path)
return true;
}

/* Subtrees of /proc/sys that are separate mounted filesystems, not sysctls
* (binfmt_misc is the canonical example). register_sysctl() cannot create a
* ctl_dir inside these, and on pre-5.0 kernels the failed attempt faults in the
* cleanup path (drop_sysctl_table()->rb_erase() NULL deref). This mirrors the
* Python _NON_SYSCTL_SUBTREES guard in hyperfile/sysctl.py, which normally
* rejects them before they reach us; this is the driver-side backstop. A
* genuinely new *sysctl* directory (e.g. a vendor-specific boot-environment
* directory) is created fine by register_sysctl and must NOT be refused. */
static bool igloo_sysctl_dir_is_fs_backed(const char *dir_path)
{
static const char * const fs_backed[] = { "fs/binfmt_misc" };
size_t i;

if (!dir_path || !dir_path[0])
return false;
for (i = 0; i < ARRAY_SIZE(fs_backed); i++) {
size_t n = strlen(fs_backed[i]);
if (strncmp(dir_path, fs_backed[i], n) == 0 &&
(dir_path[n] == '\0' || dir_path[n] == '/'))
return true;
}
return false;
}

void handle_op_sysctl_create_file(portal_region *mem_region)
{
struct portal_sysctl_create_req *req = (struct portal_sysctl_create_req *)PORTAL_DATA(mem_region);
Expand Down Expand Up @@ -442,12 +466,17 @@ void handle_op_sysctl_create_file(portal_region *mem_region)
// when it has to create a new directory hierarchy and an intermediate
// component cannot be created -- e.g. anything under the filesystem-backed
// /proc/sys/fs/binfmt_misc node, which returns -EROFS and then faults in the
// cleanup path. Refuse to create an entry whose parent directory does not
// already exist instead of handing the kernel a doomed registration.
// cleanup path. That failure mode is specific to filesystem-backed, non-sysctl
// subtrees; a genuinely new *sysctl* directory (vendor-specific dirs, e.g. a
// bootloader environment exposed under /proc/sys, common in embedded
// firmware) is created safely by register_sysctl(). So refuse only the
// filesystem-backed subtrees, not every
// not-yet-existing parent -- the previous broad existence check wrongly
// dropped legitimate vendor sysctls and hung boots that poll for them.
// Newer kernels return NULL from register_sysctl() and are handled by the
// !entry->header path below, so this guard is scoped to the affected versions.
if (clean_dir[0] && !igloo_sysctl_dir_exists(clean_dir)) {
printk(KERN_WARNING "portal_sysctl: refusing to create '%s/%s': parent sysctl directory does not exist\n",
if (igloo_sysctl_dir_is_fs_backed(clean_dir)) {
printk(KERN_WARNING "portal_sysctl: refusing to create '%s/%s': parent is a filesystem-backed (non-sysctl) subtree\n",
clean_dir, clean_name);
kfree(entry->data_buffer);
kfree(entry);
Expand Down
Loading