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 
verror_msg(const char * s,va_list p)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 
error_msg_and_die(const char * s,...)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 
vperror_msg(const char * s,va_list p)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 
perror_msg_and_die(const char * s,...)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 
xfopen(const char * path,const char * mode)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 
xstrdup(const char * s)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 
alloc_node(void)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 
parse_line(char * line)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 
get_list_from_file(FILE * file,struct name_id ** plist)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 
convert2guid(char * id_buf,struct name_id * search_list)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 
free_list(struct name_id * list)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 
add_new_directory(char * name,char * path,unsigned long uid,unsigned long gid,unsigned long mode)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 
add_new_device(char * name,char * path,unsigned long uid,unsigned long gid,unsigned long mode,dev_t rdev)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 
add_new_file(char * name,char * path,unsigned long uid,unsigned long gid,unsigned long mode)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 
add_new_fifo(char * name,char * path,unsigned long uid,unsigned long gid,unsigned long mode)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 */
interpret_table_entry(char * line)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 
parse_device_table(FILE * file)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 
parse_devtable(FILE * devtable)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 
main(int argc,char ** argv)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