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