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