Commit 05a1b863 authored by longpanda's avatar longpanda
Browse files

initial commit

parent 2090c6fa
/*
* Squashfs - a compressed read only filesystem for Linux
*
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
* Phillip Lougher <phillip@lougher.demon.co.uk>
*
* 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,
* 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* inode.c
*/
/*
* This file implements code to create and read inodes from disk.
*
* Inodes in Squashfs are identified by a 48-bit inode which encodes the
* location of the compressed metadata block containing the inode, and the byte
* offset into that block where the inode is placed (<block, offset>).
*
* To maximise compression there are different inodes for each file type
* (regular file, directory, device, etc.), the inode contents and length
* varying with the type.
*
* To further maximise compression, two types of regular file inode and
* directory inode are defined: inodes optimised for frequently occurring
* regular files and directories, and extended types where extra
* information has to be stored.
*/
#include <linux/fs.h>
#include <linux/vfs.h>
#include <linux/zlib.h>
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs_fs_i.h"
#include "squashfs.h"
/*
* Initialise VFS inode with the base inode information common to all
* Squashfs inode types. Sqsh_ino contains the unswapped base inode
* off disk.
*/
static int squashfs_new_inode(struct super_block *sb, struct inode *inode,
struct squashfs_base_inode *sqsh_ino)
{
int err;
err = squashfs_get_id(sb, le16_to_cpu(sqsh_ino->uid), &inode->i_uid);
if (err)
return err;
err = squashfs_get_id(sb, le16_to_cpu(sqsh_ino->guid), &inode->i_gid);
if (err)
return err;
inode->i_ino = le32_to_cpu(sqsh_ino->inode_number);
inode->i_mtime.tv_sec = le32_to_cpu(sqsh_ino->mtime);
inode->i_atime.tv_sec = inode->i_mtime.tv_sec;
inode->i_ctime.tv_sec = inode->i_mtime.tv_sec;
inode->i_mode = le16_to_cpu(sqsh_ino->mode);
inode->i_size = 0;
return err;
}
struct inode *squashfs_iget(struct super_block *sb, long long ino,
unsigned int ino_number)
{
struct inode *inode = iget_locked(sb, ino_number);
int err;
TRACE("Entered squashfs_iget\n");
if (!inode)
return ERR_PTR(-ENOMEM);
if (!(inode->i_state & I_NEW))
return inode;
err = squashfs_read_inode(inode, ino);
if (err) {
iget_failed(inode);
return ERR_PTR(err);
}
unlock_new_inode(inode);
return inode;
}
/*
* Initialise VFS inode by reading inode from inode table (compressed
* metadata). The format and amount of data read depends on type.
*/
int squashfs_read_inode(struct inode *inode, long long ino)
{
struct super_block *sb = inode->i_sb;
struct squashfs_sb_info *msblk = sb->s_fs_info;
u64 block = SQUASHFS_INODE_BLK(ino) + msblk->inode_table;
int err, type, offset = SQUASHFS_INODE_OFFSET(ino);
union squashfs_inode squashfs_ino;
struct squashfs_base_inode *sqshb_ino = &squashfs_ino.base;
TRACE("Entered squashfs_read_inode\n");
/*
* Read inode base common to all inode types.
*/
err = squashfs_read_metadata(sb, sqshb_ino, &block,
&offset, sizeof(*sqshb_ino));
if (err < 0)
goto failed_read;
err = squashfs_new_inode(sb, inode, sqshb_ino);
if (err)
goto failed_read;
block = SQUASHFS_INODE_BLK(ino) + msblk->inode_table;
offset = SQUASHFS_INODE_OFFSET(ino);
type = le16_to_cpu(sqshb_ino->inode_type);
switch (type) {
case SQUASHFS_REG_TYPE: {
unsigned int frag_offset, frag_size, frag;
u64 frag_blk;
struct squashfs_reg_inode *sqsh_ino = &squashfs_ino.reg;
err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
sizeof(*sqsh_ino));
if (err < 0)
goto failed_read;
frag = le32_to_cpu(sqsh_ino->fragment);
if (frag != SQUASHFS_INVALID_FRAG) {
frag_offset = le32_to_cpu(sqsh_ino->offset);
frag_size = squashfs_frag_lookup(sb, frag, &frag_blk);
if (frag_size < 0) {
err = frag_size;
goto failed_read;
}
} else {
frag_blk = SQUASHFS_INVALID_BLK;
frag_size = 0;
frag_offset = 0;
}
inode->i_nlink = 1;
inode->i_size = le32_to_cpu(sqsh_ino->file_size);
inode->i_fop = &generic_ro_fops;
inode->i_mode |= S_IFREG;
inode->i_blocks = ((inode->i_size - 1) >> 9) + 1;
squashfs_i(inode)->fragment_block = frag_blk;
squashfs_i(inode)->fragment_size = frag_size;
squashfs_i(inode)->fragment_offset = frag_offset;
squashfs_i(inode)->start = le32_to_cpu(sqsh_ino->start_block);
squashfs_i(inode)->block_list_start = block;
squashfs_i(inode)->offset = offset;
inode->i_data.a_ops = &squashfs_aops;
TRACE("File inode %x:%x, start_block %llx, block_list_start "
"%llx, offset %x\n", SQUASHFS_INODE_BLK(ino),
offset, squashfs_i(inode)->start, block, offset);
break;
}
case SQUASHFS_LREG_TYPE: {
unsigned int frag_offset, frag_size, frag;
u64 frag_blk;
struct squashfs_lreg_inode *sqsh_ino = &squashfs_ino.lreg;
err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
sizeof(*sqsh_ino));
if (err < 0)
goto failed_read;
frag = le32_to_cpu(sqsh_ino->fragment);
if (frag != SQUASHFS_INVALID_FRAG) {
frag_offset = le32_to_cpu(sqsh_ino->offset);
frag_size = squashfs_frag_lookup(sb, frag, &frag_blk);
if (frag_size < 0) {
err = frag_size;
goto failed_read;
}
} else {
frag_blk = SQUASHFS_INVALID_BLK;
frag_size = 0;
frag_offset = 0;
}
inode->i_nlink = le32_to_cpu(sqsh_ino->nlink);
inode->i_size = le64_to_cpu(sqsh_ino->file_size);
inode->i_fop = &generic_ro_fops;
inode->i_mode |= S_IFREG;
inode->i_blocks = ((inode->i_size -
le64_to_cpu(sqsh_ino->sparse) - 1) >> 9) + 1;
squashfs_i(inode)->fragment_block = frag_blk;
squashfs_i(inode)->fragment_size = frag_size;
squashfs_i(inode)->fragment_offset = frag_offset;
squashfs_i(inode)->start = le64_to_cpu(sqsh_ino->start_block);
squashfs_i(inode)->block_list_start = block;
squashfs_i(inode)->offset = offset;
inode->i_data.a_ops = &squashfs_aops;
TRACE("File inode %x:%x, start_block %llx, block_list_start "
"%llx, offset %x\n", SQUASHFS_INODE_BLK(ino),
offset, squashfs_i(inode)->start, block, offset);
break;
}
case SQUASHFS_DIR_TYPE: {
struct squashfs_dir_inode *sqsh_ino = &squashfs_ino.dir;
err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
sizeof(*sqsh_ino));
if (err < 0)
goto failed_read;
inode->i_nlink = le32_to_cpu(sqsh_ino->nlink);
inode->i_size = le16_to_cpu(sqsh_ino->file_size);
inode->i_op = &squashfs_dir_inode_ops;
inode->i_fop = &squashfs_dir_ops;
inode->i_mode |= S_IFDIR;
squashfs_i(inode)->start = le32_to_cpu(sqsh_ino->start_block);
squashfs_i(inode)->offset = le16_to_cpu(sqsh_ino->offset);
squashfs_i(inode)->dir_idx_cnt = 0;
squashfs_i(inode)->parent = le32_to_cpu(sqsh_ino->parent_inode);
TRACE("Directory inode %x:%x, start_block %llx, offset %x\n",
SQUASHFS_INODE_BLK(ino), offset,
squashfs_i(inode)->start,
le16_to_cpu(sqsh_ino->offset));
break;
}
case SQUASHFS_LDIR_TYPE: {
struct squashfs_ldir_inode *sqsh_ino = &squashfs_ino.ldir;
err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
sizeof(*sqsh_ino));
if (err < 0)
goto failed_read;
inode->i_nlink = le32_to_cpu(sqsh_ino->nlink);
inode->i_size = le32_to_cpu(sqsh_ino->file_size);
inode->i_op = &squashfs_dir_inode_ops;
inode->i_fop = &squashfs_dir_ops;
inode->i_mode |= S_IFDIR;
squashfs_i(inode)->start = le32_to_cpu(sqsh_ino->start_block);
squashfs_i(inode)->offset = le16_to_cpu(sqsh_ino->offset);
squashfs_i(inode)->dir_idx_start = block;
squashfs_i(inode)->dir_idx_offset = offset;
squashfs_i(inode)->dir_idx_cnt = le16_to_cpu(sqsh_ino->i_count);
squashfs_i(inode)->parent = le32_to_cpu(sqsh_ino->parent_inode);
TRACE("Long directory inode %x:%x, start_block %llx, offset "
"%x\n", SQUASHFS_INODE_BLK(ino), offset,
squashfs_i(inode)->start,
le16_to_cpu(sqsh_ino->offset));
break;
}
case SQUASHFS_SYMLINK_TYPE:
case SQUASHFS_LSYMLINK_TYPE: {
struct squashfs_symlink_inode *sqsh_ino = &squashfs_ino.symlink;
err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
sizeof(*sqsh_ino));
if (err < 0)
goto failed_read;
inode->i_nlink = le32_to_cpu(sqsh_ino->nlink);
inode->i_size = le32_to_cpu(sqsh_ino->symlink_size);
inode->i_op = &page_symlink_inode_operations;
inode->i_data.a_ops = &squashfs_symlink_aops;
inode->i_mode |= S_IFLNK;
squashfs_i(inode)->start = block;
squashfs_i(inode)->offset = offset;
TRACE("Symbolic link inode %x:%x, start_block %llx, offset "
"%x\n", SQUASHFS_INODE_BLK(ino), offset,
block, offset);
break;
}
case SQUASHFS_BLKDEV_TYPE:
case SQUASHFS_CHRDEV_TYPE:
case SQUASHFS_LBLKDEV_TYPE:
case SQUASHFS_LCHRDEV_TYPE: {
struct squashfs_dev_inode *sqsh_ino = &squashfs_ino.dev;
unsigned int rdev;
err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
sizeof(*sqsh_ino));
if (err < 0)
goto failed_read;
if (type == SQUASHFS_CHRDEV_TYPE)
inode->i_mode |= S_IFCHR;
else
inode->i_mode |= S_IFBLK;
inode->i_nlink = le32_to_cpu(sqsh_ino->nlink);
rdev = le32_to_cpu(sqsh_ino->rdev);
init_special_inode(inode, inode->i_mode, new_decode_dev(rdev));
TRACE("Device inode %x:%x, rdev %x\n",
SQUASHFS_INODE_BLK(ino), offset, rdev);
break;
}
case SQUASHFS_FIFO_TYPE:
case SQUASHFS_SOCKET_TYPE:
case SQUASHFS_LFIFO_TYPE:
case SQUASHFS_LSOCKET_TYPE: {
struct squashfs_ipc_inode *sqsh_ino = &squashfs_ino.ipc;
err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
sizeof(*sqsh_ino));
if (err < 0)
goto failed_read;
if (type == SQUASHFS_FIFO_TYPE)
inode->i_mode |= S_IFIFO;
else
inode->i_mode |= S_IFSOCK;
inode->i_nlink = le32_to_cpu(sqsh_ino->nlink);
init_special_inode(inode, inode->i_mode, 0);
break;
}
default:
ERROR("Unknown inode type %d in squashfs_iget!\n", type);
return -EINVAL;
}
return 0;
failed_read:
ERROR("Unable to read inode 0x%llx\n", ino);
return err;
}
/*
* Squashfs - a compressed read only filesystem for Linux
*
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
* Phillip Lougher <phillip@lougher.demon.co.uk>
*
* 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,
* 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* namei.c
*/
/*
* This file implements code to do filename lookup in directories.
*
* Like inodes, directories are packed into compressed metadata blocks, stored
* in a directory table. Directories are accessed using the start address of
* the metablock containing the directory and the offset into the
* decompressed block (<block, offset>).
*
* Directories are organised in a slightly complex way, and are not simply
* a list of file names. The organisation takes advantage of the
* fact that (in most cases) the inodes of the files will be in the same
* compressed metadata block, and therefore, can share the start block.
* Directories are therefore organised in a two level list, a directory
* header containing the shared start block value, and a sequence of directory
* entries, each of which share the shared start block. A new directory header
* is written once/if the inode start block changes. The directory
* header/directory entry list is repeated as many times as necessary.
*
* Directories are sorted, and can contain a directory index to speed up
* file lookup. Directory indexes store one entry per metablock, each entry
* storing the index/filename mapping to the first directory header
* in each metadata block. Directories are sorted in alphabetical order,
* and at lookup the index is scanned linearly looking for the first filename
* alphabetically larger than the filename being looked up. At this point the
* location of the metadata block the filename is in has been found.
* The general idea of the index is ensure only one metadata block needs to be
* decompressed to do a lookup irrespective of the length of the directory.
* This scheme has the advantage that it doesn't require extra memory overhead
* and doesn't require much extra storage on disk.
*/
#include <linux/fs.h>
#include <linux/vfs.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/dcache.h>
#include <linux/zlib.h>
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs_fs_i.h"
#include "squashfs.h"
/*
* Lookup name in the directory index, returning the location of the metadata
* block containing it, and the directory index this represents.
*
* If we get an error reading the index then return the part of the index
* (if any) we have managed to read - the index isn't essential, just
* quicker.
*/
static int get_dir_index_using_name(struct super_block *sb,
u64 *next_block, int *next_offset, u64 index_start,
int index_offset, int i_count, const char *name,
int len)
{
struct squashfs_sb_info *msblk = sb->s_fs_info;
int i, size, length = 0, err;
struct squashfs_dir_index *index;
char *str;
TRACE("Entered get_dir_index_using_name, i_count %d\n", i_count);
index = kmalloc(sizeof(*index) + SQUASHFS_NAME_LEN * 2 + 2, GFP_KERNEL);
if (index == NULL) {
ERROR("Failed to allocate squashfs_dir_index\n");
goto out;
}
str = &index->name[SQUASHFS_NAME_LEN + 1];
strncpy(str, name, len);
str[len] = '\0';
for (i = 0; i < i_count; i++) {
err = squashfs_read_metadata(sb, index, &index_start,
&index_offset, sizeof(*index));
if (err < 0)
break;
size = le32_to_cpu(index->size) + 1;
err = squashfs_read_metadata(sb, index->name, &index_start,
&index_offset, size);
if (err < 0)
break;
index->name[size] = '\0';
if (strcmp(index->name, str) > 0)
break;
length = le32_to_cpu(index->index);
*next_block = le32_to_cpu(index->start_block) +
msblk->directory_table;
}
*next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE;
kfree(index);
out:
/*
* Return index (f_pos) of the looked up metadata block. Translate
* from internal f_pos to external f_pos which is offset by 3 because
* we invent "." and ".." entries which are not actually stored in the
* directory.
*/
return length + 3;
}
static struct dentry *squashfs_lookup(struct inode *dir, struct dentry *dentry,
struct nameidata *nd)
{
const unsigned char *name = dentry->d_name.name;
int len = dentry->d_name.len;
struct inode *inode = NULL;
struct squashfs_sb_info *msblk = dir->i_sb->s_fs_info;
struct squashfs_dir_header dirh;
struct squashfs_dir_entry *dire;
u64 block = squashfs_i(dir)->start + msblk->directory_table;
int offset = squashfs_i(dir)->offset;
int err, length = 0, dir_count, size;
TRACE("Entered squashfs_lookup [%llx:%x]\n", block, offset);
dire = kmalloc(sizeof(*dire) + SQUASHFS_NAME_LEN + 1, GFP_KERNEL);
if (dire == NULL) {
ERROR("Failed to allocate squashfs_dir_entry\n");
return ERR_PTR(-ENOMEM);
}
if (len > SQUASHFS_NAME_LEN) {
err = -ENAMETOOLONG;
goto failed;
}
length = get_dir_index_using_name(dir->i_sb, &block, &offset,
squashfs_i(dir)->dir_idx_start,
squashfs_i(dir)->dir_idx_offset,
squashfs_i(dir)->dir_idx_cnt, name, len);
while (length < i_size_read(dir)) {
/*
* Read directory header.
*/
err = squashfs_read_metadata(dir->i_sb, &dirh, &block,
&offset, sizeof(dirh));
if (err < 0)
goto read_failure;
length += sizeof(dirh);
dir_count = le32_to_cpu(dirh.count) + 1;
while (dir_count--) {
/*
* Read directory entry.
*/
err = squashfs_read_metadata(dir->i_sb, dire, &block,
&offset, sizeof(*dire));
if (err < 0)
goto read_failure;
size = le16_to_cpu(dire->size) + 1;
err = squashfs_read_metadata(dir->i_sb, dire->name,
&block, &offset, size);
if (err < 0)
goto read_failure;
length += sizeof(*dire) + size;
if (name[0] < dire->name[0])
goto exit_lookup;
if (len == size && !strncmp(name, dire->name, len)) {
unsigned int blk, off, ino_num;
long long ino;
blk = le32_to_cpu(dirh.start_block);
off = le16_to_cpu(dire->offset);
ino_num = le32_to_cpu(dirh.inode_number) +
(short) le16_to_cpu(dire->inode_number);
ino = SQUASHFS_MKINODE(blk, off);
TRACE("calling squashfs_iget for directory "
"entry %s, inode %x:%x, %d\n", name,
blk, off, ino_num);
inode = squashfs_iget(dir->i_sb, ino, ino_num);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
goto failed;
}
goto exit_lookup;
}
}
}
exit_lookup:
kfree(dire);
if (inode)
return d_splice_alias(inode, dentry);
d_add(dentry, inode);
return ERR_PTR(0);
read_failure:
ERROR("Unable to read directory block [%llx:%x]\n",
squashfs_i(dir)->start + msblk->directory_table,
squashfs_i(dir)->offset);
failed:
kfree(dire);
return ERR_PTR(err);
}
const struct inode_operations squashfs_dir_inode_ops = {
.lookup = squashfs_lookup
};
/*
* Squashfs - a compressed read only filesystem for Linux
*
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
* Phillip Lougher <phillip@lougher.demon.co.uk>
*
* 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,
* 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* squashfs.h
*/
#define TRACE(s, args...) pr_debug("SQUASHFS: "s, ## args)
#define ERROR(s, args...) pr_err("SQUASHFS error: "s, ## args)
#define WARNING(s, args...) pr_warning("SQUASHFS: "s, ## args)
static inline struct squashfs_inode_info *squashfs_i(struct inode *inode)
{
return list_entry(inode, struct squashfs_inode_info, vfs_inode);
}
/* block.c */
extern int squashfs_read_data(struct super_block *, void **, u64, int, u64 *,
int);
/* cache.c */
extern struct squashfs_cache *squashfs_cache_init(char *, int, int);
extern void squashfs_cache_delete(struct squashfs_cache *);
extern struct squashfs_cache_entry *squashfs_cache_get(struct super_block *,
struct squashfs_cache *, u64, int);
extern void squashfs_cache_put(struct squashfs_cache_entry *);
extern int squashfs_copy_data(void *, struct squashfs_cache_entry *, int, int);
extern int squashfs_read_metadata(struct super_block *, void *, u64 *,
int *, int);
extern struct squashfs_cache_entry *squashfs_get_fragment(struct super_block *,
u64, int);
extern struct squashfs_cache_entry *squashfs_get_datablock(struct super_block *,
u64, int);
extern int squashfs_read_table(struct super_block *, void *, u64, int);
/* export.c */
extern __le64 *squashfs_read_inode_lookup_table(struct super_block *, u64,
unsigned int);
/* fragment.c */
extern int squashfs_frag_lookup(struct super_block *, unsigned int, u64 *);
extern __le64 *squashfs_read_fragment_index_table(struct super_block *,
u64, unsigned int);
/* id.c */
extern int squashfs_get_id(struct super_block *, unsigned int, unsigned int *);
extern __le64 *squashfs_read_id_index_table(struct super_block *, u64,
unsigned short);
/* inode.c */
extern struct inode *squashfs_iget(struct super_block *, long long,
unsigned int);
extern int squashfs_read_inode(struct inode *, long long);
/*
* Inodes and files operations
*/
/* dir.c */
extern const struct file_operations squashfs_dir_ops;
/* export.c */
extern const struct export_operations squashfs_export_ops;
/* file.c */
extern const struct address_space_operations squashfs_aops;
/* namei.c */
extern const struct inode_operations squashfs_dir_inode_ops;
/* symlink.c */
extern const struct address_space_operations squashfs_symlink_aops;
#ifndef SQUASHFS_FS
#define SQUASHFS_FS
/*
* Squashfs
*
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
* Phillip Lougher <phillip@lougher.demon.co.uk>
*
* 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,
* 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* squashfs_fs.h
*/
#define SQUASHFS_CACHED_FRAGMENTS CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE
#define SQUASHFS_MAJOR 4
#define SQUASHFS_MINOR 0
#define SQUASHFS_MAGIC 0x73717368
#define SQUASHFS_START 0
/* size of metadata (inode and directory) blocks */
#define SQUASHFS_METADATA_SIZE 8192
#define SQUASHFS_METADATA_LOG 13
/* default size of data blocks */
#define SQUASHFS_FILE_SIZE 131072
#define SQUASHFS_FILE_LOG 17
#define SQUASHFS_FILE_MAX_SIZE 1048576
#define SQUASHFS_FILE_MAX_LOG 20
/* Max number of uids and gids */
#define SQUASHFS_IDS 65536
/* Max length of filename (not 255) */
#define SQUASHFS_NAME_LEN 256
#define SQUASHFS_INVALID_FRAG (0xffffffffU)
#define SQUASHFS_INVALID_BLK (-1LL)
/* Filesystem flags */
#define SQUASHFS_NOI 0
#define SQUASHFS_NOD 1
#define SQUASHFS_NOF 3
#define SQUASHFS_NO_FRAG 4
#define SQUASHFS_ALWAYS_FRAG 5
#define SQUASHFS_DUPLICATE 6
#define SQUASHFS_EXPORT 7
#define SQUASHFS_BIT(flag, bit) ((flag >> bit) & 1)
#define SQUASHFS_UNCOMPRESSED_INODES(flags) SQUASHFS_BIT(flags, \
SQUASHFS_NOI)
#define SQUASHFS_UNCOMPRESSED_DATA(flags) SQUASHFS_BIT(flags, \
SQUASHFS_NOD)
#define SQUASHFS_UNCOMPRESSED_FRAGMENTS(flags) SQUASHFS_BIT(flags, \
SQUASHFS_NOF)
#define SQUASHFS_NO_FRAGMENTS(flags) SQUASHFS_BIT(flags, \
SQUASHFS_NO_FRAG)
#define SQUASHFS_ALWAYS_FRAGMENTS(flags) SQUASHFS_BIT(flags, \
SQUASHFS_ALWAYS_FRAG)
#define SQUASHFS_DUPLICATES(flags) SQUASHFS_BIT(flags, \
SQUASHFS_DUPLICATE)
#define SQUASHFS_EXPORTABLE(flags) SQUASHFS_BIT(flags, \
SQUASHFS_EXPORT)
/* Max number of types and file types */
#define SQUASHFS_DIR_TYPE 1
#define SQUASHFS_REG_TYPE 2
#define SQUASHFS_SYMLINK_TYPE 3
#define SQUASHFS_BLKDEV_TYPE 4
#define SQUASHFS_CHRDEV_TYPE 5
#define SQUASHFS_FIFO_TYPE 6
#define SQUASHFS_SOCKET_TYPE 7
#define SQUASHFS_LDIR_TYPE 8
#define SQUASHFS_LREG_TYPE 9
#define SQUASHFS_LSYMLINK_TYPE 10
#define SQUASHFS_LBLKDEV_TYPE 11
#define SQUASHFS_LCHRDEV_TYPE 12
#define SQUASHFS_LFIFO_TYPE 13
#define SQUASHFS_LSOCKET_TYPE 14
/* Flag whether block is compressed or uncompressed, bit is set if block is
* uncompressed */
#define SQUASHFS_COMPRESSED_BIT (1 << 15)
#define SQUASHFS_COMPRESSED_SIZE(B) (((B) & ~SQUASHFS_COMPRESSED_BIT) ? \
(B) & ~SQUASHFS_COMPRESSED_BIT : SQUASHFS_COMPRESSED_BIT)
#define SQUASHFS_COMPRESSED(B) (!((B) & SQUASHFS_COMPRESSED_BIT))
#define SQUASHFS_COMPRESSED_BIT_BLOCK (1 << 24)
#define SQUASHFS_COMPRESSED_SIZE_BLOCK(B) ((B) & \
~SQUASHFS_COMPRESSED_BIT_BLOCK)
#define SQUASHFS_COMPRESSED_BLOCK(B) (!((B) & SQUASHFS_COMPRESSED_BIT_BLOCK))
/*
* Inode number ops. Inodes consist of a compressed block number, and an
* uncompressed offset within that block
*/
#define SQUASHFS_INODE_BLK(A) ((unsigned int) ((A) >> 16))
#define SQUASHFS_INODE_OFFSET(A) ((unsigned int) ((A) & 0xffff))
#define SQUASHFS_MKINODE(A, B) ((long long)(((long long) (A)\
<< 16) + (B)))
/* Translate between VFS mode and squashfs mode */
#define SQUASHFS_MODE(A) ((A) & 0xfff)
/* fragment and fragment table defines */
#define SQUASHFS_FRAGMENT_BYTES(A) \
((A) * sizeof(struct squashfs_fragment_entry))
#define SQUASHFS_FRAGMENT_INDEX(A) (SQUASHFS_FRAGMENT_BYTES(A) / \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_FRAGMENT_INDEX_OFFSET(A) (SQUASHFS_FRAGMENT_BYTES(A) % \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_FRAGMENT_INDEXES(A) ((SQUASHFS_FRAGMENT_BYTES(A) + \
SQUASHFS_METADATA_SIZE - 1) / \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_FRAGMENT_INDEX_BYTES(A) (SQUASHFS_FRAGMENT_INDEXES(A) *\
sizeof(u64))
/* inode lookup table defines */
#define SQUASHFS_LOOKUP_BYTES(A) ((A) * sizeof(u64))
#define SQUASHFS_LOOKUP_BLOCK(A) (SQUASHFS_LOOKUP_BYTES(A) / \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_LOOKUP_BLOCK_OFFSET(A) (SQUASHFS_LOOKUP_BYTES(A) % \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_LOOKUP_BLOCKS(A) ((SQUASHFS_LOOKUP_BYTES(A) + \
SQUASHFS_METADATA_SIZE - 1) / \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_LOOKUP_BLOCK_BYTES(A) (SQUASHFS_LOOKUP_BLOCKS(A) *\
sizeof(u64))
/* uid/gid lookup table defines */
#define SQUASHFS_ID_BYTES(A) ((A) * sizeof(unsigned int))
#define SQUASHFS_ID_BLOCK(A) (SQUASHFS_ID_BYTES(A) / \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_ID_BLOCK_OFFSET(A) (SQUASHFS_ID_BYTES(A) % \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_ID_BLOCKS(A) ((SQUASHFS_ID_BYTES(A) + \
SQUASHFS_METADATA_SIZE - 1) / \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_ID_BLOCK_BYTES(A) (SQUASHFS_ID_BLOCKS(A) *\
sizeof(u64))
/* cached data constants for filesystem */
#define SQUASHFS_CACHED_BLKS 8
#define SQUASHFS_MAX_FILE_SIZE_LOG 64
#define SQUASHFS_MAX_FILE_SIZE (1LL << \
(SQUASHFS_MAX_FILE_SIZE_LOG - 2))
#define SQUASHFS_MARKER_BYTE 0xff
/* meta index cache */
#define SQUASHFS_META_INDEXES (SQUASHFS_METADATA_SIZE / sizeof(unsigned int))
#define SQUASHFS_META_ENTRIES 127
#define SQUASHFS_META_SLOTS 8
struct meta_entry {
u64 data_block;
unsigned int index_block;
unsigned short offset;
unsigned short pad;
};
struct meta_index {
unsigned int inode_number;
unsigned int offset;
unsigned short entries;
unsigned short skip;
unsigned short locked;
unsigned short pad;
struct meta_entry meta_entry[SQUASHFS_META_ENTRIES];
};
/*
* definitions for structures on disk
*/
#define ZLIB_COMPRESSION 1
struct squashfs_super_block {
__le32 s_magic;
__le32 inodes;
__le32 mkfs_time;
__le32 block_size;
__le32 fragments;
__le16 compression;
__le16 block_log;
__le16 flags;
__le16 no_ids;
__le16 s_major;
__le16 s_minor;
__le64 root_inode;
__le64 bytes_used;
__le64 id_table_start;
__le64 xattr_table_start;
__le64 inode_table_start;
__le64 directory_table_start;
__le64 fragment_table_start;
__le64 lookup_table_start;
};
struct squashfs_dir_index {
__le32 index;
__le32 start_block;
__le32 size;
unsigned char name[0];
};
struct squashfs_base_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
};
struct squashfs_ipc_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 nlink;
};
struct squashfs_dev_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 nlink;
__le32 rdev;
};
struct squashfs_symlink_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 nlink;
__le32 symlink_size;
char symlink[0];
};
struct squashfs_reg_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 start_block;
__le32 fragment;
__le32 offset;
__le32 file_size;
__le16 block_list[0];
};
struct squashfs_lreg_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le64 start_block;
__le64 file_size;
__le64 sparse;
__le32 nlink;
__le32 fragment;
__le32 offset;
__le32 xattr;
__le16 block_list[0];
};
struct squashfs_dir_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 start_block;
__le32 nlink;
__le16 file_size;
__le16 offset;
__le32 parent_inode;
};
struct squashfs_ldir_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 nlink;
__le32 file_size;
__le32 start_block;
__le32 parent_inode;
__le16 i_count;
__le16 offset;
__le32 xattr;
struct squashfs_dir_index index[0];
};
union squashfs_inode {
struct squashfs_base_inode base;
struct squashfs_dev_inode dev;
struct squashfs_symlink_inode symlink;
struct squashfs_reg_inode reg;
struct squashfs_lreg_inode lreg;
struct squashfs_dir_inode dir;
struct squashfs_ldir_inode ldir;
struct squashfs_ipc_inode ipc;
};
struct squashfs_dir_entry {
__le16 offset;
__le16 inode_number;
__le16 type;
__le16 size;
char name[0];
};
struct squashfs_dir_header {
__le32 count;
__le32 start_block;
__le32 inode_number;
};
struct squashfs_fragment_entry {
__le64 start_block;
__le32 size;
unsigned int unused;
};
#endif
#ifndef SQUASHFS_FS_I
#define SQUASHFS_FS_I
/*
* Squashfs
*
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
* Phillip Lougher <phillip@lougher.demon.co.uk>
*
* 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,
* 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* squashfs_fs_i.h
*/
struct squashfs_inode_info {
u64 start;
int offset;
union {
struct {
u64 fragment_block;
int fragment_size;
int fragment_offset;
u64 block_list_start;
};
struct {
u64 dir_idx_start;
int dir_idx_offset;
int dir_idx_cnt;
int parent;
};
};
struct inode vfs_inode;
};
#endif
#ifndef SQUASHFS_FS_SB
#define SQUASHFS_FS_SB
/*
* Squashfs
*
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
* Phillip Lougher <phillip@lougher.demon.co.uk>
*
* 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,
* 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* squashfs_fs_sb.h
*/
#include "squashfs_fs.h"
struct squashfs_cache {
char *name;
int entries;
int next_blk;
int num_waiters;
int unused;
int block_size;
int pages;
spinlock_t lock;
wait_queue_head_t wait_queue;
struct squashfs_cache_entry *entry;
};
struct squashfs_cache_entry {
u64 block;
int length;
int refcount;
u64 next_index;
int pending;
int error;
int num_waiters;
wait_queue_head_t wait_queue;
struct squashfs_cache *cache;
void **data;
};
struct squashfs_sb_info {
int devblksize;
int devblksize_log2;
struct squashfs_cache *block_cache;
struct squashfs_cache *fragment_cache;
struct squashfs_cache *read_page;
int next_meta_index;
__le64 *id_table;
__le64 *fragment_index;
unsigned int *fragment_index_2;
struct mutex read_data_mutex;
struct mutex meta_index_mutex;
struct meta_index *meta_index;
z_stream stream;
__le64 *inode_lookup_table;
u64 inode_table;
u64 directory_table;
unsigned int block_size;
unsigned short block_log;
long long bytes_used;
unsigned int inodes;
};
#endif
/*
* Squashfs - a compressed read only filesystem for Linux
*
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
* Phillip Lougher <phillip@lougher.demon.co.uk>
*
* 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,
* 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* super.c
*/
/*
* This file implements code to read the superblock, read and initialise
* in-memory structures at mount time, and all the VFS glue code to register
* the filesystem.
*/
#include <linux/fs.h>
#include <linux/vfs.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/pagemap.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/zlib.h>
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs_fs_i.h"
#include "squashfs.h"
static struct file_system_type squashfs_fs_type;
static struct super_operations squashfs_super_ops;
static int supported_squashfs_filesystem(short major, short minor, short comp)
{
if (major < SQUASHFS_MAJOR) {
ERROR("Major/Minor mismatch, older Squashfs %d.%d "
"filesystems are unsupported\n", major, minor);
return -EINVAL;
} else if (major > SQUASHFS_MAJOR || minor > SQUASHFS_MINOR) {
ERROR("Major/Minor mismatch, trying to mount newer "
"%d.%d filesystem\n", major, minor);
ERROR("Please update your kernel\n");
return -EINVAL;
}
if (comp != ZLIB_COMPRESSION)
return -EINVAL;
return 0;
}
static int squashfs_fill_super(struct super_block *sb, void *data, int silent)
{
struct squashfs_sb_info *msblk;
struct squashfs_super_block *sblk = NULL;
char b[BDEVNAME_SIZE];
struct inode *root;
long long root_inode;
unsigned short flags;
unsigned int fragments;
u64 lookup_table_start;
int err;
TRACE("Entered squashfs_fill_superblock\n");
sb->s_fs_info = kzalloc(sizeof(*msblk), GFP_KERNEL);
if (sb->s_fs_info == NULL) {
ERROR("Failed to allocate squashfs_sb_info\n");
return -ENOMEM;
}
msblk = sb->s_fs_info;
msblk->stream.workspace = kmalloc(zlib_inflate_workspacesize(),
GFP_KERNEL);
if (msblk->stream.workspace == NULL) {
ERROR("Failed to allocate zlib workspace\n");
goto failure;
}
sblk = kzalloc(sizeof(*sblk), GFP_KERNEL);
if (sblk == NULL) {
ERROR("Failed to allocate squashfs_super_block\n");
goto failure;
}
msblk->devblksize = sb_min_blocksize(sb, BLOCK_SIZE);
msblk->devblksize_log2 = ffz(~msblk->devblksize);
mutex_init(&msblk->read_data_mutex);
mutex_init(&msblk->meta_index_mutex);
/*
* msblk->bytes_used is checked in squashfs_read_table to ensure reads
* are not beyond filesystem end. But as we're using
* squashfs_read_table here to read the superblock (including the value
* of bytes_used) we need to set it to an initial sensible dummy value
*/
msblk->bytes_used = sizeof(*sblk);
err = squashfs_read_table(sb, sblk, SQUASHFS_START, sizeof(*sblk));
if (err < 0) {
ERROR("unable to read squashfs_super_block\n");
goto failed_mount;
}
/* Check it is a SQUASHFS superblock */
sb->s_magic = le32_to_cpu(sblk->s_magic);
if (sb->s_magic != SQUASHFS_MAGIC) {
if (!silent)
ERROR("Can't find a SQUASHFS superblock on %s\n",
bdevname(sb->s_bdev, b));
err = -EINVAL;
goto failed_mount;
}
/* Check the MAJOR & MINOR versions and compression type */
err = supported_squashfs_filesystem(le16_to_cpu(sblk->s_major),
le16_to_cpu(sblk->s_minor),
le16_to_cpu(sblk->compression));
if (err < 0)
goto failed_mount;
err = -EINVAL;
/*
* Check if there's xattrs in the filesystem. These are not
* supported in this version, so warn that they will be ignored.
*/
if (le64_to_cpu(sblk->xattr_table_start) != SQUASHFS_INVALID_BLK)
ERROR("Xattrs in filesystem, these will be ignored\n");
/* Check the filesystem does not extend beyond the end of the
block device */
msblk->bytes_used = le64_to_cpu(sblk->bytes_used);
if (msblk->bytes_used < 0 || msblk->bytes_used >
i_size_read(sb->s_bdev->bd_inode))
goto failed_mount;
/* Check block size for sanity */
msblk->block_size = le32_to_cpu(sblk->block_size);
if (msblk->block_size > SQUASHFS_FILE_MAX_SIZE)
goto failed_mount;
msblk->block_log = le16_to_cpu(sblk->block_log);
if (msblk->block_log > SQUASHFS_FILE_MAX_LOG)
goto failed_mount;
/* Check the root inode for sanity */
root_inode = le64_to_cpu(sblk->root_inode);
if (SQUASHFS_INODE_OFFSET(root_inode) > SQUASHFS_METADATA_SIZE)
goto failed_mount;
msblk->inode_table = le64_to_cpu(sblk->inode_table_start);
msblk->directory_table = le64_to_cpu(sblk->directory_table_start);
msblk->inodes = le32_to_cpu(sblk->inodes);
flags = le16_to_cpu(sblk->flags);
TRACE("Found valid superblock on %s\n", bdevname(sb->s_bdev, b));
TRACE("Inodes are %scompressed\n", SQUASHFS_UNCOMPRESSED_INODES(flags)
? "un" : "");
TRACE("Data is %scompressed\n", SQUASHFS_UNCOMPRESSED_DATA(flags)
? "un" : "");
TRACE("Filesystem size %lld bytes\n", msblk->bytes_used);
TRACE("Block size %d\n", msblk->block_size);
TRACE("Number of inodes %d\n", msblk->inodes);
TRACE("Number of fragments %d\n", le32_to_cpu(sblk->fragments));
TRACE("Number of ids %d\n", le16_to_cpu(sblk->no_ids));
TRACE("sblk->inode_table_start %llx\n", msblk->inode_table);
TRACE("sblk->directory_table_start %llx\n", msblk->directory_table);
TRACE("sblk->fragment_table_start %llx\n",
(u64) le64_to_cpu(sblk->fragment_table_start));
TRACE("sblk->id_table_start %llx\n",
(u64) le64_to_cpu(sblk->id_table_start));
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_flags |= MS_RDONLY;
sb->s_op = &squashfs_super_ops;
err = -ENOMEM;
msblk->block_cache = squashfs_cache_init("metadata",
SQUASHFS_CACHED_BLKS, SQUASHFS_METADATA_SIZE);
if (msblk->block_cache == NULL)
goto failed_mount;
/* Allocate read_page block */
msblk->read_page = squashfs_cache_init("data", 1, msblk->block_size);
if (msblk->read_page == NULL) {
ERROR("Failed to allocate read_page block\n");
goto failed_mount;
}
/* Allocate and read id index table */
msblk->id_table = squashfs_read_id_index_table(sb,
le64_to_cpu(sblk->id_table_start), le16_to_cpu(sblk->no_ids));
if (IS_ERR(msblk->id_table)) {
err = PTR_ERR(msblk->id_table);
msblk->id_table = NULL;
goto failed_mount;
}
fragments = le32_to_cpu(sblk->fragments);
if (fragments == 0)
goto allocate_lookup_table;
msblk->fragment_cache = squashfs_cache_init("fragment",
SQUASHFS_CACHED_FRAGMENTS, msblk->block_size);
if (msblk->fragment_cache == NULL) {
err = -ENOMEM;
goto failed_mount;
}
/* Allocate and read fragment index table */
msblk->fragment_index = squashfs_read_fragment_index_table(sb,
le64_to_cpu(sblk->fragment_table_start), fragments);
if (IS_ERR(msblk->fragment_index)) {
err = PTR_ERR(msblk->fragment_index);
msblk->fragment_index = NULL;
goto failed_mount;
}
allocate_lookup_table:
lookup_table_start = le64_to_cpu(sblk->lookup_table_start);
if (lookup_table_start == SQUASHFS_INVALID_BLK)
goto allocate_root;
/* Allocate and read inode lookup table */
msblk->inode_lookup_table = squashfs_read_inode_lookup_table(sb,
lookup_table_start, msblk->inodes);
if (IS_ERR(msblk->inode_lookup_table)) {
err = PTR_ERR(msblk->inode_lookup_table);
msblk->inode_lookup_table = NULL;
goto failed_mount;
}
sb->s_export_op = &squashfs_export_ops;
allocate_root:
root = new_inode(sb);
if (!root) {
err = -ENOMEM;
goto failed_mount;
}
err = squashfs_read_inode(root, root_inode);
if (err) {
iget_failed(root);
goto failed_mount;
}
insert_inode_hash(root);
sb->s_root = d_alloc_root(root);
if (sb->s_root == NULL) {
ERROR("Root inode create failed\n");
err = -ENOMEM;
iput(root);
goto failed_mount;
}
TRACE("Leaving squashfs_fill_super\n");
kfree(sblk);
return 0;
failed_mount:
squashfs_cache_delete(msblk->block_cache);
squashfs_cache_delete(msblk->fragment_cache);
squashfs_cache_delete(msblk->read_page);
kfree(msblk->inode_lookup_table);
kfree(msblk->fragment_index);
kfree(msblk->id_table);
kfree(msblk->stream.workspace);
kfree(sb->s_fs_info);
sb->s_fs_info = NULL;
kfree(sblk);
return err;
failure:
kfree(msblk->stream.workspace);
kfree(sb->s_fs_info);
sb->s_fs_info = NULL;
return -ENOMEM;
}
static int squashfs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
struct squashfs_sb_info *msblk = dentry->d_sb->s_fs_info;
TRACE("Entered squashfs_statfs\n");
buf->f_type = SQUASHFS_MAGIC;
buf->f_bsize = msblk->block_size;
buf->f_blocks = ((msblk->bytes_used - 1) >> msblk->block_log) + 1;
buf->f_bfree = buf->f_bavail = 0;
buf->f_files = msblk->inodes;
buf->f_ffree = 0;
buf->f_namelen = SQUASHFS_NAME_LEN;
return 0;
}
static int squashfs_remount(struct super_block *sb, int *flags, char *data)
{
*flags |= MS_RDONLY;
return 0;
}
static void squashfs_put_super(struct super_block *sb)
{
if (sb->s_fs_info) {
struct squashfs_sb_info *sbi = sb->s_fs_info;
squashfs_cache_delete(sbi->block_cache);
squashfs_cache_delete(sbi->fragment_cache);
squashfs_cache_delete(sbi->read_page);
kfree(sbi->id_table);
kfree(sbi->fragment_index);
kfree(sbi->meta_index);
kfree(sbi->stream.workspace);
kfree(sb->s_fs_info);
sb->s_fs_info = NULL;
}
}
static int squashfs_get_sb(struct file_system_type *fs_type, int flags,
const char *dev_name, void *data,
struct vfsmount *mnt)
{
return get_sb_bdev(fs_type, flags, dev_name, data, squashfs_fill_super,
mnt);
}
static struct kmem_cache *squashfs_inode_cachep;
static void init_once(void *foo)
{
struct squashfs_inode_info *ei = foo;
inode_init_once(&ei->vfs_inode);
}
static int __init init_inodecache(void)
{
squashfs_inode_cachep = kmem_cache_create("squashfs_inode_cache",
sizeof(struct squashfs_inode_info), 0,
SLAB_HWCACHE_ALIGN|SLAB_RECLAIM_ACCOUNT, init_once);
return squashfs_inode_cachep ? 0 : -ENOMEM;
}
static void destroy_inodecache(void)
{
kmem_cache_destroy(squashfs_inode_cachep);
}
static int __init init_squashfs_fs(void)
{
int err = init_inodecache();
if (err)
return err;
err = register_filesystem(&squashfs_fs_type);
if (err) {
destroy_inodecache();
return err;
}
printk(KERN_INFO "squashfs: version 4.0 (2009/01/03) "
"Phillip Lougher\n");
return 0;
}
static void __exit exit_squashfs_fs(void)
{
unregister_filesystem(&squashfs_fs_type);
destroy_inodecache();
}
static struct inode *squashfs_alloc_inode(struct super_block *sb)
{
struct squashfs_inode_info *ei =
kmem_cache_alloc(squashfs_inode_cachep, GFP_KERNEL);
return ei ? &ei->vfs_inode : NULL;
}
static void squashfs_destroy_inode(struct inode *inode)
{
kmem_cache_free(squashfs_inode_cachep, squashfs_i(inode));
}
static struct file_system_type squashfs_fs_type = {
.owner = THIS_MODULE,
.name = "squashfs",
.get_sb = squashfs_get_sb,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV
};
static struct super_operations squashfs_super_ops = {
.alloc_inode = squashfs_alloc_inode,
.destroy_inode = squashfs_destroy_inode,
.statfs = squashfs_statfs,
.put_super = squashfs_put_super,
.remount_fs = squashfs_remount
};
module_init(init_squashfs_fs);
module_exit(exit_squashfs_fs);
MODULE_DESCRIPTION("squashfs 4.0, a compressed read-only filesystem");
MODULE_AUTHOR("Phillip Lougher <phillip@lougher.demon.co.uk>");
MODULE_LICENSE("GPL");
/*
* Squashfs - a compressed read only filesystem for Linux
*
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
* Phillip Lougher <phillip@lougher.demon.co.uk>
*
* 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,
* 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* symlink.c
*/
/*
* This file implements code to handle symbolic links.
*
* The data contents of symbolic links are stored inside the symbolic
* link inode within the inode table. This allows the normally small symbolic
* link to be compressed as part of the inode table, achieving much greater
* compression than if the symbolic link was compressed individually.
*/
#include <linux/fs.h>
#include <linux/vfs.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/pagemap.h>
#include <linux/zlib.h>
#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs_fs_i.h"
#include "squashfs.h"
static int squashfs_symlink_readpage(struct file *file, struct page *page)
{
struct inode *inode = page->mapping->host;
struct super_block *sb = inode->i_sb;
struct squashfs_sb_info *msblk = sb->s_fs_info;
int index = page->index << PAGE_CACHE_SHIFT;
u64 block = squashfs_i(inode)->start;
int offset = squashfs_i(inode)->offset;
int length = min_t(int, i_size_read(inode) - index, PAGE_CACHE_SIZE);
int bytes, copied;
void *pageaddr;
struct squashfs_cache_entry *entry;
TRACE("Entered squashfs_symlink_readpage, page index %ld, start block "
"%llx, offset %x\n", page->index, block, offset);
/*
* Skip index bytes into symlink metadata.
*/
if (index) {
bytes = squashfs_read_metadata(sb, NULL, &block, &offset,
index);
if (bytes < 0) {
ERROR("Unable to read symlink [%llx:%x]\n",
squashfs_i(inode)->start,
squashfs_i(inode)->offset);
goto error_out;
}
}
/*
* Read length bytes from symlink metadata. Squashfs_read_metadata
* is not used here because it can sleep and we want to use
* kmap_atomic to map the page. Instead call the underlying
* squashfs_cache_get routine. As length bytes may overlap metadata
* blocks, we may need to call squashfs_cache_get multiple times.
*/
for (bytes = 0; bytes < length; offset = 0, bytes += copied) {
entry = squashfs_cache_get(sb, msblk->block_cache, block, 0);
if (entry->error) {
ERROR("Unable to read symlink [%llx:%x]\n",
squashfs_i(inode)->start,
squashfs_i(inode)->offset);
squashfs_cache_put(entry);
goto error_out;
}
pageaddr = kmap_atomic(page, KM_USER0);
copied = squashfs_copy_data(pageaddr + bytes, entry, offset,
length - bytes);
if (copied == length - bytes)
memset(pageaddr + length, 0, PAGE_CACHE_SIZE - length);
else
block = entry->next_index;
kunmap_atomic(pageaddr, KM_USER0);
squashfs_cache_put(entry);
}
flush_dcache_page(page);
SetPageUptodate(page);
unlock_page(page);
return 0;
error_out:
SetPageError(page);
unlock_page(page);
return 0;
}
const struct address_space_operations squashfs_symlink_aops = {
.readpage = squashfs_symlink_readpage
};
#ifndef SQUASHFS_FS
#define SQUASHFS_FS
/*
* Squashfs
*
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
* Phillip Lougher <phillip@lougher.demon.co.uk>
*
* 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,
* 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* squashfs_fs.h
*/
#define SQUASHFS_CACHED_FRAGMENTS CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE
#define SQUASHFS_MAJOR 4
#define SQUASHFS_MINOR 0
#define SQUASHFS_MAGIC 0x73717368
#define SQUASHFS_START 0
/* size of metadata (inode and directory) blocks */
#define SQUASHFS_METADATA_SIZE 8192
#define SQUASHFS_METADATA_LOG 13
/* default size of data blocks */
#define SQUASHFS_FILE_SIZE 131072
#define SQUASHFS_FILE_LOG 17
#define SQUASHFS_FILE_MAX_SIZE 1048576
/* Max number of uids and gids */
#define SQUASHFS_IDS 65536
/* Max length of filename (not 255) */
#define SQUASHFS_NAME_LEN 256
#define SQUASHFS_INVALID_FRAG (0xffffffffU)
#define SQUASHFS_INVALID_BLK (-1LL)
/* Filesystem flags */
#define SQUASHFS_NOI 0
#define SQUASHFS_NOD 1
#define SQUASHFS_NOF 3
#define SQUASHFS_NO_FRAG 4
#define SQUASHFS_ALWAYS_FRAG 5
#define SQUASHFS_DUPLICATE 6
#define SQUASHFS_EXPORT 7
#define SQUASHFS_BIT(flag, bit) ((flag >> bit) & 1)
#define SQUASHFS_UNCOMPRESSED_INODES(flags) SQUASHFS_BIT(flags, \
SQUASHFS_NOI)
#define SQUASHFS_UNCOMPRESSED_DATA(flags) SQUASHFS_BIT(flags, \
SQUASHFS_NOD)
#define SQUASHFS_UNCOMPRESSED_FRAGMENTS(flags) SQUASHFS_BIT(flags, \
SQUASHFS_NOF)
#define SQUASHFS_NO_FRAGMENTS(flags) SQUASHFS_BIT(flags, \
SQUASHFS_NO_FRAG)
#define SQUASHFS_ALWAYS_FRAGMENTS(flags) SQUASHFS_BIT(flags, \
SQUASHFS_ALWAYS_FRAG)
#define SQUASHFS_DUPLICATES(flags) SQUASHFS_BIT(flags, \
SQUASHFS_DUPLICATE)
#define SQUASHFS_EXPORTABLE(flags) SQUASHFS_BIT(flags, \
SQUASHFS_EXPORT)
/* Max number of types and file types */
#define SQUASHFS_DIR_TYPE 1
#define SQUASHFS_REG_TYPE 2
#define SQUASHFS_SYMLINK_TYPE 3
#define SQUASHFS_BLKDEV_TYPE 4
#define SQUASHFS_CHRDEV_TYPE 5
#define SQUASHFS_FIFO_TYPE 6
#define SQUASHFS_SOCKET_TYPE 7
#define SQUASHFS_LDIR_TYPE 8
#define SQUASHFS_LREG_TYPE 9
#define SQUASHFS_LSYMLINK_TYPE 10
#define SQUASHFS_LBLKDEV_TYPE 11
#define SQUASHFS_LCHRDEV_TYPE 12
#define SQUASHFS_LFIFO_TYPE 13
#define SQUASHFS_LSOCKET_TYPE 14
/* Flag whether block is compressed or uncompressed, bit is set if block is
* uncompressed */
#define SQUASHFS_COMPRESSED_BIT (1 << 15)
#define SQUASHFS_COMPRESSED_SIZE(B) (((B) & ~SQUASHFS_COMPRESSED_BIT) ? \
(B) & ~SQUASHFS_COMPRESSED_BIT : SQUASHFS_COMPRESSED_BIT)
#define SQUASHFS_COMPRESSED(B) (!((B) & SQUASHFS_COMPRESSED_BIT))
#define SQUASHFS_COMPRESSED_BIT_BLOCK (1 << 24)
#define SQUASHFS_COMPRESSED_SIZE_BLOCK(B) ((B) & \
~SQUASHFS_COMPRESSED_BIT_BLOCK)
#define SQUASHFS_COMPRESSED_BLOCK(B) (!((B) & SQUASHFS_COMPRESSED_BIT_BLOCK))
/*
* Inode number ops. Inodes consist of a compressed block number, and an
* uncompressed offset within that block
*/
#define SQUASHFS_INODE_BLK(A) ((unsigned int) ((A) >> 16))
#define SQUASHFS_INODE_OFFSET(A) ((unsigned int) ((A) & 0xffff))
#define SQUASHFS_MKINODE(A, B) ((long long)(((long long) (A)\
<< 16) + (B)))
/* Translate between VFS mode and squashfs mode */
#define SQUASHFS_MODE(A) ((A) & 0xfff)
/* fragment and fragment table defines */
#define SQUASHFS_FRAGMENT_BYTES(A) \
((A) * sizeof(struct squashfs_fragment_entry))
#define SQUASHFS_FRAGMENT_INDEX(A) (SQUASHFS_FRAGMENT_BYTES(A) / \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_FRAGMENT_INDEX_OFFSET(A) (SQUASHFS_FRAGMENT_BYTES(A) % \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_FRAGMENT_INDEXES(A) ((SQUASHFS_FRAGMENT_BYTES(A) + \
SQUASHFS_METADATA_SIZE - 1) / \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_FRAGMENT_INDEX_BYTES(A) (SQUASHFS_FRAGMENT_INDEXES(A) *\
sizeof(long long))
/* inode lookup table defines */
#define SQUASHFS_LOOKUP_BYTES(A) ((A) * sizeof(long long))
#define SQUASHFS_LOOKUP_BLOCK(A) (SQUASHFS_LOOKUP_BYTES(A) / \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_LOOKUP_BLOCK_OFFSET(A) (SQUASHFS_LOOKUP_BYTES(A) % \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_LOOKUP_BLOCKS(A) ((SQUASHFS_LOOKUP_BYTES(A) + \
SQUASHFS_METADATA_SIZE - 1) / \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_LOOKUP_BLOCK_BYTES(A) (SQUASHFS_LOOKUP_BLOCKS(A) *\
sizeof(long long))
/* uid/gid lookup table defines */
#define SQUASHFS_ID_BYTES(A) ((A) * sizeof(unsigned int))
#define SQUASHFS_ID_BLOCK(A) (SQUASHFS_ID_BYTES(A) / \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_ID_BLOCK_OFFSET(A) (SQUASHFS_ID_BYTES(A) % \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_ID_BLOCKS(A) ((SQUASHFS_ID_BYTES(A) + \
SQUASHFS_METADATA_SIZE - 1) / \
SQUASHFS_METADATA_SIZE)
#define SQUASHFS_ID_BLOCK_BYTES(A) (SQUASHFS_ID_BLOCKS(A) *\
sizeof(long long))
/* cached data constants for filesystem */
#define SQUASHFS_CACHED_BLKS 8
#define SQUASHFS_MAX_FILE_SIZE_LOG 64
#define SQUASHFS_MAX_FILE_SIZE (1LL << \
(SQUASHFS_MAX_FILE_SIZE_LOG - 2))
#define SQUASHFS_MARKER_BYTE 0xff
/* meta index cache */
#define SQUASHFS_META_INDEXES (SQUASHFS_METADATA_SIZE / sizeof(unsigned int))
#define SQUASHFS_META_ENTRIES 127
#define SQUASHFS_META_SLOTS 8
struct meta_entry {
long long data_block;
unsigned int index_block;
unsigned short offset;
unsigned short pad;
};
struct meta_index {
unsigned int inode_number;
unsigned int offset;
unsigned short entries;
unsigned short skip;
unsigned short locked;
unsigned short pad;
struct meta_entry meta_entry[SQUASHFS_META_ENTRIES];
};
/*
* definitions for structures on disk
*/
#define ZLIB_COMPRESSION 1
struct squashfs_super_block {
__le32 s_magic;
__le32 inodes;
__le32 mkfs_time;
__le32 block_size;
__le32 fragments;
__le16 compression;
__le16 block_log;
__le16 flags;
__le16 no_ids;
__le16 s_major;
__le16 s_minor;
__le64 root_inode;
__le64 bytes_used;
__le64 id_table_start;
__le64 xattr_table_start;
__le64 inode_table_start;
__le64 directory_table_start;
__le64 fragment_table_start;
__le64 lookup_table_start;
};
struct squashfs_dir_index {
__le32 index;
__le32 start_block;
__le32 size;
unsigned char name[0];
};
struct squashfs_base_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
};
struct squashfs_ipc_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 nlink;
};
struct squashfs_dev_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 nlink;
__le32 rdev;
};
struct squashfs_symlink_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 nlink;
__le32 symlink_size;
char symlink[0];
};
struct squashfs_reg_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 start_block;
__le32 fragment;
__le32 offset;
__le32 file_size;
__le16 block_list[0];
};
struct squashfs_lreg_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le64 start_block;
__le64 file_size;
__le64 sparse;
__le32 nlink;
__le32 fragment;
__le32 offset;
__le32 xattr;
__le16 block_list[0];
};
struct squashfs_dir_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 start_block;
__le32 nlink;
__le16 file_size;
__le16 offset;
__le32 parent_inode;
};
struct squashfs_ldir_inode {
__le16 inode_type;
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 nlink;
__le32 file_size;
__le32 start_block;
__le32 parent_inode;
__le16 i_count;
__le16 offset;
__le32 xattr;
struct squashfs_dir_index index[0];
};
union squashfs_inode {
struct squashfs_base_inode base;
struct squashfs_dev_inode dev;
struct squashfs_symlink_inode symlink;
struct squashfs_reg_inode reg;
struct squashfs_lreg_inode lreg;
struct squashfs_dir_inode dir;
struct squashfs_ldir_inode ldir;
struct squashfs_ipc_inode ipc;
};
struct squashfs_dir_entry {
__le16 offset;
__le16 inode_number;
__le16 type;
__le16 size;
char name[0];
};
struct squashfs_dir_header {
__le32 count;
__le32 start_block;
__le32 inode_number;
};
struct squashfs_fragment_entry {
__le64 start_block;
__le32 size;
unsigned int unused;
};
#endif
#ifndef SQUASHFS_FS_I
#define SQUASHFS_FS_I
/*
* Squashfs
*
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
* Phillip Lougher <phillip@lougher.demon.co.uk>
*
* 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,
* 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* squashfs_fs_i.h
*/
struct squashfs_inode_info {
long long start;
int offset;
union {
struct {
long long fragment_block;
int fragment_size;
int fragment_offset;
long long block_list_start;
};
struct {
long long dir_idx_start;
int dir_idx_offset;
int dir_idx_cnt;
int parent;
};
};
struct inode vfs_inode;
};
#endif
#ifndef SQUASHFS_FS_SB
#define SQUASHFS_FS_SB
/*
* Squashfs
*
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
* Phillip Lougher <phillip@lougher.demon.co.uk>
*
* 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,
* 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* squashfs_fs_sb.h
*/
#include "squashfs_fs.h"
struct squashfs_cache_entry {
long long block;
int length;
int locked;
long long next_index;
char pending;
char error;
int waiting;
wait_queue_head_t wait_queue;
char *data;
};
struct squashfs_cache {
char *name;
int entries;
int block_size;
int next_blk;
int waiting;
int unused;
int use_vmalloc;
spinlock_t lock;
wait_queue_head_t wait_queue;
struct squashfs_cache_entry entry[0];
};
struct squashfs_sb_info {
int devblksize;
int devblksize_log2;
struct squashfs_cache *block_cache;
struct squashfs_cache *fragment_cache;
int next_meta_index;
__le64 *id_table;
__le64 *fragment_index;
unsigned int *fragment_index_2;
char *read_page;
struct mutex read_data_mutex;
struct mutex read_page_mutex;
struct mutex meta_index_mutex;
struct meta_index *meta_index;
z_stream stream;
__le64 *inode_lookup_table;
long long inode_table;
long long directory_table;
unsigned int block_size;
unsigned short block_log;
long long bytes_used;
unsigned int inodes;
};
#endif
###############################################
# Compression build options #
###############################################
#
#
############# Building gzip support ###########
#
# Gzip support is by default enabled, and the compression type default
# (COMP_DEFAULT) is gzip.
#
# If you don't want/need gzip support then comment out the GZIP SUPPORT line
# below, and change COMP_DEFAULT to one of the compression types you have
# selected.
#
# Obviously, you must select at least one of the available gzip, lzma, lzo
# compression types.
#
GZIP_SUPPORT = 1
########### Building XZ support #############
#
# LZMA2 compression.
#
# XZ Utils liblzma (http://tukaani.org/xz/) is supported
#
# Development packages (libraries and header files) should be
# supported by most modern distributions. Please refer to
# your distribution package manager.
#
# To build install the library and uncomment
# the XZ_SUPPORT line below.
#
XZ_SUPPORT = 1
############ Building LZO support ##############
#
# The LZO library (http://www.oberhumer.com/opensource/lzo/) is supported.
#
# Development packages (libraries and header files) should be
# supported by most modern distributions. Please refer to
# your distribution package manager.
#
# To build install the library and uncomment
# the XZ_SUPPORT line below.
#
LZO_SUPPORT = 1
########### Building LZ4 support #############
#
# Yann Collet's LZ4 tools are supported
# LZ4 homepage: http://fastcompression.blogspot.com/p/lz4.html
# LZ4 source repository: http://code.google.com/p/lz4
#
# Development packages (libraries and header files) should be
# supported by most modern distributions. Please refer to
# your distribution package manager.
#
# To build install and uncomment
# the LZ4_SUPPORT line below.
#
LZ4_SUPPORT = 1
########### Building ZSTD support ############
#
# The ZSTD library is supported
# ZSTD homepage: http://zstd.net
# ZSTD source repository: https://github.com/facebook/zstd
#
# Development packages (libraries and header files) should be
# supported by most modern distributions. Please refer to
# your distribution package manager.
#
# To build install the library and uncomment
# the XZ_SUPPORT line below.
#
ZSTD_SUPPORT = 1
######## Specifying default compression ########
#
# The next line specifies which compression algorithm is used by default
# in Mksquashfs. Obviously the compression algorithm must have been
# selected to be built
#
COMP_DEFAULT = gzip
###############################################
# Extended attribute (XATTRs) build options #
###############################################
#
# Building XATTR support for Mksquashfs and Unsquashfs
#
# If your C library or build/target environment doesn't support XATTRs then
# comment out the next line to build Mksquashfs and Unsquashfs without XATTR
# support
XATTR_SUPPORT = 1
# Select whether you wish xattrs to be stored by Mksquashfs and extracted
# by Unsquashfs by default. If selected users can disable xattr support by
# using the -no-xattrs option
#
# If unselected, Mksquashfs/Unsquashfs won't store and extract xattrs by
# default. Users can enable xattrs by using the -xattrs option.
XATTR_DEFAULT = 1
###############################################
# Reproducible Image options #
###############################################
#
# Select whether you wish reproducible builds by default. If selected users
# can disable reproducible builds using the not-reproducible option.
# If not selected, users can enable reproducible builds using the
# -reproducible option
REPRODUCIBLE_DEFAULT = 1
###############################################
# Obsolete options #
###############################################
########### Building LZMA support #############
#
# LZMA1 compression.
#
# LZMA1 compression is obsolete, and the newer and better XZ (LZMA2)
# compression should be used in preference.
#
# Both XZ Utils liblzma (http://tukaani.org/xz/) and LZMA SDK
# (http://www.7-zip.org/sdk.html) are supported
#
# To build using XZ Utils liblzma - install the library and uncomment
# the LZMA_XZ_SUPPORT line below.
#
# To build using the LZMA SDK (4.65 used in development, other versions may
# work) - download and unpack it, uncomment and set LZMA_DIR to unpacked source,
# and uncomment the LZMA_SUPPORT line below.
#
LZMA_XZ_SUPPORT = 1
#LZMA_SUPPORT = 1
#LZMA_DIR = ../../../../LZMA/lzma465
###############################################
# End of BUILD options section #
###############################################
INCLUDEDIR = -I.
INSTALL_DIR = /usr/local/bin
MKSQUASHFS_OBJS = mksquashfs.o read_fs.o action.o swap.o pseudo.o compressor.o \
sort.o progressbar.o read_file.o info.o restore.o process_fragments.o \
caches-queues-lists.o
UNSQUASHFS_OBJS = unsquashfs.o unsquash-1.o unsquash-2.o unsquash-3.o \
unsquash-4.o unsquash-123.o unsquash-34.o swap.o compressor.o unsquashfs_info.o
CFLAGS ?= -Os
CFLAGS += $(EXTRA_CFLAGS) $(INCLUDEDIR) -D_FILE_OFFSET_BITS=64 \
-D_LARGEFILE_SOURCE -D_GNU_SOURCE -DCOMP_DEFAULT=\"$(COMP_DEFAULT)\" \
-Wall
LIBS = -lpthread
ifeq ($(GZIP_SUPPORT),1)
CFLAGS += -DGZIP_SUPPORT
MKSQUASHFS_OBJS += gzip_wrapper.o
UNSQUASHFS_OBJS += gzip_wrapper.o
LIBS += $(VTZLIB)
COMPRESSORS += gzip
endif
ifeq ($(LZMA_SUPPORT),1)
LZMA_OBJS = $(LZMA_DIR)/C/Alloc.o $(LZMA_DIR)/C/LzFind.o \
$(LZMA_DIR)/C/LzmaDec.o $(LZMA_DIR)/C/LzmaEnc.o $(LZMA_DIR)/C/LzmaLib.o
INCLUDEDIR += -I$(LZMA_DIR)/C
CFLAGS += -DLZMA_SUPPORT
MKSQUASHFS_OBJS += lzma_wrapper.o $(LZMA_OBJS)
UNSQUASHFS_OBJS += lzma_wrapper.o $(LZMA_OBJS)
COMPRESSORS += lzma
endif
ifeq ($(LZMA_XZ_SUPPORT),1)
CFLAGS += -DLZMA_SUPPORT
MKSQUASHFS_OBJS += lzma_xz_wrapper.o
UNSQUASHFS_OBJS += lzma_xz_wrapper.o
LIBS +=
COMPRESSORS += lzma
endif
ifeq ($(XZ_SUPPORT),1)
CFLAGS += -DXZ_SUPPORT -I$(LZMA_LIBDIR)/include
MKSQUASHFS_OBJS += xz_wrapper.o
UNSQUASHFS_OBJS += xz_wrapper.o
LIBS += $(LZMA_LIBDIR)/lib/liblzma.a
COMPRESSORS += xz
endif
ifeq ($(LZO_SUPPORT),1)
CFLAGS += -DLZO_SUPPORT -I $(LZO_LIBDIR)/include
MKSQUASHFS_OBJS += lzo_wrapper.o
UNSQUASHFS_OBJS += lzo_wrapper.o
LIBS += $(LZO_LIBDIR)/lib/liblzo2.a
COMPRESSORS += lzo
endif
ifeq ($(LZ4_SUPPORT),1)
CFLAGS += -DLZ4_SUPPORT -I $(LZ4_LIBDIR)/include
MKSQUASHFS_OBJS += lz4_wrapper.o
UNSQUASHFS_OBJS += lz4_wrapper.o
LIBS += $(LZ4_LIBDIR)/lib/liblz4.a
COMPRESSORS += lz4
endif
ifeq ($(ZSTD_SUPPORT),1)
CFLAGS += -DZSTD_SUPPORT -I $(ZSTD_LIBDIR)/include
MKSQUASHFS_OBJS += zstd_wrapper.o
UNSQUASHFS_OBJS += zstd_wrapper.o
LIBS += $(ZSTD_LIBDIR)/lib/libzstd.a
COMPRESSORS += zstd
endif
ifeq ($(XATTR_SUPPORT),1)
ifeq ($(XATTR_DEFAULT),1)
CFLAGS += -DXATTR_SUPPORT -DXATTR_DEFAULT
else
CFLAGS += -DXATTR_SUPPORT
endif
MKSQUASHFS_OBJS += xattr.o read_xattrs.o
UNSQUASHFS_OBJS += read_xattrs.o unsquashfs_xattr.o
endif
ifeq ($(REPRODUCIBLE_DEFAULT),1)
CFLAGS += -DREPRODUCIBLE_DEFAULT
endif
#
# If LZMA_SUPPORT is specified then LZMA_DIR must be specified too
#
ifeq ($(LZMA_SUPPORT),1)
ifndef LZMA_DIR
$(error "LZMA_SUPPORT requires LZMA_DIR to be also defined")
endif
endif
#
# Both LZMA_XZ_SUPPORT and LZMA_SUPPORT cannot be specified
#
ifeq ($(LZMA_XZ_SUPPORT),1)
ifeq ($(LZMA_SUPPORT),1)
$(error "Both LZMA_XZ_SUPPORT and LZMA_SUPPORT cannot be specified")
endif
endif
#
# At least one compressor must have been selected
#
ifndef COMPRESSORS
$(error "No compressor selected! Select one or more of GZIP, LZMA, XZ, LZO, \
LZ4 or ZSTD!")
endif
#
# COMP_DEFAULT should be defined
#
ifndef COMP_DEFAULT
$(error "COMP_DEFAULT must be set to a compressor!")
endif
#
# COMP_DEFAULT must be a selected compressor
#
ifeq (, $(findstring $(COMP_DEFAULT), $(COMPRESSORS)))
$(error "COMP_DEFAULT is set to ${COMP_DEFAULT}, which isn't selected to be \
built!")
endif
.PHONY: all
all: mksquashfs unsquashfs
mksquashfs: $(MKSQUASHFS_OBJS)
$(CC) $(LDFLAGS) $(EXTRA_LDFLAGS) $(MKSQUASHFS_OBJS) $(LIBS) -o $@
mksquashfs.o: Makefile mksquashfs.c squashfs_fs.h squashfs_swap.h mksquashfs.h \
sort.h pseudo.h compressor.h xattr.h action.h error.h progressbar.h \
info.h caches-queues-lists.h read_fs.h restore.h process_fragments.h
read_fs.o: read_fs.c squashfs_fs.h squashfs_swap.h compressor.h xattr.h \
error.h mksquashfs.h
sort.o: sort.c squashfs_fs.h mksquashfs.h sort.h error.h progressbar.h
swap.o: swap.c
pseudo.o: pseudo.c pseudo.h error.h progressbar.h
compressor.o: Makefile compressor.c compressor.h squashfs_fs.h
xattr.o: xattr.c squashfs_fs.h squashfs_swap.h mksquashfs.h xattr.h error.h \
progressbar.h
read_xattrs.o: read_xattrs.c squashfs_fs.h squashfs_swap.h xattr.h error.h
action.o: action.c squashfs_fs.h mksquashfs.h action.h error.h
progressbar.o: progressbar.c error.h
read_file.o: read_file.c error.h
info.o: info.c squashfs_fs.h mksquashfs.h error.h progressbar.h \
caches-queues-lists.h
restore.o: restore.c caches-queues-lists.h squashfs_fs.h mksquashfs.h error.h \
progressbar.h info.h
process_fragments.o: process_fragments.c process_fragments.h
caches-queues-lists.o: caches-queues-lists.c error.h caches-queues-lists.h
gzip_wrapper.o: gzip_wrapper.c squashfs_fs.h gzip_wrapper.h compressor.h
lzma_wrapper.o: lzma_wrapper.c compressor.h squashfs_fs.h
lzma_xz_wrapper.o: lzma_xz_wrapper.c compressor.h squashfs_fs.h
lzo_wrapper.o: lzo_wrapper.c squashfs_fs.h lzo_wrapper.h compressor.h
lz4_wrapper.o: lz4_wrapper.c squashfs_fs.h lz4_wrapper.h compressor.h
xz_wrapper.o: xz_wrapper.c squashfs_fs.h xz_wrapper.h compressor.h
unsquashfs: $(UNSQUASHFS_OBJS)
$(CC) $(LDFLAGS) $(EXTRA_LDFLAGS) $(UNSQUASHFS_OBJS) $(LIBS) -o $@
unsquashfs.o: unsquashfs.h unsquashfs.c squashfs_fs.h squashfs_swap.h \
squashfs_compat.h xattr.h read_fs.h compressor.h
unsquash-1.o: unsquashfs.h unsquash-1.c squashfs_fs.h squashfs_compat.h
unsquash-2.o: unsquashfs.h unsquash-2.c squashfs_fs.h squashfs_compat.h
unsquash-3.o: unsquashfs.h unsquash-3.c squashfs_fs.h squashfs_compat.h
unsquash-4.o: unsquashfs.h unsquash-4.c squashfs_fs.h squashfs_swap.h \
read_fs.h
unsquash-123.o: unsquashfs.h unsquash-123.c squashfs_fs.h squashfs_compat.h
unsquash-34.o: unsquashfs.h unsquash-34.c
unsquashfs_xattr.o: unsquashfs_xattr.c unsquashfs.h squashfs_fs.h xattr.h
unsquashfs_info.o: unsquashfs.h squashfs_fs.h
.PHONY: clean
clean:
-rm -f *.o mksquashfs unsquashfs
.PHONY: install
install: mksquashfs unsquashfs
mkdir -p $(INSTALL_DIR)
cp mksquashfs $(INSTALL_DIR)
cp unsquashfs $(INSTALL_DIR)
/*
* Create a squashfs filesystem. This is a highly compressed read only
* filesystem.
*
* Copyright (c) 2011, 2012, 2013, 2014
* Phillip Lougher <phillip@squashfs.org.uk>
*
* 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,
* 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* action.c
*/
#include <fcntl.h>
#include <dirent.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <sys/wait.h>
#include <regex.h>
#include <limits.h>
#include <errno.h>
#include "squashfs_fs.h"
#include "mksquashfs.h"
#include "action.h"
#include "error.h"
#include "fnmatch_compat.h"
/*
* code to parse actions
*/
static char *cur_ptr, *source;
static struct action *fragment_spec = NULL;
static struct action *exclude_spec = NULL;
static struct action *empty_spec = NULL;
static struct action *move_spec = NULL;
static struct action *prune_spec = NULL;
static struct action *other_spec = NULL;
static int fragment_count = 0;
static int exclude_count = 0;
static int empty_count = 0;
static int move_count = 0;
static int prune_count = 0;
static int other_count = 0;
static struct action_entry *parsing_action;
static struct file_buffer *def_fragment = NULL;
static struct token_entry token_table[] = {
{ "(", TOK_OPEN_BRACKET, 1, },
{ ")", TOK_CLOSE_BRACKET, 1 },
{ "&&", TOK_AND, 2 },
{ "||", TOK_OR, 2 },
{ "!", TOK_NOT, 1 },
{ ",", TOK_COMMA, 1 },
{ "@", TOK_AT, 1},
{ " ", TOK_WHITE_SPACE, 1 },
{ "\t ", TOK_WHITE_SPACE, 1 },
{ "", -1, 0 }
};
static struct test_entry test_table[];
static struct action_entry action_table[];
static struct expr *parse_expr(int subexp);
extern char *pathname(struct dir_ent *);
extern char *subpathname(struct dir_ent *);
extern int read_file(char *filename, char *type, int (parse_line)(char *));
/*
* Lexical analyser
*/
#define STR_SIZE 256
static int get_token(char **string)
{
/* string buffer */
static char *str = NULL;
static int size = 0;
char *str_ptr;
int cur_size, i, quoted;
while (1) {
if (*cur_ptr == '\0')
return TOK_EOF;
for (i = 0; token_table[i].token != -1; i++)
if (strncmp(cur_ptr, token_table[i].string,
token_table[i].size) == 0)
break;
if (token_table[i].token != TOK_WHITE_SPACE)
break;
cur_ptr ++;
}
if (token_table[i].token != -1) {
cur_ptr += token_table[i].size;
return token_table[i].token;
}
/* string */
if(str == NULL) {
str = malloc(STR_SIZE);
if(str == NULL)
MEM_ERROR();
size = STR_SIZE;
}
/* Initialise string being read */
str_ptr = str;
cur_size = 0;
quoted = 0;
while(1) {
while(*cur_ptr == '"') {
cur_ptr ++;
quoted = !quoted;
}
if(*cur_ptr == '\0') {
/* inside quoted string EOF, otherwise end of string */
if(quoted)
return TOK_EOF;
else
break;
}
if(!quoted) {
for(i = 0; token_table[i].token != -1; i++)
if (strncmp(cur_ptr, token_table[i].string,
token_table[i].size) == 0)
break;
if (token_table[i].token != -1)
break;
}
if(*cur_ptr == '\\') {
cur_ptr ++;
if(*cur_ptr == '\0')
return TOK_EOF;
}
if(cur_size + 2 > size) {
char *tmp;
size = (cur_size + 1 + STR_SIZE) & ~(STR_SIZE - 1);
tmp = realloc(str, size);
if(tmp == NULL)
MEM_ERROR();
str_ptr = str_ptr - str + tmp;
str = tmp;
}
*str_ptr ++ = *cur_ptr ++;
cur_size ++;
}
*str_ptr = '\0';
*string = str;
return TOK_STRING;
}
static int peek_token(char **string)
{
char *saved = cur_ptr;
int token = get_token(string);
cur_ptr = saved;
return token;
}
/*
* Expression parser
*/
static void free_parse_tree(struct expr *expr)
{
if(expr->type == ATOM_TYPE) {
int i;
for(i = 0; i < expr->atom.test->args; i++)
free(expr->atom.argv[i]);
free(expr->atom.argv);
} else if (expr->type == UNARY_TYPE)
free_parse_tree(expr->unary_op.expr);
else {
free_parse_tree(expr->expr_op.lhs);
free_parse_tree(expr->expr_op.rhs);
}
free(expr);
}
static struct expr *create_expr(struct expr *lhs, int op, struct expr *rhs)
{
struct expr *expr;
if (rhs == NULL) {
free_parse_tree(lhs);
return NULL;
}
expr = malloc(sizeof(*expr));
if (expr == NULL)
MEM_ERROR();
expr->type = OP_TYPE;
expr->expr_op.lhs = lhs;
expr->expr_op.rhs = rhs;
expr->expr_op.op = op;
return expr;
}
static struct expr *create_unary_op(struct expr *lhs, int op)
{
struct expr *expr;
if (lhs == NULL)
return NULL;
expr = malloc(sizeof(*expr));
if (expr == NULL)
MEM_ERROR();
expr->type = UNARY_TYPE;
expr->unary_op.expr = lhs;
expr->unary_op.op = op;
return expr;
}
static struct expr *parse_test(char *name)
{
char *string, **argv = NULL;
int token, args = 0;
int i;
struct test_entry *test;
struct expr *expr;
for (i = 0; test_table[i].args != -1; i++)
if (strcmp(name, test_table[i].name) == 0)
break;
test = &test_table[i];
if (test->args == -1) {
SYNTAX_ERROR("Non-existent test \"%s\"\n", name);
return NULL;
}
if(parsing_action->type == EXCLUDE_ACTION && !test->exclude_ok) {
fprintf(stderr, "Failed to parse action \"%s\"\n", source);
fprintf(stderr, "Test \"%s\" cannot be used in exclude "
"actions\n", name);
fprintf(stderr, "Use prune action instead ...\n");
return NULL;
}
expr = malloc(sizeof(*expr));
if (expr == NULL)
MEM_ERROR();
expr->type = ATOM_TYPE;
expr->atom.test = test;
expr->atom.data = NULL;
/*
* If the test has no arguments, then go straight to checking if there's
* enough arguments
*/
token = peek_token(&string);
if (token != TOK_OPEN_BRACKET)
goto skip_args;
get_token(&string);
/*
* speculatively read all the arguments, and then see if the
* number of arguments read is the number expected, this handles
* tests with a variable number of arguments
*/
token = get_token(&string);
if (token == TOK_CLOSE_BRACKET)
goto skip_args;
while(1) {
if (token != TOK_STRING) {
SYNTAX_ERROR("Unexpected token \"%s\", expected "
"argument\n", TOK_TO_STR(token, string));
goto failed;
}
argv = realloc(argv, (args + 1) * sizeof(char *));
if (argv == NULL)
MEM_ERROR();
argv[args ++ ] = strdup(string);
token = get_token(&string);
if (token == TOK_CLOSE_BRACKET)
break;
if (token != TOK_COMMA) {
SYNTAX_ERROR("Unexpected token \"%s\", expected "
"\",\" or \")\"\n", TOK_TO_STR(token, string));
goto failed;
}
token = get_token(&string);
}
skip_args:
/*
* expected number of arguments?
*/
if(test->args != -2 && args != test->args) {
SYNTAX_ERROR("Unexpected number of arguments, expected %d, "
"got %d\n", test->args, args);
goto failed;
}
expr->atom.args = args;
expr->atom.argv = argv;
if (test->parse_args) {
int res = test->parse_args(test, &expr->atom);
if (res == 0)
goto failed;
}
return expr;
failed:
free(argv);
free(expr);
return NULL;
}
static struct expr *get_atom()
{
char *string;
int token = get_token(&string);
switch(token) {
case TOK_NOT:
return create_unary_op(get_atom(), token);
case TOK_OPEN_BRACKET:
return parse_expr(1);
case TOK_STRING:
return parse_test(string);
default:
SYNTAX_ERROR("Unexpected token \"%s\", expected test "
"operation, \"!\", or \"(\"\n",
TOK_TO_STR(token, string));
return NULL;
}
}
static struct expr *parse_expr(int subexp)
{
struct expr *expr = get_atom();
while (expr) {
char *string;
int op = get_token(&string);
if (op == TOK_EOF) {
if (subexp) {
free_parse_tree(expr);
SYNTAX_ERROR("Expected \"&&\", \"||\" or "
"\")\", got EOF\n");
return NULL;
}
break;
}
if (op == TOK_CLOSE_BRACKET) {
if (!subexp) {
free_parse_tree(expr);
SYNTAX_ERROR("Unexpected \")\", expected "
"\"&&\", \"!!\" or EOF\n");
return NULL;
}
break;
}
if (op != TOK_AND && op != TOK_OR) {
free_parse_tree(expr);
SYNTAX_ERROR("Unexpected token \"%s\", expected "
"\"&&\" or \"||\"\n", TOK_TO_STR(op, string));
return NULL;
}
expr = create_expr(expr, op, get_atom());
}
return expr;
}
/*
* Action parser
*/
int parse_action(char *s, int verbose)
{
char *string, **argv = NULL;
int i, token, args = 0;
struct expr *expr;
struct action_entry *action;
void *data = NULL;
struct action **spec_list;
int spec_count;
cur_ptr = source = s;
token = get_token(&string);
if (token != TOK_STRING) {
SYNTAX_ERROR("Unexpected token \"%s\", expected name\n",
TOK_TO_STR(token, string));
return 0;
}
for (i = 0; action_table[i].args != -1; i++)
if (strcmp(string, action_table[i].name) == 0)
break;
if (action_table[i].args == -1) {
SYNTAX_ERROR("Non-existent action \"%s\"\n", string);
return 0;
}
action = &action_table[i];
token = get_token(&string);
if (token == TOK_AT)
goto skip_args;
if (token != TOK_OPEN_BRACKET) {
SYNTAX_ERROR("Unexpected token \"%s\", expected \"(\"\n",
TOK_TO_STR(token, string));
goto failed;
}
/*
* speculatively read all the arguments, and then see if the
* number of arguments read is the number expected, this handles
* actions with a variable number of arguments
*/
token = get_token(&string);
if (token == TOK_CLOSE_BRACKET)
goto skip_args;
while (1) {
if (token != TOK_STRING) {
SYNTAX_ERROR("Unexpected token \"%s\", expected "
"argument\n", TOK_TO_STR(token, string));
goto failed;
}
argv = realloc(argv, (args + 1) * sizeof(char *));
if (argv == NULL)
MEM_ERROR();
argv[args ++] = strdup(string);
token = get_token(&string);
if (token == TOK_CLOSE_BRACKET)
break;
if (token != TOK_COMMA) {
SYNTAX_ERROR("Unexpected token \"%s\", expected "
"\",\" or \")\"\n", TOK_TO_STR(token, string));
goto failed;
}
token = get_token(&string);
}
skip_args:
/*
* expected number of arguments?
*/
if(action->args != -2 && args != action->args) {
SYNTAX_ERROR("Unexpected number of arguments, expected %d, "
"got %d\n", action->args, args);
goto failed;
}
if (action->parse_args) {
int res = action->parse_args(action, args, argv, &data);
if (res == 0)
goto failed;
}
if (token == TOK_CLOSE_BRACKET)
token = get_token(&string);
if (token != TOK_AT) {
SYNTAX_ERROR("Unexpected token \"%s\", expected \"@\"\n",
TOK_TO_STR(token, string));
goto failed;
}
parsing_action = action;
expr = parse_expr(0);
if (expr == NULL)
goto failed;
/*
* choose action list and increment action counter
*/
switch(action->type) {
case FRAGMENT_ACTION:
spec_count = fragment_count ++;
spec_list = &fragment_spec;
break;
case EXCLUDE_ACTION:
spec_count = exclude_count ++;
spec_list = &exclude_spec;
break;
case EMPTY_ACTION:
spec_count = empty_count ++;
spec_list = &empty_spec;
break;
case MOVE_ACTION:
spec_count = move_count ++;
spec_list = &move_spec;
break;
case PRUNE_ACTION:
spec_count = prune_count ++;
spec_list = &prune_spec;
break;
default:
spec_count = other_count ++;
spec_list = &other_spec;
}
*spec_list = realloc(*spec_list, (spec_count + 1) *
sizeof(struct action));
if (*spec_list == NULL)
MEM_ERROR();
(*spec_list)[spec_count].type = action->type;
(*spec_list)[spec_count].action = action;
(*spec_list)[spec_count].args = args;
(*spec_list)[spec_count].argv = argv;
(*spec_list)[spec_count].expr = expr;
(*spec_list)[spec_count].data = data;
(*spec_list)[spec_count].verbose = verbose;
return 1;
failed:
free(argv);
return 0;
}
/*
* Evaluate expressions
*/
#define ALLOC_SZ 128
#define LOG_ENABLE 0
#define LOG_DISABLE 1
#define LOG_PRINT 2
#define LOG_ENABLED 3
char *_expr_log(char *string, int cmnd)
{
static char *expr_msg = NULL;
static int cur_size = 0, alloc_size = 0;
int size;
switch(cmnd) {
case LOG_ENABLE:
expr_msg = malloc(ALLOC_SZ);
alloc_size = ALLOC_SZ;
cur_size = 0;
return expr_msg;
case LOG_DISABLE:
free(expr_msg);
alloc_size = cur_size = 0;
return expr_msg = NULL;
case LOG_ENABLED:
return expr_msg;
default:
if(expr_msg == NULL)
return NULL;
break;
}
/* if string is empty append '\0' */
size = strlen(string) ? : 1;
if(alloc_size - cur_size < size) {
/* buffer too small, expand */
alloc_size = (cur_size + size + ALLOC_SZ - 1) & ~(ALLOC_SZ - 1);
expr_msg = realloc(expr_msg, alloc_size);
if(expr_msg == NULL)
MEM_ERROR();
}
memcpy(expr_msg + cur_size, string, size);
cur_size += size;
return expr_msg;
}
char *expr_log_cmnd(int cmnd)
{
return _expr_log(NULL, cmnd);
}
char *expr_log(char *string)
{
return _expr_log(string, LOG_PRINT);
}
void expr_log_atom(struct atom *atom)
{
int i;
if(atom->test->handle_logging)
return;
expr_log(atom->test->name);
if(atom->args) {
expr_log("(");
for(i = 0; i < atom->args; i++) {
expr_log(atom->argv[i]);
if (i + 1 < atom->args)
expr_log(",");
}
expr_log(")");
}
}
void expr_log_match(int match)
{
if(match)
expr_log("=True");
else
expr_log("=False");
}
static int eval_expr_log(struct expr *expr, struct action_data *action_data)
{
int match;
switch (expr->type) {
case ATOM_TYPE:
expr_log_atom(&expr->atom);
match = expr->atom.test->fn(&expr->atom, action_data);
expr_log_match(match);
break;
case UNARY_TYPE:
expr_log("!");
match = !eval_expr_log(expr->unary_op.expr, action_data);
break;
default:
expr_log("(");
match = eval_expr_log(expr->expr_op.lhs, action_data);
if ((expr->expr_op.op == TOK_AND && match) ||
(expr->expr_op.op == TOK_OR && !match)) {
expr_log(token_table[expr->expr_op.op].string);
match = eval_expr_log(expr->expr_op.rhs, action_data);
}
expr_log(")");
break;
}
return match;
}
static int eval_expr(struct expr *expr, struct action_data *action_data)
{
int match;
switch (expr->type) {
case ATOM_TYPE:
match = expr->atom.test->fn(&expr->atom, action_data);
break;
case UNARY_TYPE:
match = !eval_expr(expr->unary_op.expr, action_data);
break;
default:
match = eval_expr(expr->expr_op.lhs, action_data);
if ((expr->expr_op.op == TOK_AND && match) ||
(expr->expr_op.op == TOK_OR && !match))
match = eval_expr(expr->expr_op.rhs, action_data);
break;
}
return match;
}
static int eval_expr_top(struct action *action, struct action_data *action_data)
{
if(action->verbose) {
int match, n;
expr_log_cmnd(LOG_ENABLE);
if(action_data->subpath)
expr_log(action_data->subpath);
expr_log("=");
expr_log(action->action->name);
if(action->args) {
expr_log("(");
for (n = 0; n < action->args; n++) {
expr_log(action->argv[n]);
if(n + 1 < action->args)
expr_log(",");
}
expr_log(")");
}
expr_log("@");
match = eval_expr_log(action->expr, action_data);
/*
* Print the evaluated expression log, if the
* result matches the logging specified
*/
if((match && (action->verbose & ACTION_LOG_TRUE)) || (!match
&& (action->verbose & ACTION_LOG_FALSE)))
progressbar_info("%s\n", expr_log(""));
expr_log_cmnd(LOG_DISABLE);
return match;
} else
return eval_expr(action->expr, action_data);
}
/*
* Read action file, passing each line to parse_action() for
* parsing.
*
* One action per line, of the form
* action(arg1,arg2)@expr(arg1,arg2)....
*
* Actions can be split across multiple lines using "\".
*
* Blank lines and comment lines indicated by # are supported.
*/
int parse_action_true(char *s)
{
return parse_action(s, ACTION_LOG_TRUE);
}
int parse_action_false(char *s)
{
return parse_action(s, ACTION_LOG_FALSE);
}
int parse_action_verbose(char *s)
{
return parse_action(s, ACTION_LOG_VERBOSE);
}
int parse_action_nonverbose(char *s)
{
return parse_action(s, ACTION_LOG_NONE);
}
int read_action_file(char *filename, int verbose)
{
switch(verbose) {
case ACTION_LOG_TRUE:
return read_file(filename, "action", parse_action_true);
case ACTION_LOG_FALSE:
return read_file(filename, "action", parse_action_false);
case ACTION_LOG_VERBOSE:
return read_file(filename, "action", parse_action_verbose);
default:
return read_file(filename, "action", parse_action_nonverbose);
}
}
/*
* helper to evaluate whether action/test acts on this file type
*/
static int file_type_match(int st_mode, int type)
{
switch(type) {
case ACTION_DIR:
return S_ISDIR(st_mode);
case ACTION_REG:
return S_ISREG(st_mode);
case ACTION_ALL:
return S_ISREG(st_mode) || S_ISDIR(st_mode) ||
S_ISCHR(st_mode) || S_ISBLK(st_mode) ||
S_ISFIFO(st_mode) || S_ISSOCK(st_mode);
case ACTION_LNK:
return S_ISLNK(st_mode);
case ACTION_ALL_LNK:
default:
return 1;
}
}
/*
* General action evaluation code
*/
int actions()
{
return other_count;
}
void eval_actions(struct dir_info *root, struct dir_ent *dir_ent)
{
int i, match;
struct action_data action_data;
int st_mode = dir_ent->inode->buf.st_mode;
action_data.name = dir_ent->name;
action_data.pathname = strdup(pathname(dir_ent));
action_data.subpath = strdup(subpathname(dir_ent));
action_data.buf = &dir_ent->inode->buf;
action_data.depth = dir_ent->our_dir->depth;
action_data.dir_ent = dir_ent;
action_data.root = root;
for (i = 0; i < other_count; i++) {
struct action *action = &other_spec[i];
if (!file_type_match(st_mode, action->action->file_types))
/* action does not operate on this file type */
continue;
match = eval_expr_top(action, &action_data);
if (match)
action->action->run_action(action, dir_ent);
}
free(action_data.pathname);
free(action_data.subpath);
}
/*
* Fragment specific action code
*/
void *eval_frag_actions(struct dir_info *root, struct dir_ent *dir_ent)
{
int i, match;
struct action_data action_data;
action_data.name = dir_ent->name;
action_data.pathname = strdup(pathname(dir_ent));
action_data.subpath = strdup(subpathname(dir_ent));
action_data.buf = &dir_ent->inode->buf;
action_data.depth = dir_ent->our_dir->depth;
action_data.dir_ent = dir_ent;
action_data.root = root;
for (i = 0; i < fragment_count; i++) {
match = eval_expr_top(&fragment_spec[i], &action_data);
if (match) {
free(action_data.pathname);
free(action_data.subpath);
return &fragment_spec[i].data;
}
}
free(action_data.pathname);
free(action_data.subpath);
return &def_fragment;
}
void *get_frag_action(void *fragment)
{
struct action *spec_list_end = &fragment_spec[fragment_count];
struct action *action;
if (fragment == NULL)
return &def_fragment;
if (fragment_count == 0)
return NULL;
if (fragment == &def_fragment)
action = &fragment_spec[0] - 1;
else
action = fragment - offsetof(struct action, data);
if (++action == spec_list_end)
return NULL;
return &action->data;
}
/*
* Exclude specific action code
*/
int exclude_actions()
{
return exclude_count;
}
int eval_exclude_actions(char *name, char *pathname, char *subpath,
struct stat *buf, int depth, struct dir_ent *dir_ent)
{
int i, match = 0;
struct action_data action_data;
action_data.name = name;
action_data.pathname = pathname;
action_data.subpath = subpath;
action_data.buf = buf;
action_data.depth = depth;
action_data.dir_ent = dir_ent;
for (i = 0; i < exclude_count && !match; i++)
match = eval_expr_top(&exclude_spec[i], &action_data);
return match;
}
/*
* Fragment specific action code
*/
static void frag_action(struct action *action, struct dir_ent *dir_ent)
{
struct inode_info *inode = dir_ent->inode;
inode->no_fragments = 0;
}
static void no_frag_action(struct action *action, struct dir_ent *dir_ent)
{
struct inode_info *inode = dir_ent->inode;
inode->no_fragments = 1;
}
static void always_frag_action(struct action *action, struct dir_ent *dir_ent)
{
struct inode_info *inode = dir_ent->inode;
inode->always_use_fragments = 1;
}
static void no_always_frag_action(struct action *action, struct dir_ent *dir_ent)
{
struct inode_info *inode = dir_ent->inode;
inode->always_use_fragments = 0;
}
/*
* Compression specific action code
*/
static void comp_action(struct action *action, struct dir_ent *dir_ent)
{
struct inode_info *inode = dir_ent->inode;
inode->noD = inode->noF = 0;
}
static void uncomp_action(struct action *action, struct dir_ent *dir_ent)
{
struct inode_info *inode = dir_ent->inode;
inode->noD = inode->noF = 1;
}
/*
* Uid/gid specific action code
*/
static long long parse_uid(char *arg) {
char *b;
long long uid = strtoll(arg, &b, 10);
if (*b == '\0') {
if (uid < 0 || uid >= (1LL << 32)) {
SYNTAX_ERROR("Uid out of range\n");
return -1;
}
} else {
struct passwd *passwd = getpwnam(arg);
if (passwd)
uid = passwd->pw_uid;
else {
SYNTAX_ERROR("Invalid uid or unknown user\n");
return -1;
}
}
return uid;
}
static long long parse_gid(char *arg) {
char *b;
long long gid = strtoll(arg, &b, 10);
if (*b == '\0') {
if (gid < 0 || gid >= (1LL << 32)) {
SYNTAX_ERROR("Gid out of range\n");
return -1;
}
} else {
struct group *group = getgrnam(arg);
if (group)
gid = group->gr_gid;
else {
SYNTAX_ERROR("Invalid gid or unknown group\n");
return -1;
}
}
return gid;
}
static int parse_uid_args(struct action_entry *action, int args, char **argv,
void **data)
{
long long uid;
struct uid_info *uid_info;
uid = parse_uid(argv[0]);
if (uid == -1)
return 0;
uid_info = malloc(sizeof(struct uid_info));
if (uid_info == NULL)
MEM_ERROR();
uid_info->uid = uid;
*data = uid_info;
return 1;
}
static int parse_gid_args(struct action_entry *action, int args, char **argv,
void **data)
{
long long gid;
struct gid_info *gid_info;
gid = parse_gid(argv[0]);
if (gid == -1)
return 0;
gid_info = malloc(sizeof(struct gid_info));
if (gid_info == NULL)
MEM_ERROR();
gid_info->gid = gid;
*data = gid_info;
return 1;
}
static int parse_guid_args(struct action_entry *action, int args, char **argv,
void **data)
{
long long uid, gid;
struct guid_info *guid_info;
uid = parse_uid(argv[0]);
if (uid == -1)
return 0;
gid = parse_gid(argv[1]);
if (gid == -1)
return 0;
guid_info = malloc(sizeof(struct guid_info));
if (guid_info == NULL)
MEM_ERROR();
guid_info->uid = uid;
guid_info->gid = gid;
*data = guid_info;
return 1;
}
static void uid_action(struct action *action, struct dir_ent *dir_ent)
{
struct inode_info *inode = dir_ent->inode;
struct uid_info *uid_info = action->data;
inode->buf.st_uid = uid_info->uid;
}
static void gid_action(struct action *action, struct dir_ent *dir_ent)
{
struct inode_info *inode = dir_ent->inode;
struct gid_info *gid_info = action->data;
inode->buf.st_gid = gid_info->gid;
}
static void guid_action(struct action *action, struct dir_ent *dir_ent)
{
struct inode_info *inode = dir_ent->inode;
struct guid_info *guid_info = action->data;
inode->buf.st_uid = guid_info->uid;
inode->buf.st_gid = guid_info->gid;
}
/*
* Mode specific action code
*/
static int parse_octal_mode_args(int args, char **argv,
void **data)
{
int n, bytes;
unsigned int mode;
struct mode_data *mode_data;
/* octal mode number? */
n = sscanf(argv[0], "%o%n", &mode, &bytes);
if (n == 0)
return -1; /* not an octal number arg */
/* check there's no trailing junk */
if (argv[0][bytes] != '\0') {
SYNTAX_ERROR("Unexpected trailing bytes after octal "
"mode number\n");
return 0; /* bad octal number arg */
}
/* check there's only one argument */
if (args > 1) {
SYNTAX_ERROR("Octal mode number is first argument, "
"expected one argument, got %d\n", args);
return 0; /* bad octal number arg */
}
/* check mode is within range */
if (mode > 07777) {
SYNTAX_ERROR("Octal mode %o is out of range\n", mode);
return 0; /* bad octal number arg */
}
mode_data = malloc(sizeof(struct mode_data));
if (mode_data == NULL)
MEM_ERROR();
mode_data->operation = ACTION_MODE_OCT;
mode_data->mode = mode;
mode_data->next = NULL;
*data = mode_data;
return 1;
}
/*
* Parse symbolic mode of format [ugoa]*[[+-=]PERMS]+
* PERMS = [rwxXst]+ or [ugo]
*/
static int parse_sym_mode_arg(char *arg, struct mode_data **head,
struct mode_data **cur)
{
struct mode_data *mode_data;
int mode;
int mask = 0;
int op;
char X;
if (arg[0] != 'u' && arg[0] != 'g' && arg[0] != 'o' && arg[0] != 'a') {
/* no ownership specifiers, default to a */
mask = 0777;
goto parse_operation;
}
/* parse ownership specifiers */
while(1) {
switch(*arg) {
case 'u':
mask |= 04700;
break;
case 'g':
mask |= 02070;
break;
case 'o':
mask |= 01007;
break;
case 'a':
mask = 07777;
break;
default:
goto parse_operation;
}
arg ++;
}
parse_operation:
/* trap a symbolic mode with just an ownership specification */
if(*arg == '\0') {
SYNTAX_ERROR("Expected one of '+', '-' or '=', got EOF\n");
goto failed;
}
while(*arg != '\0') {
mode = 0;
X = 0;
switch(*arg) {
case '+':
op = ACTION_MODE_ADD;
break;
case '-':
op = ACTION_MODE_REM;
break;
case '=':
op = ACTION_MODE_SET;
break;
default:
SYNTAX_ERROR("Expected one of '+', '-' or '=', got "
"'%c'\n", *arg);
goto failed;
}
arg ++;
/* Parse PERMS */
if (*arg == 'u' || *arg == 'g' || *arg == 'o') {
/* PERMS = [ugo] */
mode = - *arg;
arg ++;
} else {
/* PERMS = [rwxXst]* */
while(1) {
switch(*arg) {
case 'r':
mode |= 0444;
break;
case 'w':
mode |= 0222;
break;
case 'x':
mode |= 0111;
break;
case 's':
mode |= 06000;
break;
case 't':
mode |= 01000;
break;
case 'X':
X = 1;
break;
case '+':
case '-':
case '=':
case '\0':
mode &= mask;
goto perms_parsed;
default:
SYNTAX_ERROR("Unrecognised permission "
"'%c'\n", *arg);
goto failed;
}
arg ++;
}
}
perms_parsed:
mode_data = malloc(sizeof(*mode_data));
if (mode_data == NULL)
MEM_ERROR();
mode_data->operation = op;
mode_data->mode = mode;
mode_data->mask = mask;
mode_data->X = X;
mode_data->next = NULL;
if (*cur) {
(*cur)->next = mode_data;
*cur = mode_data;
} else
*head = *cur = mode_data;
}
return 1;
failed:
return 0;
}
static int parse_sym_mode_args(struct action_entry *action, int args,
char **argv, void **data)
{
int i, res = 1;
struct mode_data *head = NULL, *cur = NULL;
for (i = 0; i < args && res; i++)
res = parse_sym_mode_arg(argv[i], &head, &cur);
*data = head;
return res;
}
static int parse_mode_args(struct action_entry *action, int args,
char **argv, void **data)
{
int res;
if (args == 0) {
SYNTAX_ERROR("Mode action expects one or more arguments\n");
return 0;
}
res = parse_octal_mode_args(args, argv, data);
if(res >= 0)
/* Got an octal mode argument */
return res;
else /* not an octal mode argument */
return parse_sym_mode_args(action, args, argv, data);
}
static int mode_execute(struct mode_data *mode_data, int st_mode)
{
int mode = 0;
for (;mode_data; mode_data = mode_data->next) {
if (mode_data->mode < 0) {
/* 'u', 'g' or 'o' */
switch(-mode_data->mode) {
case 'u':
mode = (st_mode >> 6) & 07;
break;
case 'g':
mode = (st_mode >> 3) & 07;
break;
case 'o':
mode = st_mode & 07;
break;
}
mode = ((mode << 6) | (mode << 3) | mode) &
mode_data->mask;
} else if (mode_data->X &&
((st_mode & S_IFMT) == S_IFDIR ||
(st_mode & 0111)))
/* X permission, only takes effect if inode is a
* directory or x is set for some owner */
mode = mode_data->mode | (0111 & mode_data->mask);
else
mode = mode_data->mode;
switch(mode_data->operation) {
case ACTION_MODE_OCT:
st_mode = (st_mode & S_IFMT) | mode;
break;
case ACTION_MODE_SET:
st_mode = (st_mode & ~mode_data->mask) | mode;
break;
case ACTION_MODE_ADD:
st_mode |= mode;
break;
case ACTION_MODE_REM:
st_mode &= ~mode;
}
}
return st_mode;
}
static void mode_action(struct action *action, struct dir_ent *dir_ent)
{
dir_ent->inode->buf.st_mode = mode_execute(action->data,
dir_ent->inode->buf.st_mode);
}
/*
* Empty specific action code
*/
int empty_actions()
{
return empty_count;
}
static int parse_empty_args(struct action_entry *action, int args,
char **argv, void **data)
{
struct empty_data *empty_data;
int val;
if (args >= 2) {
SYNTAX_ERROR("Empty action expects zero or one argument\n");
return 0;
}
if (args == 0 || strcmp(argv[0], "all") == 0)
val = EMPTY_ALL;
else if (strcmp(argv[0], "source") == 0)
val = EMPTY_SOURCE;
else if (strcmp(argv[0], "excluded") == 0)
val = EMPTY_EXCLUDED;
else {
SYNTAX_ERROR("Empty action expects zero arguments, or one"
"argument containing \"all\", \"source\", or \"excluded\""
"\n");
return 0;
}
empty_data = malloc(sizeof(*empty_data));
if (empty_data == NULL)
MEM_ERROR();
empty_data->val = val;
*data = empty_data;
return 1;
}
int eval_empty_actions(struct dir_info *root, struct dir_ent *dir_ent)
{
int i, match = 0;
struct action_data action_data;
struct empty_data *data;
struct dir_info *dir = dir_ent->dir;
/*
* Empty action only works on empty directories
*/
if (dir->count != 0)
return 0;
action_data.name = dir_ent->name;
action_data.pathname = strdup(pathname(dir_ent));
action_data.subpath = strdup(subpathname(dir_ent));
action_data.buf = &dir_ent->inode->buf;
action_data.depth = dir_ent->our_dir->depth;
action_data.dir_ent = dir_ent;
action_data.root = root;
for (i = 0; i < empty_count && !match; i++) {
data = empty_spec[i].data;
/*
* determine the cause of the empty directory and evaluate
* the empty action specified. Three empty actions:
* - EMPTY_SOURCE: empty action triggers only if the directory
* was originally empty, i.e directories that are empty
* only due to excluding are ignored.
* - EMPTY_EXCLUDED: empty action triggers only if the directory
* is empty because of excluding, i.e. directories that
* were originally empty are ignored.
* - EMPTY_ALL (the default): empty action triggers if the
* directory is empty, irrespective of the reason, i.e.
* the directory could have been originally empty or could
* be empty due to excluding.
*/
if ((data->val == EMPTY_EXCLUDED && !dir->excluded) ||
(data->val == EMPTY_SOURCE && dir->excluded))
continue;
match = eval_expr_top(&empty_spec[i], &action_data);
}
free(action_data.pathname);
free(action_data.subpath);
return match;
}
/*
* Move specific action code
*/
static struct move_ent *move_list = NULL;
int move_actions()
{
return move_count;
}
static char *move_pathname(struct move_ent *move)
{
struct dir_info *dest;
char *name, *pathname;
int res;
dest = (move->ops & ACTION_MOVE_MOVE) ?
move->dest : move->dir_ent->our_dir;
name = (move->ops & ACTION_MOVE_RENAME) ?
move->name : move->dir_ent->name;
if(dest->subpath[0] != '\0')
res = asprintf(&pathname, "%s/%s", dest->subpath, name);
else
res = asprintf(&pathname, "/%s", name);
if(res == -1)
BAD_ERROR("asprintf failed in move_pathname\n");
return pathname;
}
static char *get_comp(char **pathname)
{
char *path = *pathname, *start;
while(*path == '/')
path ++;
if(*path == '\0')
return NULL;
start = path;
while(*path != '/' && *path != '\0')
path ++;
*pathname = path;
return strndup(start, path - start);
}
static struct dir_ent *lookup_comp(char *comp, struct dir_info *dest)
{
struct dir_ent *dir_ent;
for(dir_ent = dest->list; dir_ent; dir_ent = dir_ent->next)
if(strcmp(comp, dir_ent->name) == 0)
break;
return dir_ent;
}
void eval_move(struct action_data *action_data, struct move_ent *move,
struct dir_info *root, struct dir_ent *dir_ent, char *pathname)
{
struct dir_info *dest, *source = dir_ent->our_dir;
struct dir_ent *comp_ent;
char *comp, *path = pathname;
/*
* Walk pathname to get the destination directory
*
* Like the mv command, if the last component exists and it
* is a directory, then move the file into that directory,
* otherwise, move the file into parent directory of the last
* component and rename to the last component.
*/
if (pathname[0] == '/')
/* absolute pathname, walk from root directory */
dest = root;
else
/* relative pathname, walk from current directory */
dest = source;
for(comp = get_comp(&pathname); comp; free(comp),
comp = get_comp(&pathname)) {
if (strcmp(comp, ".") == 0)
continue;
if (strcmp(comp, "..") == 0) {
/* if we're in the root directory then ignore */
if(dest->depth > 1)
dest = dest->dir_ent->our_dir;
continue;
}
/*
* Look up comp in current directory, if it exists and it is a
* directory continue walking the pathname, otherwise exit,
* we've walked as far as we can go, normally this is because
* we've arrived at the leaf component which we are going to
* rename source to
*/
comp_ent = lookup_comp(comp, dest);
if (comp_ent == NULL || (comp_ent->inode->buf.st_mode & S_IFMT)
!= S_IFDIR)
break;
dest = comp_ent->dir;
}
if(comp) {
/* Leaf component? If so we're renaming to this */
char *remainder = get_comp(&pathname);
free(remainder);
if(remainder) {
/*
* trying to move source to a subdirectory of
* comp, but comp either doesn't exist, or it isn't
* a directory, which is impossible
*/
if (comp_ent == NULL)
ERROR("Move action: cannot move %s to %s, no "
"such directory %s\n",
action_data->subpath, path, comp);
else
ERROR("Move action: cannot move %s to %s, %s "
"is not a directory\n",
action_data->subpath, path, comp);
free(comp);
return;
}
/*
* Multiple move actions triggering on one file can be merged
* if one is a RENAME and the other is a MOVE. Multiple RENAMEs
* can only merge if they're doing the same thing
*/
if(move->ops & ACTION_MOVE_RENAME) {
if(strcmp(comp, move->name) != 0) {
char *conf_path = move_pathname(move);
ERROR("Move action: Cannot move %s to %s, "
"conflicting move, already moving "
"to %s via another move action!\n",
action_data->subpath, path, conf_path);
free(conf_path);
free(comp);
return;
}
free(comp);
} else {
move->name = comp;
move->ops |= ACTION_MOVE_RENAME;
}
}
if(dest != source) {
/*
* Multiple move actions triggering on one file can be merged
* if one is a RENAME and the other is a MOVE. Multiple MOVEs
* can only merge if they're doing the same thing
*/
if(move->ops & ACTION_MOVE_MOVE) {
if(dest != move->dest) {
char *conf_path = move_pathname(move);
ERROR("Move action: Cannot move %s to %s, "
"conflicting move, already moving "
"to %s via another move action!\n",
action_data->subpath, path, conf_path);
free(conf_path);
return;
}
} else {
move->dest = dest;
move->ops |= ACTION_MOVE_MOVE;
}
}
}
static int subdirectory(struct dir_info *source, struct dir_info *dest)
{
if(source == NULL)
return 0;
return strlen(source->subpath) <= strlen(dest->subpath) &&
(dest->subpath[strlen(source->subpath)] == '/' ||
dest->subpath[strlen(source->subpath)] == '\0') &&
strncmp(source->subpath, dest->subpath,
strlen(source->subpath)) == 0;
}
void eval_move_actions(struct dir_info *root, struct dir_ent *dir_ent)
{
int i;
struct action_data action_data;
struct move_ent *move = NULL;
action_data.name = dir_ent->name;
action_data.pathname = strdup(pathname(dir_ent));
action_data.subpath = strdup(subpathname(dir_ent));
action_data.buf = &dir_ent->inode->buf;
action_data.depth = dir_ent->our_dir->depth;
action_data.dir_ent = dir_ent;
action_data.root = root;
/*
* Evaluate each move action against the current file. For any
* move actions that match don't actually perform the move now, but,
* store it, and execute all the stored move actions together once the
* directory scan is complete. This is done to ensure each separate
* move action does not nondeterministically interfere with other move
* actions. Each move action is considered to act independently, and
* each move action sees the directory tree in the same state.
*/
for (i = 0; i < move_count; i++) {
struct action *action = &move_spec[i];
int match = eval_expr_top(action, &action_data);
if(match) {
if(move == NULL) {
move = malloc(sizeof(*move));
if(move == NULL)
MEM_ERROR();
move->ops = 0;
move->dir_ent = dir_ent;
}
eval_move(&action_data, move, root, dir_ent,
action->argv[0]);
}
}
if(move) {
struct dir_ent *comp_ent;
struct dir_info *dest;
char *name;
/*
* Move contains the result of all triggered move actions.
* Check the destination doesn't already exist
*/
if(move->ops == 0) {
free(move);
goto finish;
}
dest = (move->ops & ACTION_MOVE_MOVE) ?
move->dest : dir_ent->our_dir;
name = (move->ops & ACTION_MOVE_RENAME) ?
move->name : dir_ent->name;
comp_ent = lookup_comp(name, dest);
if(comp_ent) {
char *conf_path = move_pathname(move);
ERROR("Move action: Cannot move %s to %s, "
"destination already exists\n",
action_data.subpath, conf_path);
free(conf_path);
free(move);
goto finish;
}
/*
* If we're moving a directory, check we're not moving it to a
* subdirectory of itself
*/
if(subdirectory(dir_ent->dir, dest)) {
char *conf_path = move_pathname(move);
ERROR("Move action: Cannot move %s to %s, this is a "
"subdirectory of itself\n",
action_data.subpath, conf_path);
free(conf_path);
free(move);
goto finish;
}
move->next = move_list;
move_list = move;
}
finish:
free(action_data.pathname);
free(action_data.subpath);
}
static void move_dir(struct dir_ent *dir_ent)
{
struct dir_info *dir = dir_ent->dir;
struct dir_ent *comp_ent;
/* update our directory's subpath name */
free(dir->subpath);
dir->subpath = strdup(subpathname(dir_ent));
/* recursively update the subpaths of any sub-directories */
for(comp_ent = dir->list; comp_ent; comp_ent = comp_ent->next)
if(comp_ent->dir)
move_dir(comp_ent);
}
static void move_file(struct move_ent *move_ent)
{
struct dir_ent *dir_ent = move_ent->dir_ent;
if(move_ent->ops & ACTION_MOVE_MOVE) {
struct dir_ent *comp_ent, *prev = NULL;
struct dir_info *source = dir_ent->our_dir,
*dest = move_ent->dest;
char *filename = pathname(dir_ent);
/*
* If we're moving a directory, check we're not moving it to a
* subdirectory of itself
*/
if(subdirectory(dir_ent->dir, dest)) {
char *conf_path = move_pathname(move_ent);
ERROR("Move action: Cannot move %s to %s, this is a "
"subdirectory of itself\n",
subpathname(dir_ent), conf_path);
free(conf_path);
return;
}
/* Remove the file from source directory */
for(comp_ent = source->list; comp_ent != dir_ent;
prev = comp_ent, comp_ent = comp_ent->next);
if(prev)
prev->next = comp_ent->next;
else
source->list = comp_ent->next;
source->count --;
if((comp_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
source->directory_count --;
/* Add the file to dest directory */
comp_ent->next = dest->list;
dest->list = comp_ent;
comp_ent->our_dir = dest;
dest->count ++;
if((comp_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR)
dest->directory_count ++;
/*
* We've moved the file, and so we can't now use the
* parent directory's pathname to calculate the pathname
*/
if(dir_ent->nonstandard_pathname == NULL) {
dir_ent->nonstandard_pathname = strdup(filename);
if(dir_ent->source_name) {
free(dir_ent->source_name);
dir_ent->source_name = NULL;
}
}
}
if(move_ent->ops & ACTION_MOVE_RENAME) {
/*
* If we're using name in conjunction with the parent
* directory's pathname to calculate the pathname, we need
* to use source_name to override. Otherwise it's already being
* over-ridden
*/
if(dir_ent->nonstandard_pathname == NULL &&
dir_ent->source_name == NULL)
dir_ent->source_name = dir_ent->name;
else
free(dir_ent->name);
dir_ent->name = move_ent->name;
}
if(dir_ent->dir)
/*
* dir_ent is a directory, and we have to recursively fix-up
* its subpath, and the subpaths of all of its sub-directories
*/
move_dir(dir_ent);
}
void do_move_actions()
{
while(move_list) {
struct move_ent *temp = move_list;
struct dir_info *dest = (move_list->ops & ACTION_MOVE_MOVE) ?
move_list->dest : move_list->dir_ent->our_dir;
char *name = (move_list->ops & ACTION_MOVE_RENAME) ?
move_list->name : move_list->dir_ent->name;
struct dir_ent *comp_ent = lookup_comp(name, dest);
if(comp_ent) {
char *conf_path = move_pathname(move_list);
ERROR("Move action: Cannot move %s to %s, "
"destination already exists\n",
subpathname(move_list->dir_ent), conf_path);
free(conf_path);
} else
move_file(move_list);
move_list = move_list->next;
free(temp);
}
}
/*
* Prune specific action code
*/
int prune_actions()
{
return prune_count;
}
int eval_prune_actions(struct dir_info *root, struct dir_ent *dir_ent)
{
int i, match = 0;
struct action_data action_data;
action_data.name = dir_ent->name;
action_data.pathname = strdup(pathname(dir_ent));
action_data.subpath = strdup(subpathname(dir_ent));
action_data.buf = &dir_ent->inode->buf;
action_data.depth = dir_ent->our_dir->depth;
action_data.dir_ent = dir_ent;
action_data.root = root;
for (i = 0; i < prune_count && !match; i++)
match = eval_expr_top(&prune_spec[i], &action_data);
free(action_data.pathname);
free(action_data.subpath);
return match;
}
/*
* Noop specific action code
*/
static void noop_action(struct action *action, struct dir_ent *dir_ent)
{
}
/*
* General test evaluation code
*/
/*
* A number can be of the form [range]number[size]
* [range] is either:
* '<' or '-', match on less than number
* '>' or '+', match on greater than number
* '' (nothing), match on exactly number
* [size] is either:
* '' (nothing), number
* 'k' or 'K', number * 2^10
* 'm' or 'M', number * 2^20
* 'g' or 'G', number * 2^30
*/
static int parse_number(char *start, long long *size, int *range, char **error)
{
char *end;
long long number;
if (*start == '>' || *start == '+') {
*range = NUM_GREATER;
start ++;
} else if (*start == '<' || *start == '-') {
*range = NUM_LESS;
start ++;
} else
*range = NUM_EQ;
errno = 0; /* To enable failure after call to be determined */
number = strtoll(start, &end, 10);
if((errno == ERANGE && (number == LLONG_MAX || number == LLONG_MIN))
|| (errno != 0 && number == 0)) {
/* long long underflow or overflow in conversion, or other
* conversion error.
* Note: we don't check for LLONG_MIN and LLONG_MAX only
* because strtoll can validly return that if the
* user used these values
*/
*error = "Long long underflow, overflow or other conversion "
"error";
return 0;
}
if (end == start) {
/* Couldn't read any number */
*error = "Number expected";
return 0;
}
switch (end[0]) {
case 'g':
case 'G':
number *= 1024;
case 'm':
case 'M':
number *= 1024;
case 'k':
case 'K':
number *= 1024;
if (end[1] != '\0') {
*error = "Trailing junk after size specifier";
return 0;
}
break;
case '\0':
break;
default:
*error = "Trailing junk after number";
return 0;
}
*size = number;
return 1;
}
static int parse_number_arg(struct test_entry *test, struct atom *atom)
{
struct test_number_arg *number;
long long size;
int range;
char *error;
int res = parse_number(atom->argv[0], &size, &range, &error);
if (res == 0) {
TEST_SYNTAX_ERROR(test, 0, "%s\n", error);
return 0;
}
number = malloc(sizeof(*number));
if (number == NULL)
MEM_ERROR();
number->range = range;
number->size = size;
atom->data = number;
return 1;
}
static int parse_range_args(struct test_entry *test, struct atom *atom)
{
struct test_range_args *range;
long long start, end;
int type;
int res;
char *error;
res = parse_number(atom->argv[0], &start, &type, &error);
if (res == 0) {
TEST_SYNTAX_ERROR(test, 0, "%s\n", error);
return 0;
}
if (type != NUM_EQ) {
TEST_SYNTAX_ERROR(test, 0, "Range specifier (<, >, -, +) not "
"expected\n");
return 0;
}
res = parse_number(atom->argv[1], &end, &type, &error);
if (res == 0) {
TEST_SYNTAX_ERROR(test, 1, "%s\n", error);
return 0;
}
if (type != NUM_EQ) {
TEST_SYNTAX_ERROR(test, 1, "Range specifier (<, >, -, +) not "
"expected\n");
return 0;
}
range = malloc(sizeof(*range));
if (range == NULL)
MEM_ERROR();
range->start = start;
range->end = end;
atom->data = range;
return 1;
}
/*
* Generic test code macro
*/
#define TEST_FN(NAME, MATCH, CODE) \
static int NAME##_fn(struct atom *atom, struct action_data *action_data) \
{ \
/* test operates on MATCH file types only */ \
if (!file_type_match(action_data->buf->st_mode, MATCH)) \
return 0; \
\
CODE \
}
/*
* Generic test code macro testing VAR for size (eq, less than, greater than)
*/
#define TEST_VAR_FN(NAME, MATCH, VAR) TEST_FN(NAME, MATCH, \
{ \
int match = 0; \
struct test_number_arg *number = atom->data; \
\
switch (number->range) { \
case NUM_EQ: \
match = VAR == number->size; \
break; \
case NUM_LESS: \
match = VAR < number->size; \
break; \
case NUM_GREATER: \
match = VAR > number->size; \
break; \
} \
\
return match; \
})
/*
* Generic test code macro testing VAR for range [x, y] (value between x and y
* inclusive).
*/
#define TEST_VAR_RANGE_FN(NAME, MATCH, VAR) TEST_FN(NAME##_range, MATCH, \
{ \
struct test_range_args *range = atom->data; \
\
return range->start <= VAR && VAR <= range->end; \
})
/*
* Name, Pathname and Subpathname test specific code
*/
/*
* Add a leading "/" if subpathname and pathname lacks it
*/
static int check_pathname(struct test_entry *test, struct atom *atom)
{
int res;
char *name;
if(atom->argv[0][0] != '/') {
res = asprintf(&name, "/%s", atom->argv[0]);
if(res == -1)
BAD_ERROR("asprintf failed in check_pathname\n");
free(atom->argv[0]);
atom->argv[0] = name;
}
return 1;
}
TEST_FN(name, ACTION_ALL_LNK, \
return fnmatch(atom->argv[0], action_data->name,
FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) == 0;)
TEST_FN(pathname, ACTION_ALL_LNK, \
return fnmatch(atom->argv[0], action_data->subpath,
FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) == 0;)
static int count_components(char *path)
{
int count;
for (count = 0; *path != '\0'; count ++) {
while (*path == '/')
path ++;
while (*path != '\0' && *path != '/')
path ++;
}
return count;
}
static char *get_start(char *s, int n)
{
int count;
char *path = s;
for (count = 0; *path != '\0' && count < n; count ++) {
while (*path == '/')
path ++;
while (*path != '\0' && *path != '/')
path ++;
}
if (count == n)
*path = '\0';
return s;
}
static int subpathname_fn(struct atom *atom, struct action_data *action_data)
{
return fnmatch(atom->argv[0], get_start(strdupa(action_data->subpath),
count_components(atom->argv[0])),
FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) == 0;
}
/*
* Inode attribute test operations using generic
* TEST_VAR_FN(test name, file scope, attribute name) macro.
* This is for tests that do not need to be specially handled in any way.
* They just take a variable and compare it against a number.
*/
TEST_VAR_FN(filesize, ACTION_REG, action_data->buf->st_size)
TEST_VAR_FN(dirsize, ACTION_DIR, action_data->buf->st_size)
TEST_VAR_FN(size, ACTION_ALL_LNK, action_data->buf->st_size)
TEST_VAR_FN(inode, ACTION_ALL_LNK, action_data->buf->st_ino)
TEST_VAR_FN(nlink, ACTION_ALL_LNK, action_data->buf->st_nlink)
TEST_VAR_FN(fileblocks, ACTION_REG, action_data->buf->st_blocks)
TEST_VAR_FN(dirblocks, ACTION_DIR, action_data->buf->st_blocks)
TEST_VAR_FN(blocks, ACTION_ALL_LNK, action_data->buf->st_blocks)
TEST_VAR_FN(dircount, ACTION_DIR, action_data->dir_ent->dir->count)
TEST_VAR_FN(depth, ACTION_ALL_LNK, action_data->depth)
TEST_VAR_RANGE_FN(filesize, ACTION_REG, action_data->buf->st_size)
TEST_VAR_RANGE_FN(dirsize, ACTION_DIR, action_data->buf->st_size)
TEST_VAR_RANGE_FN(size, ACTION_ALL_LNK, action_data->buf->st_size)
TEST_VAR_RANGE_FN(inode, ACTION_ALL_LNK, action_data->buf->st_ino)
TEST_VAR_RANGE_FN(nlink, ACTION_ALL_LNK, action_data->buf->st_nlink)
TEST_VAR_RANGE_FN(fileblocks, ACTION_REG, action_data->buf->st_blocks)
TEST_VAR_RANGE_FN(dirblocks, ACTION_DIR, action_data->buf->st_blocks)
TEST_VAR_RANGE_FN(blocks, ACTION_ALL_LNK, action_data->buf->st_blocks)
TEST_VAR_RANGE_FN(gid, ACTION_ALL_LNK, action_data->buf->st_gid)
TEST_VAR_RANGE_FN(uid, ACTION_ALL_LNK, action_data->buf->st_uid)
TEST_VAR_RANGE_FN(depth, ACTION_ALL_LNK, action_data->depth)
TEST_VAR_RANGE_FN(dircount, ACTION_DIR, action_data->dir_ent->dir->count)
/*
* uid specific test code
*/
TEST_VAR_FN(uid, ACTION_ALL_LNK, action_data->buf->st_uid)
static int parse_uid_arg(struct test_entry *test, struct atom *atom)
{
struct test_number_arg *number;
long long size;
int range;
char *error;
if(parse_number(atom->argv[0], &size, &range, &error)) {
/* managed to fully parse argument as a number */
if(size < 0 || size > (((long long) 1 << 32) - 1)) {
TEST_SYNTAX_ERROR(test, 1, "Numeric uid out of "
"range\n");
return 0;
}
} else {
/* couldn't parse (fully) as a number, is it a user name? */
struct passwd *uid = getpwnam(atom->argv[0]);
if(uid) {
size = uid->pw_uid;
range = NUM_EQ;
} else {
TEST_SYNTAX_ERROR(test, 1, "Invalid uid or unknown "
"user\n");
return 0;
}
}
number = malloc(sizeof(*number));
if(number == NULL)
MEM_ERROR();
number->range = range;
number->size= size;
atom->data = number;
return 1;
}
/*
* gid specific test code
*/
TEST_VAR_FN(gid, ACTION_ALL_LNK, action_data->buf->st_gid)
static int parse_gid_arg(struct test_entry *test, struct atom *atom)
{
struct test_number_arg *number;
long long size;
int range;
char *error;
if(parse_number(atom->argv[0], &size, &range, &error)) {
/* managed to fully parse argument as a number */
if(size < 0 || size > (((long long) 1 << 32) - 1)) {
TEST_SYNTAX_ERROR(test, 1, "Numeric gid out of "
"range\n");
return 0;
}
} else {
/* couldn't parse (fully) as a number, is it a group name? */
struct group *gid = getgrnam(atom->argv[0]);
if(gid) {
size = gid->gr_gid;
range = NUM_EQ;
} else {
TEST_SYNTAX_ERROR(test, 1, "Invalid gid or unknown "
"group\n");
return 0;
}
}
number = malloc(sizeof(*number));
if(number == NULL)
MEM_ERROR();
number->range = range;
number->size= size;
atom->data = number;
return 1;
}
/*
* Type test specific code
*/
struct type_entry type_table[] = {
{ S_IFSOCK, 's' },
{ S_IFLNK, 'l' },
{ S_IFREG, 'f' },
{ S_IFBLK, 'b' },
{ S_IFDIR, 'd' },
{ S_IFCHR, 'c' },
{ S_IFIFO, 'p' },
{ 0, 0 },
};
static int parse_type_arg(struct test_entry *test, struct atom *atom)
{
int i;
if (strlen(atom->argv[0]) != 1)
goto failed;
for(i = 0; type_table[i].type != 0; i++)
if (type_table[i].type == atom->argv[0][0])
break;
atom->data = &type_table[i];
if(type_table[i].type != 0)
return 1;
failed:
TEST_SYNTAX_ERROR(test, 0, "Unexpected file type, expected 'f', 'd', "
"'c', 'b', 'l', 's' or 'p'\n");
return 0;
}
static int type_fn(struct atom *atom, struct action_data *action_data)
{
struct type_entry *type = atom->data;
return (action_data->buf->st_mode & S_IFMT) == type->value;
}
/*
* True test specific code
*/
static int true_fn(struct atom *atom, struct action_data *action_data)
{
return 1;
}
/*
* False test specific code
*/
static int false_fn(struct atom *atom, struct action_data *action_data)
{
return 0;
}
/*
* File test specific code
*/
static int parse_file_arg(struct test_entry *test, struct atom *atom)
{
int res;
regex_t *preg = malloc(sizeof(regex_t));
if (preg == NULL)
MEM_ERROR();
res = regcomp(preg, atom->argv[0], REG_EXTENDED);
if (res) {
char str[1024]; /* overflow safe */
regerror(res, preg, str, 1024);
free(preg);
TEST_SYNTAX_ERROR(test, 0, "invalid regex \"%s\" because "
"\"%s\"\n", atom->argv[0], str);
return 0;
}
atom->data = preg;
return 1;
}
static int file_fn(struct atom *atom, struct action_data *action_data)
{
int child, res, size = 0, status;
int pipefd[2];
char *buffer = NULL;
regex_t *preg = atom->data;
res = pipe(pipefd);
if (res == -1)
BAD_ERROR("file_fn pipe failed\n");
child = fork();
if (child == -1)
BAD_ERROR("file_fn fork_failed\n");
if (child == 0) {
/*
* Child process
* Connect stdout to pipefd[1] and execute file command
*/
close(STDOUT_FILENO);
res = dup(pipefd[1]);
if (res == -1)
exit(EXIT_FAILURE);
execlp("file", "file", "-b", action_data->pathname,
(char *) NULL);
exit(EXIT_FAILURE);
}
/*
* Parent process. Read stdout from file command
*/
close(pipefd[1]);
do {
buffer = realloc(buffer, size + 512);
if (buffer == NULL)
MEM_ERROR();
res = read_bytes(pipefd[0], buffer + size, 512);
if (res == -1)
BAD_ERROR("file_fn pipe read error\n");
size += 512;
} while (res == 512);
size = size + res - 512;
buffer[size] = '\0';
res = waitpid(child, &status, 0);
if (res == -1)
BAD_ERROR("file_fn waitpid failed\n");
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
BAD_ERROR("file_fn file returned error\n");
close(pipefd[0]);
res = regexec(preg, buffer, (size_t) 0, NULL, 0);
free(buffer);
return res == 0;
}
/*
* Exec test specific code
*/
static int exec_fn(struct atom *atom, struct action_data *action_data)
{
int child, i, res, status;
child = fork();
if (child == -1)
BAD_ERROR("exec_fn fork_failed\n");
if (child == 0) {
/*
* Child process
* redirect stdin, stdout & stderr to /dev/null and
* execute atom->argv[0]
*/
int fd = open("/dev/null", O_RDWR);
if(fd == -1)
exit(EXIT_FAILURE);
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
for(i = 0; i < 3; i++) {
res = dup(fd);
if (res == -1)
exit(EXIT_FAILURE);
}
close(fd);
/*
* Create environment variables
* NAME: name of file
* PATHNAME: pathname of file relative to squashfs root
* SOURCE_PATHNAME: the pathname of the file in the source
* directory
*/
res = setenv("NAME", action_data->name, 1);
if(res == -1)
exit(EXIT_FAILURE);
res = setenv("PATHNAME", action_data->subpath, 1);
if(res == -1)
exit(EXIT_FAILURE);
res = setenv("SOURCE_PATHNAME", action_data->pathname, 1);
if(res == -1)
exit(EXIT_FAILURE);
execl("/bin/sh", "sh", "-c", atom->argv[0], (char *) NULL);
exit(EXIT_FAILURE);
}
/*
* Parent process.
*/
res = waitpid(child, &status, 0);
if (res == -1)
BAD_ERROR("exec_fn waitpid failed\n");
return WIFEXITED(status) ? WEXITSTATUS(status) == 0 : 0;
}
/*
* Symbolic link specific test code
*/
/*
* Walk the supplied pathname and return the directory entry corresponding
* to the pathname. If any symlinks are encountered whilst walking the
* pathname, then recursively walk these, to obtain the fully
* dereferenced canonicalised directory entry.
*
* If follow_path fails to walk a pathname either because a component
* doesn't exist, it is a non directory component when a directory
* component is expected, a symlink with an absolute path is encountered,
* or a symlink is encountered which cannot be recursively walked due to
* the above failures, then return NULL.
*/
static struct dir_ent *follow_path(struct dir_info *dir, char *pathname)
{
char *comp, *path = pathname;
struct dir_ent *dir_ent = NULL;
/* We cannot follow absolute paths */
if(pathname[0] == '/')
return NULL;
for(comp = get_comp(&path); comp; free(comp), comp = get_comp(&path)) {
if(strcmp(comp, ".") == 0)
continue;
if(strcmp(comp, "..") == 0) {
/* Move to parent if we're not in the root directory */
if(dir->depth > 1) {
dir = dir->dir_ent->our_dir;
dir_ent = NULL; /* lazily eval at loop exit */
continue;
} else
/* Failed to walk pathname */
return NULL;
}
/* Lookup comp in current directory */
dir_ent = lookup_comp(comp, dir);
if(dir_ent == NULL)
/* Doesn't exist, failed to walk pathname */
return NULL;
if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFLNK) {
/* Symbolic link, try to walk it */
dir_ent = follow_path(dir, dir_ent->inode->symlink);
if(dir_ent == NULL)
/* Failed to follow symlink */
return NULL;
}
if((dir_ent->inode->buf.st_mode & S_IFMT) != S_IFDIR)
/* Cannot walk further */
break;
dir = dir_ent->dir;
}
/* We will have exited the loop either because we've processed
* all the components, which means we've successfully walked the
* pathname, or because we've hit a non-directory, in which case
* it's success if this is the leaf component */
if(comp) {
free(comp);
comp = get_comp(&path);
free(comp);
if(comp != NULL)
/* Not a leaf component */
return NULL;
} else {
/* Fully walked pathname, dir_ent contains correct value unless
* we've walked to the parent ("..") in which case we need
* to resolve it here */
if(!dir_ent)
dir_ent = dir->dir_ent;
}
return dir_ent;
}
static int exists_fn(struct atom *atom, struct action_data *action_data)
{
/*
* Test if a symlink exists within the output filesystem, that is,
* the symlink has a relative path, and the relative path refers
* to an entry within the output filesystem.
*
* This test function evaluates the path for symlinks - that is it
* follows any symlinks in the path (and any symlinks that it contains
* etc.), to discover the fully dereferenced canonicalised relative
* path.
*
* If any symlinks within the path do not exist or are absolute
* then the symlink is considered to not exist, as it cannot be
* fully dereferenced.
*
* exists operates on symlinks only, other files by definition
* exist
*/
if (!file_type_match(action_data->buf->st_mode, ACTION_LNK))
return 1;
/* dereference the symlink, and return TRUE if it exists */
return follow_path(action_data->dir_ent->our_dir,
action_data->dir_ent->inode->symlink) ? 1 : 0;
}
static int absolute_fn(struct atom *atom, struct action_data *action_data)
{
/*
* Test if a symlink has an absolute path, which by definition
* means the symbolic link may be broken (even if the absolute path
* does point into the filesystem being squashed, because the resultant
* filesystem can be mounted/unsquashed anywhere, it is unlikely the
* absolute path will still point to the right place). If you know that
* an absolute symlink will point to the right place then you don't need
* to use this function, and/or these symlinks can be excluded by
* use of other test operators.
*
* absolute operates on symlinks only, other files by definition
* don't have problems
*/
if (!file_type_match(action_data->buf->st_mode, ACTION_LNK))
return 0;
return action_data->dir_ent->inode->symlink[0] == '/';
}
static int parse_expr_argX(struct test_entry *test, struct atom *atom,
int argno)
{
/* Call parse_expr to parse argument, which should be an expression */
/* save the current parser state */
char *save_cur_ptr = cur_ptr;
char *save_source = source;
cur_ptr = source = atom->argv[argno];
atom->data = parse_expr(0);
cur_ptr = save_cur_ptr;
source = save_source;
if(atom->data == NULL) {
/* parse_expr(0) will have reported the exact syntax error,
* but, because we recursively evaluated the expression, it
* will have been reported without the context of the stat
* test(). So here additionally report our failure to parse
* the expression in the stat() test to give context */
TEST_SYNTAX_ERROR(test, 0, "Failed to parse expression\n");
return 0;
}
return 1;
}
static int parse_expr_arg0(struct test_entry *test, struct atom *atom)
{
return parse_expr_argX(test, atom, 0);
}
static int parse_expr_arg1(struct test_entry *test, struct atom *atom)
{
return parse_expr_argX(test, atom, 1);
}
static int stat_fn(struct atom *atom, struct action_data *action_data)
{
struct stat buf;
struct action_data eval_action;
int match, res;
/* evaluate the expression using the context of the inode
* pointed to by the symlink. This allows the inode attributes
* of the file pointed to by the symlink to be evaluated, rather
* than the symlink itself.
*
* Note, stat() deliberately does not evaluate the pathname, name or
* depth of the symlink, these are left with the symlink values.
* This allows stat() to be used on any symlink, rather than
* just symlinks which are contained (if the symlink is *not*
* contained then pathname, name and depth are meaningless as they
* are relative to the filesystem being squashed). */
/* if this isn't a symlink then stat will just return the current
* information, i.e. stat(expr) == expr. This is harmless and
* is better than returning TRUE or FALSE in a non symlink case */
res = stat(action_data->pathname, &buf);
if(res == -1) {
if(expr_log_cmnd(LOG_ENABLED)) {
expr_log(atom->test->name);
expr_log("(");
expr_log_match(0);
expr_log(")");
}
return 0;
}
/* fill in the inode values of the file pointed to by the
* symlink, but, leave everything else the same */
memcpy(&eval_action, action_data, sizeof(struct action_data));
eval_action.buf = &buf;
if(expr_log_cmnd(LOG_ENABLED)) {
expr_log(atom->test->name);
expr_log("(");
match = eval_expr_log(atom->data, &eval_action);
expr_log(")");
} else
match = eval_expr(atom->data, &eval_action);
return match;
}
static int readlink_fn(struct atom *atom, struct action_data *action_data)
{
int match = 0;
struct dir_ent *dir_ent;
struct action_data eval_action;
/* Dereference the symlink and evaluate the expression in the
* context of the file pointed to by the symlink.
* All attributes are updated to refer to the file that is pointed to.
* Thus the inode attributes, pathname, name and depth all refer to
* the dereferenced file, and not the symlink.
*
* If the symlink cannot be dereferenced because it doesn't exist in
* the output filesystem, or due to some other failure to
* walk the pathname (see follow_path above), then FALSE is returned.
*
* If you wish to evaluate the inode attributes of symlinks which
* exist in the source filestem (but not in the output filesystem then
* use stat instead (see above).
*
* readlink operates on symlinks only */
if (!file_type_match(action_data->buf->st_mode, ACTION_LNK))
goto finish;
/* dereference the symlink, and get the directory entry it points to */
dir_ent = follow_path(action_data->dir_ent->our_dir,
action_data->dir_ent->inode->symlink);
if(dir_ent == NULL)
goto finish;
eval_action.name = dir_ent->name;
eval_action.pathname = strdup(pathname(dir_ent));
eval_action.subpath = strdup(subpathname(dir_ent));
eval_action.buf = &dir_ent->inode->buf;
eval_action.depth = dir_ent->our_dir->depth;
eval_action.dir_ent = dir_ent;
eval_action.root = action_data->root;
if(expr_log_cmnd(LOG_ENABLED)) {
expr_log(atom->test->name);
expr_log("(");
match = eval_expr_log(atom->data, &eval_action);
expr_log(")");
} else
match = eval_expr(atom->data, &eval_action);
free(eval_action.pathname);
free(eval_action.subpath);
return match;
finish:
if(expr_log_cmnd(LOG_ENABLED)) {
expr_log(atom->test->name);
expr_log("(");
expr_log_match(0);
expr_log(")");
}
return 0;
}
static int eval_fn(struct atom *atom, struct action_data *action_data)
{
int match;
char *path = atom->argv[0];
struct dir_ent *dir_ent = action_data->dir_ent;
struct stat *buf = action_data->buf;
struct action_data eval_action;
/* Follow path (arg1) and evaluate the expression (arg2)
* in the context of the file discovered. All attributes are updated
* to refer to the file that is pointed to.
*
* This test operation allows you to add additional context to the
* evaluation of the file being scanned, such as "if current file is
* XXX and the parent is YYY, then ..." Often times you need or
* want to test a combination of file status
*
* If the file referenced by the path does not exist in
* the output filesystem, or some other failure is experienced in
* walking the path (see follow_path above), then FALSE is returned.
*
* If you wish to evaluate the inode attributes of files which
* exist in the source filestem (but not in the output filesystem then
* use stat instead (see above). */
/* try to follow path, and get the directory entry it points to */
if(path[0] == '/') {
/* absolute, walk from root - first skip the leading / */
while(path[0] == '/')
path ++;
if(path[0] == '\0')
dir_ent = action_data->root->dir_ent;
else
dir_ent = follow_path(action_data->root, path);
} else {
/* relative, if first component is ".." walk from parent,
* otherwise walk from dir_ent.
* Note: this has to be handled here because follow_path
* will quite correctly refuse to execute ".." on anything
* which isn't a directory */
if(strncmp(path, "..", 2) == 0 && (path[2] == '\0' ||
path[2] == '/')) {
/* walk from parent */
path += 2;
while(path[0] == '/')
path ++;
if(path[0] == '\0')
dir_ent = dir_ent->our_dir->dir_ent;
else
dir_ent = follow_path(dir_ent->our_dir, path);
} else if(!file_type_match(buf->st_mode, ACTION_DIR))
dir_ent = NULL;
else
dir_ent = follow_path(dir_ent->dir, path);
}
if(dir_ent == NULL) {
if(expr_log_cmnd(LOG_ENABLED)) {
expr_log(atom->test->name);
expr_log("(");
expr_log(atom->argv[0]);
expr_log(",");
expr_log_match(0);
expr_log(")");
}
return 0;
}
eval_action.name = dir_ent->name;
eval_action.pathname = strdup(pathname(dir_ent));
eval_action.subpath = strdup(subpathname(dir_ent));
eval_action.buf = &dir_ent->inode->buf;
eval_action.depth = dir_ent->our_dir->depth;
eval_action.dir_ent = dir_ent;
eval_action.root = action_data->root;
if(expr_log_cmnd(LOG_ENABLED)) {
expr_log(atom->test->name);
expr_log("(");
expr_log(eval_action.subpath);
expr_log(",");
match = eval_expr_log(atom->data, &eval_action);
expr_log(")");
} else
match = eval_expr(atom->data, &eval_action);
free(eval_action.pathname);
free(eval_action.subpath);
return match;
}
/*
* Perm specific test code
*/
static int parse_perm_args(struct test_entry *test, struct atom *atom)
{
int res = 1, mode, op, i;
char *arg;
struct mode_data *head = NULL, *cur = NULL;
struct perm_data *perm_data;
if(atom->args == 0) {
TEST_SYNTAX_ERROR(test, 0, "One or more arguments expected\n");
return 0;
}
switch(atom->argv[0][0]) {
case '-':
op = PERM_ALL;
arg = atom->argv[0] + 1;
break;
case '/':
op = PERM_ANY;
arg = atom->argv[0] + 1;
break;
default:
op = PERM_EXACT;
arg = atom->argv[0];
break;
}
/* try to parse as an octal number */
res = parse_octal_mode_args(atom->args, atom->argv, (void **) &head);
if(res == -1) {
/* parse as sym mode argument */
for(i = 0; i < atom->args && res; i++, arg = atom->argv[i])
res = parse_sym_mode_arg(arg, &head, &cur);
}
if (res == 0)
goto finish;
/*
* Evaluate the symbolic mode against a permission of 0000 octal
*/
mode = mode_execute(head, 0);
perm_data = malloc(sizeof(struct perm_data));
if (perm_data == NULL)
MEM_ERROR();
perm_data->op = op;
perm_data->mode = mode;
atom->data = perm_data;
finish:
while(head) {
struct mode_data *tmp = head;
head = head->next;
free(tmp);
}
return res;
}
static int perm_fn(struct atom *atom, struct action_data *action_data)
{
struct perm_data *perm_data = atom->data;
struct stat *buf = action_data->buf;
switch(perm_data->op) {
case PERM_EXACT:
return (buf->st_mode & ~S_IFMT) == perm_data->mode;
case PERM_ALL:
return (buf->st_mode & perm_data->mode) == perm_data->mode;
case PERM_ANY:
default:
/*
* if no permission bits are set in perm_data->mode match
* on any file, this is to be consistent with find, which
* does this to be consistent with the behaviour of
* -perm -000
*/
return perm_data->mode == 0 || (buf->st_mode & perm_data->mode);
}
}
#ifdef SQUASHFS_TRACE
static void dump_parse_tree(struct expr *expr)
{
int i;
if(expr->type == ATOM_TYPE) {
printf("%s", expr->atom.test->name);
if(expr->atom.args) {
printf("(");
for(i = 0; i < expr->atom.args; i++) {
printf("%s", expr->atom.argv[i]);
if (i + 1 < expr->atom.args)
printf(",");
}
printf(")");
}
} else if (expr->type == UNARY_TYPE) {
printf("%s", token_table[expr->unary_op.op].string);
dump_parse_tree(expr->unary_op.expr);
} else {
printf("(");
dump_parse_tree(expr->expr_op.lhs);
printf("%s", token_table[expr->expr_op.op].string);
dump_parse_tree(expr->expr_op.rhs);
printf(")");
}
}
void dump_action_list(struct action *spec_list, int spec_count)
{
int i;
for (i = 0; i < spec_count; i++) {
printf("%s", spec_list[i].action->name);
if (spec_list[i].args) {
int n;
printf("(");
for (n = 0; n < spec_list[i].args; n++) {
printf("%s", spec_list[i].argv[n]);
if (n + 1 < spec_list[i].args)
printf(",");
}
printf(")");
}
printf("=");
dump_parse_tree(spec_list[i].expr);
printf("\n");
}
}
void dump_actions()
{
dump_action_list(exclude_spec, exclude_count);
dump_action_list(fragment_spec, fragment_count);
dump_action_list(other_spec, other_count);
dump_action_list(move_spec, move_count);
dump_action_list(empty_spec, empty_count);
}
#else
void dump_actions()
{
}
#endif
static struct test_entry test_table[] = {
{ "name", 1, name_fn, NULL, 1},
{ "pathname", 1, pathname_fn, check_pathname, 1, 0},
{ "subpathname", 1, subpathname_fn, check_pathname, 1, 0},
{ "filesize", 1, filesize_fn, parse_number_arg, 1, 0},
{ "dirsize", 1, dirsize_fn, parse_number_arg, 1, 0},
{ "size", 1, size_fn, parse_number_arg, 1, 0},
{ "inode", 1, inode_fn, parse_number_arg, 1, 0},
{ "nlink", 1, nlink_fn, parse_number_arg, 1, 0},
{ "fileblocks", 1, fileblocks_fn, parse_number_arg, 1, 0},
{ "dirblocks", 1, dirblocks_fn, parse_number_arg, 1, 0},
{ "blocks", 1, blocks_fn, parse_number_arg, 1, 0},
{ "gid", 1, gid_fn, parse_gid_arg, 1, 0},
{ "uid", 1, uid_fn, parse_uid_arg, 1, 0},
{ "depth", 1, depth_fn, parse_number_arg, 1, 0},
{ "dircount", 1, dircount_fn, parse_number_arg, 0, 0},
{ "filesize_range", 2, filesize_range_fn, parse_range_args, 1, 0},
{ "dirsize_range", 2, dirsize_range_fn, parse_range_args, 1, 0},
{ "size_range", 2, size_range_fn, parse_range_args, 1, 0},
{ "inode_range", 2, inode_range_fn, parse_range_args, 1, 0},
{ "nlink_range", 2, nlink_range_fn, parse_range_args, 1, 0},
{ "fileblocks_range", 2, fileblocks_range_fn, parse_range_args, 1, 0},
{ "dirblocks_range", 2, dirblocks_range_fn, parse_range_args, 1, 0},
{ "blocks_range", 2, blocks_range_fn, parse_range_args, 1, 0},
{ "gid_range", 2, gid_range_fn, parse_range_args, 1, 0},
{ "uid_range", 2, uid_range_fn, parse_range_args, 1, 0},
{ "depth_range", 2, depth_range_fn, parse_range_args, 1, 0},
{ "dircount_range", 2, dircount_range_fn, parse_range_args, 0, 0},
{ "type", 1, type_fn, parse_type_arg, 1, 0},
{ "true", 0, true_fn, NULL, 1, 0},
{ "false", 0, false_fn, NULL, 1, 0},
{ "file", 1, file_fn, parse_file_arg, 1, 0},
{ "exec", 1, exec_fn, NULL, 1, 0},
{ "exists", 0, exists_fn, NULL, 0, 0},
{ "absolute", 0, absolute_fn, NULL, 0, 0},
{ "stat", 1, stat_fn, parse_expr_arg0, 1, 1},
{ "readlink", 1, readlink_fn, parse_expr_arg0, 0, 1},
{ "eval", 2, eval_fn, parse_expr_arg1, 0, 1},
{ "perm", -2, perm_fn, parse_perm_args, 1, 0},
{ "", -1 }
};
static struct action_entry action_table[] = {
{ "fragment", FRAGMENT_ACTION, 1, ACTION_REG, NULL, NULL},
{ "exclude", EXCLUDE_ACTION, 0, ACTION_ALL_LNK, NULL, NULL},
{ "fragments", FRAGMENTS_ACTION, 0, ACTION_REG, NULL, frag_action},
{ "no-fragments", NO_FRAGMENTS_ACTION, 0, ACTION_REG, NULL,
no_frag_action},
{ "always-use-fragments", ALWAYS_FRAGS_ACTION, 0, ACTION_REG, NULL,
always_frag_action},
{ "dont-always-use-fragments", NO_ALWAYS_FRAGS_ACTION, 0, ACTION_REG,
NULL, no_always_frag_action},
{ "compressed", COMPRESSED_ACTION, 0, ACTION_REG, NULL, comp_action},
{ "uncompressed", UNCOMPRESSED_ACTION, 0, ACTION_REG, NULL,
uncomp_action},
{ "uid", UID_ACTION, 1, ACTION_ALL_LNK, parse_uid_args, uid_action},
{ "gid", GID_ACTION, 1, ACTION_ALL_LNK, parse_gid_args, gid_action},
{ "guid", GUID_ACTION, 2, ACTION_ALL_LNK, parse_guid_args, guid_action},
{ "mode", MODE_ACTION, -2, ACTION_ALL, parse_mode_args, mode_action },
{ "empty", EMPTY_ACTION, -2, ACTION_DIR, parse_empty_args, NULL},
{ "move", MOVE_ACTION, 1, ACTION_ALL_LNK, NULL, NULL},
{ "prune", PRUNE_ACTION, 0, ACTION_ALL_LNK, NULL, NULL},
{ "chmod", MODE_ACTION, -2, ACTION_ALL, parse_mode_args, mode_action },
{ "noop", NOOP_ACTION, 0, ACTION_ALL, NULL, noop_action },
{ "", 0, -1, 0, NULL, NULL}
};
#ifndef ACTION_H
#define ACTION_H
/*
* Create a squashfs filesystem. This is a highly compressed read only
* filesystem.
*
* Copyright (c) 2011, 2012, 2013, 2014
* Phillip Lougher <phillip@squashfs.org.uk>
*
* 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,
* 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* action.h
*/
/*
* Lexical analyser definitions
*/
#define TOK_OPEN_BRACKET 0
#define TOK_CLOSE_BRACKET 1
#define TOK_AND 2
#define TOK_OR 3
#define TOK_NOT 4
#define TOK_COMMA 5
#define TOK_AT 6
#define TOK_WHITE_SPACE 7
#define TOK_STRING 8
#define TOK_EOF 9
#define TOK_TO_STR(OP, S) ({ \
char *s; \
switch(OP) { \
case TOK_EOF: \
s = "EOF"; \
break; \
case TOK_STRING: \
s = S; \
break; \
default: \
s = token_table[OP].string; \
break; \
} \
s; \
})
struct token_entry {
char *string;
int token;
int size;
};
/*
* Expression parser definitions
*/
#define OP_TYPE 0
#define ATOM_TYPE 1
#define UNARY_TYPE 2
#define SYNTAX_ERROR(S, ARGS...) { \
char *src = strdup(source); \
src[cur_ptr - source] = '\0'; \
fprintf(stderr, "Failed to parse action \"%s\"\n", source); \
fprintf(stderr, "Syntax error: "S, ##ARGS); \
fprintf(stderr, "Got here \"%s\"\n", src); \
free(src); \
}
#define TEST_SYNTAX_ERROR(TEST, ARG, S, ARGS...) { \
char *src = strdup(source); \
src[cur_ptr - source] = '\0'; \
fprintf(stderr, "Failed to parse action \"%s\"\n", source); \
fprintf(stderr, "Syntax error in \"%s()\", arg %d: "S, TEST->name, \
ARG, ##ARGS); \
fprintf(stderr, "Got here \"%s\"\n", src); \
free(src); \
}
struct expr;
struct expr_op {
struct expr *lhs;
struct expr *rhs;
int op;
};
struct atom {
struct test_entry *test;
int args;
char **argv;
void *data;
};
struct unary_op {
struct expr *expr;
int op;
};
struct expr {
int type;
union {
struct atom atom;
struct expr_op expr_op;
struct unary_op unary_op;
};
};
/*
* Test operation definitions
*/
#define NUM_EQ 1
#define NUM_LESS 2
#define NUM_GREATER 3
struct test_number_arg {
long long size;
int range;
};
struct test_range_args {
long long start;
long long end;
};
struct action;
struct action_data;
struct test_entry {
char *name;
int args;
int (*fn)(struct atom *, struct action_data *);
int (*parse_args)(struct test_entry *, struct atom *);
int exclude_ok;
int handle_logging;
};
/*
* Type test specific definitions
*/
struct type_entry {
int value;
char type;
};
/*
* Action definitions
*/
#define FRAGMENT_ACTION 0
#define EXCLUDE_ACTION 1
#define FRAGMENTS_ACTION 2
#define NO_FRAGMENTS_ACTION 3
#define ALWAYS_FRAGS_ACTION 4
#define NO_ALWAYS_FRAGS_ACTION 5
#define COMPRESSED_ACTION 6
#define UNCOMPRESSED_ACTION 7
#define UID_ACTION 8
#define GID_ACTION 9
#define GUID_ACTION 10
#define MODE_ACTION 11
#define EMPTY_ACTION 12
#define MOVE_ACTION 13
#define PRUNE_ACTION 14
#define NOOP_ACTION 15
/*
* Define what file types each action operates over
*/
#define ACTION_DIR 1
#define ACTION_REG 2
#define ACTION_ALL_LNK 3
#define ACTION_ALL 4
#define ACTION_LNK 5
/*
* Action logging requested, specified by the various
* -action, -true-action, -false-action and -verbose-action
* options
*/
#define ACTION_LOG_NONE 0
#define ACTION_LOG_TRUE 1
#define ACTION_LOG_FALSE 2
#define ACTION_LOG_VERBOSE ACTION_LOG_TRUE | ACTION_LOG_FALSE
struct action_entry {
char *name;
int type;
int args;
int file_types;
int (*parse_args)(struct action_entry *, int, char **, void **);
void (*run_action)(struct action *, struct dir_ent *);
};
struct action_data {
int depth;
char *name;
char *pathname;
char *subpath;
struct stat *buf;
struct dir_ent *dir_ent;
struct dir_info *root;
};
struct action {
int type;
struct action_entry *action;
int args;
char **argv;
struct expr *expr;
void *data;
int verbose;
};
/*
* Uid/gid action specific definitions
*/
struct uid_info {
uid_t uid;
};
struct gid_info {
gid_t gid;
};
struct guid_info {
uid_t uid;
gid_t gid;
};
/*
* Mode action specific definitions
*/
#define ACTION_MODE_SET 0
#define ACTION_MODE_ADD 1
#define ACTION_MODE_REM 2
#define ACTION_MODE_OCT 3
struct mode_data {
struct mode_data *next;
int operation;
int mode;
unsigned int mask;
char X;
};
/*
* Empty action specific definitions
*/
#define EMPTY_ALL 0
#define EMPTY_SOURCE 1
#define EMPTY_EXCLUDED 2
struct empty_data {
int val;
};
/*
* Move action specific definitions
*/
#define ACTION_MOVE_RENAME 1
#define ACTION_MOVE_MOVE 2
struct move_ent {
int ops;
struct dir_ent *dir_ent;
char *name;
struct dir_info *dest;
struct move_ent *next;
};
/*
* Perm test function specific definitions
*/
#define PERM_ALL 1
#define PERM_ANY 2
#define PERM_EXACT 3
struct perm_data {
int op;
int mode;
};
/*
* External function definitions
*/
extern int parse_action(char *, int verbose);
extern void dump_actions();
extern void *eval_frag_actions(struct dir_info *, struct dir_ent *);
extern void *get_frag_action(void *);
extern int eval_exclude_actions(char *, char *, char *, struct stat *, int,
struct dir_ent *);
extern void eval_actions(struct dir_info *, struct dir_ent *);
extern int eval_empty_actions(struct dir_info *, struct dir_ent *dir_ent);
extern void eval_move_actions(struct dir_info *, struct dir_ent *);
extern int eval_prune_actions(struct dir_info *, struct dir_ent *);
extern void do_move_actions();
extern int read_bytes(int, void *, int);
extern int actions();
extern int move_actions();
extern int empty_actions();
extern int read_action_file(char *, int);
extern int exclude_actions();
extern int prune_actions();
#endif
#!/bin/bash
export LZMA_LIBDIR=$PWD/../../LIB/LZMA
export LZ4_LIBDIR=$PWD/../../LIB/LZ4
export ZSTD_LIBDIR=$PWD/../../LIB/ZSTD
export LZO_LIBDIR=$PWD/../../LIB/LZO
if [ -e /lib64/libz.a ]; then
export VTZLIB=/lib64/libz.a
elif [ -e /lib/libz.a ]; then
export VTZLIB=/lib/libz.a
elif [ -e /usr/lib/libz.a ]; then
export VTZLIB=/usr/lib/libz.a
fi
rm -f unsquashfs
make clean
make -e unsquashfs
if [ -e unsquashfs ]; then
strip --strip-all unsquashfs
echo -e "\n========== SUCCESS ============\n"
else
echo -e "\n========== FAILED ============\n"
fi
if uname -a | egrep -q 'x86_64|amd64'; then
name=unsquashfs_64
else
name=unsquashfs_32
fi
rm -f ../../$name
cp -a unsquashfs ../../$name
/*
* Create a squashfs filesystem. This is a highly compressed read only
* filesystem.
*
* Copyright (c) 2013, 2014, 2019
* Phillip Lougher <phillip@squashfs.org.uk>
*
* 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,
* 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* caches-queues-lists.c
*/
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "error.h"
#include "caches-queues-lists.h"
extern int add_overflow(int, int);
extern int multiply_overflow(int, int);
#define TRUE 1
#define FALSE 0
struct queue *queue_init(int size)
{
struct queue *queue = malloc(sizeof(struct queue));
if(queue == NULL)
MEM_ERROR();
if(add_overflow(size, 1) ||
multiply_overflow(size + 1, sizeof(void *)))
BAD_ERROR("Size too large in queue_init\n");
queue->data = malloc(sizeof(void *) * (size + 1));
if(queue->data == NULL)
MEM_ERROR();
queue->size = size + 1;
queue->readp = queue->writep = 0;
pthread_mutex_init(&queue->mutex, NULL);
pthread_cond_init(&queue->empty, NULL);
pthread_cond_init(&queue->full, NULL);
return queue;
}
void queue_put(struct queue *queue, void *data)
{
int nextp;
pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
pthread_mutex_lock(&queue->mutex);
while((nextp = (queue->writep + 1) % queue->size) == queue->readp)
pthread_cond_wait(&queue->full, &queue->mutex);
queue->data[queue->writep] = data;
queue->writep = nextp;
pthread_cond_signal(&queue->empty);
pthread_cleanup_pop(1);
}
void *queue_get(struct queue *queue)
{
void *data;
pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
pthread_mutex_lock(&queue->mutex);
while(queue->readp == queue->writep)
pthread_cond_wait(&queue->empty, &queue->mutex);
data = queue->data[queue->readp];
queue->readp = (queue->readp + 1) % queue->size;
pthread_cond_signal(&queue->full);
pthread_cleanup_pop(1);
return data;
}
int queue_empty(struct queue *queue)
{
int empty;
pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
pthread_mutex_lock(&queue->mutex);
empty = queue->readp == queue->writep;
pthread_cleanup_pop(1);
return empty;
}
void queue_flush(struct queue *queue)
{
pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
pthread_mutex_lock(&queue->mutex);
queue->readp = queue->writep;
pthread_cleanup_pop(1);
}
void dump_queue(struct queue *queue)
{
pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
pthread_mutex_lock(&queue->mutex);
printf("\tMax size %d, size %d%s\n", queue->size - 1,
queue->readp <= queue->writep ? queue->writep - queue->readp :
queue->size - queue->readp + queue->writep,
queue->readp == queue->writep ? " (EMPTY)" :
((queue->writep + 1) % queue->size) == queue->readp ?
" (FULL)" : "");
pthread_cleanup_pop(1);
}
/* define seq queue hash tables */
#define CALCULATE_SEQ_HASH(N) CALCULATE_HASH(N)
/* Called with the seq queue mutex held */
INSERT_HASH_TABLE(seq, struct seq_queue, CALCULATE_SEQ_HASH, sequence, seq)
/* Called with the cache mutex held */
REMOVE_HASH_TABLE(seq, struct seq_queue, CALCULATE_SEQ_HASH, sequence, seq);
struct seq_queue *seq_queue_init()
{
struct seq_queue *queue = malloc(sizeof(struct seq_queue));
if(queue == NULL)
MEM_ERROR();
memset(queue, 0, sizeof(struct seq_queue));
pthread_mutex_init(&queue->mutex, NULL);
pthread_cond_init(&queue->wait, NULL);
return queue;
}
void seq_queue_put(struct seq_queue *queue, struct file_buffer *entry)
{
pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
pthread_mutex_lock(&queue->mutex);
insert_seq_hash_table(queue, entry);
if(entry->fragment)
queue->fragment_count ++;
else
queue->block_count ++;
if(entry->sequence == queue->sequence)
pthread_cond_signal(&queue->wait);
pthread_cleanup_pop(1);
}
struct file_buffer *seq_queue_get(struct seq_queue *queue)
{
/*
* Return next buffer from queue in sequence order (queue->sequence). If
* found return it, otherwise wait for it to arrive.
*/
int hash = CALCULATE_SEQ_HASH(queue->sequence);
struct file_buffer *entry;
pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
pthread_mutex_lock(&queue->mutex);
while(1) {
for(entry = queue->hash_table[hash]; entry;
entry = entry->seq_next)
if(entry->sequence == queue->sequence)
break;
if(entry) {
/*
* found the buffer in the queue, decrement the
* appropriate count, and remove from hash list
*/
if(entry->fragment)
queue->fragment_count --;
else
queue->block_count --;
remove_seq_hash_table(queue, entry);
queue->sequence ++;
break;
}
/* entry not found, wait for it to arrive */
pthread_cond_wait(&queue->wait, &queue->mutex);
}
pthread_cleanup_pop(1);
return entry;
}
void seq_queue_flush(struct seq_queue *queue)
{
int i;
pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
pthread_mutex_lock(&queue->mutex);
for(i = 0; i < HASH_SIZE; i++)
queue->hash_table[i] = NULL;
queue->fragment_count = queue->block_count = 0;
pthread_cleanup_pop(1);
}
void dump_seq_queue(struct seq_queue *queue, int fragment_queue)
{
int size;
pthread_cleanup_push((void *) pthread_mutex_unlock, &queue->mutex);
pthread_mutex_lock(&queue->mutex);
size = fragment_queue ? queue->fragment_count : queue->block_count;
printf("\tMax size unlimited, size %d%s\n", size,
size == 0 ? " (EMPTY)" : "");
pthread_cleanup_pop(1);
}
/* define cache hash tables */
#define CALCULATE_CACHE_HASH(N) CALCULATE_HASH(llabs(N))
/* Called with the cache mutex held */
INSERT_HASH_TABLE(cache, struct cache, CALCULATE_CACHE_HASH, index, hash)
/* Called with the cache mutex held */
REMOVE_HASH_TABLE(cache, struct cache, CALCULATE_CACHE_HASH, index, hash);
/* define cache free list */
/* Called with the cache mutex held */
INSERT_LIST(free, struct file_buffer)
/* Called with the cache mutex held */
REMOVE_LIST(free, struct file_buffer)
struct cache *cache_init(int buffer_size, int max_buffers, int noshrink_lookup,
int first_freelist)
{
struct cache *cache = malloc(sizeof(struct cache));
if(cache == NULL)
MEM_ERROR();
cache->max_buffers = max_buffers;
cache->buffer_size = buffer_size;
cache->count = 0;
cache->used = 0;
cache->free_list = NULL;
/*
* The cache will grow up to max_buffers in size in response to
* an increase in readhead/number of buffers in flight. But
* once the outstanding buffers gets returned, we can either elect
* to shrink the cache, or to put the freed blocks onto a free list.
*
* For the caches where we want to do lookup (fragment/writer),
* a don't shrink policy is best, for the reader cache it
* makes no sense to keep buffers around longer than necessary as
* we don't do any lookup on those blocks.
*/
cache->noshrink_lookup = noshrink_lookup;
/*
* The default use freelist before growing cache policy behaves
* poorly with appending - with many duplicates the caches
* do not grow due to the fact that large queues of outstanding
* fragments/writer blocks do not occur, leading to small caches
* and un-uncessary performance loss to frequent cache
* replacement in the small caches. Therefore with appending
* change the policy to grow the caches before reusing blocks
* from the freelist
*/
cache->first_freelist = first_freelist;
memset(cache->hash_table, 0, sizeof(struct file_buffer *) * 65536);
pthread_mutex_init(&cache->mutex, NULL);
pthread_cond_init(&cache->wait_for_free, NULL);
pthread_cond_init(&cache->wait_for_unlock, NULL);
return cache;
}
struct file_buffer *cache_lookup(struct cache *cache, long long index)
{
/* Lookup block in the cache, if found return with usage count
* incremented, if not found return NULL */
int hash = CALCULATE_CACHE_HASH(index);
struct file_buffer *entry;
pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
pthread_mutex_lock(&cache->mutex);
for(entry = cache->hash_table[hash]; entry; entry = entry->hash_next)
if(entry->index == index)
break;
if(entry) {
/* found the block in the cache, increment used count and
* if necessary remove from free list so it won't disappear
*/
if(entry->used == 0) {
remove_free_list(&cache->free_list, entry);
cache->used ++;
}
entry->used ++;
}
pthread_cleanup_pop(1);
return entry;
}
static struct file_buffer *cache_freelist(struct cache *cache)
{
struct file_buffer *entry = cache->free_list;
remove_free_list(&cache->free_list, entry);
/* a block on the free_list is hashed */
remove_cache_hash_table(cache, entry);
cache->used ++;
return entry;
}
static struct file_buffer *cache_alloc(struct cache *cache)
{
struct file_buffer *entry = malloc(sizeof(struct file_buffer) +
cache->buffer_size);
if(entry == NULL)
MEM_ERROR();
entry->cache = cache;
entry->free_prev = entry->free_next = NULL;
cache->count ++;
return entry;
}
static struct file_buffer *_cache_get(struct cache *cache, long long index,
int hash)
{
/* Get a free block out of the cache indexed on index. */
struct file_buffer *entry = NULL;
pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
pthread_mutex_lock(&cache->mutex);
while(1) {
if(cache->noshrink_lookup) {
/* first try to get a block from the free list */
if(cache->first_freelist && cache->free_list)
entry = cache_freelist(cache);
else if(cache->count < cache->max_buffers) {
entry = cache_alloc(cache);
cache->used ++;
} else if(!cache->first_freelist && cache->free_list)
entry = cache_freelist(cache);
} else { /* shrinking non-lookup cache */
if(cache->count < cache->max_buffers) {
entry = cache_alloc(cache);
if(cache->count > cache->max_count)
cache->max_count = cache->count;
}
}
if(entry)
break;
/* wait for a block */
pthread_cond_wait(&cache->wait_for_free, &cache->mutex);
}
/* initialise block and if hash is set insert into the hash table */
entry->used = 1;
entry->locked = FALSE;
entry->wait_on_unlock = FALSE;
entry->error = FALSE;
if(hash) {
entry->index = index;
insert_cache_hash_table(cache, entry);
}
pthread_cleanup_pop(1);
return entry;
}
struct file_buffer *cache_get(struct cache *cache, long long index)
{
return _cache_get(cache, index, 1);
}
struct file_buffer *cache_get_nohash(struct cache *cache)
{
return _cache_get(cache, 0, 0);
}
void cache_hash(struct file_buffer *entry, long long index)
{
struct cache *cache = entry->cache;
pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
pthread_mutex_lock(&cache->mutex);
entry->index = index;
insert_cache_hash_table(cache, entry);
pthread_cleanup_pop(1);
}
void cache_block_put(struct file_buffer *entry)
{
struct cache *cache;
/*
* Finished with this cache entry, once the usage count reaches zero it
* can be reused.
*
* If noshrink_lookup is set, put the block onto the free list.
* As blocks remain accessible via the hash table they can be found
* getting a new lease of life before they are reused.
*
* if noshrink_lookup is not set then shrink the cache.
*/
if(entry == NULL)
return;
cache = entry->cache;
pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
pthread_mutex_lock(&cache->mutex);
entry->used --;
if(entry->used == 0) {
if(cache->noshrink_lookup) {
insert_free_list(&cache->free_list, entry);
cache->used --;
} else {
free(entry);
cache->count --;
}
/* One or more threads may be waiting on this block */
pthread_cond_signal(&cache->wait_for_free);
}
pthread_cleanup_pop(1);
}
void dump_cache(struct cache *cache)
{
pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
pthread_mutex_lock(&cache->mutex);
if(cache->noshrink_lookup)
printf("\tMax buffers %d, Current size %d, Used %d, %s\n",
cache->max_buffers, cache->count, cache->used,
cache->free_list ? "Free buffers" : "No free buffers");
else
printf("\tMax buffers %d, Current size %d, Maximum historical "
"size %d\n", cache->max_buffers, cache->count,
cache->max_count);
pthread_cleanup_pop(1);
}
struct file_buffer *cache_get_nowait(struct cache *cache, long long index)
{
struct file_buffer *entry = NULL;
/*
* block doesn't exist, create it, but return it with the
* locked flag set, so nothing tries to use it while it doesn't
* contain data.
*
* If there's no space in the cache then return NULL.
*/
pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
pthread_mutex_lock(&cache->mutex);
/* first try to get a block from the free list */
if(cache->first_freelist && cache->free_list)
entry = cache_freelist(cache);
else if(cache->count < cache->max_buffers) {
entry = cache_alloc(cache);
cache->used ++;
} else if(!cache->first_freelist && cache->free_list)
entry = cache_freelist(cache);
if(entry) {
/* initialise block and insert into the hash table */
entry->used = 1;
entry->locked = TRUE;
entry->wait_on_unlock = FALSE;
entry->error = FALSE;
entry->index = index;
insert_cache_hash_table(cache, entry);
}
pthread_cleanup_pop(1);
return entry;
}
struct file_buffer *cache_lookup_nowait(struct cache *cache, long long index,
char *locked)
{
/*
* Lookup block in the cache, if found return it with the locked flag
* indicating whether it is currently locked. In both cases increment
* the used count.
*
* If it doesn't exist in the cache return NULL;
*/
int hash = CALCULATE_CACHE_HASH(index);
struct file_buffer *entry;
pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
pthread_mutex_lock(&cache->mutex);
/* first check if the entry already exists */
for(entry = cache->hash_table[hash]; entry; entry = entry->hash_next)
if(entry->index == index)
break;
if(entry) {
if(entry->used == 0) {
remove_free_list(&cache->free_list, entry);
cache->used ++;
}
entry->used ++;
*locked = entry->locked;
}
pthread_cleanup_pop(1);
return entry;
}
void cache_wait_unlock(struct file_buffer *buffer)
{
struct cache *cache = buffer->cache;
pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
pthread_mutex_lock(&cache->mutex);
while(buffer->locked) {
/*
* another thread is filling this in, wait until it
* becomes unlocked. Used has been incremented to ensure it
* doesn't get reused. By definition a block can't be
* locked and unused, and so we don't need to worry
* about it being on the freelist now, but, it may
* become unused when unlocked unless used is
* incremented
*/
buffer->wait_on_unlock = TRUE;
pthread_cond_wait(&cache->wait_for_unlock, &cache->mutex);
}
pthread_cleanup_pop(1);
}
void cache_unlock(struct file_buffer *entry)
{
struct cache *cache = entry->cache;
/*
* Unlock this locked cache entry. If anything is waiting for this
* to become unlocked, wake it up.
*/
pthread_cleanup_push((void *) pthread_mutex_unlock, &cache->mutex);
pthread_mutex_lock(&cache->mutex);
entry->locked = FALSE;
if(entry->wait_on_unlock) {
entry->wait_on_unlock = FALSE;
pthread_cond_broadcast(&cache->wait_for_unlock);
}
pthread_cleanup_pop(1);
}
#ifndef CACHES_QUEUES_LISTS_H
#define CACHES_QUEUES_LISTS_H
/*
* Create a squashfs filesystem. This is a highly compressed read only
* filesystem.
*
* Copyright (c) 2013, 2014, 2019
* Phillip Lougher <phillip@squashfs.org.uk>
*
* 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,
* 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* caches-queues-lists.h
*/
#define INSERT_LIST(NAME, TYPE) \
void insert_##NAME##_list(TYPE **list, TYPE *entry) { \
if(*list) { \
entry->NAME##_next = *list; \
entry->NAME##_prev = (*list)->NAME##_prev; \
(*list)->NAME##_prev->NAME##_next = entry; \
(*list)->NAME##_prev = entry; \
} else { \
*list = entry; \
entry->NAME##_prev = entry->NAME##_next = entry; \
} \
}
#define REMOVE_LIST(NAME, TYPE) \
void remove_##NAME##_list(TYPE **list, TYPE *entry) { \
if(entry->NAME##_prev == entry && entry->NAME##_next == entry) { \
/* only this entry in the list */ \
*list = NULL; \
} else if(entry->NAME##_prev != NULL && entry->NAME##_next != NULL) { \
/* more than one entry in the list */ \
entry->NAME##_next->NAME##_prev = entry->NAME##_prev; \
entry->NAME##_prev->NAME##_next = entry->NAME##_next; \
if(*list == entry) \
*list = entry->NAME##_next; \
} \
entry->NAME##_prev = entry->NAME##_next = NULL; \
}
#define INSERT_HASH_TABLE(NAME, TYPE, HASH_FUNCTION, FIELD, LINK) \
void insert_##NAME##_hash_table(TYPE *container, struct file_buffer *entry) \
{ \
int hash = HASH_FUNCTION(entry->FIELD); \
\
entry->LINK##_next = container->hash_table[hash]; \
container->hash_table[hash] = entry; \
entry->LINK##_prev = NULL; \
if(entry->LINK##_next) \
entry->LINK##_next->LINK##_prev = entry; \
}
#define REMOVE_HASH_TABLE(NAME, TYPE, HASH_FUNCTION, FIELD, LINK) \
void remove_##NAME##_hash_table(TYPE *container, struct file_buffer *entry) \
{ \
if(entry->LINK##_prev) \
entry->LINK##_prev->LINK##_next = entry->LINK##_next; \
else \
container->hash_table[HASH_FUNCTION(entry->FIELD)] = \
entry->LINK##_next; \
if(entry->LINK##_next) \
entry->LINK##_next->LINK##_prev = entry->LINK##_prev; \
\
entry->LINK##_prev = entry->LINK##_next = NULL; \
}
#define HASH_SIZE 65536
#define CALCULATE_HASH(n) ((n) & 0xffff)
/* struct describing a cache entry passed between threads */
struct file_buffer {
long long index;
long long sequence;
long long file_size;
union {
long long block;
unsigned short checksum;
};
struct cache *cache;
union {
struct file_info *dupl_start;
struct file_buffer *hash_next;
};
union {
int duplicate;
struct file_buffer *hash_prev;
};
union {
struct {
struct file_buffer *free_next;
struct file_buffer *free_prev;
};
struct {
struct file_buffer *seq_next;
struct file_buffer *seq_prev;
};
};
int size;
int c_byte;
char used;
char fragment;
char error;
char locked;
char wait_on_unlock;
char noD;
char data[0] __attribute__((aligned));
};
/* struct describing queues used to pass data between threads */
struct queue {
int size;
int readp;
int writep;
pthread_mutex_t mutex;
pthread_cond_t empty;
pthread_cond_t full;
void **data;
};
/*
* struct describing seq_queues used to pass data between the read
* thread and the deflate and main threads
*/
struct seq_queue {
int fragment_count;
int block_count;
long long sequence;
struct file_buffer *hash_table[HASH_SIZE];
pthread_mutex_t mutex;
pthread_cond_t wait;
};
/* Cache status struct. Caches are used to keep
track of memory buffers passed between different threads */
struct cache {
int max_buffers;
int count;
int buffer_size;
int noshrink_lookup;
int first_freelist;
union {
int used;
int max_count;
};
pthread_mutex_t mutex;
pthread_cond_t wait_for_free;
pthread_cond_t wait_for_unlock;
struct file_buffer *free_list;
struct file_buffer *hash_table[HASH_SIZE];
};
extern struct queue *queue_init(int);
extern void queue_put(struct queue *, void *);
extern void *queue_get(struct queue *);
extern int queue_empty(struct queue *);
extern void queue_flush(struct queue *);
extern void dump_queue(struct queue *);
extern struct seq_queue *seq_queue_init();
extern void seq_queue_put(struct seq_queue *, struct file_buffer *);
extern void dump_seq_queue(struct seq_queue *, int);
extern struct file_buffer *seq_queue_get(struct seq_queue *);
extern void seq_queue_flush(struct seq_queue *);
extern struct cache *cache_init(int, int, int, int);
extern struct file_buffer *cache_lookup(struct cache *, long long);
extern struct file_buffer *cache_get(struct cache *, long long);
extern struct file_buffer *cache_get_nohash(struct cache *);
extern void cache_hash(struct file_buffer *, long long);
extern void cache_block_put(struct file_buffer *);
extern void dump_cache(struct cache *);
extern struct file_buffer *cache_get_nowait(struct cache *, long long);
extern struct file_buffer *cache_lookup_nowait(struct cache *, long long,
char *);
extern void cache_wait_unlock(struct file_buffer *);
extern void cache_unlock(struct file_buffer *);
extern int first_freelist;
#endif
/*
*
* Copyright (c) 2009, 2010, 2011
* Phillip Lougher <phillip@squashfs.org.uk>
*
* 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,
* 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* compressor.c
*/
#include <stdio.h>
#include <string.h>
#include "compressor.h"
#include "squashfs_fs.h"
#ifndef GZIP_SUPPORT
static struct compressor gzip_comp_ops = {
ZLIB_COMPRESSION, "gzip"
};
#else
extern struct compressor gzip_comp_ops;
#endif
#ifndef LZMA_SUPPORT
static struct compressor lzma_comp_ops = {
LZMA_COMPRESSION, "lzma"
};
#else
extern struct compressor lzma_comp_ops;
#endif
#ifndef LZO_SUPPORT
static struct compressor lzo_comp_ops = {
LZO_COMPRESSION, "lzo"
};
#else
extern struct compressor lzo_comp_ops;
#endif
#ifndef LZ4_SUPPORT
static struct compressor lz4_comp_ops = {
LZ4_COMPRESSION, "lz4"
};
#else
extern struct compressor lz4_comp_ops;
#endif
#ifndef XZ_SUPPORT
static struct compressor xz_comp_ops = {
XZ_COMPRESSION, "xz"
};
#else
extern struct compressor xz_comp_ops;
#endif
#ifndef ZSTD_SUPPORT
static struct compressor zstd_comp_ops = {
ZSTD_COMPRESSION, "zstd"
};
#else
extern struct compressor zstd_comp_ops;
#endif
static struct compressor unknown_comp_ops = {
0, "unknown"
};
struct compressor *compressor[] = {
&gzip_comp_ops,
&lzma_comp_ops,
&lzo_comp_ops,
&lz4_comp_ops,
&xz_comp_ops,
&zstd_comp_ops,
&unknown_comp_ops
};
struct compressor *lookup_compressor(char *name)
{
int i;
for(i = 0; compressor[i]->id; i++)
if(strcmp(compressor[i]->name, name) == 0)
break;
return compressor[i];
}
struct compressor *lookup_compressor_id(int id)
{
int i;
for(i = 0; compressor[i]->id; i++)
if(id == compressor[i]->id)
break;
return compressor[i];
}
void display_compressors(char *indent, char *def_comp)
{
int i;
for(i = 0; compressor[i]->id; i++)
if(compressor[i]->supported)
fprintf(stderr, "%s\t%s%s\n", indent,
compressor[i]->name,
strcmp(compressor[i]->name, def_comp) == 0 ?
" (default)" : "");
}
void display_compressor_usage(char *def_comp)
{
int i;
for(i = 0; compressor[i]->id; i++)
if(compressor[i]->supported) {
char *str = strcmp(compressor[i]->name, def_comp) == 0 ?
" (default)" : "";
if(compressor[i]->usage) {
fprintf(stderr, "\t%s%s\n",
compressor[i]->name, str);
compressor[i]->usage();
} else
fprintf(stderr, "\t%s (no options)%s\n",
compressor[i]->name, str);
}
}
#ifndef COMPRESSOR_H
#define COMPRESSOR_H
/*
*
* Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014
* Phillip Lougher <phillip@squashfs.org.uk>
*
* 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,
* 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* compressor.h
*/
struct compressor {
int id;
char *name;
int supported;
int (*init)(void **, int, int);
int (*compress)(void *, void *, void *, int, int, int *);
int (*uncompress)(void *, void *, int, int, int *);
int (*options)(char **, int);
int (*options_post)(int);
void *(*dump_options)(int, int *);
int (*extract_options)(int, void *, int);
int (*check_options)(int, void *, int);
void (*display_options)(void *, int);
void (*usage)();
};
extern struct compressor *lookup_compressor(char *);
extern struct compressor *lookup_compressor_id(int);
extern void display_compressors(char *, char *);
extern void display_compressor_usage(char *);
static inline int compressor_init(struct compressor *comp, void **stream,
int block_size, int datablock)
{
if(comp->init == NULL)
return 0;
return comp->init(stream, block_size, datablock);
}
static inline int compressor_compress(struct compressor *comp, void *strm,
void *dest, void *src, int size, int block_size, int *error)
{
return comp->compress(strm, dest, src, size, block_size, error);
}
static inline int compressor_uncompress(struct compressor *comp, void *dest,
void *src, int size, int block_size, int *error)
{
return comp->uncompress(dest, src, size, block_size, error);
}
/*
* For the following functions please see the lzo, lz4 or xz
* compressors for commented examples of how they are used.
*/
static inline int compressor_options(struct compressor *comp, char *argv[],
int argc)
{
if(comp->options == NULL)
return -1;
return comp->options(argv, argc);
}
static inline int compressor_options_post(struct compressor *comp, int block_size)
{
if(comp->options_post == NULL)
return 0;
return comp->options_post(block_size);
}
static inline void *compressor_dump_options(struct compressor *comp,
int block_size, int *size)
{
if(comp->dump_options == NULL)
return NULL;
return comp->dump_options(block_size, size);
}
static inline int compressor_extract_options(struct compressor *comp,
int block_size, void *buffer, int size)
{
if(comp->extract_options == NULL)
return size ? -1 : 0;
return comp->extract_options(block_size, buffer, size);
}
static inline int compressor_check_options(struct compressor *comp,
int block_size, void *buffer, int size)
{
if(comp->check_options == NULL)
return 0;
return comp->check_options(block_size, buffer, size);
}
static inline void compressor_display_options(struct compressor *comp,
void *buffer, int size)
{
if(comp->display_options != NULL)
comp->display_options(buffer, size);
}
#endif
#ifndef ERROR_H
#define ERROR_H
/*
* Create a squashfs filesystem. This is a highly compressed read only
* filesystem.
*
* Copyright (c) 2012, 2013, 2014, 2019
* Phillip Lougher <phillip@squashfs.org.uk>
*
* 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,
* 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* error.h
*/
extern int exit_on_error;
extern void prep_exit();
extern void progressbar_error(char *fmt, ...);
extern void progressbar_info(char *fmt, ...);
#ifdef SQUASHFS_TRACE
#define TRACE(s, args...) \
do { \
progressbar_info("squashfs: "s, ## args);\
} while(0)
#else
#define TRACE(s, args...)
#endif
#define INFO(s, args...) \
do {\
if(!silent)\
progressbar_info(s, ## args);\
} while(0)
#define ERROR(s, args...) \
do {\
progressbar_error(s, ## args); \
} while(0)
#define ERROR_START(s, args...) \
do { \
disable_progress_bar(); \
fprintf(stderr, s, ## args); \
} while(0)
#define ERROR_EXIT(s, args...) \
do {\
if (exit_on_error) { \
fprintf(stderr, "\n"); \
EXIT_MKSQUASHFS(); \
} else { \
fprintf(stderr, s, ## args); \
enable_progress_bar(); \
} \
} while(0)
#define EXIT_MKSQUASHFS() \
do {\
prep_exit();\
exit(1);\
} while(0)
#define BAD_ERROR(s, args...) \
do {\
progressbar_error("FATAL ERROR:" s, ##args); \
EXIT_MKSQUASHFS();\
} while(0)
#define EXIT_UNSQUASH(s, args...) BAD_ERROR(s, ##args)
#define EXIT_UNSQUASH_IGNORE(s, args...) \
do {\
if(ignore_errors) \
ERROR(s, ##args); \
else \
BAD_ERROR(s, ##args); \
} while(0)
#define EXIT_UNSQUASH_STRICT(s, args...) \
do {\
if(!strict_errors) \
ERROR(s, ##args); \
else \
BAD_ERROR(s, ##args); \
} while(0)
#define MEM_ERROR() \
do {\
progressbar_error("FATAL ERROR: Out of memory (%s)\n", \
__func__); \
EXIT_MKSQUASHFS();\
} while(0)
#endif
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