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