Last active
November 24, 2015 14:01
-
-
Save haraldh/4e58aaa067b76199da4d to your computer and use it in GitHub Desktop.
close a device file descriptor and wait for the udev change event
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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