xref: /openbmc/debug-trigger/main.c (revision b1ea254e6c3b1578127f97356612dffa85073698)
1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright (C) 2021 IBM Corp.
3 
4 /*
5  * debug-trigger listens for an external signal that the BMC is in some way unresponsive. When a
6  * signal is received it triggers a crash to collect debug data and reboots the system in the hope
7  * that it will recover.
8  *
9  * Usage: debug-trigger [SOURCE] [SINK]
10  *
11  * Options:
12  *  --sink-actions=ACTION
13  *	Set the class of sink action(s) to be used. Defaults to 'sysrq'
14  *
15  * Examples:
16  *  debug-trigger
17  *	Set the source as stdin, the sink as stdout, and use the default 'sysrq' set of sink
18  *	actions. Useful for testing.
19  *
20  *  debug-trigger --sink-actions=sysrq
21  *	Explicitly use the 'sysrq' set of sink actions with stdin as the source and stdout as the
22  *	sink.
23  *
24  *  debug-trigger /dev/serio_raw0 /proc/sysrq-trigger
25  *	Open /dev/serio_raw0 as the source and /proc/sysrq-trigger as the sink, with the default
26  *	'sysrq' set of sink actions. When 'D' is read from /dev/serio_raw0 'c' will be written to
27  *	/proc/sysrq-trigger, causing a kernel panic. When 'R' is read from /dev/serio_raw0 'b' will
28  *	be written to /proc/sysrq-trigger, causing an immediate reboot of the system.
29  */
30 
31 #include <err.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <getopt.h>
35 #include <libgen.h>
36 #include <limits.h>
37 #include <linux/reboot.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <sys/reboot.h>
41 #include <sys/stat.h>
42 #include <sys/types.h>
43 #include <unistd.h>
44 
45 struct debug_source_ops {
46 	int (*poll)(void *ctx, char *op);
47 };
48 
49 struct debug_source {
50 	const struct debug_source_ops *ops;
51 	void *ctx;
52 };
53 
54 struct debug_source_basic {
55 	int source;
56 };
57 
58 struct debug_sink_ops {
59 	void (*debug)(void *ctx);
60 	void (*reboot)(void *ctx);
61 };
62 
63 struct debug_sink {
64 	const struct debug_sink_ops *ops;
65 	void *ctx;
66 };
67 
68 struct debug_sink_sysrq {
69 	int sink;
70 };
71 
72 static void sysrq_sink_debug(void *ctx)
73 {
74 	struct debug_sink_sysrq *sysrq = ctx;
75 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n93 */
76 	static const char action = 'c';
77 	ssize_t rc;
78 
79 	sync();
80 
81 	if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
82 		return;
83 
84 	if (rc == -1) {
85 		warn("Failed to execute debug command");
86 	} else {
87 		warnx("Failed to execute debug command: %zd", rc);
88 	}
89 }
90 
91 static void sysrq_sink_reboot(void *ctx)
92 {
93 	struct debug_sink_sysrq *sysrq = ctx;
94 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n90 */
95 	static const char action = 'b';
96 	ssize_t rc;
97 
98 	sync();
99 
100 	if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
101 		return;
102 
103 	if (rc == -1) {
104 		warn("Failed to reboot BMC");
105 	} else {
106 		warnx("Failed to reboot BMC: %zd", rc);
107 	}
108 }
109 
110 const struct debug_sink_ops sysrq_sink_ops = {
111 	.debug = sysrq_sink_debug,
112 	.reboot = sysrq_sink_reboot,
113 };
114 
115 static int basic_source_poll(void *ctx, char *op)
116 {
117 	struct debug_source_basic *basic = ctx;
118 	ssize_t ingress;
119 
120 	if ((ingress = read(basic->source, op, 1)) != 1) {
121 		if (ingress < 0) {
122 			warn("Failed to read from basic source");
123 			return -errno;
124 		}
125 
126 		/* Unreachable */
127 		errx(EXIT_FAILURE, "Bad read, requested 1 got %zd", ingress);
128 	}
129 
130 	return 0;
131 }
132 
133 const struct debug_source_ops basic_source_ops = {
134 	.poll = basic_source_poll,
135 };
136 
137 static int process(struct debug_source *source, struct debug_sink *sink)
138 {
139 	char command;
140 	int rc;
141 
142 	while (!(rc = source->ops->poll(source->ctx, &command))) {
143 		switch (command) {
144 		case 'D':
145 			sink->ops->debug(sink->ctx);
146 			break;
147 		case 'R':
148 			sink->ops->reboot(sink->ctx);
149 			break;
150 		default:
151 			warnx("Unexpected command: 0x%02x (%c)", command, command);
152 		}
153 	}
154 
155 	if (rc < 0)
156 		warnx("Failed to poll source: %s", strerror(-rc));
157 
158 	return rc;
159 }
160 
161 int main(int argc, char * const argv[])
162 {
163 	const char *sink_actions = NULL;
164 	struct debug_source_basic basic;
165 	struct debug_sink_sysrq sysrq;
166 	struct debug_source source;
167 	struct debug_sink sink;
168 	char devnode[PATH_MAX];
169 	char *devid;
170 	int sourcefd;
171 	int sinkfd;
172 
173 	/* Option processing */
174 	while (1) {
175 		static struct option long_options[] = {
176 			{"sink-actions", required_argument, 0, 's'},
177 			{0, 0, 0, 0},
178 		};
179 		int c;
180 
181 		c = getopt_long(argc, argv, "", long_options, NULL);
182 		if (c == -1)
183 			break;
184 
185 		switch (c) {
186 		case 's':
187 			sink_actions = optarg;
188 			break;
189 		default:
190 			break;
191 		}
192 	}
193 
194 	/*
195 	 * The default behaviour sets the source file descriptor as stdin and the sink file
196 	 * descriptor as stdout. This allows trivial testing on the command-line with just a
197 	 * keyboard and without crashing the system.
198 	 */
199 	sourcefd = 0;
200 	sinkfd = 1;
201 
202 	/* Handle the source path argument, if any */
203 	if (optind < argc) {
204 		char devpath[PATH_MAX];
205 
206 		/*
207 		 * To make our lives easy with udev we take the basename of the source argument and
208 		 * look for it in /dev. This allows us to use %p (the devpath specifier) in the udev
209 		 * rule to pass the device of interest to the systemd unit.
210 		 */
211 		strncpy(devpath, argv[optind], sizeof(devpath));
212 		devpath[PATH_MAX - 1] = '\0';
213 		devid = basename(devpath);
214 
215 		strncpy(devnode, "/dev/", sizeof(devnode));
216 		strncat(devnode, devid, sizeof(devnode));
217 		devnode[PATH_MAX - 1] = '\0';
218 
219 		if ((sourcefd = open(devnode, O_RDONLY)) == -1)
220 			err(EXIT_FAILURE, "Failed to open %s", devnode);
221 
222 		optind++;
223 	}
224 
225 	/*
226 	 * Handle the sink path argument, if any. If sink_actions hasn't been set via the
227 	 * --sink-actions option, then default to 'sysrq'. Otherwise, if --sink-actions=sysrq has
228 	 * been passed, do as we're told and use the 'sysrq' sink actions.
229 	 */
230 	if (!sink_actions || !strcmp("sysrq", sink_actions)) {
231 		if (optind < argc) {
232 			/*
233 			 * Just open the sink path directly. If we ever need different behaviour
234 			 * then we patch this bit when we know what we need.
235 			 */
236 			if ((sinkfd = open(argv[optind], O_WRONLY)) == -1)
237 				err(EXIT_FAILURE, "Failed to open %s", argv[optind]);
238 
239 			optind++;
240 		}
241 
242 		sysrq.sink = sinkfd;
243 		sink.ops = &sysrq_sink_ops;
244 		sink.ctx = &sysrq;
245 	}
246 
247 	/* Check we're done with the command-line */
248 	if (optind < argc)
249 		err(EXIT_FAILURE, "Found %d unexpected arguments", argc - optind);
250 
251 	basic.source = sourcefd;
252 	source.ops = &basic_source_ops;
253 	source.ctx = &basic;
254 
255 	/* Trigger the actions on the sink when we receive an event from the source */
256 	if (process(&source, &sink) < 0)
257 		errx(EXIT_FAILURE, "Failure while processing command stream");
258 
259 	return 0;
260 }
261