Skip to content

A FUSE module for accessing ancient Unix filesystems.

License

Notifications You must be signed in to change notification settings

AaronJackson/retro-fuse

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

96 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

retro-fuse

A user-space filesystem (FUSE) for accessing ancient Unix filesystems.

retro-fuse provides a way to mount filesystems created by ancient Unix systems on modern OSes. The current version of retro-fuse supports mounting filesystems created by fifth, sixth and seventh-edition research Unix, as well as 2.9BSD and 2.11BSD. It can also initialize such filesystems.

The design of retro-fuse is novel in that it incorporates original source code from the historic Unix kernels. The ancient code has been "lightly modernized" to be palatable to current compilers and adapted to run in userspace. This approach (made possible by Caldera’s generous open source license for ancient Unix) allows retro-fuse to support both reading and writing of ancient filesystems, while ensuring the historical fidelity of on-disk structures.

retro-fuse provides support for a broad array of modern filesystem operations on top of the basic capabilities of the original code. This includes support for modern rename(2) semantics, filesystem statistics gathering (e.g. via statfs(2) / df(1)) and symlinks (where appropriate). The code also provides a C API with semantics that match modern Unix syscalls, making it possible to incorporate ancient filesystem access directly into other projects.

The current version of retro-fuse runs on Linux and MacOS.



Building

Building retro-fuse on Linux requires the libfuse-dev package, which can be installed on Ubuntu/debian-based systems as follows:

$ sudo apt install libfuse-dev

On MacOS, retro-fuse requires the macFUSE sofware package, an installer for which can be downloaded from the macFUSE website.

After the prerequisites have been installed, run make to build the filesystem handler programs:

$ make

Testing

retro-fuse includes a set of functionality tests for each of the filesystem handlers. These tests perform various I/O operations on a mounted test filesystem to confirm the correct behavior as observed from the modern system. Additionally, the tests verify the fidelity of the generated filesystem images by mounting them in a simulated historic Unix system and checking filesystem’s integrity (using legacy tools such as icheck and fsck) and contents.

The functionality tests rely on simh and python3. Additionally, the tests use the Pexpect python module, which must be installed separately.

simh can be installed on Ubuntu/debian-based systems as follows:

$ sudo apt install simh

The Pexpect python module can be installed using pip:

$ pip3 install --user pexpect

To run the functionality tests, use the test make target:

$ make test

Individual filesystem handlers can be tested by directly running the retro-fuse-test.py driver script and passing a path to the handler program. Pass the -v option to see details about the tests being performed:

$ ./test/retro-fuse-test.py -v ./bsd29fs

Usage

The build process produces the following filesystem handler programs:

  • v6fs — Supports fifth and sixth edition filesystems

  • v7fs — Supports seventh-edition filesystems

  • bsd29fs — Supports 2.9BSD filesystems

  • bsd211fs — Supports 2.11BSD filesystems

The filesystem handler programs can be run directly from the command line, or they can be installed into /usr/local/sbin, making it possible to mount filesystems using the standard mount(8) command.

To mount an ancient Unix filesystem, run the appropriate filesystem handler program, supplying the name of the block or image file containing the filesystem, and the desired mount point:

<handler> [<options>] <device-or-image-file> <mount-point>

Alternatively, if the handler programs have been installed in /sbin or /usr/local/sbin, you can use the mount command:

sudo mount -t fuse.<handler> [<options>] <device-or-image-file> <mount-point>

When executed directly, the filesystem handlers can be run by a non-root user. This is often more convenient than using the mount command, which must always be run as root.

Filesystem Location

The first non-option argument to a filesystem handler program is the storage location of the filesystem. This can be a regular file containing an image of the filesystem, or a standard Unix block device.

In the case of a block device, it may be useful to specify an offset to the start of the filesystem using the -o fsoffset=x option. If not specified, the filesystem is assumed to start at the beginning of the block device.

Options

The filesystem handler programs accept the standard set of options supported by the mount(8) command, as well as the FUSE mount options described in mount.fuse(8). Additionally, the handlers provide their own set of options useful for dealing with ancient filesystems.

The following is a summary of some of the more useful options:

-o ro

Mount the filesystem in read-only mode.

-o rw

Mount the filesystem in read-write mode. This is the default.

-o allow_root

Allow the root user to access the filesystem, in addition to the mounting user. Mutually exclusive with the allow_other option.

