]>
Commit | Line | Data |
---|---|---|
f13467ec | 1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
bd284db1 | 2 | /* |
810adae9 LP |
3 | * Copyright © 2009 Canonical Ltd. |
4 | * Copyright © 2009 Scott James Remnant <scott@netsplit.com> | |
bd284db1 SJR |
5 | */ |
6 | ||
bd284db1 | 7 | #include <sys/inotify.h> |
bd284db1 | 8 | |
e7aa9512 | 9 | #include "alloc-util.h" |
70068602 | 10 | #include "device-private.h" |
4d04259d | 11 | #include "device-util.h" |
8fb3f009 | 12 | #include "dirent-util.h" |
691a596d | 13 | #include "fd-util.h" |
e7aa9512 | 14 | #include "fs-util.h" |
691a596d | 15 | #include "mkdir.h" |
e7f781e4 | 16 | #include "parse-util.h" |
691a596d YW |
17 | #include "rm-rf.h" |
18 | #include "stdio-util.h" | |
19 | #include "string-util.h" | |
04b25410 | 20 | #include "udev-util.h" |
70068602 | 21 | #include "udev-watch.h" |
bd284db1 | 22 | |
3fb94c70 YW |
23 | int device_new_from_watch_handle_at(sd_device **ret, int dirfd, int wd) { |
24 | char path_wd[STRLEN("/run/udev/watch/") + DECIMAL_STR_MAX(int)]; | |
25 | _cleanup_free_ char *id = NULL; | |
26 | int r; | |
27 | ||
28 | assert(ret); | |
29 | ||
30 | if (wd < 0) | |
31 | return -EBADF; | |
32 | ||
33 | if (dirfd >= 0) { | |
34 | xsprintf(path_wd, "%d", wd); | |
35 | r = readlinkat_malloc(dirfd, path_wd, &id); | |
36 | } else { | |
37 | xsprintf(path_wd, "/run/udev/watch/%d", wd); | |
38 | r = readlink_malloc(path_wd, &id); | |
39 | } | |
40 | if (r < 0) | |
41 | return r; | |
42 | ||
43 | return sd_device_new_from_device_id(ret, id); | |
44 | } | |
45 | ||
df7ee959 | 46 | int udev_watch_restore(int inotify_fd) { |
23691523 | 47 | _cleanup_closedir_ DIR *dir = NULL; |
70068602 | 48 | int r; |
912541b0 | 49 | |
e7f781e4 YW |
50 | /* Move any old watches directory out of the way, and then restore the watches. */ |
51 | ||
df7ee959 | 52 | assert(inotify_fd >= 0); |
912541b0 | 53 | |
23691523 YW |
54 | rm_rf("/run/udev/watch.old", REMOVE_ROOT); |
55 | ||
70068602 | 56 | if (rename("/run/udev/watch", "/run/udev/watch.old") < 0) { |
23691523 YW |
57 | if (errno == ENOENT) |
58 | return 0; | |
912541b0 | 59 | |
23691523 YW |
60 | r = log_warning_errno(errno, |
61 | "Failed to move watches directory '/run/udev/watch/'. " | |
62 | "Old watches will not be restored: %m"); | |
63 | goto finalize; | |
70068602 | 64 | } |
912541b0 | 65 | |
70068602 | 66 | dir = opendir("/run/udev/watch.old"); |
23691523 YW |
67 | if (!dir) { |
68 | r = log_warning_errno(errno, | |
69 | "Failed to open old watches directory '/run/udev/watch.old/'. " | |
70 | "Old watches will not be restored: %m"); | |
71 | goto finalize; | |
72 | } | |
70068602 | 73 | |
23691523 | 74 | FOREACH_DIRENT_ALL(de, dir, break) { |
70068602 | 75 | _cleanup_(sd_device_unrefp) sd_device *dev = NULL; |
e7f781e4 | 76 | int wd; |
70068602 | 77 | |
23691523 YW |
78 | /* For backward compatibility, read symlink from watch handle to device ID. This is necessary |
79 | * when udevd is restarted after upgrading from v248 or older. The new format (ID -> wd) was | |
80 | * introduced by e7f781e473f5119bf9246208a6de9f6b76a39c5d (v249). */ | |
70068602 | 81 | |
23691523 YW |
82 | if (dot_or_dot_dot(de->d_name)) |
83 | continue; | |
e7f781e4 | 84 | |
23691523 YW |
85 | if (safe_atoi(de->d_name, &wd) < 0) |
86 | continue; | |
912541b0 | 87 | |
e7f781e4 | 88 | r = device_new_from_watch_handle_at(&dev, dirfd(dir), wd); |
70068602 | 89 | if (r < 0) { |
e7f781e4 | 90 | log_full_errno(r == -ENODEV ? LOG_DEBUG : LOG_WARNING, r, |
23691523 YW |
91 | "Failed to create sd_device object from saved watch handle '%i', ignoring: %m", |
92 | wd); | |
93 | continue; | |
70068602 | 94 | } |
912541b0 | 95 | |
df7ee959 | 96 | (void) udev_watch_begin(inotify_fd, dev); |
70068602 | 97 | } |
bd284db1 | 98 | |
23691523 | 99 | r = 0; |
bd284db1 | 100 | |
23691523 YW |
101 | finalize: |
102 | (void) rm_rf("/run/udev/watch.old", REMOVE_ROOT); | |
103 | return r; | |
bd284db1 SJR |
104 | } |
105 | ||
691a596d YW |
106 | static int udev_watch_clear(sd_device *dev, int dirfd, int *ret_wd) { |
107 | _cleanup_free_ char *wd_str = NULL, *buf = NULL; | |
108 | const char *id; | |
109 | int wd = -1, r; | |
110 | ||
111 | assert(dev); | |
112 | assert(dirfd >= 0); | |
113 | ||
114 | r = device_get_device_id(dev, &id); | |
115 | if (r < 0) | |
116 | return log_device_debug_errno(dev, r, "Failed to get device ID: %m"); | |
117 | ||
118 | /* 1. read symlink ID -> wd */ | |
119 | r = readlinkat_malloc(dirfd, id, &wd_str); | |
120 | if (r == -ENOENT) { | |
121 | if (ret_wd) | |
122 | *ret_wd = -1; | |
123 | return 0; | |
124 | } | |
125 | if (r < 0) { | |
126 | log_device_debug_errno(dev, r, "Failed to read symlink '/run/udev/watch/%s': %m", id); | |
127 | goto finalize; | |
128 | } | |
129 | ||
130 | r = safe_atoi(wd_str, &wd); | |
131 | if (r < 0) { | |
132 | log_device_debug_errno(dev, r, "Failed to parse watch handle from symlink '/run/udev/watch/%s': %m", id); | |
133 | goto finalize; | |
134 | } | |
135 | ||
136 | if (wd < 0) { | |
137 | r = log_device_debug_errno(dev, SYNTHETIC_ERRNO(EBADF), "Invalid watch handle %i.", wd); | |
138 | goto finalize; | |
139 | } | |
140 | ||
141 | /* 2. read symlink wd -> ID */ | |
142 | r = readlinkat_malloc(dirfd, wd_str, &buf); | |
143 | if (r < 0) { | |
144 | log_device_debug_errno(dev, r, "Failed to read symlink '/run/udev/watch/%s': %m", wd_str); | |
145 | goto finalize; | |
146 | } | |
147 | ||
148 | /* 3. check if the symlink wd -> ID is owned by the device. */ | |
149 | if (!streq(buf, id)) { | |
150 | r = log_device_debug_errno(dev, SYNTHETIC_ERRNO(ENOENT), | |
151 | "Symlink '/run/udev/watch/%s' is owned by another device '%s'.", wd_str, buf); | |
152 | goto finalize; | |
153 | } | |
154 | ||
155 | /* 4. remove symlink wd -> ID. | |
156 | * In the above, we already confirmed that the symlink is owned by us. Hence, no other workers remove | |
157 | * the symlink and cannot create a new symlink with the same filename but to a different ID. Hence, | |
158 | * the removal below is safe even the steps in this function are not atomic. */ | |
f5dfbdd0 | 159 | if (unlinkat(dirfd, wd_str, 0) < 0 && errno != ENOENT) |
691a596d YW |
160 | log_device_debug_errno(dev, errno, "Failed to remove '/run/udev/watch/%s', ignoring: %m", wd_str); |
161 | ||
162 | if (ret_wd) | |
163 | *ret_wd = wd; | |
164 | r = 0; | |
165 | ||
166 | finalize: | |
167 | /* 5. remove symlink ID -> wd. | |
168 | * The file is always owned by the device. Hence, it is safe to remove it unconditionally. */ | |
f5dfbdd0 | 169 | if (unlinkat(dirfd, id, 0) < 0 && errno != ENOENT) |
691a596d YW |
170 | log_device_debug_errno(dev, errno, "Failed to remove '/run/udev/watch/%s': %m", id); |
171 | ||
172 | return r; | |
173 | } | |
174 | ||
df7ee959 | 175 | int udev_watch_begin(int inotify_fd, sd_device *dev) { |
691a596d | 176 | char wd_str[DECIMAL_STR_MAX(int)]; |
254d1313 | 177 | _cleanup_close_ int dirfd = -EBADF; |
691a596d | 178 | const char *devnode, *id; |
70068602 | 179 | int wd, r; |
912541b0 | 180 | |
df7ee959 YW |
181 | assert(inotify_fd >= 0); |
182 | assert(dev); | |
70068602 | 183 | |
04b25410 YW |
184 | if (device_for_action(dev, SD_DEVICE_REMOVE)) |
185 | return 0; | |
186 | ||
70068602 YW |
187 | r = sd_device_get_devname(dev, &devnode); |
188 | if (r < 0) | |
691a596d | 189 | return log_device_debug_errno(dev, r, "Failed to get device node: %m"); |
70068602 | 190 | |
691a596d YW |
191 | r = device_get_device_id(dev, &id); |
192 | if (r < 0) | |
193 | return log_device_debug_errno(dev, r, "Failed to get device ID: %m"); | |
194 | ||
195 | r = dirfd = open_mkdir_at(AT_FDCWD, "/run/udev/watch", O_CLOEXEC | O_RDONLY, 0755); | |
196 | if (r < 0) | |
197 | return log_device_debug_errno(dev, r, "Failed to create and open '/run/udev/watch/': %m"); | |
198 | ||
199 | /* 1. Clear old symlinks */ | |
200 | (void) udev_watch_clear(dev, dirfd, NULL); | |
201 | ||
202 | /* 2. Add inotify watch */ | |
4d04259d | 203 | log_device_debug(dev, "Adding watch on '%s'", devnode); |
70068602 | 204 | wd = inotify_add_watch(inotify_fd, devnode, IN_CLOSE_WRITE); |
691a596d YW |
205 | if (wd < 0) |
206 | return log_device_debug_errno(dev, errno, "Failed to watch device node '%s': %m", devnode); | |
d4696907 | 207 | |
691a596d | 208 | xsprintf(wd_str, "%d", wd); |
912541b0 | 209 | |
691a596d YW |
210 | /* 3. Create new symlinks */ |
211 | if (symlinkat(wd_str, dirfd, id) < 0) { | |
212 | r = log_device_debug_errno(dev, errno, "Failed to create symlink '/run/udev/watch/%s' to '%s': %m", id, wd_str); | |
213 | goto on_failure; | |
e7f781e4 | 214 | } |
70068602 | 215 | |
691a596d YW |
216 | if (symlinkat(id, dirfd, wd_str) < 0) { |
217 | /* Possibly, the watch handle is previously assigned to another device, and udev_watch_end() | |
218 | * is not called for the device yet. */ | |
219 | r = log_device_debug_errno(dev, errno, "Failed to create symlink '/run/udev/watch/%s' to '%s': %m", wd_str, id); | |
220 | goto on_failure; | |
2d3af41f YW |
221 | } |
222 | ||
691a596d | 223 | return 0; |
2d3af41f | 224 | |
691a596d YW |
225 | on_failure: |
226 | (void) unlinkat(dirfd, id, 0); | |
2d3af41f | 227 | (void) inotify_rm_watch(inotify_fd, wd); |
2d3af41f | 228 | return r; |
bd284db1 SJR |
229 | } |
230 | ||
df7ee959 | 231 | int udev_watch_end(int inotify_fd, sd_device *dev) { |
254d1313 | 232 | _cleanup_close_ int dirfd = -EBADF; |
691a596d | 233 | int wd, r; |
bd284db1 | 234 | |
df7ee959 YW |
235 | assert(dev); |
236 | ||
237 | /* This may be called by 'udevadm test'. In that case, inotify_fd is not initialized. */ | |
912541b0 | 238 | if (inotify_fd < 0) |
df7ee959 | 239 | return 0; |
03e0170d | 240 | |
5e0d0510 YW |
241 | if (sd_device_get_devname(dev, NULL) < 0) |
242 | return 0; | |
243 | ||
691a596d YW |
244 | dirfd = RET_NERRNO(open("/run/udev/watch", O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW | O_RDONLY)); |
245 | if (dirfd == -ENOENT) | |
246 | return 0; | |
247 | if (dirfd < 0) | |
248 | return log_device_debug_errno(dev, dirfd, "Failed to open '/run/udev/watch/': %m"); | |
249 | ||
250 | /* First, clear symlinks. */ | |
251 | r = udev_watch_clear(dev, dirfd, &wd); | |
252 | if (r < 0) | |
253 | return r; | |
254 | ||
255 | /* Then, remove inotify watch. */ | |
256 | log_device_debug(dev, "Removing watch handle %i.", wd); | |
257 | (void) inotify_rm_watch(inotify_fd, wd); | |
70068602 YW |
258 | |
259 | return 0; | |
bd284db1 | 260 | } |