Files
scoutfs/kmod/src/tseq.c
Auke Kok ad82a5e52a Squelch warning from bpf_iter.c.
v5.7-rc2-1174-gfd4f12bc38c3 significantly rewrites the bpf iterator
which hits this _next() function. It also adds a check that verifies
that the *pos is incremented after every call, even if it goes beyond
the last member (in which case it's not used).

Signed-off-by: Auke Kok <auke.kok@versity.com>
2024-10-03 12:41:05 -07:00

250 lines
5.9 KiB
C

/*
* Copyright (C) 2018 Versity Software, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License v2 as published by the Free Software Foundation.
*
* This program 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.
*/
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/debugfs.h>
#include <linux/rbtree_augmented.h>
#include "tseq.h"
/*
* This trivial seq file wrapper takes care of the details of displaying
* a set of objects in seq file output. We use an augmented rbtree to
* add new objects at the next free file position. The caller takes
* care of the object life times, only debugfs file creation can fail.
*/
static loff_t tseq_node_total(struct rb_node *node)
{
struct scoutfs_tseq_entry *ent;
if (node == NULL)
return 0;
ent = rb_entry(node, struct scoutfs_tseq_entry, node);
return ent->total;
}
static struct scoutfs_tseq_entry *tseq_rb_next(struct scoutfs_tseq_entry *ent)
{
struct rb_node *node = rb_next(&ent->node);
if (node == NULL)
return NULL;
return rb_entry(node, struct scoutfs_tseq_entry, node);
}
#ifdef KC_RB_TREE_AUGMENTED_COMPUTE_MAX
static bool tseq_compute_total(struct scoutfs_tseq_entry *ent, bool exit)
{
loff_t total = 1 + tseq_node_total(ent->node.rb_left) +
tseq_node_total(ent->node.rb_right);
if (exit && ent->total == total)
return true;
ent->total = total;
return false;
}
RB_DECLARE_CALLBACKS(static, tseq_rb_callbacks, struct scoutfs_tseq_entry,
node, total, tseq_compute_total);
#else
static loff_t tseq_compute_total(struct scoutfs_tseq_entry *ent)
{
return 1 + tseq_node_total(ent->node.rb_left) +
tseq_node_total(ent->node.rb_right);
}
RB_DECLARE_CALLBACKS(static, tseq_rb_callbacks, struct scoutfs_tseq_entry,
node, loff_t, total, tseq_compute_total);
#endif
void scoutfs_tseq_tree_init(struct scoutfs_tseq_tree *tree,
scoutfs_tseq_show_t show)
{
spin_lock_init(&tree->lock);
tree->root = RB_ROOT;
tree->show = show;
}
/*
* Descend towards the leaf node that should be the parent for inserting
* a new entry.
*
* We use the augmented subtree totals to see when a left subtree has
* fewer entries than the current entry's pos which tells us that there
* is a lesser free pos.
*
* If there isn't a lesser free pos then we descend to the right and set
* the minimum possible pos to the pos after the entry we're traversing.
*/
void scoutfs_tseq_add(struct scoutfs_tseq_tree *tree,
struct scoutfs_tseq_entry *ins)
{
struct scoutfs_tseq_entry *ent;
struct rb_node *parent;
struct rb_node **node;
loff_t min_pos;
spin_lock(&tree->lock);
node = &tree->root.rb_node;
parent = NULL;
min_pos = 0;
while (*node) {
parent = *node;
ent = rb_entry(*node, struct scoutfs_tseq_entry, node);
ent->total++;
if (min_pos + tseq_node_total(ent->node.rb_left) < ent->pos) {
node = &ent->node.rb_left;
} else {
min_pos = ent->pos + 1;
node = &ent->node.rb_right;
}
}
ins->pos = min_pos;
ins->total = 1;
rb_link_node(&ins->node, parent, node);
rb_insert_augmented(&ins->node, &tree->root, &tseq_rb_callbacks);
spin_unlock(&tree->lock);
}
static struct scoutfs_tseq_entry *tseq_pos_next(struct scoutfs_tseq_tree *tree,
loff_t pos)
{
struct scoutfs_tseq_entry *next;
struct scoutfs_tseq_entry *ent;
struct rb_node *node;
assert_spin_locked(&tree->lock);
node = tree->root.rb_node;
next = NULL;
while (node) {
ent = rb_entry(node, struct scoutfs_tseq_entry, node);
if (pos < ent->pos) {
next = ent;
node = ent->node.rb_left;
} else if (pos > ent->pos) {
node = ent->node.rb_right;
} else {
return ent;
}
}
return next;
}
void scoutfs_tseq_del(struct scoutfs_tseq_tree *tree,
struct scoutfs_tseq_entry *ent)
{
spin_lock(&tree->lock);
rb_erase_augmented(&ent->node, &tree->root, &tseq_rb_callbacks);
RB_CLEAR_NODE(&ent->node);
spin_unlock(&tree->lock);
}
/* _stop is always called no matter what start returns */
static void *scoutfs_tseq_seq_start(struct seq_file *m, loff_t *pos)
__acquires(tree->lock)
{
struct scoutfs_tseq_tree *tree = m->private;
spin_lock(&tree->lock);
return tseq_pos_next(tree, *pos);
}
static void *scoutfs_tseq_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
struct scoutfs_tseq_entry *ent = v;
ent = tseq_rb_next(ent);
if (ent)
*pos = ent->pos;
else
/*
* once we hit the end, *pos is never used, but it has to
* be updated to avoid an error in bpf_seq_read()
*/
(*pos)++;
return ent;
}
static void scoutfs_tseq_seq_stop(struct seq_file *m, void *v)
__releases(tree->lock)
{
struct scoutfs_tseq_tree *tree = m->private;
spin_unlock(&tree->lock);
}
static int scoutfs_tseq_seq_show(struct seq_file *m, void *v)
{
struct scoutfs_tseq_tree *tree = m->private;
struct scoutfs_tseq_entry *ent = v;
tree->show(m, ent);
return 0;
}
static const struct seq_operations scoutfs_tseq_seq_ops = {
.start = scoutfs_tseq_seq_start,
.next = scoutfs_tseq_seq_next,
.stop = scoutfs_tseq_seq_stop,
.show = scoutfs_tseq_seq_show,
};
static int scoutfs_tseq_open(struct inode *inode, struct file *file)
{
struct seq_file *m;
int ret;
ret = seq_open(file, &scoutfs_tseq_seq_ops);
if (ret == 0) {
m = file->private_data;
m->private = inode->i_private;
}
return ret;
}
static const struct file_operations scoutfs_tseq_fops = {
.open = scoutfs_tseq_open,
.release = seq_release,
.read = seq_read,
.llseek = seq_lseek,
};
/*
* This doesn't create any additional state so the returned dentry
* can be destroyed with the usual debugfs file calls.
*/
struct dentry *scoutfs_tseq_create(const char *name, struct dentry *parent,
struct scoutfs_tseq_tree *tree)
{
return debugfs_create_file(name, S_IFREG|S_IRUSR, parent, tree,
&scoutfs_tseq_fops);
}