An ABI-compatible libudev that does not need udevd to be running.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1274 lines
34 KiB

/*
* libudev-fs.c
*
* All modifications to the original source file are:
* - Copyright (C) 2022 Aitor Cuadrado Zubizarreta <aitor_czr@gnuinos.org>
*
* Original copyright and license text produced below.
*/
/*
This file is part of libudev-compat.
Copyright 2015 Jude Nelson (judecn@gmail.com)
libudev-compat is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
libudev-compat 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with libudev-compat; If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#endif
#ifndef _BSD_SOURCE
#define _BSD_SOURCE
#endif
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netpacket/packet.h>
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/filter.h>
#include <sys/inotify.h>
#include <limits.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/sendfile.h>
#include <sys/uio.h>
#include <sys/epoll.h>
#include "libudev.h"
#include "libudev-private.h"
#define UDEV_FS_WATCH_DIR_FLAGS (IN_CREATE | IN_ONESHOT)
#ifdef TEST
#define UDEV_FS_EVENTS_DIR "/tmp/events"
#else
#define UDEV_FS_EVENTS_DIR "/dev/metadata/udev/events"
#endif
// how many monitors can there be per process?
// this is probably big enough.
// email me if it isn't.
#ifndef UDEV_MAX_MONITORS
#define UDEV_MAX_MONITORS 32768
#endif
// We need to make sure that on fork, a udev_monitor listening to the underlying filesystem
// will listen to its *own* process's events directory, at all times. To do this, we will
// track every monitor that exists, by holding a reference internally. When the process
// forks, we'll re-initialize all monitors to point to the new process's events directory.
static struct udev_monitor* g_monitor_table[ UDEV_MAX_MONITORS ];
static int g_monitor_lock = 0; // primitive mutex that can work across forks
static pid_t g_pid = 0; // tells us when to re-target the monitors
// spin-lock to lock the monitor table
static void g_monitor_table_spinlock() {
bool locked = false;
while( 1 ) {
locked = __sync_bool_compare_and_swap( &g_monitor_lock, 0, 1 );
if( locked ) {
break;
}
}
}
// unlock the monitor table
static void g_monitor_table_unlock() {
g_monitor_lock = 0;
// make sure spinners see it.
__sync_synchronize();
}
// on fork(), create a new events directory for each of this process's monitors
// and point them all to them. This way, both the parent and child can continue
// to receive device packets.
// NOTE: can only call async-safe methods
static void udev_monitor_atfork(void) {
int errsv = errno;
int rc = 0;
int i = 0;
int cnt = 0;
pid_t pid = getpid();
struct udev_monitor* monitor = NULL;
int socket_fds[2];
struct epoll_event ev;
write( STDERR_FILENO, "forked! begin split\n", strlen("forked! begin split\n") );
memset( &ev, 0, sizeof(struct epoll_event) );
// reset each monitor's inotify fd to point to a new PID-specific directory instead
g_monitor_table_spinlock();
if( g_pid != pid ) {
// child; do the fork
for( i = 0; i < UDEV_MAX_MONITORS; i++ ) {
if( g_monitor_table[i] == NULL ) {
continue;
}
monitor = g_monitor_table[i];
if( monitor->type != UDEV_MONITOR_TYPE_UDEV ) {
continue;
}
if( monitor->inotify_fd < 0 ) {
continue;
}
if( monitor->epoll_fd < 0 ) {
continue;
}
if( monitor->events_wd < 0 ) {
continue;
}
// reset the socket buffer--the parent will be said to have
// received intermittent events before the child was created.
if( monitor->sock >= 0 ) {
// stop watching this socket--we'll regenerate it later
epoll_ctl( monitor->epoll_fd, EPOLL_CTL_DEL, monitor->sock, NULL );
close( monitor->sock );
monitor->sock = -1;
}
if( monitor->sock_fs >= 0 ) {
close( monitor->sock_fs );
monitor->sock_fs = -1;
}
rc = socketpair( AF_LOCAL, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, socket_fds );
if( rc < 0 ) {
// not much we can do here, except log an error
write( STDERR_FILENO, "Failed to generate a socketpair\n", strlen( "Failed to generate a socketpair\n" ) );
udev_monitor_fs_shutdown( monitor );
g_monitor_table[i] = NULL;
continue;
}
// child's copy of the monitor has its own socketpair
monitor->sock = socket_fds[0];
monitor->sock_fs = socket_fds[1];
// reinstall its filter
udev_monitor_filter_update( monitor );
// watch the child's socket
ev.events = EPOLLIN;
ev.data.fd = monitor->sock;
rc = epoll_ctl( monitor->epoll_fd, EPOLL_CTL_ADD, monitor->sock, &ev );
if( rc < 0 ) {
// not much we can do here, except log an error
write( STDERR_FILENO, "Failed to add monitor socket\n", strlen("Failed to add monitor socket\n") );
udev_monitor_fs_shutdown( monitor );
g_monitor_table[i] = NULL;
continue;
}
// reset the inotify watch
rc = inotify_rm_watch( monitor->inotify_fd, monitor->events_wd );
monitor->events_wd = -1;
if( rc < 0 ) {
rc = -errno;
if( rc == -EINVAL ) {
// monitor->events_wd was invalid
rc = 0;
}
else if( rc == -EBADF ) {
// monitor->inotify_fd is invalid.
// not much we can do here, except log an error
write( STDERR_FILENO, "Invalid inotify handle\n", strlen("Invalid inotify handle"));
udev_monitor_fs_shutdown( monitor );
g_monitor_table[i] = NULL;
continue;
}
}
if( rc == 0 ) {
udev_monitor_fs_events_path( "", monitor->events_dir, i );
// try to create a new directory for this monitor
rc = mkdir( monitor->events_dir, 0700 );
if( rc < 0 ) {
// failed, we have.
// child will not get any notifications from this monitor
rc = -errno;
write( STDERR_FILENO, "Failed to mkdir ", strlen("Failed to mkdir ") );
write( STDERR_FILENO, monitor->events_dir, strlen(monitor->events_dir) );
write( STDERR_FILENO, "\n", 1 );
udev_monitor_fs_shutdown( monitor );
g_monitor_table[i] = NULL;
return;
}
// reconnect to the new directory
monitor->events_wd = inotify_add_watch( monitor->inotify_fd, monitor->events_dir, UDEV_FS_WATCH_DIR_FLAGS );
if( monitor->events_wd < 0 ) {
// there's not much we can safely do here, besides log an error
write( STDERR_FILENO, "Failed to watch ", strlen( "Failed to watch " ) );
write( STDERR_FILENO, monitor->events_dir, strlen(monitor->events_dir) );
write( STDERR_FILENO, "\n", 1 );
}
}
else {
// there's not much we can safely do here, besides log an error
rc = -errno;
write( STDERR_FILENO, "Failed to disconnect!\n", strlen("Failed to disconnect!\n") );
}
}
g_pid = pid;
}
g_monitor_table_unlock();
write( STDERR_FILENO, "end atfork()\n", strlen("end atfork()\n") );
// restore...
errno = errsv;
}
// register a monitor in our list of monitors
// return 0 on success
// return -ENOSPC if we're out of slots
static int udev_monitor_register( struct udev_monitor* monitor ) {
g_monitor_table_spinlock();
// find a free slot
int i = 0;
int rc = -ENOSPC;
for( i = 0; i < UDEV_MAX_MONITORS; i++ ) {
if( g_monitor_table[i] == NULL ) {
g_monitor_table[i] = monitor;
monitor->slot = i;
rc = 0;
break;
}
}
if( g_pid == 0 ) {
// first monitor ever.
// register our fork handler.
g_pid = getpid();
pthread_atfork( NULL, NULL, udev_monitor_atfork );
}
g_monitor_table_unlock();
return rc;
}
// unregister a monitor in our list of monitors
static void udev_monitor_unregister( struct udev_monitor* monitor ) {
if( monitor->slot < 0 ) {
return;
}
g_monitor_table_spinlock();
g_monitor_table[ monitor->slot ] = NULL;
g_monitor_table_unlock();
monitor->slot = -1;
}
// write, but mask EINTR
// return number of bytes written on success
// return -errno on I/O error
ssize_t udev_write_uninterrupted( int fd, char const* buf, size_t len ) {
ssize_t num_written = 0;
if( buf == NULL ) {
return -EINVAL;
}
while( (unsigned)num_written < len ) {
ssize_t nw = write( fd, buf + num_written, len - num_written );
if( nw < 0 ) {
int errsv = -errno;
if( errsv == -EINTR ) {
continue;
}
return errsv;
}
if( nw == 0 ) {
break;
}
num_written += nw;
}
return num_written;
}
// read, but mask EINTR
// return number of bytes read on success
// return -errno on I/O error
// NOTE: must be async-safe!
ssize_t udev_read_uninterrupted( int fd, char* buf, size_t len ) {
ssize_t num_read = 0;
if( buf == NULL ) {
return -EINVAL;
}
while( (unsigned)num_read < len ) {
ssize_t nr = read( fd, buf + num_read, len - num_read );
if( nr < 0 ) {
int errsv = -errno;
if( errsv == -EINTR ) {
continue;
}
return errsv;
}
if( nr == 0 ) {
break;
}
num_read += nr;
}
return num_read;
}
// set up a filesystem monitor
// register pthread_atfork() handlers to ensure that its children
// get their own filesystem monitor state.
// * set up /dev/events/libudev-$PID/
// * start watching /dev/events/libudev-$PID for new files
_public_ int udev_monitor_fs_setup( struct udev_monitor* monitor ) {
int rc = 0;
struct epoll_event ev;
monitor->inotify_fd = -1;
monitor->epoll_fd = -1;
monitor->sock = -1;
monitor->sock_fs = -1;
monitor->slot = -1;
int socket_fd[2] = { -1, -1 };
memset( &monitor->events_dir, 0, PATH_MAX+1 );
memset( &ev, 0, sizeof(struct epoll_event) );
// make sure this monitor can't disappear on us
udev_monitor_register( monitor );
// set up inotify
monitor->inotify_fd = inotify_init1( IN_NONBLOCK | IN_CLOEXEC );
if( monitor->inotify_fd < 0 ) {
rc = -errno;
log_error("inotify_init rc = %d", rc );
udev_monitor_fs_shutdown( monitor );
return rc;
}
// epoll descriptor unifying inotify and event counter
monitor->epoll_fd = epoll_create1( EPOLL_CLOEXEC );
if( monitor->epoll_fd < 0 ) {
rc = -errno;
log_error("epoll_create rc = %d", rc );
udev_monitor_fs_shutdown( monitor );
return rc;
}
// create our monitor directory /dev/events/libudev-$PID
udev_monitor_fs_events_path( "", monitor->events_dir, monitor->slot );
rc = mkdir( monitor->events_dir, 0700 );
if( rc != 0 ) {
rc = -errno;
log_error("mkdir('%s') rc = %d", monitor->events_dir, rc );
udev_monitor_fs_destroy( monitor );
return rc;
}
// begin watching /dev/events/libudev-$PID
monitor->events_wd = inotify_add_watch( monitor->inotify_fd, monitor->events_dir, UDEV_FS_WATCH_DIR_FLAGS );
if( monitor->events_wd < 0 ) {
rc = -errno;
log_error("inotify_add_watch('%s') rc = %d", monitor->events_dir, rc );
udev_monitor_fs_destroy( monitor );
return rc;
}
// set up local socket pair with the parent process
// needs to be a socket (not a pipe) since we're going to attach a BPF to it.
rc = socketpair( AF_LOCAL, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, socket_fd );
if( rc < 0 ) {
rc = -errno;
log_error("socketpair(AF_LOCAL, SOCK_RAW|SOCK_CLOEXEC, 0) rc = %d", rc );
udev_monitor_fs_destroy( monitor );
return rc;
}
monitor->sock = socket_fd[0]; // receiving end
monitor->sock_fs = socket_fd[1]; // sending end
// unify inotify and sock behind epoll_fd, set to poll for reading
ev.events = EPOLLIN;
ev.data.fd = monitor->inotify_fd;
rc = epoll_ctl( monitor->epoll_fd, EPOLL_CTL_ADD, monitor->inotify_fd, &ev );
if( rc != 0 ) {
rc = -errno;
log_error("epoll_ctl(%d on inotify_fd %d) rc = %d", monitor->epoll_fd, monitor->inotify_fd, rc );
udev_monitor_fs_shutdown( monitor );
return rc;
}
ev.data.fd = monitor->sock;
rc = epoll_ctl( monitor->epoll_fd, EPOLL_CTL_ADD, monitor->sock, &ev );
if( rc != 0 ) {
rc = -errno;
log_error("epoll_ctl(%d on inotify_fd %d) rc = %d", monitor->sock, monitor->sock, rc );
udev_monitor_fs_shutdown( monitor );
return rc;
}
monitor->pid = getpid();
return rc;
}
// shut down a monitor's filesystem-specific state
// not much we can do if any shutdown step fails, so try them all
_public_ int udev_monitor_fs_shutdown( struct udev_monitor* monitor ) {
int rc = 0;
// stop tracking this monitor
udev_monitor_unregister( monitor );
if( monitor->sock >= 0 ) {
rc = shutdown( monitor->sock, SHUT_RDWR );
if( rc < 0 ) {
rc = -errno;
log_error("shutdown(socket %d) rc = %d", monitor->sock, rc );
}
}
if( monitor->sock_fs >= 0 ) {
rc = shutdown( monitor->sock_fs, SHUT_RDWR );
if( rc < 0 ) {
rc = -errno;
log_error("shutdown(socket %d) rc = %d", monitor->sock_fs, rc );
}
}
if( monitor->sock >= 0 ) {
rc = close( monitor->sock );
if( rc < 0 ) {
rc = -errno;
log_error("close(socket %d) rc = %d", monitor->sock, rc );
}
else {
monitor->sock = -1;
}
}
if( monitor->sock_fs >= 0 ) {
rc = close( monitor->sock_fs );
if( rc < 0 ) {
rc = -errno;
log_error("close(socket %d) rc = %d", monitor->sock_fs, rc );
}
else {
monitor->sock_fs = -1;
}
}
if( monitor->epoll_fd >= 0 ) {
rc = close( monitor->epoll_fd );
if( rc < 0 ) {
rc = -errno;
log_error("close(epoll_fd %d) rc = %d", monitor->epoll_fd, rc );
}
else {
monitor->epoll_fd = -1;
}
}
if( monitor->events_wd >= 0 ) {
if( monitor->inotify_fd >= 0 ) {
rc = inotify_rm_watch( monitor->inotify_fd, monitor->events_wd );
if( rc < 0 ) {
rc = -errno;
log_error("close(events_wd %d) rc = %d", monitor->events_wd, rc );
}
else {
monitor->events_wd = -1;
}
}
}
if( monitor->inotify_fd >= 0 ) {
rc = close( monitor->inotify_fd );
if( rc < 0 ) {
rc = -errno;
log_error("close(inotify_fd %d) rc = %d", monitor->inotify_fd, rc );
}
else {
monitor->inotify_fd = -1;
}
}
return rc;
}
// blow away all local filesystem state for a monitor
_public_ int udev_monitor_fs_destroy( struct udev_monitor* monitor ) {
char pathbuf[ PATH_MAX+1 ];
int dirfd = 0;
int rc = 0;
DIR* dirh = NULL;
struct dirent entry;
struct dirent* result = NULL;
bool can_rmdir = true;
// stop listening
udev_monitor_fs_shutdown( monitor );
// remove events dir contents
dirfd = open( monitor->events_dir, O_DIRECTORY | O_CLOEXEC );
if( dirfd < 0 ) {
rc = -errno;
log_error("open('%s') rc = %d", monitor->events_dir, rc );
return rc;
}
dirh = fdopendir( dirfd );
if( dirh == NULL ) {
// OOM
rc = -errno;
close( dirfd );
return rc;
}
do {
// next entry
rc = readdir_r( dirh, &entry, &result );
if( rc != 0 ) {
// I/O error
log_error("readdir_r('%s') rc = %d", monitor->events_dir, rc );
break;
}
// skip . and ..
if( strcmp( entry.d_name, "." ) == 0 || strcmp( entry.d_name, ".." ) == 0 ) {
continue;
}
// generate full path
memset( pathbuf, 0, PATH_MAX+1 );
snprintf( pathbuf, PATH_MAX, "%s/%s", monitor->events_dir, entry.d_name );
// optimistically remove
if( entry.d_type == DT_DIR ) {
rc = rmdir( pathbuf );
}
else {
rc = unlink( pathbuf );
}
if( rc != 0 ) {
rc = -errno;
log_error("remove '%s' rc = %d", pathbuf, rc );
can_rmdir = false;
rc = 0;
}
} while( result != NULL );
// NOTE: closes dirfd
closedir( dirh );
if( can_rmdir ) {
rc = rmdir( monitor->events_dir );
if( rc != 0 ) {
rc = -errno;
log_error("rmdir('%s') rc = %d\n", monitor->events_dir, rc);
}
}
else {
// let the caller know...
rc = -ENOTEMPTY;
}
return rc;
}
// thread-safe and async-safe int to string for base 10
void itoa10_safe( int val, char* str ) {
int rc = 0;
int i = 0;
int len = 0;
int j = 0;
bool neg = false;
// sign check
if( val < 0 ) {
val = -val;
neg = true;
}
// consume, lowest-order to highest-order
if( val == 0 ) {
str[i] = '0';
i++;
}
else {
while( val > 0 ) {
int r = val % 10;
str[i] = '0' + r;
i++;
val /= 10;
}
}
if( neg ) {
str[i] = '-';
i++;
}
len = i;
i--;
// reverse order to get the number
while( j < i ) {
char tmp = *(str + i);
*(str + i) = *(str + j);
*(str + j) = tmp;
j++;
i--;
}
str[len] = '\0';
}
// path to a named event for this process to consume
// pathbuf must have at least PATH_MAX bytes
// NOTE: must be async-safe, since it's used in a pthread_atfork() callback
_public_ int udev_monitor_fs_events_path( char const* name, char* pathbuf, int nonce ) {
// do the equivalent of:
// snprintf( pathbuf, PATH_MAX, UDEV_FS_EVENTS_DIR "/libudev-%d-%d/%s", getpid(), nonce, name );
char pidbuf[10];
pidbuf[9] = 0;
char nonce_buf[50];
nonce_buf[49] = 0;
pid_t pid = getpid();
itoa10_safe( pid, pidbuf );
itoa10_safe( nonce, nonce_buf );
size_t pidbuf_len = strnlen(pidbuf, 10);
size_t nonce_buf_len = strnlen( nonce_buf, 50 );
size_t prefix_len = strlen( UDEV_FS_EVENTS_DIR );
size_t dirname_prefix_len = strlen( "/libudev-" );
int off = 0;
memcpy( pathbuf, UDEV_FS_EVENTS_DIR, prefix_len );
off += prefix_len;
memcpy( pathbuf + off, "/libudev-", dirname_prefix_len );
off += dirname_prefix_len;
memcpy( pathbuf + off, pidbuf, pidbuf_len );
off += pidbuf_len;
memcpy( pathbuf + off, "-", 1 );
off += 1;
memcpy( pathbuf + off, nonce_buf, nonce_buf_len );
off += nonce_buf_len;
pathbuf[off] = '/';
off++;
memcpy( pathbuf + off, name, strlen(name) );
off += strlen(name);
pathbuf[off] = '\0';
return off + strlen(name);
}
// send the file descriptor containing a serialized packet to the libudev client:
// NOTE: The file format is expected to be the same as a uevent packet:
// * all newlines (\n) will be converted to null (\0) in the function
// static struct udev_device *udev_monitor_receive_device_fs(struct udev_monitor *udev_monitor)
// of libudev-minitor.c, since that's how the kernel sends it.
// * the buffer is expected to be at most 8192 bytes long.
// return 0 on success
// return -errno on failure
_public_ int udev_monitor_fs_push_event( int fd, struct udev_monitor* monitor ) {
int rc = 0;
off_t offset = 0;
// send it along
rc = sendfile(monitor->sock_fs, fd, &offset, 8192);
if( rc < 0 ) {
rc = -errno;
log_error("udev_monitor_send_device rc = %d", rc );
}
else {
rc = 0;
}
return rc;
}
// reset the oneshot inotify watch, so it will trip on the next create.
// consume pending events, if there are any, and re-watch the directory.
// if the pid has changed since last time, watch the new directory.
// return 0 on success
// return negative on error (errno)
static int udev_monitor_fs_watch_reset( struct udev_monitor* monitor ) {
int rc = 0;
bool inotify_triggerred = false; // was inotify triggerred?
char buf[4096] __attribute__ ((aligned(__alignof__(struct inotify_event)))); // see inotify(7)
struct pollfd pfd[1];
pfd[0].fd = monitor->inotify_fd;
pfd[0].events = POLLIN;
// reset the watch by consuming all its events (should be at most one)
while( 1 ) {
// do we have data?
rc = poll( pfd, 1, 0 );
if( rc <= 0 ) {
// out of data, or error
if( rc < 0 ) {
rc = -errno;
if( rc == -EINTR ) {
// shouldn't really happen since the timeout for poll is zero,
// but you never know...
continue;
}
log_error("poll(%d) rc = %d\n", monitor->inotify_fd, rc );
rc = 0;
}
break;
}
// at least one event remaining
// consume it
rc = read( monitor->inotify_fd, buf, 4096 );
if( rc == 0 ) {
break;
}
if( rc < 0 ) {
rc = -errno;
if( rc == -EINTR ) {
continue;
}
else if( rc == -EAGAIN || rc == -EWOULDBLOCK ) {
rc = 0;
break;
}
}
// got one event
inotify_triggerred = true;
}
// has the PID changed?
// need to regenerate events path
if( getpid() != monitor->pid ) {
log_trace("Switch PID from %d to %d", monitor->pid, getpid());
udev_monitor_fs_events_path( "", monitor->events_dir, monitor->slot );
rc = mkdir( monitor->events_dir, 0700 );
if( rc != 0 ) {
rc = -errno;
if( rc != -EEXIST ) {
log_error("mkdir('%s') rc = %d\n", monitor->events_dir, rc );
return rc;
}
else {
rc = 0;
}
}
monitor->events_wd = inotify_add_watch( monitor->inotify_fd, monitor->events_dir, UDEV_FS_WATCH_DIR_FLAGS );
if( monitor->events_wd < 0 ) {
rc = -errno;
log_error("inotify_add_watch('%s') rc = %d\n", monitor->events_dir, rc );
return rc;
}
monitor->pid = getpid();
// TODO: what about events that the child was supposed to receive?
// the parent forks, receives one or more events, and the child wakes up, and will miss them
// if we only do the above.
// we need to (try to) consume them here
}
rc = inotify_add_watch( monitor->inotify_fd, monitor->events_dir, UDEV_FS_WATCH_DIR_FLAGS );
if( rc < 0 ) {
rc = -errno;
log_error("inotify_add_watch(%d) rc = %d", monitor->inotify_fd, rc );
}
return rc;
}
// scandir: skip . and ..
static int udev_monitor_fs_scandir_filter( const struct dirent* dent ) {
if( strcmp( dent->d_name, "." ) == 0 || strcmp( dent->d_name, ".." ) == 0 ) {
return 0;
}
else {
return 1;
}
}
// push as many available events from our filesystem buffer of events off to the monitor's socketpair.
// the order is determined lexicographically (i.e. we assume that whatever is writing events is naming them in
// a monotonically increasing order, such as e.g. the SEQNUM field)
// return 0 on success
// return -ENODATA if there are no events
// return -EAGAIN if there are events, but we couldn't push any
// return -errno if we can't re-watch the directory
// NOTE: this method should only be used if the underlying filesystem holding the events can't help us preserve the order.
// NOTE: not thread-safe
int udev_monitor_fs_push_events( struct udev_monitor* monitor ) {
char pathbuf[ PATH_MAX+1 ];
int dirfd = -1;
int fd = -1;
int rc = 0;
int num_events = 0;
int num_valid_events = 0;
int num_sent = 0;
struct dirent** events = NULL; // names of events to buffer
int i = 0;
// reset the watch on this directory, and ensure we're watching the right one.
rc = udev_monitor_fs_watch_reset( monitor );
if( rc < 0 ) {
log_error("Failed to re-watch '%s', rc = %d", monitor->events_dir, rc );
goto udev_monitor_fs_push_events_cleanup;
}
// find new events...
dirfd = open( monitor->events_dir, O_DIRECTORY | O_CLOEXEC );
if( dirfd < 0 ) {
rc = -errno;
log_error("open('%s') rc = %d", monitor->events_dir, rc );
goto udev_monitor_fs_push_events_cleanup;
}
num_events = scandirat( dirfd, ".", &events, udev_monitor_fs_scandir_filter, alphasort );
if( num_events < 0 ) {
rc = -errno;
log_error("scandir('%s') rc = %d", monitor->events_dir, rc );
goto udev_monitor_fs_push_events_cleanup;
}
if( num_events == 0 ) {
// got nothing
rc = -ENODATA;
goto udev_monitor_fs_push_events_cleanup;
}
num_valid_events = num_events;
// send them all off!
for( i = 0; i < num_events; i++ ) {
snprintf( pathbuf, PATH_MAX, "%s/%s", monitor->events_dir, events[i]->d_name );
fd = open( pathbuf, O_RDONLY | O_CLOEXEC );
if( fd < 0 ) {
rc = -errno;
log_error("cannot open event: open('%s') rc = %d", pathbuf, rc );
// we consider it more important to preserve order and drop events
// than to try to resend later.
unlink( pathbuf );
}
else {
// propagate to the monitor's socket
rc = udev_monitor_fs_push_event( fd, monitor );
// garbage-collect
close( fd );
unlink( pathbuf );
if( rc == -EBADMSG || rc == -EMSGSIZE ) {
// invalid message anyway
rc = 0;
num_valid_events--;
continue;
}
else if( rc < 0 ) {
if( rc != -EAGAIN ) {
// socket-level error
log_error("failed to push event '%s', rc = %d", pathbuf, rc );
break;
}
else {
// sent as many as we could
rc = 0;
break;
}
}
else if( rc == 0 ) {
num_sent++;
}
}
}
udev_monitor_fs_push_events_cleanup:
if( dirfd >= 0 ) {
close( dirfd );
}
if( events != NULL ) {
for( i = 0; i < num_events; i++ ) {
if( events[i] != NULL ) {
free( events[i] );
events[i] = NULL;
}
}
free( events );
}
if( num_sent == 0 && num_valid_events > 0 ) {
// there are pending events, but we couldn't push any.
rc = -EAGAIN;
}
return rc;
}
#ifdef TEST
#include "libudev.h"
int main( int argc, char** argv ) {
int rc = 0;
char pathbuf[PATH_MAX+1];
struct udev* udev_client = NULL;
struct udev_monitor* monitor = NULL;
int monitor_fd = 0;
struct udev_device* dev = NULL;
struct pollfd pfd[1];
int num_events = INT_MAX;
int num_forks = 0;
// usage: $0 [num events to process [num times to fork]]
if( argc > 1 ) {
char* tmp = NULL;
num_events = (int)strtol( argv[1], &tmp, 10 );
if( tmp == argv[1] || *tmp != '\0' ) {
fprintf(stderr, "Usage: %s [number of events to process [number of times to fork]]\n", argv[0] );
exit(1);
}
if( argc > 2 ) {
num_forks = (int)strtol( argv[2], &tmp, 10 );
if( tmp == argv[2] || *tmp != '\0' ) {
fprintf(stderr, "Usage: %s [number of events to process [number of times to fork]]\n", argv[0] );
exit(1);
}
}
}
// make sure events dir exists
log_trace("events directory '%s'", UDEV_FS_EVENTS_DIR);
rc = mkdir( UDEV_FS_EVENTS_DIR, 0700 );
if( rc != 0 ) {
rc = -errno;
if( rc != -EEXIST ) {
log_error("mkdir('%s') rc = %d", UDEV_FS_EVENTS_DIR, rc );
exit(1);
}
}
udev_monitor_fs_events_path( "", pathbuf, 0 );
printf("Watching '%s'\n", pathbuf );
udev_client = udev_new();
if( udev_client == NULL ) {
// OOM
exit(2);
}
monitor = udev_monitor_new_from_netlink( udev_client, "udev" );
if( monitor == NULL ) {
// OOM or error
udev_unref( udev_client );
exit(2);
}
printf("Press Ctrl-C to quit\n");
monitor_fd = udev_monitor_get_fd( monitor );
if( monitor_fd < 0 ) {
rc = -errno;
log_error("udev_monitor_get_fd rc = %d\n", rc );
exit(3);
}
pfd[0].fd = monitor_fd;
pfd[0].events = POLLIN;
while( num_events > 0 ) {
// wait for the next device
rc = poll( pfd, 1, -1 );
if( rc < 0 ) {
log_error("poll(%d) rc = %d\n", monitor_fd, rc );
break;
}
// get devices
while( num_events > 0 ) {
dev = udev_monitor_receive_device( monitor );
if( dev == NULL ) {
break;
}
int pid = getpid();
struct udev_list_entry *list_entry = NULL;
printf("[%d] [%d] ACTION: '%s'\n", pid, num_events, udev_device_get_action( dev ) );
printf("[%d] [%d] SEQNUM: %llu\n", pid, num_events, udev_device_get_seqnum( dev ) );
printf("[%d] [%d] USEC: %llu\n", pid, num_events, udev_device_get_usec_since_initialized( dev ) );
printf("[%d] [%d] DEVNODE: '%s'\n", pid, num_events, udev_device_get_devnode( dev ) );
printf("[%d] [%d] DEVPATH: '%s'\n", pid, num_events, udev_device_get_devpath( dev ) );
printf("[%d] [%d] SYSNAME: '%s'\n", pid, num_events, udev_device_get_sysname( dev ) );
printf("[%d] [%d] SYSPATH: '%s'\n", pid, num_events, udev_device_get_syspath( dev ) );
printf("[%d] [%d] SUBSYSTEM: '%s'\n", pid, num_events, udev_device_get_subsystem( dev ) );
printf("[%d] [%d] DEVTYPE: '%s'\n", pid, num_events, udev_device_get_devtype( dev ) );
printf("[%d] [%d] SYSNUM: '%s'\n", pid, num_events, udev_device_get_sysnum( dev ) );
printf("[%d] [%d] DRIVER: '%s'\n", pid, num_events, udev_device_get_driver( dev ) );
printf("[%d] [%d] DEVNUM: %d:%d\n", pid, num_events, major( udev_device_get_devnum( dev ) ), minor( udev_device_get_devnum( dev ) ) );
printf("[%d] [%d] IFINDEX: '%s'\n", pid, num_events, udev_device_get_property_value( dev, "IFINDEX" ) );
printf("[%d] [%d] DEVMODE: '%s'\n", pid, num_events, udev_device_get_property_value( dev, "DEVMODE" ) );
printf("[%d] [%d] DEVUID: '%s'\n", pid, num_events, udev_device_get_property_value( dev, "DEVUID" ) );
printf("[%d] [%d] DEVGID: '%s'\n", pid, num_events, udev_device_get_property_value( dev, "DEVGID" ) );
list_entry = udev_device_get_devlinks_list_entry( dev );
udev_list_entry_foreach( list_entry, udev_list_entry_get_next( list_entry )) {
printf("[%d] [%d] devlink: '%s'\n", pid, num_events, udev_list_entry_get_name( list_entry ) );
}
list_entry = udev_device_get_properties_list_entry( dev );
udev_list_entry_foreach( list_entry, udev_list_entry_get_next( list_entry )) {
printf("[%d] [%d] property: '%s' = '%s'\n", pid, num_events, udev_list_entry_get_name( list_entry ), udev_list_entry_get_value( list_entry ) );
}
list_entry = udev_device_get_tags_list_entry( dev );
udev_list_entry_foreach( list_entry, udev_list_entry_get_next( list_entry )) {
printf("[%d] [%d] tag: '%s'\n", pid, num_events, udev_list_entry_get_name( list_entry ) );
}
list_entry = udev_device_get_sysattr_list_entry( dev );
udev_list_entry_foreach( list_entry, udev_list_entry_get_next( list_entry )) {
printf("[%d] [%d] sysattr: '%s'\n", pid, num_events, udev_list_entry_get_name( list_entry ) );
}
printf("\n");
udev_device_unref( dev );
num_events--;
}
// do our forks
if( num_forks > 0 ) {
num_forks--;
int pid = fork();
if( pid < 0 ) {
rc = -errno;
fprintf(stderr, "fork: %s\n", strerror( -rc ) );
break;
}
else if( pid == 0 ) {
printf("[%d]\n", getpid() );
}
}
}
udev_monitor_fs_destroy( monitor );
exit(0);
}
#endif