pax_global_header00006660000000000000000000000064136751243220014517gustar00rootroot0000000000000052 comment=474111097321f03de9e009aa9f7d4a8948e310b2 genext2fs-1.5.0/000077500000000000000000000000001367512432200134275ustar00rootroot00000000000000genext2fs-1.5.0/.github/000077500000000000000000000000001367512432200147675ustar00rootroot00000000000000genext2fs-1.5.0/.github/workflows/000077500000000000000000000000001367512432200170245ustar00rootroot00000000000000genext2fs-1.5.0/.github/workflows/ccpp.yml000066400000000000000000000005451367512432200205000ustar00rootroot00000000000000name: C/C++ CI on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: autogen run: ./autogen.sh - name: configure run: ./configure - name: make run: make - name: make distcheck run: make distcheck genext2fs-1.5.0/AUTHORS000066400000000000000000000000341367512432200144740ustar00rootroot00000000000000Xavier Bestel genext2fs-1.5.0/COPYING000066400000000000000000000431101367512432200144610ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. genext2fs-1.5.0/INSTALL000066400000000000000000000002641367512432200144620ustar00rootroot00000000000000If you're using CVS, you will have to first run: ./autogen.sh To install, just do it the normal GNU way: ./configure make make install To run the regression tests: make check genext2fs-1.5.0/Makefile.am000066400000000000000000000004521367512432200154640ustar00rootroot00000000000000bin_PROGRAMS = genext2fs genext2fs_SOURCES = genext2fs.c genext2fs_LDADD = $(ARCHIVE_LIBS) man_MANS = genext2fs.8 EXTRA_DIST = $(man_MANS) test-gen.lib test-mount.sh test.sh device_table.txt device_table_link.txt cache.h list.h m4/ac_func_scanf_can_malloc.m4 m4/ac_func_snprintf.m4 TESTS = test.sh genext2fs-1.5.0/NEWS000066400000000000000000000002271367512432200141270ustar00rootroot00000000000000This is the 1.4.2 release: - fixed: Endless loop with --verbose for large images Please report bugs to https://github.com/bestouff/genext2fs/issues genext2fs-1.5.0/README.md000066400000000000000000000140001367512432200147010ustar00rootroot00000000000000GENEXT2FS ========= genext2fs - ext2 filesystem generator for embedded systems SYNOPSIS -------- `genext2fs [ options ] [ output-image ]` DESCRIPTION ----------- **genext2fs** generates an ext2 filesystem as a normal (non-root) user. It does not require you to mount the image file to copy files on it, nor does it require that you become the superuser to make device nodes. The filesystem image is created in the file *output-image*. If not specified, it is sent to stdout. The `-d` and `-a` options support reading from stdin if a single hyphen is given as an argument. Thus, genext2fs can be used as part of a pipeline without any temporary files. By default, the maximum number of inodes in the filesystem is the minimum number required to accommodate the initial contents. In this way, a minimal filesystem (typically read-only) can be created with minimal free inodes. If required, free inodes can be added by passing the relevant options. The filesystem image size in blocks can be minimised by trial and error. OPTIONS ------- **-x, --starting-image image** Use this image as a starting point. **-d, --root directory[:path]** Add the given directory and contents at a particular path (by default the root). **-D, --devtable spec-file[:path]** Use **spec-file** to specify inodes to be added, at the given path (by default the root), including files, directories and special files like devices. If the specified files are already present in the image, their ownership and permission modes will be adjusted accordingly (this can only occur when the -D option appears after the options that create the specified files). Furthermore, you can use a single table entry to create many devices with a range of minor numbers (see examples below). All specified inodes receive the mtime of **spec-file** itself. **-a, --tarball file[:path]** Add the given archive (tarball) contents at a particular path (by default the root). If **file** is a hyphen, then the tarball will be read from standard input. Note: if not compiled with `libarchive`, genext2fs will use a builtin tarball parser with very primitive capabilities (e.g. no sparse file support, generally no support other than for modern GNU tar without fancy options). **-b, --size-in-blocks blocks** Size of the image in blocks. **-B, --block-size bytes** Size of a filesystem block in bytes. **-N, --number-of-inodes inodes** Minimum number of inodes. The required inode number will be computed automatically for all input that is not read from stdin. The number given by this option sets the minimum number of inodes. If you add anything from standard input, you should set this value because in that case the required number of inodes cannot be precomputed. The value set by this option will be overwritten by the value computed from the `-i` option, if the resulting number of inodes is larger. **-L, --volume-label name** Set the volume label for the filesystem. **-i, --bytes-per-inode ratio** Used to calculate the minimum number of inodes from the available blocks. Inodes are computed by multiplying the number of blocks (`-b`) by the blocksize (1024) and dividing that by the **ratio** given in this option. If the result is larger, then the number of required inodes counted from the input or the minimum number of inodes from the `-N` option, then the value computed by this option is used. **-m, --reserved-percentage N** Number of reserved blocks as a percentage of size. Reserving 0 blocks will prevent creation of the `lost+found` directory. **-o, --creator-os name** Value for creator OS field in superblock. **-g, --block-map path** Generate a block map file for this path. **-e, --fill-value value** Fill unallocated blocks with value. **-z, --allow-holes** Make files with holes. **-f, --faketime** Use a timestamp of 0 for inode and filesystem creation, instead of the present. Useful for testing. **-q, --squash** Squash permissions and owners (same as -P -U). **-U, --squash-uids** Squash ownership of inodes added using the -d option, making them all owned by root:root. **-P, --squash-perms** Squash permissions of inodes added using the -d option. Analogous to `umask 077`. **-v, --verbose** Print resulting filesystem structure. **-V, --version** Print genext2fs version. **-h, --help** Display help. EXAMPLES -------- **genext2fs -b 1440 -d src /dev/fd0** All files in the *src* directory will be written to **/dev/fd0** as a new ext2 filesystem image. You can then mount the floppy as usual. **genext2fs -b 1024 -d src -D devicetable.txt flashdisk.img** This example builds a filesystem from all the files in *src*, then device nodes are created based on the contents of the file *devicetable.txt*. Entries in the device table take the form of: where name is the file name and 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) uid is the user id for the target file, gid is the group id for the target file. The rest of the entries (major, minor, etc) apply only to device special files. An example device file follows: # name type mode uid gid major minor start inc count /dev d 755 0 0 - - - - - /dev/mem c 640 0 0 1 1 0 0 - /dev/tty c 666 0 0 5 0 0 0 - /dev/tty c 666 0 0 4 0 0 1 6 /dev/loop b 640 0 0 7 0 0 1 2 /dev/hda b 640 0 0 3 0 0 0 - /dev/hda b 640 0 0 3 1 1 1 16 /dev/log s 666 0 0 - - - - - This device table creates the /dev directory, a character device node `/dev/mem` (major 1, minor 1), and also creates `/dev/tty`, `/dev/tty[0-5]`, `/dev/loop[0-1]`, `/dev/hda`, `/dev/hda1` to `/dev/hda15` and `/dev/log` socket. genext2fs-1.5.0/TODO000066400000000000000000000002671367512432200141240ustar00rootroot00000000000000Disclaimer: I'll probably never do all this ... - support fancy ext2 options - support ext3 - UUID generation - mke2fs includes reserved blocks in free blocks, maybe we should too genext2fs-1.5.0/autogen.sh000077500000000000000000000004441367512432200154320ustar00rootroot00000000000000#!/bin/bash die() { echo "*** $0 failed :(" exit 1 } ./clean.sh automake_flags="-c -a" for p in aclocal autoconf autoheader automake; do flags=${p}_flags if ! ${p} ${!flags} ; then echo "*** ${p} failed :(" exit 1 fi done echo echo "Now just run:" echo "./configure" echo "make" genext2fs-1.5.0/cache.h000066400000000000000000000060001367512432200146370ustar00rootroot00000000000000#ifndef __CACHE_H__ #define __CACHE_H__ #include "list.h" #define CACHE_LISTS 256 typedef struct { list_elem link; list_elem lru_link; } cache_link; typedef struct { /* LRU list holds unused items */ unsigned int lru_entries; list_elem lru_list; unsigned int max_free_entries; unsigned int entries; list_elem lists[CACHE_LISTS]; unsigned int (*elem_val)(cache_link *elem); void (*freed)(cache_link *elem); } listcache; static inline void cache_add(listcache *c, cache_link *elem) { unsigned int hash = c->elem_val(elem) % CACHE_LISTS; int delcount = c->lru_entries - c->max_free_entries; if (delcount > 0) { /* Delete some unused items. */ list_elem *lru, *next; cache_link *l; list_for_each_elem_safe(&c->lru_list, lru, next) { l = container_of(lru, cache_link, lru_link); list_del(lru); list_del(&l->link); c->entries--; c->lru_entries--; c->freed(l); delcount--; if (delcount <= 0) break; } } c->entries++; list_item_init(&elem->lru_link); /* Mark it not in the LRU list */ list_add_after(&c->lists[hash], &elem->link); } static inline void cache_item_set_unused(listcache *c, cache_link *elem) { list_add_before(&c->lru_list, &elem->lru_link); c->lru_entries++; } static inline cache_link * cache_find(listcache *c, unsigned int val) { unsigned int hash = val % CACHE_LISTS; list_elem *elem; list_for_each_elem(&c->lists[hash], elem) { cache_link *l = container_of(elem, cache_link, link); if (c->elem_val(l) == val) { if (!list_empty(&l->lru_link)) { /* It's in the unused list, remove it. */ list_del(&l->lru_link); list_item_init(&l->lru_link); c->lru_entries--; } return l; } } return NULL; } static inline int cache_flush(listcache *c) { list_elem *elem, *next; cache_link *l; int i; list_for_each_elem_safe(&c->lru_list, elem, next) { l = container_of(elem, cache_link, lru_link); list_del(elem); list_del(&l->link); c->entries--; c->lru_entries--; c->freed(l); } for (i = 0; i < CACHE_LISTS; i++) { list_for_each_elem_safe(&c->lists[i], elem, next) { l = container_of(elem, cache_link, link); list_del(&l->link); c->entries--; c->freed(l); } } return c->entries || c->lru_entries; } static inline void cache_init(listcache *c, unsigned int max_free_entries, unsigned int (*elem_val)(cache_link *elem), void (*freed)(cache_link *elem)) { int i; c->entries = 0; c->lru_entries = 0; c->max_free_entries = max_free_entries; list_init(&c->lru_list); for (i = 0; i < CACHE_LISTS; i++) list_init(&c->lists[i]); c->elem_val = elem_val; c->freed = freed; } #endif /* __CACHE_H__ */ genext2fs-1.5.0/clean.sh000077500000000000000000000005241367512432200150510ustar00rootroot00000000000000#!/bin/bash for f in \ `find . -name Makefile.in -o -name Makefile` \ `find . -name .libs -o -name .deps` \ `find . -name '*.o' -o -name '*.la' -o -name '*.lo' -o -name '*.loT'` \ aclocal.m4* autom4te.cache \ configure config.* \ depcomp install-sh ltmain.sh missing mkinstalldirs libtool \ stamp-h1 \ genext2fs do rm -rf $f done genext2fs-1.5.0/configure.ac000066400000000000000000000037431367512432200157240ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) AC_INIT([genext2fs], [1.4.2]) AC_CONFIG_SRCDIR([genext2fs.c]) builtin(include, [m4/ac_func_snprintf.m4])dnl builtin(include, [m4/ac_func_scanf_can_malloc.m4])dnl AM_INIT_AUTOMAKE([foreign]) AC_CONFIG_HEADER([config.h]) AC_GNU_SOURCE # Checks for programs. AC_PROG_CC AC_PROG_INSTALL # Checks for header files. AC_HEADER_DIRENT AC_HEADER_STDC AC_HEADER_MAJOR AC_CHECK_HEADERS([fcntl.h inttypes.h limits.h memory.h stddef.h stdint.h stdlib.h string.h strings.h unistd.h]) AC_CHECK_HEADERS([libgen.h getopt.h]) # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_TYPE_UID_T AC_C_INLINE AC_CHECK_TYPE(size_t, unsigned) AC_CHECK_TYPE(ssize_t, signed) AC_CHECK_MEMBERS([struct stat.st_rdev]) # Checks for library functions. AC_CHECK_FUNCS([getopt_long getline strtof]) AC_FUNC_SNPRINTF AC_FUNC_SCANF_CAN_MALLOC AC_MSG_CHECKING(--enable-libarchive argument) AC_ARG_ENABLE(libarchive, [ --enable-libarchive Include libarchive support.], [enable_libarchive=$enableval], [enable_libarchive="no"]) AC_MSG_RESULT($enable_libarchive) if test "$enable_libarchive" = "yes"; then # Check for libarchive (no pkg-config support) AC_CHECK_LIB([archive], [archive_read_new], [ARCHIVE_LIBS=-larchive], AC_MSG_ERROR([libarchive not found. If libarchive is installed then perhaps you should set the LDFLAGS=-L/nonstandard/lib/dir environment variable])) AC_SUBST([ARCHIVE_LIBS]) AC_CHECK_HEADERS([archive.h archive_entry.h],, AC_MSG_ERROR([libarchive headers not found. If the libarchive headers are installed then perhaps you should set the CPPFLAGS=-I/nonstandard/include/dir environment variable])) AC_DEFINE([HAVE_LIBARCHIVE], [], [Description]) fi AC_OUTPUT([Makefile],[ chmod a+x $ac_top_srcdir/test-mount.sh $ac_top_srcdir/test.sh ]) genext2fs-1.5.0/debian/000077500000000000000000000000001367512432200146515ustar00rootroot00000000000000genext2fs-1.5.0/debian/changelog000066400000000000000000000003061367512432200165220ustar00rootroot00000000000000genext2fs (1.3-1) unstable; urgency=low * Initial Release. (closes: #105263) -- David Kimdon Sat, 14 Jul 2001 13:24:49 -0700 Local variables: mode: debian-changelog End: genext2fs-1.5.0/debian/control000066400000000000000000000012631367512432200162560ustar00rootroot00000000000000Source: genext2fs Section: admin Priority: optional Maintainer: David Kimdon Build-Depends: debhelper (>> 3.0.0) Standards-Version: 3.5.2 Package: genext2fs Architecture: any Depends: ${shlibs:Depends} Description: ext2 filesystem generator for embedded systems `genext2fs' is meant to generate an ext2 filesystem as a normal (non-root) user. It doesn't require you to mount the image file to copy files on it. It doesn't even require you to be the superuser to make device nodes. . Warning ! `genext2fs' has been designed for embedded systems. As such, it will generate a filesystem for single-user usage: all files/directories/etc... will belong to UID/GID 0 genext2fs-1.5.0/debian/copyright000066400000000000000000000011151367512432200166020ustar00rootroot00000000000000This package was debianized by David Kimdon on Sat, 14 Jul 2001 13:24:49 -0700. It was downloaded from http://freshmeat.net/projects/genext2fs/ Upstream Author(s): Xavier Bestel Copyright (C) 2000 Xavier Bestel 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. On Debian systems, the complete text of the GNU General Public License can be found in /usr/share/common-licenses/GPL file. genext2fs-1.5.0/debian/rules000077500000000000000000000025251367512432200157350ustar00rootroot00000000000000#!/usr/bin/make -f # Sample debian/rules that uses debhelper. # GNU copyright 1997 to 1999 by Joey Hess. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 # This is the debhelper compatability version to use. export DH_COMPAT=2 configure: configure-stamp configure-stamp: dh_testdir # Add here commands to configure the package. # ./configure --prefix=/usr --mandir=/usr/share/man/ touch configure-stamp build: configure-stamp build-stamp build-stamp: dh_testdir # Add here commands to compile the package. $(MAKE) touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp configure-stamp # Add here commands to clean up after the build process. -$(MAKE) clean dh_clean install: build dh_testdir dh_testroot dh_clean -k dh_installdirs # Add here commands to install the package into debian/genext2fs. $(MAKE) install DESTDIR=`pwd`/debian/genext2fs # Build architecture-independent files here. binary-indep: build install # We have nothing to do by default. # Build architecture-dependent files here. binary-arch: build install dh_testdir dh_testroot dh_installdocs dh_installchangelogs dh_link dh_strip dh_compress dh_fixperms dh_installdeb dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install configure genext2fs-1.5.0/device_table.txt000066400000000000000000000056431367512432200166060ustar00rootroot00000000000000# [Examples have been moved to the manual page, genext2fs(8).] # # /dev d 755 0 0 - - - - - /dev/mem c 640 0 0 1 1 0 0 - /dev/kmem c 640 0 0 1 2 0 0 - /dev/null c 640 0 0 1 3 0 0 - /dev/zero c 640 0 0 1 5 0 0 - /dev/random c 640 0 0 1 8 0 0 - /dev/urandom c 640 0 0 1 9 0 0 - /dev/tty c 666 0 0 5 0 0 0 - /dev/tty c 666 0 0 4 0 0 1 6 /dev/console c 640 0 0 5 1 0 0 - /dev/log s 666 0 0 - - - - - /dev/ram b 640 0 0 1 1 0 0 - /dev/ram b 640 0 0 1 0 0 1 4 /dev/loop b 640 0 0 7 0 0 1 2 /dev/ptmx c 666 0 0 5 2 0 0 - #/dev/ttyS c 640 0 0 4 64 0 1 4 #/dev/psaux c 640 0 0 10 1 0 0 - #/dev/rtc c 640 0 0 10 135 0 0 - # Adjust permissions on some normal files /etc d 755 /bin/ d 755 /etc/shadow f 600 0 0 - - - - - /bin/tinylogin f 4755 0 0 - - - - - # User-mode Linux stuff /dev/ubda b 640 0 0 98 0 0 0 - /dev/ubda b 640 0 0 98 1 1 1 15 # IDE Devices /dev/hda b 640 0 0 3 0 0 0 - /dev/hda b 640 0 0 3 1 1 1 15 /dev/hdb b 640 0 0 3 64 0 0 - /dev/hdb b 640 0 0 3 65 1 1 15 #/dev/hdc b 640 0 0 22 0 0 0 - #/dev/hdc b 640 0 0 22 1 1 1 15 #/dev/hdd b 640 0 0 22 64 0 0 - #/dev/hdd b 640 0 0 22 65 1 1 15 #/dev/hde b 640 0 0 33 0 0 0 - #/dev/hde b 640 0 0 33 1 1 1 15 #/dev/hdf b 640 0 0 33 64 0 0 - #/dev/hdf b 640 0 0 33 65 1 1 15 #/dev/hdg b 640 0 0 34 0 0 0 - #/dev/hdg b 640 0 0 34 1 1 1 15 #/dev/hdh b 640 0 0 34 64 0 0 - #/dev/hdh b 640 0 0 34 65 1 1 15 # SCSI Devices #/dev/sda b 640 0 0 8 0 0 0 - #/dev/sda b 640 0 0 8 1 1 1 15 #/dev/sdb b 640 0 0 8 16 0 0 - #/dev/sdb b 640 0 0 8 17 1 1 15 #/dev/sdc b 640 0 0 8 32 0 0 - #/dev/sdc b 640 0 0 8 33 1 1 15 #/dev/sdd b 640 0 0 8 48 0 0 - #/dev/sdd b 640 0 0 8 49 1 1 15 #/dev/sde b 640 0 0 8 64 0 0 - #/dev/sde b 640 0 0 8 65 1 1 15 #/dev/sdf b 640 0 0 8 80 0 0 - #/dev/sdf b 640 0 0 8 81 1 1 15 #/dev/sdg b 640 0 0 8 96 0 0 - #/dev/sdg b 640 0 0 8 97 1 1 15 #/dev/sdh b 640 0 0 8 112 0 0 - #/dev/sdh b 640 0 0 8 113 1 1 15 #/dev/sg c 640 0 0 21 0 0 1 15 #/dev/scd b 640 0 0 11 0 0 1 15 #/dev/st c 640 0 0 9 0 0 1 8 #/dev/nst c 640 0 0 9 128 0 1 8 #/dev/st c 640 0 0 9 32 1 1 4 #/dev/st c 640 0 0 9 64 1 1 4 #/dev/st c 640 0 0 9 96 1 1 4 # Floppy disk devices #/dev/fd b 640 0 0 2 0 0 1 2 #/dev/fd0d360 b 640 0 0 2 4 0 0 - #/dev/fd1d360 b 640 0 0 2 5 0 0 - #/dev/fd0h1200 b 640 0 0 2 8 0 0 - #/dev/fd1h1200 b 640 0 0 2 9 0 0 - #/dev/fd0u1440 b 640 0 0 2 28 0 0 - #/dev/fd1u1440 b 640 0 0 2 29 0 0 - #/dev/fd0u2880 b 640 0 0 2 32 0 0 - #/dev/fd1u2880 b 640 0 0 2 33 0 0 - # All the proprietary cdrom devices in the world #/dev/aztcd b 640 0 0 29 0 0 0 - #/dev/bpcd b 640 0 0 41 0 0 0 - #/dev/capi20 c 640 0 0 68 0 0 1 2 #/dev/cdu31a b 640 0 0 15 0 0 0 - #/dev/cdu535 b 640 0 0 24 0 0 0 - #/dev/cm206cd b 640 0 0 32 0 0 0 - #/dev/sjcd b 640 0 0 18 0 0 0 - #/dev/sonycd b 640 0 0 15 0 0 0 - #/dev/gscd b 640 0 0 16 0 0 0 - #/dev/sbpcd b 640 0 0 25 0 0 0 - #/dev/sbpcd b 640 0 0 25 0 0 1 4 #/dev/mcd b 640 0 0 23 0 0 0 - #/dev/optcd b 640 0 0 17 0 0 0 - genext2fs-1.5.0/device_table_link.txt000066400000000000000000000000711367512432200176110ustar00rootroot00000000000000# do a chown on a symlink /symlink l 777 77 7 - - - - - genext2fs-1.5.0/genext2fs.8000066400000000000000000000145211367512432200154300ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH GENEXT2FS 8 "August 19, 2006" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME genext2fs \- ext2 filesystem generator for embedded systems .SH SYNOPSIS .B genext2fs .RI "[ options ] [ output\-image ]" .SH DESCRIPTION \fBgenext2fs\fP generates an ext2 filesystem as a normal (non-root) user. It does not require you to mount the image file to copy files on it, nor does it require that you become the superuser to make device nodes. The filesystem is created either from scratch, or from an already existing one if specified by the \fB-x\fP option. Then each \fB-d\fP and \fB-D\fP option successively adds a "layer" to the image. The filesystem image is created in the file \fIoutput-image\fP. If not specified, it is sent to stdout. By default, the maximum number of inodes in the filesystem is the minimum number required to accommodate the initial contents. In this way, a minimal filesystem (typically read-only) can be created with minimal free inodes. If required, free inodes can be added by passing the relevant options. The filesystem image size in blocks can be minimised by trial and error. .SH OPTIONS .TP .BI "\-x, \-\-starting\-image image" Use this image as a starting point. .TP .BI "\-d, \-\-root directory[:path]" Add the given directory and contents at a particular path (by default the root). .TP .BI "\-D, \-\-devtable spec\-file[:path]" Use \fBspec-file\fP to specify inodes to be added, at the given path (by default the root), including files, directories and special files like devices. If the specified files are already present in the image, their ownership and permission modes will be adjusted accordingly (this can only occur when the -D option appears after the options that create the specified files). Furthermore, you can use a single table entry to create many devices with a range of minor numbers (see examples below). All specified inodes receive the mtime of \fBspec-file\fP itself. .TP .BI "\-a, \-\-tarball file[:path]" Add the given archive (tarball) contents at a particular path (by default the root). Note: if not compiled with `libarchive`, genext2fs will use a builtin tarball parser with very primitive capabilities (e.g. no sparse file support, generally no support other than for modern GNU tar without fancy options). .TP .BI "\-b, \-\-size\-in\-blocks blocks" Size of the image in blocks. .TP .BI "\-B, \-\-block-size bytes" Size of a filesystem block in bytes. .TP .BI "\-N, \-\-number\-of\-inodes inodes" Maximum number of inodes. .TP .BI "\-L, \-\-volume\-label name" Set the volume label for the filesystem. .TP .BI "\-i, \-\-bytes\-per\-inode ratio" Used to calculate the maximum number of inodes from the available blocks. .TP .BI "\-m, \-\-reserved\-percentage N" Number of reserved blocks as a percentage of size. Reserving 0 blocks will prevent creation of the "lost+found" directory. .TP .BI "\-o, \-\-creator\-os name" Value for creator OS field in superblock. .TP .BI "\-g, \-\-block\-map path" Generate a block map file for this path. .TP .BI "\-e, \-\-fill\-value value" Fill unallocated blocks with value. .TP .BI "\-z, \-\-allow\-holes" Make files with holes. .TP .BI "\-f, \-\-faketime" Use a timestamp of 0 for inode and filesystem creation, instead of the present. Useful for testing. See also SOURCE_DATE_EPOCH. .TP .BI "\-q, \-\-squash" Squash permissions and owners (same as -P -U). .TP .BI "\-U, \-\-squash\-uids" Squash ownership of inodes added using the -d option, making them all owned by root:root. .TP .BI "\-P, \-\-squash\-perms" Squash permissions of inodes added using the -d option. Analogous to "umask 077". .TP .BI "\-v, \-\-verbose" Print resulting filesystem structure. .TP .BI "\-V, \-\-version" Print genext2fs version. .TP .BI "\-h, \-\-help" Display help. .SH ENVIRONMENT .TP .BI SOURCE_DATE_EPOCH Standardized date for reproducible builds, see https://reproducible-builds.org/docs/source-date-epoch/ for more information. .SH EXAMPLES .EX .B genext2fs -b 1440 -d src /dev/fd0 .EE All files in the .I src directory will be written to .B /dev/fd0 as a new ext2 filesystem image. You can then mount the floppy as usual. .EX .B genext2fs -b 1024 -d src -D device_table.txt flashdisk.img .EE This example builds a filesystem from all the files in .I src, then device nodes are created based on the contents of the file .I device_table.txt. Entries in the device table take the form of: where name is the file name and type can be one of: .RS .nf f A regular file d Directory c Character special device file b Block special device file p Fifo (named pipe) l Symbolic link .fi .RE uid is the user id for the target file, gid is the group id for the target file. The rest of the entries (major, minor, etc) apply only to device special files. An example device file follows: .RS .nf # name type mode uid gid major minor start inc count /dev d 755 0 0 - - - - - /dev/mem c 640 0 0 1 1 0 0 - /dev/tty c 666 0 0 5 0 0 0 - /dev/tty c 666 0 0 4 0 0 1 6 /dev/loop b 640 0 0 7 0 0 1 2 /dev/hda b 640 0 0 3 0 0 0 - /dev/hda b 640 0 0 3 1 1 1 16 /dev/log s 666 0 0 - - - - - .fi .RE This device table creates the /dev directory, a character device node /dev/mem (major 1, minor 1), and also creates /dev/tty, /dev/tty[0-5], /dev/loop[0-1], /dev/hda, /dev/hda1 to /dev/hda15 and /dev/log socket. .SH SEE ALSO .BR mkfs(8), .BR genromfs(8), .BR mkisofs(8), .BR mkfs.jffs2(1) .br .SH AUTHOR This manual page was written by David Kimdon , for the Debian GNU/Linux system (but may be used by others). Examples provided by Erik Andersen . genext2fs-1.5.0/genext2fs.c000066400000000000000000003144111367512432200155040ustar00rootroot00000000000000/* vi: set sw=8 ts=8: */ // genext2fs.c // // ext2 filesystem generator for embedded systems // Copyright (C) 2000 Xavier Bestel // // Please direct support requests to https://github.com/bestouff/genext2fs/issues // // '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 // Copyright (C) 2002 Edward Betts // // 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 added // mkfs.jffs2 compatible device table support, // along with -q, -P, -U /* * Allow fseeko/off_t to be 64-bit offsets to allow filesystems and * individual files >2GB. */ #define _FILE_OFFSET_BITS 64 #include #include #if HAVE_SYS_TYPES_H # include #endif #if MAJOR_IN_MKDEV # include #elif MAJOR_IN_SYSMACROS # include #endif #if HAVE_SYS_STAT_H # include #endif #if STDC_HEADERS # include # include #else # if HAVE_STDLIB_H # include # endif # if HAVE_STDDEF_H # include # endif #endif #if HAVE_STRING_H # if !STDC_HEADERS && HAVE_MEMORY_H # include # endif # include #endif #if HAVE_STRINGS_H # include #endif #if HAVE_INTTYPES_H # include #else # if HAVE_STDINT_H # include # endif #endif #if HAVE_UNISTD_H # include #endif #if HAVE_DIRENT_H # include #else # define dirent direct # if HAVE_SYS_NDIR_H # include # endif # if HAVE_SYS_DIR_H # include # endif # if HAVE_NDIR_H # include # endif #endif #if HAVE_LIBGEN_H # include #endif #include #include #include #include #include #if HAVE_FCNTL_H # include #endif #if HAVE_GETOPT_H # include #endif #if HAVE_LIMITS_H # include #endif #ifdef HAVE_LIBARCHIVE #include #include #endif #include "cache.h" #define MIN(a, b) ((a) > (b) ? (b) : (a)) #define FSLAYER_DIR 0 #define FSLAYER_TABLE 1 #define FSLAYER_TAR 2 struct fslayer { int type; char * path; }; #define TAR_BLOCKSIZE 512 #define TAR_FULLFILENAME (100 + 155 + 1) struct tar_header { char filename[100]; char filemode[8]; char uid[8]; char gid[8]; char filesize[12]; char mtime[12]; char checksum[8]; char filetype; char linkedname[100]; char ustar[8]; char owner[32]; char group[32]; char major[8]; char minor[8]; char prefix[155]; }; struct stats { unsigned long nblocks; unsigned long ninodes; }; // 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; // block size static uint32 blocksize = 1024; #define SUPERBLOCK_OFFSET 1024 #define SUPERBLOCK_SIZE 1024 #define BLOCKSIZE blocksize #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 /* The default value for s_creator_os. */ #if defined(__linux__) && defined(EXT2_OS_LINUX) #define CREATOR_OS EXT2_OS_LINUX #define CREATOR_OS_NAME "linux" #else #if defined(__GNU__) && defined(EXT2_OS_HURD) #define CREATOR_OS EXT2_OS_HURD #define CREATOR_OS_NAME "hurd" #else #if defined(__FreeBSD__) && defined(EXT2_OS_FREEBSD) #define CREATOR_OS EXT2_OS_FREEBSD #define CREATOR_OS_NAME "freebsd" #else #if defined(LITES) && defined(EXT2_OS_LITES) #define CREATOR_OS EXT2_OS_LITES #define CREATOR_OS_NAME "lites" #else #define CREATOR_OS EXT2_OS_LINUX /* by default */ #define CREATOR_OS_NAME "linux" #endif /* defined(LITES) && defined(EXT2_OS_LITES) */ #endif /* defined(__FreeBSD__) && defined(EXT2_OS_FREEBSD) */ #endif /* defined(__GNU__) && defined(EXT2_OS_HURD) */ #endif /* defined(__linux__) && defined(EXT2_OS_LINUX) */ // 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) // codes for operating systems #define EXT2_OS_LINUX 0 #define EXT2_OS_HURD 1 #define EXT2_OS_MASIX 2 #define EXT2_OS_FREEBSD 3 #define EXT2_OS_LITES 4 // 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 /* 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,bi) (get_blk((fs),(grp)->bg_block_bitmap,(bi))) #define GRP_PUT_GROUP_BBM(bi) ( put_blk((bi)) ) // Get group inode bitmap (ibm) given the group number #define GRP_GET_GROUP_IBM(fs,grp,bi) (get_blk((fs), (grp)->bg_inode_bitmap,(bi))) #define GRP_PUT_GROUP_IBM(bi) ( put_blk((bi)) ) // 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,bi,gi) \ ( GRP_GET_GROUP_IBM((fs),get_gd(fs,GRP_GROUP_OF_INODE((fs),(nod)),gi),bi) ) #define GRP_PUT_INODE_BITMAP(bi,gi) \ ( GRP_PUT_GROUP_IBM((bi)),put_gd((gi)) ) //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/put the block bitmap that covers it #define GRP_GET_BLOCK_BITMAP(fs,blk,bi,gi) \ ( GRP_GET_GROUP_BBM((fs),get_gd(fs,GRP_GROUP_OF_BLOCK((fs),(blk)),(gi)),(bi)) ) #define GRP_PUT_BLOCK_BITMAP(bi,gi) \ ( GRP_PUT_GROUP_BBM((bi)),put_gd((gi)) ) //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 ) // 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)); } static inline int is_blk_empty(uint8 *b) { uint32 i; uint32 *v = (uint32 *) b; for(i = 0; i < BLOCKSIZE / 4; i++) if (*v++) return 0; return 1; } // 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 */ \ /* rev 1 version fields start here */ \ udecl32(s_first_ino) /* First non-reserved inode */ \ udecl16(s_inode_size) /* size of inode structure */ \ udecl16(s_block_group_nr) /* block group # of this superblock */ \ udecl32(s_feature_compat) /* compatible feature set */ \ udecl32(s_feature_incompat) /* incompatible feature set */ \ udecl32(s_feature_ro_compat) /* readonly-compatible feature set */ \ utdecl8(s_uuid,16) /* 128-bit uuid for volume */ \ utdecl8(s_volume_name,16) /* volume name */ \ utdecl8(s_last_mounted,64) /* directory where last mounted */ \ udecl32(s_algorithm_usage_bitmap) /* For compression */ #define EXT2_GOOD_OLD_FIRST_INO 11 #define EXT2_GOOD_OLD_INODE_SIZE 128 #define EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 #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 utdecl8(x,n) uint8 x[n]; #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[205]; // Reserved } superblock; typedef struct { groupdescriptor_decl uint32 bg_reserved[3]; } groupdescriptor; typedef struct { inode_decl uint32 i_reserved2[2]; } inode; typedef struct { directory_decl } directory; typedef uint8 *block; /* 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; #define HDLINK_CNT 16 struct hdlink_s { uint32 src_inode; uint32 dst_nod; }; struct hdlinks_s { int32 count; struct hdlink_s *hdl; }; /* Filesystem structure that support groups */ typedef struct { FILE *f; superblock *sb; int swapit; int32 hdlink_cnt; struct hdlinks_s hdlinks; int holes; listcache blks; listcache gds; listcache inodes; listcache blkmaps; } filesystem; // now the endianness swap #undef decl8 #undef udecl8 #undef utdecl8 #undef decl16 #undef udecl16 #undef decl32 #undef udecl32 #undef utdecl32 #define decl8(x) #define udecl8(x) #define utdecl8(x,n) #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; ix[i] = swab32(this->x[i]); } 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) { uint32 i; uint32 *blk = (uint32*)b; for(i = 0; i < BLOCKSIZE/4; i++) blk[i] = swab32(blk[i]); } #undef decl8 #undef udecl8 #undef utdecl8 #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; } int is_hardlink(filesystem *fs, ino_t inode) { int i; for(i = 0; i < fs->hdlinks.count; i++) { if(fs->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); if (!b) error_msg_and_die("get_workblk() failed, out of memory"); 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)); } // Used by get_blk/put_blk to hold information about a block owned // by the user. typedef struct { cache_link link; filesystem *fs; uint32 blk; uint8 *b; uint32 usecount; } blk_info; #define MAX_FREE_CACHE_BLOCKS 100 static uint32 blk_elem_val(cache_link *elem) { blk_info *bi = container_of(elem, blk_info, link); return bi->blk; } static void blk_freed(cache_link *elem) { blk_info *bi = container_of(elem, blk_info, link); if (fseeko(bi->fs->f, ((off_t) bi->blk) * BLOCKSIZE, SEEK_SET)) perror_msg_and_die("fseek"); if (fwrite(bi->b, BLOCKSIZE, 1, bi->fs->f) != 1) perror_msg_and_die("get_blk: write"); free(bi->b); free(bi); } // Return a given block from a filesystem. Make sure to call // put_blk when you are done with it. static inline uint8 * get_blk(filesystem *fs, uint32 blk, blk_info **rbi) { cache_link *curr; blk_info *bi; if (blk >= fs->sb->s_blocks_count) error_msg_and_die("Internal error, block out of range"); curr = cache_find(&fs->blks, blk); if (curr) { bi = container_of(curr, blk_info, link); bi->usecount++; goto out; } bi = malloc(sizeof(*bi)); if (!bi) error_msg_and_die("get_blk: out of memory"); bi->fs = fs; bi->blk = blk; bi->usecount = 1; bi->b = malloc(BLOCKSIZE); if (!bi->b) error_msg_and_die("get_blk: out of memory"); cache_add(&fs->blks, &bi->link); if (fseeko(fs->f, ((off_t) blk) * BLOCKSIZE, SEEK_SET)) perror_msg_and_die("fseek"); if (fread(bi->b, BLOCKSIZE, 1, fs->f) != 1) { if (ferror(fs->f)) perror_msg_and_die("fread"); memset(bi->b, 0, BLOCKSIZE); } out: *rbi = bi; return bi->b; } static inline void put_blk(blk_info *bi) { if (bi->usecount == 0) error_msg_and_die("Internal error: put_blk usecount zero"); bi->usecount--; if (bi->usecount == 0) /* Free happens in the cache code */ cache_item_set_unused(&bi->fs->blks, &bi->link); } typedef struct { cache_link link; filesystem *fs; int gds; blk_info *bi; groupdescriptor *gd; uint32 usecount; } gd_info; #define MAX_FREE_CACHE_GDS 100 static uint32 gd_elem_val(cache_link *elem) { gd_info *gi = container_of(elem, gd_info, link); return gi->gds; } static void gd_freed(cache_link *elem) { gd_info *gi = container_of(elem, gd_info, link); if (gi->fs->swapit) swap_gd(gi->gd); put_blk(gi->bi); free(gi); } #define GDS_START ((SUPERBLOCK_OFFSET + SUPERBLOCK_SIZE + BLOCKSIZE - 1) / BLOCKSIZE) #define GDS_PER_BLOCK (BLOCKSIZE / sizeof(groupdescriptor)) // the group descriptors are aligned on the block size static inline groupdescriptor * get_gd(filesystem *fs, uint32 no, gd_info **rgi) { uint32 gdblk; uint32 offset; gd_info *gi; cache_link *curr; curr = cache_find(&fs->gds, no); if (curr) { gi = container_of(curr, gd_info, link); gi->usecount++; goto out; } gi = malloc(sizeof(*gi)); if (!gi) error_msg_and_die("get_gd: out of memory"); gi->fs = fs; gi->gds = no; gi->usecount = 1; gdblk = GDS_START + (no / GDS_PER_BLOCK); offset = no % GDS_PER_BLOCK; gi->gd = ((groupdescriptor *) get_blk(fs, gdblk, &gi->bi)) + offset; cache_add(&fs->gds, &gi->link); if (fs->swapit) swap_gd(gi->gd); out: *rgi = gi; return gi->gd; } static inline void put_gd(gd_info *gi) { if (gi->usecount == 0) error_msg_and_die("Internal error: put_gd usecount zero"); gi->usecount--; if (gi->usecount == 0) /* Free happens in the cache code */ cache_item_set_unused(&gi->fs->gds, &gi->link); } // Used by get_blkmap/put_blkmap to hold information about an block map // owned by the user. typedef struct { cache_link link; filesystem *fs; uint32 blk; uint8 *b; blk_info *bi; uint32 usecount; } blkmap_info; #define MAX_FREE_CACHE_BLOCKMAPS 100 static uint32 blkmap_elem_val(cache_link *elem) { blkmap_info *bmi = container_of(elem, blkmap_info, link); return bmi->blk; } static void blkmap_freed(cache_link *elem) { blkmap_info *bmi = container_of(elem, blkmap_info, link); if (bmi->fs->swapit) swap_block(bmi->b); put_blk(bmi->bi); free(bmi); } // Return a given block map from a filesystem. Make sure to call // put_blkmap when you are done with it. static inline uint32 * get_blkmap(filesystem *fs, uint32 blk, blkmap_info **rbmi) { blkmap_info *bmi; cache_link *curr; curr = cache_find(&fs->blkmaps, blk); if (curr) { bmi = container_of(curr, blkmap_info, link); bmi->usecount++; goto out; } bmi = malloc(sizeof(*bmi)); if (!bmi) error_msg_and_die("get_blkmap: out of memory"); bmi->fs = fs; bmi->blk = blk; bmi->b = get_blk(fs, blk, &bmi->bi); bmi->usecount = 1; cache_add(&fs->blkmaps, &bmi->link); if (fs->swapit) swap_block(bmi->b); out: *rbmi = bmi; return (uint32 *) bmi->b; } static inline void put_blkmap(blkmap_info *bmi) { if (bmi->usecount == 0) error_msg_and_die("Internal error: put_blkmap usecount zero"); bmi->usecount--; if (bmi->usecount == 0) /* Free happens in the cache code */ cache_item_set_unused(&bmi->fs->blkmaps, &bmi->link); } // Used by get_nod/put_nod to hold information about an inode owned // by the user. typedef struct { cache_link link; filesystem *fs; uint32 nod; uint8 *b; blk_info *bi; inode *itab; uint32 usecount; } nod_info; #define MAX_FREE_CACHE_INODES 100 static uint32 inode_elem_val(cache_link *elem) { nod_info *ni = container_of(elem, nod_info, link); return ni->nod; } static void inode_freed(cache_link *elem) { nod_info *ni = container_of(elem, nod_info, link); if (ni->fs->swapit) swap_nod(ni->itab); put_blk(ni->bi); free(ni); } #define INODES_PER_BLOCK (BLOCKSIZE / sizeof(inode)) // Return a given inode from a filesystem. Make sure to call // put_nod when you are done with it. static inline inode * get_nod(filesystem *fs, uint32 nod, nod_info **rni) { uint32 grp, boffset, offset; cache_link *curr; groupdescriptor *gd; gd_info *gi; nod_info *ni; curr = cache_find(&fs->inodes, nod); if (curr) { ni = container_of(curr, nod_info, link); ni->usecount++; goto out; } ni = malloc(sizeof(*ni)); if (!ni) error_msg_and_die("get_nod: out of memory"); ni->fs = fs; ni->nod = nod; ni->usecount = 1; cache_add(&fs->inodes, &ni->link); offset = GRP_IBM_OFFSET(fs,nod) - 1; boffset = offset / INODES_PER_BLOCK; offset %= INODES_PER_BLOCK; grp = GRP_GROUP_OF_INODE(fs,nod); gd = get_gd(fs, grp, &gi); ni->b = get_blk(fs, gd->bg_inode_table + boffset, &ni->bi); ni->itab = ((inode *) ni->b) + offset; if (fs->swapit) swap_nod(ni->itab); put_gd(gi); out: *rni = ni; return ni->itab; } static inline void put_nod(nod_info *ni) { if (ni->usecount == 0) error_msg_and_die("Internal error: put_nod usecount zero"); ni->usecount--; if (ni->usecount == 0) /* Free happens in the cache code */ cache_item_set_unused(&ni->fs->inodes, &ni->link); } // Used to hold state information while walking a directory inode. typedef struct { directory d; filesystem *fs; uint32 nod; uint8 *last_d; uint8 *b; blk_info *bi; int need_flush; } dirwalker; // Start a directory walk on the given inode. You must pass in a // dirwalker structure, then use that dirwalker for future operations. // Call put_dir when you are done walking the directory. // After put_dir, do not use the old pointer returned by get_dir or next_dir. static inline directory * get_dir(filesystem *fs, uint32 nod, dirwalker *dw) { dw->fs = fs; dw->b = get_blk(fs, nod, &dw->bi); dw->nod = nod; dw->last_d = dw->b; dw->need_flush = 1; memcpy(&dw->d, dw->last_d, sizeof(directory)); if (fs->swapit) swap_dir(&dw->d); return &dw->d; } // Move to the next directory. static inline directory * next_dir(dirwalker *dw) { uint8 *next_d = dw->last_d + dw->d.d_rec_len; if (dw->need_flush) { if (dw->fs->swapit) swap_dir(&dw->d); memcpy(dw->last_d, &dw->d, sizeof(directory)); } if (next_d - dw->b >= BLOCKSIZE) { // no more directories in block b dw->need_flush = 0; return NULL; } dw->last_d = next_d; memcpy(&dw->d, next_d, sizeof(directory)); if (dw->fs->swapit) swap_dir(&dw->d); return &dw->d; } // Call then when you are done with the directory walk. static inline void put_dir(dirwalker *dw) { if (dw->need_flush) { // walk ended before reaching the end of block b if (dw->fs->swapit) swap_dir(&dw->d); memcpy(dw->last_d, &dw->d, sizeof(directory)); } if (dw->nod == 0) free_workblk(dw->b); else put_blk(dw->bi); } // Create a new directory block with the given inode as it's destination // and append it to the current dirwalker. static directory * new_dir(filesystem *fs, uint32 dnod, const char *name, int nlen, dirwalker *dw) { directory *d; dw->fs = fs; dw->b = get_workblk(); dw->nod = 0; dw->last_d = dw->b; dw->need_flush = 1; d = &dw->d; d->d_inode = dnod; d->d_rec_len = BLOCKSIZE; d->d_name_len = nlen; strncpy(((char *) dw->last_d) + sizeof(directory), name, nlen); return d; } // Shrink the current directory entry, make a new one with the free // space, and return the new directory entry (making it current). static inline directory * shrink_dir(dirwalker *dw, uint32 nod, const char *name, int nlen) { int reclen, preclen; directory *d = &dw->d; reclen = d->d_rec_len; d->d_rec_len = sizeof(directory) + rndup(d->d_name_len, 4); preclen = d->d_rec_len; reclen -= preclen; if (dw->fs->swapit) swap_dir(&dw->d); memcpy(dw->last_d, &dw->d, sizeof(directory)); dw->last_d = dw->last_d + preclen; d->d_rec_len = reclen; d->d_inode = nod; d->d_name_len = nlen; strncpy(((char *) dw->last_d) + sizeof(directory), name, nlen); return d; } // Return the current block the directory is walking static inline uint8 * dir_data(dirwalker *dw) { return dw->b; } // Return the pointer to the name for the current directory static inline char * dir_name(dirwalker *dw) { return ((char *) dw->last_d) + sizeof(directory); } // Set the name for the current directory. Note that this doesn't // verify that there is space for the directory name, you must do // that yourself. static void dir_set_name(dirwalker *dw, const char *name, int nlen) { dw->d.d_name_len = nlen; strncpy(((char *) dw->last_d) + sizeof(directory), name, nlen); } // allocate a given block/inode in the bitmap // allocate first free if item == 0 static uint32 allocate(block b, uint32 item) { if(!item) { uint32 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; blk_info *bi; groupdescriptor *gd; gd_info *gi; grp = GRP_GROUP_OF_INODE(fs,nod); nbgroups = GRP_NBGROUPS(fs); gd = get_gd(fs, grp, &gi); bk = allocate(GRP_GET_GROUP_BBM(fs, gd, &bi), 0); GRP_PUT_GROUP_BBM(bi); put_gd(gi); if (!bk) { for (grp=0; grpbg_free_blocks_count--)) error_msg_and_die("group descr %d. free blocks count == 0 (corrupted fs?)",grp); put_gd(gi); if(!(fs->sb->s_free_blocks_count--)) error_msg_and_die("superblock free blocks count == 0 (corrupted fs?)"); return fs->sb->s_first_data_block + fs->sb->s_blocks_per_group*grp + (bk-1); } // free a block static void free_blk(filesystem *fs, uint32 bk) { uint32 grp; blk_info *bi; gd_info *gi; groupdescriptor *gd; grp = bk / fs->sb->s_blocks_per_group; bk %= fs->sb->s_blocks_per_group; gd = get_gd(fs, grp, &gi); deallocate(GRP_GET_GROUP_BBM(fs, gd, &bi), bk); GRP_PUT_GROUP_BBM(bi); gd->bg_free_blocks_count++; put_gd(gi); fs->sb->s_free_blocks_count++; } // allocate an inode static uint32 alloc_nod(filesystem *fs) { uint32 nod,best_group=0; uint32 grp,nbgroups,avefreei; blk_info *bi; gd_info *gi, *bestgi; groupdescriptor *gd, *bestgd; 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; bestgd = get_gd(fs, best_group, &bestgi); for(grp=0; grpbg_free_inodes_count < avefreei || gd->bg_free_inodes_count == 0) { put_gd(gi); continue; } if (!best_group || gd->bg_free_blocks_count > bestgd->bg_free_blocks_count) { put_gd(bestgi); best_group = grp; bestgd = gd; bestgi = gi; } else put_gd(gi); } if (!(nod = allocate(GRP_GET_GROUP_IBM(fs, bestgd, &bi), 0))) error_msg_and_die("couldn't allocate an inode (no free inode)"); GRP_PUT_GROUP_IBM(bi); if(!(bestgd->bg_free_inodes_count--)) error_msg_and_die("group descr. free blocks count == 0 (corrupted fs?)"); put_gd(bestgi); 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. // 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 bk = 0; blkmap_info *bmi1 = NULL, *bmi2 = NULL, *bmi3 = NULL; uint32 *b; int extend = 0, reduce = 0; inode *inod; nod_info *ni; uint32 *iblk; if(create && (*create) < 0) reduce = 1; inod = get_nod(fs, nod, &ni); if(bw->bnum >= inod->i_blocks / INOBLK) { if(create && (*create) > 0) { (*create)--; extend = 1; } else { put_nod(ni); return WALK_END; } } iblk = inod->i_block; // first direct block if(bw->bpdir == EXT2_INIT_BLOCK) { bkref = &iblk[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 = &iblk[++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 iblk[bw->bpdir] = alloc_blk(fs,nod); if(reduce) // free indirect block free_blk(fs, iblk[bw->bpdir]); b = get_blkmap(fs, iblk[bw->bpdir], &bmi1); 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 = get_blkmap(fs, iblk[bw->bpdir], &bmi1); 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 iblk[bw->bpdir] = alloc_blk(fs,nod); if(reduce) // free double indirect block free_blk(fs, iblk[bw->bpdir]); b = get_blkmap(fs, iblk[bw->bpdir], &bmi1); 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 = get_blkmap(fs, b[bw->bpind], &bmi2); 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 = get_blkmap(fs, iblk[bw->bpdir], &bmi1); b = get_blkmap(fs, b[bw->bpind], &bmi2); 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 = get_blkmap(fs, iblk[bw->bpdir], &bmi1); if(extend) // allocate indirect block b[bw->bpind] = alloc_blk(fs,nod); if(reduce) // free indirect block free_blk(fs, b[bw->bpind]); b = get_blkmap(fs, b[bw->bpind], &bmi2); 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 iblk[bw->bpdir] = alloc_blk(fs,nod); if(reduce) // free triple indirect block free_blk(fs, iblk[bw->bpdir]); b = get_blkmap(fs, iblk[bw->bpdir], &bmi1); 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 = get_blkmap(fs, b[bw->bpind], &bmi2); 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 = get_blkmap(fs, b[bw->bpdind], &bmi3); 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 = get_blkmap(fs, iblk[bw->bpdir], &bmi1); b = get_blkmap(fs, b[bw->bpind], &bmi2); b = get_blkmap(fs, b[bw->bpdind], &bmi3); 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 = get_blkmap(fs, iblk[bw->bpdir], &bmi1); b = get_blkmap(fs, b[bw->bpind], &bmi2); 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 = get_blkmap(fs, b[bw->bpdind], &bmi3); 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 = get_blkmap(fs, iblk[bw->bpdir], &bmi1); 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 = get_blkmap(fs, b[bw->bpind], &bmi2); 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 = get_blkmap(fs, b[bw->bpdind], &bmi3); 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 */ bk = *bkref; if (bmi3) put_blkmap(bmi3); if (bmi2) put_blkmap(bmi2); if (bmi1) put_blkmap(bmi1); if(bk) { blk_info *bi; gd_info *gi; uint8 *block; bw->bnum++; block = GRP_GET_BLOCK_BITMAP(fs,bk,&bi,&gi); if(!reduce && !allocated(block, GRP_BBM_OFFSET(fs,bk))) error_msg_and_die("[block %d of inode %d is unallocated !]", bk, nod); GRP_PUT_BLOCK_BITMAP(bi, gi); } if(extend) inod->i_blocks = bw->bnum * INOBLK; put_nod(ni); return bk; } typedef struct { blockwalker bw; uint32 nod; nod_info *ni; inode *inod; } inode_pos; #define INODE_POS_TRUNCATE 0 #define INODE_POS_EXTEND 1 // Call this to set up an ipos structure for future use with // extend_inode_blk to append blocks to the given inode. If // op is INODE_POS_TRUNCATE, the inode is truncated to zero size. // If op is INODE_POS_EXTEND, the position is moved to the end // of the inode's data blocks. // Call inode_pos_finish when done with the inode_pos structure. static void inode_pos_init(filesystem *fs, inode_pos *ipos, uint32 nod, int op, blockwalker *endbw) { blockwalker lbw; init_bw(&ipos->bw); ipos->nod = nod; ipos->inod = get_nod(fs, nod, &ipos->ni); if (op == INODE_POS_TRUNCATE) { int32 create = -1; while(walk_bw(fs, nod, &ipos->bw, &create, 0) != WALK_END) /*nop*/; ipos->inod->i_blocks = 0; } if (endbw) ipos->bw = *endbw; else { /* Seek to the end */ init_bw(&ipos->bw); lbw = ipos->bw; while(walk_bw(fs, nod, &ipos->bw, 0, 0) != WALK_END) lbw = ipos->bw; ipos->bw = lbw; } } // Clean up the inode_pos structure. static void inode_pos_finish(filesystem *fs, inode_pos *ipos) { fs = fs; // unused put_nod(ipos->ni); } // add blocks to an inode (file/dir/etc...) at the given position. // This will only work when appending to the end of an inode. static void extend_inode_blk(filesystem *fs, inode_pos *ipos, block b, int amount) { uint32 bk; uint32 pos; if (amount < 0) error_msg_and_die("extend_inode_blk: Got negative amount"); for (pos = 0; amount; pos += BLOCKSIZE) { int hole = (fs->holes && is_blk_empty(b + pos)); bk = walk_bw(fs, ipos->nod, &ipos->bw, &amount, hole); if (bk == WALK_END) error_msg_and_die("extend_inode_blk: extend failed"); if (!hole) { blk_info *bi; uint8 *block = get_blk(fs, bk, &bi); memcpy(block, b + pos, BLOCKSIZE); put_blk(bi); } } } // link an entry (inode #) to a directory static void add2dir(filesystem *fs, uint32 dnod, uint32 nod, const char* name) { blockwalker bw, lbw; uint32 bk; directory *d; dirwalker dw; uint32 reclen, nlen; inode *node; inode *pnode; nod_info *dni, *ni; inode_pos ipos; pnode = get_nod(fs, dnod, &dni); 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); lbw = bw; while((bk = walk_bw(fs, dnod, &bw, 0, 0)) != WALK_END) // for all blocks in dir { // for all dir entries in block for(d = get_dir(fs, bk, &dw); d; d = next_dir(&dw)) { // 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, &ni); dir_set_name(&dw, name, nlen); put_dir(&dw); node->i_links_count++; put_nod(ni); goto out; } // 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)) { d = shrink_dir(&dw, nod, name, nlen); put_dir(&dw); node = get_nod(fs, nod, &ni); node->i_links_count++; put_nod(ni); goto out; } } put_dir(&dw); lbw = bw; } // we found no free entry in the directory, so we add a block node = get_nod(fs, nod, &ni); d = new_dir(fs, nod, name, nlen, &dw); node->i_links_count++; put_nod(ni); next_dir(&dw); // Force the data into the buffer inode_pos_init(fs, &ipos, dnod, INODE_POS_EXTEND, &lbw); extend_inode_blk(fs, &ipos, dir_data(&dw), 1); inode_pos_finish(fs, &ipos); put_dir(&dw); pnode->i_size += BLOCKSIZE; out: put_nod(dni); } // 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; dirwalker dw; for (d = get_dir(fs, bk, &dw); d; d=next_dir(&dw)) if(d->d_inode && (nlen == d->d_name_len) && !strncmp(dir_name(&dw), name, nlen)) { uint32 result = d->d_inode; put_dir(&dw); return result; } put_dir(&dw); } 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; nod_info *ni; node = get_nod(fs, nod, &ni); node->i_mode = (node->i_mode & ~FM_IMASK) | (mode & FM_IMASK); node->i_uid = uid; node->i_gid = gid; put_nod(ni); } // 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_info *ni; gd_info *gi; nod = alloc_nod(fs); node = get_nod(fs, nod, &ni); 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: if (fs->swapit) { ((uint8*)node->i_block)[3] = minor; ((uint8*)node->i_block)[2] = major; } else { ((uint8*)node->i_block)[0] = minor; ((uint8*)node->i_block)[1] = major; } break; case FM_IFDIR: add2dir(fs, nod, nod, "."); add2dir(fs, nod, parent_nod, ".."); get_gd(fs,GRP_GROUP_OF_INODE(fs,nod),&gi)->bg_used_dirs_count++; put_gd(gi); break; } node->i_uid = uid; node->i_gid = gid; node->i_atime = mtime; node->i_ctime = ctime; node->i_mtime = mtime; put_nod(ni); 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); } // byte swapping for symlinks static inline void swab32_into(uint32 *dst, uint32 *src, size_t n) { size_t i; for(i = 0; i < n; i++) dst[i] = swab32(src[i]); } // 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); nod_info *ni; inode *node = get_nod(fs, nod, &ni); inode_pos ipos; inode_pos_init(fs, &ipos, nod, INODE_POS_TRUNCATE, NULL); node->i_size = size; if (size < 4 * (EXT2_TIND_BLOCK + 1)) if (fs->swapit) swab32_into(node->i_block, (uint32 *)b, EXT2_TIND_BLOCK + 1); else memcpy(node->i_block, b, 4 * (EXT2_TIND_BLOCK + 1)); else extend_inode_blk(fs, &ipos, b, rndup(size, BLOCKSIZE) / BLOCKSIZE); inode_pos_finish(fs, &ipos); put_nod(ni); return nod; } static void fs_upgrade_rev1_largefile(filesystem *fs) { fs->sb->s_rev_level = 1; fs->sb->s_first_ino = EXT2_GOOD_OLD_FIRST_INO; fs->sb->s_inode_size = EXT2_GOOD_OLD_INODE_SIZE; } #define COPY_BLOCKS 16 #define CB_SIZE (COPY_BLOCKS * BLOCKSIZE) typedef off_t (*file_read_cb)(filesystem *fs, inode_pos *ipos, off_t size, void *data); off_t fh_read(filesystem *fs, inode_pos *ipos, off_t size, void *data) { size_t readbytes; off_t remaining_size = size; int fullsize; uint8 * b = malloc(CB_SIZE); if (!b) error_msg_and_die("mkfile_fs: out of memory"); readbytes = fread(b, 1, MIN(remaining_size, CB_SIZE), (FILE *)data); while (readbytes) { remaining_size -= readbytes; fullsize = rndup(readbytes, BLOCKSIZE); // Fill to end of block with zeros. memset(b + readbytes, 0, fullsize - readbytes); extend_inode_blk(fs, ipos, b, fullsize / BLOCKSIZE); readbytes = fread(b, 1, MIN(remaining_size, CB_SIZE), (FILE *)data); } free(b); return size; } #ifdef HAVE_LIBARCHIVE off_t la_read(filesystem *fs, inode_pos *ipos, off_t s /* ignored */, void *data) { size_t readbytes; off_t actual_size = 0; int fullsize; uint8 * b = malloc(CB_SIZE); if (!b) error_msg_and_die("mkfile_fs: out of memory"); readbytes = archive_read_data((struct archive *)data, b, CB_SIZE); while (readbytes) { actual_size += readbytes; fullsize = rndup(readbytes, BLOCKSIZE); // Fill to end of block with zeros. memset(b + readbytes, 0, fullsize - readbytes); extend_inode_blk(fs, ipos, b, fullsize / BLOCKSIZE); readbytes = archive_read_data((struct archive *)data, b, CB_SIZE); } free(b); return actual_size; } #endif // make a file from a FILE* static uint32 mkfile_fs(filesystem *fs, uint32 parent_nod, const char *name, uint32 mode, file_read_cb read_cb, void *data, off_t size, uid_t uid, gid_t gid, uint32 ctime, uint32 mtime) { uint32 nod = mknod_fs(fs, parent_nod, name, mode|FM_IFREG, uid, gid, 0, 0, ctime, mtime); nod_info *ni; inode *node = get_nod(fs, nod, &ni); off_t actual_size; inode_pos ipos; inode_pos_init(fs, &ipos, nod, INODE_POS_TRUNCATE, NULL); actual_size = read_cb(fs, &ipos, size, data); if (actual_size > 0x7fffffff) { if (fs->sb->s_rev_level < 1) fs_upgrade_rev1_largefile(fs); fs->sb->s_feature_ro_compat |= EXT2_FEATURE_RO_COMPAT_LARGE_FILE; } node->i_dir_acl = actual_size >> 32; node->i_size = actual_size; inode_pos_finish(fs, &ipos); put_nod(ni); 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; } #define OCTAL_READ(field) tar_numeric_field_read((unsigned char*)field, sizeof field) long long tar_numeric_field_read(unsigned char *field, size_t size) { size_t i; unsigned long long res = 0; // this is the 256-based GNU extension if(*field == 0x80 || *field == 0xff) { if(*field == 0xff) res = -1; for(i = 1; i < size; i++) res = (res << 8) + field[i]; } else for(i = 0; i < size; i++) { char c = field[i]; if(c == ' ') continue; if(c < '0' || c > '7') break; res = 8 * res + c - '0'; } return res; } int is_zero(char *block, size_t size) { size_t i; for(i = 0; i < size; i++) if(block[i]) return 0; return 1; } static void add2fs_from_tarball(filesystem *fs, uint32 this_nod, FILE * fh, int squash_uids, int squash_perms, uint32 fs_timestamp, struct stats *stats) { #ifndef HAVE_LIBARCHIVE uint32 nod, hlnod, oldnod; uint32 uid, gid, mode, major, minor, ctime, mtime; char buffer[TAR_BLOCKSIZE]; char pathbuf[TAR_FULLFILENAME], *path, *path2 = NULL, *dir, *name; char type; struct tar_header *tarhead = (void*)buffer; size_t filesize, padding; int nbnull = 0; int i, checksum, signed_checksum, unsigned_checksum; int has_longname = 0; char *longname = NULL; size_t longname_size = 0; int has_longlink = 0; char *longlink = NULL; size_t longlink_size = 0; size_t readbytes; while(1) { if (path2) { free(path2); path2 = NULL; } readbytes = fread(buffer, 1, TAR_BLOCKSIZE, fh); if (!readbytes) break; if (readbytes != TAR_BLOCKSIZE) error_msg_and_die("tarball has wrong size"); if (is_zero(buffer, sizeof buffer)) { if (nbnull++) break; continue; } else nbnull = 0; if (*(long *)tarhead->ustar != *(long *)"ustar\00000" && strcmp(tarhead->ustar, "ustar ")) error_msg_and_die("not a tarball"); signed_checksum = unsigned_checksum = 0; checksum = OCTAL_READ(tarhead->checksum); memset(tarhead->checksum, ' ', sizeof tarhead->checksum); for(i = 0; i < TAR_BLOCKSIZE; i++) { signed_checksum += (signed char)buffer[i]; unsigned_checksum += (unsigned char)buffer[i]; } if(checksum != signed_checksum && checksum != unsigned_checksum) error_msg_and_die("tarball corrupted"); filesize = OCTAL_READ(tarhead->filesize); padding = rndup(filesize, TAR_BLOCKSIZE) - filesize; mtime = OCTAL_READ(tarhead->mtime); ctime = fs_timestamp; uid = OCTAL_READ(tarhead->uid); gid = OCTAL_READ(tarhead->gid); mode = OCTAL_READ(tarhead->filemode) & FM_IMASK; major = OCTAL_READ(tarhead->major); minor = OCTAL_READ(tarhead->minor); type = tarhead->filetype; if (squash_uids) uid = gid = 0; if (squash_perms) mode &= ~(FM_IRWXG | FM_IRWXO); if(has_longname) { path = longname; has_longname = 0; } else { strncpy(pathbuf, tarhead->prefix, sizeof tarhead->prefix); strncpy(pathbuf+strnlen(pathbuf, sizeof pathbuf), tarhead->filename, sizeof tarhead->filename); pathbuf[strnlen(pathbuf, sizeof pathbuf - 1)] = '\0'; path = pathbuf; } if (stats) { switch (type) { case '2': if (strlen(tarhead->linkedname) >= 4 * (EXT2_TIND_BLOCK+1)) stats->nblocks += (filesize + BLOCKSIZE - 1) / BLOCKSIZE; stats->ninodes++; break; case '0': case 0: case '7': stats->nblocks += (filesize + BLOCKSIZE - 1) / BLOCKSIZE; case '1': case '6': case '3': case '4': stats->ninodes++; break; default: break; } fseek(fh, filesize + padding, SEEK_CUR); } else { if (fs && strcmp(path, "/") == 0) { // if the entry modifies the root node, don't call // basename and dirname but chmod the root node // directly fseek(fh, filesize + padding, SEEK_CUR); if (type != '5') { error_msg("tarball entry \"%s\" skipped: root node must be a directory", path); continue; } mode |= FM_IFDIR; chmod_fs(fs, this_nod, mode, uid, gid); continue; } path2 = strdup(path); name = basename(path); dir = dirname(path2); if((!strcmp(name, ".")) || (!strcmp(name, ".."))) { error_msg("tarball entry %s skipped", path); fseek(fh, filesize + padding, SEEK_CUR); continue; } if(fs) { if(!(nod = find_path(fs, this_nod, dir))) { error_msg("tarball entry %s skipped: can't find directory '%s' to create '%s''", path, dir, name); fseek(fh, filesize + padding, SEEK_CUR); continue; } } else nod = 0; switch (type) { case '5': if((oldnod = find_path(fs, nod, name))) chmod_fs(fs, nod = oldnod, mode, uid, gid); else nod = mkdir_fs(fs, nod, name, mode, uid, gid, ctime, mtime); fseek(fh, filesize + padding, SEEK_CUR); break; case '0': case 0: case '7': nod = mkfile_fs(fs, nod, name, mode, fh_read, fh, filesize, uid, gid, ctime, mtime); fseek(fh, padding, SEEK_CUR); break; case '1': if(!(hlnod = find_path(fs, this_nod, tarhead->linkedname))) { error_msg("tarball entry %s skipped: can't find hardlink destination '%s' to create '%s''", path, dir, name); fseek(fh, filesize + padding, SEEK_CUR); continue; } add2dir(fs, nod, hlnod, name); fseek(fh, filesize + padding, SEEK_CUR); break; case '2': if(has_longlink) mklink_fs(fs, nod, name, strlen(longlink), (uint8*)longlink, uid, gid, ctime, mtime); else mklink_fs(fs, nod, name, strlen(tarhead->linkedname), (uint8*)tarhead->linkedname, uid, gid, ctime, mtime); has_longlink = 0; fseek(fh, filesize + padding, SEEK_CUR); break; case '6': nod = mknod_fs(fs, nod, name, mode|FM_IFIFO, uid, gid, 0, 0, ctime, mtime); fseek(fh, filesize + padding, SEEK_CUR); break; case '3': nod = mknod_fs(fs, nod, name, mode|FM_IFCHR, uid, gid, major, minor, ctime, mtime); fseek(fh, filesize + padding, SEEK_CUR); break; case '4': nod = mknod_fs(fs, nod, name, mode|FM_IFBLK, uid, gid, major, minor, ctime, mtime); fseek(fh, filesize + padding, SEEK_CUR); break; case 'L': if(has_longname) error_msg("tarball longname to '%s' hasn't been consumed", longname); if(filesize + padding > longname_size) { if(longname) free(longname); longname = malloc(longname_size = filesize + padding); } fread(longname, longname_size, 1, fh); has_longname = 1; break; case 'K': if(has_longlink) error_msg("tarball longlink to '%s' hasn't been consumed", longlink); if(filesize + padding > longlink_size) { if(longlink) free(longlink); longlink = malloc(longlink_size = filesize + padding); } fread(longlink, longlink_size, 1, fh); has_longlink = 1; break; default: error_msg("tarball entry %s skipped: bad type '%c' for entry '%s'", path, type, name); fseek(fh, filesize + padding, SEEK_CUR); continue; } } } if (path2) free(path2); if (nbnull != 2) error_msg_and_die("tarball has wrong size"); if(longname) free(longname); if(longlink) free(longlink); #else int r; uint32 nod, lnknod; struct archive *a; struct archive_entry *entry; char *path2, *path3, *dir, *name, *lnk; size_t filesize; uint32 uid, gid, mode, ctime, mtime; a = archive_read_new(); if (a == NULL) error_msg_and_die("Couldn't create archive reader."); if (archive_read_support_filter_all(a) != ARCHIVE_OK) error_msg_and_die("Couldn't enable decompression"); if (archive_read_support_format_all(a) != ARCHIVE_OK) error_msg_and_die("Couldn't enable read formats"); if ((r = archive_read_open_FILE(a, fh))) error_msg_and_die("archive_read_open_FILE(): %s", archive_error_string(a)); for (;;) { r = archive_read_next_header(a, &entry); if (r == ARCHIVE_EOF) break; if (r != ARCHIVE_OK) error_msg_and_die("archive_read_next_header(): %s", archive_error_string(a)); mode = archive_entry_mode(entry); if (stats) { // depending on the archive, the entry size might not // be set in the first place in which case the // estimate might be totally off filesize = archive_entry_size(entry); switch(mode & S_IFMT) { case S_IFLNK: if(filesize >= 4 * (EXT2_TIND_BLOCK+1)) stats->nblocks += (filesize + BLOCKSIZE - 1) / BLOCKSIZE; stats->ninodes++; break; case S_IFREG: stats->nblocks += (filesize + BLOCKSIZE - 1) / BLOCKSIZE; case S_IFCHR: case S_IFBLK: case S_IFIFO: case S_IFSOCK: stats->ninodes++; break; case S_IFDIR: stats->ninodes++; break; default: break; } continue; } path2 = strdup(archive_entry_pathname(entry)); path3 = strdup(archive_entry_pathname(entry)); name = basename(path2); dir = dirname(path3); if(!(nod = find_path(fs, this_nod, dir))) { error_msg("can't find directory '%s' to create '%s''", dir, name); continue; } uid = archive_entry_uid(entry); gid = archive_entry_gid(entry); if (squash_uids) uid = gid = 0; if (squash_perms) mode &= ~(FM_IRWXG | FM_IRWXO); if(find_dir(fs, nod, name)) chmod_fs(fs, nod, mode, uid, gid); // FIXME: if the entry is a regular file, update // content else { ctime = archive_entry_ctime(entry); mtime = archive_entry_mtime(entry); switch(mode & S_IFMT) { #if HAVE_STRUCT_STAT_ST_RDEV case S_IFCHR: mknod_fs(fs, nod, name, mode|FM_IFCHR, uid, gid, major(archive_entry_rdev(entry)), minor(archive_entry_rdev(entry)), ctime, mtime); break; case S_IFBLK: mknod_fs(fs, nod, name, mode|FM_IFBLK, uid, gid, major(archive_entry_rdev(entry)), minor(archive_entry_rdev(entry)), ctime, mtime); break; #endif case S_IFIFO: mknod_fs(fs, nod, name, mode|FM_IFIFO, uid, gid, 0, 0, ctime, mtime); break; case S_IFSOCK: mknod_fs(fs, nod, name, mode|FM_IFSOCK, uid, gid, 0, 0, ctime, mtime); break; case S_IFLNK: lnk = calloc(1, rndup(strlen(archive_entry_symlink(entry)), BLOCKSIZE)); strcpy(lnk, archive_entry_symlink(entry)); if (lnk == NULL) error_msg_and_die(memory_exhausted); mklink_fs(fs, nod, name, strlen(archive_entry_symlink(entry)), (uint8*)lnk, uid, gid, ctime, mtime); free(lnk); break; case S_IFREG: // we pass a filesize of zero because // libarchive will take care of it for // us and the archive does not // necessarily contain the file size // in the first place mkfile_fs(fs, nod, name, mode, la_read, a, 0, uid, gid, ctime, mtime); break; case S_IFDIR: mkdir_fs(fs, nod, name, mode, uid, gid, ctime, mtime); break; default: // probably a hardlink if (archive_entry_hardlink(entry) != NULL) { if(!(lnknod = find_path(fs, this_nod, archive_entry_hardlink(entry)))) error_msg_and_die("path %s not found in filesystem", archive_entry_hardlink(entry)); add2dir(fs, nod, lnknod, name); } else { error_msg("ignoring entry %s with mode %d", archive_entry_pathname(entry), mode & S_IFMT); } } } free(path2); free(path3); } archive_read_close(a); archive_read_free(a); #endif } // add or fixup entries to the filesystem from a text file /* device table entries take the form of: /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) l Symbolic link I don't bother with hard links (special cases of regular files), or sockets. 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; if (fs && strcmp(path, "/") == 0) { // if the entry modifies the root node, don't call // basename and dirname but chmod the root node // directly if (type != 'd') { error_msg("device table line %d skipped: root node must be a directory", lineno); continue; } mode |= FM_IFDIR; chmod_fs(fs, this_nod, mode, uid, gid); continue; } 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 'l': mode |= FM_IFLNK; 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); } // adds a tree of entries to the filesystem from current dir static void add2fs_from_dir(filesystem *fs, uint32 this_nod, int squash_uids, int squash_perms, 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; off_t filesize; 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); 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: if(st.st_size >= 4 * (EXT2_TIND_BLOCK+1)) stats->nblocks += (st.st_size + BLOCKSIZE - 1) / BLOCKSIZE; stats->ninodes++; break; case S_IFREG: 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, this_nod, squash_uids, squash_perms, fs_timestamp, stats); if (chdir("..") == -1) perror_msg_and_die(".."); 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, nod, squash_uids, squash_perms, fs_timestamp, stats); if (chdir("..") == -1) perror_msg_and_die(".."); } 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(fs, st.st_ino); if (hdlink >= 0) { add2dir(fs, this_nod, fs->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 = calloc(1, rndup(st.st_size, BLOCKSIZE)); if (lnk == NULL) error_msg_and_die(memory_exhausted); if (readlink(dent->d_name, lnk, st.st_size) > 0) mklink_fs(fs, this_nod, name, st.st_size, (uint8*)lnk, uid, gid, ctime, mtime); else error_msg("readlink: %s", dent->d_name); free(lnk); break; case S_IFREG: fh = fopen(dent->d_name, "rb"); fseek(fh, 0, SEEK_END); filesize = ftell(fh); rewind(fh); if (!fh) { error_msg("Unable to open file %s", dent->d_name); break; } nod = mkfile_fs(fs, this_nod, name, mode, fh_read, fh, filesize, 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, nod, squash_uids, squash_perms, fs_timestamp, stats); if (chdir("..") == -1) perror_msg_and_die(".."); break; default: error_msg("ignoring entry %s", name); } if (save_nod) { if (fs->hdlinks.count == fs->hdlink_cnt) { if ((fs->hdlinks.hdl = realloc (fs->hdlinks.hdl, (fs->hdlink_cnt + HDLINK_CNT) * sizeof (struct hdlink_s))) == NULL) { error_msg_and_die("Not enough memory"); } fs->hdlink_cnt += HDLINK_CNT; } fs->hdlinks.hdl[fs->hdlinks.count].src_inode = st.st_ino; fs->hdlinks.hdl[fs->hdlinks.count].dst_nod = nod; fs->hdlinks.count++; } } } closedir(dh); } // Copy size blocks from src to dst, putting holes in the output // file (if possible) if the input block is all zeros. static void copy_file(filesystem *fs, FILE *dst, FILE *src, size_t size) { uint8 *b; b = malloc(BLOCKSIZE); if (!b) error_msg_and_die("copy_file: out of memory"); if (fseek(src, 0, SEEK_SET)) perror_msg_and_die("fseek"); if (ftruncate(fileno(dst), 0)) perror_msg_and_die("copy_file: ftruncate"); while (size > 0) { if (fread(b, BLOCKSIZE, 1, src) != 1) perror_msg_and_die("copy failed on read"); if ((dst != stdout) && fs->holes && is_blk_empty(b)) { /* Empty block, just skip it */ if (fseek(dst, BLOCKSIZE, SEEK_CUR)) perror_msg_and_die("fseek"); } else { if (fwrite(b, BLOCKSIZE, 1, dst) != 1) perror_msg_and_die("copy failed on write"); } size--; } free(b); } // Allocate a new filesystem structure, allocate internal memory, // and initialize the contents. static filesystem * alloc_fs(int swapit, char *fname, uint32 nbblocks, FILE *srcfile) { filesystem *fs; struct stat srcstat, dststat; fs = malloc(sizeof(*fs)); if (!fs) error_msg_and_die("not enough memory for filesystem"); memset(fs, 0, sizeof(*fs)); fs->swapit = swapit; cache_init(&fs->blks, MAX_FREE_CACHE_BLOCKS, blk_elem_val, blk_freed); cache_init(&fs->gds, MAX_FREE_CACHE_GDS, gd_elem_val, gd_freed); cache_init(&fs->blkmaps, MAX_FREE_CACHE_BLOCKMAPS, blkmap_elem_val, blkmap_freed); cache_init(&fs->inodes, MAX_FREE_CACHE_INODES, inode_elem_val, inode_freed); fs->hdlink_cnt = HDLINK_CNT; fs->hdlinks.hdl = calloc(sizeof(struct hdlink_s), fs->hdlink_cnt); if (!fs->hdlinks.hdl) error_msg_and_die("Not enough memory"); fs->hdlinks.count = 0 ; if (strcmp(fname, "-") == 0) fs->f = tmpfile(); else if (srcfile) { if (fstat(fileno(srcfile), &srcstat)) perror_msg_and_die("fstat srcfile"); if (stat(fname, &dststat) == 0 && srcstat.st_ino == dststat.st_ino && srcstat.st_dev == dststat.st_dev) { // source and destination are the same file, don't // truncate or copy, just use the file. fs->f = fopen(fname, "r+b"); } else { fs->f = fopen(fname, "w+b"); if (fs->f) copy_file(fs, fs->f, srcfile, nbblocks); } } else fs->f = fopen(fname, "w+b"); if (!fs->f) perror_msg_and_die("opening %s", fname); return fs; } /* Make sure the output file is the right size */ static void set_file_size(filesystem *fs) { if (ftruncate(fileno(fs->f), ((off_t) fs->sb->s_blocks_count) * BLOCKSIZE)) perror_msg_and_die("set_file_size: ftruncate"); } // initialize an empty filesystem static filesystem * init_fs(int nbblocks, int nbinodes, int nbresrvd, int holes, uint32 fs_timestamp, uint32 creator_os, int swapit, char *fname) { uint32 i; filesystem *fs; dirwalker dw; 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; blk_info *bi; nod_info *ni; groupdescriptor *gd; gd_info *gi; inode_pos ipos; 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; /* On filesystems with 1k block size, the bootloader area uses a full * block. For 2048 and up, the superblock can be fitted into block 0. */ first_block = (BLOCKSIZE == 1024); /* nbblocks is the total number of blocks in the filesystem. * a block group can have no more than 8192 blocks. */ 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; free_blocks = nbblocks - overhead_per_group*nbgroups - first_block; free_blocks_per_group = nbblocks_per_group - overhead_per_group; fs = alloc_fs(swapit, fname, nbblocks, NULL); fs->sb = calloc(1, SUPERBLOCK_SIZE); if (!fs->sb) error_msg_and_die("error allocating header memory"); // 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; fs->sb->s_creator_os = creator_os; set_file_size(fs); // set up groupdescriptors for(i=0, bbmpos=first_block+1+gdsz, ibmpos=bbmpos+1, itblpos=ibmpos+1; i free_blocks_per_group) { gd->bg_free_blocks_count = free_blocks_per_group; free_blocks -= free_blocks_per_group; } else { gd->bg_free_blocks_count = free_blocks; free_blocks = 0; // this is the last block group } if(i) gd->bg_free_inodes_count = nbinodes_per_group; else gd->bg_free_inodes_count = nbinodes_per_group - EXT2_FIRST_INO + 2; gd->bg_used_dirs_count = 0; gd->bg_block_bitmap = bbmpos; gd->bg_inode_bitmap = ibmpos; gd->bg_inode_table = itblpos; put_gd(gi); } /* Mark non-filesystem blocks and inodes as allocated */ /* Mark system blocks and inodes as allocated */ for(i = 0; ibg_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); GRP_PUT_GROUP_BBM(bi); /* Inode bitmap */ ibm = GRP_GET_GROUP_IBM(fs, gd, &bi); //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); GRP_PUT_GROUP_IBM(bi); put_gd(gi); } // make root inode and directory /* We have groups now. Add the root filesystem in group 0 */ /* Also increment the directory count for group 0 */ gd = get_gd(fs, 0, &gi); gd->bg_free_inodes_count--; gd->bg_used_dirs_count = 1; put_gd(gi); itab0 = get_nod(fs, EXT2_ROOT_INO, &ni); itab0->i_mode = FM_IFDIR | FM_IRWXU | FM_IRGRP | FM_IROTH | FM_IXGRP | FM_IXOTH; itab0->i_ctime = fs_timestamp; itab0->i_mtime = fs_timestamp; itab0->i_atime = fs_timestamp; itab0->i_size = BLOCKSIZE; itab0->i_links_count = 2; put_nod(ni); new_dir(fs, EXT2_ROOT_INO, ".", 1, &dw); shrink_dir(&dw, EXT2_ROOT_INO, "..", 2); next_dir(&dw); // Force the data into the buffer inode_pos_init(fs, &ipos, EXT2_ROOT_INO, INODE_POS_EXTEND, NULL); extend_inode_blk(fs, &ipos, dir_data(&dw), 1); inode_pos_finish(fs, &ipos); put_dir(&dw); // make lost+found directory if(fs->sb->s_r_blocks_count) { inode *node; uint8 *b; nod = mkdir_fs(fs, EXT2_ROOT_INO, "lost+found", FM_IRWXU, 0, 0, fs_timestamp, fs_timestamp); b = get_workblk(); memset(b, 0, BLOCKSIZE); ((directory*)b)->d_rec_len = swapit ? swab16(BLOCKSIZE) : BLOCKSIZE; inode_pos_init(fs, &ipos, nod, INODE_POS_EXTEND, NULL); // It is always 16 blocks to start out with for(i = 1; i < 16; i++) extend_inode_blk(fs, &ipos, b, 1); inode_pos_finish(fs, &ipos); free_workblk(b); node = get_nod(fs, nod, &ni); node->i_size = 16 * BLOCKSIZE; put_nod(ni); } // administrative info fs->sb->s_state = 1; fs->sb->s_max_mnt_count = 20; // options for me fs->holes = holes; return fs; } // loads a filesystem from disk static filesystem * load_fs(FILE *fh, int swapit, char *fname) { off_t fssize; filesystem *fs; if((fseek(fh, 0, SEEK_END) < 0) || ((fssize = ftello(fh)) == -1)) perror_msg_and_die("input filesystem image"); rewind(fh); if ((fssize % BLOCKSIZE) != 0) error_msg_and_die("Input file not a multiple of block size"); fssize /= BLOCKSIZE; if(fssize < 16) // totally arbitrary error_msg_and_die("too small filesystem"); fs = alloc_fs(swapit, fname, fssize, fh); /* Read and check the superblock, then read the superblock * and all the group descriptors */ fs->sb = malloc(SUPERBLOCK_SIZE); if (!fs->sb) error_msg_and_die("error allocating header memory"); if (fseek(fs->f, SUPERBLOCK_OFFSET, SEEK_SET)) perror_msg_and_die("fseek"); if (fread(fs->sb, SUPERBLOCK_SIZE, 1, fs->f) != 1) perror_msg_and_die("fread filesystem image superblock"); if(swapit) swap_sb(fs->sb); if((fs->sb->s_rev_level > 1) || (fs->sb->s_magic != EXT2_MAGIC_NUMBER)) error_msg_and_die("not a suitable ext2 filesystem"); if (fs->sb->s_rev_level > 0) { if (fs->sb->s_first_ino != EXT2_GOOD_OLD_FIRST_INO) error_msg_and_die("First inode incompatible"); if (fs->sb->s_inode_size != EXT2_GOOD_OLD_INODE_SIZE) error_msg_and_die("inode size incompatible"); if (fs->sb->s_feature_compat) error_msg_and_die("Unsupported compat features"); if (fs->sb->s_feature_incompat) error_msg_and_die("Unsupported incompat features"); if (fs->sb->s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_LARGE_FILE) error_msg_and_die("Unsupported ro compat features"); } set_file_size(fs); return fs; } static void free_fs(filesystem *fs) { free(fs->hdlinks.hdl); fclose(fs->f); free(fs->sb); 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; nod_info *ni; inode *node = get_nod(fs, nod, &ni); int32 fsize = node->i_size; blk_info *bi; 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, &bi), ((uint32)fsize > BLOCKSIZE) ? BLOCKSIZE : (uint32)fsize, 1, f) != 1) error_msg_and_die("error while saving inode %d", nod); put_blk(bi); fsize -= BLOCKSIZE; } put_nod(ni); } // print block/char device minor and major static void print_dev(filesystem *fs, uint32 nod) { int minor, major; nod_info *ni; inode *node = get_nod(fs, nod, &ni); if (fs->swapit) { minor = ((uint8*)node->i_block)[3]; major = ((uint8*)node->i_block)[2]; } else { minor = ((uint8*)node->i_block)[0]; major = ((uint8*)node->i_block)[1]; } put_nod(ni); 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; dirwalker dw; for (d = get_dir(fs, bk, &dw); d; d = next_dir(&dw)) if(d->d_inode) { printf("entry '"); fwrite(dir_name(&dw), 1, d->d_name_len, stdout); printf("' (inode %d): rec_len: %d (name_len: %d)\n", d->d_inode, d->d_rec_len, d->d_name_len); } put_dir(&dw); } } // print a symbolic link static void print_link(filesystem *fs, uint32 nod) { nod_info *ni; inode *node = get_nod(fs, nod, &ni); if (!node->i_blocks) { if (fs->swapit) { uint32 *buf = malloc(4 * (EXT2_TIND_BLOCK + 1)); if (buf == NULL) error_msg_and_die(memory_exhausted); swab32_into(buf, node->i_block, EXT2_TIND_BLOCK + 1); printf("links to '%s'\n", (char*) buf); free(buf); } else { printf("links to '%s'\n", (char*) node->i_block); } } else { printf("links to '"); write_blocks(fs, nod, stdout); printf("'\n"); } put_nod(ni); } // 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]; nod_info *ni; inode *node = get_nod(fs, nod, &ni); blk_info *bi; gd_info *gi; if(!node->i_mode) goto out; 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, node->i_links_count); if(!allocated(GRP_GET_INODE_BITMAP(fs,nod,&bi,&gi), GRP_IBM_OFFSET(fs,nod))) { GRP_PUT_INODE_BITMAP(bi,gi); printf("unallocated\n"); goto out; } GRP_PUT_INODE_BITMAP(bi,gi); make_perms(node->i_mode, perms); printf("%s, size: %d byte%s (%d block%s)\n", perms, plural(node->i_size), plural(node->i_blocks / INOBLK)); switch(node->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); out: put_nod(ni); } // describes various fields in a filesystem static void print_fs(filesystem *fs) { uint32 i, j; blk_info *bi; groupdescriptor *gd; gd_info *gi; 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); gd = get_gd(fs, i, &gi); printf("block bitmap: block %d,inode bitmap: block %d, inode table: block %d\n", gd->bg_block_bitmap, gd->bg_inode_bitmap, gd->bg_inode_table); printf("block bitmap allocation:\n"); print_bm(GRP_GET_GROUP_BBM(fs, gd, &bi),fs->sb->s_blocks_per_group); GRP_PUT_GROUP_BBM(bi); printf("inode bitmap allocation:\n"); ibm = GRP_GET_GROUP_IBM(fs, gd, &bi); print_bm(ibm, fs->sb->s_inodes_per_group); for (j = 1; j <= fs->sb->s_inodes_per_group; j++) if (allocated(ibm, j)) print_inode(fs, j); GRP_PUT_GROUP_IBM(bi); put_gd(gi); } } static void finish_fs(filesystem *fs) { if (cache_flush(&fs->inodes)) error_msg_and_die("entry mismatch on inode cache flush"); if (cache_flush(&fs->blkmaps)) error_msg_and_die("entry mismatch on blockmap cache flush"); if (cache_flush(&fs->gds)) error_msg_and_die("entry mismatch on gd cache flush"); if (cache_flush(&fs->blks)) error_msg_and_die("entry mismatch on block cache flush"); if(fs->swapit) swap_sb(fs->sb); if (fseek(fs->f, SUPERBLOCK_OFFSET, SEEK_SET)) perror_msg_and_die("fseek"); if(fwrite(fs->sb, SUPERBLOCK_SIZE, 1, fs->f) != 1) perror_msg_and_die("output filesystem superblock"); if(fs->swapit) swap_sb(fs->sb); } static void populate_fs(filesystem *fs, struct fslayer *fslayers, int nlayers, int squash_uids, int squash_perms, uint32 fs_timestamp, struct stats *stats) { int i; for(i = 0; i < nlayers; i++) { struct stat st; FILE *fh; int pdir; char *pdest; uint32 nod = EXT2_ROOT_INO; if((pdest = strchr(fslayers[i].path, ':'))) { *(pdest++) = 0; if(fs && !(nod = find_path(fs, EXT2_ROOT_INO, pdest))) error_msg_and_die("path %s not found in filesystem", pdest); } /* do not compute stats when input is to be read from stdin */ if (stats != NULL && strcmp(fslayers[i].path, "-") == 0) { continue; } stat(fslayers[i].path, &st); switch(fslayers[i].type) { case FSLAYER_TABLE: if(strcmp(fslayers[i].path, "-") == 0) fh = stdin; else if((st.st_mode & S_IFMT) != S_IFREG) error_msg_and_die("%s should be a file", fslayers[i].path); else fh = xfopen(fslayers[i].path, "rb"); if(fs) fprintf(stderr, "nodes fixup and creation from device table %s\n", fslayers[i].path); add2fs_from_file(fs, nod, fh, fs_timestamp, stats); if(strcmp(fslayers[i].path, "-") != 0) fclose(fh); break; case FSLAYER_DIR: if((st.st_mode & S_IFMT) != S_IFDIR) error_msg_and_die("%s should be a directory", fslayers[i].path); if(fs) fprintf(stderr, "copying from directory %s\n", fslayers[i].path); if((pdir = open(".", O_RDONLY)) < 0) perror_msg_and_die("."); if(chdir(fslayers[i].path) < 0) perror_msg_and_die(fslayers[i].path); add2fs_from_dir(fs, nod, squash_uids, squash_perms, fs_timestamp, stats); if(fchdir(pdir) < 0) perror_msg_and_die("fchdir"); if(close(pdir) < 0) perror_msg_and_die("close"); break; case FSLAYER_TAR: if(strcmp(fslayers[i].path, "-") == 0) fh = stdin; else if((st.st_mode & S_IFMT) != S_IFREG) error_msg_and_die("%s should be a file", fslayers[i].path); else fh = xfopen(fslayers[i].path, "rb"); if(fs) fprintf(stderr, "copying from tar archive %s\n", fslayers[i].path); add2fs_from_tarball(fs, nod, fh, squash_uids, squash_perms, fs_timestamp, stats); if(strcmp(fslayers[i].path, "-") != 0) fclose(fh); break; } } } 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 \n" " -d, --root [:path] Copy from a local directory into path (or root)\n" " -D, --devtable [:path] Add or fixup nodes from a device table into path (or root)\n" " -a, --tarball [:path] Copy from a tar archive into path (or root)\n" " -B, --block-size \n" " -b, --size-in-blocks \n" " -i, --bytes-per-inode \n" " -N, --number-of-inodes \n" " -L, --volume-label \n" " -m, --reserved-percentage \n" " -o, --creator-os 'linux' (default), 'hurd', 'freebsd' or number.\n" " -g, --block-map Generate a block map file for this path.\n" " -e, --fill-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" " -h, --help\n" " -V, --version\n" " -v, --verbose\n\n" "Report bugs to https://github.com/bestouff/genext2fs/issues\n", app_name); } #define MAX_DOPT 128 #define MAX_GOPT 128 #define MAX_FILENAME 255 extern char* optarg; extern int optind, opterr, optopt; // parse the value for -o int lookup_creator_os(const char *name) { if (isdigit (*name)) return atoi(name); else if (strcasecmp(name, "linux") == 0) return EXT2_OS_LINUX; else if (strcasecmp(name, "GNU") == 0 || strcasecmp(name, "hurd") == 0) return EXT2_OS_HURD; else if (strcasecmp(name, "freebsd") == 0) return EXT2_OS_FREEBSD; else if (strcasecmp(name, "lites") == 0) return EXT2_OS_LITES; else return EXT2_OS_LINUX; } int main(int argc, char **argv) { long long nbblocks = -1; int nbinodes = -1; int nbresrvd = -1; float bytes_per_inode = -1; float reserved_frac = -1; int fs_timestamp = -1; int creator_os = CREATOR_OS; char * fsout = "-"; char * fsin = 0; struct fslayer layers[MAX_DOPT]; int nlayers = 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; uint16 endian = 1; int bigendian = !*(char*)&endian; char *volumelabel = NULL; 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' }, { "tarball", required_argument, NULL, 'a' }, { "block-size", required_argument, NULL, 'B' }, { "size-in-blocks", required_argument, NULL, 'b' }, { "bytes-per-inode", required_argument, NULL, 'i' }, { "number-of-inodes", required_argument, NULL, 'N' }, { "volume-label", required_argument, NULL, 'L' }, { "reserved-percentage", required_argument, NULL, 'm' }, { "creator-os", required_argument, NULL, 'o' }, { "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' }, { "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:a:B:b:i:N:L:m:o:g:e:zfqUPhVv", longopts, NULL)) != EOF) { #else app_name = argv[0]; while((c = getopt(argc, argv, "x:d:D:a:B:b:i:N:L:m:o:g:e:zfqUPhVv")) != EOF) { #endif /* HAVE_GETOPT_LONG */ switch(c) { case 'x': fsin = optarg; break; case 'd': layers[nlayers].type = FSLAYER_DIR; layers[nlayers++].path = optarg; break; case 'D': layers[nlayers].type = FSLAYER_TABLE; layers[nlayers++].path = optarg; break; case 'a': layers[nlayers].type = FSLAYER_TAR; layers[nlayers++].path = optarg; break; case 'B': blocksize = SI_atof(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 'L': volumelabel = optarg; break; case 'm': reserved_frac = SI_atof(optarg) / 100; break; case 'o': creator_os = lookup_creator_os(optarg); 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 '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."); fsout = argv[optind]; if(blocksize != 1024 && blocksize != 2048 && blocksize != 4096) error_msg_and_die("Valid block sizes: 1024, 2048 or 4096."); if(creator_os < 0) error_msg_and_die("Creator OS unknown."); int numstdin = 0; for(i = 0; i < nlayers; i++) if (strcmp(layers[i].path, "-") == 0) numstdin++; if (numstdin == 1 && nbinodes == -1 && bytes_per_inode == -1) fprintf(stderr, "Cannot count the required inodes for input from stdin -- use the -N or -i options to set the number of inodes or work with temporary files."); if (numstdin > 1) error_msg_and_die("only one input can come from stdin"); if(fsin) { fprintf(stderr, "starting from existing image %s", fsin); if(strcmp(fsin, "-")) { FILE * fh = xfopen(fsin, "rb"); fs = load_fs(fh, bigendian, fsout); fclose(fh); } else fs = load_fs(stdin, bigendian, fsout); } 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, layers, nlayers, squash_uids, squash_perms, 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) { char *source_date_epoch = getenv("SOURCE_DATE_EPOCH"); if (source_date_epoch == NULL) { fs_timestamp = time(NULL); } else { fs_timestamp = strtoll(source_date_epoch, NULL, 10); } } fs = init_fs(nbblocks, nbinodes, nbresrvd, holes, fs_timestamp, creator_os, bigendian, fsout); fs_upgrade_rev1_largefile(fs); } if (volumelabel != NULL) strncpy((char *)fs->sb->s_volume_name, volumelabel, sizeof(fs->sb->s_volume_name)); populate_fs(fs, layers, nlayers, squash_uids, squash_perms, fs_timestamp, NULL); if(emptyval) { uint32 b; for(b = 1; b < fs->sb->s_blocks_count; b++) { blk_info *bi; gd_info *gi; if(!allocated(GRP_GET_BLOCK_BITMAP(fs,b,&bi,&gi), GRP_BBM_OFFSET(fs,b))) { blk_info *bi2; memset(get_blk(fs, b, &bi2), emptyval, BLOCKSIZE); put_blk(bi2); } GRP_PUT_BLOCK_BITMAP(bi,gi); } } if(verbose) print_fs(fs); for(i = 0; i < gidx; i++) { uint32 nod; char fname[MAX_FILENAME]; char *p; FILE *fh; nod_info *ni; 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, &ni)->i_size); put_nod(ni); flist_blocks(fs, nod, fh); fclose(fh); } finish_fs(fs); if(strcmp(fsout, "-") == 0) copy_file(fs, stdout, fs->f, fs->sb->s_blocks_count); free_fs(fs); return 0; } genext2fs-1.5.0/list.h000066400000000000000000000032561367512432200145610ustar00rootroot00000000000000#ifndef __LIST_H__ #define __LIST_H__ #if STDC_HEADERS # include # include #else # if HAVE_STDLIB_H # include # endif # if HAVE_STDDEF_H # include # endif #endif #ifndef offsetof #define offsetof(st, m) \ ((size_t) ( (char *)&((st *)(0))->m - (char *)0 )) #endif #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) typedef struct list_elem { struct list_elem *next; struct list_elem *prev; } list_elem; static inline void list_init(list_elem *list) { list->next = list; list->prev = list; } static inline void list_add_after(list_elem *pos, list_elem *elem) { elem->next = pos->next; elem->prev = pos; pos->next->prev = elem; pos->next = elem; } static inline void list_add_before(list_elem *pos, list_elem *elem) { elem->prev = pos->prev; elem->next = pos; pos->prev->next = elem; pos->prev = elem; } static inline void list_del(list_elem *elem) { elem->next->prev = elem->prev; elem->prev->next = elem->next; } static inline void list_item_init(list_elem *elem) { elem->next = elem; elem->prev = elem; } static inline int list_empty(list_elem *elem) { return elem->next == elem; } #define list_for_each_elem(list, curr) \ for ((curr) = (list)->next; (curr) != (list); (curr) = (curr)->next) #define list_for_each_elem_safe(list, curr, next) \ for ((curr) = (list)->next, (next) = (curr)->next; \ (curr) != (list); \ (curr) = (next), (next) = (curr)->next) #endif /* __LIST_H__ */ genext2fs-1.5.0/m4/000077500000000000000000000000001367512432200137475ustar00rootroot00000000000000genext2fs-1.5.0/m4/ac_func_scanf_can_malloc.m4000066400000000000000000000027101367512432200211310ustar00rootroot00000000000000dnl AC_FUNC_SCANF_CAN_MALLOC macro dnl dnl (c) Finn Thain 2006 dnl Copying and distribution of this file, with or without modification, dnl are permitted in any medium without royalty provided the copyright dnl notice and this notice are preserved. # AC_FUNC_SCANF_CAN_MALLOC() # -------------------------------------- AC_DEFUN([AC_FUNC_SCANF_CAN_MALLOC], [ AC_CHECK_HEADERS([stdlib.h]) AC_CACHE_CHECK([whether scanf can malloc], [ac_cv_scanf_can_malloc], [ AC_RUN_IFELSE( [ AC_LANG_PROGRAM( [ #include #if STDC_HEADERS || HAVE_STDLIB_H #include #endif ], [ union { float f; char *p; } u; char *p; u.f = 0; char *scan_this = "56789"; int matched = sscanf(scan_this, "%as", &u); if(matched < 1) return 1; /* shouldn't happens */ if(u.f == (float)56789) return 2; p = u.p; while(*scan_this && *p == *scan_this) { ++p; ++scan_this; }; free(u.p); if(*scan_this == 0) return 0; return 3; ]) ], [ac_cv_scanf_can_malloc=yes], [ac_cv_scanf_can_malloc=no], [ case $host_alias in *-*-linux* ) ac_cv_scanf_can_malloc=yes ;; *-*-solaris* ) ac_cv_scanf_can_malloc=no ;; *-*-darwin* ) ac_cv_scanf_can_malloc=no ;; * ) ac_cv_scanf_can_malloc=no ;; esac ]) ]) if test x$ac_cv_scanf_can_malloc = "xyes"; then AC_DEFINE([SCANF_CAN_MALLOC], 1, [Define to 1 if the scanf %a conversion format mallocs a buffer. Undefine if %a format denotes a float.]) fi ]) genext2fs-1.5.0/m4/ac_func_snprintf.m4000066400000000000000000000046211367512432200175350ustar00rootroot00000000000000dnl From http://autoconf-archive.cryp.to/ dnl @synopsis AC_FUNC_SNPRINTF dnl dnl Checks for a fully C99 compliant snprintf, in particular checks dnl whether it does bounds checking and returns the correct string dnl length; does the same check for vsnprintf. If no working snprintf dnl or vsnprintf is found, request a replacement and warn the user dnl about it. Note: the mentioned replacement is freely available and dnl may be used in any project regardless of it's licence (just like dnl the autoconf special exemption). dnl dnl @category C dnl @author Rüdiger Kuhlmann dnl @version 2002-09-26 dnl @license AllPermissive AC_DEFUN([AC_FUNC_SNPRINTF], [AC_CHECK_FUNCS(snprintf vsnprintf) AC_MSG_CHECKING(for working snprintf) AC_CACHE_VAL(ac_cv_have_working_snprintf, [AC_TRY_RUN( [#include int main(void) { char bufs[5] = { 'x', 'x', 'x', '\0', '\0' }; char bufd[5] = { 'x', 'x', 'x', '\0', '\0' }; int i; i = snprintf (bufs, 2, "%s", "111"); if (strcmp (bufs, "1")) exit (1); if (i != 3) exit (1); i = snprintf (bufd, 2, "%d", 111); if (strcmp (bufd, "1")) exit (1); if (i != 3) exit (1); exit(0); }], ac_cv_have_working_snprintf=yes, ac_cv_have_working_snprintf=no, ac_cv_have_working_snprintf=cross)]) AC_MSG_RESULT([$ac_cv_have_working_snprintf]) AC_MSG_CHECKING(for working vsnprintf) AC_CACHE_VAL(ac_cv_have_working_vsnprintf, [AC_TRY_RUN( [#include #include int my_vsnprintf (char *buf, const char *tmpl, ...) { int i; va_list args; va_start (args, tmpl); i = vsnprintf (buf, 2, tmpl, args); va_end (args); return i; } int main(void) { char bufs[5] = { 'x', 'x', 'x', '\0', '\0' }; char bufd[5] = { 'x', 'x', 'x', '\0', '\0' }; int i; i = my_vsnprintf (bufs, "%s", "111"); if (strcmp (bufs, "1")) exit (1); if (i != 3) exit (1); i = my_vsnprintf (bufd, "%d", 111); if (strcmp (bufd, "1")) exit (1); if (i != 3) exit (1); exit(0); }], ac_cv_have_working_vsnprintf=yes, ac_cv_have_working_vsnprintf=no, ac_cv_have_working_vsnprintf=cross)]) AC_MSG_RESULT([$ac_cv_have_working_vsnprintf]) if test x$ac_cv_have_working_snprintf$ac_cv_have_working_vsnprintf != "xyesyes"; then AC_LIBOBJ(snprintf) AC_MSG_WARN([Will use fallback (v)snprintf() implementation.]) AC_DEFINE(PREFER_PORTABLE_SNPRINTF, 1, "enable replacement (v)snprintf if system (v)snprintf is broken") fi]) genext2fs-1.5.0/test-gen.lib000066400000000000000000000056761367512432200156630ustar00rootroot00000000000000#!/bin/sh # These routines contain the filesystem generation code. # This code is sourced by the other scripts so that digest # generation is consistent. LC_ALL=C export LC_ALL origin_dir="$(dirname "$(realpath "$0")")" test_dir=t_tmp_dir test_img=t_tmp_ext2.img gen_cleanup () { rm -rf $test_dir $test_img } gen_setup () { gen_cleanup mkdir $test_dir || exit 1 } calc_digest () { digest=`md5sum $test_img 2>/dev/null | cut -f 1 -d " "` if [ x$digest != x ] ; then echo $digest else digest=`md5 $test_img 2>/dev/null | cut -f 4 -d " "` echo $digest fi } # dgen - Exercises the -d option of genext2fs. # Creates an image with a file of given size. dgen () { blocks=$1; blocksz=$2; size=$3 echo Testing $blocks blocks of $blocksz bytes with file of size $size gen_setup cd $test_dir if [ x$size = x0 ]; then > file.$size else dd if=/dev/zero of=file.$size bs=$size count=1 2>/dev/null fi chmod 777 file.$size TZ=UTC-11 touch -t 200502070321.43 file.$size . cd .. ./genext2fs -B $blocksz -N 17 -b $blocks -d $test_dir -f -o Linux -q $test_img } # fgen - Exercises the -D option of genext2fs. # Creates an image with the devices listed in the given spec file. fgen () { stdin=$1; blocks=$2; fname=$4 echo Testing $blocks blocks with with devices file $fname gen_setup cp $origin_dir/$fname $test_dir TZ=UTC-11 touch -t 200502070321.43 $test_dir/$fname if [ "$stdin" = "y" ]; then ./genext2fs -N 92 -b $blocks -D - -f -o Linux $test_img < $test_dir/$fname else ./genext2fs -N 92 -b $blocks -D $test_dir/$fname -f -o Linux $test_img fi } # lgen - Exercises the -d option of genext2fs, with symlink # and a device table # Creates an image with a symlink of variable length, then # uses a device table to change its uid/gid # NB: some systems including early versions of Mac OS X cannot # change symlink timestamps; this test will fail on those systems. lgen () { stdin=$1; blocks=$2; blocksz=$3; appendage=$4; devtable=$5 echo Testing $blocks blocks of $blocksz bytes with symlink ...$appendage and devices file $devtable gen_setup cd $test_dir target=12345678901234567890123456789012345678901234567890$appendage ln -s $target symlink TZ=UTC-11 touch -h -t 201309241353.59 symlink . cd .. if [ "$stdin" = "y" ]; then ./genext2fs -B $blocksz -N 234 -b $blocks -d $test_dir -D - -f -o Linux -q $test_img < $origin_dir/$devtable else ./genext2fs -B $blocksz -N 234 -b $blocks -d $test_dir -D $origin_dir/$devtable -f -o Linux -q $test_img fi } # agen - Exercises the -a option of genext2fs. # Creates an image with a file of given size. agen () { stdin=$1; blocks=$2; blocksz=$3; tarball=$4 echo Testing $blocks blocks of $blocksz bytes with tarball gen_setup echo $tarball | base64 -d | gzip -dc > "$test_dir/input.tar" if [ "$stdin" = "y" ]; then ./genext2fs -B $blocksz -N 17 -b $blocks -a - -f -o Linux $test_img < "$test_dir/input.tar" else ./genext2fs -B $blocksz -N 17 -b $blocks -a "$test_dir/input.tar" -f -o Linux $test_img fi } genext2fs-1.5.0/test-mount.sh000077500000000000000000000051741367512432200161140ustar00rootroot00000000000000#!/bin/sh # Use this script if you need to regenerate the digest values # in test.sh, or if you don't care about digests and you just # want to see some fsck results. Should be run as root. # Each test here creates a test image, verifies it and prints # the command line for use in test.sh for regression testing. set -e origin_dir="$(dirname "$(realpath "$0")")" . $origin_dir/test-gen.lib test_mnt=t_mnt test_common () { /sbin/e2fsck -fn $test_img || fail rm -rf $test_mnt mkdir $test_mnt mount -t ext2 -o ro,loop $test_img $test_mnt || fail } test_cleanup () { umount $test_mnt rmdir $test_mnt rm -f fout lsout stout esout } fail () { echo FAIL test_cleanup gen_cleanup exit 1 } pass () { cmd=$1 shift md5=`calc_digest` echo PASS echo $cmd $md5 $@ test_cleanup gen_cleanup } # dtest_mount - Exercise the -d option of genext2fs. dtest_mount () { size=$3 dgen $@ test_common test -f $test_mnt/file.$size || fail test $size = "`ls -al $test_mnt | \ grep file.$size | \ awk '{print $5}'`" || fail pass dtest $@ } # ftest_mount - Exercise the -D option of genext2fs. ftest_mount () { fname=$3 fgen $@ test_common test -d $test_mnt/dev || fail # Exclude those devices that have interpolated # minor numbers, as being too hard to match. egrep -v "(hda|hdb|tty|loop|ram|ubda)" $fname | \ grep '^[^ #]* [bc]' | \ awk '{print $1,$4,$5,$6","$7}'| \ sort -d -k3.6 > fout ls -aln $test_mnt/dev | \ egrep -v "(hda|hdb|tty|loop|ram|ubda)" | \ grep ^[bc] | \ awk '{ print "/dev/"$10,$3,$4,$5$6}' | \ sort -d -k3.6 > lsout diff fout lsout || fail pass ftest $@ } # ltest_mount - Exercise the -d option of genext2fs, with symlink and device table. ltest_mount () { appendage=$3 lgen $@ test_common cd $test_mnt readlink symlink > ../lsout stat -c "%u %g" symlink > ../stout cd .. test -s lsout || fail echo 12345678901234567890123456789012345678901234567890$appendage > fout diff fout lsout || fail echo "77 7" > esout diff stout esout || fail pass ltest $@ } dtest_mount 4096 1024 0 dtest_mount 2048 2048 0 dtest_mount 1024 4096 0 dtest_mount 8193 1024 0 dtest_mount 8194 1024 0 dtest_mount 8193 4096 0 dtest_mount 8194 2048 0 dtest_mount 4096 1024 1 dtest_mount 1024 4096 1 dtest_mount 4096 1024 12288 dtest_mount 4096 1024 274432 dtest_mount 9000 1024 8388608 dtest_mount 4500 2048 8388608 dtest_mount 2250 4096 8388608 dtest_mount 20000 1024 16777216 dtest_mount 10000 2048 16777216 ftest_mount 4096 default device_table.txt ltest_mount 200 1024 123456789 device_table_link.txt ltest_mount 200 1024 1234567890 device_table_link.txt ltest_mount 200 4096 12345678901 device_table_link.txt genext2fs-1.5.0/test.sh000077500000000000000000000054301367512432200147470ustar00rootroot00000000000000#!/bin/sh # This script generates a variety of filesystems and checks that they # are identical to ones that are known to be mountable, and pass fsck # and various other sanity checks. # Passing these tests is preferable to passing test-mount.sh because # this script doesn't require root, and because passing these tests # guarantees byte-for-byte agreement with other builds, ports, # architectures, times of day etc. set -e origin_dir="$(dirname "$(realpath "$0")")" . $origin_dir/test-gen.lib md5cmp () { md5=`calc_digest` if [ x$md5 = x$1 ] ; then echo PASS else echo FAIL exit 1 fi } dtest () { expected_digest=$1 shift dgen $@ md5cmp $expected_digest gen_cleanup } ftest () { expected_digest=$1 shift fgen y $@ md5cmp $expected_digest fgen n $@ md5cmp $expected_digest gen_cleanup } ltest () { expected_digest=$1 shift lgen y $@ md5cmp $expected_digest lgen n $@ md5cmp $expected_digest gen_cleanup } atest() { expected_digest=$1 shift agen y $@ md5cmp $expected_digest agen n $@ md5cmp $expected_digest gen_cleanup } # NB: always use test-mount.sh to regenerate these digests, that is, # replace the following lines with the output of # sudo sh test-mount.sh|grep test dtest d28c461a408de69eef908045a11207ec 4096 1024 0 dtest 8501cc97857245d16aaf4b8a06345fc2 2048 2048 0 dtest 1fc39a40fa808aa974aa3065f1862067 1024 4096 0 dtest a6bc1b4937db12944a9ae496da1ba65c 8193 1024 0 dtest 21d8675cb8b8873540fa3d911dddca7e 8194 1024 0 dtest 346ecb90c1ca7a1d6e5fbe57383e1055 8193 4096 0 dtest b2672c5deb9cdf598db2c11118b5c91a 8194 2048 0 dtest 1e950ef4f2719cd2d4f70de504c88244 4096 1024 1 dtest 4a47abd795e5282a1762e1fa8b22a432 1024 4096 1 dtest 3e229c70850d2388c172124b05e93ddc 4096 1024 12288 dtest 495cda3636eb0f42aceb9d63bc34690b 4096 1024 274432 dtest fe792a70e336ed1e7a29aee7ecdeb0ab 9000 1024 8388608 dtest 2375e7344dfa1550583ea25d30bc02bf 4500 2048 8388608 dtest 4dedea56398027fe3267ebc602b9a6b6 2250 4096 8388608 dtest c41835904c45320731aab685436ba8f6 20000 1024 16777216 dtest 662529e81e6106288aec9516bcefe109 10000 2048 16777216 ftest 3db16dd57bd15c1c80dd6bc900411c58 4096 default device_table.txt ltest c21b5a3cad405197e821ba7143b0ea9b 200 1024 123456789 device_table_link.txt ltest 18b04b4bea2f7ebf315a116be5b92589 200 1024 1234567890 device_table_link.txt ltest 8aaed693b75dbdf77607f376d661027a 200 4096 12345678901 device_table_link.txt atest 994ca42d3179c88263af777bedec0c55 200 1024 H4sIAAAAAAAAA+3WTW6DMBAF4Fn3FD6B8fj3PKAqahQSSwSk9vY1uKssGiJliFretzECJAYeY1s3JM4UKYRlLG7H5ZhdTIHZGevK+ZTYkgrypRFN17EdlKIh5/G3++5d/6N004qbA47er8/fWVduV2aLD7D7/A85C88Ba/ufA/sQIhk25VdA/2+h5t+1gx4/pd7vfv+Hm/ytmfNH/8vr+ql7e3UR8DK6uUx9L/uMtev/3P8p+KX/oyHlZMuqntX/9T34Z9yk9Gco8//xkGWf8Uj+Mbpl/Y+JVJQtq9r5/K+bj3Z474+Xk9wG4JH86/rvyzxAirfYnOw+/+vXWTb+uv9PaV3+JfiSv/WOlJVPf/f5AwAAAAAAAAAAAMD/9A0cPbO/ACgAAA==