aboutsummaryrefslogtreecommitdiff
path: root/mkfs.ubifs/devtable.c
blob: dee035d5e948835ca728c2e824cd6609893cd625 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
/*
 * Copyright (C) 2008 Nokia Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * Author: Artem Bityutskiy
 *
 * Part of the device table parsing code was taken from the mkfs.jffs2 utility.
 * The original author of that code is Erik Andersen, hence:
 *	Copyright (C) 2001, 2002 Erik Andersen <andersen@codepoet.org>
 */

/*
 * This file implemented device table support. Device table entries take the
 * form of:
 * <path>    <type> <mode> <uid> <gid> <major> <minor> <start>	<inc> <count>
 * /dev/mem  c       640   0     0     1       1       0        0     -
 *
 * Type can be one of:
 * f  A regular file
 * d  Directory
 * c  Character special device file
 * b  Block special device file
 * p  Fifo (named pipe)
 *
 * Don't bother with symlinks (permissions are irrelevant), hard links (special
 * cases of regular files), or sockets (why bother).
 *
 * Regular files must exist in the target root directory. If a char, block,
 * fifo, or directory does not exist, it will be created.
 *
 * Please, refer the device_table.txt file which can be found at MTD utilities
 * for more information about what the device table is.
 */

#include "mkfs.ubifs.h"
#include "hashtable/hashtable.h"
#include "hashtable/hashtable_itr.h"

/*
 * The hash table which contains paths to files/directories/device nodes
 * referred to in the device table. For example, if the device table refers
 * "/dev/loop0", the @path_htbl will contain "/dev" element.
 */
static struct hashtable *path_htbl;

/* Hash function used for hash tables */
static unsigned int r5_hash(void *s)
{
	unsigned int a = 0;
	const signed char *str = s;

	while (*str) {
		a += *str << 4;
		a += *str >> 4;
		a *= 11;
		str++;
	}

	return a;
}

/*
 * Check whether 2 keys of a hash table are equivalent. The keys are path/file
 * names, so we simply use 'strcmp()'.
 */
static int is_equivalent(void *k1, void *k2)
{
	return !strcmp(k1, k2);
}

/**
 * separate_last - separate out the last path component
 * @buf: the path to split
 * @len: length of the @buf string
 * @path: the beginning of path is returned here
 * @name: the last path component is returned here
 *
 * This helper function separates out the the last component of the full path
 * string. For example, "/dev/loop" would be split on "/dev" and "loop". This
 * function allocates memory for @path and @name and return the result there.
 * Returns zero in case of success and a negative error code in case of
 * failure.
 */
static int separate_last(const char *buf, int len, char **path, char **name)
{
	int path_len = len, name_len;
	const char *p = buf + len, *n;

	while (*--p != '/')
		path_len -= 1;

	/* Drop the final '/' unless this is the root directory */
	name_len = len - path_len;
	n = buf + path_len;
	if (path_len > 1)
		path_len -= 1;

	*path = malloc(path_len + 1);
	if (!*path)
		return err_msg("cannot allocate %d bytes of memory",
			       path_len + 1);
	memcpy(*path, buf, path_len);
	(*path)[path_len] = '\0';

	*name = malloc(name_len + 1);
	if (!*name) {
		free(*path);
		return err_msg("cannot allocate %d bytes of memory",
			       name_len + 1);
	}
	memcpy(*name, n, name_len + 1);

	return 0;
}

