diff options
Diffstat (limited to 'genext2fs.c')
-rw-r--r-- | genext2fs.c | 2726 |
1 files changed, 2726 insertions, 0 deletions
diff --git a/genext2fs.c b/genext2fs.c new file mode 100644 index 0000000..11c9c37 --- /dev/null +++ b/genext2fs.c @@ -0,0 +1,2726 @@ +/* vi: set sw=8 ts=8: */ +// genext2fs.c +// +// ext2 filesystem generator for embedded systems +// Copyright (C) 2000 Xavier Bestel <xavier.bestel@free.fr> +// +// Please direct support requests to genext2fs-devel@lists.sourceforge.net +// +// 'du' portions taken from coreutils/du.c in busybox: +// Copyright (C) 1999,2000 by Lineo, inc. and John Beppu +// Copyright (C) 1999,2000,2001 by John Beppu <beppu@codepoet.org> +// Copyright (C) 2002 Edward Betts <edward@debian.org> +// +// 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; version +// 2 of the License. +// +// Changes: +// 3 Jun 2000 Initial release +// 6 Jun 2000 Bugfix: fs size multiple of 8 +// Bugfix: fill blocks with inodes +// 14 Jun 2000 Bugfix: bad chdir() with -d option +// Bugfix: removed size=8n constraint +// Changed -d file to -f file +// Added -e option +// 22 Jun 2000 Changed types for 64bits archs +// 24 Jun 2000 Added endianness swap +// Bugfix: bad dir name lookup +// 03 Aug 2000 Bugfix: ind. blocks endian swap +// 09 Aug 2000 Bugfix: symlinks endian swap +// 01 Sep 2000 Bugfix: getopt returns int, not char proski@gnu.org +// 10 Sep 2000 Bugfix: device nodes endianness xavier.gueguen@col.bsf.alcatel.fr +// Bugfix: getcwd values for Solaris xavier.gueguen@col.bsf.alcatel.fr +// Bugfix: ANSI scanf for non-GNU C xavier.gueguen@col.bsf.alcatel.fr +// 28 Jun 2001 Bugfix: getcwd differs for Solaris/GNU mike@sowbug.com +// 8 Mar 2002 Bugfix: endianness swap of x-indirects +// 23 Mar 2002 Bugfix: test for IFCHR or IFBLK was flawed +// 10 Oct 2002 Added comments,makefile targets, vsundar@ixiacom.com +// endianess swap assert check. +// Copyright (C) 2002 Ixia communications +// 12 Oct 2002 Added support for triple indirection vsundar@ixiacom.com +// Copyright (C) 2002 Ixia communications +// 14 Oct 2002 Added support for groups vsundar@ixiacom.com +// Copyright (C) 2002 Ixia communications +// 5 Jan 2003 Bugfixes: reserved inodes should be set vsundar@usc.edu +// only in the first group; directory names +// need to be null padded at the end; and +// number of blocks per group should be a +// multiple of 8. Updated md5 values. +// 6 Jan 2003 Erik Andersen <andersee@debian.org> added +// mkfs.jffs2 compatible device table support, +// along with -q, -P, -U + + +#include <config.h> +#include <stdio.h> + +#if HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif + +#if MAJOR_IN_MKDEV +# include <sys/mkdev.h> +#elif MAJOR_IN_SYSMACROS +# include <sys/sysmacros.h> +#endif + +#if HAVE_SYS_STAT_H +# include <sys/stat.h> +#endif + +#if STDC_HEADERS +# include <stdlib.h> +# include <stddef.h> +#else +# if HAVE_STDLIB_H +# include <stdlib.h> +# endif +# if HAVE_STDDEF_H +# include <stddef.h> +# endif +#endif + +#if HAVE_STRING_H +# if !STDC_HEADERS && HAVE_MEMORY_H +# include <memory.h> +# endif +# include <string.h> +#endif + +#if HAVE_STRINGS_H +# include <strings.h> +#endif + +#if HAVE_INTTYPES_H +# include <inttypes.h> +#else +# if HAVE_STDINT_H +# include <stdint.h> +# endif +#endif + +#if HAVE_UNISTD_H +# include <unistd.h> +#endif + +#if HAVE_DIRENT_H +# include <dirent.h> +# define NAMLEN(dirent) strlen((dirent)->d_name) +#else +# define dirent direct +# define NAMLEN(dirent) (dirent)->d_namlen +# if HAVE_SYS_NDIR_H +# include <sys/ndir.h> +# endif +# if HAVE_SYS_DIR_H +# include <sys/dir.h> +# endif +# if HAVE_NDIR_H +# include <ndir.h> +# endif +#endif + +#if HAVE_LIBGEN_H +# include <libgen.h> +#endif + +#include <stdarg.h> +#include <assert.h> +#include <time.h> +#include <ctype.h> +#include <errno.h> + +#if HAVE_FCNTL_H +# include <fcntl.h> +#endif + +#if HAVE_GETOPT_H +# include <getopt.h> +#endif + +#if HAVE_LIMITS_H +# include <limits.h> +#endif + +#include <private/android_filesystem_config.h> +unsigned source_path_len = 0; + +struct stats { + unsigned long nblocks; + unsigned long ninodes; +}; + +// block size + +#define BLOCKSIZE 1024 +#define BLOCKS_PER_GROUP 8192 +#define INODES_PER_GROUP 8192 +/* Percentage of blocks that are reserved.*/ +#define RESERVED_BLOCKS 5/100 +#define MAX_RESERVED_BLOCKS 25/100 + + +// inode block size (why is it != BLOCKSIZE ?!?) +/* The field i_blocks in the ext2 inode stores the number of data blocks + but in terms of 512 bytes. That is what INODE_BLOCKSIZE represents. + INOBLK is the number of such blocks in an actual disk block */ + +#define INODE_BLOCKSIZE 512 +#define INOBLK (BLOCKSIZE / INODE_BLOCKSIZE) + +// reserved inodes + +#define EXT2_BAD_INO 1 // Bad blocks inode +#define EXT2_ROOT_INO 2 // Root inode +#define EXT2_ACL_IDX_INO 3 // ACL inode +#define EXT2_ACL_DATA_INO 4 // ACL inode +#define EXT2_BOOT_LOADER_INO 5 // Boot loader inode +#define EXT2_UNDEL_DIR_INO 6 // Undelete directory inode +#define EXT2_FIRST_INO 11 // First non reserved inode + +// magic number for ext2 + +#define EXT2_MAGIC_NUMBER 0xEF53 + + +// direct/indirect block addresses + +#define EXT2_NDIR_BLOCKS 11 // direct blocks +#define EXT2_IND_BLOCK 12 // indirect block +#define EXT2_DIND_BLOCK 13 // double indirect block +#define EXT2_TIND_BLOCK 14 // triple indirect block +#define EXT2_INIT_BLOCK 0xFFFFFFFF // just initialized (not really a block address) + +// end of a block walk + +#define WALK_END 0xFFFFFFFE + +// file modes + +#define FM_IFMT 0170000 // format mask +#define FM_IFSOCK 0140000 // socket +#define FM_IFLNK 0120000 // symbolic link +#define FM_IFREG 0100000 // regular file + +#define FM_IFBLK 0060000 // block device +#define FM_IFDIR 0040000 // directory +#define FM_IFCHR 0020000 // character device +#define FM_IFIFO 0010000 // fifo + +#define FM_IMASK 0007777 // *all* perms mask for everything below + +#define FM_ISUID 0004000 // SUID +#define FM_ISGID 0002000 // SGID +#define FM_ISVTX 0001000 // sticky bit + +#define FM_IRWXU 0000700 // entire "user" mask +#define FM_IRUSR 0000400 // read +#define FM_IWUSR 0000200 // write +#define FM_IXUSR 0000100 // execute + +#define FM_IRWXG 0000070 // entire "group" mask +#define FM_IRGRP 0000040 // read +#define FM_IWGRP 0000020 // write +#define FM_IXGRP 0000010 // execute + +#define FM_IRWXO 0000007 // entire "other" mask +#define FM_IROTH 0000004 // read +#define FM_IWOTH 0000002 // write +#define FM_IXOTH 0000001 // execute + +// options + +#define OP_HOLES 0x01 // make files with holes + +/* Defines for accessing group details */ + +// Number of groups in the filesystem +#define GRP_NBGROUPS(fs) \ + (((fs)->sb.s_blocks_count - fs->sb.s_first_data_block + \ + (fs)->sb.s_blocks_per_group - 1) / (fs)->sb.s_blocks_per_group) + +// Get group block bitmap (bbm) given the group number +#define GRP_GET_GROUP_BBM(fs,grp) ( get_blk((fs),(fs)->gd[(grp)].bg_block_bitmap) ) + +// Get group inode bitmap (ibm) given the group number +#define GRP_GET_GROUP_IBM(fs,grp) ( get_blk((fs),(fs)->gd[(grp)].bg_inode_bitmap) ) + +// Given an inode number find the group it belongs to +#define GRP_GROUP_OF_INODE(fs,nod) ( ((nod)-1) / (fs)->sb.s_inodes_per_group) + +//Given an inode number get the inode bitmap that covers it +#define GRP_GET_INODE_BITMAP(fs,nod) \ + ( GRP_GET_GROUP_IBM((fs),GRP_GROUP_OF_INODE((fs),(nod))) ) + +//Given an inode number find its offset within the inode bitmap that covers it +#define GRP_IBM_OFFSET(fs,nod) \ + ( (nod) - GRP_GROUP_OF_INODE((fs),(nod))*(fs)->sb.s_inodes_per_group ) + +// Given a block number find the group it belongs to +#define GRP_GROUP_OF_BLOCK(fs,blk) ( ((blk)-1) / (fs)->sb.s_blocks_per_group) + +//Given a block number get the block bitmap that covers it +#define GRP_GET_BLOCK_BITMAP(fs,blk) \ + ( GRP_GET_GROUP_BBM((fs),GRP_GROUP_OF_BLOCK((fs),(blk))) ) + +//Given a block number find its offset within the block bitmap that covers it +#define GRP_BBM_OFFSET(fs,blk) \ + ( (blk) - GRP_GROUP_OF_BLOCK((fs),(blk))*(fs)->sb.s_blocks_per_group ) + + +// used types + +typedef signed char int8; +typedef unsigned char uint8; +typedef signed short int16; +typedef unsigned short uint16; +typedef signed int int32; +typedef unsigned int uint32; + + +// the GNU C library has a wonderful scanf("%as", string) which will +// allocate the string with the right size, good to avoid buffer +// overruns. the following macros use it if available or use a +// hacky workaround +// moreover it will define a snprintf() like a sprintf(), i.e. +// without the buffer overrun checking, to work around bugs in +// older solaris. Note that this is still not very portable, in that +// the return value cannot be trusted. + +#if 0 // SCANF_CAN_MALLOC +// C99 define "a" for floating point, so you can have runtime surprise +// according the library versions +# define SCANF_PREFIX "a" +# define SCANF_STRING(s) (&s) +#else +# define SCANF_PREFIX "511" +# define SCANF_STRING(s) (s = malloc(512)) +#endif /* SCANF_CAN_MALLOC */ + +#if PREFER_PORTABLE_SNPRINTF +static inline int +portable_snprintf(char *str, size_t n, const char *fmt, ...) +{ + int ret; + va_list ap; + va_start(ap, fmt); + ret = vsprintf(str, fmt, ap); + va_end(ap); + return ret; +} +# define SNPRINTF portable_snprintf +#else +# define SNPRINTF snprintf +#endif /* PREFER_PORTABLE_SNPRINTF */ + +#if !HAVE_GETLINE +// getline() replacement for Darwin and Solaris etc. +// This code uses backward seeks (unless rchunk is set to 1) which can't work +// on pipes etc. However, add2fs_from_file() only calls getline() for +// regular files, so a larger rchunk and backward seeks are okay. + +ssize_t +getdelim(char **lineptr, size_t *n, int delim, FILE *stream) +{ + char *p; // reads stored here + size_t const rchunk = 512; // number of bytes to read + size_t const mchunk = 512; // number of extra bytes to malloc + size_t m = rchunk + 1; // initial buffer size + + if (*lineptr) { + if (*n < m) { + *lineptr = (char*)realloc(*lineptr, m); + if (!*lineptr) return -1; + *n = m; + } + } else { + *lineptr = (char*)malloc(m); + if (!*lineptr) return -1; + *n = m; + } + + m = 0; // record length including seperator + + do { + size_t i; // number of bytes read etc + size_t j = 0; // number of bytes searched + + p = *lineptr + m; + + i = fread(p, 1, rchunk, stream); + if (i < rchunk && ferror(stream)) + return -1; + while (j < i) { + ++j; + if (*p++ == (char)delim) { + *p = '\0'; + if (j != i) { + if (fseek(stream, j - i, SEEK_CUR)) + return -1; + if (feof(stream)) + clearerr(stream); + } + m += j; + return m; + } + } + + m += j; + if (feof(stream)) { + if (m) return m; + if (!i) return -1; + } + + // allocate space for next read plus possible null terminator + i = ((m + (rchunk + 1 > mchunk ? rchunk + 1 : mchunk) + + mchunk - 1) / mchunk) * mchunk; + if (i != *n) { + *lineptr = (char*)realloc(*lineptr, i); + if (!*lineptr) return -1; + *n = i; + } + } while (1); +} +#define getline(a,b,c) getdelim(a,b,'\n',c) +#endif /* HAVE_GETLINE */ + +// Convert a numerical string to a float, and multiply the result by an +// IEC or SI multiplier if provided; supported multipliers are Ki, Mi, Gi, k, M +// and G. + +float +SI_atof(const char *nptr) +{ + float f = 0; + float m = 1; + char *suffixptr; + +#if HAVE_STRTOF + f = strtof(nptr, &suffixptr); +#else + f = (float)strtod(nptr, &suffixptr); +#endif /* HAVE_STRTOF */ + + if (*suffixptr) { + if (!strcmp(suffixptr, "Ki")) + m = 1 << 10; + else if (!strcmp(suffixptr, "Mi")) + m = 1 << 20; + else if (!strcmp(suffixptr, "Gi")) + m = 1 << 30; + else if (!strcmp(suffixptr, "k")) + m = 1000; + else if (!strcmp(suffixptr, "M")) + m = 1000 * 1000; + else if (!strcmp(suffixptr, "G")) + m = 1000 * 1000 * 1000; + } + return f * m; +} + +// endianness swap + +static inline uint16 +swab16(uint16 val) +{ + return (val >> 8) | (val << 8); +} + +static inline uint32 +swab32(uint32 val) +{ + return ((val>>24) | ((val>>8)&0xFF00) | + ((val<<8)&0xFF0000) | (val<<24)); +} + + +// on-disk structures +// this trick makes me declare things only once +// (once for the structures, once for the endianness swap) + +#define superblock_decl \ + udecl32(s_inodes_count) /* Count of inodes in the filesystem */ \ + udecl32(s_blocks_count) /* Count of blocks in the filesystem */ \ + udecl32(s_r_blocks_count) /* Count of the number of reserved blocks */ \ + udecl32(s_free_blocks_count) /* Count of the number of free blocks */ \ + udecl32(s_free_inodes_count) /* Count of the number of free inodes */ \ + udecl32(s_first_data_block) /* The first block which contains data */ \ + udecl32(s_log_block_size) /* Indicator of the block size */ \ + decl32(s_log_frag_size) /* Indicator of the size of the fragments */ \ + udecl32(s_blocks_per_group) /* Count of the number of blocks in each block group */ \ + udecl32(s_frags_per_group) /* Count of the number of fragments in each block group */ \ + udecl32(s_inodes_per_group) /* Count of the number of inodes in each block group */ \ + udecl32(s_mtime) /* The time that the filesystem was last mounted */ \ + udecl32(s_wtime) /* The time that the filesystem was last written to */ \ + udecl16(s_mnt_count) /* The number of times the file system has been mounted */ \ + decl16(s_max_mnt_count) /* The number of times the file system can be mounted */ \ + udecl16(s_magic) /* Magic number indicating ex2fs */ \ + udecl16(s_state) /* Flags indicating the current state of the filesystem */ \ + udecl16(s_errors) /* Flags indicating the procedures for error reporting */ \ + udecl16(s_minor_rev_level) /* The minor revision level of the filesystem */ \ + udecl32(s_lastcheck) /* The time that the filesystem was last checked */ \ + udecl32(s_checkinterval) /* The maximum time permissable between checks */ \ + udecl32(s_creator_os) /* Indicator of which OS created the filesystem */ \ + udecl32(s_rev_level) /* The revision level of the filesystem */ \ + udecl16(s_def_resuid) /* The default uid for reserved blocks */ \ + udecl16(s_def_resgid) /* The default gid for reserved blocks */ + +#define groupdescriptor_decl \ + udecl32(bg_block_bitmap) /* Block number of the block bitmap */ \ + udecl32(bg_inode_bitmap) /* Block number of the inode bitmap */ \ + udecl32(bg_inode_table) /* Block number of the inode table */ \ + udecl16(bg_free_blocks_count) /* Free blocks in the group */ \ + udecl16(bg_free_inodes_count) /* Free inodes in the group */ \ + udecl16(bg_used_dirs_count) /* Number of directories in the group */ \ + udecl16(bg_pad) + +#define inode_decl \ + udecl16(i_mode) /* Entry type and file mode */ \ + udecl16(i_uid) /* User id */ \ + udecl32(i_size) /* File/dir size in bytes */ \ + udecl32(i_atime) /* Last access time */ \ + udecl32(i_ctime) /* Creation time */ \ + udecl32(i_mtime) /* Last modification time */ \ + udecl32(i_dtime) /* Deletion time */ \ + udecl16(i_gid) /* Group id */ \ + udecl16(i_links_count) /* Number of (hard) links to this inode */ \ + udecl32(i_blocks) /* Number of blocks used (1 block = 512 bytes) */ \ + udecl32(i_flags) /* ??? */ \ + udecl32(i_reserved1) \ + utdecl32(i_block,15) /* Blocks table */ \ + udecl32(i_version) /* ??? */ \ + udecl32(i_file_acl) /* File access control list */ \ + udecl32(i_dir_acl) /* Directory access control list */ \ + udecl32(i_faddr) /* Fragment address */ \ + udecl8(i_frag) /* Fragments count*/ \ + udecl8(i_fsize) /* Fragment size */ \ + udecl16(i_pad1) + +#define directory_decl \ + udecl32(d_inode) /* Inode entry */ \ + udecl16(d_rec_len) /* Total size on record */ \ + udecl16(d_name_len) /* Size of entry name */ + +#define decl8(x) int8 x; +#define udecl8(x) uint8 x; +#define decl16(x) int16 x; +#define udecl16(x) uint16 x; +#define decl32(x) int32 x; +#define udecl32(x) uint32 x; +#define utdecl32(x,n) uint32 x[n]; + +typedef struct +{ + superblock_decl + uint32 s_reserved[235]; // Reserved +} superblock; + +typedef struct +{ + groupdescriptor_decl + uint32 bg_reserved[3]; +} groupdescriptor; + +typedef struct +{ + inode_decl + uint32 i_reserved2[2]; +} inode; + +typedef struct +{ + directory_decl + char d_name[0]; +} directory; + +typedef uint8 block[BLOCKSIZE]; + +/* blockwalker fields: + The blockwalker is used to access all the blocks of a file (including + the indirection blocks) through repeated calls to walk_bw. + + bpdir -> index into the inode->i_block[]. Indicates level of indirection. + bnum -> total number of blocks so far accessed. including indirection + blocks. + bpind,bpdind,bptind -> index into indirection blocks. + + bpind, bpdind, bptind do *NOT* index into single, double and triple + indirect blocks resp. as you might expect from their names. Instead + they are in order the 1st, 2nd & 3rd index to be used + + As an example.. + To access data block number 70000: + bpdir: 15 (we are doing triple indirection) + bpind: 0 ( index into the triple indirection block) + bpdind: 16 ( index into the double indirection block) + bptind: 99 ( index into the single indirection block) + 70000 = 12 + 256 + 256*256 + 16*256 + 100 (indexing starts from zero) + + So,for double indirection bpind will index into the double indirection + block and bpdind into the single indirection block. For single indirection + only bpind will be used. +*/ + +typedef struct +{ + uint32 bnum; + uint32 bpdir; + uint32 bpind; + uint32 bpdind; + uint32 bptind; +} blockwalker; + + +/* Filesystem structure that support groups */ +#if BLOCKSIZE == 1024 +typedef struct +{ + block zero; // The famous block 0 + superblock sb; // The superblock + groupdescriptor gd[0]; // The group descriptors +} filesystem; +#else +#error UNHANDLED BLOCKSIZE +#endif + +// now the endianness swap + +#undef decl8 +#undef udecl8 +#undef decl16 +#undef udecl16 +#undef decl32 +#undef udecl32 +#undef utdecl32 + +#define decl8(x) +#define udecl8(x) +#define decl16(x) this->x = swab16(this->x); +#define udecl16(x) this->x = swab16(this->x); +#define decl32(x) this->x = swab32(this->x); +#define udecl32(x) this->x = swab32(this->x); +#define utdecl32(x,n) { int i; for(i=0; i<n; i++) this->x[i] = swab32(this->x[i]); } + +#define HDLINK_CNT 16 +static int32 hdlink_cnt = HDLINK_CNT; +struct hdlink_s +{ + uint32 src_inode; + uint32 dst_nod; +}; + +struct hdlinks_s +{ + int32 count; + struct hdlink_s *hdl; +}; + +static struct hdlinks_s hdlinks; + +static void +swap_sb(superblock *sb) +{ +#define this sb + superblock_decl +#undef this +} + +static void +swap_gd(groupdescriptor *gd) +{ +#define this gd + groupdescriptor_decl +#undef this +} + +static void +swap_nod(inode *nod) +{ +#define this nod + inode_decl +#undef this +} + +static void +swap_dir(directory *dir) +{ +#define this dir + directory_decl +#undef this +} + +static void +swap_block(block b) +{ + int i; + uint32 *blk = (uint32*)b; + for(i = 0; i < BLOCKSIZE/4; i++) + blk[i] = swab32(blk[i]); +} + +#undef decl8 +#undef udecl8 +#undef decl16 +#undef udecl16 +#undef decl32 +#undef udecl32 +#undef utdecl32 + +static char * app_name; +static const char *const memory_exhausted = "memory exhausted"; + +// error (un)handling +static void +verror_msg(const char *s, va_list p) +{ + fflush(stdout); + fprintf(stderr, "%s: ", app_name); + vfprintf(stderr, s, p); +} +static void +error_msg(const char *s, ...) +{ + va_list p; + va_start(p, s); + verror_msg(s, p); + va_end(p); + putc('\n', stderr); +} + +static void +error_msg_and_die(const char *s, ...) +{ + va_list p; + va_start(p, s); + verror_msg(s, p); + va_end(p); + putc('\n', stderr); + exit(EXIT_FAILURE); +} + +static void +vperror_msg(const char *s, va_list p) +{ + int err = errno; + if (s == 0) + s = ""; + verror_msg(s, p); + if (*s) + s = ": "; + fprintf(stderr, "%s%s\n", s, strerror(err)); +} + +static void +perror_msg_and_die(const char *s, ...) +{ + va_list p; + va_start(p, s); + vperror_msg(s, p); + va_end(p); + exit(EXIT_FAILURE); +} + +static FILE * +xfopen(const char *path, const char *mode) +{ + FILE *fp; + if ((fp = fopen(path, mode)) == NULL) + perror_msg_and_die("%s", path); + return fp; +} + +static char * +xstrdup(const char *s) +{ + char *t; + + if (s == NULL) + return NULL; + t = strdup(s); + if (t == NULL) + error_msg_and_die(memory_exhausted); + return t; +} + +static void * +xrealloc(void *ptr, size_t size) +{ + ptr = realloc(ptr, size); + if (ptr == NULL && size != 0) + error_msg_and_die(memory_exhausted); + return ptr; +} + +static char * +xreadlink(const char *path) +{ + static const int GROWBY = 80; /* how large we will grow strings by */ + + char *buf = NULL; + int bufsize = 0, readsize = 0; + + do { + buf = xrealloc(buf, bufsize += GROWBY); + readsize = readlink(path, buf, bufsize); /* 1st try */ + if (readsize == -1) { + perror_msg_and_die("%s:%s", app_name, path); + } + } + while (bufsize < readsize + 1); + + buf[readsize] = '\0'; + return buf; +} + +int +is_hardlink(ino_t inode) +{ + int i; + + for(i = 0; i < hdlinks.count; i++) { + if(hdlinks.hdl[i].src_inode == inode) + return i; + } + return -1; +} + +// printf helper macro +#define plural(a) (a), ((a) > 1) ? "s" : "" + +// temporary working block +static inline uint8 * +get_workblk(void) +{ + unsigned char* b=calloc(1,BLOCKSIZE); + return b; +} +static inline void +free_workblk(block b) +{ + free(b); +} + +/* Rounds qty upto a multiple of siz. siz should be a power of 2 */ +static inline uint32 +rndup(uint32 qty, uint32 siz) +{ + return (qty + (siz - 1)) & ~(siz - 1); +} + +// check if something is allocated in the bitmap +static inline uint32 +allocated(block b, uint32 item) +{ + return b[(item-1) / 8] & (1 << ((item-1) % 8)); +} + +// return a given block from a filesystem +static inline uint8 * +get_blk(filesystem *fs, uint32 blk) +{ + return (uint8*)fs + blk*BLOCKSIZE; +} + +// return a given inode from a filesystem +static inline inode * +get_nod(filesystem *fs, uint32 nod) +{ + int grp,offset; + inode *itab; + + offset = GRP_IBM_OFFSET(fs,nod); + grp = GRP_GROUP_OF_INODE(fs,nod); + itab = (inode *)get_blk(fs, fs->gd[grp].bg_inode_table); + return itab+offset-1; +} + +// allocate a given block/inode in the bitmap +// allocate first free if item == 0 +static uint32 +allocate(block b, uint32 item) +{ + if(!item) + { + int i; + uint8 bits; + for(i = 0; i < BLOCKSIZE; i++) + if((bits = b[i]) != (uint8)-1) + { + int j; + for(j = 0; j < 8; j++) + if(!(bits & (1 << j))) + break; + item = i * 8 + j + 1; + break; + } + if(i == BLOCKSIZE) + return 0; + } + b[(item-1) / 8] |= (1 << ((item-1) % 8)); + return item; +} + +// deallocate a given block/inode +static void +deallocate(block b, uint32 item) +{ + b[(item-1) / 8] &= ~(1 << ((item-1) % 8)); +} + +// allocate a block +static uint32 +alloc_blk(filesystem *fs, uint32 nod) +{ + uint32 bk=0; + uint32 grp,nbgroups; + + grp = GRP_GROUP_OF_INODE(fs,nod); + nbgroups = GRP_NBGROUPS(fs); + if(!(bk = allocate(get_blk(fs,fs->gd[grp].bg_block_bitmap), 0))) { + for(grp=0;grp<nbgroups && !bk;grp++) + bk=allocate(get_blk(fs,fs->gd[grp].bg_block_bitmap),0); + grp--; + } + if (!bk) + error_msg_and_die("couldn't allocate a block (no free space)"); + if(!(fs->gd[grp].bg_free_blocks_count--)) + error_msg_and_die("group descr %d. free blocks count == 0 (corrupted fs?)",grp); + if(!(fs->sb.s_free_blocks_count--)) + error_msg_and_die("superblock free blocks count == 0 (corrupted fs?)"); + return fs->sb.s_blocks_per_group*grp + bk; +} + +// free a block +static void +free_blk(filesystem *fs, uint32 bk) +{ + uint32 grp; + + grp = bk / fs->sb.s_blocks_per_group; + bk %= fs->sb.s_blocks_per_group; + deallocate(get_blk(fs,fs->gd[grp].bg_block_bitmap), bk); + fs->gd[grp].bg_free_blocks_count++; + fs->sb.s_free_blocks_count++; +} + +// allocate an inode +static uint32 +alloc_nod(filesystem *fs) +{ + uint32 nod,best_group=0; + uint32 grp,nbgroups,avefreei; + + nbgroups = GRP_NBGROUPS(fs); + + /* Distribute inodes amongst all the blocks */ + /* For every block group with more than average number of free inodes */ + /* find the one with the most free blocks and allocate node there */ + /* Idea from find_group_dir in fs/ext2/ialloc.c in 2.4.19 kernel */ + /* We do it for all inodes. */ + avefreei = fs->sb.s_free_inodes_count / nbgroups; + for(grp=0; grp<nbgroups; grp++) { + if (fs->gd[grp].bg_free_inodes_count < avefreei || + fs->gd[grp].bg_free_inodes_count == 0) + continue; + if (!best_group || + fs->gd[grp].bg_free_blocks_count > fs->gd[best_group].bg_free_blocks_count) + best_group = grp; + } + if (!(nod = allocate(get_blk(fs,fs->gd[best_group].bg_inode_bitmap),0))) + error_msg_and_die("couldn't allocate an inode (no free inode)"); + if(!(fs->gd[best_group].bg_free_inodes_count--)) + error_msg_and_die("group descr. free blocks count == 0 (corrupted fs?)"); + if(!(fs->sb.s_free_inodes_count--)) + error_msg_and_die("superblock free blocks count == 0 (corrupted fs?)"); + return fs->sb.s_inodes_per_group*best_group+nod; +} + +// print a bitmap allocation +static void +print_bm(block b, uint32 max) +{ + uint32 i; + printf("----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+----9----+----0\n"); + for(i=1; i <= max; i++) + { + putchar(allocated(b, i) ? '*' : '.'); + if(!(i % 100)) + printf("\n"); + } + if((i-1) % 100) + printf("\n"); +} + +// initalize a blockwalker (iterator for blocks list) +static inline void +init_bw(blockwalker *bw) +{ + bw->bnum = 0; + bw->bpdir = EXT2_INIT_BLOCK; +} + +// return next block of inode (WALK_END for end) +// if *create>0, append a newly allocated block at the end +// if *create<0, free the block - warning, the metadata blocks contents is +// used after being freed, so once you start +// freeing blocks don't stop until the end of +// the file. moreover, i_blocks isn't updated. +// in fact, don't do that, just use extend_blk +// if hole!=0, create a hole in the file +static uint32 +walk_bw(filesystem *fs, uint32 nod, blockwalker *bw, int32 *create, uint32 hole) +{ + uint32 *bkref = 0; + uint32 *b; + int extend = 0, reduce = 0; + if(create && (*create) < 0) + reduce = 1; + if(bw->bnum >= get_nod(fs, nod)->i_blocks / INOBLK) + { + if(create && (*create) > 0) + { + (*create)--; + extend = 1; + } + else + return WALK_END; + } + // first direct block + if(bw->bpdir == EXT2_INIT_BLOCK) + { + bkref = &get_nod(fs, nod)->i_block[bw->bpdir = 0]; + if(extend) // allocate first block + *bkref = hole ? 0 : alloc_blk(fs,nod); + if(reduce) // free first block + free_blk(fs, *bkref); + } + // direct block + else if(bw->bpdir < EXT2_NDIR_BLOCKS) + { + bkref = &get_nod(fs, nod)->i_block[++bw->bpdir]; + if(extend) // allocate block + *bkref = hole ? 0 : alloc_blk(fs,nod); + if(reduce) // free block + free_blk(fs, *bkref); + } + // first block in indirect block + else if(bw->bpdir == EXT2_NDIR_BLOCKS) + { + bw->bnum++; + bw->bpdir = EXT2_IND_BLOCK; + bw->bpind = 0; + if(extend) // allocate indirect block + get_nod(fs, nod)->i_block[bw->bpdir] = alloc_blk(fs,nod); + if(reduce) // free indirect block + free_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]); + b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]); + bkref = &b[bw->bpind]; + if(extend) // allocate first block + *bkref = hole ? 0 : alloc_blk(fs,nod); + if(reduce) // free first block + free_blk(fs, *bkref); + } + // block in indirect block + else if((bw->bpdir == EXT2_IND_BLOCK) && (bw->bpind < BLOCKSIZE/4 - 1)) + { + bw->bpind++; + b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]); + bkref = &b[bw->bpind]; + if(extend) // allocate block + *bkref = hole ? 0 : alloc_blk(fs,nod); + if(reduce) // free block + free_blk(fs, *bkref); + } + // first block in first indirect block in first double indirect block + else if(bw->bpdir == EXT2_IND_BLOCK) + { + bw->bnum += 2; + bw->bpdir = EXT2_DIND_BLOCK; + bw->bpind = 0; + bw->bpdind = 0; + if(extend) // allocate double indirect block + get_nod(fs, nod)->i_block[bw->bpdir] = alloc_blk(fs,nod); + if(reduce) // free double indirect block + free_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]); + b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]); + if(extend) // allocate first indirect block + b[bw->bpind] = alloc_blk(fs,nod); + if(reduce) // free firstindirect block + free_blk(fs, b[bw->bpind]); + b = (uint32*)get_blk(fs, b[bw->bpind]); + bkref = &b[bw->bpdind]; + if(extend) // allocate first block + *bkref = hole ? 0 : alloc_blk(fs,nod); + if(reduce) // free first block + free_blk(fs, *bkref); + } + // block in indirect block in double indirect block + else if((bw->bpdir == EXT2_DIND_BLOCK) && (bw->bpdind < BLOCKSIZE/4 - 1)) + { + bw->bpdind++; + b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]); + b = (uint32*)get_blk(fs, b[bw->bpind]); + bkref = &b[bw->bpdind]; + if(extend) // allocate block + *bkref = hole ? 0 : alloc_blk(fs,nod); + if(reduce) // free block + free_blk(fs, *bkref); + } + // first block in indirect block in double indirect block + else if((bw->bpdir == EXT2_DIND_BLOCK) && (bw->bpind < BLOCKSIZE/4 - 1)) + { + bw->bnum++; + bw->bpdind = 0; + bw->bpind++; + b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]); + if(extend) // allocate indirect block + b[bw->bpind] = alloc_blk(fs,nod); + if(reduce) // free indirect block + free_blk(fs, b[bw->bpind]); + b = (uint32*)get_blk(fs, b[bw->bpind]); + bkref = &b[bw->bpdind]; + if(extend) // allocate first block + *bkref = hole ? 0 : alloc_blk(fs,nod); + if(reduce) // free first block + free_blk(fs, *bkref); + } + + /* Adding support for triple indirection */ + /* Just starting triple indirection. Allocate the indirection + blocks and the first data block + */ + else if (bw->bpdir == EXT2_DIND_BLOCK) + { + bw->bnum += 3; + bw->bpdir = EXT2_TIND_BLOCK; + bw->bpind = 0; + bw->bpdind = 0; + bw->bptind = 0; + if(extend) // allocate triple indirect block + get_nod(fs, nod)->i_block[bw->bpdir] = alloc_blk(fs,nod); + if(reduce) // free triple indirect block + free_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]); + b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]); + if(extend) // allocate first double indirect block + b[bw->bpind] = alloc_blk(fs,nod); + if(reduce) // free first double indirect block + free_blk(fs, b[bw->bpind]); + b = (uint32*)get_blk(fs, b[bw->bpind]); + if(extend) // allocate first indirect block + b[bw->bpdind] = alloc_blk(fs,nod); + if(reduce) // free first indirect block + free_blk(fs, b[bw->bpind]); + b = (uint32*)get_blk(fs, b[bw->bpdind]); + bkref = &b[bw->bptind]; + if(extend) // allocate first data block + *bkref = hole ? 0 : alloc_blk(fs,nod); + if(reduce) // free first block + free_blk(fs, *bkref); + } + /* Still processing a single indirect block down the indirection + chain.Allocate a data block for it + */ + else if ( (bw->bpdir == EXT2_TIND_BLOCK) && + (bw->bptind < BLOCKSIZE/4 -1) ) + { + bw->bptind++; + b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]); + b = (uint32*)get_blk(fs, b[bw->bpind]); + b = (uint32*)get_blk(fs, b[bw->bpdind]); + bkref = &b[bw->bptind]; + if(extend) // allocate data block + *bkref = hole ? 0 : alloc_blk(fs,nod); + if(reduce) // free block + free_blk(fs, *bkref); + } + /* Finished processing a single indirect block. But still in the + same double indirect block. Allocate new single indirect block + for it and a data block + */ + else if ( (bw->bpdir == EXT2_TIND_BLOCK) && + (bw->bpdind < BLOCKSIZE/4 -1) ) + { + bw->bnum++; + bw->bptind = 0; + bw->bpdind++; + b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]); + b = (uint32*)get_blk(fs, b[bw->bpind]); + if(extend) // allocate single indirect block + b[bw->bpdind] = alloc_blk(fs,nod); + if(reduce) // free indirect block + free_blk(fs, b[bw->bpind]); + b = (uint32*)get_blk(fs, b[bw->bpdind]); + bkref = &b[bw->bptind]; + if(extend) // allocate first data block + *bkref = hole ? 0 : alloc_blk(fs,nod); + if(reduce) // free first block + free_blk(fs, *bkref); + } + /* Finished processing a double indirect block. Allocate the next + double indirect block and the single,data blocks for it + */ + else if ( (bw->bpdir == EXT2_TIND_BLOCK) && + (bw->bpind < BLOCKSIZE/4 - 1) ) + { + bw->bnum += 2; + bw->bpdind = 0; + bw->bptind = 0; + bw->bpind++; + b = (uint32*)get_blk(fs, get_nod(fs, nod)->i_block[bw->bpdir]); + if(extend) // allocate double indirect block + b[bw->bpind] = alloc_blk(fs,nod); + if(reduce) // free double indirect block + free_blk(fs, b[bw->bpind]); + b = (uint32*)get_blk(fs, b[bw->bpind]); + if(extend) // allocate single indirect block + b[bw->bpdind] = alloc_blk(fs,nod); + if(reduce) // free indirect block + free_blk(fs, b[bw->bpind]); + b = (uint32*)get_blk(fs, b[bw->bpdind]); + bkref = &b[bw->bptind]; + if(extend) // allocate first block + *bkref = hole ? 0 : alloc_blk(fs,nod); + if(reduce) // free first block + free_blk(fs, *bkref); + } + else + error_msg_and_die("file too big !"); + /* End change for walking triple indirection */ + + if(*bkref) + { + bw->bnum++; + if(!reduce && !allocated(GRP_GET_BLOCK_BITMAP(fs,*bkref), GRP_BBM_OFFSET(fs,*bkref))) + error_msg_and_die("[block %d of inode %d is unallocated !]", *bkref, nod); + } + if(extend) + get_nod(fs, nod)->i_blocks = bw->bnum * INOBLK; + return *bkref; +} + +// add blocks to an inode (file/dir/etc...) +static void +extend_blk(filesystem *fs, uint32 nod, block b, int amount) +{ + int create = amount; + blockwalker bw, lbw; + uint32 bk; + init_bw(&bw); + if(amount < 0) + { + uint32 i; + for(i = 0; i < get_nod(fs, nod)->i_blocks / INOBLK + amount; i++) + walk_bw(fs, nod, &bw, 0, 0); + while(walk_bw(fs, nod, &bw, &create, 0) != WALK_END) + /*nop*/; + get_nod(fs, nod)->i_blocks += amount * INOBLK; + } + else + { + lbw = bw; + while((bk = walk_bw(fs, nod, &bw, 0, 0)) != WALK_END) + lbw = bw; + bw = lbw; + while(create) + { + int i, copyb = 0; + if(!(fs->sb.s_reserved[200] & OP_HOLES)) + copyb = 1; + else + for(i = 0; i < BLOCKSIZE / 4; i++) + if(((int32*)(b + BLOCKSIZE * (amount - create)))[i]) + { + copyb = 1; + break; + } + if((bk = walk_bw(fs, nod, &bw, &create, !copyb)) == WALK_END) + break; + if(copyb) + memcpy(get_blk(fs, bk), b + BLOCKSIZE * (amount - create - 1), BLOCKSIZE); + } + } +} + +// link an entry (inode #) to a directory +static void +add2dir(filesystem *fs, uint32 dnod, uint32 nod, const char* name) +{ + blockwalker bw; + uint32 bk; + uint8 *b; + directory *d; + int reclen, nlen; + inode *node; + inode *pnode; + + pnode = get_nod(fs, dnod); + if((pnode->i_mode & FM_IFMT) != FM_IFDIR) + error_msg_and_die("can't add '%s' to a non-directory", name); + if(!*name) + error_msg_and_die("can't create an inode with an empty name"); + if(strchr(name, '/')) + error_msg_and_die("bad name '%s' (contains a slash)", name); + nlen = strlen(name); + reclen = sizeof(directory) + rndup(nlen, 4); + if(reclen > BLOCKSIZE) + error_msg_and_die("bad name '%s' (too long)", name); + init_bw(&bw); + while((bk = walk_bw(fs, dnod, &bw, 0, 0)) != WALK_END) // for all blocks in dir + { + b = get_blk(fs, bk); + // for all dir entries in block + for(d = (directory*)b; (int8*)d + sizeof(*d) < (int8*)b + BLOCKSIZE; d = (directory*)((int8*)d + d->d_rec_len)) + { + // if empty dir entry, large enough, use it + if((!d->d_inode) && (d->d_rec_len >= reclen)) + { + d->d_inode = nod; + node = get_nod(fs, nod); + node->i_links_count++; + d->d_name_len = nlen; + strncpy(d->d_name, name, nlen); + return; + } + // if entry with enough room (last one?), shrink it & use it + if(d->d_rec_len >= (sizeof(directory) + rndup(d->d_name_len, 4) + reclen)) + { + reclen = d->d_rec_len; + d->d_rec_len = sizeof(directory) + rndup(d->d_name_len, 4); + reclen -= d->d_rec_len; + d = (directory*) (((int8*)d) + d->d_rec_len); + d->d_rec_len = reclen; + d->d_inode = nod; + node = get_nod(fs, nod); + node->i_links_count++; + d->d_name_len = nlen; + strncpy(d->d_name, name, nlen); + return; + } + } + } + // we found no free entry in the directory, so we add a block + if(!(b = get_workblk())) + error_msg_and_die("get_workblk() failed."); + d = (directory*)b; + d->d_inode = nod; + node = get_nod(fs, nod); + node->i_links_count++; + d->d_rec_len = BLOCKSIZE; + d->d_name_len = nlen; + strncpy(d->d_name, name, nlen); + extend_blk(fs, dnod, b, 1); + get_nod(fs, dnod)->i_size += BLOCKSIZE; + free_workblk(b); +} + +// find an entry in a directory +static uint32 +find_dir(filesystem *fs, uint32 nod, const char * name) +{ + blockwalker bw; + uint32 bk; + int nlen = strlen(name); + init_bw(&bw); + while((bk = walk_bw(fs, nod, &bw, 0, 0)) != WALK_END) + { + directory *d; + uint8 *b; + b = get_blk(fs, bk); + for(d = (directory*)b; (int8*)d + sizeof(*d) < (int8*)b + BLOCKSIZE; d = (directory*)((int8*)d + d->d_rec_len)) + if(d->d_inode && (nlen == d->d_name_len) && !strncmp(d->d_name, name, nlen)) + return d->d_inode; + } + return 0; +} + +// find the inode of a full path +static uint32 +find_path(filesystem *fs, uint32 nod, const char * name) +{ + char *p, *n, *n2 = xstrdup(name); + n = n2; + while(*n == '/') + { + nod = EXT2_ROOT_INO; + n++; + } + while(*n) + { + if((p = strchr(n, '/'))) + (*p) = 0; + if(!(nod = find_dir(fs, nod, n))) + break; + if(p) + n = p + 1; + else + break; + } + free(n2); + return nod; +} + +// chmod an inode +void +chmod_fs(filesystem *fs, uint32 nod, uint16 mode, uint16 uid, uint16 gid) +{ + inode *node; + node = get_nod(fs, nod); + node->i_mode = (node->i_mode & ~FM_IMASK) | (mode & FM_IMASK); + node->i_uid = uid; + node->i_gid = gid; +} + +// create a simple inode +static uint32 +mknod_fs(filesystem *fs, uint32 parent_nod, const char *name, uint16 mode, uint16 uid, uint16 gid, uint8 major, uint8 minor, uint32 ctime, uint32 mtime) +{ + uint32 nod; + inode *node; + { + nod = alloc_nod(fs); + node = get_nod(fs, nod); + node->i_mode = mode; + add2dir(fs, parent_nod, nod, name); + switch(mode & FM_IFMT) + { + case FM_IFLNK: + mode = FM_IFLNK | FM_IRWXU | FM_IRWXG | FM_IRWXO; + break; + case FM_IFBLK: + case FM_IFCHR: + ((uint8*)get_nod(fs, nod)->i_block)[0] = minor; + ((uint8*)get_nod(fs, nod)->i_block)[1] = major; + break; + case FM_IFDIR: + add2dir(fs, nod, nod, "."); + add2dir(fs, nod, parent_nod, ".."); + fs->gd[GRP_GROUP_OF_INODE(fs,nod)].bg_used_dirs_count++; + break; + } + } + node->i_uid = uid; + node->i_gid = gid; + node->i_atime = mtime; + node->i_ctime = ctime; + node->i_mtime = mtime; + return nod; +} + +// make a full-fledged directory (i.e. with "." & "..") +static inline uint32 +mkdir_fs(filesystem *fs, uint32 parent_nod, const char *name, uint32 mode, + uid_t uid, gid_t gid, uint32 ctime, uint32 mtime) +{ + return mknod_fs(fs, parent_nod, name, mode|FM_IFDIR, uid, gid, 0, 0, ctime, mtime); +} + +// make a symlink +static uint32 +mklink_fs(filesystem *fs, uint32 parent_nod, const char *name, size_t size, uint8 *b, uid_t uid, gid_t gid, uint32 ctime, uint32 mtime) +{ + uint32 nod = mknod_fs(fs, parent_nod, name, FM_IFLNK | FM_IRWXU | FM_IRWXG | FM_IRWXO, uid, gid, 0, 0, ctime, mtime); + extend_blk(fs, nod, 0, - (int)get_nod(fs, nod)->i_blocks / INOBLK); + get_nod(fs, nod)->i_size = size; + if(size <= 4 * (EXT2_TIND_BLOCK+1)) + { + strncpy((char*)get_nod(fs, nod)->i_block, (char*)b, size); + return nod; + } + extend_blk(fs, nod, b, rndup(size, BLOCKSIZE) / BLOCKSIZE); + return nod; +} + +// make a file from a FILE* +static uint32 +mkfile_fs(filesystem *fs, uint32 parent_nod, const char *name, uint32 mode, size_t size, FILE *f, uid_t uid, gid_t gid, uint32 ctime, uint32 mtime) +{ + uint8 * b; + uint32 nod = mknod_fs(fs, parent_nod, name, mode|FM_IFREG, uid, gid, 0, 0, ctime, mtime); + extend_blk(fs, nod, 0, - (int)get_nod(fs, nod)->i_blocks / INOBLK); + get_nod(fs, nod)->i_size = size; + if (size) { + if(!(b = (uint8*)calloc(rndup(size, BLOCKSIZE), 1))) + error_msg_and_die("not enough mem to read file '%s'", name); + if(f) + fread(b, size, 1, f); // FIXME: ugly. use mmap() ... + extend_blk(fs, nod, b, rndup(size, BLOCKSIZE) / BLOCKSIZE); + free(b); + } + return nod; +} + +// retrieves a mode info from a struct stat +static uint32 +get_mode(struct stat *st) +{ + uint32 mode = 0; + + if(st->st_mode & S_IRUSR) + mode |= FM_IRUSR; + if(st->st_mode & S_IWUSR) + mode |= FM_IWUSR; + if(st->st_mode & S_IXUSR) + mode |= FM_IXUSR; + if(st->st_mode & S_IRGRP) + mode |= FM_IRGRP; + if(st->st_mode & S_IWGRP) + mode |= FM_IWGRP; + if(st->st_mode & S_IXGRP) + mode |= FM_IXGRP; + if(st->st_mode & S_IROTH) + mode |= FM_IROTH; + if(st->st_mode & S_IWOTH) + mode |= FM_IWOTH; + if(st->st_mode & S_IXOTH) + mode |= FM_IXOTH; + if(st->st_mode & S_ISUID) + mode |= FM_ISUID; + if(st->st_mode & S_ISGID) + mode |= FM_ISGID; + if(st->st_mode & S_ISVTX) + mode |= FM_ISVTX; + return mode; +} + +// add or fixup entries to the filesystem from a text file +/* device table entries take the form of: + <path> <type> <mode> <uid> <gid> <major> <minor> <start> <inc> <count> + /dev/mem c 640 0 0 1 1 0 0 - + + type can be one of: + f A regular file + d Directory + c Character special device file + b Block special device file + p Fifo (named pipe) + + I don't bother with symlinks (permissions are irrelevant), hard + links (special cases of regular files), or sockets (why bother). + + Regular files must exist in the target root directory. If a char, + block, fifo, or directory does not exist, it will be created. +*/ + +static void +add2fs_from_file(filesystem *fs, uint32 this_nod, FILE * fh, uint32 fs_timestamp, struct stats *stats) +{ + unsigned long mode, uid, gid, major, minor; + unsigned long start, increment, count; + uint32 nod, ctime, mtime; + char *c, type, *path = NULL, *path2 = NULL, *dir, *name, *line = NULL; + size_t len; + struct stat st; + int nbargs, lineno = 0; + + fstat(fileno(fh), &st); + ctime = fs_timestamp; + mtime = st.st_mtime; + while(getline(&line, &len, fh) >= 0) + { + mode = uid = gid = major = minor = 0; + start = 0; increment = 1; count = 0; + lineno++; + if((c = strchr(line, '#'))) + *c = 0; + if (path) { + free(path); + path = NULL; + } + if (path2) { + free(path2); + path2 = NULL; + } + nbargs = sscanf (line, "%" SCANF_PREFIX "s %c %lo %lu %lu %lu %lu %lu %lu %lu", + SCANF_STRING(path), &type, &mode, &uid, &gid, &major, &minor, + &start, &increment, &count); + if(nbargs < 3) + { + if(nbargs > 0) + error_msg("device table line %d skipped: bad format for entry '%s'", lineno, path); + continue; + } + mode &= FM_IMASK; + path2 = strdup(path); + name = basename(path); + dir = dirname(path2); + if((!strcmp(name, ".")) || (!strcmp(name, ".."))) + { + error_msg("device table line %d skipped", lineno); + continue; + } + if(fs) + { + if(!(nod = find_path(fs, this_nod, dir))) + { + error_msg("device table line %d skipped: can't find directory '%s' to create '%s''", lineno, dir, name); + continue; + } + } + else + nod = 0; + switch (type) + { + case 'd': + mode |= FM_IFDIR; + break; + case 'f': + mode |= FM_IFREG; + break; + case 'p': + mode |= FM_IFIFO; + break; + case 's': + mode |= FM_IFSOCK; + break; + case 'c': + mode |= FM_IFCHR; + break; + case 'b': + mode |= FM_IFBLK; + break; + default: + error_msg("device table line %d skipped: bad type '%c' for entry '%s'", lineno, type, name); + continue; + } + if(stats) { + if(count > 0) + stats->ninodes += count - start; + else + stats->ninodes++; + } else { + if(count > 0) + { + char *dname; + unsigned long i; + unsigned len; + len = strlen(name) + 10; + dname = malloc(len + 1); + for(i = start; i < count; i++) + { + uint32 oldnod; + SNPRINTF(dname, len, "%s%lu", name, i); + oldnod = find_dir(fs, nod, dname); + if(oldnod) + chmod_fs(fs, oldnod, mode, uid, gid); + else + mknod_fs(fs, nod, dname, mode, uid, gid, major, minor + (i * increment - start), ctime, mtime); + } + free(dname); + } + else + { + uint32 oldnod = find_dir(fs, nod, name); + if(oldnod) + chmod_fs(fs, oldnod, mode, uid, gid); + else + mknod_fs(fs, nod, name, mode, uid, gid, major, minor, ctime, mtime); + } + } + } + if (line) + free(line); + if (path) + free(path); + if (path2) + free(path2); +} + +static void +prep_stat(const char *root_path) +{ + int len = strlen(root_path); + + if((len >= 4) && (!strcmp(root_path + len - 4, "data"))) { + source_path_len = len - 4; + } else if((len >= 7) && (!strcmp(root_path + len - 6, "system"))) { + source_path_len = len - 6; + } else { + error_msg_and_die("Fixstats (-a) option requested but " + "filesystem is not data or android!"); + } +} + +static void +fix_stat(const char *path, struct stat *s) +{ + path += source_path_len; + fs_config(path, S_ISDIR(s->st_mode), &s->st_uid, &s->st_gid, &s->st_mode); +} + +// adds a tree of entries to the filesystem from current dir +static void +add2fs_from_dir(filesystem *fs, const char *path, uint32 this_nod, int squash_uids, int squash_perms, int fixstats, uint32 fs_timestamp, struct stats *stats) +{ + uint32 nod; + uint32 uid, gid, mode, ctime, mtime; + const char *name; + FILE *fh; + DIR *dh; + struct dirent *dent; + struct stat st; + char *lnk; + uint32 save_nod; + char full_name[2048]; + + if(!(dh = opendir("."))) + perror_msg_and_die("."); + while((dent = readdir(dh))) + { + if((!strcmp(dent->d_name, ".")) || (!strcmp(dent->d_name, ".."))) + continue; + + lstat(dent->d_name, &st); + + if(fixstats) { + int tmp = snprintf(full_name, sizeof(full_name), + "%s/%s", path, dent->d_name); + if(tmp >= (int)sizeof(full_name)) + error_msg_and_die("Path too long!"); + fix_stat(full_name, &st); + } else + full_name[0] = '\0'; + uid = st.st_uid; + gid = st.st_gid; + ctime = fs_timestamp; + mtime = st.st_mtime; + name = dent->d_name; + mode = get_mode(&st); + if(squash_uids) + uid = gid = 0; + if(squash_perms) + mode &= ~(FM_IRWXG | FM_IRWXO); + if(stats) + switch(st.st_mode & S_IFMT) + { + case S_IFLNK: + case S_IFREG: + if((st.st_mode & S_IFMT) == S_IFREG || st.st_size > 4 * (EXT2_TIND_BLOCK+1)) + stats->nblocks += (st.st_size + BLOCKSIZE - 1) / BLOCKSIZE; + case S_IFCHR: + case S_IFBLK: + case S_IFIFO: + case S_IFSOCK: + stats->ninodes++; + break; + case S_IFDIR: + stats->ninodes++; + if(chdir(dent->d_name) < 0) + perror_msg_and_die(dent->d_name); + add2fs_from_dir(fs, full_name, this_nod, squash_uids, squash_perms, fixstats, fs_timestamp, stats); + chdir(".."); + break; + default: + break; + } + else + { + if((nod = find_dir(fs, this_nod, name))) + { + error_msg("ignoring duplicate entry %s", name); + if(S_ISDIR(st.st_mode)) { + if(chdir(dent->d_name) < 0) + perror_msg_and_die(name); + add2fs_from_dir(fs, full_name, nod, squash_uids, squash_perms, fixstats, fs_timestamp, stats); + chdir(".."); + } + continue; + } + save_nod = 0; + /* Check for hardlinks */ + if (!S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode) && st.st_nlink > 1) { + int32 hdlink = is_hardlink(st.st_ino); + if (hdlink >= 0) { + add2dir(fs, this_nod, hdlinks.hdl[hdlink].dst_nod, name); + continue; + } else { + save_nod = 1; + } + } + switch(st.st_mode & S_IFMT) + { +#if HAVE_STRUCT_STAT_ST_RDEV + case S_IFCHR: + nod = mknod_fs(fs, this_nod, name, mode|FM_IFCHR, uid, gid, major(st.st_rdev), minor(st.st_rdev), ctime, mtime); + break; + case S_IFBLK: + nod = mknod_fs(fs, this_nod, name, mode|FM_IFBLK, uid, gid, major(st.st_rdev), minor(st.st_rdev), ctime, mtime); + break; +#endif + case S_IFIFO: + nod = mknod_fs(fs, this_nod, name, mode|FM_IFIFO, uid, gid, 0, 0, ctime, mtime); + break; + case S_IFSOCK: + nod = mknod_fs(fs, this_nod, name, mode|FM_IFSOCK, uid, gid, 0, 0, ctime, mtime); + break; + case S_IFLNK: + lnk = xreadlink(dent->d_name); + mklink_fs(fs, this_nod, name, st.st_size, (uint8*)lnk, uid, gid, ctime, mtime); + free(lnk); + break; + case S_IFREG: + fh = xfopen(dent->d_name, "rb"); + nod = mkfile_fs(fs, this_nod, name, mode, st.st_size, fh, uid, gid, ctime, mtime); + fclose(fh); + break; + case S_IFDIR: + nod = mkdir_fs(fs, this_nod, name, mode, uid, gid, ctime, mtime); + if(chdir(dent->d_name) < 0) + perror_msg_and_die(name); + add2fs_from_dir(fs, full_name, nod, squash_uids, squash_perms, fixstats, fs_timestamp, stats); + chdir(".."); + break; + default: + error_msg("ignoring entry %s", name); + } + if (save_nod) { + if (hdlinks.count == hdlink_cnt) { + if ((hdlinks.hdl = + realloc (hdlinks.hdl, (hdlink_cnt + HDLINK_CNT) * + sizeof (struct hdlink_s))) == NULL) { + error_msg_and_die("Not enough memory"); + } + hdlink_cnt += HDLINK_CNT; + } + hdlinks.hdl[hdlinks.count].src_inode = st.st_ino; + hdlinks.hdl[hdlinks.count].dst_nod = nod; + hdlinks.count++; + } + } + } + closedir(dh); +} + +// endianness swap of x-indirect blocks +static void +swap_goodblocks(filesystem *fs, inode *nod) +{ + uint32 i,j; + int done=0; + uint32 *b,*b2; + + uint32 nblk = nod->i_blocks / INOBLK; + if((nod->i_size && !nblk) || ((nod->i_mode & FM_IFBLK) == FM_IFBLK) || ((nod->i_mode & FM_IFCHR) == FM_IFCHR)) + for(i = 0; i <= EXT2_TIND_BLOCK; i++) + nod->i_block[i] = swab32(nod->i_block[i]); + if(nblk <= EXT2_IND_BLOCK) + return; + swap_block(get_blk(fs, nod->i_block[EXT2_IND_BLOCK])); + if(nblk <= EXT2_DIND_BLOCK + BLOCKSIZE/4) + return; + /* Currently this will fail b'cos the number of blocks as stored + in i_blocks also includes the indirection blocks (see + walk_bw). But this function assumes that i_blocks only + stores the count of data blocks ( Actually according to + "Understanding the Linux Kernel" (Table 17-3 p502 1st Ed) + i_blocks IS supposed to store the count of data blocks). so + with a file of size 268K nblk would be 269.The above check + will be false even though double indirection hasn't been + started.This is benign as 0 means block 0 which has been + zeroed out and therefore points back to itself from any offset + */ + // FIXME: I have fixed that, but I have the feeling the rest of + // ths function needs to be fixed for the same reasons - Xav + assert(nod->i_block[EXT2_DIND_BLOCK] != 0); + for(i = 0; i < BLOCKSIZE/4; i++) + if(nblk > EXT2_IND_BLOCK + BLOCKSIZE/4 + (BLOCKSIZE/4)*i ) + swap_block(get_blk(fs, ((uint32*)get_blk(fs, nod->i_block[EXT2_DIND_BLOCK]))[i])); + swap_block(get_blk(fs, nod->i_block[EXT2_DIND_BLOCK])); + if(nblk <= EXT2_IND_BLOCK + BLOCKSIZE/4 + BLOCKSIZE/4 * BLOCKSIZE/4) + return; + /* Adding support for triple indirection */ + b = (uint32*)get_blk(fs,nod->i_block[EXT2_TIND_BLOCK]); + for(i=0;i < BLOCKSIZE/4 && !done ; i++) { + b2 = (uint32*)get_blk(fs,b[i]); + for(j=0; j<BLOCKSIZE/4;j++) { + if (nblk > ( EXT2_IND_BLOCK + BLOCKSIZE/4 + + (BLOCKSIZE/4)*(BLOCKSIZE/4) + + i*(BLOCKSIZE/4)*(BLOCKSIZE/4) + + j*(BLOCKSIZE/4)) ) + swap_block(get_blk(fs,b2[j])); + else { + done = 1; + break; + } + } + swap_block((uint8 *)b2); + } + swap_block((uint8 *)b); + return; +} + +static void +swap_badblocks(filesystem *fs, inode *nod) +{ + uint32 i,j; + int done=0; + uint32 *b,*b2; + + uint32 nblk = nod->i_blocks / INOBLK; + if((nod->i_size && !nblk) || ((nod->i_mode & FM_IFBLK) == FM_IFBLK) || ((nod->i_mode & FM_IFCHR) == FM_IFCHR)) + for(i = 0; i <= EXT2_TIND_BLOCK; i++) + nod->i_block[i] = swab32(nod->i_block[i]); + if(nblk <= EXT2_IND_BLOCK) + return; + swap_block(get_blk(fs, nod->i_block[EXT2_IND_BLOCK])); + if(nblk <= EXT2_DIND_BLOCK + BLOCKSIZE/4) + return; + /* See comment in swap_goodblocks */ + assert(nod->i_block[EXT2_DIND_BLOCK] != 0); + swap_block(get_blk(fs, nod->i_block[EXT2_DIND_BLOCK])); + for(i = 0; i < BLOCKSIZE/4; i++) + if(nblk > EXT2_IND_BLOCK + BLOCKSIZE/4 + (BLOCKSIZE/4)*i ) + swap_block(get_blk(fs, ((uint32*)get_blk(fs, nod->i_block[EXT2_DIND_BLOCK]))[i])); + if(nblk <= EXT2_IND_BLOCK + BLOCKSIZE/4 + BLOCKSIZE/4 * BLOCKSIZE/4) + return; + /* Adding support for triple indirection */ + b = (uint32*)get_blk(fs,nod->i_block[EXT2_TIND_BLOCK]); + swap_block((uint8 *)b); + for(i=0;i < BLOCKSIZE/4 && !done ; i++) { + b2 = (uint32*)get_blk(fs,b[i]); + swap_block((uint8 *)b2); + for(j=0; j<BLOCKSIZE/4;j++) { + if (nblk > ( EXT2_IND_BLOCK + BLOCKSIZE/4 + + (BLOCKSIZE/4)*(BLOCKSIZE/4) + + i*(BLOCKSIZE/4)*(BLOCKSIZE/4) + + j*(BLOCKSIZE/4)) ) + swap_block(get_blk(fs,b2[j])); + else { + done = 1; + break; + } + } + } + return; +} + +// endianness swap of the whole filesystem +static void +swap_goodfs(filesystem *fs) +{ + uint32 i; + for(i = 1; i < fs->sb.s_inodes_count; i++) + { + inode *nod = get_nod(fs, i); + if(nod->i_mode & FM_IFDIR) + { + blockwalker bw; + uint32 bk; + init_bw(&bw); + while((bk = walk_bw(fs, i, &bw, 0, 0)) != WALK_END) + { + directory *d; + uint8 *b; + b = get_blk(fs, bk); + for(d = (directory*)b; (int8*)d + sizeof(*d) < (int8*)b + BLOCKSIZE; d = (directory*)((int8*)d + swab16(d->d_rec_len))) + swap_dir(d); + } + } + swap_goodblocks(fs, nod); + swap_nod(nod); + } + for(i=0;i<GRP_NBGROUPS(fs);i++) + swap_gd(&(fs->gd[i])); + swap_sb(&fs->sb); +} + +static void +swap_badfs(filesystem *fs) +{ + uint32 i; + swap_sb(&fs->sb); + for(i=0;i<GRP_NBGROUPS(fs);i++) + swap_gd(&(fs->gd[i])); + for(i = 1; i < fs->sb.s_inodes_count; i++) + { + inode *nod = get_nod(fs, i); + swap_nod(nod); + swap_badblocks(fs, nod); + if(nod->i_mode & FM_IFDIR) + { + blockwalker bw; + uint32 bk; + init_bw(&bw); + while((bk = walk_bw(fs, i, &bw, 0, 0)) != WALK_END) + { + directory *d; + uint8 *b; + b = get_blk(fs, bk); + for(d = (directory*)b; (int8*)d + sizeof(*d) < (int8*)b + BLOCKSIZE; d = (directory*)((int8*)d + d->d_rec_len)) + swap_dir(d); + } + } + } +} + +// initialize an empty filesystem +static filesystem * +init_fs(int nbblocks, int nbinodes, int nbresrvd, int holes, uint32 fs_timestamp) +{ + uint32 i; + filesystem *fs; + directory *d; + uint8 * b; + uint32 nod, first_block; + uint32 nbgroups,nbinodes_per_group,overhead_per_group,free_blocks, + free_blocks_per_group,nbblocks_per_group,min_nbgroups; + uint32 gdsz,itblsz,bbmpos,ibmpos,itblpos; + uint32 j; + uint8 *bbm,*ibm; + inode *itab0; + + if(nbresrvd < 0) + error_msg_and_die("reserved blocks value is invalid. Note: options have changed, see --help or the man page."); + if(nbinodes < EXT2_FIRST_INO - 1 + (nbresrvd ? 1 : 0)) + error_msg_and_die("too few inodes. Note: options have changed, see --help or the man page."); + if(nbblocks < 8) + error_msg_and_die("too few blocks. Note: options have changed, see --help or the man page."); + + /* nbinodes is the total number of inodes in the system. + * a block group can have no more than 8192 inodes. + */ + min_nbgroups = (nbinodes + INODES_PER_GROUP - 1) / INODES_PER_GROUP; + + /* nbblocks is the total number of blocks in the filesystem. + * a block group can have no more than 8192 blocks. + */ + first_block = (BLOCKSIZE == 1024); + nbgroups = (nbblocks - first_block + BLOCKS_PER_GROUP - 1) / BLOCKS_PER_GROUP; + if(nbgroups < min_nbgroups) nbgroups = min_nbgroups; + nbblocks_per_group = rndup((nbblocks - first_block + nbgroups - 1)/nbgroups, 8); + nbinodes_per_group = rndup((nbinodes + nbgroups - 1)/nbgroups, + (BLOCKSIZE/sizeof(inode))); + if (nbinodes_per_group < 16) + nbinodes_per_group = 16; //minimum number b'cos the first 10 are reserved + + gdsz = rndup(nbgroups*sizeof(groupdescriptor),BLOCKSIZE)/BLOCKSIZE; + itblsz = nbinodes_per_group * sizeof(inode)/BLOCKSIZE; + overhead_per_group = 3 /*sb,bbm,ibm*/ + gdsz + itblsz; + if((uint32)nbblocks - 1 < overhead_per_group * nbgroups) + error_msg_and_die("too much overhead, try fewer inodes or more blocks. Note: options have changed, see --help or the man page."); + free_blocks = nbblocks - overhead_per_group*nbgroups - 1 /*boot block*/; + free_blocks_per_group = nbblocks_per_group - overhead_per_group; + + if(!(fs = (filesystem*)calloc(nbblocks, BLOCKSIZE))) + error_msg_and_die("not enough memory for filesystem"); + + // create the superblock for an empty filesystem + fs->sb.s_inodes_count = nbinodes_per_group * nbgroups; + fs->sb.s_blocks_count = nbblocks; + fs->sb.s_r_blocks_count = nbresrvd; + fs->sb.s_free_blocks_count = free_blocks; + fs->sb.s_free_inodes_count = fs->sb.s_inodes_count - EXT2_FIRST_INO + 1; + fs->sb.s_first_data_block = first_block; + fs->sb.s_log_block_size = BLOCKSIZE >> 11; + fs->sb.s_log_frag_size = BLOCKSIZE >> 11; + fs->sb.s_blocks_per_group = nbblocks_per_group; + fs->sb.s_frags_per_group = nbblocks_per_group; + fs->sb.s_inodes_per_group = nbinodes_per_group; + fs->sb.s_wtime = fs_timestamp; + fs->sb.s_magic = EXT2_MAGIC_NUMBER; + fs->sb.s_lastcheck = fs_timestamp; + + // set up groupdescriptors + for(i=0, bbmpos=gdsz+2, ibmpos=bbmpos+1, itblpos=ibmpos+1; + i<nbgroups; + i++, bbmpos+=nbblocks_per_group, ibmpos+=nbblocks_per_group, itblpos+=nbblocks_per_group) + { + if(free_blocks > free_blocks_per_group) { + fs->gd[i].bg_free_blocks_count = free_blocks_per_group; + free_blocks -= free_blocks_per_group; + } else { + fs->gd[i].bg_free_blocks_count = free_blocks; + free_blocks = 0; // this is the last block group + } + if(i) + fs->gd[i].bg_free_inodes_count = nbinodes_per_group; + else + fs->gd[i].bg_free_inodes_count = nbinodes_per_group - + EXT2_FIRST_INO + 2; + fs->gd[i].bg_used_dirs_count = 0; + fs->gd[i].bg_block_bitmap = bbmpos; + fs->gd[i].bg_inode_bitmap = ibmpos; + fs->gd[i].bg_inode_table = itblpos; + } + + /* Mark non-filesystem blocks and inodes as allocated */ + /* Mark system blocks and inodes as allocated */ + for(i = 0; i<nbgroups;i++) { + + /* Block bitmap */ + bbm = get_blk(fs,fs->gd[i].bg_block_bitmap); + //non-filesystem blocks + for(j = fs->gd[i].bg_free_blocks_count + + overhead_per_group + 1; j <= BLOCKSIZE * 8; j++) + allocate(bbm, j); + //system blocks + for(j = 1; j <= overhead_per_group; j++) + allocate(bbm, j); + + /* Inode bitmap */ + ibm = get_blk(fs,fs->gd[i].bg_inode_bitmap); + //non-filesystem inodes + for(j = fs->sb.s_inodes_per_group+1; j <= BLOCKSIZE * 8; j++) + allocate(ibm, j); + + //system inodes + if(i == 0) + for(j = 1; j < EXT2_FIRST_INO; j++) + allocate(ibm, j); + } + + // make root inode and directory + /* We have groups now. Add the root filesystem in group 0 */ + /* Also increment the directory count for group 0 */ + fs->gd[0].bg_free_inodes_count--; + fs->gd[0].bg_used_dirs_count = 1; + itab0 = (inode *)get_blk(fs,fs->gd[0].bg_inode_table); + itab0[EXT2_ROOT_INO-1].i_mode = FM_IFDIR | FM_IRWXU | FM_IRGRP | FM_IROTH | FM_IXGRP | FM_IXOTH; + itab0[EXT2_ROOT_INO-1].i_ctime = fs_timestamp; + itab0[EXT2_ROOT_INO-1].i_mtime = fs_timestamp; + itab0[EXT2_ROOT_INO-1].i_atime = fs_timestamp; + itab0[EXT2_ROOT_INO-1].i_size = BLOCKSIZE; + itab0[EXT2_ROOT_INO-1].i_links_count = 2; + + if(!(b = get_workblk())) + error_msg_and_die("get_workblk() failed."); + d = (directory*)b; + d->d_inode = EXT2_ROOT_INO; + d->d_rec_len = sizeof(directory)+4; + d->d_name_len = 1; + strcpy(d->d_name, "."); + d = (directory*)(b + d->d_rec_len); + d->d_inode = EXT2_ROOT_INO; + d->d_rec_len = BLOCKSIZE - (sizeof(directory)+4); + d->d_name_len = 2; + strcpy(d->d_name, ".."); + extend_blk(fs, EXT2_ROOT_INO, b, 1); + + // make lost+found directory and reserve blocks + if(fs->sb.s_r_blocks_count) + { + nod = mkdir_fs(fs, EXT2_ROOT_INO, "lost+found", FM_IRWXU, 0, 0, fs_timestamp, fs_timestamp); + memset(b, 0, BLOCKSIZE); + ((directory*)b)->d_rec_len = BLOCKSIZE; + /* We run into problems with e2fsck if directory lost+found grows + * bigger than this. Need to find out why this happens - sundar + */ + if (fs->sb.s_r_blocks_count > fs->sb.s_blocks_count * MAX_RESERVED_BLOCKS ) + fs->sb.s_r_blocks_count = fs->sb.s_blocks_count * MAX_RESERVED_BLOCKS; + for(i = 1; i < fs->sb.s_r_blocks_count; i++) + extend_blk(fs, nod, b, 1); + get_nod(fs, nod)->i_size = fs->sb.s_r_blocks_count * BLOCKSIZE; + } + free_workblk(b); + + // administrative info + fs->sb.s_state = 1; + fs->sb.s_max_mnt_count = 20; + + // options for me + if(holes) + fs->sb.s_reserved[200] |= OP_HOLES; + + return fs; +} + +// loads a filesystem from disk +static filesystem * +load_fs(FILE * fh, int swapit) +{ + size_t fssize; + filesystem *fs; + if((fseek(fh, 0, SEEK_END) < 0) || ((ssize_t)(fssize = ftell(fh)) == -1)) + perror_msg_and_die("input filesystem image"); + rewind(fh); + fssize = (fssize + BLOCKSIZE - 1) / BLOCKSIZE; + if(fssize < 16) // totally arbitrary + error_msg_and_die("too small filesystem"); + if(!(fs = (filesystem*)calloc(fssize, BLOCKSIZE))) + error_msg_and_die("not enough memory for filesystem"); + if(fread(fs, BLOCKSIZE, fssize, fh) != fssize) + perror_msg_and_die("input filesystem image"); + if(swapit) + swap_badfs(fs); + if(fs->sb.s_rev_level || (fs->sb.s_magic != EXT2_MAGIC_NUMBER)) + error_msg_and_die("not a suitable ext2 filesystem"); + return fs; +} + +static void +free_fs(filesystem *fs) +{ + free(fs); +} + +// just walk through blocks list +static void +flist_blocks(filesystem *fs, uint32 nod, FILE *fh) +{ + blockwalker bw; + uint32 bk; + init_bw(&bw); + while((bk = walk_bw(fs, nod, &bw, 0, 0)) != WALK_END) + fprintf(fh, " %d", bk); + fprintf(fh, "\n"); +} + +// walk through blocks list +static void +list_blocks(filesystem *fs, uint32 nod) +{ + int bn = 0; + blockwalker bw; + uint32 bk; + init_bw(&bw); + printf("blocks in inode %d:", nod); + while((bk = walk_bw(fs, nod, &bw, 0, 0)) != WALK_END) + printf(" %d", bk), bn++; + printf("\n%d blocks (%d bytes)\n", bn, bn * BLOCKSIZE); +} + +// saves blocks to FILE* +static void +write_blocks(filesystem *fs, uint32 nod, FILE* f) +{ + blockwalker bw; + uint32 bk; + int32 fsize = get_nod(fs, nod)->i_size; + init_bw(&bw); + while((bk = walk_bw(fs, nod, &bw, 0, 0)) != WALK_END) + { + if(fsize <= 0) + error_msg_and_die("wrong size while saving inode %d", nod); + if(fwrite(get_blk(fs, bk), (fsize > BLOCKSIZE) ? BLOCKSIZE : fsize, 1, f) != 1) + error_msg_and_die("error while saving inode %d", nod); + fsize -= BLOCKSIZE; + } +} + + +// print block/char device minor and major +static void +print_dev(filesystem *fs, uint32 nod) +{ + int minor, major; + minor = ((uint8*)get_nod(fs, nod)->i_block)[0]; + major = ((uint8*)get_nod(fs, nod)->i_block)[1]; + printf("major: %d, minor: %d\n", major, minor); +} + +// print an inode as a directory +static void +print_dir(filesystem *fs, uint32 nod) +{ + blockwalker bw; + uint32 bk; + init_bw(&bw); + printf("directory for inode %d:\n", nod); + while((bk = walk_bw(fs, nod, &bw, 0, 0)) != WALK_END) + { + directory *d; + uint8 *b; + b = get_blk(fs, bk); + for(d = (directory*)b; (int8*)d + sizeof(*d) < (int8*)b + BLOCKSIZE; d = (directory*)((int8*)d + d->d_rec_len)) + if(d->d_inode) + { + int i; + printf("entry '"); + for(i = 0; i < d->d_name_len; i++) + putchar(d->d_name[i]); + printf("' (inode %d): rec_len: %d (name_len: %d)\n", d->d_inode, d->d_rec_len, d->d_name_len); + } + } +} + +// print a symbolic link +static void +print_link(filesystem *fs, uint32 nod) +{ + if(!get_nod(fs, nod)->i_blocks) + printf("links to '%s'\n", (char*)get_nod(fs, nod)->i_block); + else + { + printf("links to '"); + write_blocks(fs, nod, stdout); + printf("'\n"); + } +} + +// make a ls-like printout of permissions +static void +make_perms(uint32 mode, char perms[11]) +{ + strcpy(perms, "----------"); + if(mode & FM_IRUSR) + perms[1] = 'r'; + if(mode & FM_IWUSR) + perms[2] = 'w'; + if(mode & FM_IXUSR) + perms[3] = 'x'; + if(mode & FM_IRGRP) + perms[4] = 'r'; + if(mode & FM_IWGRP) + perms[5] = 'w'; + if(mode & FM_IXGRP) + perms[6] = 'x'; + if(mode & FM_IROTH) + perms[7] = 'r'; + if(mode & FM_IWOTH) + perms[8] = 'w'; + if(mode & FM_IXOTH) + perms[9] = 'x'; + if(mode & FM_ISUID) + perms[3] = 's'; + if(mode & FM_ISGID) + perms[6] = 's'; + if(mode & FM_ISVTX) + perms[9] = 't'; + switch(mode & FM_IFMT) + { + case 0: + *perms = '0'; + break; + case FM_IFSOCK: + *perms = 's'; + break; + case FM_IFLNK: + *perms = 'l'; + break; + case FM_IFREG: + *perms = '-'; + break; + case FM_IFBLK: + *perms = 'b'; + break; + case FM_IFDIR: + *perms = 'd'; + break; + case FM_IFCHR: + *perms = 'c'; + break; + case FM_IFIFO: + *perms = 'p'; + break; + default: + *perms = '?'; + } +} + +// print an inode +static void +print_inode(filesystem *fs, uint32 nod) +{ + char *s; + char perms[11]; + if(!get_nod(fs, nod)->i_mode) + return; + switch(nod) + { + case EXT2_BAD_INO: + s = "bad blocks"; + break; + case EXT2_ROOT_INO: + s = "root"; + break; + case EXT2_ACL_IDX_INO: + case EXT2_ACL_DATA_INO: + s = "ACL"; + break; + case EXT2_BOOT_LOADER_INO: + s = "boot loader"; + break; + case EXT2_UNDEL_DIR_INO: + s = "undelete directory"; + break; + default: + s = (nod >= EXT2_FIRST_INO) ? "normal" : "unknown reserved"; + } + printf("inode %d (%s, %d links): ", nod, s, get_nod(fs, nod)->i_links_count); + if(!allocated(GRP_GET_INODE_BITMAP(fs,nod), GRP_IBM_OFFSET(fs,nod))) + { + printf("unallocated\n"); + return; + } + make_perms(get_nod(fs, nod)->i_mode, perms); + printf("%s, size: %d byte%s (%d block%s)\n", perms, plural(get_nod(fs, nod)->i_size), plural(get_nod(fs, nod)->i_blocks / INOBLK)); + switch(get_nod(fs, nod)->i_mode & FM_IFMT) + { + case FM_IFSOCK: + list_blocks(fs, nod); + break; + case FM_IFLNK: + print_link(fs, nod); + break; + case FM_IFREG: + list_blocks(fs, nod); + break; + case FM_IFBLK: + print_dev(fs, nod); + break; + case FM_IFDIR: + list_blocks(fs, nod); + print_dir(fs, nod); + break; + case FM_IFCHR: + print_dev(fs, nod); + break; + case FM_IFIFO: + list_blocks(fs, nod); + break; + default: + list_blocks(fs, nod); + } + printf("Done with inode %d\n",nod); +} + +// describes various fields in a filesystem +static void +print_fs(filesystem *fs) +{ + uint32 i; + uint8 *ibm; + + printf("%d blocks (%d free, %d reserved), first data block: %d\n", + fs->sb.s_blocks_count, fs->sb.s_free_blocks_count, + fs->sb.s_r_blocks_count, fs->sb.s_first_data_block); + printf("%d inodes (%d free)\n", fs->sb.s_inodes_count, + fs->sb.s_free_inodes_count); + printf("block size = %d, frag size = %d\n", + fs->sb.s_log_block_size ? (fs->sb.s_log_block_size << 11) : 1024, + fs->sb.s_log_frag_size ? (fs->sb.s_log_frag_size << 11) : 1024); + printf("number of groups: %d\n",GRP_NBGROUPS(fs)); + printf("%d blocks per group,%d frags per group,%d inodes per group\n", + fs->sb.s_blocks_per_group, fs->sb.s_frags_per_group, + fs->sb.s_inodes_per_group); + printf("Size of inode table: %d blocks\n", + (int)(fs->sb.s_inodes_per_group * sizeof(inode) / BLOCKSIZE)); + for (i = 0; i < GRP_NBGROUPS(fs); i++) { + printf("Group No: %d\n", i+1); + printf("block bitmap: block %d,inode bitmap: block %d, inode table: block %d\n", + fs->gd[i].bg_block_bitmap, fs->gd[i].bg_inode_bitmap, + fs->gd[i].bg_inode_table); + printf("block bitmap allocation:\n"); + print_bm(GRP_GET_GROUP_BBM(fs, i),fs->sb.s_blocks_per_group); + printf("inode bitmap allocation:\n"); + ibm = GRP_GET_GROUP_IBM(fs, i); + print_bm(ibm, fs->sb.s_inodes_per_group); + for (i = 1; i <= fs->sb.s_inodes_per_group; i++) + if (allocated(ibm, i)) + print_inode(fs, i); + } +} + +static void +dump_fs(filesystem *fs, FILE * fh, int swapit) +{ + uint32 nbblocks = fs->sb.s_blocks_count; + fs->sb.s_reserved[200] = 0; + if(swapit) + swap_goodfs(fs); + if(fwrite(fs, BLOCKSIZE, nbblocks, fh) < nbblocks) + perror_msg_and_die("output filesystem image"); + if(swapit) + swap_badfs(fs); +} + +static void +populate_fs(filesystem *fs, char **dopt, int didx, int squash_uids, int squash_perms, int fixstats, uint32 fs_timestamp, struct stats *stats) +{ + int i; + for(i = 0; i < didx; i++) + { + struct stat st; + FILE *fh; + int pdir; + char *pdest; + uint32 nod = EXT2_ROOT_INO; + if(fs) + if((pdest = strchr(dopt[i], ':'))) + { + *(pdest++) = 0; + if(!(nod = find_path(fs, EXT2_ROOT_INO, pdest))) + error_msg_and_die("path %s not found in filesystem", pdest); + } + stat(dopt[i], &st); + switch(st.st_mode & S_IFMT) + { + case S_IFREG: + fh = xfopen(dopt[i], "rb"); + add2fs_from_file(fs, nod, fh, fs_timestamp, stats); + fclose(fh); + break; + case S_IFDIR: + if((pdir = open(".", O_RDONLY)) < 0) + perror_msg_and_die("."); + if(chdir(dopt[i]) < 0) + perror_msg_and_die(dopt[i]); + if (fixstats) + prep_stat(dopt[i]); + add2fs_from_dir(fs, dopt[i], nod, squash_uids, squash_perms, fixstats, fs_timestamp, stats); + if(fchdir(pdir) < 0) + perror_msg_and_die("fchdir"); + if(close(pdir) < 0) + perror_msg_and_die("close"); + break; + default: + error_msg_and_die("%s is neither a file nor a directory", dopt[i]); + } + } +} + +static void +showversion(void) +{ + printf("genext2fs " VERSION "\n"); +} + +static void +showhelp(void) +{ + fprintf(stderr, "Usage: %s [options] image\n" + "Create an ext2 filesystem image from directories/files\n\n" + " -x, --starting-image <image>\n" + " -d, --root <directory>\n" + " -D, --devtable <file>\n" + " -b, --size-in-blocks <blocks>\n" + " -i, --bytes-per-inode <bytes per inode>\n" + " -N, --number-of-inodes <number of inodes>\n" + " -m, --reserved-percentage <percentage of blocks to reserve>\n" + " -g, --block-map <path> Generate a block map file for this path.\n" + " -e, --fill-value <value> Fill unallocated blocks with value.\n" + " -z, --allow-holes Allow files with holes.\n" + " -f, --faketime Set filesystem timestamps to 0 (for testing).\n" + " -q, --squash Same as \"-U -P\".\n" + " -U, --squash-uids Squash owners making all files be owned by root.\n" + " -P, --squash-perms Squash permissions on all files.\n" + " -a, --fix-android-stats Fix-up file stats (user, perms, ...)\n" + " -h, --help\n" + " -V, --version\n" + " -v, --verbose\n\n" + "Report bugs to genext2fs-devel@lists.sourceforge.net\n", app_name); +} + +#define MAX_DOPT 128 +#define MAX_GOPT 128 + +#define MAX_FILENAME 255 + +extern char* optarg; +extern int optind, opterr, optopt; + +int +main(int argc, char **argv) +{ + int nbblocks = -1; + int nbinodes = -1; + int nbresrvd = -1; + float bytes_per_inode = -1; + float reserved_frac = -1; + int fs_timestamp = -1; + char * fsout = "-"; + char * fsin = 0; + char * dopt[MAX_DOPT]; + int didx = 0; + char * gopt[MAX_GOPT]; + int gidx = 0; + int verbose = 0; + int holes = 0; + int emptyval = 0; + int squash_uids = 0; + int squash_perms = 0; + int fix_android_stats = 0; + uint16 endian = 1; + int bigendian = !*(char*)&endian; + filesystem *fs; + int i; + int c; + struct stats stats; + +#if HAVE_GETOPT_LONG + struct option longopts[] = { + { "starting-image", required_argument, NULL, 'x' }, + { "root", required_argument, NULL, 'd' }, + { "devtable", required_argument, NULL, 'D' }, + { "size-in-blocks", required_argument, NULL, 'b' }, + { "bytes-per-inode", required_argument, NULL, 'i' }, + { "number-of-inodes", required_argument, NULL, 'N' }, + { "reserved-percentage", required_argument, NULL, 'm' }, + { "block-map", required_argument, NULL, 'g' }, + { "fill-value", required_argument, NULL, 'e' }, + { "allow-holes", no_argument, NULL, 'z' }, + { "faketime", no_argument, NULL, 'f' }, + { "squash", no_argument, NULL, 'q' }, + { "squash-uids", no_argument, NULL, 'U' }, + { "squash-perms", no_argument, NULL, 'P' }, + { "fix-android-stats",no_argument, NULL, 'a' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "verbose", no_argument, NULL, 'v' }, + { 0, 0, 0, 0} + } ; + + app_name = argv[0]; + + while((c = getopt_long(argc, argv, "x:d:D:b:i:N:m:g:e:zfqUPahVv", longopts, NULL)) != EOF) { +#else + app_name = argv[0]; + + while((c = getopt(argc, argv, "x:d:D:b:i:N:m:g:e:zfqUPahVv")) != EOF) { +#endif /* HAVE_GETOPT_LONG */ + switch(c) + { + case 'x': + fsin = optarg; + break; + case 'd': + case 'D': + dopt[didx++] = optarg; + break; + case 'b': + nbblocks = SI_atof(optarg); + break; + case 'i': + bytes_per_inode = SI_atof(optarg); + break; + case 'N': + nbinodes = SI_atof(optarg); + break; + case 'm': + reserved_frac = SI_atof(optarg) / 100; + break; + case 'g': + gopt[gidx++] = optarg; + break; + case 'e': + emptyval = atoi(optarg); + break; + case 'z': + holes = 1; + break; + case 'f': + fs_timestamp = 0; + break; + case 'q': + squash_uids = 1; + squash_perms = 1; + break; + case 'U': + squash_uids = 1; + break; + case 'P': + squash_perms = 1; + break; + case 'a': + fix_android_stats = 1; + break; + case 'h': + showhelp(); + exit(0); + case 'V': + showversion(); + exit(0); + case 'v': + verbose = 1; + showversion(); + break; + default: + error_msg_and_die("Note: options have changed, see --help or the man page."); + } + } + + if(optind < (argc - 1)) + error_msg_and_die("Too many arguments. Try --help or else see the man page."); + if(optind > (argc - 1)) + error_msg_and_die("Not enough arguments. Try --help or else see the man page."); + + if(fix_android_stats && (squash_uids || squash_perms)) + error_msg_and_die("Cannot squash uid/perms and fix them up for Android at the same time."); + + fsout = argv[optind]; + + hdlinks.hdl = (struct hdlink_s *)malloc(hdlink_cnt * sizeof(struct hdlink_s)); + if (!hdlinks.hdl) + error_msg_and_die("Not enough memory"); + hdlinks.count = 0 ; + + if(fsin) + { + if(strcmp(fsin, "-")) + { + FILE * fh = xfopen(fsin, "rb"); + fs = load_fs(fh, bigendian); + fclose(fh); + } + else + fs = load_fs(stdin, bigendian); + } + else + { + if(reserved_frac == -1) + nbresrvd = nbblocks * RESERVED_BLOCKS; + else + nbresrvd = nbblocks * reserved_frac; + + stats.ninodes = EXT2_FIRST_INO - 1 + (nbresrvd ? 1 : 0); + stats.nblocks = 0; + + populate_fs(NULL, dopt, didx, squash_uids, squash_perms, 0, fs_timestamp, &stats); + + if(nbinodes == -1) + nbinodes = stats.ninodes; + else + if(stats.ninodes > (unsigned long)nbinodes) + { + fprintf(stderr, "number of inodes too low, increasing to %ld\n", stats.ninodes); + nbinodes = stats.ninodes; + } + + if(bytes_per_inode != -1) { + int tmp_nbinodes = nbblocks * BLOCKSIZE / bytes_per_inode; + if(tmp_nbinodes > nbinodes) + nbinodes = tmp_nbinodes; + } + if(fs_timestamp == -1) + fs_timestamp = time(NULL); + fs = init_fs(nbblocks, nbinodes, nbresrvd, holes, fs_timestamp); + } + + populate_fs(fs, dopt, didx, squash_uids, squash_perms, fix_android_stats, fs_timestamp, NULL); + + if(emptyval) { + uint32 b; + for(b = 1; b < fs->sb.s_blocks_count; b++) + if(!allocated(GRP_GET_BLOCK_BITMAP(fs,b),GRP_BBM_OFFSET(fs,b))) + memset(get_blk(fs, b), emptyval, BLOCKSIZE); + } + if(verbose) + print_fs(fs); + for(i = 0; i < gidx; i++) + { + uint32 nod; + char fname[MAX_FILENAME]; + char *p; + FILE *fh; + if(!(nod = find_path(fs, EXT2_ROOT_INO, gopt[i]))) + error_msg_and_die("path %s not found in filesystem", gopt[i]); + while((p = strchr(gopt[i], '/'))) + *p = '_'; + SNPRINTF(fname, MAX_FILENAME-1, "%s.blk", gopt[i]); + fh = xfopen(fname, "wb"); + fprintf(fh, "%d:", get_nod(fs, nod)->i_size); + flist_blocks(fs, nod, fh); + fclose(fh); + } + if(strcmp(fsout, "-")) + { + FILE * fh = xfopen(fsout, "wb"); + dump_fs(fs, fh, bigendian); + fclose(fh); + } + else + dump_fs(fs, stdout, bigendian); + free_fs(fs); + return 0; +} |