Skip to content

Instantly share code, notes, and snippets.

@haraldh
Last active November 24, 2015 14:01
Show Gist options
  • Save haraldh/4e58aaa067b76199da4d to your computer and use it in GitHub Desktop.
Save haraldh/4e58aaa067b76199da4d to your computer and use it in GitHub Desktop.
close a device file descriptor and wait for the udev change event
/*
close(fd) of a device file descriptor after making a change and wait for any udev change event
if none is received, trigger the change by writing change to the uevent file in /sys
Without udev watch rule:
$ sudo ./udev_device_wait_change
Issued 'change' uevent via '/sys/devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda/uevent'
Change event seen!
With udev watch rule:
$ sudo ./udev_device_wait_change
Got automatic change event... maybe from watch rule
Change event seen!
*/
/*
Copyright (C) 2015 Harald Hoyer
Copyright (C) 2015 Red Hat, Inc. All rights reserved.
This program is free software: you can redistribute it and/or modify
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.
This program 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 this program; If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libudev.h>
#include <sys/epoll.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#define memzero(x,l) (memset((x), 0, (l)))
#define ELEMENTSOF(x) \
__extension__ (__builtin_choose_expr( \
!__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \
sizeof(x)/sizeof((x)[0]), \
(void)0))
#define _cleanup_(x) __attribute__((cleanup(x)))
#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \
static inline void func##p(type *p) { \
if (*p) \
func(*p); \
}
static inline void closep(int *fd) {
if (fd && *fd >= 0)
close(*fd);
}
#define _cleanup_close_ _cleanup_(closep)
DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev*, udev_unref)
DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_device*, udev_device_unref)
DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_enumerate*, udev_enumerate_unref)
DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_monitor*, udev_monitor_unref)
#define _cleanup_udev_unref_ _cleanup_(udev_unrefp)
#define _cleanup_udev_device_unref_ _cleanup_(udev_device_unrefp)
#define _cleanup_udev_enumerate_unref_ _cleanup_(udev_enumerate_unrefp)
#define _cleanup_udev_monitor_unref_ _cleanup_(udev_monitor_unrefp)
#define _cleanup_udev_list_cleanup_ _cleanup_(udev_list_cleanup)
size_t strpcpy(char **dest, size_t size, const char *src) {
size_t len;
len = strlen(src);
if (len >= size) {
if (size > 1)
*dest = mempcpy(*dest, src, size-1);
size = 0;
} else {
if (len > 0) {
*dest = mempcpy(*dest, src, len);
size -= len;
}
}
*dest[0] = '\0';
return size;
}
size_t strscpyl(char *dest, size_t size, const char *src, ...) {
va_list va;
char *s;
va_start(va, src);
s = dest;
do {
size = strpcpy(&s, size, src);
src = va_arg(va, char *);
} while (src != NULL);
va_end(va);
return size;
}
struct udev_device *udev_device_new_from_devpath(struct udev *udev, char *devpath)
{
struct stat st;
if(stat(devpath, &st) < 0) {
return NULL;
}
if (!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode))
return NULL;
return udev_device_new_from_devnum(udev, S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev);
}
int close_device_wait_change(char *devpath, int devfd, int timeout)
{
_cleanup_udev_unref_ struct udev *udev = NULL;
_cleanup_udev_monitor_unref_ struct udev_monitor *udev_monitor = NULL;
_cleanup_udev_monitor_unref_ struct udev_monitor *kernel_monitor = NULL;
_cleanup_udev_device_unref_ struct udev_device *udev_device = NULL;
_cleanup_close_ int fd_ep = -1, fd = -1;
char filename[1024];
int fd_kernel = -1, fd_udev = -1;
struct epoll_event ep_kernel, ep_udev;
udev = udev_new();
if (udev == NULL)
return 1;
udev_device = udev_device_new_from_devpath(udev, devpath);
if (udev_device == NULL)
return 1;
fd_ep = epoll_create1(EPOLL_CLOEXEC);
if (fd_ep < 0) {
return 1;
}
udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
if (udev_monitor == NULL) {
return 1;
}
udev_monitor_set_receive_buffer_size(udev_monitor, 128*1024*1024);
fd_udev = udev_monitor_get_fd(udev_monitor);
if (udev_monitor_filter_add_match_subsystem_devtype(udev_monitor,
udev_device_get_subsystem(udev_device),
udev_device_get_devtype(udev_device)) < 0)
fprintf(stderr, "error: unable to apply subsystem filter '%s'\n", udev_device_get_subsystem(udev_device));
memzero(&ep_udev, sizeof(struct epoll_event));
ep_udev.events = EPOLLIN;
ep_udev.data.fd = fd_udev;
if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_udev, &ep_udev) < 0) {
return 2;
}
/* Now close the file descriptor */
if (close(devfd) == -1)
return -errno;
/*
Here we could miss the change event and would emit a second change needlessly.
We just assume we can enable the monitoring quicker than udevd does any blkid or stuff.
*/
if (udev_monitor_enable_receiving(udev_monitor) < 0) {
return 2;
}
while (1) {
int fdcount;
struct epoll_event ev[4];
int i;
fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), timeout);
if (fdcount == 0)
break;
if (fdcount < 0) {
if (errno == EINTR)
continue;
fprintf(stderr, "error receiving uevent message: %m\n");
break;
}
for (i = 0; i < fdcount; i++) {
if (ev[i].data.fd == fd_udev && ev[i].events & EPOLLIN) {
_cleanup_udev_device_unref_ struct udev_device *device = NULL;
device = udev_monitor_receive_device(udev_monitor);
if (device == NULL)
continue;
if ((strcmp(udev_device_get_action(device), "change") == 0)
&& (strcmp(udev_device_get_devpath(device), udev_device_get_devpath(udev_device)) == 0)) {
fprintf(stderr, "Got automatic change event... maybe from watch rule\n");
return 0;
}
}
}
}
/* Ok, we didn't get an automatic change event, so we just emulate one */
strscpyl(filename, sizeof(filename), "/sys", udev_device_get_devpath(udev_device), "/uevent", NULL);
fd = open(filename, O_WRONLY|O_CLOEXEC);
if (fd < 0)
return -errno;
if (write(fd, "change", 6) < 0) {
fprintf(stderr, "error writing 'change' to '%s': %m\n", filename);
} else {
fprintf(stderr, "Issued 'change' uevent via '%s'\n", filename);
}
close(fd);
/* now wait for the change event again */
while (1) {
int fdcount;
struct epoll_event ev[4];
int i;
fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), timeout);
if (fdcount == 0)
break;
if (fdcount < 0) {
if (errno == EINTR)
continue;
fprintf(stderr, "error receiving uevent message: %m\n");
break;
}
for (i = 0; i < fdcount; i++) {
if (ev[i].data.fd == fd_udev && ev[i].events & EPOLLIN) {
_cleanup_udev_device_unref_ struct udev_device *device = NULL;
device = udev_monitor_receive_device(udev_monitor);
if (device == NULL)
continue;
if ((strcmp(udev_device_get_action(device), "change") == 0)
&& (strcmp(udev_device_get_devpath(device), udev_device_get_devpath(udev_device)) == 0)) {
return 0;
}
}
}
}
/* No change event seen in <timeout> */
return 100;
}
int main()
{
int fd;
int ret;
char *devicepath = "/dev/sda";
fd = open(devicepath, O_WRONLY|O_CLOEXEC);
if (fd < 0) {
fprintf(stderr, "Failed to open '%s': %m\n", devicepath);
return EXIT_FAILURE;
}
/* Wait 2 seconds for the change event to happen */
ret = close_device_wait_change(devicepath, fd, 2*1000);
if(ret != 0) {
fprintf(stderr, "close_device_wait_change() failed with %d\n", ret);
return EXIT_FAILURE;
}
fprintf(stderr, "Change event seen!\n");
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment