OpenWrt – Rev 4

Subversion Repositories:
Rev:
/*
 * jffs2 on-disk structure generator for mtd
 *
 * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License v2
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * Based on:
 *   JFFS2 -- Journalling Flash File System, Version 2.
 *   Copyright © 2001-2007 Red Hat, Inc.
 *   Created by David Woodhouse <dwmw2@infradead.org>
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdint.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
#include <endian.h>
#include "jffs2.h"
#include "crc32.h"
#include "mtd.h"

#define PAD(x) (((x)+3)&~3)

#if BYTE_ORDER == BIG_ENDIAN
# define CLEANMARKER "\x19\x85\x20\x03\x00\x00\x00\x0c\xf0\x60\xdc\x98"
#else
# define CLEANMARKER "\x85\x19\x03\x20\x0c\x00\x00\x00\xb1\xb0\x1e\xe4"
#endif

static int last_ino = 0;
static int last_version = 0;
static char *buf = NULL;
static int ofs = 0;
static int outfd = -1;
static int mtdofs = 0;
static int target_ino = 0;

static void prep_eraseblock(void);

static void pad(int size)
{
        if ((ofs % size == 0) && (ofs < erasesize))
                return;

        if (ofs < erasesize) {
                memset(buf + ofs, 0xff, (size - (ofs % size)));
                ofs += (size - (ofs % size));
        }
        ofs = ofs % erasesize;
        if (ofs == 0) {
                while (mtd_block_is_bad(outfd, mtdofs) && (mtdofs < mtdsize)) {
                        if (!quiet)
                                fprintf(stderr, "\nSkipping bad block at 0x%08x   ", mtdofs);

                        mtdofs += erasesize;

                        /* Move the file pointer along over the bad block. */
                        lseek(outfd, erasesize, SEEK_CUR);
                }
                mtd_erase_block(outfd, mtdofs);
                write(outfd, buf, erasesize);
                mtdofs += erasesize;
        }
}

static inline int rbytes(void)
{
        return erasesize - (ofs % erasesize);
}

static inline void add_data(char *ptr, int len)
{
        if (ofs + len > erasesize) {
                pad(erasesize);
                prep_eraseblock();
        }
        memcpy(buf + ofs, ptr, len);
        ofs += len;
}

static void prep_eraseblock(void)
{
        if (ofs > 0)
                return;

        add_data(CLEANMARKER, sizeof(CLEANMARKER) - 1);
}

static int add_dirent(const char *name, const char type, int parent)
{
        struct jffs2_raw_dirent *de;

        if (ofs - erasesize < sizeof(struct jffs2_raw_dirent) + strlen(name))
                pad(erasesize);

        prep_eraseblock();
        last_ino++;
        memset(buf + ofs, 0, sizeof(struct jffs2_raw_dirent));
        de = (struct jffs2_raw_dirent *) (buf + ofs);

        de->magic = JFFS2_MAGIC_BITMASK;
        de->nodetype = JFFS2_NODETYPE_DIRENT;
        de->type = type;
        de->name_crc = crc32(0, name, strlen(name));
        de->ino = last_ino++;
        de->pino = parent;
        de->totlen = sizeof(*de) + strlen(name);
        de->hdr_crc = crc32(0, (void *) de, sizeof(struct jffs2_unknown_node) - 4);
        de->version = last_version++;
        de->mctime = 0;
        de->nsize = strlen(name);
        de->node_crc = crc32(0, (void *) de, sizeof(*de) - 8);
        memcpy(de->name, name, strlen(name));

        ofs += sizeof(struct jffs2_raw_dirent) + de->nsize;
        pad(4);

        return de->ino;
}

static int add_dir(const char *name, int parent)
{
        struct jffs2_raw_inode ri;
        int inode;

        inode = add_dirent(name, IFTODT(S_IFDIR), parent);

        if (rbytes() < sizeof(ri))
                pad(erasesize);
        prep_eraseblock();

        memset(&ri, 0, sizeof(ri));
        ri.magic = JFFS2_MAGIC_BITMASK;
        ri.nodetype = JFFS2_NODETYPE_INODE;
        ri.totlen = sizeof(ri);
        ri.hdr_crc = crc32(0, &ri, sizeof(struct jffs2_unknown_node) - 4);

        ri.ino = inode;
        ri.mode = S_IFDIR | 0755;
        ri.uid = ri.gid = 0;
        ri.atime = ri.ctime = ri.mtime = 0;
        ri.isize = ri.csize = ri.dsize = 0;
        ri.version = 1;
        ri.node_crc = crc32(0, &ri, sizeof(ri) - 8);
        ri.data_crc = 0;

        add_data((char *) &ri, sizeof(ri));
        pad(4);
        return inode;
}

