xref: /openbmc/debug-trigger/main.c (revision 1dc6adc90d294b0acd85c5203af266c8e22f9527)
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 the
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  * Examples:
12  *  debug-trigger
13  *	Set the source as stdin and the sink as stdout. Useful for testing.
14  *
15  *  debug-trigger /dev/serio_raw0 /proc/sysrq-trigger
16  *	Open /dev/serio_raw0 as the source and /proc/sysrq-trigger as the sink.
17  */
18 
19 #include <err.h>
20 #include <fcntl.h>
21 #include <getopt.h>
22 #include <libgen.h>
23 #include <limits.h>
24 #include <linux/reboot.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/reboot.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #include <unistd.h>
31 
32 struct debug_sink_ops {
33 	void (*debug)(void *ctx);
34 	void (*reboot)(void *ctx);
35 };
36 
37 struct debug_sink {
38 	const struct debug_sink_ops *ops;
39 	void *ctx;
40 };
41 
42 struct debug_sink_sysrq {
43 	int sink;
44 };
45 
46 static void sysrq_sink_debug(void *ctx)
47 {
48 	struct debug_sink_sysrq *sysrq = ctx;
49 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n93 */
50 	static const char action = 'c';
51 	ssize_t rc;
52 
53 	sync();
54 
55 	if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
56 		return;
57 
58 	if (rc == -1) {
59 		warn("Failed to execute debug command");
60 	} else {
61 		warnx("Failed to execute debug command: %zd", rc);
62 	}
63 }
64 
65 static void sysrq_sink_reboot(void *ctx)
66 {
67 	struct debug_sink_sysrq *sysrq = ctx;
68 	/* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/admin-guide/sysrq.rst?h=v5.16#n90 */
69 	static const char action = 'b';
70 	ssize_t rc;
71 
72 	sync();
73 
74 	if ((rc = write(sysrq->sink, &action, sizeof(action))) == sizeof(action))
75 		return;
76 
77 	if (rc == -1) {
78 		warn("Failed to reboot BMC");
79 	} else {
80 		warnx("Failed to reboot BMC: %zd", rc);
81 	}
82 }
83 
84 const struct debug_sink_ops sysrq_sink_ops = {
85 	.debug = sysrq_sink_debug,
86 	.reboot = sysrq_sink_reboot,
87 };
88 
89 static int process(int source, struct debug_sink *sink)
90 {
91 	ssize_t ingress;
92 	char command;
93 
94 	while ((ingress = read(source, &command, sizeof(command))) == sizeof(command)) {
95 		switch (command) {
96 		case 'D':
97 			sink->ops->debug(sink->ctx);
98 			break;
99 		case 'R':
100 			sink->ops->reboot(sink->ctx);
101 			break;
102 		default:
103 			warnx("Unexpected command: 0x%02x (%c)", command, command);
104 		}
105 	}
106 
107 	if (ingress == -1)
108 		warn("Failed to read from source");
109 
110 	return ingress;
111 }
112 
113 int main(int argc, char * const argv[])
114 {
115 	struct debug_sink_sysrq sysrq;
116 	struct debug_sink sink;
117 	char devnode[PATH_MAX];
118 	char *devid;
119 	int sourcefd;
120 	int sinkfd;
121 
122 	/* Option processing. Currently nothing implemented, but allows us to use optind */
123 	while (1) {
124 		static struct option long_options[] = {
125 			{0, 0, 0, 0},
126 		};
127 		int c;
128 
129 		c = getopt_long(argc, argv, "", long_options, NULL);
130 		if (c == -1)
131 			break;
132 	}
133 
134 	/*
135 	 * The default behaviour sets the source as stdin and the sink as stdout. This allows
136 	 * trivial testing on the command-line with just a keyboard and without crashing the system.
137 	 */
138 	sourcefd = 0;
139 	sinkfd = 1;
140 
141 	/* Handle the source argument, if any */
142 	if (optind < argc) {
143 		char devpath[PATH_MAX];
144 
145 		/*
146 		 * To make our lives easy with udev we take the basename of the source argument and
147 		 * look for it in /dev. This allows us to use %p (the devpath specifier) in the udev
148 		 * rule to pass the device of interest to the systemd unit.
149 		 */
150 		strncpy(devpath, argv[optind], sizeof(devpath));
151 		devpath[PATH_MAX - 1] = '\0';
152 		devid = basename(devpath);
153 
154 		strncpy(devnode, "/dev/", sizeof(devnode));
155 		strncat(devnode, devid, sizeof(devnode));
156 		devnode[PATH_MAX - 1] = '\0';
157 
158 		if ((sourcefd = open(devnode, O_RDONLY)) == -1)
159 			err(EXIT_FAILURE, "Failed to open %s", devnode);
160 
161 		optind++;
162 	}
163 
164 	/* Handle the sink argument, if any */
165 	if (optind < argc) {
166 		/*
167 		 * Just open the sink path directly. If we ever need different behaviour then we
168 		 * patch this bit when we know what we need.
169 		 */
170 		if ((sinkfd = open(argv[optind], O_WRONLY)) == -1)
171 			err(EXIT_FAILURE, "Failed to open %s", argv[optind]);
172 
173 		optind++;
174 	}
175 
176 	/* Check we're done with the command-line */
177 	if (optind < argc)
178 		err(EXIT_FAILURE, "Found %d unexpected arguments", argc - optind);
179 
180 	sysrq.sink = sinkfd;
181 	sink.ops = &sysrq_sink_ops;
182 	sink.ctx = &sysrq;
183 
184 	/* Trigger the actions on the sink when we receive an event from the source */
185 	if (process(sourcefd, &sink) < 0)
186 		errx(EXIT_FAILURE, "Failure while processing command stream");
187 
188 	return 0;
189 }
190