Commit e6adbbcd authored by Daniel Quinlan's avatar Daniel Quinlan Committed by Linus Torvalds

[PATCH] cramfs updates for 2.5.6-pre2

Documentation/filesystems/cramfs.txt
  - remove comment about ROM size limit
  - fix up magic
  - update location of tools

fs/cramfs/README
  - add note about sorted directory entries
  - mkcramfs compile-time DO_HOLES option replaced by run-time -z option
  - update tools section
  - add note about PAGE_CACHE_SIZE possibly changing on arm and ia64

fs/cramfs/inode.c
  - statfs->f_namelen = CRAMFS_MAXPATHLEN

include/linux/cramfs_fs.h
  - add CRAMFS_MAXPATHLEN (252)
  - clarify CRAMFS_SUPPORTED_FLAGS definition

scripts/cramfs
  - directory removed, the cramfs user-space tools are now located at
    <http://sourceforge.net/projects/cramfs/>.
parent 6282c8d3
......@@ -10,7 +10,7 @@ diskspace than traditional filesystems.
You can't write to a cramfs filesystem (making it compressible and
compact also makes it _very_ hard to update on-the-fly), so you have to
create the disk image with the "mkcramfs" utility in scripts/cramfs.
create the disk image with the "mkcramfs" utility.
Usage Notes
......@@ -19,9 +19,7 @@ Usage Notes
File sizes are limited to less than 16MB.
Maximum filesystem size is a little over 256MB. (The last file on the
filesystem is allowed to extend past 256MB.) (Comments in mkcramfs.c
suggest that ROM sizes may be limited to 64MB, though that's not a
limitation in cramfs code.)
filesystem is allowed to extend past 256MB.)
Only the low 8 bits of gid are stored. The current version of
mkcramfs simply truncates to 8 bits, which is a potential security
......@@ -48,18 +46,28 @@ mind the filesystem becoming unreadable to future kernels.
For /usr/share/magic
------------------
--------------------
0 long 0x28cd3d45 Linux cramfs
>4 long x size %d
>8 long x flags 0x%x
>12 long x future 0x%x
0 ulelong 0x28cd3d45 Linux cramfs offset 0
>4 ulelong x size %d
>8 ulelong x flags 0x%x
>12 ulelong x future 0x%x
>16 string >\0 signature "%.16s"
>32 long x fsid.crc 0x%x
>36 long x fsid.edition %d
>40 long x fsid.blocks %d
>44 long x fsid.files %d
>32 ulelong x fsid.crc 0x%x
>36 ulelong x fsid.edition %d
>40 ulelong x fsid.blocks %d
>44 ulelong x fsid.files %d
>48 string >\0 name "%.16s"
512 ulelong 0x28cd3d45 Linux cramfs offset 512
>516 ulelong x size %d
>520 ulelong x flags 0x%x
>524 ulelong x future 0x%x
>528 string >\0 signature "%.16s"
>544 ulelong x fsid.crc 0x%x
>548 ulelong x fsid.edition %d
>552 ulelong x fsid.blocks %d
>556 ulelong x fsid.files %d
>560 string >\0 name "%.16s"
Hacker Notes
......
......@@ -6,8 +6,8 @@ a bit looser, e.g. it doesn't care if the <file_data> items are
swapped around (though it does care that directory entries (inodes) in
a given directory are contiguous, as this is used by readdir).
All data is in host-endian format; neither mkcramfs nor the kernel
ever do swabbing. (See section `Block Size' below.)
All data is currently in host-endian format; neither mkcramfs nor the
kernel ever do swabbing. (See section `Block Size' below.)
<filesystem>:
<superblock>
......@@ -29,6 +29,10 @@ same order as `ls -AUR' (but without the /^\..*:$/ directory header
lines); put another way, the same order as `find -type d -exec
ls -AU1 {} \;'.
Beginning in 2.4.7, directory entries are sorted. This optimization
allows cramfs_lookup to return more quickly when a filename does not
exist, speeds up user-space directory sorts, etc.
<data>:
One <file_data> for each file that's either a symlink or a
regular file of non-zero st_size.
......@@ -63,17 +67,15 @@ Holes
This kernel supports cramfs holes (i.e. [efficient representation of]
blocks in uncompressed data consisting entirely of NUL bytes), but by
default mkcramfs doesn't test for & create holes, since cramfs in
kernels up to at least 2.3.39 didn't support holes. Compile mkcramfs
with -DDO_HOLES if you want it to create files that can have holes in
them.
kernels up to at least 2.3.39 didn't support holes. Run mkcramfs
with -z if you want it to create files that can have holes in them.
Tools
-----
If you're hacking on cramfs, you might find useful some tools for
testing cramfs at <http://cvs.bofh.asn.au/cramfs/>, including a
rudimentary fsck for cramfs.
The cramfs user-space tools, including mkcramfs and cramfsck, are
located at <http://sourceforge.net/projects/cramfs/>.
Future Development
......@@ -103,8 +105,8 @@ require the least amount of change: just change `#define
PAGE_CACHE_SIZE (4096)' to `#include <asm/page.h>'. The disadvantage
is that the generated cramfs cannot always be shared between different
kernels, not even necessarily kernels of the same architecture if
PAGE_CACHE_SIZE is subject to change between kernel versions.
PAGE_CACHE_SIZE is subject to change between kernel versions
(currently possible with arm and ia64).
The remaining options try to make cramfs more sharable.
......
......@@ -266,7 +266,7 @@ static int cramfs_statfs(struct super_block *sb, struct statfs *buf)
buf->f_bavail = 0;
buf->f_files = sb->CRAMFS_SB_FILES;
buf->f_ffree = 0;
buf->f_namelen = 255;
buf->f_namelen = CRAMFS_MAXPATHLEN;
return 0;
}
......@@ -476,4 +476,3 @@ static void __exit exit_cramfs_fs(void)
module_init(init_cramfs_fs)
module_exit(exit_cramfs_fs)
MODULE_LICENSE("GPL");
......@@ -23,6 +23,12 @@ typedef unsigned int u32;
#define CRAMFS_NAMELEN_WIDTH 6
#define CRAMFS_OFFSET_WIDTH 26
/*
* Since inode.namelen is a unsigned 6-bit number, the maximum cramfs
* path length is 63 << 2 = 252.
*/
#define CRAMFS_MAXPATHLEN (((1 << CRAMFS_NAMELEN_WIDTH) - 1) << 2)
/*
* Reasonably terse representation of the inode data.
*/
......@@ -54,12 +60,12 @@ struct cramfs_info {
struct cramfs_super {
u32 magic; /* 0x28cd3d45 - random number */
u32 size; /* length in bytes */
u32 flags; /* 0 */
u32 future; /* 0 */
u32 flags; /* feature flags */
u32 future; /* reserved for future use */
u8 signature[16]; /* "Compressed ROMFS" */
struct cramfs_info fsid; /* unique filesystem info */
u8 name[16]; /* user-defined name */
struct cramfs_inode root; /* Root inode data */
struct cramfs_inode root; /* root inode data */
};
/*
......@@ -79,7 +85,10 @@ struct cramfs_super {
* if (flags & ~CRAMFS_SUPPORTED_FLAGS). Maybe that should be
* changed to test super.future instead.
*/
#define CRAMFS_SUPPORTED_FLAGS (0x7ff)
#define CRAMFS_SUPPORTED_FLAGS ( 0x000000ff \
| CRAMFS_FLAG_HOLES \
| CRAMFS_FLAG_WRONG_SIGNATURE \
| CRAMFS_FLAG_SHIFTED_ROOT_OFFSET )
/* Uncompression interfaces to the underlying zlib */
int cramfs_uncompress_block(void *dst, int dstlen, void *src, int srclen);
......
CC = gcc
CFLAGS = -W -Wall -O2 -g
CPPFLAGS = -I../../include
LDLIBS = -lz
PROGS = mkcramfs cramfsck
all: $(PROGS)
distclean clean:
rm -f $(PROGS)
.PHONY: all clean
/*
* cramfsck - check a cramfs file system
*
* Copyright (C) 2000-2001 Transmeta Corporation
*
* 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
*
* 1999/12/03: Linus Torvalds (cramfs tester and unarchive program)
* 2000/06/03: Daniel Quinlan (CRC and length checking program)
* 2000/06/04: Daniel Quinlan (merged programs, added options, support
* for special files, preserve permissions and
* ownership, cramfs superblock v2, bogus mode
* test, pathname length test, etc.)
* 2000/06/06: Daniel Quinlan (support for holes, pretty-printing,
* symlink size test)
* 2000/07/11: Daniel Quinlan (file length tests, start at offset 0 or 512,
* fsck-compatible exit codes)
* 2000/07/15: Daniel Quinlan (initial support for block devices)
*/
/* compile-time options */
#define INCLUDE_FS_TESTS /* include cramfs checking and extraction */
#include <sys/types.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <getopt.h>
#include <sys/sysmacros.h>
#include <utime.h>
#include <sys/ioctl.h>
#define _LINUX_STRING_H_
#include <linux/fs.h>
#include <linux/cramfs_fs.h>
#include <zlib.h>
static const char *progname = "cramfsck";
static int fd; /* ROM image file descriptor */
static char *filename; /* ROM image filename */
struct cramfs_super *super; /* just find the cramfs superblock once */
static int opt_verbose = 0; /* 1 = verbose (-v), 2+ = very verbose (-vv) */
#ifdef INCLUDE_FS_TESTS
static int opt_extract = 0; /* extract cramfs (-x) */
char *extract_dir = NULL; /* extraction directory (-x) */
unsigned long start_inode = 1 << 28; /* start of first non-root inode */
unsigned long end_inode = 0; /* end of the directory structure */
unsigned long start_data = 1 << 28; /* start of the data (256 MB = max) */
unsigned long end_data = 0; /* end of the data */
/* true? cramfs_super < start_inode < end_inode <= start_data <= end_data */
static uid_t euid; /* effective UID */
#define PAD_SIZE 512
#define PAGE_CACHE_SIZE (4096)
/* Guarantee access to at least 8kB at a time */
#define ROMBUFFER_BITS 13
#define ROMBUFFERSIZE (1 << ROMBUFFER_BITS)
#define ROMBUFFERMASK (ROMBUFFERSIZE-1)
static char read_buffer[ROMBUFFERSIZE * 2];
static unsigned long read_buffer_block = ~0UL;
/* Uncompressing data structures... */
static char outbuffer[PAGE_CACHE_SIZE*2];
z_stream stream;
#endif /* INCLUDE_FS_TESTS */
/* Input status of 0 to print help and exit without an error. */
static void usage(int status)
{
FILE *stream = status ? stderr : stdout;
fprintf(stream, "usage: %s [-hv] [-x dir] file\n"
" -h print this help\n"
" -x dir extract into dir\n"
" -v be more verbose\n"
" file file to test\n", progname);
exit(status);
}
#ifdef INCLUDE_FS_TESTS
void print_node(char type, struct cramfs_inode *i, char *name)
{
char info[10];
if (S_ISCHR(i->mode) || (S_ISBLK(i->mode))) {
/* major/minor numbers can be as high as 2^12 or 4096 */
snprintf(info, 10, "%4d,%4d", major(i->size), minor(i->size));
}
else {
/* size be as high as 2^24 or 16777216 */
snprintf(info, 10, "%9d", i->size);
}
printf("%c %04o %s %5d:%-3d %s\n",
type, i->mode & ~S_IFMT, info, i->uid, i->gid, name);
}
/*
* Create a fake "blocked" access
*/
static void *romfs_read(unsigned long offset)
{
unsigned int block = offset >> ROMBUFFER_BITS;
if (block != read_buffer_block) {
read_buffer_block = block;
lseek(fd, block << ROMBUFFER_BITS, SEEK_SET);
read(fd, read_buffer, ROMBUFFERSIZE * 2);
}
return read_buffer + (offset & ROMBUFFERMASK);
}
static struct cramfs_inode *cramfs_iget(struct cramfs_inode * i)
{
struct cramfs_inode *inode = malloc(sizeof(struct cramfs_inode));
*inode = *i;
return inode;
}
static struct cramfs_inode *iget(unsigned int ino)
{
return cramfs_iget(romfs_read(ino));
}
void iput(struct cramfs_inode *inode)
{
free(inode);
}
/*
* Return the offset of the root directory,
* or 0 if none.
*/
static struct cramfs_inode *read_super(void)
{
unsigned long offset;
offset = super->root.offset << 2;
if (super->magic != CRAMFS_MAGIC)
return NULL;
if (memcmp(super->signature, CRAMFS_SIGNATURE, sizeof(super->signature)) != 0)
return NULL;
if (offset < sizeof(super))
return NULL;
return cramfs_iget(&super->root);
}
static int uncompress_block(void *src, int len)
{
int err;
stream.next_in = src;
stream.avail_in = len;
stream.next_out = (unsigned char *) outbuffer;
stream.avail_out = PAGE_CACHE_SIZE*2;
inflateReset(&stream);
err = inflate(&stream, Z_FINISH);
if (err != Z_STREAM_END) {
fprintf(stderr, "%s: error %d while decompressing! %p(%d)\n",
filename, err, src, len);
exit(4);
}
return stream.total_out;
}
static void change_file_status(char *path, struct cramfs_inode *i)
{
struct utimbuf epoch = { 0, 0 };
if (euid == 0) {
if (lchown(path, i->uid, i->gid) < 0) {
perror(path);
exit(8);
}
if (S_ISLNK(i->mode))
return;
if ((S_ISUID | S_ISGID) & i->mode) {
if (chmod(path, i->mode) < 0) {
perror(path);
exit(8);
}
}
}
if (S_ISLNK(i->mode))
return;
if (utime(path, &epoch) < 0) {
perror(path);
exit(8);
}
}
static void do_symlink(char *path, struct cramfs_inode *i)
{
unsigned long offset = i->offset << 2;
unsigned long curr = offset + 4;
unsigned long next = *(u32 *) romfs_read(offset);
unsigned long size;
if (next > end_data) {
end_data = next;
}
size = uncompress_block(romfs_read(curr), next - curr);
if (size != i->size) {
fprintf(stderr, "%s: size error in symlink `%s'\n",
filename, path);
exit(4);
}
outbuffer[size] = 0;
if (opt_verbose) {
char *str;
str = malloc(strlen(outbuffer) + strlen(path) + 5);
strcpy(str, path);
strncat(str, " -> ", 4);
strncat(str, outbuffer, size);
print_node('l', i, str);
if (opt_verbose > 1) {
printf(" uncompressing block at %ld to %ld (%ld)\n", curr, next, next - curr);
}
}
if (opt_extract) {
symlink(outbuffer, path);
change_file_status(path, i);
}
}
static void do_special_inode(char *path, struct cramfs_inode *i)
{
dev_t devtype = 0;
char type;
if (S_ISCHR(i->mode)) {
devtype = i->size;
type = 'c';
}
else if (S_ISBLK(i->mode)) {
devtype = i->size;
type = 'b';
}
else if (S_ISFIFO(i->mode))
type = 'p';
else if (S_ISSOCK(i->mode))
type = 's';
else {
fprintf(stderr, "%s: bogus mode on `%s' (%o)\n", filename, path, i->mode);
exit(4);
}
if (opt_verbose) {
print_node(type, i, path);
}
if (opt_extract) {
if (mknod(path, i->mode, devtype) < 0) {
perror(path);
exit(8);
}
change_file_status(path, i);
}
}
static void do_uncompress(int fd, unsigned long offset, unsigned long size)
{
unsigned long curr = offset + 4 * ((size + PAGE_CACHE_SIZE - 1) / PAGE_CACHE_SIZE);
do {
unsigned long out = PAGE_CACHE_SIZE;
unsigned long next = *(u32 *) romfs_read(offset);
if (next > end_data) {
end_data = next;
}
offset += 4;
if (curr == next) {
if (opt_verbose > 1) {
printf(" hole at %ld (%d)\n", curr, PAGE_CACHE_SIZE);
}
if (size < PAGE_CACHE_SIZE)
out = size;
memset(outbuffer, 0x00, out);
}
else {
if (opt_verbose > 1) {
printf(" uncompressing block at %ld to %ld (%ld)\n", curr, next, next - curr);
}
out = uncompress_block(romfs_read(curr), next - curr);
}
if (size >= PAGE_CACHE_SIZE) {
if (out != PAGE_CACHE_SIZE) {
fprintf(stderr, "%s: Non-block (%ld) bytes\n", filename, out);
exit(4);
}
} else {
if (out != size) {
fprintf(stderr, "%s: Non-size (%ld vs %ld) bytes\n", filename, out, size);
exit(4);
}
}
size -= out;
if (opt_extract) {
write(fd, outbuffer, out);
}
curr = next;
} while (size);
}
static void expand_fs(int pathlen, char *path, struct cramfs_inode *inode)
{
if (S_ISDIR(inode->mode)) {
int count = inode->size;
unsigned long offset = inode->offset << 2;
char *newpath = malloc(pathlen + 256);
if (count > 0 && offset < start_inode) {
start_inode = offset;
}
/* XXX - need to check end_inode for empty case? */
memcpy(newpath, path, pathlen);
newpath[pathlen] = '/';
pathlen++;
if (opt_verbose) {
print_node('d', inode, path);
}
if (opt_extract) {
mkdir(path, inode->mode);
change_file_status(path, inode);
}
while (count > 0) {
struct cramfs_inode *child = iget(offset);
int size;
int newlen = child->namelen << 2;
size = sizeof(struct cramfs_inode) + newlen;
count -= size;
offset += sizeof(struct cramfs_inode);
memcpy(newpath + pathlen, romfs_read(offset), newlen);
newpath[pathlen + newlen] = 0;
if ((pathlen + newlen) - strlen(newpath) > 3) {
fprintf(stderr, "%s: invalid cramfs--bad path length\n", filename);
exit(4);
}
expand_fs(strlen(newpath), newpath, child);
offset += newlen;
if (offset > end_inode) {
end_inode = offset;
}
}
return;
}
if (S_ISREG(inode->mode)) {
int fd = 0;
unsigned long offset = inode->offset << 2;
if (offset > 0 && offset < start_data) {
start_data = offset;
}
if (opt_verbose) {
print_node('f', inode, path);
}
if (opt_extract) {
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, inode->mode);
}
if (inode->size) {
do_uncompress(fd, offset, inode->size);
}
if (opt_extract) {
close(fd);
change_file_status(path, inode);
}
return;
}
if (S_ISLNK(inode->mode)) {
unsigned long offset = inode->offset << 2;
if (offset < start_data) {
start_data = offset;
}
do_symlink(path, inode);
return;
}
else {
do_special_inode(path, inode);
return;
}
}
#endif /* INCLUDE_FS_TESTS */
int main(int argc, char **argv)
{
void *buf;
size_t length;
struct stat st;
u32 crc_old, crc_new;
#ifdef INCLUDE_FS_TESTS
struct cramfs_inode *root;
#endif /* INCLUDE_FS_TESTS */
int c; /* for getopt */
int start = 0;
if (argc)
progname = argv[0];
/* command line options */
while ((c = getopt(argc, argv, "hx:v")) != EOF) {
switch (c) {
case 'h':
usage(0);
case 'x':
#ifdef INCLUDE_FS_TESTS
opt_extract = 1;
extract_dir = malloc(strlen(optarg) + 1);
strcpy(extract_dir, optarg);
break;
#else /* not INCLUDE_FS_TESTS */
fprintf(stderr, "%s: compiled without -x support\n",
progname);
exit(16);
#endif /* not INCLUDE_FS_TESTS */
case 'v':
opt_verbose++;
break;
}
}
if ((argc - optind) != 1)
usage(16);
filename = argv[optind];
/* find the physical size of the file or block device */
if (lstat(filename, &st) < 0) {
perror(filename);
exit(8);
}
fd = open(filename, O_RDONLY);
if (fd < 0) {
perror(filename);
exit(8);
}
if (S_ISBLK(st.st_mode)) {
if (ioctl(fd, BLKGETSIZE, &length) < 0) {
fprintf(stderr, "%s: warning--unable to determine filesystem size \n", filename);
exit(4);
}
length = length * 512;
}
else if (S_ISREG(st.st_mode)) {
length = st.st_size;
}
else {
fprintf(stderr, "%s is not a block device or file\n", filename);
exit(8);
}
if (length < sizeof(struct cramfs_super)) {
fprintf(stderr, "%s: invalid cramfs--file length too short\n", filename);
exit(4);
}
if (S_ISBLK(st.st_mode)) {
/* nasty because mmap of block devices fails */
buf = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
read(fd, buf, length);
}
else {
/* nice and easy */
buf = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
}
/* XXX - this could be cleaner... */
if (((struct cramfs_super *) buf)->magic == CRAMFS_MAGIC) {
start = 0;
super = (struct cramfs_super *) buf;
}
else if (length >= (PAD_SIZE + sizeof(struct cramfs_super)) &&
((((struct cramfs_super *) (buf + PAD_SIZE))->magic == CRAMFS_MAGIC)))
{
start = PAD_SIZE;
super = (struct cramfs_super *) (buf + PAD_SIZE);
}
else {
fprintf(stderr, "%s: invalid cramfs--wrong magic\n", filename);
exit(4);
}
if (super->flags & CRAMFS_FLAG_FSID_VERSION_2) {
/* length test */
if (length < super->size) {
fprintf(stderr, "%s: invalid cramfs--file length too short\n", filename);
exit(4);
}
else if (length > super->size) {
fprintf(stderr, "%s: warning--file length too long, padded image?\n", filename);
}
/* CRC test */
crc_old = super->fsid.crc;
super->fsid.crc = crc32(0L, Z_NULL, 0);
crc_new = crc32(0L, Z_NULL, 0);
crc_new = crc32(crc_new, (unsigned char *) buf+start, super->size - start);
if (crc_new != crc_old) {
fprintf(stderr, "%s: invalid cramfs--crc error\n", filename);
exit(4);
}
}
else {
fprintf(stderr, "%s: warning--old cramfs image, no CRC\n",
filename);
}
#ifdef INCLUDE_FS_TESTS
super = (struct cramfs_super *) malloc(sizeof(struct cramfs_super));
if (((struct cramfs_super *) buf)->magic == CRAMFS_MAGIC) {
memcpy(super, buf, sizeof(struct cramfs_super));
}
else if (length >= (PAD_SIZE + sizeof(struct cramfs_super)) &&
((((struct cramfs_super *) (buf + PAD_SIZE))->magic == CRAMFS_MAGIC)))
{
memcpy(super, (buf + PAD_SIZE), sizeof(struct cramfs_super));
}
munmap(buf, length);
/* file format test, uses fake "blocked" accesses */
root = read_super();
umask(0);
euid = geteuid();
if (!root) {
fprintf(stderr, "%s: invalid cramfs--bad superblock\n",
filename);
exit(4);
}
stream.next_in = NULL;
stream.avail_in = 0;
inflateInit(&stream);
if (!extract_dir) {
extract_dir = "root";
}
expand_fs(strlen(extract_dir), extract_dir, root);
inflateEnd(&stream);
if (start_data != 1 << 28 && end_inode != start_data) {
fprintf(stderr, "%s: invalid cramfs--directory data end (%ld) != file data start (%ld)\n", filename, end_inode, start_data);
exit(4);
}
if (super->flags & CRAMFS_FLAG_FSID_VERSION_2) {
if (end_data > super->size) {
fprintf(stderr, "%s: invalid cramfs--invalid file data offset\n", filename);
exit(4);
}
}
#endif /* INCLUDE_FS_TESTS */
exit(0);
}
/*
* mkcramfs - make a cramfs file system
*
* Copyright (C) 1999-2001 Transmeta Corporation
*
* 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
*/
#include <sys/types.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <getopt.h>
#include <linux/cramfs_fs.h>
#include <zlib.h>
#define PAD_SIZE 512 /* only 0 and 512 supported by kernel */
static const char *progname = "mkcramfs";
/* N.B. If you change the disk format of cramfs, please update fs/cramfs/README. */
/* Input status of 0 to print help and exit without an error. */
static void usage(int status)
{
FILE *stream = status ? stderr : stdout;
fprintf(stream, "usage: %s [-h] [-e edition] [-i file] [-n name] dirname outfile\n"
" -h print this help\n"
" -E make all warnings errors (non-zero exit status)\n"
" -e edition set edition number (part of fsid)\n"
" -i file insert a file image into the filesystem (requires >= 2.4.0)\n"
" -n name set name of cramfs filesystem\n"
" -p pad by %d bytes for boot code\n"
" -s sort directory entries (old option, ignored)\n"
" -z make explicit holes (requires >= 2.3.39)\n"
" dirname root of the filesystem to be compressed\n"
" outfile output file\n", progname, PAD_SIZE);
exit(status);
}
#define PAGE_CACHE_SIZE (4096)
/* The kernel assumes PAGE_CACHE_SIZE as block size. */
static unsigned int blksize = PAGE_CACHE_SIZE;
static long total_blocks = 0, total_nodes = 1; /* pre-count the root node */
static int image_length = 0;
/*
* If opt_holes is set, then mkcramfs can create explicit holes in the
* data, which saves 26 bytes per hole (which is a lot smaller a
* saving than most most filesystems).
*
* Note that kernels up to at least 2.3.39 don't support cramfs holes,
* which is why this is turned off by default.
*/
static int opt_edition = 0;
static int opt_errors = 0;
static int opt_holes = 0;
static int opt_pad = 0;
static char *opt_image = NULL;
static char *opt_name = NULL;
static int warn_dev, warn_gid, warn_namelen, warn_skip, warn_size, warn_uid;
#ifndef MIN
# define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
#endif
/* In-core version of inode / directory entry. */
struct entry {
/* stats */
char *name;
unsigned int mode, size, uid, gid;
/* FS data */
void *uncompressed;
/* points to other identical file */
struct entry *same;
unsigned int offset; /* pointer to compressed data in archive */
unsigned int dir_offset; /* Where in the archive is the directory entry? */
/* organization */
struct entry *child; /* null for non-directories and empty directories */
struct entry *next;
};
/*
* The longest file name component to allow for in the input directory tree.
* Ext2fs (and many others) allow up to 255 bytes. A couple of filesystems
* allow longer (e.g. smbfs 1024), but there isn't much use in supporting
* >255-byte names in the input directory tree given that such names get
* truncated to 255 bytes when written to cramfs.
*/
#define MAX_INPUT_NAMELEN 255
static int find_identical_file(struct entry *orig,struct entry *newfile)
{
if(orig==newfile) return 1;
if(!orig) return 0;
if(orig->size==newfile->size && orig->uncompressed && !memcmp(orig->uncompressed,newfile->uncompressed,orig->size)) {
newfile->same=orig;
return 1;
}
return find_identical_file(orig->child,newfile) ||
find_identical_file(orig->next,newfile);
}
static void eliminate_doubles(struct entry *root,struct entry *orig) {
if(orig) {
if(orig->size && orig->uncompressed)
find_identical_file(root,orig);
eliminate_doubles(root,orig->child);
eliminate_doubles(root,orig->next);
}
}
/*
* We define our own sorting function instead of using alphasort which
* uses strcoll and changes ordering based on locale information.
*/
static int cramsort (const void *a, const void *b)
{
return strcmp ((*(const struct dirent **) a)->d_name,
(*(const struct dirent **) b)->d_name);
}
static unsigned int parse_directory(struct entry *root_entry, const char *name, struct entry **prev, loff_t *fslen_ub)
{
struct dirent **dirlist;
int totalsize = 0, dircount, dirindex;
char *path, *endpath;
size_t len = strlen(name);
/* Set up the path. */
/* TODO: Reuse the parent's buffer to save memcpy'ing and duplication. */
path = malloc(len + 1 + MAX_INPUT_NAMELEN + 1);
if (!path) {
perror(NULL);
exit(8);
}
memcpy(path, name, len);
endpath = path + len;
*endpath = '/';
endpath++;
/* read in the directory and sort */
dircount = scandir(name, &dirlist, 0, cramsort);
if (dircount < 0) {
perror(name);
exit(8);
}
/* process directory */
for (dirindex = 0; dirindex < dircount; dirindex++) {
struct dirent *dirent;
struct entry *entry;
struct stat st;
int size;
size_t namelen;
dirent = dirlist[dirindex];
/* Ignore "." and ".." - we won't be adding them to the archive */
if (dirent->d_name[0] == '.') {
if (dirent->d_name[1] == '\0')
continue;
if (dirent->d_name[1] == '.') {
if (dirent->d_name[2] == '\0')
continue;
}
}
namelen = strlen(dirent->d_name);
if (namelen > MAX_INPUT_NAMELEN) {
fprintf(stderr,
"Very long (%u bytes) filename `%s' found.\n"
" Please increase MAX_INPUT_NAMELEN in mkcramfs.c and recompile. Exiting.\n",
namelen, dirent->d_name);
exit(8);
}
memcpy(endpath, dirent->d_name, namelen + 1);
if (lstat(path, &st) < 0) {
perror(endpath);
warn_skip = 1;
continue;
}
entry = calloc(1, sizeof(struct entry));
if (!entry) {
perror(NULL);
exit(8);
}
entry->name = strdup(dirent->d_name);
if (!entry->name) {
perror(NULL);
exit(8);
}
if (namelen > 255) {
/* Can't happen when reading from ext2fs. */
/* TODO: we ought to avoid chopping in half
multi-byte UTF8 characters. */
entry->name[namelen = 255] = '\0';
warn_namelen = 1;
}
entry->mode = st.st_mode;
entry->size = st.st_size;
entry->uid = st.st_uid;
if (entry->uid >= 1 << CRAMFS_UID_WIDTH)
warn_uid = 1;
entry->gid = st.st_gid;
if (entry->gid >= 1 << CRAMFS_GID_WIDTH)
/* TODO: We ought to replace with a default
gid instead of truncating; otherwise there
are security problems. Maybe mode should
be &= ~070. Same goes for uid once Linux
supports >16-bit uids. */
warn_gid = 1;
size = sizeof(struct cramfs_inode) + ((namelen + 3) & ~3);
*fslen_ub += size;
if (S_ISDIR(st.st_mode)) {
entry->size = parse_directory(root_entry, path, &entry->child, fslen_ub);
} else if (S_ISREG(st.st_mode)) {
/* TODO: We ought to open files in do_compress, one
at a time, instead of amassing all these memory
maps during parse_directory (which don't get used
until do_compress anyway). As it is, we tend to
get EMFILE errors (especially if mkcramfs is run
by non-root).
While we're at it, do analagously for symlinks
(which would just save a little memory). */
int fd = open(path, O_RDONLY);
if (fd < 0) {
perror(path);
warn_skip = 1;
continue;
}
if (entry->size) {
if ((entry->size >= 1 << CRAMFS_SIZE_WIDTH)) {
warn_size = 1;
entry->size = (1 << CRAMFS_SIZE_WIDTH) - 1;
}
entry->uncompressed = mmap(NULL, entry->size, PROT_READ, MAP_PRIVATE, fd, 0);
if (-1 == (int) (long) entry->uncompressed) {
perror("mmap");
exit(8);
}
}
close(fd);
} else if (S_ISLNK(st.st_mode)) {
entry->uncompressed = malloc(entry->size);
if (!entry->uncompressed) {
perror(NULL);
exit(8);
}
if (readlink(path, entry->uncompressed, entry->size) < 0) {
perror(path);
warn_skip = 1;
continue;
}
} else if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) {
/* maybe we should skip sockets */
entry->size = 0;
} else {
entry->size = st.st_rdev;
if (entry->size & -(1<<CRAMFS_SIZE_WIDTH))
warn_dev = 1;
}
if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
int blocks = ((entry->size - 1) / blksize + 1);
/* block pointers & data expansion allowance + data */
if(entry->size)
*fslen_ub += (4+26)*blocks + entry->size + 3;
}
/* Link it into the list */
*prev = entry;
prev = &entry->next;
totalsize += size;
}
free(path);
free(dirlist); /* allocated by scandir() with malloc() */
return totalsize;
}
/* Returns sizeof(struct cramfs_super), which includes the root inode. */
static unsigned int write_superblock(struct entry *root, char *base, int size)
{
struct cramfs_super *super = (struct cramfs_super *) base;
unsigned int offset = sizeof(struct cramfs_super) + image_length;
if (opt_pad) {
offset += opt_pad;
}
super->magic = CRAMFS_MAGIC;
super->flags = CRAMFS_FLAG_FSID_VERSION_2 | CRAMFS_FLAG_SORTED_DIRS;
if (opt_holes)
super->flags |= CRAMFS_FLAG_HOLES;
if (image_length > 0)
super->flags |= CRAMFS_FLAG_SHIFTED_ROOT_OFFSET;
super->size = size;
memcpy(super->signature, CRAMFS_SIGNATURE, sizeof(super->signature));
super->fsid.crc = crc32(0L, Z_NULL, 0);
super->fsid.edition = opt_edition;
super->fsid.blocks = total_blocks;
super->fsid.files = total_nodes;
memset(super->name, 0x00, sizeof(super->name));
if (opt_name)
strncpy(super->name, opt_name, sizeof(super->name));
else
strncpy(super->name, "Compressed", sizeof(super->name));
super->root.mode = root->mode;
super->root.uid = root->uid;
super->root.gid = root->gid;
super->root.size = root->size;
super->root.offset = offset >> 2;
return offset;
}
static void set_data_offset(struct entry *entry, char *base, unsigned long offset)
{
struct cramfs_inode *inode = (struct cramfs_inode *) (base + entry->dir_offset);
#ifdef DEBUG
assert ((offset & 3) == 0);
#endif /* DEBUG */
if (offset >= (1 << (2 + CRAMFS_OFFSET_WIDTH))) {
fprintf(stderr, "filesystem too big. Exiting.\n");
exit(8);
}
inode->offset = (offset >> 2);
}
/*
* We do a width-first printout of the directory
* entries, using a stack to remember the directories
* we've seen.
*/
#define MAXENTRIES (100)
static unsigned int write_directory_structure(struct entry *entry, char *base, unsigned int offset)
{
int stack_entries = 0;
struct entry *entry_stack[MAXENTRIES];
for (;;) {
int dir_start = stack_entries;
while (entry) {
struct cramfs_inode *inode = (struct cramfs_inode *) (base + offset);
size_t len = strlen(entry->name);
entry->dir_offset = offset;
inode->mode = entry->mode;
inode->uid = entry->uid;
inode->gid = entry->gid;
inode->size = entry->size;
inode->offset = 0;
/* Non-empty directories, regfiles and symlinks will
write over inode->offset later. */
offset += sizeof(struct cramfs_inode);
total_nodes++; /* another node */
memcpy(base + offset, entry->name, len);
/* Pad up the name to a 4-byte boundary */
while (len & 3) {
*(base + offset + len) = '\0';
len++;
}
inode->namelen = len >> 2;
offset += len;
/* TODO: this may get it wrong for chars >= 0x80.
Most filesystems use UTF8 encoding for filenames,
whereas the console is a single-byte character
set like iso-latin-1. */
printf(" %s\n", entry->name);
if (entry->child) {
if (stack_entries >= MAXENTRIES) {
fprintf(stderr, "Exceeded MAXENTRIES. Raise this value in mkcramfs.c and recompile. Exiting.\n");
exit(8);
}
entry_stack[stack_entries] = entry;
stack_entries++;
}
entry = entry->next;
}
/*
* Reverse the order the stack entries pushed during
* this directory, for a small optimization of disk
* access in the created fs. This change makes things
* `ls -UR' order.
*/
{
struct entry **lo = entry_stack + dir_start;
struct entry **hi = entry_stack + stack_entries;
struct entry *tmp;
while (lo < --hi) {
tmp = *lo;
*lo++ = *hi;
*hi = tmp;
}
}
/* Pop a subdirectory entry from the stack, and recurse. */
if (!stack_entries)
break;
stack_entries--;
entry = entry_stack[stack_entries];
set_data_offset(entry, base, offset);
printf("'%s':\n", entry->name);
entry = entry->child;
}
return offset;
}
static int is_zero(char const *begin, unsigned len)
{
if (opt_holes)
/* Returns non-zero iff the first LEN bytes from BEGIN are
all NULs. */
return (len-- == 0 ||
(begin[0] == '\0' &&
(len-- == 0 ||
(begin[1] == '\0' &&
(len-- == 0 ||
(begin[2] == '\0' &&
(len-- == 0 ||
(begin[3] == '\0' &&
memcmp(begin, begin + 4, len) == 0))))))));
else
/* Never create holes. */
return 0;
}
/*
* One 4-byte pointer per block and then the actual blocked
* output. The first block does not need an offset pointer,
* as it will start immediately after the pointer block;
* so the i'th pointer points to the end of the i'th block
* (i.e. the start of the (i+1)'th block or past EOF).
*
* Note that size > 0, as a zero-sized file wouldn't ever
* have gotten here in the first place.
*/
static unsigned int do_compress(char *base, unsigned int offset, char const *name, char *uncompressed, unsigned int size)
{
unsigned long original_size = size;
unsigned long original_offset = offset;
unsigned long new_size;
unsigned long blocks = (size - 1) / blksize + 1;
unsigned long curr = offset + 4 * blocks;
int change;
total_blocks += blocks;
do {
unsigned long len = 2 * blksize;
unsigned int input = size;
if (input > blksize)
input = blksize;
size -= input;
if (!is_zero (uncompressed, input)) {
compress(base + curr, &len, uncompressed, input);
curr += len;
}
uncompressed += input;
if (len > blksize*2) {
/* (I don't think this can happen with zlib.) */
printf("AIEEE: block \"compressed\" to > 2*blocklength (%ld)\n", len);
exit(8);
}
*(u32 *) (base + offset) = curr;
offset += 4;
} while (size);
curr = (curr + 3) & ~3;
new_size = curr - original_offset;
/* TODO: Arguably, original_size in these 2 lines should be
st_blocks * 512. But if you say that then perhaps
administrative data should also be included in both. */
change = new_size - original_size;
printf("%6.2f%% (%+d bytes)\t%s\n",
(change * 100) / (double) original_size, change, name);
return curr;
}
/*
* Traverse the entry tree, writing data for every item that has
* non-null entry->compressed (i.e. every symlink and non-empty
* regfile).
*/
static unsigned int write_data(struct entry *entry, char *base, unsigned int offset)
{
do {
if (entry->uncompressed) {
if(entry->same) {
set_data_offset(entry, base, entry->same->offset);
entry->offset=entry->same->offset;
} else {
set_data_offset(entry, base, offset);
entry->offset=offset;
offset = do_compress(base, offset, entry->name, entry->uncompressed, entry->size);
}
}
else if (entry->child)
offset = write_data(entry->child, base, offset);
entry=entry->next;
} while (entry);
return offset;
}
static unsigned int write_file(char *file, char *base, unsigned int offset)
{
int fd;
char *buf;
fd = open(file, O_RDONLY);
if (fd < 0) {
perror(file);
exit(8);
}
buf = mmap(NULL, image_length, PROT_READ, MAP_PRIVATE, fd, 0);
memcpy(base + offset, buf, image_length);
munmap(buf, image_length);
close (fd);
/* Pad up the image_length to a 4-byte boundary */
while (image_length & 3) {
*(base + offset + image_length) = '\0';
image_length++;
}
return (offset + image_length);
}
/*
* Maximum size fs you can create is roughly 256MB. (The last file's
* data must begin within 256MB boundary but can extend beyond that.)
*
* Note that if you want it to fit in a ROM then you're limited to what the
* hardware and kernel can support (64MB?).
*/
#define MAXFSLEN ((((1 << CRAMFS_OFFSET_WIDTH) - 1) << 2) /* offset */ \
+ (1 << CRAMFS_SIZE_WIDTH) - 1 /* filesize */ \
+ (1 << CRAMFS_SIZE_WIDTH) * 4 / PAGE_CACHE_SIZE /* block pointers */ )
/*
* Usage:
*
* mkcramfs directory-name outfile
*
* where "directory-name" is simply the root of the directory
* tree that we want to generate a compressed filesystem out
* of.
*/
int main(int argc, char **argv)
{
struct stat st; /* used twice... */
struct entry *root_entry;
char *rom_image;
ssize_t offset, written;
int fd;
/* initial guess (upper-bound) of required filesystem size */
loff_t fslen_ub = sizeof(struct cramfs_super);
char const *dirname, *outfile;
u32 crc = crc32(0L, Z_NULL, 0);
int c; /* for getopt */
total_blocks = 0;
if (argc)
progname = argv[0];
/* command line options */
while ((c = getopt(argc, argv, "hEe:i:n:psz")) != EOF) {
switch (c) {
case 'h':
usage(0);
case 'E':
opt_errors = 1;
break;
case 'e':
opt_edition = atoi(optarg);
break;
case 'i':
opt_image = optarg;
if (lstat(opt_image, &st) < 0) {
perror(opt_image);
exit(16);
}
image_length = st.st_size; /* may be padded later */
fslen_ub += (image_length + 3); /* 3 is for padding */
break;
case 'n':
opt_name = optarg;
break;
case 'p':
opt_pad = PAD_SIZE;
fslen_ub += PAD_SIZE;
break;
case 's':
/* old option, ignored */
break;
case 'z':
opt_holes = 1;
break;
}
}
if ((argc - optind) != 2)
usage(16);
dirname = argv[optind];
outfile = argv[optind + 1];
if (stat(dirname, &st) < 0) {
perror(dirname);
exit(16);
}
fd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
root_entry = calloc(1, sizeof(struct entry));
if (!root_entry) {
perror(NULL);
exit(8);
}
root_entry->mode = st.st_mode;
root_entry->uid = st.st_uid;
root_entry->gid = st.st_gid;
root_entry->size = parse_directory(root_entry, dirname, &root_entry->child, &fslen_ub);
/* always allocate a multiple of blksize bytes because that's
what we're going to write later on */
fslen_ub = ((fslen_ub - 1) | (blksize - 1)) + 1;
if (fslen_ub > MAXFSLEN) {
fprintf(stderr,
"warning: guestimate of required size (upper bound) is %LdMB, but maximum image size is %uMB. We might die prematurely.\n",
fslen_ub >> 20,
MAXFSLEN >> 20);
fslen_ub = MAXFSLEN;
}
/* find duplicate files. TODO: uses the most inefficient algorithm
possible. */
eliminate_doubles(root_entry,root_entry);
/* TODO: Why do we use a private/anonymous mapping here
followed by a write below, instead of just a shared mapping
and a couple of ftruncate calls? Is it just to save us
having to deal with removing the file afterwards? If we
really need this huge anonymous mapping, we ought to mmap
in smaller chunks, so that the user doesn't need nn MB of
RAM free. If the reason is to be able to write to
un-mmappable block devices, then we could try shared mmap
and revert to anonymous mmap if the shared mmap fails. */
rom_image = mmap(NULL, fslen_ub?fslen_ub:1, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (-1 == (int) (long) rom_image) {
perror("ROM image map");
exit(8);
}
/* Skip the first opt_pad bytes for boot loader code */
offset = opt_pad;
memset(rom_image, 0x00, opt_pad);
/* Skip the superblock and come back to write it later. */
offset += sizeof(struct cramfs_super);
/* Insert a file image. */
if (opt_image) {
printf("Including: %s\n", opt_image);
offset = write_file(opt_image, rom_image, offset);
}
offset = write_directory_structure(root_entry->child, rom_image, offset);
printf("Directory data: %d bytes\n", offset);
offset = write_data(root_entry, rom_image, offset);
/* We always write a multiple of blksize bytes, so that
losetup works. */
offset = ((offset - 1) | (blksize - 1)) + 1;
printf("Everything: %d kilobytes\n", offset >> 10);
/* Write the superblock now that we can fill in all of the fields. */
write_superblock(root_entry, rom_image+opt_pad, offset);
printf("Super block: %d bytes\n", sizeof(struct cramfs_super));
/* Put the checksum in. */
crc = crc32(crc, (rom_image+opt_pad), (offset-opt_pad));
((struct cramfs_super *) (rom_image+opt_pad))->fsid.crc = crc;
printf("CRC: %x\n", crc);
/* Check to make sure we allocated enough space. */
if (fslen_ub < offset) {
fprintf(stderr, "not enough space allocated for ROM image (%Ld allocated, %d used)\n",
fslen_ub, offset);
exit(8);
}
written = write(fd, rom_image, offset);
if (written < 0) {
perror("ROM image");
exit(8);
}
if (offset != written) {
fprintf(stderr, "ROM image write failed (%d %d)\n", written, offset);
exit(8);
}
/* (These warnings used to come at the start, but they scroll off the
screen too quickly.) */
if (warn_namelen) /* (can't happen when reading from ext2fs) */
fprintf(stderr, /* bytes, not chars: think UTF8. */
"warning: filenames truncated to 255 bytes.\n");
if (warn_skip)
fprintf(stderr, "warning: files were skipped due to errors.\n");
if (warn_size)
fprintf(stderr,
"warning: file sizes truncated to %luMB (minus 1 byte).\n",
1L << (CRAMFS_SIZE_WIDTH - 20));
if (warn_uid) /* (not possible with current Linux versions) */
fprintf(stderr,
"warning: uids truncated to %u bits. (This may be a security concern.)\n",
CRAMFS_UID_WIDTH);
if (warn_gid)
fprintf(stderr,
"warning: gids truncated to %u bits. (This may be a security concern.)\n",
CRAMFS_GID_WIDTH);
if (warn_dev)
fprintf(stderr,
"WARNING: device numbers truncated to %u bits. This almost certainly means\n"
"that some device files will be wrong.\n",
CRAMFS_OFFSET_WIDTH);
if (opt_errors &&
(warn_namelen||warn_skip||warn_size||warn_uid||warn_gid||warn_dev))
exit(8);
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment