fstools: Add support to read-only MTD partitions (eg. recovery images)
[oweals/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);