-o allow_other

Allow any user to access the filesystem, including root and the mounting user. Mutually exclusive with the allow_root option.

-o mapuid=<host-uid>:<fs-uid>
-o mapgid=<host-gid>:<fs-gid>

Map a particular user or group id on the host system to different id on the mounted filesystem. This is useful when accessing a filesystem containing files owned by root or other special users.

The specified id mapping applies both ways. Specifically, the uid/gid on the host is mapped to the corresponding id in the filesystem when performing access control checking, or when the id is stored with a file or directory. Conversely, the filesystem id is mapped to the host id whenever a file is stat()ed or a directory is read.

Multiple mapping options may be given, up to a limit of 100.

-o fssize=<blocks>

The size of the filesystem, in blocks. This is used to limit the range of I/O on the underlying device/image file. This can be useful to prevent malformed filesystems from accessing blocks outside of the intended area. It is also necessary when initializing a new filesystem using an image file (see the initfs option for further details).

The size of a block varies by filesystem type:

Filesystem Block Size (bytes)

v6

512

v7

512

2.9BSD

1024

2.11BSD

1024

For an existing filesystem, the fssize value defaults to the size given in the filesystem superblock.

-o fsoffset=<blocks>

Offset into the device/image file at which the filesystem starts, in blocks. Defaults to 0.

-o initfs
-o initfs=<params>

Create an empty filesystem on the underlying device/image file before mounting. When using the initfs option on an image file, the desired size of the filesystem must be specified using the fssize option.

params is a set of initialization parameters which control the layout and configuration of the new filesystem. The interpretation of these parameters varies by filesystem type. Details on the syntax can be found in the help output of the associated filesystem handler (e.g. by running v6fs --help).

If params is not specified, the filesystem is initialized using the default parameters as used by the original Unix mkfs(8) command.

-o overwrite

When used with the initfs option, instructs the filesystem handler to overwrite any existing filesystem image file. Without this option, the initfs option will fail with an error if an image file exists.

-f
--foreground

Run in foreground (useful for debugging).

-d
--debug

Enable debug output to stderr (implies -f)

-V
--version

Print version information

-h
--help

Print usage information.

2.11BSD and disklabels

2.11BSD makes use of BSD disklables to describe the location and size of filesystem partitions on a disk. Currently, the bsd211fs handler does not have the ability to read or create BSD disklabels. As a result, one must typically specify the block offset of the filesystem when mounting a filesystem on an existing disk.

For example, to mount the /usr filesystem on an RD54 using the standard BSD partition layout (as described in /etc/disktab) one would use the following command:

$ bsd211fs -o fsoffset=16302 system-rd54.dsk /mnt/tmp

Note that the value of the -o fsoffset option is specified in 1K-byte blocks. This differs from the values in /etc/disktab, which are given 512-byte sectors.

Because bsd211fs does not support creating disklabels, in many cases it will be necessary use the disklabel(8) program within a running 2.11BSD system to setup the desired partition layout prior to initializing the filesystems. In these situations, given that one is already working within a running 2.11BSD system, it is often easier to simply use the native newfs command, rather running bsd211fs -o initfs on a modern host.

As a special case, when creating disks that contain a single filesystem positioned at the start of the disk, it is possible to skip the disk labelling process and simply initialize the filesystem directly without writing a disklabel. This works because the BSD kernel will automatically presume a default partition (partition 'a') covering the entire disk for any disk found without a disklabel.

Conveniently, this feature makes it possible to initialize such disks from a modern host using the bsd211fs command alone. For example, to create a new filesystem on a disk image matching the size of an RD54, one can use the following command:

$ bsd211fs -o initfs,fssize=155600 data-rd54.dsk /mnt/tmp

Assuming this image appears as device rd1 on the BSD system, the filesystem can then be mounted as follows:

$ mount /dev/rd1a /mnt

Examples

Mount an image file containing a v6 filesystem

$ v6fs root.dsk /mnt/tmp

Mount a v7 filesystem occupying a particular offset and size on a host block device

$ v7fs -o fssize=4872,fsoffset=4194304 /dev/sdc /mnt/tmp

Mount a 2.9BSD filesystem, mapping the host’s user id 1000 to uid 33 on the filesystem

$ bsd29fs -o mapuid=1000:33 root.dsk /mnt/tmp

