summaryrefslogtreecommitdiff
path: root/miscutils/ubi_tools.c
blob: 94a637eee797ac098982fc5c90260dd7397cd900 (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
/*
 * Ported to busybox from mtd-utils.
 *
 * Licensed under GPLv2, see file LICENSE in this source tree.
 */
//config:config UBIATTACH
//config:	bool "ubiattach (4.2 kb)"
//config:	default y
//config:	help
//config:	Attach MTD device to an UBI device.
//config:
//config:config UBIDETACH
//config:	bool "ubidetach (4.1 kb)"
//config:	default y
//config:	help
//config:	Detach MTD device from an UBI device.
//config:
//config:config UBIMKVOL
//config:	bool "ubimkvol (5.3 kb)"
//config:	default y
//config:	help
//config:	Create a UBI volume.
//config:
//config:config UBIRMVOL
//config:	bool "ubirmvol (4.9 kb)"
//config:	default y
//config:	help
//config:	Delete a UBI volume.
//config:
//config:config UBIRSVOL
//config:	bool "ubirsvol (4.2 kb)"
//config:	default y
//config:	help
//config:	Resize a UBI volume.
//config:
//config:config UBIUPDATEVOL
//config:	bool "ubiupdatevol (5.2 kb)"
//config:	default y
//config:	help
//config:	Update a UBI volume.

//                       APPLET_ODDNAME:name       main       location         suid_type     help
//applet:IF_UBIATTACH(   APPLET_ODDNAME(ubiattach, ubi_tools, BB_DIR_USR_SBIN, BB_SUID_DROP, ubiattach))
//applet:IF_UBIDETACH(   APPLET_ODDNAME(ubidetach, ubi_tools, BB_DIR_USR_SBIN, BB_SUID_DROP, ubidetach))
//applet:IF_UBIMKVOL(    APPLET_ODDNAME(ubimkvol,  ubi_tools, BB_DIR_USR_SBIN, BB_SUID_DROP, ubimkvol))
//applet:IF_UBIRMVOL(    APPLET_ODDNAME(ubirmvol,  ubi_tools, BB_DIR_USR_SBIN, BB_SUID_DROP, ubirmvol))
//applet:IF_UBIRSVOL(    APPLET_ODDNAME(ubirsvol,  ubi_tools, BB_DIR_USR_SBIN, BB_SUID_DROP, ubirsvol))
//applet:IF_UBIUPDATEVOL(APPLET_ODDNAME(ubiupdatevol, ubi_tools, BB_DIR_USR_SBIN, BB_SUID_DROP, ubiupdatevol))
/* not NOEXEC: if flash operation stalls, use less memory in "hung" process */

//kbuild:lib-$(CONFIG_UBIATTACH) += ubi_tools.o
//kbuild:lib-$(CONFIG_UBIDETACH) += ubi_tools.o
//kbuild:lib-$(CONFIG_UBIMKVOL)  += ubi_tools.o
//kbuild:lib-$(CONFIG_UBIRMVOL)  += ubi_tools.o
//kbuild:lib-$(CONFIG_UBIRSVOL)  += ubi_tools.o
//kbuild:lib-$(CONFIG_UBIUPDATEVOL) += ubi_tools.o

#include "libbb.h"
/* Some versions of kernel have broken headers, need this hack */
#ifndef __packed
# define __packed __attribute__((packed))
#endif
#include <mtd/ubi-user.h>

#define UBI_APPLET_CNT (0 \
	+ ENABLE_UBIATTACH \
	+ ENABLE_UBIDETACH \
	+ ENABLE_UBIMKVOL \
	+ ENABLE_UBIRMVOL \
	+ ENABLE_UBIRSVOL \
	+ ENABLE_UBIUPDATEVOL \
	)

#define do_attach (ENABLE_UBIATTACH    && (UBI_APPLET_CNT == 1 || applet_name[4] == 't'))
#define do_detach (ENABLE_UBIDETACH    && (UBI_APPLET_CNT == 1 || applet_name[4] == 'e'))
#define do_mkvol  (ENABLE_UBIMKVOL     && (UBI_APPLET_CNT == 1 || applet_name[4] == 'k'))
#define do_rmvol  (ENABLE_UBIRMVOL     && (UBI_APPLET_CNT == 1 || applet_name[4] == 'm'))
#define do_rsvol  (ENABLE_UBIRSVOL     && (UBI_APPLET_CNT == 1 || applet_name[4] == 's'))
#define do_update (ENABLE_UBIUPDATEVOL && (UBI_APPLET_CNT == 1 || applet_name[4] == 'p'))

