1 /* 2 * SPDX-License-Identifier: GPL-2.0-only 3 */ 4 5 #define _GNU_SOURCE 6 #include <stdio.h> 7 #include <errno.h> 8 #include <string.h> 9 #include <stdarg.h> 10 #include <stdlib.h> 11 #include <ctype.h> 12 #include <fcntl.h> 13 #include <dirent.h> 14 #include <unistd.h> 15 #include <time.h> 16 #include <getopt.h> 17 #include <libgen.h> 18 #include <sys/types.h> 19 #include <sys/stat.h> 20 21 #define MINORBITS 8 22 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) 23 #define MAX_ID_LEN 40 24 #define MAX_NAME_LEN 40 25 #ifndef PATH_MAX 26 #define PATH_MAX 4096 27 #endif 28 #define VERSION "1.0.1" 29 30 /* These are all stolen from busybox's libbb to make 31 * error handling simpler (and since I maintain busybox, 32 * I'm rather partial to these for error handling). 33 * -Erik 34 */ 35 static const char *const app_name = "makedevs"; 36 static const char *const memory_exhausted = "memory exhausted"; 37 static char default_rootdir[]="."; 38 static char *rootdir = default_rootdir; 39 static int trace = 0; 40 41 struct name_id { 42 char name[MAX_NAME_LEN+1]; 43 unsigned long id; 44 struct name_id *next; 45 }; 46 47 static struct name_id *usr_list = NULL; 48 static struct name_id *grp_list = NULL; 49 50 static void verror_msg(const char *s, va_list p) 51 { 52 fflush(stdout); 53 fprintf(stderr, "%s: ", app_name); 54 vfprintf(stderr, s, p); 55 } 56 57 static void error_msg_and_die(const char *s, ...) 58 { 59 va_list p; 60 61 va_start(p, s); 62 verror_msg(s, p); 63 va_end(p); 64 putc('\n', stderr); 65 exit(EXIT_FAILURE); 66 } 67 68 static void vperror_msg(const char *s, va_list p) 69 { 70 int err = errno; 71 72 if (s == 0) 73 s = ""; 74 verror_msg(s, p); 75 if (*s) 76 s = ": "; 77 fprintf(stderr, "%s%s\n", s, strerror(err)); 78 } 79 80 static void perror_msg_and_die(const char *s, ...) 81 { 82 va_list p; 83 84 va_start(p, s); 85 vperror_msg(s, p); 86 va_end(p); 87 exit(EXIT_FAILURE); 88 } 89 90 static FILE *xfopen(const char *path, const char *mode) 91 { 92 FILE *fp; 93 94 if ((fp = fopen(path, mode)) == NULL) 95 perror_msg_and_die("%s", path); 96 return fp; 97 } 98 99 static char *xstrdup(const char *s) 100 { 101 char *t; 102 103 if (s == NULL) 104 return NULL; 105 106 t = strdup(s); 107 108 if (t == NULL) 109 error_msg_and_die(memory_exhausted); 110 111 return t; 112 } 113 114 static struct name_id* alloc_node(void) 115 { 116 struct name_id *node; 117 node = (struct name_id*)malloc(sizeof(struct name_id)); 118 if (node == NULL) { 119 error_msg_and_die(memory_exhausted); 120 } 121 memset((void *)node->name, 0, MAX_NAME_LEN+1); 122 node->id = 0xffffffff; 123 node->next = NULL; 124 return node; 125 } 126 127 static struct name_id* parse_line(char *line) 128 { 129 char *p; 130 int i; 131 char id_buf[MAX_ID_LEN+1]; 132 struct name_id *node; 133 node = alloc_node(); 134 p = line; 135 i = 0; 136 // Get name field 137 while (*p != ':') { 138 if (i > MAX_NAME_LEN) 139 error_msg_and_die("Name field too long"); 140 node->name[i++] = *p++; 141 } 142 node->name[i] = '\0'; 143 p++; 144 // Skip the second field 145 while (*p != ':') 146 p++; 147 p++; 148 // Get id field 149 i = 0; 150 while (*p != ':') { 151 if (i > MAX_ID_LEN) 152 error_msg_and_die("ID filed too long"); 153 id_buf[i++] = *p++; 154 } 155 id_buf[i] = '\0'; 156 node->id = atol(id_buf); 157 return node; 158 } 159 160 static void get_list_from_file(FILE *file, struct name_id **plist) 161 { 162 char *line; 163 int len = 0; 164 size_t length = 256; 165 struct name_id *node, *cur; 166 167 if((line = (char *)malloc(length)) == NULL) { 168 error_msg_and_die(memory_exhausted); 169 } 170 171 while ((len = getline(&line, &length, file)) != -1) { 172 node = parse_line(line); 173 if (*plist == NULL) { 174 *plist = node; 175 cur = *plist; 176 } else { 177 cur->next = node; 178 cur = cur->next; 179 } 180 } 181 182 if (line) 183 free(line); 184 } 185 186 static unsigned long convert2guid(char *id_buf, struct name_id *search_list) 187 { 188 char *p; 189 int isnum; 190 struct name_id *node; 191 p = id_buf; 192 isnum = 1; 193 while (*p != '\0') { 194 if (!isdigit(*p)) { 195 isnum = 0; 196 break; 197 } 198 p++; 199 } 200 if (isnum) { 201 // Check for bad user/group name 202 node = search_list; 203 while (node != NULL) { 204 if (!strncmp(node->name, id_buf, strlen(id_buf))) { 205 fprintf(stderr, "WARNING: Bad user/group name %s detected\n", id_buf); 206 break; 207 } 208 node = node->next; 209 } 210 return (unsigned long)atol(id_buf); 211 } else { 212 node = search_list; 213 while (node != NULL) { 214 if (!strncmp(node->name, id_buf, strlen(id_buf))) 215 return node->id; 216 node = node->next; 217 } 218 error_msg_and_die("No entry for %s in search list", id_buf); 219 } 220 } 221 222 static void free_list(struct name_id *list) 223 { 224 struct name_id *cur; 225 cur = list; 226 while (cur != NULL) { 227 list = cur; 228 cur = cur->next; 229 free(list); 230 } 231 } 232 233 static void add_new_directory(char *name, char *path, 234 unsigned long uid, unsigned long gid, unsigned long mode) 235 { 236 if (trace) 237 fprintf(stderr, "Directory: %s %s UID: %lu GID %lu MODE: %04lo", path, name, uid, gid, mode); 238 239 if (mkdir(path, mode) < 0) { 240 if (EEXIST == errno) { 241 /* Unconditionally apply the mode setting to the existing directory. 242 * XXX should output something when trace */ 243 chmod(path, mode & ~S_IFMT); 244 } 245 } 246 if (trace) 247 putc('\n', stderr); 248 chown(path, uid, gid); 249 } 250 251 static void add_new_device(char *name, char *path, unsigned long uid, 252 unsigned long gid, unsigned long mode, dev_t rdev) 253 { 254 int status; 255 struct stat sb; 256 257 if (trace) { 258 fprintf(stderr, "Device: %s %s UID: %lu GID: %lu MODE: %04lo MAJOR: %d MINOR: %d", 259 path, name, uid, gid, mode, (short)(rdev >> 8), (short)(rdev & 0xff)); 260 } 261 262 memset(&sb, 0, sizeof(struct stat)); 263 status = lstat(path, &sb); 264 if (status >= 0) { 265 /* It is ok for some types of files to not exit on disk (such as 266 * device nodes), but if they _do_ exist, the file type bits had 267 * better match those of the actual file or strange things will happen... */ 268 if ((mode & S_IFMT) != (sb.st_mode & S_IFMT)) { 269 if (trace) 270 putc('\n', stderr); 271 error_msg_and_die("%s: existing file (04%o) type does not match specified file type (04%lo)!", 272 path, (sb.st_mode & S_IFMT), (mode & S_IFMT)); 273 } 274 if (mode != sb.st_mode) { 275 if (trace) 276 fprintf(stderr, " -- applying new mode 04%lo (old was 04%o)\n", mode & ~S_IFMT, sb.st_mode & ~S_IFMT); 277 /* Apply the mode setting to the existing device node */ 278 chmod(path, mode & ~S_IFMT); 279 } 280 else { 281 if (trace) 282 fprintf(stderr, " -- extraneous entry in table\n", path); 283 } 284 } 285 else { 286 mknod(path, mode, rdev); 287 if (trace) 288 putc('\n', stderr); 289 290 } 291 292 chown(path, uid, gid); 293 } 294 295 static void add_new_file(char *name, char *path, unsigned long uid, 296 unsigned long gid, unsigned long mode) 297 { 298 if (trace) { 299 fprintf(stderr, "File: %s %s UID: %lu GID: %lu MODE: %04lo\n", 300 path, name, gid, uid, mode); 301 } 302 303 int fd = open(path,O_CREAT | O_WRONLY, mode); 304 if (fd < 0) { 305 error_msg_and_die("%s: file can not be created!", path); 306 } else { 307 close(fd); 308 } 309 chmod(path, mode); 310 chown(path, uid, gid); 311 } 312 313 314 static void add_new_fifo(char *name, char *path, unsigned long uid, 315 unsigned long gid, unsigned long mode) 316 { 317 if (trace) { 318 printf("Fifo: %s %s UID: %lu GID: %lu MODE: %04lo\n", 319 path, name, gid, uid, mode); 320 } 321 322 int status; 323 struct stat sb; 324 325 memset(&sb, 0, sizeof(struct stat)); 326 status = stat(path, &sb); 327 328 329 /* Update the mode if we exist and are a fifo already */ 330 if (status >= 0 && S_ISFIFO(sb.st_mode)) { 331 chmod(path, mode); 332 } else { 333 if (mknod(path, mode, 0)) 334 error_msg_and_die("%s: file can not be created with mknod!", path); 335 } 336 chown(path, uid, gid); 337 } 338 339 340 /* device table entries take the form of: 341 <path> <type> <mode> <usr> <grp> <major> <minor> <start> <inc> <count> 342 /dev/mem c 640 0 0 1 1 0 0 - 343 /dev/zero c 644 root root 1 5 - - - 344 345 type can be one of: 346 f A regular file 347 d Directory 348 c Character special device file 349 b Block special device file 350 p Fifo (named pipe) 351 352 I don't bother with symlinks (permissions are irrelevant), hard 353 links (special cases of regular files), or sockets (why bother). 354 355 Regular files must exist in the target root directory. If a char, 356 block, fifo, or directory does not exist, it will be created. 357 */ 358 static int interpret_table_entry(char *line) 359 { 360 char *name; 361 char usr_buf[MAX_ID_LEN]; 362 char grp_buf[MAX_ID_LEN]; 363 char path[4096], type; 364 unsigned long mode = 0755, uid = 0, gid = 0, major = 0, minor = 0; 365 unsigned long start = 0, increment = 1, count = 0; 366 367 if (0 > sscanf(line, "%4095s %c %lo %39s %39s %lu %lu %lu %lu %lu", path, 368 &type, &mode, usr_buf, grp_buf, &major, &minor, &start, 369 &increment, &count)) 370 { 371 fprintf(stderr, "%s: sscanf returned < 0 for line '%s'\n", app_name, line); 372 return 1; 373 } 374 375 uid = convert2guid(usr_buf, usr_list); 376 gid = convert2guid(grp_buf, grp_list); 377 378 if (strncmp(path, "/", 1)) { 379 error_msg_and_die("Device table entries require absolute paths"); 380 } 381 name = xstrdup(path + 1); 382 /* prefix path with rootdir */ 383 sprintf(path, "%s/%s", rootdir, name); 384 385 /* XXX Why is name passed into all of the add_new_*() routines? */ 386 switch (type) { 387 case 'd': 388 mode |= S_IFDIR; 389 add_new_directory(name, path, uid, gid, mode); 390 break; 391 case 'f': 392 mode |= S_IFREG; 393 add_new_file(name, path, uid, gid, mode); 394 break; 395 case 'p': 396 mode |= S_IFIFO; 397 add_new_fifo(name, path, uid, gid, mode); 398 break; 399 case 'c': 400 case 'b': 401 mode |= (type == 'c') ? S_IFCHR : S_IFBLK; 402 if (count > 0) { 403 int i; 404 dev_t rdev; 405 char buf[80]; 406 407 for (i = start; i < start + count; i++) { 408 sprintf(buf, "%s%d", name, i); 409 sprintf(path, "%s/%s%d", rootdir, name, i); 410 /* FIXME: MKDEV uses illicit insider knowledge of kernel 411 * major/minor representation... */ 412 rdev = MKDEV(major, minor + (i - start) * increment); 413 sprintf(path, "%s/%s\0", rootdir, buf); 414 add_new_device(buf, path, uid, gid, mode, rdev); 415 } 416 } else { 417 /* FIXME: MKDEV uses illicit insider knowledge of kernel 418 * major/minor representation... */ 419 dev_t rdev = MKDEV(major, minor); 420 add_new_device(name, path, uid, gid, mode, rdev); 421 } 422 break; 423 default: 424 error_msg_and_die("Unsupported file type"); 425 } 426 if (name) free(name); 427 return 0; 428 } 429 430 431 static void parse_device_table(FILE * file) 432 { 433 char *line; 434 size_t length = 256; 435 int len = 0; 436 437 if((line = (char *)malloc(length)) == NULL) { 438 error_msg_and_die(memory_exhausted); 439 } 440 /* Looks ok so far. The general plan now is to read in one 441 * line at a time, trim off leading and trailing whitespace, 442 * check for leading comment delimiters ('#') or a blank line, 443 * then try and parse the line as a device table entry. If we fail 444 * to parse things, try and help the poor fool to fix their 445 * device table with a useful error msg... */ 446 447 while ((len = getline(&line, &length, file)) != -1) { 448 /* First trim off any whitespace */ 449 450 /* trim trailing whitespace */ 451 while (len > 0 && isspace(line[len - 1])) 452 line[--len] = '\0'; 453 454 /* trim leading whitespace */ 455 memmove(line, &line[strspn(line, " \n\r\t\v")], len + 1); 456 457 /* If this is NOT a comment or an empty line, try to interpret it */ 458 if (*line != '#' && *line != '\0') interpret_table_entry(line); 459 } 460 461 if (line) 462 free(line); 463 } 464 465 static int parse_devtable(FILE * devtable) 466 { 467 struct stat sb; 468 469 if (lstat(rootdir, &sb)) { 470 perror_msg_and_die("%s", rootdir); 471 } 472 if (chdir(rootdir)) 473 perror_msg_and_die("%s", rootdir); 474 475 if (devtable) 476 parse_device_table(devtable); 477 478 return 0; 479 } 480 481 482 static struct option long_options[] = { 483 {"root", 1, NULL, 'r'}, 484 {"help", 0, NULL, 'h'}, 485 {"trace", 0, NULL, 't'}, 486 {"version", 0, NULL, 'v'}, 487 {"devtable", 1, NULL, 'D'}, 488 {NULL, 0, NULL, 0} 489 }; 490 491 static char *helptext = 492 "Usage: makedevs [OPTIONS]\n" 493 "Build entries based upon device_table.txt\n\n" 494 "Options:\n" 495 " -r, -d, --root=DIR Build filesystem from directory DIR (default: cwd)\n" 496 " -D, --devtable=FILE Use the named FILE as a device table file\n" 497 " -h, --help Display this help text\n" 498 " -t, --trace Be verbose\n" 499 " -v, --version Display version information\n\n"; 500 501 502 int main(int argc, char **argv) 503 { 504 int c, opt; 505 extern char *optarg; 506 struct stat statbuf; 507 char passwd_path[PATH_MAX]; 508 char group_path[PATH_MAX]; 509 FILE *passwd_file = NULL; 510 FILE *group_file = NULL; 511 FILE *devtable = NULL; 512 DIR *dir = NULL; 513 514 umask (0); 515 516 if (argc==1) { 517 fputs( helptext , stderr ); 518 exit(1); 519 } 520 521 while ((opt = getopt_long(argc, argv, "D:d:r:htv", 522 long_options, &c)) >= 0) { 523 switch (opt) { 524 case 'D': 525 devtable = xfopen(optarg, "r"); 526 if (fstat(fileno(devtable), &statbuf) < 0) 527 perror_msg_and_die(optarg); 528 if (statbuf.st_size < 10) 529 error_msg_and_die("%s: not a proper device table file", optarg); 530 break; 531 case 'h': 532 puts(helptext); 533 exit(0); 534 case 'r': 535 case 'd': /* for compatibility with mkfs.jffs, genext2fs, etc... */ 536 if (rootdir != default_rootdir) { 537 error_msg_and_die("root directory specified more than once"); 538 } 539 if ((dir = opendir(optarg)) == NULL) { 540 perror_msg_and_die(optarg); 541 } else { 542 closedir(dir); 543 } 544 /* If "/" is specified, use "" because rootdir is always prepended to a 545 * string that starts with "/" */ 546 if (0 == strcmp(optarg, "/")) 547 rootdir = xstrdup(""); 548 else 549 rootdir = xstrdup(optarg); 550 break; 551 552 case 't': 553 trace = 1; 554 break; 555 556 case 'v': 557 printf("%s: %s\n", app_name, VERSION); 558 exit(0); 559 default: 560 fputs(helptext,stderr); 561 exit(1); 562 } 563 } 564 565 if (argv[optind] != NULL) { 566 fputs(helptext,stderr); 567 exit(1); 568 } 569 570 // Get name-id mapping 571 sprintf(passwd_path, "%s/etc/passwd", rootdir); 572 sprintf(group_path, "%s/etc/group", rootdir); 573 if ((passwd_file = fopen(passwd_path, "r")) != NULL) { 574 get_list_from_file(passwd_file, &usr_list); 575 fclose(passwd_file); 576 } 577 if ((group_file = fopen(group_path, "r")) != NULL) { 578 get_list_from_file(group_file, &grp_list); 579 fclose(group_file); 580 } 581 582 // Parse devtable 583 if(devtable) { 584 parse_devtable(devtable); 585 fclose(devtable); 586 } 587 588 // Free list 589 free_list(usr_list); 590 free_list(grp_list); 591 592 return 0; 593 } 594