1 /*
2  * Create a squashfs filesystem.  This is a highly compressed read only
3  * filesystem.
4  *
5  * Copyright (c) 2009, 2010, 2012, 2014
6  * Phillip Lougher <phillip@squashfs.org.uk>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2,
11  * or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21  *
22  * pseudo.c
23  */
24 
25 #include <pwd.h>
26 #include <grp.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <fcntl.h>
30 #include <errno.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <sys/types.h>
34 #include <sys/wait.h>
35 #include <ctype.h>
36 
37 #include "pseudo.h"
38 #include "error.h"
39 #include "progressbar.h"
40 
41 #define TRUE 1
42 #define FALSE 0
43 
44 extern int read_file(char *filename, char *type, int (parse_line)(char *));
45 
46 struct pseudo_dev **pseudo_file = NULL;
47 struct pseudo *pseudo = NULL;
48 int pseudo_count = 0;
49 
get_component(char * target,char ** targname)50 static char *get_component(char *target, char **targname)
51 {
52 	char *start;
53 
54 	while(*target == '/')
55 		target ++;
56 
57 	start = target;
58 	while(*target != '/' && *target != '\0')
59 		target ++;
60 
61 	*targname = strndup(start, target - start);
62 
63 	while(*target == '/')
64 		target ++;
65 
66 	return target;
67 }
68 
69 
70 /*
71  * Add pseudo device target to the set of pseudo devices.  Pseudo_dev
72  * describes the pseudo device attributes.
73  */
add_pseudo(struct pseudo * pseudo,struct pseudo_dev * pseudo_dev,char * target,char * alltarget)74 struct pseudo *add_pseudo(struct pseudo *pseudo, struct pseudo_dev *pseudo_dev,
75 	char *target, char *alltarget)
76 {
77 	char *targname;
78 	int i;
79 
80 	target = get_component(target, &targname);
81 
82 	if(pseudo == NULL) {
83 		pseudo = malloc(sizeof(struct pseudo));
84 		if(pseudo == NULL)
85 			MEM_ERROR();
86 
87 		pseudo->names = 0;
88 		pseudo->count = 0;
89 		pseudo->name = NULL;
90 	}
91 
92 	for(i = 0; i < pseudo->names; i++)
93 		if(strcmp(pseudo->name[i].name, targname) == 0)
94 			break;
95 
96 	if(i == pseudo->names) {
97 		/* allocate new name entry */
98 		pseudo->names ++;
99 		pseudo->name = realloc(pseudo->name, (i + 1) *
100 			sizeof(struct pseudo_entry));
101 		if(pseudo->name == NULL)
102 			MEM_ERROR();
103 		pseudo->name[i].name = targname;
104 
105 		if(target[0] == '\0') {
106 			/* at leaf pathname component */
107 			pseudo->name[i].pseudo = NULL;
108 			pseudo->name[i].pathname = strdup(alltarget);
109 			pseudo->name[i].dev = pseudo_dev;
110 		} else {
111 			/* recurse adding child components */
112 			pseudo->name[i].dev = NULL;
113 			pseudo->name[i].pseudo = add_pseudo(NULL, pseudo_dev,
114 				target, alltarget);
115 		}
116 	} else {
117 		/* existing matching entry */
118 		free(targname);
119 
120 		if(pseudo->name[i].pseudo == NULL) {
121 			/* No sub-directory which means this is the leaf
122 			 * component of a pre-existing pseudo file.
123 			 */
124 			if(target[0] != '\0') {
125 				/*
126 				 * entry must exist as either a 'd' type or
127 				 * 'm' type pseudo file
128 				 */
129 				if(pseudo->name[i].dev->type == 'd' ||
130 					pseudo->name[i].dev->type == 'm')
131 					/* recurse adding child components */
132 					pseudo->name[i].pseudo =
133 						add_pseudo(NULL, pseudo_dev,
134 						target, alltarget);
135 				else {
136 					ERROR_START("%s already exists as a "
137 						"non directory.",
138 						pseudo->name[i].name);
139 					ERROR_EXIT(".  Ignoring %s!\n",
140 						alltarget);
141 				}
142 			} else if(memcmp(pseudo_dev, pseudo->name[i].dev,
143 					sizeof(struct pseudo_dev)) != 0) {
144 				ERROR_START("%s already exists as a different "
145 					"pseudo definition.", alltarget);
146 				ERROR_EXIT("  Ignoring!\n");
147 			} else {
148 				ERROR_START("%s already exists as an identical "
149 					"pseudo definition!", alltarget);
150 				ERROR_EXIT("  Ignoring!\n");
151 			}
152 		} else {
153 			if(target[0] == '\0') {
154 				/*
155 				 * sub-directory exists, which means we can only
156 				 * add a pseudo file of type 'd' or type 'm'
157 				 */
158 				if(pseudo->name[i].dev == NULL &&
159 						(pseudo_dev->type == 'd' ||
160 						pseudo_dev->type == 'm')) {
161 					pseudo->name[i].pathname =
162 						strdup(alltarget);
163 					pseudo->name[i].dev = pseudo_dev;
164 				} else {
165 					ERROR_START("%s already exists as a "
166 						"different pseudo definition.",
167 						pseudo->name[i].name);
168 					ERROR_EXIT("  Ignoring %s!\n",
169 						alltarget);
170 				}
171 			} else
172 				/* recurse adding child components */
173 				add_pseudo(pseudo->name[i].pseudo, pseudo_dev,
174 					target, alltarget);
175 		}
176 	}
177 
178 	return pseudo;
179 }
180 
181 
182 /*
183  * Find subdirectory in pseudo directory referenced by pseudo, matching
184  * filename.  If filename doesn't exist or if filename is a leaf file
185  * return NULL
186  */
pseudo_subdir(char * filename,struct pseudo * pseudo)187 struct pseudo *pseudo_subdir(char *filename, struct pseudo *pseudo)
188 {
189 	int i;
190 
191 	if(pseudo == NULL)
192 		return NULL;
193 
194 	for(i = 0; i < pseudo->names; i++)
195 		if(strcmp(filename, pseudo->name[i].name) == 0)
196 			return pseudo->name[i].pseudo;
197 
198 	return NULL;
199 }
200 
201 
pseudo_readdir(struct pseudo * pseudo)202 struct pseudo_entry *pseudo_readdir(struct pseudo *pseudo)
203 {
204 	if(pseudo == NULL)
205 		return NULL;
206 
207 	while(pseudo->count < pseudo->names) {
208 		if(pseudo->name[pseudo->count].dev != NULL)
209 			return &pseudo->name[pseudo->count++];
210 		else
211 			pseudo->count++;
212 	}
213 
214 	return NULL;
215 }
216 
217 
pseudo_exec_file(struct pseudo_dev * dev,int * child)218 int pseudo_exec_file(struct pseudo_dev *dev, int *child)
219 {
220 	int res, pipefd[2];
221 
222 	res = pipe(pipefd);
223 	if(res == -1) {
224 		ERROR("Executing dynamic pseudo file, pipe failed\n");
225 		return 0;
226 	}
227 
228 	*child = fork();
229 	if(*child == -1) {
230 		ERROR("Executing dynamic pseudo file, fork failed\n");
231 		goto failed;
232 	}
233 
234 	if(*child == 0) {
235 		close(pipefd[0]);
236 		close(STDOUT_FILENO);
237 		res = dup(pipefd[1]);
238 		if(res == -1)
239 			exit(EXIT_FAILURE);
240 
241 		execl("/bin/sh", "sh", "-c", dev->command, (char *) NULL);
242 		exit(EXIT_FAILURE);
243 	}
244 
245 	close(pipefd[1]);
246 	return pipefd[0];
247 
248 failed:
249 	close(pipefd[0]);
250 	close(pipefd[1]);
251 	return 0;
252 }
253 
254 
add_pseudo_file(struct pseudo_dev * dev)255 void add_pseudo_file(struct pseudo_dev *dev)
256 {
257 	pseudo_file = realloc(pseudo_file, (pseudo_count + 1) *
258 		sizeof(struct pseudo_dev *));
259 	if(pseudo_file == NULL)
260 		MEM_ERROR();
261 
262 	dev->pseudo_id = pseudo_count;
263 	pseudo_file[pseudo_count ++] = dev;
264 }
265 
266 
get_pseudo_file(int pseudo_id)267 struct pseudo_dev *get_pseudo_file(int pseudo_id)
268 {
269 	return pseudo_file[pseudo_id];
270 }
271 
272 
read_pseudo_def(char * def)273 int read_pseudo_def(char *def)
274 {
275 	int n, bytes;
276 	unsigned int major = 0, minor = 0, mode;
277 	char type, *ptr;
278 	char suid[100], sgid[100]; /* overflow safe */
279 	char *filename, *name;
280 	char *orig_def = def;
281 	long long uid, gid;
282 	struct pseudo_dev *dev;
283 
284 	/*
285 	 * Scan for filename, don't use sscanf() and "%s" because
286 	 * that can't handle filenames with spaces
287 	 */
288 	filename = malloc(strlen(def) + 1);
289 	if(filename == NULL)
290 		MEM_ERROR();
291 
292 	for(name = filename; !isspace(*def) && *def != '\0';) {
293 		if(*def == '\\') {
294 			def ++;
295 			if (*def == '\0')
296 				break;
297 		}
298 		*name ++ = *def ++;
299 	}
300 	*name = '\0';
301 
302 	if(*filename == '\0') {
303 		ERROR("Not enough or invalid arguments in pseudo file "
304 			"definition \"%s\"\n", orig_def);
305 		goto error;
306 	}
307 
308 	n = sscanf(def, " %c %o %99s %99s %n", &type, &mode, suid, sgid,
309 		&bytes);
310 	def += bytes;
311 
312 	if(n < 4) {
313 		ERROR("Not enough or invalid arguments in pseudo file "
314 			"definition \"%s\"\n", orig_def);
315 		switch(n) {
316 		case -1:
317 			/* FALLTHROUGH */
318 		case 0:
319 			ERROR("Read filename, but failed to read or match "
320 				"type\n");
321 			break;
322 		case 1:
323 			ERROR("Read filename and type, but failed to read or "
324 				"match octal mode\n");
325 			break;
326 		case 2:
327 			ERROR("Read filename, type and mode, but failed to "
328 				"read or match uid\n");
329 			break;
330 		default:
331 			ERROR("Read filename, type, mode and uid, but failed "
332 				"to read or match gid\n");
333 			break;
334 		}
335 		goto error;
336 	}
337 
338 	switch(type) {
339 	case 'b':
340 		/* FALLTHROUGH */
341 	case 'c':
342 		n = sscanf(def, "%u %u %n", &major, &minor, &bytes);
343 		def += bytes;
344 
345 		if(n < 2) {
346 			ERROR("Not enough or invalid arguments in %s device "
347 				"pseudo file definition \"%s\"\n", type == 'b' ?
348 				"block" : "character", orig_def);
349 			if(n < 1)
350 				ERROR("Read filename, type, mode, uid and gid, "
351 					"but failed to read or match major\n");
352 			else
353 				ERROR("Read filename, type, mode, uid, gid "
354 					"and major, but failed to read  or "
355 					"match minor\n");
356 			goto error;
357 		}
358 
359 		if(major > 0xfff) {
360 			ERROR("Major %d out of range\n", major);
361 			goto error;
362 		}
363 
364 		if(minor > 0xfffff) {
365 			ERROR("Minor %d out of range\n", minor);
366 			goto error;
367 		}
368 		/* FALLTHROUGH */
369 	case 'd':
370 		/* FALLTHROUGH */
371 	case 'm':
372 		/*
373 		 * Check for trailing junk after expected arguments
374 		 */
375 		if(def[0] != '\0') {
376 			ERROR("Unexpected tailing characters in pseudo file "
377 				"definition \"%s\"\n", orig_def);
378 			goto error;
379 		}
380 		break;
381 	case 'f':
382 		if(def[0] == '\0') {
383 			ERROR("Not enough arguments in dynamic file pseudo "
384 				"definition \"%s\"\n", orig_def);
385 			ERROR("Expected command, which can be an executable "
386 				"or a piece of shell script\n");
387 			goto error;
388 		}
389 		break;
390 	default:
391 		ERROR("Unsupported type %c\n", type);
392 		goto error;
393 	}
394 
395 
396 	if(mode > 07777) {
397 		ERROR("Mode %o out of range\n", mode);
398 		goto error;
399 	}
400 
401 	uid = strtoll(suid, &ptr, 10);
402 	if(*ptr == '\0') {
403 		if(uid < 0 || uid > ((1LL << 32) - 1)) {
404 			ERROR("Uid %s out of range\n", suid);
405 			goto error;
406 		}
407 	} else {
408 		struct passwd *pwuid = getpwnam(suid);
409 		if(pwuid)
410 			uid = pwuid->pw_uid;
411 		else {
412 			ERROR("Uid %s invalid uid or unknown user\n", suid);
413 			goto error;
414 		}
415 	}
416 
417 	gid = strtoll(sgid, &ptr, 10);
418 	if(*ptr == '\0') {
419 		if(gid < 0 || gid > ((1LL << 32) - 1)) {
420 			ERROR("Gid %s out of range\n", sgid);
421 			goto error;
422 		}
423 	} else {
424 		struct group *grgid = getgrnam(sgid);
425 		if(grgid)
426 			gid = grgid->gr_gid;
427 		else {
428 			ERROR("Gid %s invalid uid or unknown user\n", sgid);
429 			goto error;
430 		}
431 	}
432 
433 	switch(type) {
434 	case 'b':
435 		mode |= S_IFBLK;
436 		break;
437 	case 'c':
438 		mode |= S_IFCHR;
439 		break;
440 	case 'd':
441 		mode |= S_IFDIR;
442 		break;
443 	case 'f':
444 		mode |= S_IFREG;
445 		break;
446 	}
447 
448 	dev = malloc(sizeof(struct pseudo_dev));
449 	if(dev == NULL)
450 		MEM_ERROR();
451 
452 	dev->type = type;
453 	dev->mode = mode;
454 	dev->uid = uid;
455 	dev->gid = gid;
456 	dev->major = major;
457 	dev->minor = minor;
458 	if(type == 'f') {
459 		dev->command = strdup(def);
460 		add_pseudo_file(dev);
461 	}
462 
463 	pseudo = add_pseudo(pseudo, dev, filename, filename);
464 
465 	free(filename);
466 	return TRUE;
467 
468 error:
469 	ERROR("Pseudo definitions should be of format\n");
470 	ERROR("\tfilename d mode uid gid\n");
471 	ERROR("\tfilename m mode uid gid\n");
472 	ERROR("\tfilename b mode uid gid major minor\n");
473 	ERROR("\tfilename c mode uid gid major minor\n");
474 	ERROR("\tfilename f mode uid command\n");
475 	free(filename);
476 	return FALSE;
477 }
478 
479 
read_pseudo_file(char * filename)480 int read_pseudo_file(char *filename)
481 {
482 	return read_file(filename, "pseudo", read_pseudo_def);
483 }
484 
485 
get_pseudo()486 struct pseudo *get_pseudo()
487 {
488 	return pseudo;
489 }
490 
491 
492 #ifdef SQUASHFS_TRACE
dump_pseudo(struct pseudo * pseudo,char * string)493 static void dump_pseudo(struct pseudo *pseudo, char *string)
494 {
495 	int i, res;
496 	char *path;
497 
498 	for(i = 0; i < pseudo->names; i++) {
499 		struct pseudo_entry *entry = &pseudo->name[i];
500 		if(string) {
501 			res = asprintf(&path, "%s/%s", string, entry->name);
502 			if(res == -1)
503 				BAD_ERROR("asprintf failed in dump_pseudo\n");
504 		} else
505 			path = entry->name;
506 		if(entry->dev)
507 			ERROR("%s %c 0%o %d %d %d %d\n", path, entry->dev->type,
508 				entry->dev->mode & ~S_IFMT, entry->dev->uid,
509 				entry->dev->gid, entry->dev->major,
510 				entry->dev->minor);
511 		if(entry->pseudo)
512 			dump_pseudo(entry->pseudo, path);
513 		if(string)
514 			free(path);
515 	}
516 }
517 
518 
dump_pseudos()519 void dump_pseudos()
520 {
521     if (pseudo)
522         dump_pseudo(pseudo, NULL);
523 }
524 #else
dump_pseudos()525 void dump_pseudos()
526 {
527 }
528 #endif
529