blob: df2e3cfad5cf18583864bb9a743dc5174585ae35 [file] [log] [blame]
Andrew Geissler78b72792022-06-14 06:47:25 -05001/*
2 * SPDX-License-Identifier: GPL-2.0-only
3 */
4
Patrick Williamsc124f4f2015-09-15 14:41:29 -05005#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 */
35static const char *const app_name = "makedevs";
36static const char *const memory_exhausted = "memory exhausted";
37static char default_rootdir[]=".";
38static char *rootdir = default_rootdir;
39static int trace = 0;
40
41struct name_id {
42 char name[MAX_NAME_LEN+1];
43 unsigned long id;
44 struct name_id *next;
45};
46
47static struct name_id *usr_list = NULL;
48static struct name_id *grp_list = NULL;
49
50static 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
57static 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
68static 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
80static 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
90static 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
99static 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
114static 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
127static 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
160static 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
186static 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
222static 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
233static void add_new_directory(char *name, char *path,
234 unsigned long uid, unsigned long gid, unsigned long mode)
235{
236 if (trace)
Brad Bishop64c979e2019-11-04 13:55:29 -0500237 fprintf(stderr, "Directory: %s %s UID: %lu GID %lu MODE: %04lo", path, name, uid, gid, mode);
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500238
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
251static 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) {
Brad Bishop64c979e2019-11-04 13:55:29 -0500258 fprintf(stderr, "Device: %s %s UID: %lu GID: %lu MODE: %04lo MAJOR: %d MINOR: %d",
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500259 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
295static void add_new_file(char *name, char *path, unsigned long uid,
296 unsigned long gid, unsigned long mode)
297{
298 if (trace) {
Brad Bishop64c979e2019-11-04 13:55:29 -0500299 fprintf(stderr, "File: %s %s UID: %lu GID: %lu MODE: %04lo\n",
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500300 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
314static void add_new_fifo(char *name, char *path, unsigned long uid,
315 unsigned long gid, unsigned long mode)
316{
317 if (trace) {
Brad Bishop64c979e2019-11-04 13:55:29 -0500318 printf("Fifo: %s %s UID: %lu GID: %lu MODE: %04lo\n",
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319 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*/
358static 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
Brad Bishop64c979e2019-11-04 13:55:29 -0500367 if (0 > sscanf(line, "%4095s %c %lo %39s %39s %lu %lu %lu %lu %lu", path,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500368 &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
431static 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
465static 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
482static 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
491static 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
502int 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) {
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600517 fputs( helptext , stderr );
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500518 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':
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600532 puts(helptext);
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500533 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:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600560 fputs(helptext,stderr);
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500561 exit(1);
562 }
563 }
564
565 if (argv[optind] != NULL) {
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600566 fputs(helptext,stderr);
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500567 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}