Packages
nerves_runtime
0.11.1
0.13.13
0.13.12
0.13.11
retired
0.13.10
0.13.9
0.13.8
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.10
0.11.9
0.11.8
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
retired
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.3
0.5.2
0.5.1
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.1
0.3.0
0.2.0
0.1.2
0.1.1
0.1.0
Small, general runtime utilities for Nerves devices
Current section
Files
Jump to
Current section
Files
src/uevent.c
/*
* Copyright 2016 Nerves
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "utils.h"
#include <ctype.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <libmnl/libmnl.h>
#include <linux/rtnetlink.h>
#include <linux/limits.h>
#include <ei.h>
static void erlcmd_write_header_len(char *response, size_t len)
{
uint16_t be_len = htons(len - sizeof(uint16_t));
memcpy(response, &be_len, sizeof(be_len));
}
static void write_all(char *response, size_t len)
{
size_t wrote = 0;
do {
ssize_t amount_written = write(STDOUT_FILENO, response + wrote, len - wrote);
if (amount_written < 0) {
if (errno == EINTR)
continue;
err(EXIT_FAILURE, "write");
}
wrote += amount_written;
} while (wrote < len);
}
static struct mnl_socket *uevent_open()
{
struct mnl_socket *nl_uevent = mnl_socket_open2(NETLINK_KOBJECT_UEVENT, O_NONBLOCK | O_CLOEXEC);
if (!nl_uevent)
err(EXIT_FAILURE, "mnl_socket_open (NETLINK_KOBJECT_UEVENT)");
// There is one single group in kobject over netlink
if (mnl_socket_bind(nl_uevent, (1 << 0), MNL_SOCKET_AUTOPID) < 0)
err(EXIT_FAILURE, "mnl_socket_bind");
// Turn off ENOBUFS notifications since there's nothing that we can do
// about them.
unsigned int val = 1;
if (mnl_socket_setsockopt(nl_uevent, NETLINK_NO_ENOBUFS, &val, sizeof(val)) < 0)
err(EXIT_FAILURE, "mnl_socket_setsockopt(NETLINK_NO_ENOBUFS)");
return nl_uevent;
}
static void str_tolower(char *str)
{
for (; *str; str++)
*str = tolower(*str);
}
static int ei_encode_elixir_string(char *buf, int *index, const char *p)
{
size_t len = strlen(p);
return ei_encode_binary(buf, index, p, len);
}
static int ei_encode_devpath(char * buf, int *index, char *devpath, char **end_devpath)
{
// The devpath is of the form "/devices/something/something_else/etc"
//
// Encode it as: ["devices", "something", "something_else", "etc"]
// Skip the root slash
devpath++;
#define MAX_SEGMENTS 32
char *segments[MAX_SEGMENTS];
segments[0] = devpath;
int segment_ix = 1;
char *p = devpath;
while (*p != '\0') {
if (*p == '/') {
*p = '\0';
p++;
segments[segment_ix] = p;
segment_ix++;
if (segment_ix >= MAX_SEGMENTS) {
while (*p != '\0') p++;
break;
}
} else {
p++;
}
}
*end_devpath = p + 1;
ei_encode_list_header(buf, index, segment_ix);
for (int ix = 0; ix < segment_ix; ix++)
ei_encode_elixir_string(buf, index, segments[ix]);
return ei_encode_empty_list(buf, index);
}
static int nl_uevent_process_one(struct mnl_socket *nl_uevent, char *resp)
{
char nlbuf[8192]; // See MNL_SOCKET_BUFFER_SIZE
int bytecount = mnl_socket_recvfrom(nl_uevent, nlbuf, sizeof(nlbuf));
if (bytecount <= 0) {
if (errno == EAGAIN)
return -1;
else
err(EXIT_FAILURE, "mnl_socket_recvfrom");
}
char *str = nlbuf;
char *str_end = str + bytecount;
debug("uevent: %s", str);
int resp_index = sizeof(uint16_t); // Skip over payload size
ei_encode_version(resp, &resp_index);
// The uevent comes in with the form:
//
// "action@devpath\0ACTION=action\0DEVPATH=devpath\0KEY=value\0"
//
// Construct the tuple for Elixir:
// {action, devpath, kv_map}
//
// The kv_map contains all of the kv pairs in the uevent except
// ACTION, DEVPATH, SEQNUM, SYNTH_UUID.
ei_encode_tuple_header(resp, &resp_index, 3);
char *atsign = strchr(str, '@');
if (!atsign)
return 0;
*atsign = '\0';
// action
ei_encode_elixir_string(resp, &resp_index, str);
// devpath - filter anything that's not under "/devices"
str = atsign + 1;
if (strncmp("/devices", str, 8) != 0)
return 0;
ei_encode_devpath(resp, &resp_index, str, &str);
#define MAX_KV_PAIRS 16
int kvpairs_count = 0;
char *keys[MAX_KV_PAIRS];
char *values[MAX_KV_PAIRS];
for (; str < str_end; str += strlen(str) + 1) {
// Don't encode these keys in the map:
//
// ACTION: already delivered
// DEVPATH: already delivered
// SEQNUM: unused in Elixir
// SYNTH_UUID: unused in Elixir (when Elixir triggers synthetic events, it currently doesn't set a UUID)
if (strncmp("ACTION=", str, 7) == 0 ||
strncmp("DEVPATH=", str, 8) == 0 ||
strncmp("SEQNUM=", str, 7) == 0 ||
strncmp("SYNTH_UUID=", str, 11) == 0)
continue;
char *equalsign = strchr(str, '=');
if (!equalsign)
continue;
*equalsign = '\0';
// We like lowercase keys
str_tolower(str);
keys[kvpairs_count] = str;
values[kvpairs_count] = equalsign + 1;
kvpairs_count++;
}
ei_encode_map_header(resp, &resp_index, kvpairs_count);
for (int i = 0; i < kvpairs_count; i++) {
ei_encode_elixir_string(resp, &resp_index, keys[i]);
ei_encode_elixir_string(resp, &resp_index, values[i]);
}
erlcmd_write_header_len(resp, resp_index);
return resp_index;
}
static void nl_uevent_process_all(struct mnl_socket *nl_uevent)
{
// Erlang response buffer
char resp[8192];
size_t resp_index;
// Process uevents until there aren't any more or we're
// within 1K of the end. This is pretty conservative since
// the Erlang reports looks like they're nearly always < 200 bytes.
for (resp_index = 0; resp_index < sizeof(resp) - 1024;) {
int bytes_added = nl_uevent_process_one(nl_uevent, &resp[resp_index]);
if (bytes_added < 0)
break;
resp_index += bytes_added;
}
if (resp_index > 0)
write_all(resp, resp_index);
}
static int filter(const struct dirent *dirp)
{
return (dirp->d_type == DT_REG && strcmp(dirp->d_name, "uevent") == 0) ||
(dirp->d_type == DT_DIR && dirp->d_name[0] != '.');
}
static void scandirs(char *path, int path_end)
{
struct dirent **namelist;
int n;
n = scandir(path, &namelist, filter, NULL);
if (n < 0)
return;
path[path_end] = '/';
int i;
for (i = 0; i < n; i++) {
strcpy(&path[path_end + 1], namelist[i]->d_name);
if (namelist[i]->d_type == DT_DIR) {
scandirs(path, strlen(path));
} else {
int fd = open(path, O_WRONLY);
if (fd >= 0) {
int result = write(fd, "add", 3);
if (result < 0)
debug("Ignoring error when writing to %s", path);
close(fd);
}
}
free(namelist[i]);
}
free(namelist);
path[path_end] = 0;
}
static void discovery_exit_handler(int signum)
{
// Call wait on the child so that it's not a zombie, but ignore whether
// it exited successfully.
int status;
wait(&status);
}
static void uevent_discover()
{
// Fork the discover work into a separate process so that it can occur in
// parallel with sending events back.
pid_t pid = fork();
if (pid == 0) {
char path[PATH_MAX] = "/sys/devices";
scandirs(path, strlen(path));
exit(EXIT_SUCCESS);
}
}
int uevent_main(int argc, char *argv[])
{
(void) argc;
(void) argv;
struct sigaction act;
act.sa_handler = discovery_exit_handler;
sigemptyset (&act.sa_mask);
act.sa_flags = 0;
sigaction (SIGCHLD, &act, NULL);
struct mnl_socket *nl_uevent = uevent_open();
// It's necessary to run the discovery process after every start to avoid
// missing device additions. Removals between restarts can still be missed.
// This is unhandled, but less of an issue since restarts should be rare
// and removed devices usually cause errors against anything using them.
uevent_discover();
for (;;) {
struct pollfd fdset[2];
fdset[0].fd = mnl_socket_get_fd(nl_uevent);
fdset[0].events = POLLIN;
fdset[0].revents = 0;
fdset[1].fd = STDIN_FILENO;
fdset[1].events = POLLIN;
fdset[1].revents = 0;
int rc = poll(fdset, 2, -1);
if (rc < 0) {
// Retry if EINTR
if (errno == EINTR)
continue;
err(EXIT_FAILURE, "poll");
}
if (fdset[0].revents & (POLLIN | POLLHUP))
nl_uevent_process_all(nl_uevent);
// Any notification from Erlang is to exit
if (fdset[1].revents & (POLLIN | POLLHUP))
break;
}
mnl_socket_close(nl_uevent);
return 0;
}