• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #ifndef _GNU_SOURCE
2 #define _GNU_SOURCE
3 #endif
4 
5 #include <stdio.h>
6 #include <getopt.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <limits.h>
10 #include <ext2fs/ext2fs.h>
11 
12 #include "perms.h"
13 #include "base_fs.h"
14 #include "block_list.h"
15 #include "basefs_allocator.h"
16 #include "create_inode.h"
17 
18 #ifndef UID_GID_MAP_MAX_EXTENTS
19 /*
20  * The value is defined in linux/user_namspace.h.
21  * The value is (arbitrarily) 5 in 4.14 and earlier, or 340 in 4.15 and later.
22  * Here, the bigger value is taken. See also man user_namespace(7).
23  */
24 #define UID_GID_MAP_MAX_EXTENTS 340
25 #endif
26 
27 static char *prog_name = "e2fsdroid";
28 static char *in_file;
29 static char *block_list;
30 static char *basefs_out;
31 static char *basefs_in;
32 static char *mountpoint = "";
33 static time_t fixed_time = -1;
34 static char *fs_config_file;
35 static struct selinux_opt seopt_file[8];
36 static int max_nr_opt = (int)sizeof(seopt_file) / sizeof(seopt_file[0]);
37 static char *product_out;
38 static char *src_dir;
39 static int android_configure;
40 static int android_sparse_file = 1;
41 
usage(int ret)42 static void usage(int ret)
43 {
44 	fprintf(stderr, "%s [-B block_list] [-D basefs_out] [-T timestamp]\n"
45 			"\t[-C fs_config] [-S file_contexts] [-p product_out]\n"
46 			"\t[-a mountpoint] [-d basefs_in] [-f src_dir] [-e] [-s]\n"
47 			"\t[-u uid-mapping] [-g gid-mapping] image\n",
48                 prog_name);
49 	exit(ret);
50 }
51 
absolute_path(const char * file)52 static char *absolute_path(const char *file)
53 {
54 	char *ret;
55 	char cwd[PATH_MAX];
56 
57 	if (file[0] != '/') {
58 		if (getcwd(cwd, PATH_MAX) == NULL) {
59 			fprintf(stderr, "Failed to getcwd\n");
60 			exit(EXIT_FAILURE);
61 		}
62 		ret = malloc(strlen(cwd) + 1 + strlen(file) + 1);
63 		if (ret)
64 			sprintf(ret, "%s/%s", cwd, file);
65 	} else
66 		ret = strdup(file);
67 	return ret;
68 }
69 
parse_ugid_map_entry(char * line,struct ugid_map_entry * result)70 static int parse_ugid_map_entry(char* line, struct ugid_map_entry* result)
71 {
72 	char *token, *token_saveptr;
73 	size_t num_tokens;
74 	unsigned int *parsed[] = {&result->child_id,
75 				  &result->parent_id,
76 				  &result->length};
77 	for (token = strtok_r(line, " ", &token_saveptr), num_tokens = 0;
78 	     token && num_tokens < 3;
79 	     token = strtok_r(NULL, " ", &token_saveptr), ++num_tokens) {
80 		char* endptr = NULL;
81 		unsigned long t = strtoul(token, &endptr, 10);
82 		if ((t == ULONG_MAX && errno) || (t > UINT_MAX) || *endptr) {
83 			fprintf(stderr, "Malformed u/gid mapping line\n");
84 			return 0;
85 		}
86 		*parsed[num_tokens] = (unsigned int) t;
87 	}
88 	if (num_tokens < 3 || strtok_r(NULL, " ", &token_saveptr) != NULL) {
89 		fprintf(stderr, "Malformed u/gid mapping line\n");
90 		return 0;
91 	}
92 	if (result->child_id + result->length < result->child_id ||
93 	    result->parent_id + result->length < result->parent_id) {
94 		fprintf(stderr, "u/gid mapping overflow\n");
95 		return 0;
96 	}
97 	return 1;
98 }
99 
100 /*
101  * Returns 1 if [begin1, begin1+length1) and [begin2, begin2+length2) have
102  * overlapping range. Otherwise 0.
103  */
is_overlapping(unsigned int begin1,unsigned int length1,unsigned int begin2,unsigned int length2)104 static int is_overlapping(unsigned int begin1, unsigned int length1,
105 			  unsigned int begin2, unsigned int length2)
106 {
107 	unsigned int end1 = begin1 + length1;
108 	unsigned int end2 = begin2 + length2;
109 	return !(end1 <= begin2 || end2 <= begin1);
110 }
111 
112 /*
113  * Verifies if the given mapping works.
114  * - Checks if the number of entries is less than or equals to
115  *   UID_GID_MAP_MAX_EXTENTS.
116  * - Checks if there is no overlapped ranges.
117  * Returns 1 if valid, otherwise 0.
118  */
is_valid_ugid_map(const struct ugid_map * mapping)119 static int is_valid_ugid_map(const struct ugid_map* mapping)
120 {
121 	size_t i, j;
122 
123 	if (mapping->size > UID_GID_MAP_MAX_EXTENTS) {
124 		fprintf(stderr, "too many u/gid mapping entries\n");
125 		return 0;
126 	}
127 
128 	for (i = 0; i < mapping->size; ++i) {
129 		const struct ugid_map_entry *entry1 = &mapping->entries[i];
130 		for (j = i + 1; j < mapping->size; ++j) {
131 			const struct ugid_map_entry *entry2 =
132 				&mapping->entries[j];
133 			if (is_overlapping(entry1->child_id, entry1->length,
134 					   entry2->child_id, entry2->length)) {
135 				fprintf(stderr,
136 					"Overlapping child u/gid: [%d %d %d],"
137 					" [%d %d %d]\n",
138 					entry1->child_id, entry1->parent_id,
139 					entry1->length, entry2->child_id,
140 					entry2->parent_id, entry2->length);
141 				return 0;
142 			}
143 			if (is_overlapping(entry1->parent_id, entry1->length,
144 					   entry2->parent_id, entry2->length)) {
145 				fprintf(stderr,
146 					"Overlapping parent u/gid: [%d %d %d],"
147 					" [%d %d %d]\n",
148 					entry1->child_id, entry1->parent_id,
149 					entry1->length, entry2->child_id,
150 					entry2->parent_id, entry2->length);
151 				return 0;
152 			}
153 		}
154 	}
155 	return 1;
156 }
157 
158 /*
159  * Parses the UID/GID mapping argument. The argument could be a multi-line
160  * string (separated by '\n', no trailing '\n' is allowed). Each line must
161  * contain exact three integer tokens; the first token is |child_id|,
162  * the second is |parent_id|, and the last is |length| of the mapping range.
163  * See also user_namespace(7) man page.
164  * On success, the parsed entries are stored in |result|, and it returns 1.
165  * Otherwise, returns 0.
166  */
parse_ugid_map(char * arg,struct ugid_map * result)167 static int parse_ugid_map(char* arg, struct ugid_map* result)
168 {
169 	int i;
170 	char *line, *line_saveptr;
171 	size_t current_index;
172 
173 	/* Count the number of lines. */
174 	result->size = 1;
175 	for (i = 0; arg[i]; ++i) {
176 		if (arg[i] == '\n')
177 			++result->size;
178 	}
179 
180 	/* Allocate memory for entries. */
181 	result->entries = malloc(sizeof(struct ugid_map_entry) * result->size);
182 	if (!result->entries) {
183 		result->size = 0;
184 		return 0;
185 	}
186 
187 	/* Parse each line */
188 	for (line = strtok_r(arg, "\n", &line_saveptr), current_index = 0;
189 	     line;
190 	     line = strtok_r(NULL, "\n", &line_saveptr), ++current_index) {
191 		if (!parse_ugid_map_entry(
192 			line, &result->entries[current_index])) {
193 			return 0;
194 		}
195 	}
196 
197 	return is_valid_ugid_map(result);
198 }
199 
main(int argc,char * argv[])200 int main(int argc, char *argv[])
201 {
202 	int c;
203 	char *p;
204 	int flags = EXT2_FLAG_RW;
205 	errcode_t retval;
206 	io_manager io_mgr;
207 	ext2_filsys fs = NULL;
208 	struct fs_ops_callbacks fs_callbacks = { NULL, NULL };
209 	char *token;
210 	int nr_opt = 0;
211 	ext2_ino_t inodes_count;
212 	ext2_ino_t free_inodes_count;
213 	blk64_t blocks_count;
214 	blk64_t free_blocks_count;
215 	struct ugid_map uid_map = { 0, NULL }, gid_map = { 0, NULL };
216 
217 	add_error_table(&et_ext2_error_table);
218 
219 	while ((c = getopt (argc, argv, "T:C:S:p:a:D:d:B:f:esu:g:")) != EOF) {
220 		switch (c) {
221 		case 'T':
222 			fixed_time = strtoul(optarg, &p, 0);
223 			android_configure = 1;
224 			break;
225 		case 'C':
226 			fs_config_file = absolute_path(optarg);
227 			android_configure = 1;
228 			break;
229 		case 'S':
230 			token = strtok(optarg, ",");
231 			while (token) {
232 				if (nr_opt == max_nr_opt) {
233 					fprintf(stderr, "Expected at most %d selinux opts\n",
234 						max_nr_opt);
235 					exit(EXIT_FAILURE);
236 				}
237 				seopt_file[nr_opt].type = SELABEL_OPT_PATH;
238 				seopt_file[nr_opt].value = absolute_path(token);
239 				nr_opt++;
240 				token = strtok(NULL, ",");
241 			}
242 			android_configure = 1;
243 			break;
244 		case 'p':
245 			product_out = absolute_path(optarg);
246 			android_configure = 1;
247 			break;
248 		case 'a':
249 			mountpoint = strdup(optarg);
250 			break;
251 		case 'D':
252 			basefs_out = absolute_path(optarg);
253 			break;
254 		case 'd':
255 			basefs_in = absolute_path(optarg);
256 			break;
257 		case 'B':
258 			block_list = absolute_path(optarg);
259 			break;
260 		case 'f':
261 			src_dir = absolute_path(optarg);
262 			break;
263 		case 'e':
264 			android_sparse_file = 0;
265 			break;
266 		case 's':
267 			flags |= EXT2_FLAG_SHARE_DUP;
268 			break;
269 		case 'u':
270 			if (!parse_ugid_map(optarg, &uid_map))
271 				exit(EXIT_FAILURE);
272 			android_configure = 1;
273 			break;
274 		case 'g':
275 			if (!parse_ugid_map(optarg, &gid_map))
276 				exit(EXIT_FAILURE);
277 			android_configure = 1;
278 			break;
279 		default:
280 			usage(EXIT_FAILURE);
281 		}
282 	}
283 	if (optind >= argc) {
284 		fprintf(stderr, "Expected filename after options\n");
285 		exit(EXIT_FAILURE);
286 	}
287 
288 	if (android_sparse_file) {
289 		io_mgr = sparse_io_manager;
290 		if (asprintf(&in_file, "(%s)", argv[optind]) == -1) {
291 			fprintf(stderr, "Failed to allocate file name\n");
292 			exit(EXIT_FAILURE);
293 		}
294 	} else {
295 		io_mgr = unix_io_manager;
296 		in_file = strdup(argv[optind]);
297 	}
298 	retval = ext2fs_open(in_file, flags, 0, 0, io_mgr, &fs);
299 	if (retval) {
300 		com_err(prog_name, retval, "while opening file %s\n", in_file);
301 		return retval;
302 	}
303 
304 	if (src_dir) {
305 		ext2fs_read_bitmaps(fs);
306 		if (basefs_in) {
307 			retval = base_fs_alloc_load(fs, basefs_in, mountpoint,
308 				src_dir);
309 			if (retval) {
310 				com_err(prog_name, retval, "%s",
311 				"while reading base_fs file");
312 			    exit(1);
313 			}
314 			fs_callbacks.create_new_inode =
315 				base_fs_alloc_set_target;
316 			fs_callbacks.end_create_new_inode =
317 				base_fs_alloc_unset_target;
318 		}
319 		retval = populate_fs2(fs, EXT2_ROOT_INO, src_dir,
320 				      EXT2_ROOT_INO, &fs_callbacks);
321 		if (retval) {
322 			com_err(prog_name, retval, "%s",
323 			"while populating file system");
324 		    exit(1);
325 		}
326 		if (basefs_in)
327 			base_fs_alloc_cleanup(fs);
328 	}
329 
330 	if (android_configure) {
331 		retval = android_configure_fs(
332 			fs, src_dir, product_out, mountpoint, seopt_file,
333 			nr_opt, fs_config_file, fixed_time, &uid_map, &gid_map);
334 		if (retval) {
335 			com_err(prog_name, retval, "%s",
336 				"while configuring the file system");
337 			exit(1);
338 		}
339 	}
340 
341 	if (block_list) {
342 		retval = fsmap_iter_filsys(fs, &block_list_format, block_list,
343 					   mountpoint);
344 		if (retval) {
345 			com_err(prog_name, retval, "%s",
346 				"while creating the block_list");
347 			exit(1);
348 		}
349 	}
350 
351 	if (basefs_out) {
352 		retval = fsmap_iter_filsys(fs, &base_fs_format,
353 					   basefs_out, mountpoint);
354 		if (retval) {
355 			com_err(prog_name, retval, "%s",
356 				"while creating the basefs file");
357 			exit(1);
358 		}
359 	}
360 
361 	inodes_count = fs->super->s_inodes_count;
362 	free_inodes_count = fs->super->s_free_inodes_count;
363 	blocks_count = ext2fs_blocks_count(fs->super);
364 	free_blocks_count = ext2fs_free_blocks_count(fs->super);
365 
366 	retval = ext2fs_close_free(&fs);
367 	if (retval) {
368 		com_err(prog_name, retval, "%s",
369 				"while writing superblocks");
370 		exit(1);
371 	}
372 
373 	printf("Created filesystem with %u/%u inodes and %llu/%llu blocks\n",
374 			inodes_count - free_inodes_count, inodes_count,
375 			blocks_count - free_blocks_count, blocks_count);
376 
377 	remove_error_table(&et_ext2_error_table);
378 	return 0;
379 }
380