Packages

Experimental libudev NIF for Linux

Current section

Files

Jump to
udev src udev.c
Raw

src/udev.c

#include <libudev.h>
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <linux/kdev_t.h>
#include "erl_nif.h"
/*#define DEBUG*/
#define ASSERT(e) ((void)((e) ? 1 : (assert_error(#e, __func__, __FILE__, __LINE__), 0)))
#define SET_NONBLOCKING(fd) fcntl((fd), F_SETFL, fcntl((fd), F_GETFL, 0) | O_NONBLOCK)
static void assert_error(const char *expr, const char *func, const char *file, int line)
{
fflush(stdout);
fprintf(stderr, "%s:%d:%s() Assertion failed: %s\n", file, line, func, expr);
fflush(stderr);
abort();
}
typedef struct {
struct udev *context;
struct udev_monitor *monitor;
int fd;
int open;
} Monitor;
typedef struct {
ERL_NIF_TERM atom_ok;
ERL_NIF_TERM atom_undefined;
ERL_NIF_TERM atom_error;
ERL_NIF_TERM atom_nil;
ERL_NIF_TERM atom_add;
ERL_NIF_TERM atom_remove;
ERL_NIF_TERM atom_change;
ERL_NIF_TERM atom_online;
ERL_NIF_TERM atom_offline;
ERL_NIF_TERM atom_devnode;
ERL_NIF_TERM atom_subsystem;
ERL_NIF_TERM atom_devtype;
ERL_NIF_TERM atom_major;
ERL_NIF_TERM atom_minor;
ERL_NIF_TERM atom_action;
ERL_NIF_TERM atom_devpath;
ERL_NIF_TERM atom_syspath;
ERL_NIF_TERM atom_sysname;
ERL_NIF_TERM atom_sysnum;
ERL_NIF_TERM atom_vid;
ERL_NIF_TERM atom_pid;
ERL_NIF_TERM atom_man;
ERL_NIF_TERM atom_prod;
ERL_NIF_TERM atom_serial;
ERL_NIF_TERM atom_driver;
ERL_NIF_TERM atom_seqnum;
ERL_NIF_TERM atom_dnf;
ERL_NIF_TERM atom_pnf;
} monitor_priv;
static void mon_rt_dtor(ErlNifEnv *env, void *obj)
{
enif_fprintf(stderr, "mon_rt_dtor called\n");
}
static void mon_rt_stop(ErlNifEnv *env, void *obj, int fd, int is_direct_call)
{
Monitor *rt = (Monitor *)obj;
enif_fprintf(stderr, "mon_rt_stop called %s\n", (is_direct_call ? "DIRECT" : "LATER"));
udev_monitor_unref(rt->monitor);
udev_unref(rt->context);
}
static void mon_rt_down(ErlNifEnv* env, void* obj, ErlNifPid* pid, ErlNifMonitor* mon)
{
Monitor *rt = (Monitor *)obj;
int rv;
enif_fprintf(stderr, "mon_rt_down called\n");
if(rt->open) {
ERL_NIF_TERM undefined;
enif_make_existing_atom(env, "undefined", &undefined, ERL_NIF_LATIN1);
rv = enif_select(env, rt->fd, ERL_NIF_SELECT_STOP, rt, NULL, undefined);
ASSERT(rv >= 0);
}
}
static ErlNifResourceType *mon_rt;
static ErlNifResourceTypeInit mon_rt_init = {mon_rt_dtor, mon_rt_stop, mon_rt_down};
static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) {
monitor_priv* data = enif_alloc(sizeof(monitor_priv));
if (data == NULL) {
return 1;
}
data->atom_ok = enif_make_atom(env, "ok");
data->atom_undefined = enif_make_atom(env, "undefined");
data->atom_error = enif_make_atom(env, "error");
data->atom_nil = enif_make_atom(env, "nil");
data->atom_add = enif_make_atom(env, "add");
data->atom_remove = enif_make_atom(env, "remove");
data->atom_change = enif_make_atom(env, "change");
data->atom_online = enif_make_atom(env, "online");
data->atom_offline = enif_make_atom(env, "offline");
data->atom_devnode = enif_make_atom(env, "devnode");
data->atom_subsystem = enif_make_atom(env, "subsystem");
data->atom_devtype = enif_make_atom(env, "devtype");
data->atom_major = enif_make_atom(env, "major");
data->atom_minor = enif_make_atom(env, "minor");
data->atom_action = enif_make_atom(env, "action");
data->atom_devpath = enif_make_atom(env, "devpath");
data->atom_syspath = enif_make_atom(env, "syspath");
data->atom_sysname = enif_make_atom(env, "sysname");
data->atom_sysnum = enif_make_atom(env, "sysnum");
data->atom_driver = enif_make_atom(env, "driver");
data->atom_seqnum = enif_make_atom(env, "seqnum");
data->atom_dnf = enif_make_atom(env, "device_not_found");
data->atom_pnf = enif_make_atom(env, "parent_not_found");
*priv = (void*) data;
mon_rt = enif_open_resource_type_x(env, "monitor", &mon_rt_init, ERL_NIF_RT_CREATE, NULL);
return !mon_rt;
}
static int reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) {
return 0;
}
static int upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) {
return load(env, priv, info);
}
static void unload(ErlNifEnv* env, void* priv) {
enif_free(priv);
}
static ERL_NIF_TERM mon_start(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
Monitor *mon;
ERL_NIF_TERM res;
int fd;
struct udev *context;
struct udev_monitor *udev_mon;
monitor_priv* priv = enif_priv_data(env);
char name[7];
ErlNifPid self;
if (!enif_get_atom(env, argv[0], name, 7, ERL_NIF_LATIN1)) {
return enif_make_badarg(env);
}
if(strcmp(name, "udev") > 0 && strcmp(name, "kernel") > 0) {
return enif_make_badarg(env);
}
context = udev_new();
if(!context) {
enif_fprintf(stderr, "Can't create udev context\n");
return enif_make_badarg(env);
}
enif_self(env, &self);
udev_mon = udev_monitor_new_from_netlink(context, name);
udev_monitor_enable_receiving(udev_mon);
fd = udev_monitor_get_fd(udev_mon);
SET_NONBLOCKING(fd);
mon = enif_alloc_resource(mon_rt, sizeof(Monitor));
mon->context = context;
mon->monitor = udev_mon;
mon->fd = fd;
mon->open = 1;
enif_monitor_process(env, mon, &self, NULL);
res = enif_make_resource(env, mon);
enif_release_resource(mon);
return enif_make_tuple2(env, priv->atom_ok, res);
}
static ERL_NIF_TERM mon_stop(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
Monitor *mon;
monitor_priv* priv = enif_priv_data(env);
int do_stop;
if(!enif_get_resource(env, argv[0], mon_rt, (void **)&mon)) {
return enif_make_badarg(env);
}
do_stop = mon->open;
mon->open = 0;
if(do_stop) {
enif_fprintf(stderr, "Closing fd=%d\n", mon->fd);
int rv = enif_select(env, mon->fd, ERL_NIF_SELECT_STOP, mon, NULL, priv->atom_undefined);
ASSERT(rv >= 0);
}
return priv->atom_ok;
}
static ERL_NIF_TERM mon_poll(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
Monitor *mon;
int rv;
monitor_priv* priv = enif_priv_data(env);
if(!enif_get_resource(env, argv[0], mon_rt, (void **)&mon)) {
return enif_make_badarg(env);
}
/*enif_fprintf(stderr, "Polling fd=%d\n", mon->fd);*/
rv = enif_select(env, mon->fd, ERL_NIF_SELECT_READ, mon, NULL, priv->atom_undefined);
ASSERT(rv >= 0);
return priv->atom_ok;
}
static int map_put_string(ErlNifEnv *env, ERL_NIF_TERM map_in, ERL_NIF_TERM* map_out, ERL_NIF_TERM key, const char *value)
{
ErlNifBinary bin;
monitor_priv* priv = enif_priv_data(env);
if(value == NULL) {
return enif_make_map_put(env, map_in, key, priv->atom_nil, map_out);
}
enif_alloc_binary(strlen(value), &bin);
memcpy(bin.data, value, strlen(value));
return enif_make_map_put(env, map_in, key, enif_make_binary(env, &bin), map_out);
}
static int map_put(ErlNifEnv *env, ERL_NIF_TERM map_in, ERL_NIF_TERM* map_out, ERL_NIF_TERM key, ERL_NIF_TERM value)
{
return enif_make_map_put(env, map_in, key, value, map_out);
}
static ERL_NIF_TERM build_device_map(ErlNifEnv *env, struct udev_device *dev)
{
monitor_priv* priv = enif_priv_data(env);
ERL_NIF_TERM map = enif_make_new_map(env);
dev_t devnum = udev_device_get_devnum(dev);
ERL_NIF_TERM atom_action;
const char *action = udev_device_get_action(dev);
if(action) {
enif_make_existing_atom(env, action, &atom_action, ERL_NIF_LATIN1);
map_put(env, map, &map, priv->atom_action, atom_action);
} else {
map_put(env, map, &map, priv->atom_action, priv->atom_nil);
}
map_put(env, map, &map, priv->atom_seqnum, enif_make_long(env, udev_device_get_seqnum(dev)));
map_put_string(env, map, &map, priv->atom_devnode, udev_device_get_devnode(dev));
map_put_string(env, map, &map, priv->atom_subsystem, udev_device_get_subsystem(dev));
map_put_string(env, map, &map, priv->atom_devtype, udev_device_get_devtype(dev));
map_put_string(env, map, &map, priv->atom_devpath, udev_device_get_devpath(dev));
map_put_string(env, map, &map, priv->atom_syspath, udev_device_get_syspath(dev));
map_put_string(env, map, &map, priv->atom_sysname, udev_device_get_sysname(dev));
map_put_string(env, map, &map, priv->atom_sysnum, udev_device_get_sysnum(dev));
map_put_string(env, map, &map, priv->atom_driver, udev_device_get_driver(dev));
map_put(env, map, &map, priv->atom_major, enif_make_uint(env, MAJOR(devnum)));
map_put(env, map, &map, priv->atom_minor, enif_make_uint(env, MINOR(devnum)));
return map;
}
static ERL_NIF_TERM mon_receive_device(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
Monitor *mon;
struct udev_device *dev;
monitor_priv* priv = enif_priv_data(env);
if(!enif_get_resource(env, argv[0], mon_rt, (void **)&mon)) {
return enif_make_badarg(env);
}
/*enif_fprintf(stderr, "Reading fd=%d\n", mon->fd);*/
dev = udev_monitor_receive_device(mon->monitor);
if(dev) {
ERL_NIF_TERM map = build_device_map(env, dev);
udev_device_unref(dev);
return map;
}
return priv->atom_error;
}
static ERL_NIF_TERM dev_get_parent_sub_devtype(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct udev *context;
struct udev_device *device;
struct udev_device *parent;
monitor_priv* priv = enif_priv_data(env);
char path[255], subsystem[255], devtype[255];
(void)memset(&path, '\0', sizeof(path));
(void)memset(&subsystem, '\0', sizeof(subsystem));
(void)memset(&devtype, '\0', sizeof(devtype));
if(enif_get_string(env, argv[0], path, sizeof(path), ERL_NIF_LATIN1) < 1) {
return enif_make_badarg(env);
}
if(enif_get_string(env, argv[1], subsystem, sizeof(subsystem), ERL_NIF_LATIN1) < 1) {
return enif_make_badarg(env);
}
if(enif_get_string(env, argv[2], devtype, sizeof(devtype), ERL_NIF_LATIN1) < 1) {
return enif_make_badarg(env);
}
context = udev_new();
if(!context) {
enif_fprintf(stderr, "Can't create udev context\n");
return enif_make_badarg(env);
}
device = udev_device_new_from_syspath(context, path);
if(!device) {
return enif_make_tuple2(env, priv->atom_error, priv->atom_dnf);
}
parent = udev_device_get_parent_with_subsystem_devtype(device, subsystem, devtype);
if(!parent) {
return enif_make_tuple2(env, priv->atom_error, priv->atom_pnf);
}
ERL_NIF_TERM map = build_device_map(env, parent);
udev_device_unref(device);
udev_device_unref(parent);
udev_unref(context);
return enif_make_tuple2(env, priv->atom_ok, map);
}
static ERL_NIF_TERM dev_new_from_syspath(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct udev *context;
struct udev_device *device;
monitor_priv* priv = enif_priv_data(env);
char path[255];
(void)memset(&path, '\0', sizeof(path));
if(enif_get_string(env, argv[0], path, sizeof(path), ERL_NIF_LATIN1) < 1) {
return enif_make_badarg(env);
}
context = udev_new();
if(!context) {
enif_fprintf(stderr, "Can't create udev context\n");
return enif_make_badarg(env);
}
device = udev_device_new_from_syspath(context, path);
if(!device) {
return enif_make_tuple2(env, priv->atom_error, priv->atom_dnf);
}
ERL_NIF_TERM map = build_device_map(env, device);
udev_device_unref(device);
udev_unref(context);
return enif_make_tuple2(env, priv->atom_ok, map);
}
static ErlNifFunc nif_funcs[] = {
{"start", 1, mon_start},
{"stop", 1, mon_stop},
{"poll", 1, mon_poll},
{"receive_device", 1, mon_receive_device},
{"get_parent_with_subsystem_devtype", 3, dev_get_parent_sub_devtype},
{"new_from_syspath", 1, dev_new_from_syspath}
};
ERL_NIF_INIT(Elixir.Udev, nif_funcs, &load, &reload, &upgrade, &unload)