static int interpret_table_entry(const char *line)
{
	char buf[1024], type, *path = NULL, *name = NULL;
	int len;
	struct path_htbl_element *ph_elt = NULL;
	struct name_htbl_element *nh_elt = NULL;
	unsigned int mode = 0755, uid = 0, gid = 0, major = 0, minor = 0;
	unsigned int start = 0, increment = 0, count = 0;

	if (sscanf(line, "%1023s %c %o %u %u %u %u %u %u %u",
		   buf, &type, &mode, &uid, &gid, &major, &minor,
		   &start, &increment, &count) < 0)
		return sys_err_msg("sscanf failed");

	dbg_msg(3, "name %s, type %c, mode %o, uid %u, gid %u, major %u, "
		"minor %u, start %u, inc %u, cnt %u",
		buf, type, mode, uid, gid, major, minor, start,
		increment, count);

	len = strnlen(buf, 1024);
	if (len == 1024)
		return err_msg("too long path");

	if (!strcmp(buf, "/"))
		return err_msg("device table entries require absolute paths");
	if (buf[1] == '\0')
		return err_msg("root directory cannot be created");
	if (strstr(buf, "//"))
		return err_msg("'//' cannot be used in the path");
	if (buf[len - 1] == '/')
		return err_msg("do not put '/' at the end");

	if (strstr(buf, "/./") || strstr(buf, "/../") ||
	    !strcmp(buf + len - 2, "/.") || !strcmp(buf + len - 3, "/.."))
		return err_msg("'.' and '..' cannot be used in the path");

	switch (type) {
		case 'd':
			mode |= S_IFDIR;
			break;
		case 'f':
			mode |= S_IFREG;
			break;
		case 'p':
			mode |= S_IFIFO;
			break;
		case 'c':
			mode |= S_IFCHR;
			break;
		case 'b':
			mode |= S_IFBLK;
			break;
		default:
			return err_msg("unsupported file type '%c'", type);
	}

	if (separate_last(buf, len, &path, &name))
		return -1;

	/*
	 * Check if this path already exist in the path hash table and add it
	 * if it is not.
	 */
	ph_elt = hashtable_search(path_htbl, path);
	if (!ph_elt) {
		dbg_msg(3, "inserting '%s' into path hash table", path);
		ph_elt = malloc(sizeof(struct path_htbl_element));
		if (!ph_elt) {
			err_msg("cannot allocate %zd bytes of memory",
				sizeof(struct path_htbl_element));
			goto out_free;
		}

		if (!hashtable_insert(path_htbl, path, ph_elt)) {
			err_msg("cannot insert into path hash table");
			goto out_free;
		}

		ph_elt->path = path;
		path = NULL;
		ph_elt->name_htbl = create_hashtable(128, &r5_hash,
						     &is_equivalent);
		if (!ph_elt->name_htbl) {
			err_msg("cannot create name hash table");
			goto out_free;
		}
	}

	if (increment != 0 && count == 0)
		return err_msg("count cannot be zero if increment is non-zero");

	/*
	 * Add the file/directory/device node (last component of the path) to
	 * the name hashtable. The name hashtable resides in the corresponding
	 * path hashtable element.
	 */

	if (count == 0) {
		/* This entry does not require any iterating */
		nh_elt = malloc(sizeof(struct name_htbl_element));
		if (!nh_elt) {
			err_msg("cannot allocate %zd bytes of memory",
				sizeof(struct name_htbl_element));
			goto out_free;
		}

		nh_elt->mode = mode;
		nh_elt->uid = uid;
		nh_elt->gid = gid;
		nh_elt->dev = makedev(major, minor);

		dbg_msg(3, "inserting '%s' into name hash table (major %d, minor %d)",
			name, major(nh_elt->dev), minor(nh_elt->dev));

		if (hashtable_search(ph_elt->name_htbl, name))
			return err_msg("'%s' is referred twice", buf);

		nh_elt->name = name;
		if (!hashtable_insert(ph_elt->name_htbl, name, nh_elt)) {
			err_msg("cannot insert into name hash table");
			goto out_free;
		}
	} else {
		int i, num = start + count, len = strlen(name) + 20;
		char *nm;

		for (i = start; i < num; i++) {
			nh_elt = malloc(sizeof(struct name_htbl_element));
			if (!nh_elt) {
				err_msg("cannot allocate %zd bytes of memory",
					sizeof(struct name_htbl_element));
				goto out_free;
			}

			nh_elt->mode = mode;
			nh_elt->uid = uid;
			nh_elt->gid = gid;
			nh_elt->dev = makedev(major, minor + (i - start) * increment);

			nm = malloc(len);
			if (!nm) {
				err_msg("cannot allocate %d bytes of memory", len);
				goto out_free;
			}

			sprintf(nm, "%s%d", name, i);
			nh_elt->name = nm;

			dbg_msg(3, "inserting '%s' into name hash table (major %d, minor %d)",
			        nm, major(nh_elt->dev), minor(nh_elt->dev));

			if (hashtable_search(ph_elt->name_htbl, nm)) {
				err_msg("'%s' is referred twice", buf);
				free (nm);
				goto out_free;
			}

			if (!hashtable_insert(ph_elt->name_htbl, nm, nh_elt)) {
				err_msg("cannot insert into name hash table");
				free (nm);
				goto out_free;
			}
		}
		free(name);
		name = NULL;
	}

	return 0;

out_free:
	free(ph_elt);
	free(nh_elt);
	free(path);
	free(name);
	return -1;
}

