Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 1 | /* |
| 2 | * kmod-static-nodes - manage modules.devname |
| 3 | * |
| 4 | * Copyright (C) 2004-2012 Kay Sievers <kay@vrfy.org> |
| 5 | * Copyright (C) 2011-2013 ProFUSION embedded systems |
| 6 | * Copyright (C) 2013 Tom Gundersen <teg@jklm.no> |
| 7 | * |
| 8 | * This program is free software: you can redistribute it and/or modify |
| 9 | * it under the terms of the GNU General Public License as published by |
| 10 | * the Free Software Foundation, either version 2 of the License, or |
| 11 | * (at your option) any later version. |
| 12 | * |
| 13 | * This program is distributed in the hope that it will be useful, |
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | * GNU General Public License for more details. |
| 17 | * |
| 18 | * You should have received a copy of the GNU General Public License |
| 19 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 20 | */ |
| 21 | |
Lucas De Marchi | c2e4286 | 2014-10-03 01:41:42 -0300 | [diff] [blame] | 22 | #include <errno.h> |
| 23 | #include <getopt.h> |
| 24 | #include <limits.h> |
| 25 | #include <stddef.h> |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 26 | #include <stdio.h> |
| 27 | #include <stdlib.h> |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 28 | #include <string.h> |
Lucas De Marchi | c2e4286 | 2014-10-03 01:41:42 -0300 | [diff] [blame] | 29 | #include <unistd.h> |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 30 | #include <sys/stat.h> |
| 31 | #include <sys/types.h> |
Lucas De Marchi | c2e4286 | 2014-10-03 01:41:42 -0300 | [diff] [blame] | 32 | #include <sys/utsname.h> |
Lucas De Marchi | 96573a0 | 2014-10-03 00:01:35 -0300 | [diff] [blame] | 33 | |
| 34 | #include <shared/util.h> |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 35 | |
| 36 | #include "kmod.h" |
| 37 | |
| 38 | struct static_nodes_format { |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 39 | const char *name; |
| 40 | int (*write)(FILE *, char[], char[], char, unsigned int, unsigned int); |
| 41 | const char *description; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 42 | }; |
| 43 | |
| 44 | static const struct static_nodes_format static_nodes_format_human; |
| 45 | static const struct static_nodes_format static_nodes_format_tmpfiles; |
| 46 | static const struct static_nodes_format static_nodes_format_devname; |
| 47 | |
| 48 | static const struct static_nodes_format *static_nodes_formats[] = { |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 49 | &static_nodes_format_human, |
| 50 | &static_nodes_format_tmpfiles, |
| 51 | &static_nodes_format_devname, |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 52 | }; |
| 53 | |
| 54 | static const char cmdopts_s[] = "o:f:h"; |
| 55 | static const struct option cmdopts[] = { |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 56 | { "output", required_argument, 0, 'o'}, |
| 57 | { "format", required_argument, 0, 'f'}, |
| 58 | { "help", no_argument, 0, 'h'}, |
| 59 | { }, |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 60 | }; |
| 61 | |
| 62 | static int write_human(FILE *out, char modname[], char devname[], char type, unsigned int maj, unsigned int min) |
| 63 | { |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 64 | int ret; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 65 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 66 | ret = fprintf(out, |
| 67 | "Module: %s\n" |
| 68 | "\tDevice node: /dev/%s\n" |
| 69 | "\t\tType: %s device\n" |
| 70 | "\t\tMajor: %u\n" |
| 71 | "\t\tMinor: %u\n", |
| 72 | modname, devname, |
| 73 | (type == 'c') ? "character" : "block", maj, min); |
| 74 | if (ret >= 0) |
| 75 | return EXIT_SUCCESS; |
| 76 | else |
| 77 | return EXIT_FAILURE; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 78 | } |
| 79 | |
| 80 | static const struct static_nodes_format static_nodes_format_human = { |
| 81 | .name = "human", |
| 82 | .write = write_human, |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 83 | .description = "(default) a human readable format. Do not parse.", |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 84 | }; |
| 85 | |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 86 | static int write_tmpfiles(FILE *out, char modname[], char devname[], char type, unsigned int maj, unsigned int min) |
| 87 | { |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 88 | const char *dir; |
| 89 | int ret; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 90 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 91 | dir = strrchr(devname, '/'); |
| 92 | if (dir) { |
| 93 | ret = fprintf(out, "d /dev/%.*s 0755 - - -\n", |
| 94 | (int)(dir - devname), devname); |
| 95 | if (ret < 0) |
| 96 | return EXIT_FAILURE; |
| 97 | } |
Tom Gundersen | 4905769 | 2013-04-19 23:53:35 +0200 | [diff] [blame] | 98 | |
Tom Gundersen | 27eceb2 | 2014-10-27 17:55:03 +0100 | [diff] [blame] | 99 | ret = fprintf(out, "%c! /dev/%s 0600 - - - %u:%u\n", |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 100 | type, devname, maj, min); |
| 101 | if (ret < 0) |
| 102 | return EXIT_FAILURE; |
Tom Gundersen | 4905769 | 2013-04-19 23:53:35 +0200 | [diff] [blame] | 103 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 104 | return EXIT_SUCCESS; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 105 | } |
| 106 | |
| 107 | static const struct static_nodes_format static_nodes_format_tmpfiles = { |
| 108 | .name = "tmpfiles", |
| 109 | .write = write_tmpfiles, |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 110 | .description = "the tmpfiles.d(5) format used by systemd-tmpfiles.", |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 111 | }; |
| 112 | |
| 113 | static int write_devname(FILE *out, char modname[], char devname[], char type, unsigned int maj, unsigned int min) |
| 114 | { |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 115 | int ret; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 116 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 117 | ret = fprintf(out, "%s %s %c%u:%u\n", modname, devname, type, maj, min); |
| 118 | if (ret >= 0) |
| 119 | return EXIT_SUCCESS; |
| 120 | else |
| 121 | return EXIT_FAILURE; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 122 | } |
| 123 | |
| 124 | static const struct static_nodes_format static_nodes_format_devname = { |
| 125 | .name = "devname", |
| 126 | .write = write_devname, |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 127 | .description = "the modules.devname format.", |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 128 | }; |
| 129 | |
| 130 | static void help(void) |
| 131 | { |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 132 | size_t i; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 133 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 134 | printf("Usage:\n" |
| 135 | "\t%s static-nodes [options]\n" |
| 136 | "\n" |
| 137 | "kmod static-nodes outputs the static-node information of the currently running kernel.\n" |
| 138 | "\n" |
| 139 | "Options:\n" |
Lucas De Marchi | a5cbde4 | 2013-07-01 23:00:20 -0300 | [diff] [blame] | 140 | "\t-f, --format=FORMAT choose format to use: see \"Formats\"\n" |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 141 | "\t-o, --output=FILE write output to file\n" |
| 142 | "\t-h, --help show this help\n" |
| 143 | "\n" |
| 144 | "Formats:\n", |
| 145 | program_invocation_short_name); |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 146 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 147 | for (i = 0; i < ARRAY_SIZE(static_nodes_formats); i++) { |
| 148 | if (static_nodes_formats[i]->description != NULL) { |
| 149 | printf("\t%-12s %s\n", static_nodes_formats[i]->name, |
| 150 | static_nodes_formats[i]->description); |
| 151 | } |
| 152 | } |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 153 | } |
| 154 | |
| 155 | static int do_static_nodes(int argc, char *argv[]) |
| 156 | { |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 157 | struct utsname kernel; |
Tom Gundersen | 232bf4d | 2013-07-14 15:13:34 +0200 | [diff] [blame] | 158 | char modules[PATH_MAX], buf[4096]; |
Tom Gundersen | ae17710 | 2013-07-14 15:13:33 +0200 | [diff] [blame] | 159 | const char *output = "/dev/stdout"; |
| 160 | FILE *in = NULL, *out = NULL; |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 161 | const struct static_nodes_format *format = &static_nodes_format_human; |
Tom Gundersen | 232bf4d | 2013-07-14 15:13:34 +0200 | [diff] [blame] | 162 | int r, ret = EXIT_SUCCESS; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 163 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 164 | for (;;) { |
| 165 | int c, idx = 0, valid; |
| 166 | size_t i; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 167 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 168 | c = getopt_long(argc, argv, cmdopts_s, cmdopts, &idx); |
| 169 | if (c == -1) { |
| 170 | break; |
| 171 | } |
| 172 | switch (c) { |
| 173 | case 'o': |
Tom Gundersen | ae17710 | 2013-07-14 15:13:33 +0200 | [diff] [blame] | 174 | output = optarg; |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 175 | break; |
| 176 | case 'f': |
| 177 | valid = 0; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 178 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 179 | for (i = 0; i < ARRAY_SIZE(static_nodes_formats); i++) { |
| 180 | if (streq(static_nodes_formats[i]->name, optarg)) { |
| 181 | format = static_nodes_formats[i]; |
| 182 | valid = 1; |
| 183 | } |
| 184 | } |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 185 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 186 | if (!valid) { |
| 187 | fprintf(stderr, "Unknown format: '%s'.\n", |
| 188 | optarg); |
| 189 | help(); |
| 190 | ret = EXIT_FAILURE; |
| 191 | goto finish; |
| 192 | } |
| 193 | break; |
| 194 | case 'h': |
| 195 | help(); |
| 196 | goto finish; |
| 197 | case '?': |
| 198 | ret = EXIT_FAILURE; |
| 199 | goto finish; |
| 200 | default: |
| 201 | fprintf(stderr, "Unexpected commandline option '%c'.\n", |
| 202 | c); |
| 203 | help(); |
| 204 | ret = EXIT_FAILURE; |
| 205 | goto finish; |
| 206 | } |
| 207 | } |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 208 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 209 | if (uname(&kernel) < 0) { |
| 210 | fputs("Error: uname failed!\n", stderr); |
| 211 | ret = EXIT_FAILURE; |
| 212 | goto finish; |
| 213 | } |
Lucas De Marchi | 279b177 | 2013-04-17 00:54:17 -0300 | [diff] [blame] | 214 | |
Tom Gundersen | ae17710 | 2013-07-14 15:13:33 +0200 | [diff] [blame] | 215 | snprintf(modules, sizeof(modules), "/lib/modules/%s/modules.devname", kernel.release); |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 216 | in = fopen(modules, "re"); |
| 217 | if (in == NULL) { |
Tom Gundersen | ae17710 | 2013-07-14 15:13:33 +0200 | [diff] [blame] | 218 | if (errno == ENOENT) { |
| 219 | fprintf(stderr, "Warning: /lib/modules/%s/modules.devname not found - ignoring\n", |
| 220 | kernel.release); |
| 221 | ret = EXIT_SUCCESS; |
| 222 | } else { |
| 223 | fprintf(stderr, "Error: could not open /lib/modules/%s/modules.devname - %m\n", |
| 224 | kernel.release); |
| 225 | ret = EXIT_FAILURE; |
| 226 | } |
| 227 | goto finish; |
| 228 | } |
| 229 | |
Tom Gundersen | 232bf4d | 2013-07-14 15:13:34 +0200 | [diff] [blame] | 230 | r = mkdir_parents(output, 0755); |
| 231 | if (r < 0) { |
| 232 | fprintf(stderr, "Error: could not create parent directory for %s - %m.\n", output); |
| 233 | ret = EXIT_FAILURE; |
| 234 | goto finish; |
| 235 | } |
| 236 | |
Tom Gundersen | ae17710 | 2013-07-14 15:13:33 +0200 | [diff] [blame] | 237 | out = fopen(output, "we"); |
| 238 | if (out == NULL) { |
| 239 | fprintf(stderr, "Error: could not create %s - %m\n", output); |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 240 | ret = EXIT_FAILURE; |
| 241 | goto finish; |
| 242 | } |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 243 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 244 | while (fgets(buf, sizeof(buf), in) != NULL) { |
| 245 | char modname[PATH_MAX]; |
| 246 | char devname[PATH_MAX]; |
| 247 | char type; |
| 248 | unsigned int maj, min; |
| 249 | int matches; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 250 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 251 | if (buf[0] == '#') |
| 252 | continue; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 253 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 254 | matches = sscanf(buf, "%s %s %c%u:%u", modname, devname, |
| 255 | &type, &maj, &min); |
| 256 | if (matches != 5 || (type != 'c' && type != 'b')) { |
| 257 | fprintf(stderr, "Error: invalid devname entry: %s", buf); |
| 258 | ret = EXIT_FAILURE; |
| 259 | continue; |
| 260 | } |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 261 | |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 262 | format->write(out, modname, devname, type, maj, min); |
| 263 | } |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 264 | |
| 265 | finish: |
Lucas De Marchi | 19ac5bd | 2013-04-19 19:08:43 -0300 | [diff] [blame] | 266 | if (in) |
| 267 | fclose(in); |
| 268 | if (out) |
| 269 | fclose(out); |
| 270 | return ret; |
Tom Gundersen | db6f2fc | 2013-04-16 22:39:55 +0200 | [diff] [blame] | 271 | } |
| 272 | |
| 273 | const struct kmod_cmd kmod_cmd_static_nodes = { |
| 274 | .name = "static-nodes", |
| 275 | .cmd = do_static_nodes, |
| 276 | .help = "outputs the static-node information installed with the currently running kernel", |
| 277 | }; |