static unsigned get_num_from_file(const char *path, unsigned max)
{
	char buf[sizeof(long long)*3];
	unsigned long long num;

	if (open_read_close(path, buf, sizeof(buf)) < 0)
		bb_perror_msg_and_die("can't open '%s'", path);
	/* It can be \n terminated, xatoull won't work well */
	if (sscanf(buf, "%llu", &num) != 1 || num > max)
		bb_error_msg_and_die("number in '%s' is malformed or too large", path);
	return num;
}

/* To prevent malloc(1G) accidents */
#define MAX_SANE_ERASEBLOCK (16*1024*1024)

int ubi_tools_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int ubi_tools_main(int argc UNUSED_PARAM, char **argv)
{
	static const struct suffix_mult size_suffixes[] = {
		{ "KiB", 1024 },
		{ "MiB", 1024*1024 },
		{ "GiB", 1024*1024*1024 },
		{ "", 0 }
	};

	unsigned opts;
	char *ubi_ctrl;
	int fd;
	int mtd_num;
	int dev_num = UBI_DEV_NUM_AUTO;
	int vol_id = UBI_VOL_NUM_AUTO;
	int vid_hdr_offset = 0;
	char *vol_name;
	unsigned long long size_bytes = size_bytes; /* for compiler */
	char *size_bytes_str;
	int alignment = 1;
	char *type;
	union {
		struct ubi_attach_req attach_req;
		struct ubi_mkvol_req  mkvol_req;
		struct ubi_rsvol_req  rsvol_req;
	} req_structs;
#define attach_req req_structs.attach_req
#define mkvol_req  req_structs.mkvol_req
#define rsvol_req  req_structs.rsvol_req
	char path[sizeof("/sys/class/ubi/ubi%d_%d/usable_eb_size")
				+ 2 * sizeof(int)*3 + /*just in case:*/ 16];
#define path_sys_class_ubi_ubi (path + sizeof("/sys/class/ubi/ubi")-1)

	strcpy(path, "/sys/class/ubi/ubi");
	memset(&req_structs, 0, sizeof(req_structs));

#define OPTION_m  (1 << 0)
#define OPTION_d  (1 << 1)
#define OPTION_n  (1 << 2)
#define OPTION_N  (1 << 3)
#define OPTION_s  (1 << 4)
#define OPTION_a  (1 << 5)
#define OPTION_t  (1 << 6)
	if (do_mkvol) {
		opts = getopt32(argv, "^" "md:+n:+N:s:a:+t:O:+" "\0" "-1",
				&dev_num, &vol_id,
				&vol_name, &size_bytes_str, &alignment, &type,
				&vid_hdr_offset
			);
	} else
	if (do_update) {
		opts = getopt32(argv, "^" "s:at" "\0" "-1", &size_bytes_str);
		opts *= OPTION_s;
	} else {
		opts = getopt32(argv, "^" "m:+d:+n:+N:s:a:+t:" "\0" "-1",
				&mtd_num, &dev_num, &vol_id,
				&vol_name, &size_bytes_str, &alignment, &type
		);
	}

	if (opts & OPTION_s)
		size_bytes = xatoull_sfx(size_bytes_str, size_suffixes);
	argv += optind;
	ubi_ctrl = *argv++;

	fd = xopen(ubi_ctrl, O_RDWR);
	//xfstat(fd, &st, ubi_ctrl);
	//if (!S_ISCHR(st.st_mode))
	//	bb_error_msg_and_die("%s: not a char device", ubi_ctrl);

//usage:#define ubiattach_trivial_usage
//usage:       "-m MTD_NUM [-d UBI_NUM] [-O VID_HDR_OFF] UBI_CTRL_DEV"
//usage:#define ubiattach_full_usage "\n\n"
//usage:       "Attach MTD device to UBI\n"
//usage:     "\n	-m MTD_NUM	MTD device number to attach"
//usage:     "\n	-d UBI_NUM	UBI device number to assign"
//usage:     "\n	-O VID_HDR_OFF	VID header offset"
	if (do_attach) {
		if (!(opts & OPTION_m))
			bb_error_msg_and_die("%s device not specified", "MTD");

		attach_req.mtd_num = mtd_num;
		attach_req.ubi_num = dev_num;
		attach_req.vid_hdr_offset = vid_hdr_offset;

		xioctl(fd, UBI_IOCATT, &attach_req);
	} else

//usage:#define ubidetach_trivial_usage
//usage:       "-d UBI_NUM UBI_CTRL_DEV"
//usage:#define ubidetach_full_usage "\n\n"
//usage:       "Detach MTD device from UBI\n"
//usage:     "\n	-d UBI_NUM	UBI device number"
	if (do_detach) {
		if (!(opts & OPTION_d))
			bb_error_msg_and_die("%s device not specified", "UBI");

		/* FIXME? kernel expects int32_t* here: */
		xioctl(fd, UBI_IOCDET, &dev_num);
	} else

//usage:#define ubimkvol_trivial_usage
//usage:       "-N NAME [-s SIZE | -m] UBI_DEVICE"
//usage:#define ubimkvol_full_usage "\n\n"
//usage:       "Create UBI volume\n"
//usage:     "\n	-a ALIGNMENT	Volume alignment (default 1)"
//usage:     "\n	-m		Set volume size to maximum available"
//usage:     "\n	-n VOLID	Volume ID. If not specified,"
//usage:     "\n			assigned automatically"
//usage:     "\n	-N NAME		Volume name"
//usage:     "\n	-s SIZE		Size in bytes"
//usage:     "\n	-t TYPE		Volume type (static|dynamic)"
	if (do_mkvol) {
		if (opts & OPTION_m) {
			unsigned leb_avail;
			unsigned leb_size;
			unsigned num;
			char *p;

			num = ubi_devnum_from_devname(ubi_ctrl);
			p = path_sys_class_ubi_ubi + sprintf(path_sys_class_ubi_ubi, "%u/", num);

			strcpy(p, "avail_eraseblocks");
			leb_avail = get_num_from_file(path, UINT_MAX);

			strcpy(p, "eraseblock_size");
			leb_size = get_num_from_file(path, MAX_SANE_ERASEBLOCK);

			size_bytes = leb_avail * (unsigned long long)leb_size;
			//if (size_bytes <= 0)
			//	bb_error_msg_and_die("%s invalid maximum size calculated", "UBI");
		} else
		if (!(opts & OPTION_s))
			bb_simple_error_msg_and_die("size not specified");

		if (!(opts & OPTION_N))
			bb_simple_error_msg_and_die("name not specified");

		/* the structure is memset(0) above */
		mkvol_req.vol_id = vol_id;
		mkvol_req.vol_type = UBI_DYNAMIC_VOLUME;
		if ((opts & OPTION_t) && type[0] == 's')
			mkvol_req.vol_type = UBI_STATIC_VOLUME;
		mkvol_req.alignment = alignment;
		mkvol_req.bytes = size_bytes; /* signed int64_t */
		/* strnlen avoids overflow of 16-bit field (paranoia) */
		mkvol_req.name_len = strnlen(vol_name, UBI_MAX_VOLUME_NAME+1);
		if (mkvol_req.name_len > UBI_MAX_VOLUME_NAME)
			bb_error_msg_and_die("volume name too long: '%s'", vol_name);
		/* this is safe: .name[] is UBI_MAX_VOLUME_NAME+1 bytes */
		strcpy(mkvol_req.name, vol_name);

		xioctl(fd, UBI_IOCMKVOL, &mkvol_req);
	} else

//usage:#define ubirmvol_trivial_usage
//usage:       "-n VOLID / -N VOLNAME UBI_DEVICE"
//usage:#define ubirmvol_full_usage "\n\n"
//usage:       "Remove UBI volume\n"
//usage:     "\n	-n VOLID	Volume ID"
//usage:     "\n	-N VOLNAME	Volume name"
	if (do_rmvol) {
		if (!(opts & (OPTION_n|OPTION_N)))
			bb_simple_error_msg_and_die("volume id not specified");

		if (opts & OPTION_N) {
			unsigned num = ubi_devnum_from_devname(ubi_ctrl);
			vol_id = ubi_get_volid_by_name(num, vol_name);
		}

		if (sizeof(vol_id) != 4) {
			/* kernel expects int32_t* in this ioctl */
			int32_t t = vol_id;
			xioctl(fd, UBI_IOCRMVOL, &t);
		} else {
			xioctl(fd, UBI_IOCRMVOL, &vol_id);
		}
	} else

//usage:#define ubirsvol_trivial_usage
//usage:       "-n VOLID -s SIZE UBI_DEVICE"
//usage:#define ubirsvol_full_usage "\n\n"
//usage:       "Resize UBI volume\n"
//usage:     "\n	-n VOLID	Volume ID"
//usage:     "\n	-s SIZE		Size in bytes"
	if (do_rsvol) {
		if (!(opts & OPTION_s))
			bb_simple_error_msg_and_die("size not specified");
		if (!(opts & OPTION_n))
			bb_simple_error_msg_and_die("volume id not specified");

		rsvol_req.bytes = size_bytes; /* signed int64_t */
		rsvol_req.vol_id = vol_id;

		xioctl(fd, UBI_IOCRSVOL, &rsvol_req);
	} else

//usage:#define ubiupdatevol_trivial_usage
//usage:       "-t UBI_DEVICE | [-s SIZE] UBI_DEVICE IMG_FILE"
//usage:#define ubiupdatevol_full_usage "\n\n"
//usage:       "Update UBI volume\n"
//usage:     "\n	-t	Truncate to zero size"
//usage:     "\n	-s SIZE	Size in bytes to resize to"
	if (do_update) {
		int64_t bytes64;

		if (opts & OPTION_t) {
			/* truncate the volume by starting an update for size 0 */
			bytes64 = 0;
			/* this ioctl expects int64_t* parameter */
			xioctl(fd, UBI_IOCVOLUP, &bytes64);
		}
		else {
			unsigned ubinum, volnum;
			unsigned leb_size;
			char *buf;

			/* Assume that device is in normal format. */
			/* Removes need for scanning sysfs tree as full libubi does. */
			if (sscanf(ubi_ctrl, "/dev/ubi%u_%u", &ubinum, &volnum) != 2)
				bb_error_msg_and_die("UBI device name '%s' is not /dev/ubiN_M", ubi_ctrl);

			sprintf(path_sys_class_ubi_ubi, "%u_%u/usable_eb_size", ubinum, volnum);
			leb_size = get_num_from_file(path, MAX_SANE_ERASEBLOCK);

			if (!*argv)
				bb_show_usage();
			if (NOT_LONE_DASH(*argv)) /* mtd-utils supports "-" as stdin */
				xmove_fd(xopen(*argv, O_RDONLY), STDIN_FILENO);

			if (!(opts & OPTION_s)) {
				struct stat st;
				xfstat(STDIN_FILENO, &st, *argv);
				size_bytes = st.st_size;
			}

			bytes64 = size_bytes;
			/* this ioctl expects signed int64_t* parameter */
			xioctl(fd, UBI_IOCVOLUP, &bytes64);

			/* can't use bb_copyfd_exact_size(): copy in blocks of exactly leb_size */
			buf = xmalloc(leb_size);
			while (size_bytes != 0) {
				int len = full_read(STDIN_FILENO, buf, leb_size);
				if (len <= 0) {
					if (len < 0)
						bb_perror_msg_and_die("read error from '%s'", *argv);
					break;
				}
				if ((unsigned)len > size_bytes) {
					/* for this case: "ubiupdatevol -s 1024000 $UBIDEV /dev/urandom" */
					len = size_bytes;
				}
				xwrite(fd, buf, len);
				size_bytes -= len;
			}
			if (ENABLE_FEATURE_CLEAN_UP)
				free(buf);
		}
	}

	if (ENABLE_FEATURE_CLEAN_UP)
		close(fd);

	return EXIT_SUCCESS;
}