xref: /openbmc/debug-trigger/main.c (revision 86094694a28e0c50a4f10bc18b9ac6e9c11c0f52)
111cd254bSAndrew Jeffery // SPDX-License-Identifier: Apache-2.0
211cd254bSAndrew Jeffery // Copyright (C) 2021 IBM Corp.
311cd254bSAndrew Jeffery 
4d65368beSAndrew Jeffery /*
5e998ba77SAndrew Jeffery  * debug-trigger listens for an external signal that the BMC is in some way unresponsive. When a
6d65368beSAndrew Jeffery  * signal is received it triggers a crash to collect debug data and reboots the system in the hope
7d65368beSAndrew Jeffery  * that it will recover.
8d65368beSAndrew Jeffery  *
9d65368beSAndrew Jeffery  * Usage: debug-trigger [SOURCE] [SINK]
10d65368beSAndrew Jeffery  *
11e998ba77SAndrew Jeffery  * Options:
12e998ba77SAndrew Jeffery  *  --sink-actions=ACTION
13*86094694SAndrew Jeffery  *	Set the class of sink action(s) to be used. Can take the value of 'sysrq' or 'dbus'.
14*86094694SAndrew Jeffery  *	Defaults to 'sysrq'.
15e998ba77SAndrew Jeffery  *
16d65368beSAndrew Jeffery  * Examples:
17d65368beSAndrew Jeffery  *  debug-trigger
18e998ba77SAndrew Jeffery  *	Set the source as stdin, the sink as stdout, and use the default 'sysrq' set of sink
19e998ba77SAndrew Jeffery  *	actions. Useful for testing.
20e998ba77SAndrew Jeffery  *
21e998ba77SAndrew Jeffery  *  debug-trigger --sink-actions=sysrq
22e998ba77SAndrew Jeffery  *	Explicitly use the 'sysrq' set of sink actions with stdin as the source and stdout as the
23e998ba77SAndrew Jeffery  *	sink.
24d65368beSAndrew Jeffery  *
25d65368beSAndrew Jeffery  *  debug-trigger /dev/serio_raw0 /proc/sysrq-trigger
26e998ba77SAndrew Jeffery  *	Open /dev/serio_raw0 as the source and /proc/sysrq-trigger as the sink, with the default
27e998ba77SAndrew Jeffery  *	'sysrq' set of sink actions. When 'D' is read from /dev/serio_raw0 'c' will be written to
28e998ba77SAndrew Jeffery  *	/proc/sysrq-trigger, causing a kernel panic. When 'R' is read from /dev/serio_raw0 'b' will
29e998ba77SAndrew Jeffery  *	be written to /proc/sysrq-trigger, causing an immediate reboot of the system.
30*86094694SAndrew Jeffery  *
31*86094694SAndrew Jeffery  *  dbug-trigger --sink-actions=dbus /dev/serio_raw0
32*86094694SAndrew Jeffery  *	Open /dev/serio_raw0 as the source and configure the 'dbus' set of sink actions. When 'D' is
33*86094694SAndrew Jeffery  *	read from /dev/serio_raw0 create a dump via phosphor-debug-collector by calling through its
34*86094694SAndrew Jeffery  *	D-Bus interface, then reboot the system by starting systemd's 'reboot.target'
35d65368beSAndrew Jeffery  */
36*86094694SAndrew Jeffery #define _GNU_SOURCE
37*86094694SAndrew Jeffery 
38*86094694SAndrew Jeffery #include "config.h"
39d65368beSAndrew Jeffery 
4011cd254bSAndrew Jeffery #include <err.h>
41b1ea254eSAndrew Jeffery #include <errno.h>
4211cd254bSAndrew Jeffery #include <fcntl.h>
4311cd254bSAndrew Jeffery #include <getopt.h>
4411cd254bSAndrew Jeffery #include <libgen.h>
4511cd254bSAndrew Jeffery #include <limits.h>
4611cd254bSAndrew Jeffery #include <linux/reboot.h>
47*86094694SAndrew Jeffery #include <poll.h>
48*86094694SAndrew Jeffery #include <stdbool.h>
49*86094694SAndrew Jeffery #include <stdio.h>
5011cd254bSAndrew Jeffery #include <stdlib.h>
5111cd254bSAndrew Jeffery #include <string.h>
5211cd254bSAndrew Jeffery #include <sys/reboot.h>
5311cd254bSAndrew Jeffery #include <sys/stat.h>
5411cd254bSAndrew Jeffery #include <sys/types.h>
5511cd254bSAndrew Jeffery #include <unistd.h>
5611cd254bSAndrew Jeffery 
57*86094694SAndrew Jeffery #define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
58*86094694SAndrew Jeffery 
59*86094694SAndrew Jeffery struct sd_bus;
60*86094694SAndrew Jeffery 
61b1ea254eSAndrew Jeffery struct debug_source_ops {
62b1ea254eSAndrew Jeffery 	int (*poll)(void *ctx, char *op);
63b1ea254eSAndrew Jeffery };
64b1ea254eSAndrew Jeffery 
65b1ea254eSAndrew Jeffery struct debug_source {
66b1ea254eSAndrew Jeffery 	const struct debug_source_ops *ops;
67b1ea254eSAndrew Jeffery 	void *ctx;
68b1ea254eSAndrew Jeffery };
69b1ea254eSAndrew Jeffery 
70b1ea254eSAndrew Jeffery struct debug_source_basic {
71b1ea254eSAndrew Jeffery 	int source;
72b1ea254eSAndrew Jeffery };
73b1ea254eSAndrew Jeffery 
74*86094694SAndrew Jeffery struct debug_source_dbus {
75*86094694SAndrew Jeffery 	struct sd_bus *bus;
76*86094694SAndrew Jeffery #define DBUS_SOURCE_PFD_SOURCE	0
77*86094694SAndrew Jeffery #define DBUS_SOURCE_PFD_DBUS	1
78*86094694SAndrew Jeffery 	struct pollfd pfds[2];
79*86094694SAndrew Jeffery };
80*86094694SAndrew Jeffery 
811dc6adc9SAndrew Jeffery struct debug_sink_ops {
821dc6adc9SAndrew Jeffery 	void (*debug)(void *ctx);
831dc6adc9SAndrew Jeffery 	void (*reboot)(void *ctx);
841dc6adc9SAndrew Jeffery };
851dc6adc9SAndrew Jeffery 
861dc6adc9SAndrew Jeffery struct debug_sink {
871dc6adc9SAndrew Jeffery 	const struct debug_sink_ops *ops;
881dc6adc9SAndrew Jeffery 	void *ctx;
891dc6adc9SAndrew Jeffery };
901dc6adc9SAndrew Jeffery 
911dc6adc9SAndrew Jeffery struct debug_sink_sysrq {
921dc6adc9SAndrew Jeffery 	int sink;
931dc6adc9SAndrew Jeffery };
941dc6adc9SAndrew Jeffery 
95*86094694SAndrew Jeffery struct debug_sink_dbus {
96*86094694SAndrew Jeffery 	struct sd_bus *bus;
97*86094694SAndrew Jeffery };
98*86094694SAndrew Jeffery 
991dc6adc9SAndrew Jeffery static void sysrq_sink_debug(void *ctx)
100db47cd7fSAndrew Jeffery {
1011dc6adc9SAndrew Jeffery 	struct debug_sink_sysrq *sysrq = ctx;
10230b6496aSAndrew Jeffery 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n93 */
103db47cd7fSAndrew Jeffery 	static const char action = 'c';
104db47cd7fSAndrew Jeffery 	ssize_t rc;
105db47cd7fSAndrew Jeffery 
106db47cd7fSAndrew Jeffery 	sync();
107db47cd7fSAndrew Jeffery 
1081dc6adc9SAndrew Jeffery 	if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
109db47cd7fSAndrew Jeffery 		return;
110db47cd7fSAndrew Jeffery 
111db47cd7fSAndrew Jeffery 	if (rc == -1) {
112db47cd7fSAndrew Jeffery 		warn("Failed to execute debug command");
113db47cd7fSAndrew Jeffery 	} else {
114db47cd7fSAndrew Jeffery 		warnx("Failed to execute debug command: %zd", rc);
115db47cd7fSAndrew Jeffery 	}
116db47cd7fSAndrew Jeffery }
117db47cd7fSAndrew Jeffery 
1181dc6adc9SAndrew Jeffery static void sysrq_sink_reboot(void *ctx)
11911cd254bSAndrew Jeffery {
1201dc6adc9SAndrew Jeffery 	struct debug_sink_sysrq *sysrq = ctx;
12130b6496aSAndrew Jeffery 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n90 */
12230b6496aSAndrew Jeffery 	static const char action = 'b';
12311cd254bSAndrew Jeffery 	ssize_t rc;
12411cd254bSAndrew Jeffery 
12511cd254bSAndrew Jeffery 	sync();
12611cd254bSAndrew Jeffery 
1271dc6adc9SAndrew Jeffery 	if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
12830b6496aSAndrew Jeffery 		return;
12930b6496aSAndrew Jeffery 
13030b6496aSAndrew Jeffery 	if (rc == -1) {
13111cd254bSAndrew Jeffery 		warn("Failed to reboot BMC");
13230b6496aSAndrew Jeffery 	} else {
13311cd254bSAndrew Jeffery 		warnx("Failed to reboot BMC: %zd", rc);
13411cd254bSAndrew Jeffery 	}
135210ad636SAndrew Jeffery }
136210ad636SAndrew Jeffery 
137b1ea254eSAndrew Jeffery static int basic_source_poll(void *ctx, char *op)
138210ad636SAndrew Jeffery {
139b1ea254eSAndrew Jeffery 	struct debug_source_basic *basic = ctx;
140210ad636SAndrew Jeffery 	ssize_t ingress;
141210ad636SAndrew Jeffery 
142b1ea254eSAndrew Jeffery 	if ((ingress = read(basic->source, op, 1)) != 1) {
143b1ea254eSAndrew Jeffery 		if (ingress < 0) {
144b1ea254eSAndrew Jeffery 			warn("Failed to read from basic source");
145b1ea254eSAndrew Jeffery 			return -errno;
146b1ea254eSAndrew Jeffery 		}
147b1ea254eSAndrew Jeffery 
148b1ea254eSAndrew Jeffery 		/* Unreachable */
149b1ea254eSAndrew Jeffery 		errx(EXIT_FAILURE, "Bad read, requested 1 got %zd", ingress);
150b1ea254eSAndrew Jeffery 	}
151b1ea254eSAndrew Jeffery 
152b1ea254eSAndrew Jeffery 	return 0;
153b1ea254eSAndrew Jeffery }
154b1ea254eSAndrew Jeffery 
155*86094694SAndrew Jeffery const struct debug_sink_ops sysrq_sink_ops = {
156*86094694SAndrew Jeffery 	.debug = sysrq_sink_debug,
157*86094694SAndrew Jeffery 	.reboot = sysrq_sink_reboot,
158*86094694SAndrew Jeffery };
159*86094694SAndrew Jeffery 
160b1ea254eSAndrew Jeffery const struct debug_source_ops basic_source_ops = {
161b1ea254eSAndrew Jeffery 	.poll = basic_source_poll,
162b1ea254eSAndrew Jeffery };
163b1ea254eSAndrew Jeffery 
164*86094694SAndrew Jeffery #if HAVE_SYSTEMD
165*86094694SAndrew Jeffery #include <systemd/sd-bus.h>
166*86094694SAndrew Jeffery 
167*86094694SAndrew Jeffery static void dbus_sink_reboot(void *ctx);
168*86094694SAndrew Jeffery static int dbus_sink_dump_progress(sd_bus_message *m, void *userdata,
169*86094694SAndrew Jeffery 				   sd_bus_error *ret_error __attribute__((unused)))
170*86094694SAndrew Jeffery {
171*86094694SAndrew Jeffery 	struct debug_sink_dbus *dbus = userdata;
172*86094694SAndrew Jeffery 	const char *status;
173*86094694SAndrew Jeffery 	const char *iface;
174*86094694SAndrew Jeffery 	int rc;
175*86094694SAndrew Jeffery 
176*86094694SAndrew Jeffery 	// sa{sv}as
177*86094694SAndrew Jeffery 	rc = sd_bus_message_read_basic(m, 's', &iface);
178*86094694SAndrew Jeffery 	if (rc < 0) {
179*86094694SAndrew Jeffery 		warnx("Failed to extract interface from PropertiesChanged signal: %s",
180*86094694SAndrew Jeffery 		      strerror(-rc));
181*86094694SAndrew Jeffery 		return rc;
182*86094694SAndrew Jeffery 	}
183*86094694SAndrew Jeffery 
184*86094694SAndrew Jeffery 	/* Bail if it's not an update to the Progress interface */
185*86094694SAndrew Jeffery 	if (strcmp(iface, "xyz.openbmc_project.Common.Progress"))
186*86094694SAndrew Jeffery 		return 0;
187*86094694SAndrew Jeffery 
188*86094694SAndrew Jeffery 	rc = sd_bus_message_enter_container(m, 'a', "{sv}");
189*86094694SAndrew Jeffery 	if (rc < 0)
190*86094694SAndrew Jeffery 		return rc;
191*86094694SAndrew Jeffery 
192*86094694SAndrew Jeffery 	if (!rc)
193*86094694SAndrew Jeffery 		return 0;
194*86094694SAndrew Jeffery 
195*86094694SAndrew Jeffery 	status = NULL;
196*86094694SAndrew Jeffery 	while (1) {
197*86094694SAndrew Jeffery 		const char *member;
198*86094694SAndrew Jeffery 
199*86094694SAndrew Jeffery 		rc = sd_bus_message_enter_container(m, 'e', "sv");
200*86094694SAndrew Jeffery 		if (rc < 0)
201*86094694SAndrew Jeffery 			return rc;
202*86094694SAndrew Jeffery 
203*86094694SAndrew Jeffery 		if (!rc)
204*86094694SAndrew Jeffery 			break;
205*86094694SAndrew Jeffery 
206*86094694SAndrew Jeffery 		rc = sd_bus_message_read_basic(m, 's', &member);
207*86094694SAndrew Jeffery 		if (rc < 0) {
208*86094694SAndrew Jeffery 			warnx("Failed to extract member name from PropertiesChanged signal: %s",
209*86094694SAndrew Jeffery 			      strerror(-rc));
210*86094694SAndrew Jeffery 			return rc;
211*86094694SAndrew Jeffery 		}
212*86094694SAndrew Jeffery 
213*86094694SAndrew Jeffery 		if (!strcmp(member, "Status")) {
214*86094694SAndrew Jeffery 			rc = sd_bus_message_enter_container(m, 'v', "s");
215*86094694SAndrew Jeffery 			if (rc < 0) {
216*86094694SAndrew Jeffery 				warnx("Failed to enter variant container in PropertiesChanged signal: %s",
217*86094694SAndrew Jeffery 				      strerror(-rc));
218*86094694SAndrew Jeffery 				return rc;
219*86094694SAndrew Jeffery 			}
220*86094694SAndrew Jeffery 
221*86094694SAndrew Jeffery 			if (!rc)
222*86094694SAndrew Jeffery 				goto exit_dict_container;
223*86094694SAndrew Jeffery 
224*86094694SAndrew Jeffery 			rc = sd_bus_message_read_basic(m, 's', &status);
225*86094694SAndrew Jeffery 			if (rc < 0) {
226*86094694SAndrew Jeffery 				warnx("Failed to extract status value from PropertiesChanged signal: %s",
227*86094694SAndrew Jeffery 				      strerror(-rc));
228*86094694SAndrew Jeffery 				return rc;
229*86094694SAndrew Jeffery 			}
230*86094694SAndrew Jeffery 
231*86094694SAndrew Jeffery 			sd_bus_message_exit_container(m);
232*86094694SAndrew Jeffery 		} else {
233*86094694SAndrew Jeffery 			rc = sd_bus_message_skip(m, "v");
234*86094694SAndrew Jeffery 			if (rc < 0) {
235*86094694SAndrew Jeffery 				warnx("Failed to skip variant for unrecognised member %s in PropertiesChanged signal: %s",
236*86094694SAndrew Jeffery 				      member, strerror(-rc));
237*86094694SAndrew Jeffery 				return rc;
238*86094694SAndrew Jeffery 			}
239*86094694SAndrew Jeffery 		}
240*86094694SAndrew Jeffery 
241*86094694SAndrew Jeffery exit_dict_container:
242*86094694SAndrew Jeffery 		sd_bus_message_exit_container(m);
243*86094694SAndrew Jeffery 	}
244*86094694SAndrew Jeffery 
245*86094694SAndrew Jeffery 	sd_bus_message_exit_container(m);
246*86094694SAndrew Jeffery 
247*86094694SAndrew Jeffery 	if (!status)
248*86094694SAndrew Jeffery 		return 0;
249*86094694SAndrew Jeffery 
250*86094694SAndrew Jeffery 	printf("Dump progress on %s: %s\n", sd_bus_message_get_path(m), status);
251*86094694SAndrew Jeffery 
252*86094694SAndrew Jeffery 	/* If we're finished with the dump, reboot the system */
253*86094694SAndrew Jeffery 	if (!strcmp(status, "xyz.openbmc_project.Common.Progress.OperationStatus.Completed")) {
254*86094694SAndrew Jeffery 		sd_bus_slot *slot = sd_bus_get_current_slot(dbus->bus);
255*86094694SAndrew Jeffery 		sd_bus_slot_unref(slot);
256*86094694SAndrew Jeffery 		dbus_sink_reboot(userdata);
257*86094694SAndrew Jeffery 	}
258*86094694SAndrew Jeffery 
259*86094694SAndrew Jeffery 	return 0;
260*86094694SAndrew Jeffery }
261*86094694SAndrew Jeffery 
262*86094694SAndrew Jeffery static void dbus_sink_debug(void *ctx)
263*86094694SAndrew Jeffery {
264*86094694SAndrew Jeffery 	sd_bus_error ret_error = SD_BUS_ERROR_NULL;
265*86094694SAndrew Jeffery 	struct debug_sink_dbus *dbus = ctx;
266*86094694SAndrew Jeffery 	sd_bus_message *reply;
267*86094694SAndrew Jeffery 	sd_bus_slot *slot;
268*86094694SAndrew Jeffery 	const char *path;
269*86094694SAndrew Jeffery 	char *status;
270*86094694SAndrew Jeffery 	int rc;
271*86094694SAndrew Jeffery 
272*86094694SAndrew Jeffery 	/* Start a BMC dump */
273*86094694SAndrew Jeffery 	rc = sd_bus_call_method(dbus->bus,
274*86094694SAndrew Jeffery 				"xyz.openbmc_project.Dump.Manager",
275*86094694SAndrew Jeffery 				"/xyz/openbmc_project/dump/bmc",
276*86094694SAndrew Jeffery 				"xyz.openbmc_project.Dump.Create",
277*86094694SAndrew Jeffery 				"CreateDump",
278*86094694SAndrew Jeffery 				&ret_error,
279*86094694SAndrew Jeffery 				&reply, "a{sv}", 0);
280*86094694SAndrew Jeffery 	if (rc < 0) {
281*86094694SAndrew Jeffery 		warnx("Failed to call CreateDump: %s", strerror(-rc));
282*86094694SAndrew Jeffery 		return;
283*86094694SAndrew Jeffery 	}
284*86094694SAndrew Jeffery 
285*86094694SAndrew Jeffery 	/* Extract the dump path */
286*86094694SAndrew Jeffery 	rc = sd_bus_message_read_basic(reply, 'o', &path);
287*86094694SAndrew Jeffery 	if (rc < 0) {
288*86094694SAndrew Jeffery 		warnx("Failed to extract dump object path: %s", strerror(-rc));
289*86094694SAndrew Jeffery 		goto cleanup_reply;
290*86094694SAndrew Jeffery 	}
291*86094694SAndrew Jeffery 
292*86094694SAndrew Jeffery 	/* Set up a match watching for completion of the dump */
293*86094694SAndrew Jeffery 	rc = sd_bus_match_signal(dbus->bus,
294*86094694SAndrew Jeffery 				 &slot,
295*86094694SAndrew Jeffery 				 "xyz.openbmc_project.Dump.Manager",
296*86094694SAndrew Jeffery 				 path,
297*86094694SAndrew Jeffery 				 "org.freedesktop.DBus.Properties",
298*86094694SAndrew Jeffery 				 "PropertiesChanged",
299*86094694SAndrew Jeffery 				 dbus_sink_dump_progress,
300*86094694SAndrew Jeffery 				 ctx);
301*86094694SAndrew Jeffery 	if (rc < 0) {
302*86094694SAndrew Jeffery 		warnx("Failed to add signal match for progress status on dump object %s: %s",
303*86094694SAndrew Jeffery 		      path, strerror(-rc));
304*86094694SAndrew Jeffery 		goto cleanup_reply;
305*86094694SAndrew Jeffery 	}
306*86094694SAndrew Jeffery 
307*86094694SAndrew Jeffery 	/*
308*86094694SAndrew Jeffery 	 * Mark the slot as 'floating'. If a slot is _not_ marked as floating it holds a reference
309*86094694SAndrew Jeffery 	 * to the bus, and the bus will stay alive so long as the slot is referenced. If the slot is
310*86094694SAndrew Jeffery 	 * instead marked floating the relationship is inverted: The lifetime of the slot is defined
311*86094694SAndrew Jeffery 	 * in terms of the bus, which means we relieve ourselves of having to track the lifetime of
312*86094694SAndrew Jeffery 	 * the slot.
313*86094694SAndrew Jeffery 	 *
314*86094694SAndrew Jeffery 	 * For more details see `man 3 sd_bus_slot_set_floating`, also documented here:
315*86094694SAndrew Jeffery 	 *
316*86094694SAndrew Jeffery 	 * https://www.freedesktop.org/software/systemd/man/sd_bus_slot_set_floating.html
317*86094694SAndrew Jeffery 	 */
318*86094694SAndrew Jeffery 	rc = sd_bus_slot_set_floating(slot, 0);
319*86094694SAndrew Jeffery 	if (rc < 0) {
320*86094694SAndrew Jeffery 		warnx("Failed to mark progress match slot on %s as floating: %s",
321*86094694SAndrew Jeffery 		      path, strerror(-rc));
322*86094694SAndrew Jeffery 		goto cleanup_reply;
323*86094694SAndrew Jeffery 	}
324*86094694SAndrew Jeffery 
325*86094694SAndrew Jeffery 	printf("Registered progress match on dump object %s\n", path);
326*86094694SAndrew Jeffery 
327*86094694SAndrew Jeffery 	/* Now that the match is set up, check the current value in case we missed any updates */
328*86094694SAndrew Jeffery 	rc = sd_bus_get_property_string(dbus->bus,
329*86094694SAndrew Jeffery 					"xyz.openbmc_project.Dump.Manager",
330*86094694SAndrew Jeffery 					path,
331*86094694SAndrew Jeffery 					"xyz.openbmc_project.Common.Progress",
332*86094694SAndrew Jeffery 					"Status",
333*86094694SAndrew Jeffery 					&ret_error,
334*86094694SAndrew Jeffery 					&status);
335*86094694SAndrew Jeffery 	if (rc < 0) {
336*86094694SAndrew Jeffery 		warnx("Failed to get progress status property on dump object %s: %s",
337*86094694SAndrew Jeffery 		      path, strerror(-rc));
338*86094694SAndrew Jeffery 		sd_bus_slot_unref(slot);
339*86094694SAndrew Jeffery 		goto cleanup_reply;
340*86094694SAndrew Jeffery 	}
341*86094694SAndrew Jeffery 
342*86094694SAndrew Jeffery 	printf("Dump state for %s is currently %s\n", path, status);
343*86094694SAndrew Jeffery 
344*86094694SAndrew Jeffery 	/*
345*86094694SAndrew Jeffery 	 * If we're finished with the dump, reboot the system. If the dump isn't finished the reboot
346*86094694SAndrew Jeffery 	 * will instead take place via the dbus_sink_dump_progress() callback on the match.
347*86094694SAndrew Jeffery 	 */
348*86094694SAndrew Jeffery 	if (!strcmp(status, "xyz.openbmc_project.Common.Progress.OperationStatus.Completed")) {
349*86094694SAndrew Jeffery 		sd_bus_slot_unref(slot);
350*86094694SAndrew Jeffery 		dbus_sink_reboot(ctx);
351*86094694SAndrew Jeffery 	}
352*86094694SAndrew Jeffery 
353*86094694SAndrew Jeffery cleanup_reply:
354*86094694SAndrew Jeffery 	sd_bus_message_unref(reply);
355*86094694SAndrew Jeffery }
356*86094694SAndrew Jeffery 
357*86094694SAndrew Jeffery static void dbus_sink_reboot(void *ctx)
358*86094694SAndrew Jeffery {
359*86094694SAndrew Jeffery 	sd_bus_error ret_error = SD_BUS_ERROR_NULL;
360*86094694SAndrew Jeffery 	struct debug_sink_dbus *dbus = ctx;
361*86094694SAndrew Jeffery 	sd_bus_message *reply;
362*86094694SAndrew Jeffery 	int rc;
363*86094694SAndrew Jeffery 
364*86094694SAndrew Jeffery 	warnx("Rebooting the system");
365*86094694SAndrew Jeffery 
366*86094694SAndrew Jeffery 	rc = sd_bus_call_method(dbus->bus,
367*86094694SAndrew Jeffery 				"org.freedesktop.systemd1",
368*86094694SAndrew Jeffery 				"/org/freedesktop/systemd1",
369*86094694SAndrew Jeffery 				"org.freedesktop.systemd1.Manager",
370*86094694SAndrew Jeffery 				"StartUnit",
371*86094694SAndrew Jeffery 				&ret_error,
372*86094694SAndrew Jeffery 				&reply,
373*86094694SAndrew Jeffery 				"ss",
374*86094694SAndrew Jeffery 				"reboot.target",
375*86094694SAndrew Jeffery 				"replace-irreversibly");
376*86094694SAndrew Jeffery 	if (rc < 0) {
377*86094694SAndrew Jeffery 		warnx("Failed to start reboot.target: %s", strerror(-rc));
378*86094694SAndrew Jeffery 	}
379*86094694SAndrew Jeffery }
380*86094694SAndrew Jeffery 
381*86094694SAndrew Jeffery static int dbus_source_poll(void *ctx, char *op)
382*86094694SAndrew Jeffery {
383*86094694SAndrew Jeffery 	struct debug_source_dbus *dbus = ctx;
384*86094694SAndrew Jeffery 	int rc;
385*86094694SAndrew Jeffery 
386*86094694SAndrew Jeffery 	while (1) {
387*86094694SAndrew Jeffery 		struct timespec tsto, *ptsto;
388*86094694SAndrew Jeffery 		uint64_t dbusto;
389*86094694SAndrew Jeffery 
390*86094694SAndrew Jeffery 		/* See SD_BUS_GET_FD(3) */
391*86094694SAndrew Jeffery 		dbus->pfds[DBUS_SOURCE_PFD_DBUS].fd = sd_bus_get_fd(dbus->bus);
392*86094694SAndrew Jeffery 		dbus->pfds[DBUS_SOURCE_PFD_DBUS].events = sd_bus_get_events(dbus->bus);
393*86094694SAndrew Jeffery 		rc = sd_bus_get_timeout(dbus->bus, &dbusto);
394*86094694SAndrew Jeffery 		if (rc < 0)
395*86094694SAndrew Jeffery 			return rc;
396*86094694SAndrew Jeffery 
397*86094694SAndrew Jeffery 		if (dbusto == UINT64_MAX) {
398*86094694SAndrew Jeffery 			ptsto = NULL;
399*86094694SAndrew Jeffery 		} else if (dbus->pfds[DBUS_SOURCE_PFD_DBUS].events == 0) {
400*86094694SAndrew Jeffery 			ptsto = NULL;
401*86094694SAndrew Jeffery 		} else {
402*86094694SAndrew Jeffery #define MSEC_PER_SEC 1000U
403*86094694SAndrew Jeffery #define USEC_PER_SEC (MSEC_PER_SEC * 1000U)
404*86094694SAndrew Jeffery #define NSEC_PER_SEC (USEC_PER_SEC * 1000U)
405*86094694SAndrew Jeffery #define NSEC_PER_USEC (NSEC_PER_SEC / USEC_PER_SEC)
406*86094694SAndrew Jeffery 			tsto.tv_sec = dbusto / USEC_PER_SEC;
407*86094694SAndrew Jeffery 			tsto.tv_nsec = (dbusto % USEC_PER_SEC) * NSEC_PER_USEC;
408*86094694SAndrew Jeffery 			ptsto = &tsto;
409*86094694SAndrew Jeffery 		}
410*86094694SAndrew Jeffery 
411*86094694SAndrew Jeffery 		if ((rc = ppoll(dbus->pfds, ARRAY_SIZE(dbus->pfds), ptsto, NULL)) < 0) {
412*86094694SAndrew Jeffery 			warn("Failed polling source fds");
413*86094694SAndrew Jeffery 			return -errno;
414*86094694SAndrew Jeffery 		}
415*86094694SAndrew Jeffery 
416*86094694SAndrew Jeffery 		if (dbus->pfds[DBUS_SOURCE_PFD_SOURCE].revents) {
417*86094694SAndrew Jeffery 			ssize_t ingress;
418*86094694SAndrew Jeffery 
419*86094694SAndrew Jeffery 			if ((ingress = read(dbus->pfds[DBUS_SOURCE_PFD_SOURCE].fd, op, 1)) != 1) {
420*86094694SAndrew Jeffery 				if (ingress < 0) {
421*86094694SAndrew Jeffery 					warn("Failed to read from basic source");
422*86094694SAndrew Jeffery 					return -errno;
423*86094694SAndrew Jeffery 				}
424*86094694SAndrew Jeffery 
425*86094694SAndrew Jeffery 				errx(EXIT_FAILURE, "Bad read, requested 1 got %zd", ingress);
426*86094694SAndrew Jeffery 			}
427*86094694SAndrew Jeffery 
428*86094694SAndrew Jeffery 			return 0;
429*86094694SAndrew Jeffery 		}
430*86094694SAndrew Jeffery 
431*86094694SAndrew Jeffery 		if (dbus->pfds[DBUS_SOURCE_PFD_DBUS].revents) {
432*86094694SAndrew Jeffery 			if ((rc = sd_bus_process(dbus->bus, NULL)) < 0) {
433*86094694SAndrew Jeffery 				warnx("Failed processing inbound D-Bus messages: %s",
434*86094694SAndrew Jeffery 				      strerror(-rc));
435*86094694SAndrew Jeffery 				return rc;
436*86094694SAndrew Jeffery 			}
437*86094694SAndrew Jeffery 		}
438*86094694SAndrew Jeffery 	}
439*86094694SAndrew Jeffery }
440*86094694SAndrew Jeffery #else
441*86094694SAndrew Jeffery static void dbus_sink_debug(void *ctx)
442*86094694SAndrew Jeffery {
443*86094694SAndrew Jeffery 	warnx("%s: Configured without systemd, dbus sinks disabled", __func__);
444*86094694SAndrew Jeffery }
445*86094694SAndrew Jeffery 
446*86094694SAndrew Jeffery static void dbus_sink_reboot(void *ctx)
447*86094694SAndrew Jeffery {
448*86094694SAndrew Jeffery 	warnx("%s: Configured without systemd, dbus sinks disabled", __func__);
449*86094694SAndrew Jeffery }
450*86094694SAndrew Jeffery 
451*86094694SAndrew Jeffery static int dbus_source_poll(void *ctx, char *op)
452*86094694SAndrew Jeffery {
453*86094694SAndrew Jeffery 	errx(EXIT_FAILURE, "Configured without systemd, dbus sources disabled", __func__);
454*86094694SAndrew Jeffery }
455*86094694SAndrew Jeffery #endif
456*86094694SAndrew Jeffery 
457*86094694SAndrew Jeffery const struct debug_sink_ops dbus_sink_ops = {
458*86094694SAndrew Jeffery 	.debug = dbus_sink_debug,
459*86094694SAndrew Jeffery 	.reboot = dbus_sink_reboot,
460*86094694SAndrew Jeffery };
461*86094694SAndrew Jeffery 
462*86094694SAndrew Jeffery const struct debug_source_ops dbus_source_ops = {
463*86094694SAndrew Jeffery 	.poll = dbus_source_poll,
464*86094694SAndrew Jeffery };
465*86094694SAndrew Jeffery 
466b1ea254eSAndrew Jeffery static int process(struct debug_source *source, struct debug_sink *sink)
467b1ea254eSAndrew Jeffery {
468b1ea254eSAndrew Jeffery 	char command;
469b1ea254eSAndrew Jeffery 	int rc;
470b1ea254eSAndrew Jeffery 
471b1ea254eSAndrew Jeffery 	while (!(rc = source->ops->poll(source->ctx, &command))) {
472210ad636SAndrew Jeffery 		switch (command) {
473210ad636SAndrew Jeffery 		case 'D':
4741dc6adc9SAndrew Jeffery 			sink->ops->debug(sink->ctx);
475210ad636SAndrew Jeffery 			break;
476210ad636SAndrew Jeffery 		case 'R':
4771dc6adc9SAndrew Jeffery 			sink->ops->reboot(sink->ctx);
47811cd254bSAndrew Jeffery 			break;
47911cd254bSAndrew Jeffery 		default:
48011cd254bSAndrew Jeffery 			warnx("Unexpected command: 0x%02x (%c)", command, command);
48111cd254bSAndrew Jeffery 		}
48211cd254bSAndrew Jeffery 	}
48311cd254bSAndrew Jeffery 
484b1ea254eSAndrew Jeffery 	if (rc < 0)
485b1ea254eSAndrew Jeffery 		warnx("Failed to poll source: %s", strerror(-rc));
48611cd254bSAndrew Jeffery 
487b1ea254eSAndrew Jeffery 	return rc;
48811cd254bSAndrew Jeffery }
48911cd254bSAndrew Jeffery 
49011cd254bSAndrew Jeffery int main(int argc, char * const argv[])
49111cd254bSAndrew Jeffery {
492*86094694SAndrew Jeffery 	struct debug_source_basic basic_source;
493*86094694SAndrew Jeffery 	struct debug_source_dbus dbus_source;
494*86094694SAndrew Jeffery 	struct debug_sink_sysrq sysrq_sink;
495*86094694SAndrew Jeffery 	struct debug_sink_dbus dbus_sink;
496e998ba77SAndrew Jeffery 	const char *sink_actions = NULL;
497b1ea254eSAndrew Jeffery 	struct debug_source source;
4981dc6adc9SAndrew Jeffery 	struct debug_sink sink;
49911cd254bSAndrew Jeffery 	char devnode[PATH_MAX];
50011cd254bSAndrew Jeffery 	char *devid;
5011dc6adc9SAndrew Jeffery 	int sourcefd;
5021dc6adc9SAndrew Jeffery 	int sinkfd;
50311cd254bSAndrew Jeffery 
504e998ba77SAndrew Jeffery 	/* Option processing */
50511cd254bSAndrew Jeffery 	while (1) {
50611cd254bSAndrew Jeffery 		static struct option long_options[] = {
507e998ba77SAndrew Jeffery 			{"sink-actions", required_argument, 0, 's'},
50811cd254bSAndrew Jeffery 			{0, 0, 0, 0},
50911cd254bSAndrew Jeffery 		};
51011cd254bSAndrew Jeffery 		int c;
51111cd254bSAndrew Jeffery 
51211cd254bSAndrew Jeffery 		c = getopt_long(argc, argv, "", long_options, NULL);
51311cd254bSAndrew Jeffery 		if (c == -1)
51411cd254bSAndrew Jeffery 			break;
515e998ba77SAndrew Jeffery 
516e998ba77SAndrew Jeffery 		switch (c) {
517e998ba77SAndrew Jeffery 		case 's':
518e998ba77SAndrew Jeffery 			sink_actions = optarg;
519e998ba77SAndrew Jeffery 			break;
520e998ba77SAndrew Jeffery 		default:
521e998ba77SAndrew Jeffery 			break;
522e998ba77SAndrew Jeffery 		}
52311cd254bSAndrew Jeffery 	}
52411cd254bSAndrew Jeffery 
525d65368beSAndrew Jeffery 	/*
526e998ba77SAndrew Jeffery 	 * The default behaviour sets the source file descriptor as stdin and the sink file
527e998ba77SAndrew Jeffery 	 * descriptor as stdout. This allows trivial testing on the command-line with just a
528e998ba77SAndrew Jeffery 	 * keyboard and without crashing the system.
529d65368beSAndrew Jeffery 	 */
5301dc6adc9SAndrew Jeffery 	sourcefd = 0;
5311dc6adc9SAndrew Jeffery 	sinkfd = 1;
53211cd254bSAndrew Jeffery 
533e998ba77SAndrew Jeffery 	/* Handle the source path argument, if any */
53411cd254bSAndrew Jeffery 	if (optind < argc) {
53511cd254bSAndrew Jeffery 		char devpath[PATH_MAX];
53611cd254bSAndrew Jeffery 
537d65368beSAndrew Jeffery 		/*
538d65368beSAndrew Jeffery 		 * To make our lives easy with udev we take the basename of the source argument and
539d65368beSAndrew Jeffery 		 * look for it in /dev. This allows us to use %p (the devpath specifier) in the udev
540d65368beSAndrew Jeffery 		 * rule to pass the device of interest to the systemd unit.
541d65368beSAndrew Jeffery 		 */
54211cd254bSAndrew Jeffery 		strncpy(devpath, argv[optind], sizeof(devpath));
54311cd254bSAndrew Jeffery 		devpath[PATH_MAX - 1] = '\0';
54411cd254bSAndrew Jeffery 		devid = basename(devpath);
54511cd254bSAndrew Jeffery 
54611cd254bSAndrew Jeffery 		strncpy(devnode, "/dev/", sizeof(devnode));
54711cd254bSAndrew Jeffery 		strncat(devnode, devid, sizeof(devnode));
54811cd254bSAndrew Jeffery 		devnode[PATH_MAX - 1] = '\0';
54911cd254bSAndrew Jeffery 
5501dc6adc9SAndrew Jeffery 		if ((sourcefd = open(devnode, O_RDONLY)) == -1)
55111cd254bSAndrew Jeffery 			err(EXIT_FAILURE, "Failed to open %s", devnode);
55211cd254bSAndrew Jeffery 
55311cd254bSAndrew Jeffery 		optind++;
55411cd254bSAndrew Jeffery 	}
55511cd254bSAndrew Jeffery 
556e998ba77SAndrew Jeffery 	/*
557e998ba77SAndrew Jeffery 	 * Handle the sink path argument, if any. If sink_actions hasn't been set via the
558e998ba77SAndrew Jeffery 	 * --sink-actions option, then default to 'sysrq'. Otherwise, if --sink-actions=sysrq has
559e998ba77SAndrew Jeffery 	 * been passed, do as we're told and use the 'sysrq' sink actions.
560e998ba77SAndrew Jeffery 	 */
561e998ba77SAndrew Jeffery 	if (!sink_actions || !strcmp("sysrq", sink_actions)) {
56211cd254bSAndrew Jeffery 		if (optind < argc) {
563d65368beSAndrew Jeffery 			/*
564e998ba77SAndrew Jeffery 			 * Just open the sink path directly. If we ever need different behaviour
565e998ba77SAndrew Jeffery 			 * then we patch this bit when we know what we need.
566d65368beSAndrew Jeffery 			 */
5671dc6adc9SAndrew Jeffery 			if ((sinkfd = open(argv[optind], O_WRONLY)) == -1)
56811cd254bSAndrew Jeffery 				err(EXIT_FAILURE, "Failed to open %s", argv[optind]);
56911cd254bSAndrew Jeffery 
57011cd254bSAndrew Jeffery 			optind++;
57111cd254bSAndrew Jeffery 		}
57211cd254bSAndrew Jeffery 
573*86094694SAndrew Jeffery 		basic_source.source = sourcefd;
574*86094694SAndrew Jeffery 		source.ops = &basic_source_ops;
575*86094694SAndrew Jeffery 		source.ctx = &basic_source;
576*86094694SAndrew Jeffery 
577*86094694SAndrew Jeffery 		sysrq_sink.sink = sinkfd;
5781dc6adc9SAndrew Jeffery 		sink.ops = &sysrq_sink_ops;
579*86094694SAndrew Jeffery 		sink.ctx = &sysrq_sink;
580*86094694SAndrew Jeffery 	}
581*86094694SAndrew Jeffery 
582*86094694SAndrew Jeffery 	/* Set up the dbus sink actions if requested via --sink-actions=dbus */
583*86094694SAndrew Jeffery 	if (sink_actions && !strcmp("dbus", sink_actions)) {
584*86094694SAndrew Jeffery 		sd_bus *bus;
585*86094694SAndrew Jeffery 		int rc;
586*86094694SAndrew Jeffery 
587*86094694SAndrew Jeffery 		rc = sd_bus_open_system(&bus);
588*86094694SAndrew Jeffery 		if (rc < 0) {
589*86094694SAndrew Jeffery 			errx(EXIT_FAILURE, "Failed to connect to the system bus: %s",
590*86094694SAndrew Jeffery 			       strerror(-rc));
591*86094694SAndrew Jeffery 		}
592*86094694SAndrew Jeffery 
593*86094694SAndrew Jeffery 		dbus_source.bus = bus;
594*86094694SAndrew Jeffery 		dbus_source.pfds[DBUS_SOURCE_PFD_SOURCE].fd = sourcefd;
595*86094694SAndrew Jeffery 		dbus_source.pfds[DBUS_SOURCE_PFD_SOURCE].events = POLLIN;
596*86094694SAndrew Jeffery 		source.ops = &dbus_source_ops;
597*86094694SAndrew Jeffery 		source.ctx = &dbus_source;
598*86094694SAndrew Jeffery 
599*86094694SAndrew Jeffery 		dbus_sink.bus = bus;
600*86094694SAndrew Jeffery 		sink.ops = &dbus_sink_ops;
601*86094694SAndrew Jeffery 		sink.ctx = &dbus_sink;
602e998ba77SAndrew Jeffery 	}
603e998ba77SAndrew Jeffery 
604e998ba77SAndrew Jeffery 	/* Check we're done with the command-line */
605e998ba77SAndrew Jeffery 	if (optind < argc)
606*86094694SAndrew Jeffery 		errx(EXIT_FAILURE, "Found %d unexpected arguments", argc - optind);
6071dc6adc9SAndrew Jeffery 
608*86094694SAndrew Jeffery 	if (!(source.ops && source.ctx))
609*86094694SAndrew Jeffery 		errx(EXIT_FAILURE, "Invalid source configuration");
610*86094694SAndrew Jeffery 
611*86094694SAndrew Jeffery 	if (!(sink.ops && sink.ctx))
612*86094694SAndrew Jeffery 		errx(EXIT_FAILURE, "Unrecognised sink: %s", sink_actions);
613b1ea254eSAndrew Jeffery 
614d65368beSAndrew Jeffery 	/* Trigger the actions on the sink when we receive an event from the source */
615b1ea254eSAndrew Jeffery 	if (process(&source, &sink) < 0)
61611cd254bSAndrew Jeffery 		errx(EXIT_FAILURE, "Failure while processing command stream");
61711cd254bSAndrew Jeffery 
61811cd254bSAndrew Jeffery 	return 0;
61911cd254bSAndrew Jeffery }
620