1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 /*
3  * Copyright © 2009 Canonical Ltd.
4  * Copyright © 2009 Scott James Remnant <scott@netsplit.com>
5  */
6 
7 #include <sys/inotify.h>
8 
9 #include "alloc-util.h"
10 #include "device-private.h"
11 #include "device-util.h"
12 #include "dirent-util.h"
13 #include "fs-util.h"
14 #include "parse-util.h"
15 #include "random-util.h"
16 #include "udev-watch.h"
17 
18 #define SAVE_WATCH_HANDLE_MAX_RETRIES  128
19 #define MAX_RANDOM_DELAY (100 * USEC_PER_MSEC)
20 #define MIN_RANDOM_DELAY ( 10 * USEC_PER_MSEC)
21 
udev_watch_restore(int inotify_fd)22 int udev_watch_restore(int inotify_fd) {
23         DIR *dir;
24         int r;
25 
26         /* Move any old watches directory out of the way, and then restore the watches. */
27 
28         assert(inotify_fd >= 0);
29 
30         if (rename("/run/udev/watch", "/run/udev/watch.old") < 0) {
31                 if (errno != ENOENT)
32                         return log_warning_errno(errno, "Failed to move watches directory /run/udev/watch. "
33                                                  "Old watches will not be restored: %m");
34 
35                 return 0;
36         }
37 
38         dir = opendir("/run/udev/watch.old");
39         if (!dir)
40                 return log_warning_errno(errno, "Failed to open old watches directory /run/udev/watch.old. "
41                                          "Old watches will not be restored: %m");
42 
43         FOREACH_DIRENT_ALL(ent, dir, break) {
44                 _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
45                 int wd;
46 
47                 if (ent->d_name[0] == '.')
48                         continue;
49 
50                 /* For backward compatibility, read symlink from watch handle to device id, and ignore
51                  * the opposite direction symlink. */
52 
53                 if (safe_atoi(ent->d_name, &wd) < 0)
54                         goto unlink;
55 
56                 r = device_new_from_watch_handle_at(&dev, dirfd(dir), wd);
57                 if (r < 0) {
58                         log_full_errno(r == -ENODEV ? LOG_DEBUG : LOG_WARNING, r,
59                                        "Failed to create sd_device object from saved watch handle '%s', ignoring: %m",
60                                        ent->d_name);
61                         goto unlink;
62                 }
63 
64                 log_device_debug(dev, "Restoring old watch");
65                 (void) udev_watch_begin(inotify_fd, dev);
66 unlink:
67                 (void) unlinkat(dirfd(dir), ent->d_name, 0);
68         }
69 
70         (void) closedir(dir);
71         (void) rmdir("/run/udev/watch.old");
72 
73         return 0;
74 }
75 
udev_watch_begin(int inotify_fd,sd_device * dev)76 int udev_watch_begin(int inotify_fd, sd_device *dev) {
77         const char *devnode;
78         int wd, r;
79 
80         assert(inotify_fd >= 0);
81         assert(dev);
82 
83         r = sd_device_get_devname(dev, &devnode);
84         if (r < 0)
85                 return log_device_debug_errno(dev, r, "Failed to get device name: %m");
86 
87         log_device_debug(dev, "Adding watch on '%s'", devnode);
88         wd = inotify_add_watch(inotify_fd, devnode, IN_CLOSE_WRITE);
89         if (wd < 0) {
90                 bool ignore = errno == ENOENT;
91 
92                 r = log_device_full_errno(dev, ignore ? LOG_DEBUG : LOG_WARNING, errno,
93                                           "Failed to add device '%s' to watch%s: %m",
94                                           devnode, ignore ? ", ignoring" : "");
95 
96                 (void) device_set_watch_handle(dev, -1);
97                 return ignore ? 0 : r;
98         }
99 
100         for (unsigned i = 0; i < SAVE_WATCH_HANDLE_MAX_RETRIES; i++) {
101                 if (i > 0) {
102                         usec_t delay = MIN_RANDOM_DELAY + random_u64_range(MAX_RANDOM_DELAY - MIN_RANDOM_DELAY);
103 
104                         /* When the same handle is reused for different device node, we may fail to
105                          * save the watch handle with -EEXIST. Let's consider the case of two workers A
106                          * and B do the following:
107                          *
108                          * 1. A calls inotify_rm_watch()
109                          * 2. B calls inotify_add_watch()
110                          * 3. B calls device_set_watch_handle()
111                          * 4. A calls device_set_watch_handle(-1)
112                          *
113                          * At step 3, the old symlinks to save the watch handle still exist. So,
114                          * device_set_watch_handle() fails with -EEXIST. */
115 
116                         log_device_debug_errno(dev, r,
117                                                "Failed to save watch handle '%i' for %s in "
118                                                "/run/udev/watch, retrying in after %s: %m",
119                                                wd, devnode, FORMAT_TIMESPAN(delay, USEC_PER_MSEC));
120                         (void) usleep(delay);
121                 }
122 
123                 r = device_set_watch_handle(dev, wd);
124                 if (r >= 0)
125                         return 0;
126                 if (r != -EEXIST)
127                         break;
128         }
129 
130         log_device_warning_errno(dev, r,
131                                  "Failed to save watch handle '%i' for %s in /run/udev/watch: %m",
132                                  wd, devnode);
133 
134         (void) inotify_rm_watch(inotify_fd, wd);
135 
136         return r;
137 }
138 
udev_watch_end(int inotify_fd,sd_device * dev)139 int udev_watch_end(int inotify_fd, sd_device *dev) {
140         int wd;
141 
142         assert(dev);
143 
144         /* This may be called by 'udevadm test'. In that case, inotify_fd is not initialized. */
145         if (inotify_fd < 0)
146                 return 0;
147 
148         if (sd_device_get_devname(dev, NULL) < 0)
149                 return 0;
150 
151         wd = device_get_watch_handle(dev);
152         if (wd < 0)
153                 log_device_debug_errno(dev, wd, "Failed to get watch handle, ignoring: %m");
154         else {
155                 log_device_debug(dev, "Removing watch");
156                 (void) inotify_rm_watch(inotify_fd, wd);
157         }
158         (void) device_set_watch_handle(dev, -1);
159 
160         return 0;
161 }
162