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