static void add_file(const char *name, int parent)
{
        int inode, f_offset = 0, fd;
        struct jffs2_raw_inode ri;
        struct stat st;
        char wbuf[4096];
        const char *fname;

        if (stat(name, &st)) {
                fprintf(stderr, "File %s does not exist\n", name);
                return;
        }

        fname = strrchr(name, '/');
        if (fname)
                fname++;
        else
                fname = name;

        inode = add_dirent(fname, IFTODT(S_IFREG), parent);
        memset(&ri, 0, sizeof(ri));
        ri.magic = JFFS2_MAGIC_BITMASK;
        ri.nodetype = JFFS2_NODETYPE_INODE;

        ri.ino = inode;
        ri.mode = st.st_mode;
        ri.uid = ri.gid = 0;
        ri.atime = st.st_atime;
        ri.ctime = st.st_ctime;
        ri.mtime = st.st_mtime;
        ri.isize = st.st_size;
        ri.compr = 0;
        ri.usercompr = 0;

        fd = open(name, 0);
        if (fd < 0) {
                fprintf(stderr, "File %s does not exist\n", name);
                return;
        }

        for (;;) {
                int len = 0;

                for (;;) {
                        len = rbytes() - sizeof(ri);
                        if (len > 128)
                                break;

                        pad(erasesize);
                        prep_eraseblock();
                }

                if (len > sizeof(wbuf))
                        len = sizeof(wbuf);

                len = read(fd, wbuf, len);
                if (len <= 0)
                        break;

                ri.totlen = sizeof(ri) + len;
                ri.hdr_crc = crc32(0, &ri, sizeof(struct jffs2_unknown_node) - 4);
                ri.version = ++last_version;
                ri.offset = f_offset;
                ri.csize = ri.dsize = len;
                ri.node_crc = crc32(0, &ri, sizeof(ri) - 8);
                ri.data_crc = crc32(0, wbuf, len);
                f_offset += len;
                add_data((char *) &ri, sizeof(ri));
                add_data(wbuf, len);
                pad(4);
                prep_eraseblock();
        }

        close(fd);
}

int mtd_replace_jffs2(const char *mtd, int fd, int ofs, const char *filename)
{
        outfd = fd;
        mtdofs = ofs;

        buf = malloc(erasesize);
        target_ino = 1;
        if (!last_ino)
                last_ino = 1;
        add_file(filename, target_ino);
        pad(erasesize);

        /* add eof marker, pad to eraseblock size and write the data */
        add_data(JFFS2_EOF, sizeof(JFFS2_EOF) - 1);
        pad(erasesize);
        free(buf);

        return (mtdofs - ofs);
}

void mtd_parse_jffs2data(const char *buf, const char *dir)
{
        struct jffs2_unknown_node *node = (struct jffs2_unknown_node *) buf;
        unsigned int ofs = 0;

        while (ofs < erasesize) {
                node = (struct jffs2_unknown_node *) (buf + ofs);
                if (node->magic != 0x1985)
                        break;

                ofs += PAD(node->totlen);
                if (node->nodetype == JFFS2_NODETYPE_DIRENT) {
                        struct jffs2_raw_dirent *de = (struct jffs2_raw_dirent *) node;

                        /* is this the right directory name and is it a subdirectory of / */
                        if (*dir && (de->pino == 1) && !strncmp((char *) de->name, dir, de->nsize))
                                target_ino = de->ino;

                        /* store the last inode and version numbers for adding extra files */
                        if (last_ino < de->ino)
                                last_ino = de->ino;
                        if (last_version < de->version)
                                last_version = de->version;
                }
        }
}

int mtd_write_jffs2(const char *mtd, const char *filename, const char *dir)
{
        int err = -1, fdeof = 0;

        outfd = mtd_check_open(mtd);
        if (outfd < 0)
                return -1;

        if (quiet < 2)
                fprintf(stderr, "Appending %s to jffs2 partition %s\n", filename, mtd);
        
        buf = malloc(erasesize);
        if (!buf) {
                fprintf(stderr, "Out of memory!\n");
                goto done;
        }

        if (!*dir)
                target_ino = 1;

        /* parse the structure of the jffs2 first
         * locate the directory that the file is going to be placed in */
        for(;;) {
                struct jffs2_unknown_node *node = (struct jffs2_unknown_node *) buf;

                if (read(outfd, buf, erasesize) != erasesize) {
                        fdeof = 1;
                        break;
                }
                mtdofs += erasesize;

                if (node->magic == 0x8519) {
                        fprintf(stderr, "Error: wrong endianness filesystem\n");
                        goto done;
                }

                /* assume  no magic == end of filesystem
                 * the filesystem will probably end with be32(0xdeadc0de) */
                if (node->magic != 0x1985)
                        break;

                mtd_parse_jffs2data(buf, dir);
        }

        if (fdeof) {
                fprintf(stderr, "Error: No room for additional data\n");
                goto done;
        }

        /* jump back one eraseblock */
        mtdofs -= erasesize;
        lseek(outfd, mtdofs, SEEK_SET);

        ofs = 0;

        if (!last_ino)
                last_ino = 1;

        if (!target_ino)
                target_ino = add_dir(dir, 1);

        add_file(filename, target_ino);
        pad(erasesize);

        /* add eof marker, pad to eraseblock size and write the data */
        add_data(JFFS2_EOF, sizeof(JFFS2_EOF) - 1);
        pad(erasesize);

        err = 0;

        if (trx_fixup) {
          trx_fixup(outfd, mtd);
        }

done:
        close(outfd);
        if (buf)
                free(buf);

        return err;
}