fstools: Add support to read-only MTD partitions (eg. recovery images)
[project/fstools.git] / libfstools / mtd.c
1 /*
2 * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License version 2.1
6 * as published by the Free Software Foundation
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14 #include <sys/mount.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <fcntl.h>
18 #include <asm/byteorder.h>
19 #include <unistd.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <mtd/mtd-user.h>
23
24 #include "libfstools.h"
25
26 #include "volume.h"
27
28 #define PATH_MAX 256
29
30 struct mtd_volume {
31 struct volume v;
32 int fd;
33 int idx;
34 char *chr;
35 };
36
37 static struct driver mtd_driver;
38
39 static int mtd_open_device(const char *dev)
40 {
41 int ret;
42
43 ret = open(dev, O_RDWR | O_SYNC);
44 if (ret < 0)
45 ret = open(dev, O_RDONLY);
46
47 return ret;
48 }
49
50 static int mtd_open(const char *mtd, int block)
51 {
52 FILE *fp;
53 char dev[PATH_MAX];
54 int i, ret;
55
56 if ((fp = fopen("/proc/mtd", "r"))) {
57 while (fgets(dev, sizeof(dev), fp)) {
58 if (sscanf(dev, "mtd%d:", &i) && strstr(dev, mtd)) {
59 snprintf(dev, sizeof(dev), "/dev/mtd%s/%d", (block ? "block" : ""), i);
60 ret = mtd_open_device(dev);
61 if (ret < 0) {
62 snprintf(dev, sizeof(dev), "/dev/mtd%s%d", (block ? "block" : ""), i);
63 ret = mtd_open_device(dev);
64 }
65 fclose(fp);
66 return ret;
67 }
68 }
69 fclose(fp);
70 }
71
72 return mtd_open_device(mtd);
73 }
74
75 static void mtd_volume_close(struct mtd_volume *p)
76 {
77 if (!p->fd)
78 return;
79
80 close(p->fd);
81 p->fd = 0;
82 }
83
84 static int mtd_volume_load(struct mtd_volume *p)
85 {
86 struct volume *v = &p->v;
87 struct mtd_info_user mtdInfo;
88 struct erase_info_user mtdLockInfo;
89
90 if (p->fd) {
91 lseek(p->fd, 0, SEEK_SET);
92 return 0;
93 }
94
95 if (!p->chr)
96 return -1;
97
98 p->fd = mtd_open(p->chr, 0);
99 if (p->fd < 0) {
100 p->fd = 0;
101 ULOG_ERR("Could not open mtd device: %s\n", p->chr);
102 return -1;
103 }
104
105 if (ioctl(p->fd, MEMGETINFO, &mtdInfo)) {
106 mtd_volume_close(p);
107 ULOG_ERR("Could not get MTD device info from %s\n", p->chr);
108 return -1;
109 }
110
111 v->size = mtdInfo.size;
112 v->block_size = mtdInfo.erasesize;
113 switch (mtdInfo.type) {
114 case MTD_NORFLASH:
115 v->type = NORFLASH;
116 break;
117 case MTD_NANDFLASH:
118 v->type = NANDFLASH;
119 break;
120 case MTD_UBIVOLUME:
121 v->type = UBIVOLUME;
122 break;
123 default:
124 v->type = UNKNOWN_TYPE;
125 break;
126 }
127
128 mtdLockInfo.start = 0;
129 mtdLockInfo.length = v->size;
130 ioctl(p->fd, MEMUNLOCK, &mtdLockInfo);
131
132 return 0;
133 }
134
135 static char* mtd_find_index(char *name)
136 {
137 FILE *fp = fopen("/proc/mtd", "r");
138 static char line[256];
139 char *index = NULL;
140
141 if(!fp)
142 return index;
143
144 while (!index && fgets(line, sizeof(line), fp)) {
145 char *ret;
146
147 if ((ret = strstr(line, name)) && (ret[strlen(name)] == '"')) {
148 char *eol = strstr(line, ":");
149
150 if (!eol)
151 continue;
152
153 *eol = '\0';
154 index = &line[3];
155 }
156 }
157
158 fclose(fp);
159
160 return index;
161 }
162
163 static struct volume *mtd_volume_find(char *name)
164 {
165 char *idx = mtd_find_index(name);
166 struct mtd_volume *p;
167 struct volume *v;
168 char buffer[32];
169
170 if (!idx)
171 return NULL;
172
173 p = calloc(1, sizeof(struct mtd_volume));
174 if (!p)
175 return NULL;
176
177 v = &p->v;
178 v->name = strdup(name);
179 v->drv = &mtd_driver;
180 p->idx = atoi(idx);
181
182 snprintf(buffer, sizeof(buffer), "/dev/mtdblock%s", idx);
183 v->blk = strdup(buffer);
184
185 snprintf(buffer, sizeof(buffer), "/dev/mtd%s", idx);
186 p->chr = strdup(buffer);
187
188 if (mtd_volume_load(p)) {
189 ULOG_ERR("reading %s failed\n", v->name);
190 free(p);
191 return NULL;
192 }
193
194 return v;
195 }
196
197 static int mtd_volume_identify(struct volume *v)
198 {
199 struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
200 __u32 deadc0de;
201 __u16 jffs2;
202 size_t sz;
203
204 if (mtd_volume_load(p)) {
205 ULOG_ERR("reading %s failed\n", v->name);
206 return -1;
207 }
208
209 sz = read(p->fd, &deadc0de, sizeof(deadc0de));
210
211 if (sz != sizeof(deadc0de)) {
212 ULOG_ERR("reading %s failed: %m\n", v->name);
213 return -1;
214 }
215
216 if (deadc0de == __be32_to_cpu(0x4f575254))
217 return FS_SNAPSHOT;
218
219 deadc0de = __be32_to_cpu(deadc0de);
220 if (deadc0de == 0xdeadc0de) {
221 return FS_DEADCODE;
222 }
223
224 jffs2 = __be16_to_cpu(deadc0de >> 16);
225 if (jffs2 == 0x1985) {
226 return FS_JFFS2;
227 }
228
229 if (v->type == UBIVOLUME && deadc0de == 0xffffffff) {
230 return FS_JFFS2;
231 }
232
233 return FS_NONE;
234 }
235
236 static int mtd_volume_erase(struct volume *v, int offset, int len)
237 {
238 struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
239 struct erase_info_user eiu;
240 int first_block, num_blocks;
241
242 if (mtd_volume_load(p))
243 return -1;
244
245 if (offset % v->block_size || len % v->block_size) {
246 ULOG_ERR("mtd erase needs to be block aligned\n");
247 return -1;
248 }
249
250 first_block = offset / v->block_size;
251 num_blocks = len / v->block_size;
252 eiu.length = v->block_size;
253
254 for (eiu.start = first_block * v->block_size;
255 eiu.start < v->size && eiu.start < (first_block + num_blocks) * v->block_size;
256 eiu.start += v->block_size) {
257 ULOG_INFO("erasing %x %x\n", eiu.start, v->block_size);
258 ioctl(p->fd, MEMUNLOCK, &eiu);
259 if (ioctl(p->fd, MEMERASE, &eiu))
260 ULOG_ERR("Failed to erase block at 0x%x\n", eiu.start);
261 }
262
263 mtd_volume_close(p);
264
265 return 0;
266 }
267
268 static int mtd_volume_erase_all(struct volume *v)
269 {
270 struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
271
272 mtd_volume_erase(v, 0, v->size);
273 mtd_volume_close(p);
274
275 return 0;
276 }
277
278 static int mtd_volume_init(struct volume *v)
279 {
280 struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
281 struct mtd_info_user mtdinfo;
282 int ret;
283
284 if (mtd_volume_load(p))
285 return -1;
286
287 ret = ioctl(p->fd, MEMGETINFO, &mtdinfo);
288 if (ret) {
289 ULOG_ERR("ioctl(%d, MEMGETINFO) failed: %m\n", p->fd);
290 } else {
291 struct erase_info_user mtdlock;
292
293 mtdlock.start = 0;
294 mtdlock.length = mtdinfo.size;
295 ioctl(p->fd, MEMUNLOCK, &mtdlock);
296 }
297
298 return ret;
299 }
300
301 static int mtd_volume_read(struct volume *v, void *buf, int offset, int length)
302 {
303 struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
304
305 if (mtd_volume_load(p))
306 return -1;
307
308 if (lseek(p->fd, offset, SEEK_SET) == (off_t) -1) {
309 ULOG_ERR("lseek/read failed\n");
310 return -1;
311 }
312
313 if (read(p->fd, buf, length) == -1) {
314 ULOG_ERR("read failed\n");
315 return -1;
316 }
317
318 return 0;
319 }
320
321 static int mtd_volume_write(struct volume *v, void *buf, int offset, int length)
322 {
323 struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
324
325 if (mtd_volume_load(p))
326 return -1;
327
328 if (lseek(p->fd, offset, SEEK_SET) == (off_t) -1) {
329 ULOG_ERR("lseek/write failed at offset %d\n", offset);
330 perror("lseek");
331 return -1;
332 }
333
334 if (write(p->fd, buf, length) == -1) {
335 ULOG_ERR("write failed\n");
336 return -1;
337 }
338
339 return 0;
340 }
341
342 static struct driver mtd_driver = {
343 .name = "mtd",
344 .find = mtd_volume_find,
345 .init = mtd_volume_init,
346 .erase = mtd_volume_erase,
347 .erase_all = mtd_volume_erase_all,
348 .read = mtd_volume_read,
349 .write = mtd_volume_write,
350 .identify = mtd_volume_identify,
351 };
352 DRIVER(mtd_driver);