Skip to content

Instantly share code, notes, and snippets.

@binarycraft007
Last active July 11, 2026 19:21
Show Gist options
  • Select an option

  • Save binarycraft007/e81aa94625a7b745072e94eb0c0fef9d to your computer and use it in GitHub Desktop.

Select an option

Save binarycraft007/e81aa94625a7b745072e94eb0c0fef9d to your computer and use it in GitHub Desktop.
udev hid bpf cli tool
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <ftw.h>
#include <libudev.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <bpf/btf.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <linux/limits.h>
#include <dirent.h>
/* Constants and Structs */
#define HID_MAX_DESCRIPTOR_SIZE 4096
#define BPF_FS_PATH "/sys/fs/bpf/hid"
struct hid_bpf_probe_args {
unsigned int hid;
unsigned int rdesc_size;
unsigned char rdesc[HID_MAX_DESCRIPTOR_SIZE];
int retval;
};
struct attach_prog_args {
int prog_fd;
unsigned int hid;
int retval;
};
static int mkdir_p(const char *path)
{
char tmp[PATH_MAX];
char *p = NULL;
size_t len;
snprintf(tmp, sizeof(tmp), "%s", path);
len = strlen(tmp);
if (tmp[len - 1] == '/')
tmp[len - 1] = 0;
for (p = tmp + 1; *p; p++) {
if (*p == '/') {
*p = 0;
if (mkdir(tmp, 0755) < 0 && errno != EEXIST) {
return -1;
}
*p = '/';
}
}
if (mkdir(tmp, 0755) < 0 && errno != EEXIST) {
return -1;
}
return 0;
}
static int unlink_cb(const char *fpath, const struct stat *sb, int typeflag,
struct FTW *ftwbuf)
{
int rv = remove(fpath);
if (rv)
perror(fpath);
return rv;
}
static int rm_rf(const char *path)
{
if (access(path, F_OK) != 0)
return 0;
return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
}
static int get_hid_id(struct udev_device *dev, unsigned int *id)
{
const char *sysname = udev_device_get_sysname(dev);
if (!sysname)
return -1;
const char *dot = strrchr(sysname, '.');
if (!dot)
return -1;
char *endptr;
unsigned long val = strtoul(dot + 1, &endptr, 16);
if (*endptr != '\0')
return -1;
*id = (unsigned int)val;
return 0;
}
static int get_report_descriptor(const char *sysname, unsigned char *buf,
size_t max_len, unsigned int *size)
{
char path[PATH_MAX];
snprintf(path, sizeof(path),
"/sys/bus/hid/devices/%s/report_descriptor", sysname);
int fd = open(path, O_RDONLY);
if (fd < 0)
return -errno;
ssize_t ret = read(fd, buf, max_len);
close(fd);
if (ret < 0)
return -errno;
*size = (unsigned int)ret;
return 0;
}
static void sanitize_str(char *str)
{
for (; *str; str++) {
if (*str == ':' || *str == '.')
*str = '_';
}
}
static const char *get_filename(const char *path)
{
const char *f = strrchr(path, '/');
return f ? f + 1 : path;
}
static int inject_properties(struct bpf_object *obj, struct udev_device *dev)
{
struct btf *btf = bpf_object__btf(obj);
if (!btf)
return 0;
struct bpf_map *map;
bpf_object__for_each_map(map, obj) {
const char *map_name = bpf_map__name(map);
if (strcmp(map_name, ".bss") != 0 &&
strcmp(map_name, ".data") != 0 &&
strcmp(map_name, ".rodata") != 0) {
continue;
}
int type_id = bpf_map__btf_value_type_id(map);
if (type_id <= 0)
continue;
const struct btf_type *sec = btf__type_by_id(btf, type_id);
if (!sec || btf_kind(sec) != BTF_KIND_DATASEC)
continue;
int num_vars = btf_vlen(sec);
struct btf_var_secinfo *infos = btf_var_secinfos(sec);
size_t value_sz = bpf_map__value_size(map);
void *data = malloc(value_sz);
if (!data)
return -ENOMEM;
int key = 0;
/* Using libbpf high-level API correctly */
if (bpf_map__lookup_elem(map, &key, sizeof(key), data, value_sz,
0) < 0) {
free(data);
continue;
}
bool modified = false;
for (int i = 0; i < num_vars; i++) {
const struct btf_type *var_type =
btf__type_by_id(btf, infos[i].type);
const char *var_name =
btf__name_by_offset(btf, var_type->name_off);
if (strncmp(var_name, "UDEV_PROP_", 10) == 0) {
const char *prop_name = var_name + 10;
const char *prop_val =
udev_device_get_property_value(
dev, prop_name);
if (prop_val) {
size_t prop_len = strlen(prop_val);
size_t var_size = infos[i].size;
size_t offset = infos[i].offset;
if (prop_len < var_size) {
memset((char *)data + offset, 0,
var_size);
memcpy((char *)data + offset,
prop_val, prop_len);
modified = true;
printf("Injected property %s=%s into map %s\n",
prop_name, prop_val,
map_name);
} else {
fprintf(stderr,
"Property %s value too long for BPF variable\n",
prop_name);
}
}
}
}
if (modified) {
if (bpf_map__update_elem(map, &key, sizeof(key), data,
value_sz, BPF_ANY) < 0) {
perror("Failed to update map with properties");
}
}
free(data);
}
return 0;
}
static int load_attach_helper(struct bpf_object **obj_out, int *prog_fd_out)
{
const char *paths[] = { "attach.bpf.o", "src/bpf/attach.bpf.o",
"/usr/libexec/udev-hid-bpf/attach.bpf.o",
NULL };
struct bpf_object *obj = NULL;
int i = 0;
while (paths[i]) {
if (access(paths[i], R_OK) == 0) {
obj = bpf_object__open_file(paths[i], NULL);
if (!libbpf_get_error(obj)) {
break;
}
}
i++;
}
if (!obj) {
fprintf(stderr,
"Failed to locate or open attach.bpf.o helper.\n");
return -1;
}
if (bpf_object__load(obj)) {
fprintf(stderr, "Failed to load helper object.\n");
bpf_object__close(obj);
return -1;
}
struct bpf_program *prog =
bpf_object__find_program_by_name(obj, "attach_prog");
if (!prog) {
fprintf(stderr, "Helper object missing 'attach_prog'.\n");
bpf_object__close(obj);
return -1;
}
*obj_out = obj;
*prog_fd_out = bpf_program__fd(prog);
return 0;
}
static const char *bus_to_string(int bus)
{
switch (bus) {
case 0x01:
return "BUS_PCI";
case 0x02:
return "BUS_ISAPNP";
case 0x03:
return "BUS_USB";
case 0x04:
return "BUS_HIL";
case 0x05:
return "BUS_BLUETOOTH";
case 0x06:
return "BUS_VIRTUAL";
case 0x10:
return "BUS_ISA";
case 0x11:
return "BUS_I8042";
case 0x12:
return "BUS_XTKBD";
case 0x13:
return "BUS_RS232";
case 0x14:
return "BUS_GAMEPORT";
case 0x15:
return "BUS_PARPORT";
case 0x16:
return "BUS_AMIGA";
case 0x17:
return "BUS_ADB";
case 0x18:
return "BUS_I2C";
case 0x19:
return "BUS_HOST";
case 0x1A:
return "BUS_GSC";
case 0x1B:
return "BUS_ATARI";
case 0x1C:
return "BUS_SPI";
case 0x1D:
return "BUS_RMI";
case 0x1E:
return "BUS_CEC";
case 0x1F:
return "BUS_INTEL_ISHTP";
case 0x20:
return "BUS_AMD_SFH";
default:
return NULL;
}
}
static const char *group_to_string(int group)
{
switch (group) {
case 0x01:
return "HID_GROUP_GENERIC";
case 0x02:
return "HID_GROUP_MULTITOUCH";
case 0x03:
return "HID_GROUP_SENSOR_HUB";
case 0x04:
return "HID_GROUP_MULTITOUCH_WIN_8";
case 0x100:
return "HID_GROUP_RMI";
case 0x101:
return "HID_GROUP_WACOM";
case 0x102:
return "HID_GROUP_LOGITECH_DJ_DEVICE";
case 0x103:
return "HID_GROUP_STEAM";
case 0x104:
return "HID_GROUP_LOGITECH_27MHZ_DEVICE";
case 0x105:
return "HID_GROUP_VIVALDI";
default:
return NULL;
}
}
static int cmd_list_devices(void)
{
struct udev *udev;
struct udev_enumerate *enumerate;
struct udev_list_entry *devices, *dev_list_entry;
udev = udev_new();
if (!udev)
return -ENOMEM;
enumerate = udev_enumerate_new(udev);
udev_enumerate_add_match_subsystem(enumerate, "hid");
udev_enumerate_scan_devices(enumerate);
devices = udev_enumerate_get_list_entry(enumerate);
printf("devices:\n");
udev_list_entry_foreach(dev_list_entry, devices)
{
const char *path = udev_list_entry_get_name(dev_list_entry);
struct udev_device *dev =
udev_device_new_from_syspath(udev, path);
if (dev) {
const char *sysname = udev_device_get_sysname(dev);
const char *name =
udev_device_get_property_value(dev, "HID_NAME");
const char *modalias =
udev_device_get_property_value(dev, "MODALIAS");
unsigned int bus = 0, group = 0, vid = 0, pid = 0;
char bus_s[32], group_s[32];
if (modalias) {
sscanf(modalias, "hid:b%Xg%Xv%Xp%X", &bus,
&group, &vid, &pid);
}
const char *b_str = bus_to_string(bus);
if (b_str)
snprintf(bus_s, sizeof(bus_s), "%s", b_str);
else
snprintf(bus_s, sizeof(bus_s), "%04X", bus);
const char *g_str = group_to_string(group);
if (g_str)
snprintf(group_s, sizeof(group_s), "%s", g_str);
else
snprintf(group_s, sizeof(group_s), "%04X",
group);
printf(" - syspath: \"%s\"\n", path);
printf(" name: \"%s\"\n",
name ? name : "UNKNOWN");
printf(" device entry: \"HID_DEVICE(%s, %s, 0x%04X, 0x%04X)\"\n",
bus_s, group_s, vid, pid);
/* Check for BPFs */
char sysname_clean[256];
strncpy(sysname_clean, sysname, sizeof(sysname_clean));
sanitize_str(sysname_clean);
char pin_path[PATH_MAX];
snprintf(pin_path, sizeof(pin_path), "%s/%s",
BPF_FS_PATH, sysname_clean);
if (access(pin_path, F_OK) == 0) {
DIR *dir = opendir(pin_path);
if (dir) {
struct dirent *ent;
int count = 0;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_type == DT_DIR &&
ent->d_name[0] != '.') {
if (count == 0)
printf(" bpfs:\n");
printf(" - \"%s\"\n",
ent->d_name);
count++;
}
}
closedir(dir);
}
}
udev_device_unref(dev);
}
}
udev_enumerate_unref(enumerate);
udev_unref(udev);
return 0;
}
/* Helper to ensure we have the HID device (walks up parents) */
static struct udev_device *ensure_hid_device(struct udev_device *dev)
{
struct udev_device *parent = dev;
udev_device_ref(parent); // Increment ref to handle the loop correctly
while (parent) {
const char *subsystem = udev_device_get_subsystem(parent);
if (subsystem && strcmp(subsystem, "hid") == 0) {
return parent;
}
struct udev_device *old = parent;
parent = udev_device_get_parent_with_subsystem_devtype(
old, "hid", NULL);
if (parent)
udev_device_ref(parent);
udev_device_unref(old);
}
return NULL;
}
static int cmd_add(const char *syspath, const char *bpf_obj_path)
{
struct udev *udev = NULL;
struct udev_device *dev_raw = NULL;
struct udev_device *dev = NULL;
struct bpf_object *obj = NULL;
struct bpf_object *helper_obj = NULL;
int helper_fd = -1;
unsigned int hid_id = 0;
int err = 0;
udev = udev_new();
if (!udev)
return -ENOMEM;
dev_raw = udev_device_new_from_syspath(udev, syspath);
if (!dev_raw) {
fprintf(stderr, "Device not found: %s\n", syspath);
err = -ENOENT;
goto cleanup;
}
dev = ensure_hid_device(dev_raw);
udev_device_unref(dev_raw); // We are done with the raw device
if (!dev) {
fprintf(stderr,
"Device %s is not a HID device or has no HID parent.\n",
syspath);
err = -ENODEV;
goto cleanup;
}
const char *sysname_raw = udev_device_get_sysname(dev);
if (get_hid_id(dev, &hid_id) < 0) {
fprintf(stderr, "Invalid HID sysname format: %s\n",
sysname_raw);
err = -EINVAL;
goto cleanup;
}
printf("Target: %s (ID: 0x%04X)\n", sysname_raw, hid_id);
obj = bpf_object__open_file(bpf_obj_path, NULL);
if (libbpf_get_error(obj)) {
fprintf(stderr, "Failed to open BPF object: %s\n",
bpf_obj_path);
err = -ENOENT;
goto cleanup;
}
bool is_struct_ops = false;
struct bpf_program *prog;
bpf_object__for_each_program(prog, obj) {
if (bpf_program__type(prog) == BPF_PROG_TYPE_STRUCT_OPS) {
is_struct_ops = true;
break;
}
}
if (is_struct_ops) {
struct bpf_map *map;
bpf_object__for_each_map(map, obj) {
if (bpf_map__type(map) == BPF_MAP_TYPE_STRUCT_OPS) {
size_t sz = 0;
void *data = bpf_map__initial_value(map, &sz);
if (data && sz >= sizeof(hid_id)) {
memcpy(data, &hid_id, sizeof(hid_id));
} else {
fprintf(stderr,
"Error: struct_ops map too small or no initial value.\n");
}
}
}
}
if (bpf_object__load(obj)) {
fprintf(stderr, "Failed to load BPF object.\n");
err = -EINVAL;
goto cleanup;
}
if (inject_properties(obj, dev) < 0) {
fprintf(stderr, "Warning: Failed to inject properties.\n");
}
struct bpf_program *probe_prog =
bpf_object__find_program_by_name(obj, "probe");
if (probe_prog) {
struct hid_bpf_probe_args args = { .hid = hid_id,
.retval = -1 };
unsigned int rsize = 0;
if (get_report_descriptor(sysname_raw, args.rdesc,
sizeof(args.rdesc), &rsize) == 0) {
args.rdesc_size = rsize;
struct bpf_test_run_opts opts = {
.sz = sizeof(struct bpf_test_run_opts),
.ctx_in = &args,
.ctx_size_in = sizeof(args),
};
err = bpf_prog_test_run_opts(
bpf_program__fd(probe_prog), &opts);
if (err || opts.retval != 0) {
fprintf(stderr,
"Probe rejected device (ret=%d, err=%d).\n",
opts.retval, err);
err = -EPERM;
goto cleanup;
}
} else {
fprintf(stderr,
"Warning: Could not read report descriptor for probe.\n");
}
}
/* Path Construction matching Rust: /sys/fs/bpf/hid/<sysname_clean>/<objname_clean> */
char sysname_clean[256];
strncpy(sysname_clean, sysname_raw, sizeof(sysname_clean));
sanitize_str(sysname_clean);
char objname_clean[256];
strncpy(objname_clean, get_filename(bpf_obj_path),
sizeof(objname_clean));
// Remove .bpf.o or .o extension if present? Rust does "file_stem".
char *dot = strstr(objname_clean, ".bpf.o");
if (dot)
*dot = 0;
else {
dot = strrchr(objname_clean, '.');
if (dot)
*dot = 0;
}
sanitize_str(objname_clean);
char pin_dir[PATH_MAX];
snprintf(pin_dir, sizeof(pin_dir), "%s/%s/%s", BPF_FS_PATH,
sysname_clean, objname_clean);
if (mkdir_p(pin_dir) < 0) {
perror("Failed to create pin directory");
err = -errno;
goto cleanup;
}
if (is_struct_ops) {
struct bpf_map *map;
bpf_object__for_each_map(map, obj) {
if (bpf_map__type(map) == BPF_MAP_TYPE_STRUCT_OPS) {
struct bpf_link *link =
bpf_map__attach_struct_ops(map);
if (!link) {
fprintf(stderr,
"Failed to attach struct_ops map %s\n",
bpf_map__name(map));
continue;
}
char pin_path[PATH_MAX];
snprintf(pin_path, sizeof(pin_path), "%s/%s",
pin_dir, bpf_map__name(map));
if (bpf_link__pin(link, pin_path)) {
fprintf(stderr,
"Failed to pin link %s\n",
pin_path);
} else {
printf("Attached StructOps: %s\n",
pin_path);
}
}
}
} else {
if (load_attach_helper(&helper_obj, &helper_fd) < 0) {
err = -ENOENT;
goto cleanup;
}
bpf_object__for_each_program(prog, obj) {
if (bpf_program__type(prog) == BPF_PROG_TYPE_TRACING) {
struct attach_prog_args args = {
.prog_fd = bpf_program__fd(prog),
.hid = hid_id,
.retval = -1
};
struct bpf_test_run_opts opts = {
.sz = sizeof(struct bpf_test_run_opts),
.ctx_in = &args,
.ctx_size_in = sizeof(args),
};
err = bpf_prog_test_run_opts(helper_fd, &opts);
if (err) {
fprintf(stderr,
"Attach helper execution failed: %s\n",
strerror(errno));
continue;
}
if (args.retval < 0) {
fprintf(stderr,
"Attach failed for %s (ret=%d)\n",
bpf_program__name(prog),
args.retval);
continue;
}
int link_fd = args.retval;
char pin_path[PATH_MAX];
snprintf(pin_path, sizeof(pin_path), "%s/%s",
pin_dir, bpf_program__name(prog));
if (bpf_obj_pin(link_fd, pin_path)) {
fprintf(stderr,
"Failed to pin link FD to %s\n",
pin_path);
close(link_fd);
} else {
printf("Attached Tracing: %s\n",
pin_path);
}
}
}
}
struct bpf_map *map;
bpf_object__for_each_map(map, obj) {
if (bpf_map__type(map) != BPF_MAP_TYPE_STRUCT_OPS &&
!strchr(bpf_map__name(map), '.') &&
strcmp(bpf_map__name(map), ".bss") != 0 &&
strcmp(bpf_map__name(map), ".data") != 0 &&
strcmp(bpf_map__name(map), ".rodata") != 0) {
char pin_path[PATH_MAX];
snprintf(pin_path, sizeof(pin_path), "%s/%s", pin_dir,
bpf_map__name(map));
if (bpf_map__pin(map, pin_path) == 0) {
printf("Pinned map: %s\n", pin_path);
}
}
}
cleanup:
if (helper_obj)
bpf_object__close(helper_obj);
if (obj)
bpf_object__close(obj);
if (dev)
udev_device_unref(dev);
if (udev)
udev_unref(udev);
return err;
}
static int cmd_remove(const char *syspath)
{
struct udev *udev = NULL;
struct udev_device *dev = NULL;
int err = 0;
udev = udev_new();
if (!udev)
return -ENOMEM;
dev = udev_device_new_from_syspath(udev, syspath);
if (!dev) {
fprintf(stderr, "Device not found: %s\n", syspath);
udev_unref(udev);
return -ENOENT;
}
const char *sysname_raw = udev_device_get_sysname(dev);
char sysname_clean[256];
strncpy(sysname_clean, sysname_raw, sizeof(sysname_clean));
sanitize_str(sysname_clean);
char pin_dir[PATH_MAX];
snprintf(pin_dir, sizeof(pin_dir), "%s/%s", BPF_FS_PATH, sysname_clean);
printf("Detaching BPF programs (removing %s)...\n", pin_dir);
if (rm_rf(pin_dir) < 0) {
perror("Failed to remove BPF directory");
err = -errno;
} else {
printf("Success.\n");
}
udev_device_unref(dev);
udev_unref(udev);
return err;
}
typedef struct {
unsigned int bus;
unsigned int group;
unsigned int vid;
unsigned int pid;
} hid_device_id;
static int parse_hid_bpf_config(struct bpf_object *obj, hid_device_id **ids_out,
int *count_out)
{
struct btf *btf = bpf_object__btf(obj);
if (!btf)
return 0;
int sec_id = btf__find_by_name_kind(btf, ".hid_bpf_config",
BTF_KIND_DATASEC);
if (sec_id < 0)
return 0;
const struct btf_type *sec = btf__type_by_id(btf, sec_id);
if (!sec)
return 0;
struct btf_var_secinfo *infos = btf_var_secinfos(sec);
int num_vars = btf_vlen(sec);
for (int i = 0; i < num_vars; i++) {
const struct btf_type *var =
btf__type_by_id(btf, infos[i].type);
/* Skip typedefs/consts/volatiles to get to the type */
int type_id = var->type;
while (1) {
const struct btf_type *t =
btf__type_by_id(btf, type_id);
if (btf_is_typedef(t) || btf_is_const(t) ||
btf_is_volatile(t)) {
type_id = t->type;
} else {
break;
}
}
const struct btf_type *t = btf__type_by_id(btf, type_id);
if (!btf_is_union(t))
continue;
int num_devices = btf_vlen(t);
const struct btf_member *devices = btf_members(t);
*ids_out = calloc(num_devices, sizeof(hid_device_id));
*count_out = num_devices;
for (int j = 0; j < num_devices; j++) {
int struct_id = devices[j].type;
const struct btf_type *dev_struct =
btf__type_by_id(btf, struct_id);
if (!btf_is_struct(dev_struct))
continue;
const struct btf_member *fields =
btf_members(dev_struct);
int num_fields = btf_vlen(dev_struct);
for (int k = 0; k < num_fields; k++) {
const char *fname = btf__name_by_offset(
btf, fields[k].name_off);
int ptr_id = fields[k].type;
const struct btf_type *ptr =
btf__type_by_id(btf, ptr_id);
if (!btf_is_ptr(ptr))
continue;
const struct btf_type *arr =
btf__type_by_id(btf, ptr->type);
if (!btf_is_array(arr))
continue;
unsigned int val = btf_array(arr)->nelems;
if (strcmp(fname, "bus") == 0)
(*ids_out)[j].bus = val;
else if (strcmp(fname, "group") == 0)
(*ids_out)[j].group = val;
else if (strcmp(fname, "vid") == 0)
(*ids_out)[j].vid = val;
else if (strcmp(fname, "pid") == 0)
(*ids_out)[j].pid = val;
}
}
return 0;
}
return 0;
}
static int cmd_inspect(const char *bpf_obj_path)
{
struct bpf_object *obj = bpf_object__open_file(bpf_obj_path, NULL);
if (libbpf_get_error(obj)) {
fprintf(stderr, "Failed to open %s\n", bpf_obj_path);
return -1;
}
printf("Filename: %s\n", get_filename(bpf_obj_path));
hid_device_id *ids = NULL;
int count = 0;
parse_hid_bpf_config(obj, &ids, &count);
if (count > 0) {
printf("Supported Devices:\n");
for (int i = 0; i < count; i++) {
printf(" - Bus: 0x%04X, Group: 0x%04X, VID: 0x%04X, PID: 0x%04X\n",
ids[i].bus, ids[i].group, ids[i].vid,
ids[i].pid);
}
free(ids);
} else {
printf("Supported Devices: (None found in .hid_bpf_config)\n");
}
printf("Programs:\n");
struct bpf_program *prog;
bpf_object__for_each_program(prog, obj) {
printf(" - %s (%s)\n", bpf_program__name(prog),
bpf_program__section_name(prog));
}
printf("Maps:\n");
struct bpf_map *map;
bpf_object__for_each_map(map, obj) {
printf(" - %s\n", bpf_map__name(map));
}
bpf_object__close(obj);
return 0;
}
static int cmd_install(const char *bpf_obj_path)
{
char clean_name[256];
strncpy(clean_name, get_filename(bpf_obj_path), sizeof(clean_name));
char *dot = strstr(clean_name, ".bpf.o");
if (dot)
*dot = 0;
// 1. Inspect to get devices
struct bpf_object *obj = bpf_object__open_file(bpf_obj_path, NULL);
if (libbpf_get_error(obj)) {
fprintf(stderr, "Failed to open %s\n", bpf_obj_path);
return -1;
}
hid_device_id *ids = NULL;
int count = 0;
parse_hid_bpf_config(obj, &ids, &count);
bpf_object__close(obj);
if (count == 0) {
fprintf(stderr,
"No device matches found in %s, cannot generate udev rule.\n",
bpf_obj_path);
return -1;
}
// 2. Install file
if (mkdir_p("/etc/udev-hid-bpf") < 0) {
perror("Failed to create /etc/udev-hid-bpf");
return -1;
}
char target_path[PATH_MAX];
snprintf(target_path, sizeof(target_path), "/etc/udev-hid-bpf/%s",
get_filename(bpf_obj_path));
char cp_cmd[PATH_MAX * 2];
snprintf(cp_cmd, sizeof(cp_cmd), "cp '%s' '%s'", bpf_obj_path,
target_path);
if (system(cp_cmd) != 0) {
fprintf(stderr, "Failed to copy file to %s\n", target_path);
free(ids);
return -1;
}
printf("Installed BPF object to %s\n", target_path);
// 3. Generate Rule
if (mkdir_p("/etc/udev/rules.d") < 0) {
perror("Failed to create /etc/udev/rules.d");
return -1;
}
char rule_path[PATH_MAX];
snprintf(rule_path, sizeof(rule_path),
"/etc/udev/rules.d/99-hid-bpf-%s.rules", clean_name);
FILE *f = fopen(rule_path, "w");
if (!f) {
perror("Failed to create udev rule file");
free(ids);
return -1;
}
/* Find absolute path of ourselves for the rule */
char self_exe[PATH_MAX];
ssize_t len =
readlink("/proc/self/exe", self_exe, sizeof(self_exe) - 1);
if (len > 0)
self_exe[len] = 0;
else
strcpy(self_exe, "/usr/local/bin/udev-hid-bpf");
fprintf(f, "# Generated by udev-hid-bpf install\n");
fprintf(f, "ACTION!=\"add|remove\", GOTO=\"hid_bpf_end\"\n");
fprintf(f, "SUBSYSTEM!=\"hid\", GOTO=\"hid_bpf_end\"\n\n");
for (int i = 0; i < count; i++) {
char bus_s[10] = "*", group_s[10] = "*", vid_s[10] = "*",
pid_s[10] = "*";
if (ids[i].bus)
snprintf(bus_s, sizeof(bus_s), "%04X", ids[i].bus);
if (ids[i].group)
snprintf(group_s, sizeof(group_s), "%04X",
ids[i].group);
if (ids[i].vid)
snprintf(vid_s, sizeof(vid_s), "%08X", ids[i].vid);
if (ids[i].pid)
snprintf(pid_s, sizeof(pid_s), "%08X", ids[i].pid);
fprintf(f, "# Match %d\n", i);
fprintf(f,
"ENV{MODALIAS}==\"hid:b%sg%sv%sp%s\", RUN+=\"%s add $sys$devpath %s\"\n",
bus_s, group_s, vid_s, pid_s, self_exe, target_path);
fprintf(f,
"ENV{MODALIAS}==\"hid:b%sg%sv%sp%s\", ACTION==\"remove\", RUN+=\"%s remove $sys$devpath\"\n",
bus_s, group_s, vid_s, pid_s, self_exe);
}
fprintf(f, "\nLABEL=\"hid_bpf_end\"\n");
fclose(f);
free(ids);
printf("Installed udev rule to %s\n", rule_path);
printf("Reloading udev rules...\n");
system("udevadm control --reload");
return 0;
}
int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr,
"Usage: %s <add|remove|list-devices|inspect|install> [args...]\n",
argv[0]);
return 1;
}
if (strcmp(argv[1], "add") == 0) {
if (argc < 4) {
fprintf(stderr, "Usage: %s add <syspath> <bpf_obj>\n",
argv[0]);
return 1;
}
return cmd_add(argv[2], argv[3]);
} else if (strcmp(argv[1], "remove") == 0) {
if (argc < 3) {
fprintf(stderr, "Usage: %s remove <syspath>\n",
argv[0]);
return 1;
}
return cmd_remove(argv[2]);
} else if (strcmp(argv[1], "list-devices") == 0) {
return cmd_list_devices();
} else if (strcmp(argv[1], "inspect") == 0) {
if (argc < 3) {
fprintf(stderr, "Usage: %s inspect <bpf_obj>\n",
argv[0]);
return 1;
}
return cmd_inspect(argv[2]);
} else if (strcmp(argv[1], "install") == 0) {
if (argc < 3) {
fprintf(stderr, "Usage: %s install <bpf_obj>\n",
argv[0]);
return 1;
}
return cmd_install(argv[2]);
} else {
fprintf(stderr, "Unknown command: %s\n", argv[1]);
return 1;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment