| /* |
| * cramfs.c |
| * |
| * Copyright (C) 1999 Linus Torvalds |
| * |
| * Copyright (C) 2000-2002 Transmeta Corporation |
| * |
| * Copyright (C) 2003 Kai-Uwe Bloem, |
| * Auerswald GmbH & Co KG, <linux-development@auerswald.de> |
| * - adapted from the www.tuxbox.org barebox tree, added "ls" command |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License (Version 2) as |
| * published by the Free Software Foundation. |
| * |
| * Compressed ROM filesystem for Linux. |
| * |
| * TODO: |
| * add support for resolving symbolic links |
| */ |
| |
| /* |
| * These are the VFS interfaces to the compressed ROM filesystem. |
| * The actual compression is based on zlib, see the other files. |
| */ |
| |
| #include <common.h> |
| #include <malloc.h> |
| #include <driver.h> |
| #include <init.h> |
| #include <errno.h> |
| #include <fs.h> |
| #include <xfuncs.h> |
| |
| #include <asm/byteorder.h> |
| #include <linux/stat.h> |
| #include <cramfs/cramfs_fs.h> |
| |
| /* These two macros may change in future, to provide better st_ino |
| semantics. */ |
| #define CRAMINO(x) (CRAMFS_GET_OFFSET(x) ? CRAMFS_GET_OFFSET(x)<<2 : 1) |
| #define OFFSET(x) ((x)->i_ino) |
| |
| struct cramfs_priv { |
| struct cramfs_super super; |
| int curr_base; |
| char buf[4096]; |
| size_t curr_block_len; |
| struct cdev *cdev; |
| }; |
| |
| struct cramfs_inode_info { |
| struct cramfs_inode inode; |
| unsigned long *block_ptrs; |
| }; |
| |
| static int cramfs_read_super(struct cramfs_priv *priv) |
| { |
| unsigned long root_offset; |
| struct cramfs_super *super = &priv->super; |
| struct cdev *cdev = priv->cdev; |
| |
| if (cdev_read(cdev, super, sizeof (struct cramfs_super), 0, 0) < sizeof (struct cramfs_super)) { |
| printf("read superblock failed\n"); |
| return -EINVAL; |
| } |
| |
| /* Do sanity checks on the superblock */ |
| if (super->magic != CRAMFS_32 (CRAMFS_MAGIC)) { |
| /* check at 512 byte offset */ |
| if (cdev_read(cdev, super, sizeof (struct cramfs_super), 512, 0) < sizeof (struct cramfs_super)) { |
| printf("read superblock failed\n"); |
| return -EINVAL; |
| } |
| if (super->magic != CRAMFS_32 (CRAMFS_MAGIC)) { |
| printf ("cramfs: wrong magic\n"); |
| return -1; |
| } |
| } |
| |
| /* flags is reused several times, so swab it once */ |
| super->flags = CRAMFS_32 (super->flags); |
| super->size = CRAMFS_32 (super->size); |
| |
| /* get feature flags first */ |
| if (super->flags & ~CRAMFS_SUPPORTED_FLAGS) { |
| printf ("cramfs: unsupported filesystem features\n"); |
| return -1; |
| } |
| |
| /* Check that the root inode is in a sane state */ |
| if (!S_ISDIR (CRAMFS_16 (super->root.mode))) { |
| printf ("cramfs: root is not a directory\n"); |
| return -1; |
| } |
| root_offset = CRAMFS_GET_OFFSET (&(super->root)) << 2; |
| if (root_offset == 0) { |
| printf ("cramfs: empty filesystem"); |
| } else if (!(super->flags & CRAMFS_FLAG_SHIFTED_ROOT_OFFSET) && |
| ((root_offset != sizeof (struct cramfs_super)) && |
| (root_offset != 512 + sizeof (struct cramfs_super)))) { |
| printf ("cramfs: bad root offset %lu\n", root_offset); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static struct cramfs_inode_info *cramfs_get_inode(struct cramfs_priv *priv, unsigned long offset) |
| { |
| struct cramfs_inode_info *inodei = xmalloc(sizeof(*inodei)); |
| |
| if (cdev_read(priv->cdev, &inodei->inode, sizeof(struct cramfs_inode), offset, 0) < 0) { |
| free(inodei); |
| return NULL; |
| } |
| |
| return inodei; |
| } |
| |
| static struct cramfs_inode_info *cramfs_resolve (struct cramfs_priv *priv, unsigned long offset, |
| unsigned long size, int raw, |
| char *filename) |
| { |
| unsigned long inodeoffset = 0, nextoffset; |
| struct cramfs_inode_info *inodei = NULL, *ret; |
| char *name = xmalloc(256); |
| |
| while (inodeoffset < size) { |
| int namelen; |
| inodei = cramfs_get_inode(priv, offset + inodeoffset); |
| |
| /* |
| * Namelengths on disk are shifted by two |
| * and the name padded out to 4-byte boundaries |
| * with zeroes. |
| */ |
| namelen = CRAMFS_GET_NAMELEN (&inodei->inode) << 2; |
| cdev_read(priv->cdev, name, namelen, offset + inodeoffset + sizeof (struct cramfs_inode), 0); |
| |
| nextoffset = |
| inodeoffset + sizeof (struct cramfs_inode) + namelen; |
| |
| if (!strncmp (filename, name, namelen)) { |
| char *p = strtok (NULL, "/"); |
| |
| if (raw && (p == NULL || *p == '\0')) |
| goto out1; |
| |
| if (S_ISDIR (CRAMFS_16 (inodei->inode.mode))) { |
| ret = cramfs_resolve(priv, |
| CRAMFS_GET_OFFSET(&inodei->inode) << 2, |
| CRAMFS_24 (inodei->inode.size), |
| raw, p); |
| goto out; |
| } else if (S_ISREG (CRAMFS_16 (inodei->inode.mode))) { |
| goto out1; |
| } else { |
| printf ("%*.*s: unsupported file type (%x)\n", |
| namelen, namelen, name, |
| CRAMFS_16 (inodei->inode.mode)); |
| ret = NULL; |
| goto out; |
| } |
| } |
| |
| free(inodei); |
| inodeoffset = nextoffset; |
| } |
| |
| free(name); |
| return NULL; |
| |
| out1: |
| ret = cramfs_get_inode(priv, offset + inodeoffset); |
| out: |
| free(inodei); |
| free(name); |
| return ret; |
| } |
| |
| static int cramfs_fill_dirent (struct cramfs_priv *priv, unsigned long offset, struct dirent *d) |
| { |
| struct cramfs_inode_info *inodei = cramfs_get_inode(priv, offset); |
| int namelen; |
| |
| if (!inodei) |
| return -EINVAL; |
| |
| memset(d->d_name, 0, 256); |
| |
| /* |
| * Namelengths on disk are shifted by two |
| * and the name padded out to 4-byte boundaries |
| * with zeroes. |
| */ |
| |
| namelen = CRAMFS_GET_NAMELEN (&inodei->inode) << 2; |
| cdev_read(priv->cdev, d->d_name, namelen, offset + sizeof(struct cramfs_inode), 0); |
| free(inodei); |
| return namelen; |
| } |
| |
| struct cramfs_dir { |
| unsigned long offset, size; |
| unsigned long inodeoffset; |
| DIR dir; |
| }; |
| |
| static DIR* cramfs_opendir(struct device_d *_dev, const char *filename) |
| { |
| struct cramfs_priv *priv = _dev->priv; |
| char *f; |
| |
| struct cramfs_dir *dir = xzalloc(sizeof(struct cramfs_dir)); |
| dir->dir.priv = dir; |
| |
| if (strlen (filename) == 0 || !strcmp (filename, "/")) { |
| /* Root directory. Use root inode in super block */ |
| dir->offset = CRAMFS_GET_OFFSET (&(priv->super.root)) << 2; |
| dir->size = CRAMFS_24 (priv->super.root.size); |
| } else { |
| struct cramfs_inode_info *inodei; |
| |
| f = strdup(filename); |
| /* Resolve the path */ |
| inodei = cramfs_resolve(priv, |
| CRAMFS_GET_OFFSET (&(priv->super.root)) << |
| 2, CRAMFS_24 (priv->super.root.size), 1, |
| strtok (f, "/")); |
| free(f); |
| if (!inodei) |
| goto err_free; |
| |
| /* Resolving was successful. Examine the inode */ |
| if (!S_ISDIR (CRAMFS_16 (inodei->inode.mode))) { |
| /* It's not a directory */ |
| free(inodei); |
| goto err_free; |
| } |
| |
| dir->offset = CRAMFS_GET_OFFSET (&inodei->inode) << 2; |
| dir->size = CRAMFS_24 (inodei->inode.size); |
| free(inodei); |
| } |
| |
| return &dir->dir; |
| |
| err_free: |
| free(dir); |
| return NULL; |
| } |
| |
| static struct dirent* cramfs_readdir(struct device_d *_dev, DIR *_dir) |
| { |
| struct cramfs_priv *priv = _dev->priv; |
| struct cramfs_dir *dir = _dir->priv; |
| unsigned long nextoffset; |
| |
| /* List the given directory */ |
| if (dir->inodeoffset < dir->size) { |
| nextoffset = cramfs_fill_dirent (priv, dir->offset + dir->inodeoffset, &_dir->d); |
| |
| dir->inodeoffset += sizeof (struct cramfs_inode) + nextoffset; |
| return &_dir->d; |
| } |
| return NULL; |
| } |
| |
| static int cramfs_closedir(struct device_d *dev, DIR *_dir) |
| { |
| struct cramfs_dir *dir = _dir->priv; |
| free(dir); |
| return 0; |
| } |
| |
| static int cramfs_open(struct device_d *_dev, FILE *file, const char *filename) |
| { |
| struct cramfs_priv *priv = _dev->priv; |
| struct cramfs_inode_info *inodei; |
| char *f; |
| |
| f = strdup(filename); |
| inodei = cramfs_resolve (priv, |
| CRAMFS_GET_OFFSET (&(priv->super.root)) << 2, |
| CRAMFS_24 (priv->super.root.size), 0, |
| strtok (f, "/")); |
| free(f); |
| |
| if (!inodei) |
| return -ENOENT; |
| |
| file->inode = inodei; |
| file->size = inodei->inode.size; |
| |
| inodei->block_ptrs = xzalloc(4096); |
| cdev_read(priv->cdev, inodei->block_ptrs, 4096, CRAMFS_GET_OFFSET(&inodei->inode) << 2, 0); |
| |
| return 0; |
| } |
| |
| static int cramfs_close(struct device_d *dev, FILE *file) |
| { |
| struct cramfs_inode_info *inodei = file->inode; |
| |
| free(inodei->block_ptrs); |
| free(inodei); |
| |
| return 0; |
| } |
| |
| static int cramfs_read(struct device_d *_dev, FILE *f, void *buf, size_t size) |
| { |
| struct cramfs_priv *priv = _dev->priv; |
| struct cramfs_inode_info *inodei = f->inode; |
| struct cramfs_inode *inode = &inodei->inode; |
| unsigned int blocknr; |
| int outsize = 0; |
| unsigned long *block_ptrs = inodei->block_ptrs; |
| int ofs = f->pos % 4096; |
| static char cramfs_read_buf[4096]; |
| |
| if (f->pos + size > inode->size) |
| size = inode->size - f->pos; |
| |
| while (size) { |
| unsigned long base; |
| int copy; |
| |
| blocknr = (f->pos + outsize) >> 12; |
| |
| if (blocknr) |
| base = CRAMFS_32 (block_ptrs[blocknr - 1]); |
| else |
| base = (CRAMFS_GET_OFFSET(inode) + (((CRAMFS_24 (inode->size)) + 4095) >> 12)) << 2; |
| |
| if (priv->curr_base < 0 || priv->curr_base != base) { |
| |
| cdev_read(priv->cdev, cramfs_read_buf, 4096, base, 0); |
| priv->curr_block_len = cramfs_uncompress_block(priv->buf, |
| cramfs_read_buf, 4096); |
| if (priv->curr_block_len <= 0) |
| break; |
| |
| priv->curr_base = base; |
| } |
| |
| copy = min(priv->curr_block_len, size); |
| |
| memcpy(buf, priv->buf + ofs, copy); |
| ofs = 0; |
| |
| outsize += copy; |
| size -= copy; |
| buf += copy; |
| } |
| |
| return outsize; |
| } |
| |
| static off_t cramfs_lseek(struct device_d *dev, FILE *f, off_t pos) |
| { |
| f->pos = pos; |
| return f->pos; |
| } |
| |
| static int cramfs_stat(struct device_d *_dev, const char *filename, struct stat *stat) |
| { |
| struct cramfs_priv *priv = _dev->priv; |
| struct cramfs_inode_info *inodei; |
| struct cramfs_inode *inode; |
| char *f; |
| |
| f = strdup(filename); |
| |
| inodei = cramfs_resolve (priv, |
| CRAMFS_GET_OFFSET (&(priv->super.root)) << 2, |
| CRAMFS_24 (priv->super.root.size), 1, |
| strtok (f, "/")); |
| free(f); |
| |
| if (!inodei) |
| return -ENOENT; |
| |
| inode = &inodei->inode; |
| stat->st_mode = CRAMFS_16 (inode->mode); |
| stat->st_size = CRAMFS_24 (inode->size); |
| |
| free(inodei); |
| |
| return 0; |
| } |
| #if 0 |
| static int cramfs_info (struct device_d *dev) |
| { |
| if (cramfs_read_super (dev)) |
| return 0; |
| |
| printf ("size: 0x%x (%u)\n", super.size, super.size); |
| |
| if (super.flags != 0) { |
| printf ("flags:\n"); |
| if (super.flags & CRAMFS_FLAG_FSID_VERSION_2) |
| printf ("\tFSID version 2\n"); |
| if (super.flags & CRAMFS_FLAG_SORTED_DIRS) |
| printf ("\tsorted dirs\n"); |
| if (super.flags & CRAMFS_FLAG_HOLES) |
| printf ("\tholes\n"); |
| if (super.flags & CRAMFS_FLAG_SHIFTED_ROOT_OFFSET) |
| printf ("\tshifted root offset\n"); |
| } |
| |
| printf ("fsid:\n\tcrc: 0x%x\n\tedition: 0x%x\n", |
| super.fsid.crc, super.fsid.edition); |
| printf ("name: %16s\n", super.name); |
| |
| return 1; |
| } |
| #endif |
| |
| static int cramfs_probe(struct device_d *dev) |
| { |
| struct fs_device_d *fsdev; |
| struct cramfs_priv *priv; |
| |
| fsdev = dev->type_data; |
| |
| priv = xmalloc(sizeof(struct cramfs_priv)); |
| dev->priv = priv; |
| |
| if (strncmp(fsdev->backingstore, "/dev/", 5)) |
| return -ENODEV; |
| |
| priv->cdev = cdev_by_name(fsdev->backingstore + 5); |
| if (!priv->cdev) |
| return -ENODEV; |
| |
| if (cramfs_read_super(priv)) { |
| dev_info(dev, "no valid cramfs found\n"); |
| free(priv); |
| return -EINVAL; |
| } |
| |
| priv->curr_base = -1; |
| |
| cramfs_uncompress_init (); |
| return 0; |
| } |
| |
| static void cramfs_remove(struct device_d *dev) |
| { |
| struct cramfs_priv *priv = dev->priv; |
| |
| cramfs_uncompress_exit(); |
| free(priv); |
| } |
| |
| static struct fs_driver_d cramfs_driver = { |
| .open = cramfs_open, |
| .close = cramfs_close, |
| .read = cramfs_read, |
| .lseek = cramfs_lseek, |
| .opendir = cramfs_opendir, |
| .readdir = cramfs_readdir, |
| .closedir = cramfs_closedir, |
| .stat = cramfs_stat, |
| .drv = { |
| .probe = cramfs_probe, |
| .remove = cramfs_remove, |
| .name = "cramfs", |
| .type_data = &cramfs_driver, |
| } |
| }; |
| |
| static int cramfs_init(void) |
| { |
| return register_fs_driver(&cramfs_driver); |
| } |
| |
| device_initcall(cramfs_init); |
| |