/**
 * parse_devtable - parse the device table.
 * @tbl_file: device table file name
 *
 * This function parses the device table and prepare the hash table which will
 * later be used by mkfs.ubifs to create the specified files/device nodes.
 * Returns zero in case of success and a negative error code in case of
 * failure.
 */
int parse_devtable(const char *tbl_file)
{
	FILE *f;
	char *line = NULL;
	struct stat st;
	size_t len;

	dbg_msg(1, "parsing device table file '%s'", tbl_file);

	path_htbl = create_hashtable(128, &r5_hash, &is_equivalent);
	if (!path_htbl)
		return err_msg("cannot create path hash table");

	f = fopen(tbl_file, "r");
	if (!f)
		return sys_err_msg("cannot open '%s'", tbl_file);

	if (fstat(fileno(f), &st) < 0) {
		sys_err_msg("cannot stat '%s'", tbl_file);
		goto out_close;
	}

	if (st.st_size < 10) {
		sys_err_msg("'%s' is too short", tbl_file);
		goto out_close;
	}

	/*
	 * The general plan now is to read in one line at a time, check for
	 * leading comment delimiters ('#'), then try and parse the line as a
	 * device table
	 */
	while (getline(&line, &len, f) != -1) {
		/* First trim off any white-space */
		len = strlen(line);

		/* Trim trailing white-space */
		while (len > 0 && isspace(line[len - 1]))
			line[--len] = '\0';
		/* Trim leading white-space */
		memmove(line, &line[strspn(line, " \n\r\t\v")], len);

		/* How long are we after trimming? */
		len = strlen(line);

		/* If this is not a comment line, try to interpret it */
		if (len && *line != '#') {
			if (interpret_table_entry(line)) {
				err_msg("cannot parse '%s'", line);
				goto out_close;
			}
		}

		free(line);
		line = NULL;
	}

	dbg_msg(1, "finished parsing");
	fclose(f);
	return 0;

out_close:
	fclose(f);
	free_devtable_info();
	return -1;
}

/**
 * devtbl_find_path - find a path in the path hash table.
 * @path: UBIFS path to find.
 *
 * This looks up the path hash table. Returns the path hash table element
 * reference if @path was found and %NULL if not.
 */
struct path_htbl_element *devtbl_find_path(const char *path)
{
	if (!path_htbl)
		return NULL;

	return hashtable_search(path_htbl, (void *)path);
}

/**
 * devtbl_find_name - find a name in the name hash table.
 * @ph_etl: path hash table element to find at
 * @name: name to find
 *
 * This looks up the name hash table. Returns the name hash table element
 * reference if @name found and %NULL if not.
 */
struct name_htbl_element *devtbl_find_name(struct path_htbl_element *ph_elt,
					   const char *name)
{
	if (!path_htbl)
		return NULL;

	return hashtable_search(ph_elt->name_htbl, (void *)name);
}

