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