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