/**
 * override_attributes - override inode attributes.
 * @st: struct stat object to containing the attributes to override
 * @ph_elt: path hash table element object
 * @nh_elt: name hash table element object containing the new values
 *
 * The device table file may override attributes like UID of files. For
 * example, the device table may contain a "/dev" entry, and the UBIFS FS on
 * the host may contain "/dev" directory. In this case the attributes of the
 * "/dev" directory inode has to be as the device table specifies.
 *
 * Note, the hash element is removed by this function as well.
 */
int override_attributes(struct stat *st, struct path_htbl_element *ph_elt,
			struct name_htbl_element *nh_elt)
{
	if (!path_htbl)
		return 0;

	if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode) ||
	    S_ISFIFO(st->st_mode))
		return err_msg("%s/%s both exists at UBIFS root at host, "
			       "and is referred from the device table",
			       strcmp(ph_elt->path, "/") ? ph_elt->path : "",
			       nh_elt->name);

	if ((st->st_mode & S_IFMT) != (nh_elt->mode & S_IFMT))
		return err_msg("%s/%s is referred from the device table also exists in "
			       "the UBIFS root directory at host, but the file type is "
			       "different", strcmp(ph_elt->path, "/") ? ph_elt->path : "",
			       nh_elt->name);

	dbg_msg(3, "set UID %d, GID %d, mode %o for %s/%s as device table says",
		nh_elt->uid, nh_elt->gid, nh_elt->mode, ph_elt->path, nh_elt->name);

	st->st_uid = nh_elt->uid;
	st->st_gid = nh_elt->gid;
	st->st_mode = nh_elt->mode;

	hashtable_remove(ph_elt->name_htbl, (void *)nh_elt->name);
	return 0;
}

/**
 * first_name_htbl_element - return first element of the name hash table.
 * @ph_elt: the path hash table the name hash table belongs to
 * @itr: double pointer to a 'struct hashtable_itr' object where the
 *       information about further iterations is stored
 *
 * This function implements name hash table iteration together with
 * 'next_name_htbl_element()'. Returns the first name hash table element or
 * %NULL if the hash table is empty.
 */
struct name_htbl_element *
first_name_htbl_element(struct path_htbl_element *ph_elt,
			struct hashtable_itr **itr)
{
	if (!path_htbl || !ph_elt || hashtable_count(ph_elt->name_htbl) == 0)
		return NULL;

	*itr = hashtable_iterator(ph_elt->name_htbl);
	return hashtable_iterator_value(*itr);
}

/**
 * first_name_htbl_element - return next element of the name hash table.
 * @ph_elt: the path hash table the name hash table belongs to
 * @itr: double pointer to a 'struct hashtable_itr' object where the
 *       information about further iterations is stored
 *
 * This function implements name hash table iteration together with
 * 'first_name_htbl_element()'. Returns the next name hash table element or
 * %NULL if there are no more elements.
 */
struct name_htbl_element *
next_name_htbl_element(struct path_htbl_element *ph_elt,
		       struct hashtable_itr **itr)
{
	if (!path_htbl || !ph_elt || !hashtable_iterator_advance(*itr))
		return NULL;

	return hashtable_iterator_value(*itr);
}

/**
 * free_devtable_info - free device table information.
 *
 * This function frees the path hash table and the name hash tables.
 */
void free_devtable_info(void)
{
	struct hashtable_itr *ph_itr;
	struct path_htbl_element *ph_elt;

	if (!path_htbl)
		return;

	if (hashtable_count(path_htbl) > 0) {
		ph_itr = hashtable_iterator(path_htbl);
		do {
			ph_elt = hashtable_iterator_value(ph_itr);
			/*
			 * Note, since we use the same string for the key and
			 * @name in the name hash table elements, we do not
			 * have to iterate name hash table because @name memory
			 * will be freed when freeing the key.
			 */
			hashtable_destroy(ph_elt->name_htbl, 1);
		} while (hashtable_iterator_advance(ph_itr));
	}
	hashtable_destroy(path_htbl, 1);
}