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