libfstools: add basic documentation of mount functions
[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(const char *mtd, int block)
40 {
41         FILE *fp;
42         char dev[PATH_MAX];
43         int i, ret, flags = O_RDWR | O_SYNC;
44
45         if ((fp = fopen("/proc/mtd", "r"))) {
46                 while (fgets(dev, sizeof(dev), fp)) {
47                         if (sscanf(dev, "mtd%d:", &i) && strstr(dev, mtd)) {
48                                 snprintf(dev, sizeof(dev), "/dev/mtd%s/%d", (block ? "block" : ""), i);
49                                 ret = open(dev, flags);
50                                 if (ret < 0) {
51                                         snprintf(dev, sizeof(dev), "/dev/mtd%s%d", (block ? "block" : ""), i);
52                                         ret = open(dev, flags);
53                                 }
54                                 fclose(fp);
55                                 return ret;
56                         }
57                 }
58                 fclose(fp);
59         }
60
61         return open(mtd, flags);
62 }
63
64 static void mtd_volume_close(struct mtd_volume *p)
65 {
66         if (!p->fd)
67                 return;
68
69         close(p->fd);
70         p->fd = 0;
71 }
72
73 static int mtd_volume_load(struct mtd_volume *p)
74 {
75         struct volume *v = &p->v;
76         struct mtd_info_user mtdInfo;
77         struct erase_info_user mtdLockInfo;
78
79         if (p->fd)
80                 return 0;
81
82         if (!p->chr)
83                 return -1;
84
85         p->fd = mtd_open(p->chr, 0);
86         if (p->fd < 0) {
87                 p->fd = 0;
88                 ULOG_ERR("Could not open mtd device: %s\n", p->chr);
89                 return -1;
90         }
91
92         if (ioctl(p->fd, MEMGETINFO, &mtdInfo)) {
93                 mtd_volume_close(p);
94                 ULOG_ERR("Could not get MTD device info from %s\n", p->chr);
95                 return -1;
96         }
97
98         v->size = mtdInfo.size;
99         v->block_size = mtdInfo.erasesize;
100         switch (mtdInfo.type) {
101         case MTD_NORFLASH:
102                 v->type = NORFLASH;
103                 break;
104         case MTD_NANDFLASH:
105                 v->type = NANDFLASH;
106                 break;
107         case MTD_UBIVOLUME:
108                 v->type = UBIVOLUME;
109                 break;
110         default:
111                 v->type = UNKNOWN_TYPE;
112                 break;
113         }
114
115         mtdLockInfo.start = 0;
116         mtdLockInfo.length = v->size;
117         ioctl(p->fd, MEMUNLOCK, &mtdLockInfo);
118
119         return 0;
120 }
121
122 static char* mtd_find_index(char *name)
123 {
124         FILE *fp = fopen("/proc/mtd", "r");
125         static char line[256];
126         char *index = NULL;
127
128         if(!fp)
129                 return index;
130
131         while (!index && fgets(line, sizeof(line), fp)) {
132                 char *ret;
133
134                 if ((ret = strstr(line, name)) && (ret[strlen(name)] == '"')) {
135                         char *eol = strstr(line, ":");
136
137                         if (!eol)
138                                 continue;
139
140                         *eol = '\0';
141                         index = &line[3];
142                 }
143         }
144
145         fclose(fp);
146
147         return index;
148 }
149
150 static struct volume *mtd_volume_find(char *name)
151 {
152         char *idx = mtd_find_index(name);
153         struct mtd_volume *p;
154         struct volume *v;
155         char buffer[32];
156
157         if (!idx)
158                 return NULL;
159
160         p = calloc(1, sizeof(struct mtd_volume));
161         if (!p)
162                 return NULL;
163
164         v = &p->v;
165         v->name = strdup(name);
166         v->drv = &mtd_driver;
167         p->idx = atoi(idx);
168
169         snprintf(buffer, sizeof(buffer), "/dev/mtdblock%s", idx);
170         v->blk = strdup(buffer);
171
172         snprintf(buffer, sizeof(buffer), "/dev/mtd%s", idx);
173         p->chr = strdup(buffer);
174
175         if (mtd_volume_load(p)) {
176                 ULOG_ERR("reading %s failed\n", v->name);
177                 free(p);
178                 return NULL;
179         }
180
181         return v;
182 }
183
184 static int mtd_volume_identify(struct volume *v)
185 {
186         struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
187         __u32 deadc0de;
188         __u16 jffs2;
189         size_t sz;
190
191         if (mtd_volume_load(p)) {
192                 ULOG_ERR("reading %s failed\n", v->name);
193                 return -1;
194         }
195
196         sz = read(p->fd, &deadc0de, sizeof(deadc0de));
197
198         if (sz != sizeof(deadc0de)) {
199                 ULOG_ERR("reading %s failed: %s\n", v->name, strerror(errno));
200                 return -1;
201         }
202
203         if (deadc0de == __be32_to_cpu(0x4f575254))
204                 return FS_SNAPSHOT;
205
206         deadc0de = __be32_to_cpu(deadc0de);
207         if (deadc0de == 0xdeadc0de) {
208                 return FS_DEADCODE;
209         }
210
211         jffs2 = __be16_to_cpu(deadc0de >> 16);
212         if (jffs2 == 0x1985) {
213                 return FS_JFFS2;
214         }
215
216         if (v->type == UBIVOLUME && deadc0de == 0xffffffff) {
217                 return FS_JFFS2;
218         }
219
220         return FS_NONE;
221 }
222
223 static int mtd_volume_erase(struct volume *v, int offset, int len)
224 {
225         struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
226         struct erase_info_user eiu;
227         int first_block, num_blocks;
228
229         if (mtd_volume_load(p))
230                 return -1;
231
232         if (offset % v->block_size || len % v->block_size) {
233                 ULOG_ERR("mtd erase needs to be block aligned\n");
234                 return -1;
235         }
236
237         first_block = offset / v->block_size;
238         num_blocks = len / v->block_size;
239         eiu.length = v->block_size;
240
241         for (eiu.start = first_block * v->block_size;
242                         eiu.start < v->size && eiu.start < (first_block + num_blocks) * v->block_size;
243                         eiu.start += v->block_size) {
244                 ULOG_INFO("erasing %x %x\n", eiu.start, v->block_size);
245                 ioctl(p->fd, MEMUNLOCK, &eiu);
246                 if (ioctl(p->fd, MEMERASE, &eiu))
247                         ULOG_ERR("Failed to erase block at 0x%x\n", eiu.start);
248         }
249
250         mtd_volume_close(p);
251
252         return 0;
253 }
254
255 static int mtd_volume_erase_all(struct volume *v)
256 {
257         struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
258
259         mtd_volume_erase(v, 0, v->size);
260         mtd_volume_close(p);
261
262         return 0;
263 }
264
265 static int mtd_volume_init(struct volume *v)
266 {
267         struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
268         struct mtd_info_user mtdinfo;
269         int ret;
270
271         if (mtd_volume_load(p))
272                 return -1;
273
274         ret = ioctl(p->fd, MEMGETINFO, &mtdinfo);
275         if (ret) {
276                 ULOG_ERR("ioctl(%d, MEMGETINFO) failed: %s\n", p->fd, strerror(errno));
277         } else {
278                 struct erase_info_user mtdlock;
279
280                 mtdlock.start = 0;
281                 mtdlock.length = mtdinfo.size;
282                 ioctl(p->fd, MEMUNLOCK, &mtdlock);
283         }
284
285         return ret;
286 }
287
288 static int mtd_volume_read(struct volume *v, void *buf, int offset, int length)
289 {
290         struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
291
292         if (mtd_volume_load(p))
293                 return -1;
294
295         if (lseek(p->fd, offset, SEEK_SET) == (off_t) -1) {
296                 ULOG_ERR("lseek/read failed\n");
297                 return -1;
298         }
299
300         if (read(p->fd, buf, length) == -1) {
301                 ULOG_ERR("read failed\n");
302                 return -1;
303         }
304
305         return 0;
306 }
307
308 static int mtd_volume_write(struct volume *v, void *buf, int offset, int length)
309 {
310         struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
311
312         if (mtd_volume_load(p))
313                 return -1;
314
315         if (lseek(p->fd, offset, SEEK_SET) == (off_t) -1) {
316                 ULOG_ERR("lseek/write failed at offset %d\n", offset);
317                 perror("lseek");
318                 return -1;
319         }
320
321         if (write(p->fd, buf, length) == -1) {
322                 ULOG_ERR("write failed\n");
323                 return -1;
324         }
325
326         return 0;
327 }
328
329 static struct driver mtd_driver = {
330         .name = "mtd",
331         .find = mtd_volume_find,
332         .init = mtd_volume_init,
333         .erase = mtd_volume_erase,
334         .erase_all = mtd_volume_erase_all,
335         .read = mtd_volume_read,
336         .write = mtd_volume_write,
337         .identify = mtd_volume_identify,
338 };
339 DRIVER(mtd_driver);