| 1 | /* |
| 2 | * jffs2 on-disk structure generator for mtd |
| 3 | * |
| 4 | * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org> |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or |
| 7 | * modify it under the terms of the GNU General Public License v2 |
| 8 | * as published by the Free Software Foundation. |
| 9 | * |
| 10 | * This program is distributed in the hope that it will be useful, |
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | * GNU General Public License for more details. |
| 14 | * Based on: |
| 15 | * JFFS2 -- Journalling Flash File System, Version 2. |
| 16 | * Copyright © 2001-2007 Red Hat, Inc. |
| 17 | * Created by David Woodhouse <dwmw2@infradead.org> |
| 18 | */ |
| 19 | #include <sys/types.h> |
| 20 | #include <sys/stat.h> |
| 21 | #include <stdint.h> |
| 22 | #include <stdio.h> |
| 23 | #include <fcntl.h> |
| 24 | #include <stdlib.h> |
| 25 | #include <string.h> |
| 26 | #include <dirent.h> |
| 27 | #include <unistd.h> |
| 28 | #include <endian.h> |
| 29 | #include "jffs2.h" |
| 30 | #include "crc32.h" |
| 31 | #include "mtd.h" |
| 32 | |
| 33 | #define PAD(x) (((x)+3)&~3) |
| 34 | |
| 35 | #if BYTE_ORDER == BIG_ENDIAN |
| 36 | # define CLEANMARKER "\x19\x85\x20\x03\x00\x00\x00\x0c\xf0\x60\xdc\x98" |
| 37 | #else |
| 38 | # define CLEANMARKER "\x85\x19\x03\x20\x0c\x00\x00\x00\xb1\xb0\x1e\xe4" |
| 39 | #endif |
| 40 | |
| 41 | static int last_ino = 0; |
| 42 | static int last_version = 0; |
| 43 | static char *buf = NULL; |
| 44 | static int ofs = 0; |
| 45 | static int outfd = -1; |
| 46 | static int mtdofs = 0; |
| 47 | static int target_ino = 0; |
| 48 | |
| 49 | static void prep_eraseblock(void); |
| 50 | |
| 51 | static void pad(int size) |
| 52 | { |
| 53 | if ((ofs % size == 0) && (ofs < erasesize)) |
| 54 | return; |
| 55 | |
| 56 | if (ofs < erasesize) { |
| 57 | memset(buf + ofs, 0xff, (size - (ofs % size))); |
| 58 | ofs += (size - (ofs % size)); |
| 59 | } |
| 60 | ofs = ofs % erasesize; |
| 61 | if (ofs == 0) { |
| 62 | mtd_erase_block(outfd, mtdofs); |
| 63 | write(outfd, buf, erasesize); |
| 64 | mtdofs += erasesize; |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | static inline int rbytes(void) |
| 69 | { |
| 70 | return erasesize - (ofs % erasesize); |
| 71 | } |
| 72 | |
| 73 | static inline void add_data(char *ptr, int len) |
| 74 | { |
| 75 | if (ofs + len > erasesize) { |
| 76 | pad(erasesize); |
| 77 | prep_eraseblock(); |
| 78 | } |
| 79 | memcpy(buf + ofs, ptr, len); |
| 80 | ofs += len; |
| 81 | } |
| 82 | |
| 83 | static void prep_eraseblock(void) |
| 84 | { |
| 85 | if (ofs > 0) |
| 86 | return; |
| 87 | |
| 88 | add_data(CLEANMARKER, sizeof(CLEANMARKER) - 1); |
| 89 | } |
| 90 | |
| 91 | static int add_dirent(const char *name, const char type, int parent) |
| 92 | { |
| 93 | struct jffs2_raw_dirent *de; |
| 94 | |
| 95 | if (ofs - erasesize < sizeof(struct jffs2_raw_dirent) + strlen(name)) |
| 96 | pad(erasesize); |
| 97 | |
| 98 | prep_eraseblock(); |
| 99 | last_ino++; |
| 100 | memset(buf + ofs, 0, sizeof(struct jffs2_raw_dirent)); |
| 101 | de = (struct jffs2_raw_dirent *) (buf + ofs); |
| 102 | |
| 103 | de->magic = JFFS2_MAGIC_BITMASK; |
| 104 | de->nodetype = JFFS2_NODETYPE_DIRENT; |
| 105 | de->type = type; |
| 106 | de->name_crc = crc32(0, name, strlen(name)); |
| 107 | de->ino = last_ino++; |
| 108 | de->pino = parent; |
| 109 | de->totlen = sizeof(*de) + strlen(name); |
| 110 | de->hdr_crc = crc32(0, (void *) de, sizeof(struct jffs2_unknown_node) - 4); |
| 111 | de->version = last_version++; |
| 112 | de->mctime = 0; |
| 113 | de->nsize = strlen(name); |
| 114 | de->node_crc = crc32(0, (void *) de, sizeof(*de) - 8); |
| 115 | memcpy(de->name, name, strlen(name)); |
| 116 | |
| 117 | ofs += sizeof(struct jffs2_raw_dirent) + de->nsize; |
| 118 | pad(4); |
| 119 | |
| 120 | return de->ino; |
| 121 | } |
| 122 | |
| 123 | static int add_dir(const char *name, int parent) |
| 124 | { |
| 125 | struct jffs2_raw_inode ri; |
| 126 | int inode; |
| 127 | |
| 128 | inode = add_dirent(name, IFTODT(S_IFDIR), parent); |
| 129 | |
| 130 | if (rbytes() < sizeof(ri)) |
| 131 | pad(erasesize); |
| 132 | prep_eraseblock(); |
| 133 | |
| 134 | memset(&ri, 0, sizeof(ri)); |
| 135 | ri.magic = JFFS2_MAGIC_BITMASK; |
| 136 | ri.nodetype = JFFS2_NODETYPE_INODE; |
| 137 | ri.totlen = sizeof(ri); |
| 138 | ri.hdr_crc = crc32(0, &ri, sizeof(struct jffs2_unknown_node) - 4); |
| 139 | |
| 140 | ri.ino = inode; |
| 141 | ri.mode = S_IFDIR | 0755; |
| 142 | ri.uid = ri.gid = 0; |
| 143 | ri.atime = ri.ctime = ri.mtime = 0; |
| 144 | ri.isize = ri.csize = ri.dsize = 0; |
| 145 | ri.version = 1; |
| 146 | ri.node_crc = crc32(0, &ri, sizeof(ri) - 8); |
| 147 | ri.data_crc = 0; |
| 148 | |
| 149 | add_data((char *) &ri, sizeof(ri)); |
| 150 | pad(4); |
| 151 | return inode; |
| 152 | } |
| 153 | |
| 154 | static void add_file(const char *name, int parent) |
| 155 | { |
| 156 | int inode, f_offset = 0, fd; |
| 157 | struct jffs2_raw_inode ri; |
| 158 | struct stat st; |
| 159 | char wbuf[4096]; |
| 160 | const char *fname; |
| 161 | |
| 162 | if (stat(name, &st)) { |
| 163 | fprintf(stderr, "File %s does not exist\n", name); |
| 164 | return; |
| 165 | } |
| 166 | |
| 167 | fname = strrchr(name, '/'); |
| 168 | if (fname) |
| 169 | fname++; |
| 170 | else |
| 171 | fname = name; |
| 172 | |
| 173 | inode = add_dirent(fname, IFTODT(S_IFREG), parent); |
| 174 | memset(&ri, 0, sizeof(ri)); |
| 175 | ri.magic = JFFS2_MAGIC_BITMASK; |
| 176 | ri.nodetype = JFFS2_NODETYPE_INODE; |
| 177 | |
| 178 | ri.ino = inode; |
| 179 | ri.mode = st.st_mode; |
| 180 | ri.uid = ri.gid = 0; |
| 181 | ri.atime = st.st_atime; |
| 182 | ri.ctime = st.st_ctime; |
| 183 | ri.mtime = st.st_mtime; |
| 184 | ri.isize = st.st_size; |
| 185 | ri.compr = 0; |
| 186 | ri.usercompr = 0; |
| 187 | |
| 188 | fd = open(name, 0); |
| 189 | if (fd < 0) { |
| 190 | fprintf(stderr, "File %s does not exist\n", name); |
| 191 | return; |
| 192 | } |
| 193 | |
| 194 | for (;;) { |
| 195 | int len = 0; |
| 196 | |
| 197 | for (;;) { |
| 198 | len = rbytes() - sizeof(ri); |
| 199 | if (len > 128) |
| 200 | break; |
| 201 | |
| 202 | pad(erasesize); |
| 203 | prep_eraseblock(); |
| 204 | } |
| 205 | |
| 206 | if (len > sizeof(wbuf)) |
| 207 | len = sizeof(wbuf); |
| 208 | |
| 209 | len = read(fd, wbuf, len); |
| 210 | if (len <= 0) |
| 211 | break; |
| 212 | |
| 213 | ri.totlen = sizeof(ri) + len; |
| 214 | ri.hdr_crc = crc32(0, &ri, sizeof(struct jffs2_unknown_node) - 4); |
| 215 | ri.version = ++last_version; |
| 216 | ri.offset = f_offset; |
| 217 | ri.csize = ri.dsize = len; |
| 218 | ri.node_crc = crc32(0, &ri, sizeof(ri) - 8); |
| 219 | ri.data_crc = crc32(0, wbuf, len); |
| 220 | f_offset += len; |
| 221 | add_data((char *) &ri, sizeof(ri)); |
| 222 | add_data(wbuf, len); |
| 223 | pad(4); |
| 224 | prep_eraseblock(); |
| 225 | } |
| 226 | |
| 227 | close(fd); |
| 228 | } |
| 229 | |
| 230 | int mtd_replace_jffs2(const char *mtd, int fd, int ofs, const char *filename) |
| 231 | { |
| 232 | outfd = fd; |
| 233 | mtdofs = ofs; |
| 234 | |
| 235 | buf = malloc(erasesize); |
| 236 | target_ino = 1; |
| 237 | if (!last_ino) |
| 238 | last_ino = 1; |
| 239 | add_file(filename, target_ino); |
| 240 | pad(erasesize); |
| 241 | |
| 242 | /* add eof marker, pad to eraseblock size and write the data */ |
| 243 | add_data(JFFS2_EOF, sizeof(JFFS2_EOF) - 1); |
| 244 | pad(erasesize); |
| 245 | free(buf); |
| 246 | |
| 247 | return (mtdofs - ofs); |
| 248 | } |
| 249 | |
| 250 | void mtd_parse_jffs2data(const char *buf, const char *dir) |
| 251 | { |
| 252 | struct jffs2_unknown_node *node = (struct jffs2_unknown_node *) buf; |
| 253 | unsigned int ofs = 0; |
| 254 | |
| 255 | while (ofs < erasesize) { |
| 256 | node = (struct jffs2_unknown_node *) (buf + ofs); |
| 257 | if (node->magic != 0x1985) |
| 258 | break; |
| 259 | |
| 260 | ofs += PAD(node->totlen); |
| 261 | if (node->nodetype == JFFS2_NODETYPE_DIRENT) { |
| 262 | struct jffs2_raw_dirent *de = (struct jffs2_raw_dirent *) node; |
| 263 | |
| 264 | /* is this the right directory name and is it a subdirectory of / */ |
| 265 | if (*dir && (de->pino == 1) && !strncmp((char *) de->name, dir, de->nsize)) |
| 266 | target_ino = de->ino; |
| 267 | |
| 268 | /* store the last inode and version numbers for adding extra files */ |
| 269 | if (last_ino < de->ino) |
| 270 | last_ino = de->ino; |
| 271 | if (last_version < de->version) |
| 272 | last_version = de->version; |
| 273 | } |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | int mtd_write_jffs2(const char *mtd, const char *filename, const char *dir) |
| 278 | { |
| 279 | int err = -1, fdeof = 0; |
| 280 | |
| 281 | outfd = mtd_check_open(mtd); |
| 282 | if (outfd < 0) |
| 283 | return -1; |
| 284 | |
| 285 | if (quiet < 2) |
| 286 | fprintf(stderr, "Appending %s to jffs2 partition %s\n", filename, mtd); |
| 287 | |
| 288 | buf = malloc(erasesize); |
| 289 | if (!buf) { |
| 290 | fprintf(stderr, "Out of memory!\n"); |
| 291 | goto done; |
| 292 | } |
| 293 | |
| 294 | if (!*dir) |
| 295 | target_ino = 1; |
| 296 | |
| 297 | /* parse the structure of the jffs2 first |
| 298 | * locate the directory that the file is going to be placed in */ |
| 299 | for(;;) { |
| 300 | struct jffs2_unknown_node *node = (struct jffs2_unknown_node *) buf; |
| 301 | |
| 302 | if (read(outfd, buf, erasesize) != erasesize) { |
| 303 | fdeof = 1; |
| 304 | break; |
| 305 | } |
| 306 | mtdofs += erasesize; |
| 307 | |
| 308 | if (node->magic == 0x8519) { |
| 309 | fprintf(stderr, "Error: wrong endianness filesystem\n"); |
| 310 | goto done; |
| 311 | } |
| 312 | |
| 313 | /* assume no magic == end of filesystem |
| 314 | * the filesystem will probably end with be32(0xdeadc0de) */ |
| 315 | if (node->magic != 0x1985) |
| 316 | break; |
| 317 | |
| 318 | mtd_parse_jffs2data(buf, dir); |
| 319 | } |
| 320 | |
| 321 | if (fdeof) { |
| 322 | fprintf(stderr, "Error: No room for additional data\n"); |
| 323 | goto done; |
| 324 | } |
| 325 | |
| 326 | /* jump back one eraseblock */ |
| 327 | mtdofs -= erasesize; |
| 328 | lseek(outfd, mtdofs, SEEK_SET); |
| 329 | |
| 330 | ofs = 0; |
| 331 | |
| 332 | if (!last_ino) |
| 333 | last_ino = 1; |
| 334 | |
| 335 | if (!target_ino) |
| 336 | target_ino = add_dir(dir, 1); |
| 337 | |
| 338 | add_file(filename, target_ino); |
| 339 | pad(erasesize); |
| 340 | |
| 341 | /* add eof marker, pad to eraseblock size and write the data */ |
| 342 | add_data(JFFS2_EOF, sizeof(JFFS2_EOF) - 1); |
| 343 | pad(erasesize); |
| 344 | |
| 345 | err = 0; |
| 346 | |
| 347 | if (trx_fixup) { |
| 348 | trx_fixup(outfd, mtd); |
| 349 | } |
| 350 | |
| 351 | done: |
| 352 | close(outfd); |
| 353 | if (buf) |
| 354 | free(buf); |
| 355 | |
| 356 | return err; |
| 357 | } |
| 358 | |