xref: /minix/minix/usr.bin/toproto/toproto.c (revision 7f5f010b)
1 #if HAVE_NBTOOL_CONFIG_H
2 #include "nbtool_config.h"
3 #else
4 #include <sys/cdefs.h>
5 #endif
6 
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <errno.h>
11 #include <assert.h>
12 
13 #include <getopt.h>
14 #include <unistd.h>
15 
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <fcntl.h>
19 
20 #include "pack_dev.h"
21 
22 #define MAX_ENTRIES 100000
23 #define MAX_LINE_SIZE 0xfff
24 
25 
26 /*
27  * Tool to convert the netbsd METALOG into a proto file usable by mkfs.mfs
28  *
29  * todo:
30  * Possibly use netbsd usr.sbin/makefs to create mfs file systems.
31  */
32 
33 enum entry_type
34 { ENTRY_DIR, ENTRY_FILE, ENTRY_LINK, ENTRY_BLOCK, ENTRY_CHAR };
35 
36 
37 struct entry
38 {
39 	char *path;
40 	char *filename;		/* point to last component in the path */
41 	enum entry_type type;	/* entry type */
42 	int mode;		/* unix mode e.g. 0755 */
43 	char *uid;
44 	char *gid;
45 	char *time;		/* time 1365836670.000000000 */
46 	char *size;
47 	char *link;
48 	/* Can't use devmajor_t/devminor_t on linux systems :( */
49 	int32_t dev_major;
50 	int32_t dev_minor;
51 
52 	/* just internal variables used to create a tree */
53 	int depth;
54 	struct entry *parent;
55 
56 };
57 
58 static struct entry entries[MAX_ENTRIES];
59 static int entry_total_count;
60 
61 static int
62 convert_to_entry(char *line, struct entry *entry)
63 {
64 	/* convert a input line from sanitized input into an entry */
65 	char *saveptr;
66 	char *key, *value;
67 
68 	saveptr = NULL;
69 
70 	/* we need to have a terminated string */
71 	assert(strnlen(line, MAX_LINE_SIZE - 1) != MAX_LINE_SIZE - 1);
72 
73 	/* skip comment lines */
74 	if (*line == '#')
75 		return 1;
76 
77 	line = strtok_r(line, " ", &saveptr);
78 
79 	/* skip empty lines */
80 	if (!line) {
81 		return 1;
82 	}
83 
84 	memset(entry, 0, sizeof(struct entry));
85 
86 	/* the first entry is the path name */
87 	entry->path = strndup(line, MAX_LINE_SIZE);
88 
89 	/* the next entries are key,value pairs */
90 	while ((line = strtok_r(NULL, " ", &saveptr)) != NULL) {
91 		key = value = NULL;
92 		char *p;
93 		if (strstr(line, "=") == NULL) {
94 			fprintf(stderr, "expected key/value pair in %s\n",
95 			    line);
96 			free(entry->path);
97 			return 1;
98 		}
99 		p = NULL;
100 		key = strtok_r(line, "=", &p);
101 		value = strtok_r(NULL, "=", &p);
102 		if (value) {
103 			if (strncmp(key, "type", 5) == 0) {
104 				if (strncmp(value, "dir", 4) == 0) {
105 					entry->type = ENTRY_DIR;
106 				} else if (strncmp(value, "file", 5) == 0) {
107 					entry->type = ENTRY_FILE;
108 				} else if (strncmp(value, "link", 5) == 0) {
109 					entry->type = ENTRY_LINK;
110 				} else if (strncmp(value, "block", 6) == 0) {
111 					entry->type = ENTRY_BLOCK;
112 				} else if (strncmp(value, "char", 5) == 0) {
113 					entry->type = ENTRY_CHAR;
114 				} else {
115 					fprintf(stderr,
116 					    "\tunknown type %s -> '%s'\n", key,
117 					    value);
118 				}
119 			} else if (strncmp(key, "mode", 5) == 0) {
120 				sscanf(value,"%o",&entry->mode);
121 			} else if (strncmp(key, "uid", 4) == 0) {
122 				entry->uid = strndup(value, MAX_LINE_SIZE);
123 			} else if (strncmp(key, "gid", 4) == 0) {
124 				entry->gid = strndup(value, MAX_LINE_SIZE);
125 			} else if (strncmp(key, "time", 5) == 0) {
126 				entry->time = strndup(value, MAX_LINE_SIZE);
127 			} else if (strncmp(key, "size", 5) == 0) {
128 				entry->size = strndup(value, MAX_LINE_SIZE);
129 			} else if (strncmp(key, "link", 5) == 0) {
130 				entry->link = strndup(value, MAX_LINE_SIZE);
131 			} else if (strncmp(key, "device", 7) == 0) {
132 				long int dev_id;
133 				dev_id = strtoul(value, NULL, 16);
134 				entry->dev_major = major_netbsd(dev_id);
135 				entry->dev_minor = minor_netbsd(dev_id);
136 			} else {
137 				fprintf(stderr,
138 				    "\tunknown attribute %s -> %s\n", key,
139 				    value);
140 			}
141 		}
142 	}
143 	return 0;
144 }
145 
146 static int
147 iterate_over_input(int fh_in, void (*callback) (char *line))
148 {
149 	char buf[MAX_LINE_SIZE];
150 	int r_size, err;
151 	int line_size;
152 	int buf_end;
153 	int line_nr;
154 	char *p;
155 	memset(buf, 0, MAX_LINE_SIZE);
156 
157 	r_size = 0;
158 	buf_end = 0;
159 	line_nr = 0;
160 
161 	while (1 == 1) {
162 		/* fill buffer taking into account there can already be
163 		 * content at the start */
164 		r_size = read(fh_in, &buf[buf_end], MAX_LINE_SIZE - buf_end);
165 		if (r_size == -1) {
166 			err = errno;
167 			fprintf(stderr, "failed reading input:%s\n",
168 			    strerror(err));
169 			return 1;
170 		}
171 		/* checking for read size of 0 is not enough as the buffer
172 		 * still can contain content */
173 		buf_end = buf_end + r_size;
174 
175 		/* is there data we need to process ? */
176 		if (buf_end == 0) {
177 			return 0;	/* normal exit is here */
178 		}
179 
180 		/* find end of line or eof. start a the start of the buffer */
181 		p = buf;
182 		while (p < buf + buf_end) {
183 			if (*p == '\n' || *p == '\0') {
184 				/* replace either by a null terminator */
185 				line_nr++;
186 				*p = '\0';
187 				break;
188 			}
189 			p++;
190 		}
191 
192 		/* If we are at the end of the buffer we did not find a
193 		 * terminator */
194 		if (p == buf + buf_end) {
195 			fprintf(stderr,
196 			    "Line(%d) does not fit the buffer %d\n", line_nr,
197 			    MAX_LINE_SIZE);
198 			return 1;
199 		}
200 
201 		line_size = p - buf;	/* size of the line we currently are
202 					 * reading */
203 
204 		/* here we have a valid line */
205 		callback(buf);
206 
207 		/* copy the remaining data over to the start */
208 		memmove(buf, p + 1, MAX_LINE_SIZE - line_size);
209 		buf_end -= (line_size + 1);
210 	}
211 	return 0;
212 }
213 
214 static void
215 parse_line_cb(char *line)
216 {
217 	if (convert_to_entry(line, &entries[entry_total_count]) == 0) {
218 		entry_total_count++;
219 		assert(entry_total_count < MAX_ENTRIES);
220 	} else {
221 		memset(&entries[entry_total_count], 0, sizeof(struct entry));
222 	}
223 }
224 
225 static int
226 create_entries(int handle)
227 {
228 	int c;
229 	char *p;
230 	struct entry *entry;
231 
232 	char tmppath[MAX_LINE_SIZE];
233 	int i;
234 
235 	if (iterate_over_input(handle, parse_line_cb)) {
236 		return 1;
237 	}
238 
239 	/* calculate depth for each entry */
240 	for (c = 0; c < entry_total_count; c++) {
241 		p = entries[c].path;
242 		while (*p != 0) {
243 			if (*p == '/') {
244 				entries[c].depth++;
245 			}
246 			p++;
247 		}
248 	}
249 
250 	/* find parent entry and set the filename */
251 	for (c = 0; c < entry_total_count; c++) {
252 		entry = &entries[c];
253 		if (entry->depth > 0) {
254 			/* calculate path */
255 			/* find last "/" element and "null" it */
256 			strncpy(tmppath, entry->path, MAX_LINE_SIZE - 1);
257 			i = strlen(tmppath);
258 			while (i > 0) {
259 				if (tmppath[i] == '/') {
260 					entry->filename = &entry->path[i + 1];
261 					tmppath[i] = '\0';
262 					break;
263 				}
264 				i--;
265 			}
266 			if (i == 0) {
267 				fprintf
268 				    (stderr,
269 				    "error while searching for parent path of %s\n",
270 				    entry->path);
271 				return 1;
272 			}
273 
274 			/* now compare with the other entries */
275 			for (i = 0; i < entry_total_count; i++) {
276 				if (strncmp(entries[i].path, tmppath,
277 					MAX_LINE_SIZE) == 0) {
278 					/* found entry */
279 					entry->parent = &entries[i];
280 					break;
281 				}
282 			}
283 			if (entry->parent == NULL) {
284 				fprintf(stderr,
285 				    "Failed to find parent directory of %s\n",
286 				    entry->path);
287 				return 1;
288 			}
289 			assert(entry->parent->type == ENTRY_DIR);
290 		} else {
291 			/* same in this case */
292 			entry->filename = entry->path;
293 		}
294 	}
295 
296 	return 0;
297 }
298 
299 static char * parse_mode(int mode){
300 	/* Convert a 4 digit octal number int a proto  entry as described in
301    the mkfs.mfs man page e.g. [suid-char][guid-char]0777 mode */
302 
303 	static char m[6];
304 	memset(m,0,6);
305 	char suid,guid;
306 	suid = (mode & 04000)?'u':'-';
307 	guid = (mode & 02000)?'g':'-';
308 	snprintf(m,6,"%c%c%3o",suid,guid,mode & 0777);
309 	return m;
310 }
311 
312 static char *parse_filename(char *path)
313 {
314 	/* Skipping the first . in the path */
315 	return &path[1];
316 }
317 
318 static int
319 dump_entry(FILE * out, int mindex, const char *base_dir)
320 {
321 
322 	int space;
323 	int i;
324 	struct entry *entry = &entries[mindex];
325 
326 	/* Ensure uid & gid are set to something meaningful. */
327 	if (entry->uid == NULL) {
328 		entry->uid = __UNCONST("0");
329 	}
330 	if (entry->gid == NULL) {
331 		entry->gid = __UNCONST("0");
332 	}
333 
334 	/* Indent the line */
335 	for (space = 0; space < entries[mindex].depth; space++) {
336 		fprintf(out, " ");
337 	}
338 	if (entry->type == ENTRY_DIR) {
339 		if (entries[mindex].depth > 0) {
340 			fprintf(out, "%s ", entry->filename);
341 		}
342 		fprintf(out, "d%s", parse_mode(entry->mode));
343 		fprintf(out, " %s", entry->uid);
344 		fprintf(out, " %s", entry->gid);
345 		fprintf(out, "\n");
346 		for (i = 0; i < entry_total_count; i++) {
347 			if (entries[i].parent == entry) {
348 				dump_entry(out, i, base_dir);
349 			}
350 		}
351 		for (space = 0; space < entries[mindex].depth; space++) {
352 			fprintf(out, " ");
353 		}
354 		fprintf(out, "$\n");
355 	} else if (entry->type == ENTRY_FILE) {
356 		fprintf(out, "%s -%s %s %s %s%s\n", entry->filename,
357 			parse_mode(entry->mode), entry->uid, entry->gid,
358 			base_dir, parse_filename(entry->path));
359 	} else if (entry->type == ENTRY_LINK) {
360 		fprintf(out, "%s s%s %s %s %s\n", entry->filename,
361 			parse_mode(entry->mode), entry->uid, entry->gid,
362 			entry->link);
363 	} else if (entry->type == ENTRY_CHAR) {
364 		fprintf(out, "%s c%s %s %s %d %d\n", entry->filename,
365 			parse_mode(entry->mode), entry->uid, entry->gid,
366 			entry->dev_major, entry->dev_minor);
367 	} else if (entry->type == ENTRY_BLOCK) {
368 		fprintf(out, "%s b%s %s %s %d %d\n", entry->filename,
369 			parse_mode(entry->mode), entry->uid, entry->gid,
370 			entry->dev_major, entry->dev_minor);
371 	} else {
372 		/* Unknown line type.  */
373 		fprintf(out, "#");
374 		fprintf(out, "%i %s\n", entry->type, entry->path);
375 		exit(1);
376 		return 1;
377 	}
378 	return 0;
379 }
380 
381 static int
382 dump_proto(FILE * out, const char *base_dir)
383 {
384 	int i;
385 	fprintf(out, "boot\n0 0");
386 	for (i = 0; i < entry_total_count; i++) {
387 		if (entries[i].depth == 0) {
388 			fprintf(out, "\n");
389 			dump_entry(out, i, base_dir);
390 		}
391 	}
392 	return 0;
393 }
394 
395 static void
396 print_usage(void)
397 {
398 	printf("Usage: toproto [OPTION]...\n");
399 	printf
400 	    ("Convert a netbsd METALOG file into a proto file for mkfs.mfs.\n");
401 	printf("\n");
402 	printf("  -i input METALOG\n");
403 	printf("  -b base_path\n");
404 	printf("  -o output proto\n");
405 	printf("  -h show this this help and exit\n");
406 }
407 
408 int
409 main(int argc, char **argv)
410 {
411 	int ch, fh_in;
412 	FILE *out;
413 	const char *base_path;
414 	char *input_file, *output_file;
415 
416 	input_file = NULL;
417 	output_file = NULL;
418 	base_path = ".";
419 	fh_in = STDIN_FILENO;
420 	out = stdout;
421 
422 	while ((ch = getopt(argc, argv, "i:b:o:h")) != -1) {
423 		switch (ch) {
424 		case 'i':
425 			input_file = optarg;
426 			break;
427 		case 'b':
428 			base_path = optarg;
429 			break;
430 		case 'o':
431 			output_file = optarg;
432 			break;
433 		case 'h':
434 			print_usage();
435 			exit(0);
436 			break;
437 		default:
438 			print_usage();
439 			exit(1);
440 		}
441 	}
442 	argc -= optind;
443 	argv += optind;
444 
445 	if (input_file) {
446 		fh_in = open(input_file, O_RDONLY);
447 		if (fh_in == -1) {
448 			fprintf(stderr, "Failed to open input file (%s):%s\n",
449 			    input_file, strerror(errno));
450 			exit(1);
451 		}
452 	}
453 	if (output_file) {
454 		out = fopen(output_file, "w+");
455 		if (!out) {
456 			fprintf(stderr, "Failed to open input file (%s):%s\n",
457 			    input_file, strerror(errno));
458 			exit(1);
459 		}
460 	}
461 
462 	if (create_entries(fh_in)) {
463 		fprintf(stderr, "Failed to create entries\n");
464 		exit(1);
465 	}
466 	if (input_file)
467 		close(fh_in);
468 
469 	if (dump_proto(out, base_path)) {
470 		fprintf(stderr, "Failed to create entries\n");
471 		exit(1);
472 	}
473 
474 	if (output_file)
475 		fclose(out);
476 	return 0;
477 }
478