FreeWRT Configuration Filesystem ════════════════════════════════ Specification Document Version 1.04 - 2 July 2007 Copyright © 2006, 2007 Thorsten Glaser Provided that these terms and disclaimer and all copyright notices are retained or reproduced in an accompanying document, permission is granted to deal in this work without restriction, including un- limited rights to use, publicly perform, distribute, sell, modify, merge, give away, or sublicence. Advertising materials mentioning features or use of this work must display the following acknowledgement: This product includes material provided by Thorsten Glaser for the FreeWRT Project. This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to the utmost extent permitted by applicable law, neither express nor implied; without malicious intent or gross negligence. In no event may a licensor, author or contributor be held liable for indirect, direct, other damage, loss, or other issues arising in any way out of dealing in the work, even if advised of the possibility of such damage or existence of a defect, except proven that it results out of said person's immediate fault when using the work as intended. 1. Abstract ――――――――――― FreeWRT is an operating system for embedded devices. At the moment, it provides a uClibc/GNU/Linux-based operating environment for mips- based hardware routers, e.g. from Linksys or Asus. FreeWRT operates on flash memory and as such is under constraints to reduce the amount of write operations to the root filesystem, because flash memory has limited lifetime. Changing the file-based configura- tion in /etc, however, often requires a fair amount of write opera- tions; furthermore, usual reconfiguration operations change more than only one file, possibly erasing and re-writing the same flash memory block several times. In addition, in between these changes, the sy- stem is in an inconsistent state, and, if the configuration changes render the system unusable, a simple reboot will not be able to fix it, a full reflash and reconfiguration is required. My proposed implementation will present /etc as a memory filesystem, loaded at boot with the content of the underlying /etc from the de- fault root filesystem (usually on squashfs or jffs2), then populated with additional files read from a custom flash partition in the be- low documented format. Changes to /etc will never be reflected in the underlying root filesystem, and the fwcf partition is only updated by a userland programme to be run manually. 2. Implementation details ――――――――――――――――――――――――― The size of the flash partition has been set by the FreeWRT project to 128 KiB (usually two flash blocks). A custom flash map driver has been added to the FreeWRT kernel before the import of fwcf. The command-line utility will support three operations: • fwcf setup to be run by the rc bootup script early • fwcf commit similar to Cisco ‘write’ • fwcf erase similar to Cisco ‘erase startup-config’ • fwcf status NEW IN 1.03: check if commit is needed • fwcf dump NEW IN 1.03: make a backup of the fwcf filesystem • fwcf restore NEW IN 1.03: restore a previously made backup • halt \ • poweroff > NEW IN 1.04: wrapper around busybox • reboot / This utility is implemented as rapid prototype as a shell script in ash, using one C helper programme. Later versions will be pure C. 2.1. Operation of ‘fwcf setup’ ―――――――――――――――――――――――――――――― This command will first remap the existing /etc (via ‘mount --bind’) to /tmp/.fwcf/root. Then, it will create a memory filesystem (tmpfs) at /tmp/.fwcf/temp and populate it with all files from /tmp/.fwcf/root. Now, the fwcf flash partition will be read, the format and checksum verified and data extracted to /tmp/.fwcf/temp, possibly overwriting pre-existing files†. Then, the /tmp/.fwcf/temp filesystem will be re- bound to /etc and, finally, the mountpoint at /tmp/.fwcf/temp unloaded. NEW IN 1.03: If /etc/.fwcf_deleted exists, the files listed in it, newline-separated, will be removed from and relative to /etc, then the file itself will be removed. Data from the end of the fwcf data in the flash partition to the end of the 64 KiB block the end of data resides in will be written to /dev/urandom. NEW IN 1.03: Afterwards, a sorted list of all files is given to the busybox md5sum applet, the output is stored as /tmp/.fwcf/status.asz If the “fwcf” mtd partition does not start with the four letters FWCF on invoking ‘fwcf setup’, it is erased (i.e. populated with an empty FWCF filesystem). NEW IN 1.03: If run with ‘-N’, it will not read out the data from flash and force an “unclean startup”, as described below. †) NEW IN 1.03: If this fails, but the “fwcf” mtd partition starts with FWCF, i.e. we cannot read the flash filesystem, possibly because it's from an incompatible format or unknown compressor, a flag file is created as /etc/.fwcf_unclean to prevent a following commit which would lead to data loss. The user must remove this file to override. 2.2. Operation of ‘fwcf commit’ ――――――――――――――――――――――――――――――― A new memory filesystem (tmpfs) will be createt at /tmp/.fwcf/temp and populated with the data currently in /etc. Now, NEW IN 1.03, the /tmp/.fwcf/status.asz file is recreated. Then, ALSO NEW IN 1.03, files in /tmp/.fwcf/root but not in /tmp/.fwcf/temp will be listed in /tmp/.fwcf/temp/.fwcf_deleted, newline-separated. Now, all files with exactly the same content in /tmp/.fwcf/root will be removed from /tmp/.fwcf/temp. Any remaining files will be packed into the fwcf format documented below and written to the flash partition, padded to a multiple of 64 KiB with data read from /dev/urandom. Unclean setups, NEW IN 1.03, will prevent a commit, unless the file /etc/.fwcf_unclean is removed manually, or the ‘-f’ option is given. The first public release does only support directories, files and symbolic links, for simplicity. Stored hard links and other file types will be skipped, because their storage format is al- ready specified (as “reserved for future use”), and ignored. No inode or file-sequential-number information is read or written. 2.3. Operation of ‘fwcf erase’ ―――――――――――――――――――――――――――――― In theory, just writing a NUL byte to the beginning of the flash partition would suffice. However, this requires an mtd erase and flash operation of one entire flash block (usually 64 KiB), so an empty fwcf filesystem padded with random data to the next 64 KiB will be written instead, for the added benefit of improving the quality of the kernel PRNG even over total reconfigurations. 2.4. Operation of ‘fwcf status’ (NEW IN 1.03) ――――――――――――――――――――――――――――――― ┄┄┄┄┄┄┄┄┄┄┄┄┄ For all files in /etc, the ‘md5sum’ busybox applet is run, output stored in a temporary file and compared against the saved values from ‘fwcf setup’. If the ‘-q’ flag is not given, the differences are shown as “”, where the md5 is expressed as shown by the busybox applet, or as padded¹ “” if the file does not exist on either side, where “old” is the status at fwcf setup time (or the /etc from the root fs, if ‘-r’ is given), and “new” is the status of the current (tmpfs) /etc. If there are no differences, the exit status is 0, non-0 otherwise. If the ‘-r’ flag is given, operation is done against the data that is stored in the ROM, without considering the contents of the FWCF filesystem, instead. ¹) Every “MD5” value is padded to be 32 bytes long, at its left side, with spaces (just to clarify). 2.5. Operation of ‘fwcf dump’ (NEW IN 1.03) ――――――――――――――――――――――――――――― ┄┄┄┄┄┄┄┄┄┄┄┄┄ A dump of the data currently stored in flash (commit first if you have changed anything!) is dumped to the filename argument, or to standard output, if none is present. Note: dumps are a LZO1X compressed tarball of a 256-byte entropy seed (“seed”) and the contents of the “inner filesystem” in “asz” format (“dump”), which is stored as .tar.asz itself. There is no version information, and this is by design. Implementation information: the fwcf helper tool has a new mo- de of operation in which it works as compressor/decompressor – the algorithm used is determined with the -C option on a build system, and at compile time (i.e. the only one compiled in) on the target system. That's a compromise relative to using gzip, because it makes dumps depend on the compression algorithms in use, but it's always LZO1X-1 in FreeWRT 1.03 and up, and no de- pendency on a 50+ KiB gzip binary is added; the .tar.asz enco- ded dump can be recompressed with the tool on the build system. While the dump itself is tar → compressed → asz encoded, “asz” itself is not a compressing format, just a storage container – which stores one octet stream, plus size and checksum informa- tion only. Because many compressors have no idea about the un- compressed size, the actual encoded data is prefixed by a lit- tle endian unsigned 32-bit integer of the uncompressed length; the resulting larger buffer is then passed to the asz encoder. 2.6. Operation of ‘fwcf restore’ (NEW IN 1.03) ―――――――――――――――――――――――――――――――― ┄┄┄┄┄┄┄┄┄┄┄┄┄ A dump created with “fwcf dump” is expected to be read from the filename argument, or standard input, if none is present, and then written to flash. 3. Structure of the fwcf data ――――――――――――――――――――――――――――― All data is written in little-endian format. The fwcf data begins at offset 0 in the flash partition, with the magic bytes “FWCF” (0x46435746). The next doubleword (four bytes) is the “outer length” of the fwcf data, including the header (including the magic bytes and the length information itself) and the trailer (checksum), but not the padding; the length takes up the lower 24 bits of this doubleword. The upper 8 bits are the (major) version of the specification adhered to, i.e. 0x01 for this document. This information shall be true for all ver- sions of this specification in order to enable the fwcf command-line utility to perform as follows: it is not required to process any non- native versions of fwcf data, but even if reading a different version, the random data used for the padding should be written to /dev/urandom. The following information is dependent on the version of the speci- fication. The next doubleword (starting at offset 8) is the “inner length” of the compressed fwcf data (lower 24 bit), or'd with the identification number of the compression algorithm used (upper 8 bit). Note this ef- fectively limits both the uncompressed and the compressed size of an fwcf filesystem to 2²⁴ bytes = 16 MiB. Since the filesystem is de- signed for /etc, this limitation is not expected to be troublesome. After this, at offset 12, the compressed data starts. It is padded to the next 4-byte boundary with zeroes. The next doubleword is the ADLER-32 checksum (as defined by libz) of all previous data, starting from the magic bytes at offset 0, ending with the zero-padding of the compressed data. Note that this does not check the integrity of the data after decompressing; cur- rently we must trust the decompressor to check integrity and do a length check on the decompressed data returned by the plugin our- selves. The next major version of the specification may change that. 4. Compression algorithm allocation ――――――――――――――――――――――――――――――――――― An implementation is only required to be able to use exactly one of the compression algorithms defined below, but it is not required to implement a specific algorithm. Conversion might be achieved by un- and repacking the data, or using an fwcf implementation with multi- ple algorithms. Every implementation, however, is required to offer at least one of the non-private algorithms below. This draft of the specification offers two compression algorithms: 0x00 = plain uncompressed data 0x01 = zlib deflate compression as per http://www.zlib.net/ 0x10 = LZO1X as per http://www.oberhumer.com/opensource/lzo/ Algorithm codes from 0xE0 to 0xFF are available for private use. 5. Structure of the fwcf filesystem ――――――――――――――――――――――――――――――――――― The compressed/inner data consists of a byte stream without padding applied, in the following format: entry ::= file-entry | NUL-byte file-entry ::= pathname NUL-byte attributes NUL-byte data attributes ::= attribute ( attribute )* The pathname is a POSIX pathname, i.e. can contain any character except NUL. Directories are separated with ‘/’ and automatically created by the extraction tool if required. If the first octet of the pathname is a NUL byte (i.e. it is of zero length), the end of the filesystem has been reached. Any data read afterwards MUST be discarded for security reasons. Attributes consist of a one-byte identifier, which is usually a letter, and a zero-to-multiple-bytes payload. If the identifier is a letter, its lowercase and uppercase forms denote the same attribute with a different payload length. The raw file data is not padded or aligned; its length is an at- tribute. Alternate streams / forks are not supported. 6. Currently defined attributes ――――――――――――――――――――――――――――――― 0x01 this file is a block special device ① no payload reserved for future use 0x02 this file is a character special device ① no payload reserved for future use 0x03 this file is a symbolic link ② no payload 0x04 this file is a hard link to another file ① ④ no payload reserved for future use 0x05 this file is a directory ④ no payload 0x0D this file is deleted ① reserved for future use 0x10 modification time of the entry optional ignored for symbolic links payload length: 32 bit g/G group of the file (numeric GID) optional payload length: lowercase = 8 bit, uppercase = 32 bit i/I “inode” of the file ① ④ required if this file is a hard link source or target optional (ignored) otherwise payload length: lowercase = 8 bit, uppercase = 16 bit reserved for future use m/M mode_t / permissions of the file ③ optional ignored for symbolic links payload length: lowercase = 16 bit, uppercase = 32 bit o/O owner of the file (numeric UID) optional payload length: lowercase = 8 bit, uppercase = 32 bit s/S size of the file for files and symbolic links: mandatory for directories, device nodes and hard links: forbidden payload length: lowercase = 8 bit, uppercase = 24 bit ① These identifiers are defined in this specification for future use; implementations do not need to support them at this time. ② The name of the target is the data, thus, size is required. ③ Defaults to 0 if not used (for security reasons), so labelling it as “optional” is probably a farce ☺ ④ Implementing hard links and directories is, of course, optional for the writer. 7. Miscellaneous ―――――――――――――――― The initial idea for a “configuration filesystem” based upon the Linux FUSE kernel module has been communicated to me by Waldemar Brodkorb, FreeWRT Project Founder. After a discussion with him I decided on the archive/userland tool layout outlined in sections 1 and 2 above. For FreeWRT 1.0, it has been realised in shell. For now, nodes other than directories, files, and symbolic links are not supported. Development of FWCF is hosted in the CVS repository of the MirOS project. Anonymous read-only CVS access is available at the root “:ext:anoncvs@anoncvs.mirbsd.org:/cvs” using password “anoncvs”, SSH on port 22; module “fwcf”. CVSweb is also available, e.g. at http://cvs.mirbsd.de/contrib/hosted/fwcf/ or its mirrors. FWCF code is released under the same licence terms as the speci- fication, but without the advertising clause. The author however would really appreciate users to credit his name and that of the FreeWRT Project in derived works and/or links to the CVS reposi- tory of the original source. 8. The “asz” file format (NEW IN 1.03) ―――――――――――――――――――――――― ┄┄┄┄┄┄┄┄┄┄┄┄┄ The “asz” format is intended for storing small arbitrary 8-bit data, and used in the “fwcf dump” and “fwcf restore” formats – the dump itself is a raw uncompressed “inner fwcf filesystem”, stored as “asz”, and the storage used by the dump/restore com- mands is a tarball, compressed with lzo1X1 (in the current im- plementation), the result stored, again, as “asz”. It almost looks like the “outer fwcf” format, except the start isn't a header but the ADLER-32 checksum double-word (2 unsig- ned 16-bit integer in LITTLE ENDIAN), i.e. without magic bytes to identify the format; followed by the length double-word – 1 unsigned 32-bit integer in LITTLE ENDIAN – and finally the raw binary data. Only the lowest three octets of the length should be used because the current implementation malloc(3)s a buffer containing the whole data. 9. Future directions ―――――――――――――――――――― The next major version of the FWCF filesystem specification is likely to contain the following changes: • An additional checksum (probably ADLER32 as well) shall be placed inside the compressed portion, to be checked after decompression. The idea of adding a random IV has not been adopted because we pretty much want the same FWCF blocks, except the random padding at the end, to be generated for the same input data. (This is not guaranteed because fts() may traverse the directory hierarchy differently.) • Revisit the current size limits and file types. • Implement a r̲e̲a̲l̲ file type “deleted”, replacing the hack with the .fwcf_deleted file. These future directions have come up during or after the fwcf 1.00 release process, and from the discussion thereafter. They are provided as hint only and not part of the specifi- cation itself. They may change without notice. ⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼⎼ $MirOS: contrib/hosted/fwcf/fwcf.txt,v 1.37 2007/07/02 14:55:44 tg Exp $