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 <errno.h>
8 #include <getopt.h>
9 #include <poll.h>
10 #include <stddef.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <unistd.h>
14
15 #include "sd-bus.h"
16 #include "sd-login.h"
17 #include "sd-messages.h"
18
19 #include "bus-util.h"
20 #include "fd-util.h"
21 #include "io-util.h"
22 #include "string-util.h"
23 #include "strv.h"
24 #include "time-util.h"
25 #include "udev-ctrl.h"
26 #include "udev-util.h"
27 #include "udevadm.h"
28 #include "unit-def.h"
29 #include "util.h"
30 #include "virt.h"
31
32 static usec_t arg_timeout = 120 * USEC_PER_SEC;
33 static const char *arg_exists = NULL;
34
help(void)35 static int help(void) {
36 printf("%s settle [OPTIONS]\n\n"
37 "Wait for pending udev events.\n\n"
38 " -h --help Show this help\n"
39 " -V --version Show package version\n"
40 " -t --timeout=SEC Maximum time to wait for events\n"
41 " -E --exit-if-exists=FILE Stop waiting if file exists\n",
42 program_invocation_short_name);
43
44 return 0;
45 }
46
parse_argv(int argc,char * argv[])47 static int parse_argv(int argc, char *argv[]) {
48 static const struct option options[] = {
49 { "timeout", required_argument, NULL, 't' },
50 { "exit-if-exists", required_argument, NULL, 'E' },
51 { "version", no_argument, NULL, 'V' },
52 { "help", no_argument, NULL, 'h' },
53 { "seq-start", required_argument, NULL, 's' }, /* removed */
54 { "seq-end", required_argument, NULL, 'e' }, /* removed */
55 { "quiet", no_argument, NULL, 'q' }, /* removed */
56 {}
57 };
58
59 int c, r;
60
61 while ((c = getopt_long(argc, argv, "t:E:Vhs:e:q", options, NULL)) >= 0) {
62 switch (c) {
63 case 't':
64 r = parse_sec(optarg, &arg_timeout);
65 if (r < 0)
66 return log_error_errno(r, "Failed to parse timeout value '%s': %m", optarg);
67 break;
68 case 'E':
69 arg_exists = optarg;
70 break;
71 case 'V':
72 return print_version();
73 case 'h':
74 return help();
75 case 's':
76 case 'e':
77 case 'q':
78 return log_info_errno(SYNTHETIC_ERRNO(EINVAL),
79 "Option -%c no longer supported.",
80 c);
81 case '?':
82 return -EINVAL;
83 default:
84 assert_not_reached();
85 }
86 }
87
88 return 1;
89 }
90
emit_deprecation_warning(void)91 static int emit_deprecation_warning(void) {
92 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
93 _cleanup_strv_free_ char **a = NULL;
94 _cleanup_free_ char *unit = NULL;
95 int r;
96
97 r = sd_pid_get_unit(0, &unit);
98 if (r < 0) {
99 log_debug_errno(r, "Failed to determine unit we run in, ignoring: %m");
100 return 0;
101 }
102
103 if (!streq(unit, "systemd-udev-settle.service"))
104 return 0;
105
106 r = bus_connect_system_systemd(&bus);
107 if (r < 0)
108 log_debug_errno(r, "Failed to open connection to systemd, skipping dependency queries: %m");
109 else {
110 _cleanup_strv_free_ char **b = NULL;
111 _cleanup_free_ char *unit_path = NULL;
112
113 unit_path = unit_dbus_path_from_name("systemd-udev-settle.service");
114 if (!unit_path)
115 return -ENOMEM;
116
117 (void) sd_bus_get_property_strv(
118 bus,
119 "org.freedesktop.systemd1",
120 unit_path,
121 "org.freedesktop.systemd1.Unit",
122 "WantedBy",
123 NULL,
124 &a);
125
126 (void) sd_bus_get_property_strv(
127 bus,
128 "org.freedesktop.systemd1",
129 unit_path,
130 "org.freedesktop.systemd1.Unit",
131 "RequiredBy",
132 NULL,
133 &b);
134
135 r = strv_extend_strv(&a, b, true);
136 if (r < 0)
137 return r;
138 }
139
140 if (strv_isempty(a))
141 /* Print a simple message if we cannot determine the dependencies */
142 log_notice("systemd-udev-settle.service is deprecated.");
143 else {
144 /* Print a longer, structured message if we can acquire the dependencies (this should be the
145 * common case). This is hooked up with a catalog entry and everything. */
146 _cleanup_free_ char *t = NULL;
147
148 t = strv_join(a, ", ");
149 if (!t)
150 return -ENOMEM;
151
152 log_struct(LOG_NOTICE,
153 LOG_MESSAGE("systemd-udev-settle.service is deprecated. Please fix %s not to pull it in.", t),
154 "OFFENDING_UNITS=%s", t,
155 "MESSAGE_ID=" SD_MESSAGE_SYSTEMD_UDEV_SETTLE_DEPRECATED_STR);
156 }
157
158 return 0;
159 }
160
settle_main(int argc,char * argv[],void * userdata)161 int settle_main(int argc, char *argv[], void *userdata) {
162 _cleanup_close_ int fd = -1;
163 usec_t deadline;
164 int r;
165
166 r = parse_argv(argc, argv);
167 if (r <= 0)
168 return r;
169
170 if (running_in_chroot() > 0) {
171 log_info("Running in chroot, ignoring request.");
172 return 0;
173 }
174
175 deadline = now(CLOCK_MONOTONIC) + arg_timeout;
176
177 /* guarantee that the udev daemon isn't pre-processing */
178 if (getuid() == 0) {
179 _cleanup_(udev_ctrl_unrefp) UdevCtrl *uctrl = NULL;
180
181 if (udev_ctrl_new(&uctrl) >= 0) {
182 r = udev_ctrl_send_ping(uctrl);
183 if (r < 0) {
184 log_debug_errno(r, "Failed to connect to udev daemon: %m");
185 return 0;
186 }
187
188 r = udev_ctrl_wait(uctrl, MAX(5 * USEC_PER_SEC, arg_timeout));
189 if (r < 0)
190 return log_error_errno(r, "Failed to wait for daemon to reply: %m");
191 }
192 }
193
194 fd = udev_queue_init();
195 if (fd < 0) {
196 log_debug_errno(fd, "Queue is empty, nothing to watch: %m");
197 return 0;
198 }
199
200 (void) emit_deprecation_warning();
201
202 for (;;) {
203 if (arg_exists && access(arg_exists, F_OK) >= 0)
204 return 0;
205
206 /* exit if queue is empty */
207 r = udev_queue_is_empty();
208 if (r < 0)
209 return log_error_errno(r, "Failed to check queue is empty: %m");
210 if (r > 0)
211 return 0;
212
213 if (now(CLOCK_MONOTONIC) >= deadline)
214 return -ETIMEDOUT;
215
216 /* wake up when queue becomes empty */
217 r = fd_wait_for_event(fd, POLLIN, MSEC_PER_SEC);
218 if (r < 0)
219 return r;
220 if (r & POLLIN) {
221 r = flush_fd(fd);
222 if (r < 0)
223 return log_error_errno(r, "Failed to flush queue: %m");
224 }
225 }
226 }
227