xref: /OK3568_Linux_fs/external/recovery/mtdutils/mtdutils.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <unistd.h>
21 #include <fcntl.h>
22 #include <errno.h>
23 #include <sys/mount.h>  // for _IOW, _IOR, mount()
24 #include <sys/stat.h>
25 #include <mtd/mtd-user.h>
26 #undef NDEBUG
27 #include <assert.h>
28 
29 #include "mtdutils.h"
30 
31 struct MtdReadContext {
32     const MtdPartition *partition;
33     char *buffer;
34     size_t consumed;
35     int fd;
36 };
37 
38 struct MtdWriteContext {
39     const MtdPartition *partition;
40     char *buffer;
41     size_t stored;
42     int fd;
43 
44     off_t* bad_block_offsets;
45     int bad_block_alloc;
46     int bad_block_count;
47 };
48 
49 typedef struct {
50     MtdPartition *partitions;
51     int partitions_allocd;
52     int partition_count;
53 } MtdState;
54 
55 static MtdState g_mtd_state = {
56     NULL,   // partitions
57     0,      // partitions_allocd
58     -1      // partition_count
59 };
60 
61 #define MTD_PROC_FILENAME   "/proc/mtd"
62 
63 int
mtd_scan_partitions()64 mtd_scan_partitions()
65 {
66     char buf[2048];
67     const char *bufp;
68     int fd;
69     int i;
70     ssize_t nbytes;
71 
72     if (g_mtd_state.partitions == NULL) {
73         const int nump = 32;
74         MtdPartition *partitions = malloc(nump * sizeof(*partitions));
75         if (partitions == NULL) {
76             errno = ENOMEM;
77             return -1;
78         }
79         g_mtd_state.partitions = partitions;
80         g_mtd_state.partitions_allocd = nump;
81         memset(partitions, 0, nump * sizeof(*partitions));
82     }
83     g_mtd_state.partition_count = 0;
84 
85     /* Initialize all of the entries to make things easier later.
86      * (Lets us handle sparsely-numbered partitions, which
87      * may not even be possible.)
88      */
89     for (i = 0; i < g_mtd_state.partitions_allocd; i++) {
90         MtdPartition *p = &g_mtd_state.partitions[i];
91         if (p->name != NULL) {
92             free(p->name);
93             p->name = NULL;
94         }
95         p->device_index = -1;
96     }
97 
98     /* Open and read the file contents.
99      */
100     fd = open(MTD_PROC_FILENAME, O_RDONLY);
101     if (fd < 0) {
102         goto bail;
103     }
104     nbytes = read(fd, buf, sizeof(buf) - 1);
105     close(fd);
106     if (nbytes < 0) {
107         goto bail;
108     }
109     buf[nbytes] = '\0';
110 
111     /* Parse the contents of the file, which looks like:
112      *
113      *     # cat /proc/mtd
114      *     dev:    size   erasesize  name
115      *     mtd0: 00080000 00020000 "bootloader"
116      *     mtd1: 00400000 00020000 "mfg_and_gsm"
117      *     mtd2: 00400000 00020000 "0000000c"
118      *     mtd3: 00200000 00020000 "0000000d"
119      *     mtd4: 04000000 00020000 "system"
120      *     mtd5: 03280000 00020000 "userdata"
121      */
122     bufp = buf;
123     while (nbytes > 0) {
124         int mtdnum, mtdsize, mtderasesize;
125         int matches;
126         char mtdname[64];
127         mtdname[0] = '\0';
128         mtdnum = -1;
129 
130         matches = sscanf(bufp, "mtd%d: %x %x \"%63[^\"]",
131                          &mtdnum, &mtdsize, &mtderasesize, mtdname);
132         /* This will fail on the first line, which just contains
133          * column headers.
134          */
135         if (matches == 4) {
136             MtdPartition *p = &g_mtd_state.partitions[mtdnum];
137             p->device_index = mtdnum;
138             p->size = mtdsize;
139             p->erase_size = mtderasesize;
140             p->name = strdup(mtdname);
141             if (p->name == NULL) {
142                 errno = ENOMEM;
143                 goto bail;
144             }
145             g_mtd_state.partition_count++;
146         }
147 
148         /* Eat the line.
149          */
150         while (nbytes > 0 && *bufp != '\n') {
151             bufp++;
152             nbytes--;
153         }
154         if (nbytes > 0) {
155             bufp++;
156             nbytes--;
157         }
158     }
159 
160     return g_mtd_state.partition_count;
161 
162 bail:
163     // keep "partitions" around so we can free the names on a rescan.
164     g_mtd_state.partition_count = -1;
165     return -1;
166 }
167 
168 const MtdPartition *
mtd_find_partition_by_name(const char * name)169 mtd_find_partition_by_name(const char *name)
170 {
171     if (g_mtd_state.partitions != NULL) {
172         int i;
173         for (i = 0; i < g_mtd_state.partitions_allocd; i++) {
174             MtdPartition *p = &g_mtd_state.partitions[i];
175             if (p->device_index >= 0 && p->name != NULL) {
176                 if (strcmp(p->name, name) == 0) {
177                     return p;
178                 }
179             }
180         }
181     }
182     return NULL;
183 }
184 
185 int
mtd_mount_partition(const MtdPartition * partition,const char * mount_point,const char * filesystem,int read_only)186 mtd_mount_partition(const MtdPartition *partition, const char *mount_point,
187                     const char *filesystem, int read_only)
188 {
189     const unsigned long flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME;
190     char devname[64];
191     int rv = -1;
192 
193     sprintf(devname, "/dev/block/mtdblock%d", partition->device_index);
194     if (!read_only) {
195         rv = mount(devname, mount_point, filesystem, flags, NULL);
196     }
197     if (read_only || rv < 0) {
198         rv = mount(devname, mount_point, filesystem, flags | MS_RDONLY, 0);
199         if (rv < 0) {
200             printf("Failed to mount %s on %s: %s\n",
201                    devname, mount_point, strerror(errno));
202         } else {
203             printf("Mount %s on %s read-only\n", devname, mount_point);
204         }
205     }
206 #if 1   //TODO: figure out why this is happening; remove include of stat.h
207     if (rv >= 0) {
208         /* For some reason, the x bits sometimes aren't set on the root
209          * of mounted volumes.
210          */
211         struct stat st;
212         rv = stat(mount_point, &st);
213         if (rv < 0) {
214             return rv;
215         }
216         mode_t new_mode = st.st_mode | S_IXUSR | S_IXGRP | S_IXOTH;
217         if (new_mode != st.st_mode) {
218             printf("Fixing execute permissions for %s\n", mount_point);
219             rv = chmod(mount_point, new_mode);
220             if (rv < 0) {
221                 printf("Couldn't fix permissions for %s: %s\n",
222                        mount_point, strerror(errno));
223             }
224         }
225     }
226 #endif
227     return rv;
228 }
229 
mtd_get_flash_info(size_t * total_size,size_t * block_size,size_t * page_size)230 int mtd_get_flash_info(size_t *total_size, size_t *block_size, size_t *page_size)
231 {
232     int fd = open("/dev/mtd0", O_RDONLY);
233     if (fd < 0) {
234         fprintf(stderr, "mtd: open /dev/mtd0 (%s)\n", strerror(errno));
235         return -1;
236     }
237 
238     struct mtd_info_user mtd_info;
239     int ret = ioctl(fd, MEMGETINFO, &mtd_info);
240     close(fd);
241     if (ret < 0) {
242         fprintf(stderr, "mtd: ioctl MEMGETINFO (%s)\n", strerror(errno));
243         return -1;
244     }
245 
246     printf("mtd_info.size = [%d]\n", mtd_info.size);
247     printf("mtd_info.erasesize = [%d]\n", mtd_info.erasesize);
248     printf("mtd_info.writesize = [%d]\n", mtd_info.writesize);
249     if (total_size) *total_size = mtd_info.size;
250     if (block_size) *block_size = mtd_info.erasesize;
251     if (page_size) *page_size = mtd_info.writesize;
252     return 0;
253 }
254 
255 int
mtd_partition_info(const MtdPartition * partition,size_t * total_size,size_t * erase_size,size_t * write_size)256 mtd_partition_info(const MtdPartition *partition,
257                    size_t *total_size, size_t *erase_size, size_t *write_size)
258 {
259     char mtddevname[32];
260     //sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
261     sprintf(mtddevname, "/dev/mtd%d", partition->device_index);
262     int fd = open(mtddevname, O_RDONLY);
263     if (fd < 0) return -1;
264 
265     struct mtd_info_user mtd_info;
266     int ret = ioctl(fd, MEMGETINFO, &mtd_info);
267     close(fd);
268     if (ret < 0) return -1;
269 
270     if (total_size != NULL) *total_size = mtd_info.size;
271     if (erase_size != NULL) *erase_size = mtd_info.erasesize;
272     if (write_size != NULL) *write_size = mtd_info.writesize;
273     return 0;
274 }
275 
mtd_read_partition(const MtdPartition * partition)276 MtdReadContext *mtd_read_partition(const MtdPartition *partition)
277 {
278     MtdReadContext *ctx = (MtdReadContext*) malloc(sizeof(MtdReadContext));
279     if (ctx == NULL) return NULL;
280 
281     ctx->buffer = malloc(partition->erase_size);
282     if (ctx->buffer == NULL) {
283         free(ctx);
284         return NULL;
285     }
286 
287     char mtddevname[32];
288     sprintf(mtddevname, "/dev/mtd%d", partition->device_index);
289     //sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
290     ctx->fd = open(mtddevname, O_RDONLY);
291     if (ctx->fd < 0) {
292         free(ctx);
293         free(ctx->buffer);
294         return NULL;
295     }
296 
297     ctx->partition = partition;
298     ctx->consumed = partition->erase_size;
299     return ctx;
300 }
301 
302 // Seeks to a location in the partition.  Don't mix with reads of
303 // anything other than whole blocks; unpredictable things will result.
mtd_read_skip_to(const MtdReadContext * ctx,size_t offset)304 void mtd_read_skip_to(const MtdReadContext* ctx, size_t offset)
305 {
306     lseek64(ctx->fd, offset, SEEK_SET);
307 }
308 
read_block(const MtdPartition * partition,int fd,char * data)309 static int read_block(const MtdPartition *partition, int fd, char *data)
310 {
311     struct mtd_ecc_stats before, after;
312     if (ioctl(fd, ECCGETSTATS, &before)) {
313         fprintf(stderr, "mtd: ECCGETSTATS error (%s)\n", strerror(errno));
314         return -1;
315     }
316 
317     loff_t pos = lseek64(fd, 0, SEEK_CUR);
318 
319     ssize_t size = partition->erase_size;
320     int mgbb;
321 
322     while (pos + size <= (int) partition->size) {
323         if (lseek64(fd, pos, SEEK_SET) != pos || read(fd, data, size) != size) {
324             fprintf(stderr, "mtd: read error at 0x%08llx (%s)\n",
325                     pos, strerror(errno));
326         } else if (ioctl(fd, ECCGETSTATS, &after)) {
327             fprintf(stderr, "mtd: ECCGETSTATS error (%s)\n", strerror(errno));
328             return -1;
329         } else if (after.failed != before.failed) {
330             fprintf(stderr, "mtd: ECC errors (%d soft, %d hard) at 0x%08llx\n",
331                     after.corrected - before.corrected,
332                     after.failed - before.failed, pos);
333             // copy the comparison baseline for the next read.
334             memcpy(&before, &after, sizeof(struct mtd_ecc_stats));
335         } else if ((mgbb = ioctl(fd, MEMGETBADBLOCK, &pos))) {
336             fprintf(stderr,
337                     "mtd: MEMGETBADBLOCK returned %d at 0x%08llx (errno=%d)\n",
338                     mgbb, pos, errno);
339         } else {
340             return 0;  // Success!
341         }
342 
343         pos += partition->erase_size;
344     }
345 
346     errno = ENOSPC;
347     return -1;
348 }
349 
mtd_read_data(MtdReadContext * ctx,char * data,size_t len)350 ssize_t mtd_read_data(MtdReadContext *ctx, char *data, size_t len)
351 {
352     ssize_t read = 0;
353     while (read < (int) len) {
354         if (ctx->consumed < ctx->partition->erase_size) {
355             size_t avail = ctx->partition->erase_size - ctx->consumed;
356             size_t copy = len - read < avail ? len - read : avail;
357             memcpy(data + read, ctx->buffer + ctx->consumed, copy);
358             ctx->consumed += copy;
359             read += copy;
360         }
361 
362         // Read complete blocks directly into the user's buffer
363         while (ctx->consumed == ctx->partition->erase_size &&
364                len - read >= ctx->partition->erase_size) {
365             if (read_block(ctx->partition, ctx->fd, data + read)) return -1;
366             read += ctx->partition->erase_size;
367         }
368 
369         if (read >= len) {
370             return read;
371         }
372 
373         // Read the next block into the buffer
374         if (ctx->consumed == ctx->partition->erase_size && read < (int) len) {
375             if (read_block(ctx->partition, ctx->fd, ctx->buffer)) return -1;
376             ctx->consumed = 0;
377         }
378     }
379 
380     return read;
381 }
382 
mtd_read_close(MtdReadContext * ctx)383 void mtd_read_close(MtdReadContext *ctx)
384 {
385     close(ctx->fd);
386     free(ctx->buffer);
387     free(ctx);
388 }
389 
mtd_write_partition(const MtdPartition * partition)390 MtdWriteContext *mtd_write_partition(const MtdPartition *partition)
391 {
392     MtdWriteContext *ctx = (MtdWriteContext*) malloc(sizeof(MtdWriteContext));
393     if (ctx == NULL) return NULL;
394 
395     ctx->bad_block_offsets = NULL;
396     ctx->bad_block_alloc = 0;
397     ctx->bad_block_count = 0;
398 
399     ctx->buffer = malloc(partition->erase_size);
400     if (ctx->buffer == NULL) {
401         free(ctx);
402         return NULL;
403     }
404 
405     char mtddevname[32];
406     sprintf(mtddevname, "/dev/mtd%d", partition->device_index);
407     //sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
408     ctx->fd = open(mtddevname, O_RDWR);
409     if (ctx->fd < 0) {
410         free(ctx->buffer);
411         free(ctx);
412         return NULL;
413     }
414 
415     ctx->partition = partition;
416     ctx->stored = 0;
417     return ctx;
418 }
419 
add_bad_block_offset(MtdWriteContext * ctx,off_t pos)420 static void add_bad_block_offset(MtdWriteContext *ctx, off_t pos)
421 {
422     if (ctx->bad_block_count + 1 > ctx->bad_block_alloc) {
423         ctx->bad_block_alloc = (ctx->bad_block_alloc * 2) + 1;
424         ctx->bad_block_offsets = realloc(ctx->bad_block_offsets,
425                                          ctx->bad_block_alloc * sizeof(off_t));
426     }
427     ctx->bad_block_offsets[ctx->bad_block_count++] = pos;
428 }
429 
write_block(MtdWriteContext * ctx,const char * data)430 static int write_block(MtdWriteContext *ctx, const char *data)
431 {
432     const MtdPartition *partition = ctx->partition;
433     int fd = ctx->fd;
434 
435     off_t pos = lseek(fd, 0, SEEK_CUR);
436     if (pos == (off_t) -1) return 1;
437 
438     ssize_t size = partition->erase_size;
439     while (pos + size <= (int) partition->size) {
440         loff_t bpos = pos;
441         int ret = ioctl(fd, MEMGETBADBLOCK, &bpos);
442         if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) {
443             add_bad_block_offset(ctx, pos);
444             fprintf(stderr,
445                     "mtd: not writing bad block at 0x%08lx (ret %d errno %d)\n",
446                     pos, ret, errno);
447             pos += partition->erase_size;
448             continue;  // Don't try to erase known factory-bad blocks.
449         }
450 
451         struct erase_info_user erase_info;
452         erase_info.start = pos;
453         erase_info.length = size;
454         int retry;
455         for (retry = 0; retry < 2; ++retry) {
456             if (ioctl(fd, MEMERASE, &erase_info) < 0) {
457                 fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n",
458                         pos, strerror(errno));
459                 continue;
460             }
461             if (lseek(fd, pos, SEEK_SET) != pos ||
462                 write(fd, data, size) != size) {
463                 fprintf(stderr, "mtd: write error at 0x%08lx (%s)\n",
464                         pos, strerror(errno));
465             }
466 
467             char verify[size];
468             if (lseek(fd, pos, SEEK_SET) != pos ||
469                 read(fd, verify, size) != size) {
470                 fprintf(stderr, "mtd: re-read error at 0x%08lx (%s)\n",
471                         pos, strerror(errno));
472                 continue;
473             }
474             if (memcmp(data, verify, size) != 0) {
475                 fprintf(stderr, "mtd: verification error at 0x%08lx (%s)\n",
476                         pos, strerror(errno));
477                 continue;
478             }
479 
480             if (retry > 0) {
481                 fprintf(stderr, "mtd: wrote block after %d retries\n", retry);
482             }
483             fprintf(stderr, "mtd: successfully wrote block at %llx\n", pos);
484             return 0;  // Success!
485         }
486 
487         // Try to erase it once more as we give up on this block
488         add_bad_block_offset(ctx, pos);
489         fprintf(stderr, "mtd: skipping write block at 0x%08lx\n", pos);
490         ioctl(fd, MEMERASE, &erase_info);
491         pos += partition->erase_size;
492     }
493 
494     // Ran out of space on the device
495     errno = ENOSPC;
496     return -1;
497 }
498 
mtd_write_data(MtdWriteContext * ctx,const char * data,size_t len)499 ssize_t mtd_write_data(MtdWriteContext *ctx, const char *data, size_t len)
500 {
501     size_t wrote = 0;
502     while (wrote < len) {
503         // Coalesce partial writes into complete blocks
504         if (ctx->stored > 0 || len - wrote < ctx->partition->erase_size) {
505             size_t avail = ctx->partition->erase_size - ctx->stored;
506             size_t copy = len - wrote < avail ? len - wrote : avail;
507             memcpy(ctx->buffer + ctx->stored, data + wrote, copy);
508             ctx->stored += copy;
509             wrote += copy;
510         }
511 
512         // If a complete block was accumulated, write it
513         if (ctx->stored == ctx->partition->erase_size) {
514             if (write_block(ctx, ctx->buffer)) return -1;
515             ctx->stored = 0;
516         }
517 
518         // Write complete blocks directly from the user's buffer
519         while (ctx->stored == 0 && len - wrote >= ctx->partition->erase_size) {
520             if (write_block(ctx, data + wrote)) return -1;
521             wrote += ctx->partition->erase_size;
522         }
523     }
524 
525     return wrote;
526 }
527 
mtd_erase_blocks(MtdWriteContext * ctx,int blocks)528 off_t mtd_erase_blocks(MtdWriteContext *ctx, int blocks)
529 {
530     // Zero-pad and write any pending data to get us to a block boundary
531     if (ctx->stored > 0) {
532         size_t zero = ctx->partition->erase_size - ctx->stored;
533         memset(ctx->buffer + ctx->stored, 0, zero);
534         if (write_block(ctx, ctx->buffer)) return -1;
535         ctx->stored = 0;
536     }
537 
538     off_t pos = lseek(ctx->fd, 0, SEEK_CUR);
539     if ((off_t) pos == (off_t) -1) return pos;
540 
541     const int total = (ctx->partition->size - pos) / ctx->partition->erase_size;
542     if (blocks < 0) blocks = total;
543     if (blocks > total) {
544         errno = ENOSPC;
545         return -1;
546     }
547 
548     // Erase the specified number of blocks
549     while (blocks-- > 0) {
550         loff_t bpos = pos;
551         if (ioctl(ctx->fd, MEMGETBADBLOCK, &bpos) > 0) {
552             fprintf(stderr, "mtd: not erasing bad block at 0x%08lx\n", pos);
553             pos += ctx->partition->erase_size;
554             continue;  // Don't try to erase known factory-bad blocks.
555         }
556 
557         struct erase_info_user erase_info;
558         erase_info.start = pos;
559         erase_info.length = ctx->partition->erase_size;
560         if (ioctl(ctx->fd, MEMERASE, &erase_info) < 0) {
561             fprintf(stderr, "mtd: erase failure at 0x%08lx\n", pos);
562         }
563         pos += ctx->partition->erase_size;
564     }
565 
566     return pos;
567 }
568 
mtd_write_close(MtdWriteContext * ctx)569 int mtd_write_close(MtdWriteContext *ctx)
570 {
571     int r = 0;
572     // Make sure any pending data gets written
573     if (mtd_erase_blocks(ctx, 0) == (off_t) -1) r = -1;
574     if (close(ctx->fd)) r = -1;
575     free(ctx->bad_block_offsets);
576     free(ctx->buffer);
577     free(ctx);
578     return r;
579 }
580 
581 /* Return the offset of the first good block at or after pos (which
582  * might be pos itself).
583  */
mtd_find_write_start(MtdWriteContext * ctx,off_t pos)584 off_t mtd_find_write_start(MtdWriteContext *ctx, off_t pos)
585 {
586     int i;
587     for (i = 0; i < ctx->bad_block_count; ++i) {
588         if (ctx->bad_block_offsets[i] == pos) {
589             pos += ctx->partition->erase_size;
590         } else if (ctx->bad_block_offsets[i] > pos) {
591             return pos;
592         }
593     }
594     return pos;
595 }
596