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
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
|
|
|