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