| /* vi: set sw=4 ts=4: */ |
| /* |
| * Build a JFFS2 image in a file, from a given directory tree. |
| * |
| * Copyright 2001, 2002 Red Hat, Inc. |
| * 2001 David A. Schleef <ds@lineo.com> |
| * 2002 Axis Communications AB |
| * 2001, 2002 Erik Andersen <andersen@codepoet.org> |
| * 2004 University of Szeged, Hungary |
| * 2006 KaiGai Kohei <kaigai@ak.jp.nec.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| * Cross-endian support added by David Schleef <ds@schleef.org>. |
| * |
| * Major architectural rewrite by Erik Andersen <andersen@codepoet.org> |
| * to allow support for making hard links (though hard links support is |
| * not yet implemented), and for munging file permissions and ownership |
| * on the fly using --faketime, --squash, --devtable. And I plugged a |
| * few memory leaks, adjusted the error handling and fixed some little |
| * nits here and there. |
| * |
| * I also added a sample device table file. See device_table.txt |
| * -Erik, September 2001 |
| * |
| * Cleanmarkers support added by Axis Communications AB |
| * |
| * Rewritten again. Cleanly separated host and target filsystem |
| * activities (mainly so I can reuse all the host handling stuff as I |
| * rewrite other mkfs utils). Added a verbose option to list types |
| * and attributes as files are added to the file system. Major cleanup |
| * and scrubbing of the code so it can be read, understood, and |
| * modified by mere mortals. |
| * |
| * -Erik, November 2002 |
| */ |
| |
| #define _GNU_SOURCE |
| #include <sys/types.h> |
| #include <stdio.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <sys/mman.h> |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #include <stdint.h> |
| #include <libgen.h> |
| #include <ctype.h> |
| #include <time.h> |
| #include <getopt.h> |
| #ifndef WITHOUT_XATTR |
| #include <sys/xattr.h> |
| #include <sys/acl.h> |
| #endif |
| #include <byteswap.h> |
| #define crc32 __complete_crap |
| #include <zlib.h> |
| #undef crc32 |
| #include "crc32.h" |
| #include "rbtree.h" |
| |
| /* Do not use the weird XPG version of basename */ |
| #undef basename |
| |
| //#define DMALLOC |
| //#define mkfs_debug_msg error_msg |
| #define mkfs_debug_msg(a...) { } |
| #define min(x,y) ({ typeof((x)) _x = (x); typeof((y)) _y = (y); (_x>_y)?_y:_x; }) |
| |
| #define PAD(x) (((x)+3)&~3) |
| |
| struct filesystem_entry { |
| char *name; /* Name of this directory (think basename) */ |
| char *path; /* Path of this directory (think dirname) */ |
| char *fullname; /* Full name of this directory (i.e. path+name) */ |
| char *hostname; /* Full path to this file on the host filesystem */ |
| uint32_t ino; /* Inode number of this file in JFFS2 */ |
| struct stat sb; /* Stores directory permissions and whatnot */ |
| char *link; /* Target a symlink points to. */ |
| struct filesystem_entry *parent; /* Parent directory */ |
| struct filesystem_entry *prev; /* Only relevant to non-directories */ |
| struct filesystem_entry *next; /* Only relevant to non-directories */ |
| struct filesystem_entry *files; /* Only relevant to directories */ |
| struct rb_node hardlink_rb; |
| }; |
| |
| struct rb_root hardlinks; |
| static int out_fd = -1; |
| static int in_fd = -1; |
| static char default_rootdir[] = "."; |
| static char *rootdir = default_rootdir; |
| static int verbose = 0; |
| static int squash_uids = 0; |
| static int squash_perms = 0; |
| static int fake_times = 0; |
| int target_endian = __BYTE_ORDER; |
| static const char *const app_name = "mkfs.jffs2"; |
| static const char *const memory_exhausted = "memory exhausted"; |
| |
| uint32_t find_hardlink(struct filesystem_entry *e) |
| { |
| struct filesystem_entry *f; |
| struct rb_node **n = &hardlinks.rb_node; |
| struct rb_node *parent = NULL; |
| |
| while (*n) { |
| parent = *n; |
| f = rb_entry(parent, struct filesystem_entry, hardlink_rb); |
| |
| if ((f->sb.st_dev < e->sb.st_dev) || |
| (f->sb.st_dev == e->sb.st_dev && |
| f->sb.st_ino < e->sb.st_ino)) |
| n = &parent->rb_left; |
| else if ((f->sb.st_dev > e->sb.st_dev) || |
| (f->sb.st_dev == e->sb.st_dev && |
| f->sb.st_ino > e->sb.st_ino)) { |
| n = &parent->rb_right; |
| } else |
| return f->ino; |
| } |
| |
| rb_link_node(&e->hardlink_rb, parent, n); |
| rb_insert_color(&e->hardlink_rb, &hardlinks); |
| return 0; |
| } |
| |
| static void verror_msg(const char *s, va_list p) |
| { |
| fflush(stdout); |
| fprintf(stderr, "%s: ", app_name); |
| vfprintf(stderr, s, p); |
| } |
| static void error_msg(const char *s, ...) |
| { |
| va_list p; |
| |
| va_start(p, s); |
| verror_msg(s, p); |
| va_end(p); |
| putc('\n', stderr); |
| } |
| |
| static void error_msg_and_die(const char *s, ...) |
| { |
| va_list p; |
| |
| va_start(p, s); |
| verror_msg(s, p); |
| va_end(p); |
| putc('\n', stderr); |
| exit(EXIT_FAILURE); |
| } |
| |
| static void vperror_msg(const char *s, va_list p) |
| { |
| int err = errno; |
| |
| if (s == 0) |
| s = ""; |
| verror_msg(s, p); |
| if (*s) |
| s = ": "; |
| fprintf(stderr, "%s%s\n", s, strerror(err)); |
| } |
| |
| static void perror_msg(const char *s, ...) |
| { |
| va_list p; |
| |
| va_start(p, s); |
| vperror_msg(s, p); |
| va_end(p); |
| } |
| |
| static void perror_msg_and_die(const char *s, ...) |
| { |
| va_list p; |
| |
| va_start(p, s); |
| vperror_msg(s, p); |
| va_end(p); |
| exit(EXIT_FAILURE); |
| } |
| |
| #ifndef DMALLOC |
| extern void *xmalloc(size_t size) |
| { |
| void *ptr = malloc(size); |
| |
| if (ptr == NULL && size != 0) |
| error_msg_and_die(memory_exhausted); |
| return ptr; |
| } |
| |
| extern void *xcalloc(size_t nmemb, size_t size) |
| { |
| void *ptr = calloc(nmemb, size); |
| |
| if (ptr == NULL && nmemb != 0 && size != 0) |
| error_msg_and_die(memory_exhausted); |
| return ptr; |
| } |
| |
| extern void *xrealloc(void *ptr, size_t size) |
| { |
| ptr = realloc(ptr, size); |
| if (ptr == NULL && size != 0) |
| error_msg_and_die(memory_exhausted); |
| return ptr; |
| } |
| |
| extern char *xstrdup(const char *s) |
| { |
| char *t; |
| |
| if (s == NULL) |
| return NULL; |
| t = strdup(s); |
| if (t == NULL) |
| error_msg_and_die(memory_exhausted); |
| return t; |
| } |
| #endif |
| |
| extern char *xreadlink(const char *path) |
| { |
| static const int GROWBY = 80; /* how large we will grow strings by */ |
| |
| char *buf = NULL; |
| int bufsize = 0, readsize = 0; |
| |
| do { |
| buf = xrealloc(buf, bufsize += GROWBY); |
| readsize = readlink(path, buf, bufsize); /* 1st try */ |
| if (readsize == -1) { |
| perror_msg("%s:%s", app_name, path); |
| return NULL; |
| } |
| } |
| while (bufsize < readsize + 1); |
| |
| buf[readsize] = '\0'; |
| |
| return buf; |
| } |
| static FILE *xfopen(const char *path, const char *mode) |
| { |
| FILE *fp; |
| if ((fp = fopen(path, mode)) == NULL) |
| perror_msg_and_die("%s", path); |
| return fp; |
| } |
| |
| static struct filesystem_entry *find_filesystem_entry( |
| struct filesystem_entry *dir, char *fullname, uint32_t type) |
| { |
| struct filesystem_entry *e = dir; |
| |
| if (S_ISDIR(dir->sb.st_mode)) { |
| /* If this is the first call, and we actually want this |
| * directory, then return it now */ |
| if (strcmp(fullname, e->fullname) == 0) |
| return e; |
| |
| e = dir->files; |
| } |
| while (e) { |
| if (S_ISDIR(e->sb.st_mode)) { |
| int len = strlen(e->fullname); |
| |
| /* Check if we are a parent of the correct path */ |
| if (strncmp(e->fullname, fullname, len) == 0) { |
| /* Is this an _exact_ match? */ |
| if (strcmp(fullname, e->fullname) == 0) { |
| return (e); |
| } |
| /* Looks like we found a parent of the correct path */ |
| if (fullname[len] == '/') { |
| if (e->files) { |
| return (find_filesystem_entry (e, fullname, type)); |
| } else { |
| return NULL; |
| } |
| } |
| } |
| } else { |
| if (strcmp(fullname, e->fullname) == 0) { |
| return (e); |
| } |
| } |
| e = e->next; |
| } |
| return (NULL); |
| } |
| |
| static struct filesystem_entry *add_host_filesystem_entry( |
| char *name, char *path, unsigned long uid, unsigned long gid, |
| unsigned long mode, dev_t rdev, struct filesystem_entry *parent) |
| { |
| int status; |
| char *tmp; |
| struct stat sb; |
| time_t timestamp = time(NULL); |
| struct filesystem_entry *entry; |
| |
| memset(&sb, 0, sizeof(struct stat)); |
| status = lstat(path, &sb); |
| |
| if (status >= 0) { |
| /* It is ok for some types of files to not exit on disk (such as |
| * device nodes), but if they _do_ exist the specified mode had |
| * better match the actual file or strange things will happen.... */ |
| if ((mode & S_IFMT) != (sb.st_mode & S_IFMT)) { |
| error_msg_and_die ("%s: file type does not match specified type!", path); |
| } |
| timestamp = sb.st_mtime; |
| } else { |
| /* If this is a regular file, it _must_ exist on disk */ |
| if ((mode & S_IFMT) == S_IFREG) { |
| error_msg_and_die("%s: does not exist!", path); |
| } |
| } |
| |
| /* Squash all permissions so files are owned by root, all |
| * timestamps are _right now_, and file permissions |
| * have group and other write removed */ |
| if (squash_uids) { |
| uid = gid = 0; |
| } |
| if (squash_perms) { |
| if (!S_ISLNK(mode)) { |
| mode &= ~(S_IWGRP | S_IWOTH); |
| mode &= ~(S_ISUID | S_ISGID); |
| } |
| } |
| if (fake_times) { |
| timestamp = 0; |
| } |
| |
| entry = xcalloc(1, sizeof(struct filesystem_entry)); |
| |
| entry->hostname = xstrdup(path); |
| entry->fullname = xstrdup(name); |
| tmp = xstrdup(name); |
| entry->name = xstrdup(basename(tmp)); |
| free(tmp); |
| tmp = xstrdup(name); |
| entry->path = xstrdup(dirname(tmp)); |
| free(tmp); |
| |
| entry->sb.st_ino = sb.st_ino; |
| entry->sb.st_dev = sb.st_dev; |
| entry->sb.st_nlink = sb.st_nlink; |
| |
| entry->sb.st_uid = uid; |
| entry->sb.st_gid = gid; |
| entry->sb.st_mode = mode; |
| entry->sb.st_rdev = rdev; |
| entry->sb.st_atime = entry->sb.st_ctime = |
| entry->sb.st_mtime = timestamp; |
| if (S_ISREG(mode)) { |
| entry->sb.st_size = sb.st_size; |
| } |
| if (S_ISLNK(mode)) { |
| entry->link = xreadlink(path); |
| entry->sb.st_size = strlen(entry->link); |
| } |
| |
| /* This happens only for root */ |
| if (!parent) |
| return (entry); |
| |
| /* Hook the file into the parent directory */ |
| entry->parent = parent; |
| if (!parent->files) { |
| parent->files = entry; |
| } else { |
| struct filesystem_entry *prev; |
| for (prev = parent->files; prev->next; prev = prev->next); |
| prev->next = entry; |
| entry->prev = prev; |
| } |
| |
| return (entry); |
| } |
| |
| static struct filesystem_entry *recursive_add_host_directory( |
| struct filesystem_entry *parent, char *targetpath, char *hostpath) |
| { |
| int i, n; |
| struct stat sb; |
| char *hpath, *tpath; |
| struct dirent *dp, **namelist; |
| struct filesystem_entry *entry; |
| |
| |
| if (lstat(hostpath, &sb)) { |
| perror_msg_and_die("%s", hostpath); |
| } |
| |
| entry = add_host_filesystem_entry(targetpath, hostpath, |
| sb.st_uid, sb.st_gid, sb.st_mode, 0, parent); |
| |
| n = scandir(hostpath, &namelist, 0, alphasort); |
| if (n < 0) { |
| perror_msg_and_die("opening directory %s", hostpath); |
| } |
| |
| for (i=0; i<n; i++) |
| { |
| dp = namelist[i]; |
| if (dp->d_name[0] == '.' && (dp->d_name[1] == 0 || |
| (dp->d_name[1] == '.' && dp->d_name[2] == 0))) |
| { |
| free(dp); |
| continue; |
| } |
| |
| asprintf(&hpath, "%s/%s", hostpath, dp->d_name); |
| if (lstat(hpath, &sb)) { |
| perror_msg_and_die("%s", hpath); |
| } |
| if (strcmp(targetpath, "/") == 0) { |
| asprintf(&tpath, "%s%s", targetpath, dp->d_name); |
| } else { |
| asprintf(&tpath, "%s/%s", targetpath, dp->d_name); |
| } |
| |
| switch (sb.st_mode & S_IFMT) { |
| case S_IFDIR: |
| recursive_add_host_directory(entry, tpath, hpath); |
| break; |
| |
| case S_IFREG: |
| case S_IFSOCK: |
| case S_IFIFO: |
| case S_IFLNK: |
| case S_IFCHR: |
| case S_IFBLK: |
| add_host_filesystem_entry(tpath, hpath, sb.st_uid, |
| sb.st_gid, sb.st_mode, sb.st_rdev, entry); |
| break; |
| |
| default: |
| error_msg("Unknown file type %o for %s", sb.st_mode, hpath); |
| break; |
| } |
| free(dp); |
| free(hpath); |
| free(tpath); |
| } |
| free(namelist); |
| return (entry); |
| } |
| |
| /* the GNU C library has a wonderful scanf("%as", string) which will |
| allocate the string with the right size, good to avoid buffer overruns. |
| the following macros use it if available or use a hacky workaround... |
| */ |
| |
| #ifdef __GNUC__ |
| #define SCANF_PREFIX "a" |
| #define SCANF_STRING(s) (&s) |
| #define GETCWD_SIZE 0 |
| #else |
| #define SCANF_PREFIX "511" |
| #define SCANF_STRING(s) (s = malloc(512)) |
| #define GETCWD_SIZE -1 |
| inline int snprintf(char *str, size_t n, const char *fmt, ...) |
| { |
| int ret; |
| va_list ap; |
| |
| va_start(ap, fmt); |
| ret = vsprintf(str, fmt, ap); |
| va_end(ap); |
| return ret; |
| } |
| #endif |
| |
| /* device table entries take the form of: |
| <path> <type> <mode> <uid> <gid> <major> <minor> <start> <inc> <count> |
| /dev/mem c 640 0 0 1 1 0 0 - |
| |
| type can be one of: |
| f A regular file |
| d Directory |
| c Character special device file |
| b Block special device file |
| p Fifo (named pipe) |
| |
| I don't bother with symlinks (permissions are irrelevant), hard |
| links (special cases of regular files), or sockets (why bother). |
| |
| Regular files must exist in the target root directory. If a char, |
| block, fifo, or directory does not exist, it will be created. |
| */ |
| static int interpret_table_entry(struct filesystem_entry *root, char *line) |
| { |
| char *hostpath; |
| char type, *name = NULL, *tmp, *dir; |
| unsigned long mode = 0755, uid = 0, gid = 0, major = 0, minor = 0; |
| unsigned long start = 0, increment = 1, count = 0; |
| struct filesystem_entry *parent, *entry; |
| |
| if (sscanf (line, "%" SCANF_PREFIX "s %c %lo %lu %lu %lu %lu %lu %lu %lu", |
| SCANF_STRING(name), &type, &mode, &uid, &gid, &major, &minor, |
| &start, &increment, &count) < 0) |
| { |
| return 1; |
| } |
| |
| if (!strcmp(name, "/")) { |
| error_msg_and_die("Device table entries require absolute paths"); |
| } |
| |
| asprintf(&hostpath, "%s%s", rootdir, name); |
| |
| /* Check if this file already exists... */ |
| switch (type) { |
| case 'd': |
| mode |= S_IFDIR; |
| break; |
| case 'f': |
| mode |= S_IFREG; |
| break; |
| case 'p': |
| mode |= S_IFIFO; |
| break; |
| case 'c': |
| mode |= S_IFCHR; |
| break; |
| case 'b': |
| mode |= S_IFBLK; |
| break; |
| case 'l': |
| mode |= S_IFLNK; |
| break; |
| default: |
| error_msg_and_die("Unsupported file type '%c'", type); |
| } |
| entry = find_filesystem_entry(root, name, mode); |
| if (entry) { |
| /* Ok, we just need to fixup the existing entry |
| * and we will be all done... */ |
| entry->sb.st_uid = uid; |
| entry->sb.st_gid = gid; |
| entry->sb.st_mode = mode; |
| if (major && minor) { |
| entry->sb.st_rdev = makedev(major, minor); |
| } |
| } else { |
| /* If parent is NULL (happens with device table entries), |
| * try and find our parent now) */ |
| tmp = strdup(name); |
| dir = dirname(tmp); |
| parent = find_filesystem_entry(root, dir, S_IFDIR); |
| free(tmp); |
| if (parent == NULL) { |
| error_msg ("skipping device_table entry '%s': no parent directory!", name); |
| free(name); |
| free(hostpath); |
| return 1; |
| } |
| |
| switch (type) { |
| case 'd': |
| add_host_filesystem_entry(name, hostpath, uid, gid, mode, 0, parent); |
| break; |
| case 'f': |
| add_host_filesystem_entry(name, hostpath, uid, gid, mode, 0, parent); |
| break; |
| case 'p': |
| add_host_filesystem_entry(name, hostpath, uid, gid, mode, 0, parent); |
| break; |
| case 'c': |
| case 'b': |
| if (count > 0) { |
| dev_t rdev; |
| unsigned long i; |
| char *dname, *hpath; |
| |
| for (i = start; i < count; i++) { |
| asprintf(&dname, "%s%lu", name, i); |
| asprintf(&hpath, "%s/%s%lu", rootdir, name, i); |
| rdev = makedev(major, minor + (i * increment - start)); |
| add_host_filesystem_entry(dname, hpath, uid, gid, |
| mode, rdev, parent); |
| free(dname); |
| free(hpath); |
| } |
| } else { |
| dev_t rdev = makedev(major, minor); |
| add_host_filesystem_entry(name, hostpath, uid, gid, |
| mode, rdev, parent); |
| } |
| break; |
| default: |
| error_msg_and_die("Unsupported file type '%c'", type); |
| } |
| } |
| free(name); |
| free(hostpath); |
| return 0; |
| } |
| |
| static int parse_device_table(struct filesystem_entry *root, FILE * file) |
| { |
| char *line; |
| int status = 0; |
| size_t length = 0; |
| |
| /* Turn off squash, since we must ensure that values |
| * entered via the device table are not squashed */ |
| squash_uids = 0; |
| squash_perms = 0; |
| |
| /* Looks ok so far. The general plan now is to read in one |
| * line at a time, check for leading comment delimiters ('#'), |
| * then try and parse the line as a device table. If we fail |
| * to parse things, try and help the poor fool to fix their |
| * device table with a useful error msg... */ |
| line = NULL; |
| while (getline(&line, &length, file) != -1) { |
| /* First trim off any whitespace */ |
| int len = strlen(line); |
| |
| /* trim trailing whitespace */ |
| while (len > 0 && isspace(line[len - 1])) |
| line[--len] = '\0'; |
| /* trim leading whitespace */ |
| memmove(line, &line[strspn(line, " \n\r\t\v")], len); |
| |
| /* How long are we after trimming? */ |
| len = strlen(line); |
| |
| /* If this is NOT a comment line, try to interpret it */ |
| if (len && *line != '#') { |
| if (interpret_table_entry(root, line)) |
| status = 1; |
| } |
| |
| free(line); |
| line = NULL; |
| } |
| fclose(file); |
| |
| return status; |
| } |
| |
| static void cleanup(struct filesystem_entry *dir) |
| { |
| struct filesystem_entry *e, *prev; |
| |
| e = dir->files; |
| while (e) { |
| if (e->name) |
| free(e->name); |
| if (e->path) |
| free(e->path); |
| if (e->fullname) |
| free(e->fullname); |
| e->next = NULL; |
| e->name = NULL; |
| e->path = NULL; |
| e->fullname = NULL; |
| e->prev = NULL; |
| prev = e; |
| if (S_ISDIR(e->sb.st_mode)) { |
| cleanup(e); |
| } |
| e = e->next; |
| free(prev); |
| } |
| } |
| |
| /* Here is where we do the actual creation of the file system */ |
| #include "mtd/jffs2-user.h" |
| |
| #define JFFS2_MAX_FILE_SIZE 0xFFFFFFFF |
| #ifndef JFFS2_MAX_SYMLINK_LEN |
| #define JFFS2_MAX_SYMLINK_LEN 254 |
| #endif |
| |
| static uint32_t ino = 0; |
| static uint8_t *file_buffer = NULL; /* file buffer contains the actual erase block*/ |
| static int out_ofs = 0; |
| static int erase_block_size = 65536; |
| static int pad_fs_size = 0; |
| static int add_cleanmarkers = 1; |
| static struct jffs2_unknown_node cleanmarker; |
| static int cleanmarker_size = sizeof(cleanmarker); |
| static unsigned char ffbuf[16] = |
| { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff |
| }; |
| |
| /* We set this at start of main() using sysconf(), -1 means we don't know */ |
| /* When building an fs for non-native systems, use --pagesize=SIZE option */ |
| int page_size = -1; |
| |
| #include "compr.h" |
| |
| static void full_write(int fd, const void *buf, int len) |
| { |
| int ret; |
| |
| while (len > 0) { |
| ret = write(fd, buf, len); |
| |
| if (ret < 0) |
| perror_msg_and_die("write"); |
| |
| if (ret == 0) |
| perror_msg_and_die("write returned zero"); |
| |
| len -= ret; |
| buf += ret; |
| out_ofs += ret; |
| } |
| } |
| |
| static void padblock(void) |
| { |
| while (out_ofs % erase_block_size) { |
| full_write(out_fd, ffbuf, min(sizeof(ffbuf), |
| erase_block_size - (out_ofs % erase_block_size))); |
| } |
| } |
| |
| static void pad(int req) |
| { |
| while (req) { |
| if (req > sizeof(ffbuf)) { |
| full_write(out_fd, ffbuf, sizeof(ffbuf)); |
| req -= sizeof(ffbuf); |
| } else { |
| full_write(out_fd, ffbuf, req); |
| req = 0; |
| } |
| } |
| } |
| |
| static inline void padword(void) |
| { |
| if (out_ofs % 4) { |
| full_write(out_fd, ffbuf, 4 - (out_ofs % 4)); |
| } |
| } |
| |
| static inline void pad_block_if_less_than(int req) |
| { |
| if (add_cleanmarkers) { |
| if ((out_ofs % erase_block_size) == 0) { |
| full_write(out_fd, &cleanmarker, sizeof(cleanmarker)); |
| pad(cleanmarker_size - sizeof(cleanmarker)); |
| padword(); |
| } |
| } |
| if ((out_ofs % erase_block_size) + req > erase_block_size) { |
| padblock(); |
| } |
| if (add_cleanmarkers) { |
| if ((out_ofs % erase_block_size) == 0) { |
| full_write(out_fd, &cleanmarker, sizeof(cleanmarker)); |
| pad(cleanmarker_size - sizeof(cleanmarker)); |
| padword(); |
| } |
| } |
| } |
| |
| static void write_dirent(struct filesystem_entry *e) |
| { |
| char *name = e->name; |
| struct jffs2_raw_dirent rd; |
| struct stat *statbuf = &(e->sb); |
| static uint32_t version = 0; |
| |
| memset(&rd, 0, sizeof(rd)); |
| |
| rd.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); |
| rd.nodetype = cpu_to_je16(JFFS2_NODETYPE_DIRENT); |
| rd.totlen = cpu_to_je32(sizeof(rd) + strlen(name)); |
| rd.hdr_crc = cpu_to_je32(crc32(0, &rd, |
| sizeof(struct jffs2_unknown_node) - 4)); |
| rd.pino = cpu_to_je32((e->parent) ? e->parent->ino : 1); |
| rd.version = cpu_to_je32(version++); |
| rd.ino = cpu_to_je32(e->ino); |
| rd.mctime = cpu_to_je32(statbuf->st_mtime); |
| rd.nsize = strlen(name); |
| rd.type = IFTODT(statbuf->st_mode); |
| //rd.unused[0] = 0; |
| //rd.unused[1] = 0; |
| rd.node_crc = cpu_to_je32(crc32(0, &rd, sizeof(rd) - 8)); |
| rd.name_crc = cpu_to_je32(crc32(0, name, strlen(name))); |
| |
| pad_block_if_less_than(sizeof(rd) + rd.nsize); |
| full_write(out_fd, &rd, sizeof(rd)); |
| full_write(out_fd, name, rd.nsize); |
| padword(); |
| } |
| |
| static unsigned int write_regular_file(struct filesystem_entry *e) |
| { |
| int fd, len; |
| uint32_t ver; |
| unsigned int offset; |
| unsigned char *buf, *cbuf, *wbuf; |
| struct jffs2_raw_inode ri; |
| struct stat *statbuf; |
| unsigned int totcomp = 0; |
| |
| statbuf = &(e->sb); |
| if (statbuf->st_size >= JFFS2_MAX_FILE_SIZE) { |
| error_msg("Skipping file \"%s\" too large.", e->path); |
| return -1; |
| } |
| fd = open(e->hostname, O_RDONLY); |
| if (fd == -1) { |
| perror_msg_and_die("%s: open file", e->hostname); |
| } |
| |
| e->ino = ++ino; |
| mkfs_debug_msg("writing file '%s' ino=%lu parent_ino=%lu", |
| e->name, (unsigned long) e->ino, |
| (unsigned long) e->parent->ino); |
| write_dirent(e); |
| |
| buf = xmalloc(page_size); |
| cbuf = NULL; |
| |
| ver = 0; |
| offset = 0; |
| |
| memset(&ri, 0, sizeof(ri)); |
| ri.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); |
| ri.nodetype = cpu_to_je16(JFFS2_NODETYPE_INODE); |
| |
| ri.ino = cpu_to_je32(e->ino); |
| ri.mode = cpu_to_jemode(statbuf->st_mode); |
| ri.uid = cpu_to_je16(statbuf->st_uid); |
| ri.gid = cpu_to_je16(statbuf->st_gid); |
| ri.atime = cpu_to_je32(statbuf->st_atime); |
| ri.ctime = cpu_to_je32(statbuf->st_ctime); |
| ri.mtime = cpu_to_je32(statbuf->st_mtime); |
| ri.isize = cpu_to_je32(statbuf->st_size); |
| |
| while ((len = read(fd, buf, page_size))) { |
| unsigned char *tbuf = buf; |
| |
| if (len < 0) { |
| perror_msg_and_die("read"); |
| } |
| |
| while (len) { |
| uint32_t dsize, space; |
| uint16_t compression; |
| |
| pad_block_if_less_than(sizeof(ri) + JFFS2_MIN_DATA_LEN); |
| |
| dsize = len; |
| space = |
| erase_block_size - (out_ofs % erase_block_size) - |
| sizeof(ri); |
| if (space > dsize) |
| space = dsize; |
| |
| compression = jffs2_compress(tbuf, &cbuf, &dsize, &space); |
| |
| ri.compr = compression & 0xff; |
| ri.usercompr = (compression >> 8) & 0xff; |
| |
| if (ri.compr) { |
| wbuf = cbuf; |
| } else { |
| wbuf = tbuf; |
| dsize = space; |
| } |
| |
| ri.totlen = cpu_to_je32(sizeof(ri) + space); |
| ri.hdr_crc = cpu_to_je32(crc32(0, |
| &ri, sizeof(struct jffs2_unknown_node) - 4)); |
| |
| ri.version = cpu_to_je32(++ver); |
| ri.offset = cpu_to_je32(offset); |
| ri.csize = cpu_to_je32(space); |
| ri.dsize = cpu_to_je32(dsize); |
| ri.node_crc = cpu_to_je32(crc32(0, &ri, sizeof(ri) - 8)); |
| ri.data_crc = cpu_to_je32(crc32(0, wbuf, space)); |
| |
| full_write(out_fd, &ri, sizeof(ri)); |
| totcomp += sizeof(ri); |
| full_write(out_fd, wbuf, space); |
| totcomp += space; |
| padword(); |
| |
| if (tbuf != cbuf) { |
| free(cbuf); |
| cbuf = NULL; |
| } |
| |
| tbuf += dsize; |
| len -= dsize; |
| offset += dsize; |
| |
| } |
| } |
| if (!je32_to_cpu(ri.version)) { |
| /* Was empty file */ |
| pad_block_if_less_than(sizeof(ri)); |
| |
| ri.version = cpu_to_je32(++ver); |
| ri.totlen = cpu_to_je32(sizeof(ri)); |
| ri.hdr_crc = cpu_to_je32(crc32(0, |
| &ri, sizeof(struct jffs2_unknown_node) - 4)); |
| ri.csize = cpu_to_je32(0); |
| ri.dsize = cpu_to_je32(0); |
| ri.node_crc = cpu_to_je32(crc32(0, &ri, sizeof(ri) - 8)); |
| |
| full_write(out_fd, &ri, sizeof(ri)); |
| padword(); |
| } |
| free(buf); |
| close(fd); |
| return totcomp; |
| } |
| |
| static void write_symlink(struct filesystem_entry *e) |
| { |
| int len; |
| struct stat *statbuf; |
| struct jffs2_raw_inode ri; |
| |
| statbuf = &(e->sb); |
| e->ino = ++ino; |
| mkfs_debug_msg("writing symlink '%s' ino=%lu parent_ino=%lu", |
| e->name, (unsigned long) e->ino, |
| (unsigned long) e->parent->ino); |
| write_dirent(e); |
| |
| len = strlen(e->link); |
| if (len > JFFS2_MAX_SYMLINK_LEN) { |
| error_msg("symlink too large. Truncated to %d chars.", |
| JFFS2_MAX_SYMLINK_LEN); |
| len = JFFS2_MAX_SYMLINK_LEN; |
| } |
| |
| memset(&ri, 0, sizeof(ri)); |
| |
| ri.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); |
| ri.nodetype = cpu_to_je16(JFFS2_NODETYPE_INODE); |
| ri.totlen = cpu_to_je32(sizeof(ri) + len); |
| ri.hdr_crc = cpu_to_je32(crc32(0, |
| &ri, sizeof(struct jffs2_unknown_node) - 4)); |
| |
| ri.ino = cpu_to_je32(e->ino); |
| ri.mode = cpu_to_jemode(statbuf->st_mode); |
| ri.uid = cpu_to_je16(statbuf->st_uid); |
| ri.gid = cpu_to_je16(statbuf->st_gid); |
| ri.atime = cpu_to_je32(statbuf->st_atime); |
| ri.ctime = cpu_to_je32(statbuf->st_ctime); |
| ri.mtime = cpu_to_je32(statbuf->st_mtime); |
| ri.isize = cpu_to_je32(statbuf->st_size); |
| ri.version = cpu_to_je32(1); |
| ri.csize = cpu_to_je32(len); |
| ri.dsize = cpu_to_je32(len); |
| ri.node_crc = cpu_to_je32(crc32(0, &ri, sizeof(ri) - 8)); |
| ri.data_crc = cpu_to_je32(crc32(0, e->link, len)); |
| |
| pad_block_if_less_than(sizeof(ri) + len); |
| full_write(out_fd, &ri, sizeof(ri)); |
| full_write(out_fd, e->link, len); |
| padword(); |
| } |
| |
| static void write_pipe(struct filesystem_entry *e) |
| { |
| struct stat *statbuf; |
| struct jffs2_raw_inode ri; |
| |
| statbuf = &(e->sb); |
| e->ino = ++ino; |
| if (S_ISDIR(statbuf->st_mode)) { |
| mkfs_debug_msg("writing dir '%s' ino=%lu parent_ino=%lu", |
| e->name, (unsigned long) e->ino, |
| (unsigned long) (e->parent) ? e->parent->ino : 1); |
| } |
| write_dirent(e); |
| |
| memset(&ri, 0, sizeof(ri)); |
| |
| ri.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); |
| ri.nodetype = cpu_to_je16(JFFS2_NODETYPE_INODE); |
| ri.totlen = cpu_to_je32(sizeof(ri)); |
| ri.hdr_crc = cpu_to_je32(crc32(0, |
| &ri, sizeof(struct jffs2_unknown_node) - 4)); |
| |
| ri.ino = cpu_to_je32(e->ino); |
| ri.mode = cpu_to_jemode(statbuf->st_mode); |
| ri.uid = cpu_to_je16(statbuf->st_uid); |
| ri.gid = cpu_to_je16(statbuf->st_gid); |
| ri.atime = cpu_to_je32(statbuf->st_atime); |
| ri.ctime = cpu_to_je32(statbuf->st_ctime); |
| ri.mtime = cpu_to_je32(statbuf->st_mtime); |
| ri.isize = cpu_to_je32(0); |
| ri.version = cpu_to_je32(1); |
| ri.csize = cpu_to_je32(0); |
| ri.dsize = cpu_to_je32(0); |
| ri.node_crc = cpu_to_je32(crc32(0, &ri, sizeof(ri) - 8)); |
| ri.data_crc = cpu_to_je32(0); |
| |
| pad_block_if_less_than(sizeof(ri)); |
| full_write(out_fd, &ri, sizeof(ri)); |
| padword(); |
| } |
| |
| static void write_special_file(struct filesystem_entry *e) |
| { |
| jint16_t kdev; |
| struct stat *statbuf; |
| struct jffs2_raw_inode ri; |
| |
| statbuf = &(e->sb); |
| e->ino = ++ino; |
| write_dirent(e); |
| |
| kdev = cpu_to_je16((major(statbuf->st_rdev) << 8) + |
| minor(statbuf->st_rdev)); |
| |
| memset(&ri, 0, sizeof(ri)); |
| |
| ri.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); |
| ri.nodetype = cpu_to_je16(JFFS2_NODETYPE_INODE); |
| ri.totlen = cpu_to_je32(sizeof(ri) + sizeof(kdev)); |
| ri.hdr_crc = cpu_to_je32(crc32(0, |
| &ri, sizeof(struct jffs2_unknown_node) - 4)); |
| |
| ri.ino = cpu_to_je32(e->ino); |
| ri.mode = cpu_to_jemode(statbuf->st_mode); |
| ri.uid = cpu_to_je16(statbuf->st_uid); |
| ri.gid = cpu_to_je16(statbuf->st_gid); |
| ri.atime = cpu_to_je32(statbuf->st_atime); |
| ri.ctime = cpu_to_je32(statbuf->st_ctime); |
| ri.mtime = cpu_to_je32(statbuf->st_mtime); |
| ri.isize = cpu_to_je32(statbuf->st_size); |
| ri.version = cpu_to_je32(1); |
| ri.csize = cpu_to_je32(sizeof(kdev)); |
| ri.dsize = cpu_to_je32(sizeof(kdev)); |
| ri.node_crc = cpu_to_je32(crc32(0, &ri, sizeof(ri) - 8)); |
| ri.data_crc = cpu_to_je32(crc32(0, &kdev, sizeof(kdev))); |
| |
| pad_block_if_less_than(sizeof(ri) + sizeof(kdev)); |
| full_write(out_fd, &ri, sizeof(ri)); |
| full_write(out_fd, &kdev, sizeof(kdev)); |
| padword(); |
| } |
| |
| #ifndef WITHOUT_XATTR |
| typedef struct xattr_entry { |
| struct xattr_entry *next; |
| uint32_t xid; |
| int xprefix; |
| char *xname; |
| char *xvalue; |
| int name_len; |
| int value_len; |
| } xattr_entry_t; |
| |
| #define XATTR_BUFFER_SIZE (64 * 1024) /* 64KB */ |
| static uint32_t enable_xattr = 0; |
| static uint32_t highest_xid = 0; |
| static uint32_t highest_xseqno = 0; |
| |
| static struct { |
| int xprefix; |
| char *string; |
| int length; |
| } xprefix_tbl[] = { |
| { JFFS2_XPREFIX_USER, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN }, |
| { JFFS2_XPREFIX_SECURITY, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN }, |
| { JFFS2_XPREFIX_ACL_ACCESS, POSIX_ACL_XATTR_ACCESS, POSIX_ACL_XATTR_ACCESS_LEN }, |
| { JFFS2_XPREFIX_ACL_DEFAULT, POSIX_ACL_XATTR_DEFAULT, POSIX_ACL_XATTR_DEFAULT_LEN }, |
| { JFFS2_XPREFIX_TRUSTED, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN }, |
| { 0, NULL, 0 } |
| }; |
| |
| static void formalize_posix_acl(void *xvalue, int *value_len) |
| { |
| struct posix_acl_xattr_header *pacl_header; |
| struct posix_acl_xattr_entry *pent, *plim; |
| struct jffs2_acl_header *jacl_header; |
| struct jffs2_acl_entry *jent; |
| struct jffs2_acl_entry_short *jent_s; |
| char buffer[XATTR_BUFFER_SIZE]; |
| int offset = 0; |
| |
| pacl_header = xvalue;; |
| pent = pacl_header->a_entries; |
| plim = xvalue + *value_len; |
| |
| jacl_header = (struct jffs2_acl_header *)buffer; |
| offset += sizeof(struct jffs2_acl_header); |
| jacl_header->a_version = cpu_to_je32(JFFS2_ACL_VERSION); |
| |
| while (pent < plim) { |
| switch(le16_to_cpu(pent->e_tag)) { |
| case ACL_USER_OBJ: |
| case ACL_GROUP_OBJ: |
| case ACL_MASK: |
| case ACL_OTHER: |
| jent_s = (struct jffs2_acl_entry_short *)(buffer + offset); |
| offset += sizeof(struct jffs2_acl_entry_short); |
| jent_s->e_tag = cpu_to_je16(le16_to_cpu(pent->e_tag)); |
| jent_s->e_perm = cpu_to_je16(le16_to_cpu(pent->e_perm)); |
| break; |
| case ACL_USER: |
| case ACL_GROUP: |
| jent = (struct jffs2_acl_entry *)(buffer + offset); |
| offset += sizeof(struct jffs2_acl_entry); |
| jent->e_tag = cpu_to_je16(le16_to_cpu(pent->e_tag)); |
| jent->e_perm = cpu_to_je16(le16_to_cpu(pent->e_perm)); |
| jent->e_id = cpu_to_je32(le32_to_cpu(pent->e_id)); |
| break; |
| default: |
| printf("%04x : Unknown XATTR entry tag.\n", le16_to_cpu(pent->e_tag)); |
| exit(1); |
| } |
| pent++; |
| } |
| if (offset > *value_len) { |
| printf("Length of JFFS2 ACL expression(%u) is longer than general one(%u).\n", |
| offset, *value_len); |
| exit(1); |
| } |
| memcpy(xvalue, buffer, offset); |
| *value_len = offset; |
| } |
| |
| static xattr_entry_t *create_xattr_entry(int xprefix, char *xname, char *xvalue, int value_len) |
| { |
| xattr_entry_t *xe; |
| struct jffs2_raw_xattr rx; |
| int name_len; |
| |
| /* create xattr entry */ |
| name_len = strlen(xname); |
| xe = xcalloc(1, sizeof(xattr_entry_t) + name_len + 1 + value_len); |
| xe->next = NULL; |
| xe->xid = ++highest_xid; |
| xe->xprefix = xprefix; |
| xe->xname = ((char *)xe) + sizeof(xattr_entry_t); |
| xe->xvalue = xe->xname + name_len + 1; |
| xe->name_len = name_len; |
| xe->value_len = value_len; |
| strcpy(xe->xname, xname); |
| memcpy(xe->xvalue, xvalue, value_len); |
| |
| /* write xattr node */ |
| memset(&rx, 0, sizeof(rx)); |
| rx.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); |
| rx.nodetype = cpu_to_je16(JFFS2_NODETYPE_XATTR); |
| rx.totlen = cpu_to_je32(PAD(sizeof(rx) + xe->name_len + 1 + xe->value_len)); |
| rx.hdr_crc = cpu_to_je32(crc32(0, &rx, sizeof(struct jffs2_unknown_node) - 4)); |
| |
| rx.xid = cpu_to_je32(xe->xid); |
| rx.version = cpu_to_je32(1); /* initial version */ |
| rx.xprefix = xprefix; |
| rx.name_len = xe->name_len; |
| rx.value_len = cpu_to_je16(xe->value_len); |
| rx.data_crc = cpu_to_je32(crc32(0, xe->xname, xe->name_len + 1 + xe->value_len)); |
| rx.node_crc = cpu_to_je32(crc32(0, &rx, sizeof(rx) - 4)); |
| |
| pad_block_if_less_than(sizeof(rx) + xe->name_len + 1 + xe->value_len); |
| full_write(out_fd, &rx, sizeof(rx)); |
| full_write(out_fd, xe->xname, xe->name_len + 1 + xe->value_len); |
| padword(); |
| |
| return xe; |
| } |
| |
| #define XATTRENTRY_HASHSIZE 57 |
| static xattr_entry_t *find_xattr_entry(int xprefix, char *xname, char *xvalue, int value_len) |
| { |
| static xattr_entry_t **xentry_hash = NULL; |
| xattr_entry_t *xe; |
| int index, name_len; |
| |
| /* create hash table */ |
| if (!xentry_hash) |
| xentry_hash = xcalloc(1, sizeof(xe) * XATTRENTRY_HASHSIZE); |
| |
| if (xprefix == JFFS2_XPREFIX_ACL_ACCESS |
| || xprefix == JFFS2_XPREFIX_ACL_DEFAULT) |
| formalize_posix_acl(xvalue, &value_len); |
| |
| name_len = strlen(xname); |
| index = (crc32(0, xname, name_len) ^ crc32(0, xvalue, value_len)) % XATTRENTRY_HASHSIZE; |
| for (xe = xentry_hash[index]; xe; xe = xe->next) { |
| if (xe->xprefix == xprefix |
| && xe->value_len == value_len |
| && !strcmp(xe->xname, xname) |
| && !memcmp(xe->xvalue, xvalue, value_len)) |
| break; |
| } |
| if (!xe) { |
| xe = create_xattr_entry(xprefix, xname, xvalue, value_len); |
| xe->next = xentry_hash[index]; |
| xentry_hash[index] = xe; |
| } |
| return xe; |
| } |
| |
| static void write_xattr_entry(struct filesystem_entry *e) |
| { |
| struct jffs2_raw_xref ref; |
| struct xattr_entry *xe; |
| char xlist[XATTR_BUFFER_SIZE], xvalue[XATTR_BUFFER_SIZE]; |
| char *xname, *prefix_str; |
| int i, xprefix, prefix_len; |
| int list_sz, offset, name_len, value_len; |
| |
| if (!enable_xattr) |
| return; |
| |
| list_sz = llistxattr(e->hostname, xlist, XATTR_BUFFER_SIZE); |
| if (list_sz < 0) { |
| if (verbose) |
| printf("llistxattr('%s') = %d : %s\n", |
| e->hostname, errno, strerror(errno)); |
| return; |
| } |
| |
| for (offset = 0; offset < list_sz; offset += name_len) { |
| xname = xlist + offset; |
| name_len = strlen(xname) + 1; |
| |
| for (i = 0; (xprefix = xprefix_tbl[i].xprefix); i++) { |
| prefix_str = xprefix_tbl[i].string; |
| prefix_len = xprefix_tbl[i].length; |
| if (prefix_str[prefix_len - 1] == '.') { |
| if (!strncmp(xname, prefix_str, prefix_len - 1)) |
| break; |
| } else { |
| if (!strcmp(xname, prefix_str)) |
| break; |
| } |
| } |
| if (!xprefix) { |
| if (verbose) |
| printf("%s: xattr '%s' is not supported.\n", |
| e->hostname, xname); |
| continue; |
| } |
| if ((enable_xattr & (1 << xprefix)) == 0) |
| continue; |
| |
| value_len = lgetxattr(e->hostname, xname, xvalue, XATTR_BUFFER_SIZE); |
| if (value_len < 0) { |
| if (verbose) |
| printf("lgetxattr('%s', '%s') = %d : %s\n", |
| e->hostname, xname, errno, strerror(errno)); |
| continue; |
| } |
| xe = find_xattr_entry(xprefix, xname + prefix_len, xvalue, value_len); |
| if (!xe) { |
| if (verbose) |
| printf("%s : xattr '%s' was ignored.\n", |
| e->hostname, xname); |
| continue; |
| } |
| |
| memset(&ref, 0, sizeof(ref)); |
| ref.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); |
| ref.nodetype = cpu_to_je16(JFFS2_NODETYPE_XREF); |
| ref.totlen = cpu_to_je32(sizeof(ref)); |
| ref.hdr_crc = cpu_to_je32(crc32(0, &ref, sizeof(struct jffs2_unknown_node) - 4)); |
| ref.ino = cpu_to_je32(e->ino); |
| ref.xid = cpu_to_je32(xe->xid); |
| ref.xseqno = cpu_to_je32(highest_xseqno += 2); |
| ref.node_crc = cpu_to_je32(crc32(0, &ref, sizeof(ref) - 4)); |
| |
| pad_block_if_less_than(sizeof(ref)); |
| full_write(out_fd, &ref, sizeof(ref)); |
| padword(); |
| } |
| } |
| |
| #else /* WITHOUT_XATTR */ |
| #define write_xattr_entry(x) |
| #endif |
| |
| static void recursive_populate_directory(struct filesystem_entry *dir) |
| { |
| struct filesystem_entry *e; |
| unsigned int wrote; |
| |
| if (verbose) { |
| printf("%s\n", dir->fullname); |
| } |
| write_xattr_entry(dir); /* for '/' */ |
| |
| e = dir->files; |
| while (e) { |
| if (e->sb.st_nlink >= 1 && |
| (e->ino = find_hardlink(e))) { |
| |
| write_dirent(e); |
| if (verbose) { |
| printf("\tL %04o %9lu %5d:%-3d %s\n", |
| e->sb.st_mode & ~S_IFMT, (unsigned long) e->ino, |
| (int) (e->sb.st_uid), (int) (e->sb.st_gid), |
| e->name); |
| } |
| } else switch (e->sb.st_mode & S_IFMT) { |
| case S_IFDIR: |
| if (verbose) { |
| printf("\td %04o %9lu %5d:%-3d %s\n", |
| e->sb.st_mode & ~S_IFMT, e->sb.st_size, |
| (int) (e->sb.st_uid), (int) (e->sb.st_gid), |
| e->name); |
| } |
| write_pipe(e); |
| write_xattr_entry(e); |
| break; |
| case S_IFSOCK: |
| if (verbose) { |
| printf("\ts %04o %9lu %5d:%-3d %s\n", |
| e->sb.st_mode & ~S_IFMT, e->sb.st_size, |
| (int) e->sb.st_uid, (int) e->sb.st_gid, e->name); |
| } |
| write_pipe(e); |
| write_xattr_entry(e); |
| break; |
| case S_IFIFO: |
| if (verbose) { |
| printf("\tp %04o %9lu %5d:%-3d %s\n", |
| e->sb.st_mode & ~S_IFMT, e->sb.st_size, |
| (int) e->sb.st_uid, (int) e->sb.st_gid, e->name); |
| } |
| write_pipe(e); |
| write_xattr_entry(e); |
| break; |
| case S_IFCHR: |
| if (verbose) { |
| printf("\tc %04o %4d,%4d %5d:%-3d %s\n", |
| e->sb.st_mode & ~S_IFMT, major(e->sb.st_rdev), |
| minor(e->sb.st_rdev), (int) e->sb.st_uid, |
| (int) e->sb.st_gid, e->name); |
| } |
| write_special_file(e); |
| write_xattr_entry(e); |
| break; |
| case S_IFBLK: |
| if (verbose) { |
| printf("\tb %04o %4d,%4d %5d:%-3d %s\n", |
| e->sb.st_mode & ~S_IFMT, major(e->sb.st_rdev), |
| minor(e->sb.st_rdev), (int) e->sb.st_uid, |
| (int) e->sb.st_gid, e->name); |
| } |
| write_special_file(e); |
| write_xattr_entry(e); |
| break; |
| case S_IFLNK: |
| if (verbose) { |
| printf("\tl %04o %9lu %5d:%-3d %s -> %s\n", |
| e->sb.st_mode & ~S_IFMT, e->sb.st_size, |
| (int) e->sb.st_uid, (int) e->sb.st_gid, e->name, |
| e->link); |
| } |
| write_symlink(e); |
| write_xattr_entry(e); |
| break; |
| case S_IFREG: |
| wrote = write_regular_file(e); |
| write_xattr_entry(e); |
| if (verbose) { |
| printf("\tf %04o %9lu (%9u) %5d:%-3d %s\n", |
| e->sb.st_mode & ~S_IFMT, e->sb.st_size, wrote, |
| (int) e->sb.st_uid, (int) e->sb.st_gid, e->name); |
| } |
| break; |
| default: |
| error_msg("Unknown mode %o for %s", e->sb.st_mode, |
| e->fullname); |
| break; |
| } |
| e = e->next; |
| } |
| |
| e = dir->files; |
| while (e) { |
| if (S_ISDIR(e->sb.st_mode)) { |
| if (e->files) { |
| recursive_populate_directory(e); |
| } else if (verbose) { |
| printf("%s\n", e->fullname); |
| } |
| } |
| e = e->next; |
| } |
| } |
| |
| static void create_target_filesystem(struct filesystem_entry *root) |
| { |
| cleanmarker.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); |
| cleanmarker.nodetype = cpu_to_je16(JFFS2_NODETYPE_CLEANMARKER); |
| cleanmarker.totlen = cpu_to_je32(cleanmarker_size); |
| cleanmarker.hdr_crc = cpu_to_je32(crc32(0, &cleanmarker, sizeof(struct jffs2_unknown_node)-4)); |
| |
| if (ino == 0) |
| ino = 1; |
| |
| root->ino = 1; |
| recursive_populate_directory(root); |
| |
| if (pad_fs_size == -1) { |
| padblock(); |
| } else { |
| if (pad_fs_size && add_cleanmarkers){ |
| padblock(); |
| while (out_ofs < pad_fs_size) { |
| full_write(out_fd, &cleanmarker, sizeof(cleanmarker)); |
| pad(cleanmarker_size - sizeof(cleanmarker)); |
| padblock(); |
| } |
| } else { |
| while (out_ofs < pad_fs_size) { |
| full_write(out_fd, ffbuf, min(sizeof(ffbuf), pad_fs_size - out_ofs)); |
| } |
| |
| } |
| } |
| } |
| |
| static struct option long_options[] = { |
| {"pad", 2, NULL, 'p'}, |
| {"root", 1, NULL, 'r'}, |
| {"pagesize", 1, NULL, 's'}, |
| {"eraseblock", 1, NULL, 'e'}, |
| {"output", 1, NULL, 'o'}, |
| {"help", 0, NULL, 'h'}, |
| {"verbose", 0, NULL, 'v'}, |
| {"version", 0, NULL, 'V'}, |
| {"big-endian", 0, NULL, 'b'}, |
| {"little-endian", 0, NULL, 'l'}, |
| {"no-cleanmarkers", 0, NULL, 'n'}, |
| {"cleanmarker", 1, NULL, 'c'}, |
| {"squash", 0, NULL, 'q'}, |
| {"squash-uids", 0, NULL, 'U'}, |
| {"squash-perms", 0, NULL, 'P'}, |
| {"faketime", 0, NULL, 'f'}, |
| {"devtable", 1, NULL, 'D'}, |
| {"compression-mode", 1, NULL, 'm'}, |
| {"disable-compressor", 1, NULL, 'x'}, |
| {"test-compression", 0, NULL, 't'}, |
| {"compressor-priority", 1, NULL, 'y'}, |
| {"incremental", 1, NULL, 'i'}, |
| #ifndef WITHOUT_XATTR |
| {"with-xattr", 0, NULL, 1000 }, |
| {"with-selinux", 0, NULL, 1001 }, |
| {"with-posix-acl", 0, NULL, 1002 }, |
| #endif |
| {NULL, 0, NULL, 0} |
| }; |
| |
| static char *helptext = |
| "Usage: mkfs.jffs2 [OPTIONS]\n" |
| "Make a JFFS2 file system image from an existing directory tree\n\n" |
| "Options:\n" |
| " -p, --pad[=SIZE] Pad output to SIZE bytes with 0xFF. If SIZE is\n" |
| " not specified, the output is padded to the end of\n" |
| " the final erase block\n" |
| " -r, -d, --root=DIR Build file system from directory DIR (default: cwd)\n" |
| " -s, --pagesize=SIZE Use page size (max data node size) SIZE (default: 4KiB)\n" |
| " -e, --eraseblock=SIZE Use erase block size SIZE (default: 64KiB)\n" |
| " -c, --cleanmarker=SIZE Size of cleanmarker (default 12)\n" |
| " -m, --compr-mode=MODE Select compression mode (default: priortiry)\n" |
| " -x, --disable-compressor=COMPRESSOR_NAME\n" |
| " Disable a compressor\n" |
| " -X, --enable-compressor=COMPRESSOR_NAME\n" |
| " Enable a compressor\n" |
| " -y, --compressor-priority=PRIORITY:COMPRESSOR_NAME\n" |
| " Set the priority of a compressor\n" |
| " -L, --list-compressors Show the list of the avaiable compressors\n" |
| " -t, --test-compression Call decompress and compare with the original (for test)\n" |
| " -n, --no-cleanmarkers Don't add a cleanmarker to every eraseblock\n" |
| " -o, --output=FILE Output to FILE (default: stdout)\n" |
| " -l, --little-endian Create a little-endian filesystem\n" |
| " -b, --big-endian Create a big-endian filesystem\n" |
| " -D, --devtable=FILE Use the named FILE as a device table file\n" |
| " -f, --faketime Change all file times to '0' for regression testing\n" |
| " -q, --squash Squash permissions and owners making all files be owned by root\n" |
| " -U, --squash-uids Squash owners making all files be owned by root\n" |
| " -P, --squash-perms Squash permissions on all files\n" |
| #ifndef WITHOUT_XATTR |
| " --with-xattr stuff all xattr entries into image\n" |
| " --with-selinux stuff only SELinux Labels into jffs2 image\n" |
| " --with-posix-acl stuff only POSIX ACL entries into jffs2 image\n" |
| #endif |
| " -h, --help Display this help text\n" |
| " -v, --verbose Verbose operation\n" |
| " -V, --version Display version information\n" |
| " -i, --incremental=FILE Parse FILE and generate appendage output for it\n\n"; |
| |
| static char *revtext = "1.60"; |
| |
| int load_next_block() { |
| |
| int ret; |
| ret = read(in_fd, file_buffer, erase_block_size); |
| |
| if(verbose) |
| printf("Load next block : %d bytes read\n",ret); |
| |
| return ret; |
| } |
| |
| void process_buffer(int inp_size) { |
| uint8_t *p = file_buffer; |
| union jffs2_node_union *node; |
| uint16_t type; |
| int bitchbitmask = 0; |
| int obsolete; |
| |
| char name[256]; |
| |
| while ( p < (file_buffer + inp_size)) { |
| |
| node = (union jffs2_node_union *) p; |
| |
| /* Skip empty space */ |
| if (je16_to_cpu (node->u.magic) == 0xFFFF && je16_to_cpu (node->u.nodetype) == 0xFFFF) { |
| p += 4; |
| continue; |
| } |
| |
| if (je16_to_cpu (node->u.magic) != JFFS2_MAGIC_BITMASK) { |
| if (!bitchbitmask++) |
| printf ("Wrong bitmask at 0x%08x, 0x%04x\n", p - file_buffer, je16_to_cpu (node->u.magic)); |
| p += 4; |
| continue; |
| } |
| |
| bitchbitmask = 0; |
| |
| type = je16_to_cpu(node->u.nodetype); |
| if ((type & JFFS2_NODE_ACCURATE) != JFFS2_NODE_ACCURATE) { |
| obsolete = 1; |
| type |= JFFS2_NODE_ACCURATE; |
| } else |
| obsolete = 0; |
| |
| node->u.nodetype = cpu_to_je16(type); |
| |
| switch(je16_to_cpu(node->u.nodetype)) { |
| |
| case JFFS2_NODETYPE_INODE: |
| if(verbose) |
| printf ("%8s Inode node at 0x%08x, totlen 0x%08x, #ino %5d, version %5d, isize %8d, csize %8d, dsize %8d, offset %8d\n", |
| obsolete ? "Obsolete" : "", |
| p - file_buffer, je32_to_cpu (node->i.totlen), je32_to_cpu (node->i.ino), |
| je32_to_cpu ( node->i.version), je32_to_cpu (node->i.isize), |
| je32_to_cpu (node->i.csize), je32_to_cpu (node->i.dsize), je32_to_cpu (node->i.offset)); |
| |
| if ( je32_to_cpu (node->i.ino) > ino ) |
| ino = je32_to_cpu (node->i.ino); |
| |
| p += PAD(je32_to_cpu (node->i.totlen)); |
| break; |
| |
| case JFFS2_NODETYPE_DIRENT: |
| memcpy (name, node->d.name, node->d.nsize); |
| name [node->d.nsize] = 0x0; |
| |
| if(verbose) |
| printf ("%8s Dirent node at 0x%08x, totlen 0x%08x, #pino %5d, version %5d, #ino %8d, nsize %8d, name %s\n", |
| obsolete ? "Obsolete" : "", |
| p - file_buffer, je32_to_cpu (node->d.totlen), je32_to_cpu (node->d.pino), |
| je32_to_cpu ( node->d.version), je32_to_cpu (node->d.ino), |
| node->d.nsize, name); |
| |
| p += PAD(je32_to_cpu (node->d.totlen)); |
| break; |
| |
| case JFFS2_NODETYPE_CLEANMARKER: |
| if (verbose) { |
| printf ("%8s Cleanmarker at 0x%08x, totlen 0x%08x\n", |
| obsolete ? "Obsolete" : "", |
| p - file_buffer, je32_to_cpu (node->u.totlen)); |
| } |
| |
| p += PAD(je32_to_cpu (node->u.totlen)); |
| break; |
| |
| case JFFS2_NODETYPE_PADDING: |
| if (verbose) { |
| printf ("%8s Padding node at 0x%08x, totlen 0x%08x\n", |
| obsolete ? "Obsolete" : "", |
| p - file_buffer, je32_to_cpu (node->u.totlen)); |
| } |
| |
| p += PAD(je32_to_cpu (node->u.totlen)); |
| break; |
| |
| case 0xffff: |
| p += 4; |
| break; |
| |
| default: |
| if (verbose) { |
| printf ("%8s Unknown node at 0x%08x, totlen 0x%08x\n", |
| obsolete ? "Obsolete" : "", |
| p - file_buffer, je32_to_cpu (node->u.totlen)); |
| } |
| |
| p += PAD(je32_to_cpu (node->u.totlen)); |
| } |
| } |
| } |
| |
| void parse_image(){ |
| int ret; |
| |
| file_buffer = malloc(erase_block_size); |
| |
| if (!file_buffer) { |
| perror("out of memory"); |
| close (in_fd); |
| close (out_fd); |
| exit(1); |
| } |
| |
| while ((ret = load_next_block())) { |
| process_buffer(ret); |
| } |
| |
| if (file_buffer) |
| free(file_buffer); |
| |
| close(in_fd); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int c, opt; |
| char *cwd; |
| struct stat sb; |
| FILE *devtable = NULL; |
| struct filesystem_entry *root; |
| char *compr_name = NULL; |
| int compr_prior = -1; |
| int warn_page_size = 0; |
| |
| page_size = sysconf(_SC_PAGESIZE); |
| if (page_size < 0) /* System doesn't know so ... */ |
| page_size = 4096; /* ... we make an educated guess */ |
| if (page_size != 4096) |
| warn_page_size = 1; /* warn user if page size not 4096 */ |
| |
| jffs2_compressors_init(); |
| |
| while ((opt = getopt_long(argc, argv, |
| "D:d:r:s:o:qUPfh?vVe:lbp::nc:m:x:X:Lty:i:", long_options, &c)) >= 0) |
| { |
| switch (opt) { |
| case 'D': |
| devtable = xfopen(optarg, "r"); |
| if (fstat(fileno(devtable), &sb) < 0) |
| perror_msg_and_die(optarg); |
| if (sb.st_size < 10) |
| error_msg_and_die("%s: not a proper device table file", optarg); |
| break; |
| |
| case 'r': |
| case 'd': /* for compatibility with mkfs.jffs, genext2fs, etc... */ |
| if (rootdir != default_rootdir) { |
| error_msg_and_die("root directory specified more than once"); |
| } |
| rootdir = xstrdup(optarg); |
| break; |
| |
| case 's': |
| page_size = strtol(optarg, NULL, 0); |
| warn_page_size = 0; /* set by user, so don't need to warn */ |
| break; |
| |
| case 'o': |
| if (out_fd != -1) { |
| error_msg_and_die("output filename specified more than once"); |
| } |
| out_fd = open(optarg, O_CREAT | O_TRUNC | O_RDWR, 0644); |
| if (out_fd == -1) { |
| perror_msg_and_die("open output file"); |
| } |
| break; |
| |
| case 'q': |
| squash_uids = 1; |
| squash_perms = 1; |
| break; |
| |
| case 'U': |
| squash_uids = 1; |
| break; |
| |
| case 'P': |
| squash_perms = 1; |
| break; |
| |
| case 'f': |
| fake_times = 1; |
| break; |
| |
| case 'h': |
| case '?': |
| error_msg_and_die(helptext); |
| |
| case 'v': |
| verbose = 1; |
| break; |
| |
| case 'V': |
| error_msg_and_die("revision %s\n", revtext); |
| |
| case 'e': { |
| char *next; |
| unsigned units = 0; |
| erase_block_size = strtol(optarg, &next, 0); |
| if (!erase_block_size) |
| error_msg_and_die("Unrecognisable erase size\n"); |
| |
| if (*next) { |
| if (!strcmp(next, "KiB")) { |
| units = 1024; |
| } else if (!strcmp(next, "MiB")) { |
| units = 1024 * 1024; |
| } else { |
| error_msg_and_die("Unknown units in erasesize\n"); |
| } |
| } else { |
| if (erase_block_size < 0x1000) |
| units = 1024; |
| else |
| units = 1; |
| } |
| erase_block_size *= units; |
| |
| /* If it's less than 8KiB, they're not allowed */ |
| if (erase_block_size < 0x2000) { |
| fprintf(stderr, "Erase size 0x%x too small. Increasing to 8KiB minimum\n", |
| erase_block_size); |
| erase_block_size = 0x2000; |
| } |
| break; |
| } |
| |
| case 'l': |
| target_endian = __LITTLE_ENDIAN; |
| break; |
| |
| case 'b': |
| target_endian = __BIG_ENDIAN; |
| break; |
| |
| case 'p': |
| if (optarg) |
| pad_fs_size = strtol(optarg, NULL, 0); |
| else |
| pad_fs_size = -1; |
| break; |
| case 'n': |
| add_cleanmarkers = 0; |
| break; |
| case 'c': |
| cleanmarker_size = strtol(optarg, NULL, 0); |
| if (cleanmarker_size < sizeof(cleanmarker)) { |
| error_msg_and_die("cleanmarker size must be >= 12"); |
| } |
| if (cleanmarker_size >= erase_block_size) { |
| error_msg_and_die("cleanmarker size must be < eraseblock size"); |
| } |
| break; |
| case 'm': |
| if (jffs2_set_compression_mode_name(optarg)) { |
| error_msg_and_die("Unknown compression mode %s", optarg); |
| } |
| break; |
| case 'x': |
| if (jffs2_disable_compressor_name(optarg)) { |
| error_msg_and_die("Unknown compressor name %s",optarg); |
| } |
| break; |
| case 'X': |
| if (jffs2_enable_compressor_name(optarg)) { |
| error_msg_and_die("Unknown compressor name %s",optarg); |
| } |
| break; |
| case 'L': |
| error_msg_and_die("\n%s",jffs2_list_compressors()); |
| break; |
| case 't': |
| jffs2_compression_check_set(1); |
| break; |
| case 'y': |
| compr_name = malloc(strlen(optarg)); |
| sscanf(optarg,"%d:%s",&compr_prior,compr_name); |
| if ((compr_prior>=0)&&(compr_name)) { |
| if (jffs2_set_compressor_priority(compr_name, compr_prior)) |
| exit(EXIT_FAILURE); |
| } |
| else { |
| error_msg_and_die("Cannot parse %s",optarg); |
| } |
| free(compr_name); |
| break; |
| case 'i': |
| if (in_fd != -1) { |
| error_msg_and_die("(incremental) filename specified more than once"); |
| } |
| in_fd = open(optarg, O_RDONLY); |
| if (in_fd == -1) { |
| perror_msg_and_die("cannot open (incremental) file"); |
| } |
| break; |
| #ifndef WITHOUT_XATTR |
| case 1000: /* --with-xattr */ |
| enable_xattr |= (1 << JFFS2_XPREFIX_USER) |
| | (1 << JFFS2_XPREFIX_SECURITY) |
| | (1 << JFFS2_XPREFIX_ACL_ACCESS) |
| | (1 << JFFS2_XPREFIX_ACL_DEFAULT) |
| | (1 << JFFS2_XPREFIX_TRUSTED); |
| break; |
| case 1001: /* --with-selinux */ |
| enable_xattr |= (1 << JFFS2_XPREFIX_SECURITY); |
| break; |
| case 1002: /* --with-posix-acl */ |
| enable_xattr |= (1 << JFFS2_XPREFIX_ACL_ACCESS) |
| | (1 << JFFS2_XPREFIX_ACL_DEFAULT); |
| break; |
| #endif |
| } |
| } |
| if (warn_page_size) { |
| error_msg("Page size for this system is by default %d", page_size); |
| error_msg("Use the --pagesize=SIZE option if this is not what you want"); |
| } |
| if (out_fd == -1) { |
| if (isatty(1)) { |
| error_msg_and_die(helptext); |
| } |
| out_fd = 1; |
| } |
| if (lstat(rootdir, &sb)) { |
| perror_msg_and_die("%s", rootdir); |
| } |
| if (chdir(rootdir)) |
| perror_msg_and_die("%s", rootdir); |
| |
| if (!(cwd = getcwd(0, GETCWD_SIZE))) |
| perror_msg_and_die("getcwd failed"); |
| |
| if(in_fd != -1) |
| parse_image(); |
| |
| root = recursive_add_host_directory(NULL, "/", cwd); |
| |
| if (devtable) |
| parse_device_table(root, devtable); |
| |
| create_target_filesystem(root); |
| |
| cleanup(root); |
| |
| if (rootdir != default_rootdir) |
| free(rootdir); |
| |
| close(out_fd); |
| |
| if (verbose) { |
| char *s = jffs2_stats(); |
| fprintf(stderr,"\n\n%s",s); |
| free(s); |
| } |
| if ((verbose)||(jffs2_compression_check_get()&&(jffs2_compression_check_errorcnt_get()))) { |
| fprintf(stderr,"Compression errors: %d\n",jffs2_compression_check_errorcnt_get()); |
| } |
| |
| jffs2_compressors_exit(); |
| |
| return 0; |
| } |