Mount a v6 filesystem as root, allowing other users to access it

$ sudo v6fs -oallow_other root.dsk /mnt/tmp

Initialize and mount a new v6 filesystem having the same size as a DEC RK05 disk

$ v6fs -o initfs,fssize=4872 scratch.dsk /mnt/tmp

NOTE — When using the initfs option with an image file, the file must not exist when the command is invoked.

Initialize and mount a new v7 filesystem with a specific number of inode blocks

$ v7fs -o initfs=200,fssize=4872 scratch.dsk /mnt/tmp

Unmount a filesystem that has been mounted by the active (non-root) user

$ fusermount -u /mnt/tmp

Unmount a filesystem that has been mounted by root or another user

$ sudo umount /mnt/tmp

Code Structure and Operation

The retro-fuse code has the following structure:

retro fuse architecture

Source Modules

Unix kernel (ancient-src/v6/*, ancient-src/v7/*, etc.) — Modernized ancient Unix source code. Modifications to the original kernel code are purposefully minimal, and consist mostly of syntactical and type compatibility changes. Additionally, a series of #defines and selective hand editing is used to add a prefix for functions and global variables (e.g. "v6_"), so as to avoid conflicts with similarly named modern constructs.

Unix adapter (src/v6adapt.[ch], src/v7adapt.[ch], etc.) — Code supporting the modernized kernel code. This includes replacements for various Unix functions that either require significantly different behavior in the retro-fuse context, or were originally written in PDP-11 assembly.

filesystem API (src/v6fs.[ch], src/v7fs.[ch], etc.) — Provides a modern API for accessing ancient filesystems. The API closely mimics the modern Unix filesystem API, with the notible exception that errors are returned as return values rather than via errno. This API is designed such that it could be reused outside of the context of a FUSE filesystem.

fuse app (src/fusecommon.c, src/v6fuse.c, src/v7fuse.c, etc.) — Main program implementing the FUSE filesystem handler. The primary purpose of the fuse module is to handle filesystem I/O requests from the host kernel via the libfuse library. Implements a variety of command-line options to make it easier to work with ancient filesystems.

dsk (src/dsk.[ch]) — Provides a simple abstraction of a virtual block-oriented disk device. Supports filesystems contained in image files as well as host block devices (e.g. a MicroSD card).

Test Modules

test driver (test/retro-fuse-test.py) — Main script for testing filesystem handler programs. Automatically selects and invokes the appropriate tests based on the type of filesystem handler specified.

filesystem-specific tests (test/V6Tests.py, test/V7Tests.py, etc.) — Filesystem functionality tests tailored to each filesystem type.

test framework code (test/*.py) — Various python modules implementing a reusable framework for testing ancient filesystems.

test system images (test/system-images/*) — Prebuilt ancient Unix system disk images for testing filesystem integrity.

ancient cksum code (test/ancient-cksum/*.c) — Implementations of POSIX-compliant cksum command for use on ancient Unix systems.

License

The modern portions of retro-fuse are licensed under the Apache 2.0 license.

Code derived from ancient Unix source is licensed under Caldera open source license.

Release Notes

Version 8 / 2022/08/23 — Support for 32-bit rPI OS, minor bug fixes.

Version 7 / 2022/01/25 — Added support for initializing and mounting 2.11BSD filesystems. Fixed various bugs, big and small, in v6, v7 and 2.9BSD support. Improved functionality tests. Improved support for macOS.

Version 6 / 2021/12/19 — Added support for 2.9BSD filesystems. Implemented functionality tests for v6, v7 and 2.9BSD filesystem drivers. Fixed various bugs reported by users and uncovered by functionality tests.

Version 5 / 2021/11/10 — Various code restructuring and clean up. Fixed a few minor bugs. Improved Makefile.

Version 4 / 2021/11/06 — Added support for seventh-edition Unix filesystems.

Version 3 / 2021/03/27 — Added support for MacOS.

Version 2 / 2021/03/02 — Fixed bug in link(), unlink(), chmod() and chown() that caused the files modified time to be updated (this fixes problems with rsync). Minor modifications to error handling.

Version 1 / 2021/02/23 — Initial release.

About

A FUSE module for accessing ancient Unix filesystems.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C 90.9%
  • Python 6.3%
  • Assembly 2.3%
  • Other 0.5%