Search by

homelan / l3fsreader

johnhomelan

Provides class for reading disk images from the Acorn Econet level 3 fileserver, and the Acorn Econet filestore.

Package info

github.com/johnhomelan/acorn-l3-fs

Type:project

pkg:composer/homelan/l3fsreader

Statistics

Installs: 375

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v0.2.5 2026-10-12 15:39 UTC

This package is auto-updated.

Last update: 2026-10-12 16:51:34 UTC


README

This package allows the reading of AFS disk images for the Econet Level 3 fileserver and Econet Filestores.

Features

  • PSR-4 autoloading compliant structure
  • Disk Image must be read from a file

Install

composer requre homelan/L3fsReader

Overview

The L3fsReader class, allows files and metadata to be read from a AFS disk image stored as a raw dump. AFS filing systems are like modern filing systems in that there is a hierarchical directory structure, with '$' being the root of the fs.

L3fsReader Usage

Creating a L3fsReader Object to directly open a file on disk

$oAfs = new \HomeLan\Retro\Acorn\Disk\L3fsReader('l3_disk_image.img');

Once the L3fsReader object exists a few simple methods can be used to read data from it.

$oAfs->getCatalogue()

Gets the catalogue of what is on the disk *CAT

e.g.

$aCatalogue = $oAfs->getCatalogue();
foreach($aCatalogue as $sDirectoy=>$aDir)
{
	echo $sDirectoy."\n==============\n";
	foreach($aDir as $sFileName=>$aEntryMetadata){
		echo $sFileName."  [".$aEntryMetadata['loadaddr'].' '.$aEntryMetadata['execaddr'].' '.$aEntryMetadata['size'].' '.$aEntryMetadata['startsector'].' '$aEntryMetadata['type']."\n";
		
	}
}


$oAfs->getFile('$.!BOOT');

The the contents of a give file

e.g.

$sFileContents = $oAfs->getFile('$.!BOOT');


$oAfs->getStat('$.!BOOT');

Stats a file

$oAfs->isFile('$.!BOOT');

Test if a given path is a file or not

e.g.

$bFile = $oAfs->isFile('$.!BOOT');
if($bFile){
	echo "!BOOT is file.\n" 
}

$oAfs->isDir('A');

Test if a given path is a file or not

$bDir = $oAfs->isDir('D');
if($bDir){
	echo "D is a dir.\n" 
}

L3fsRecovery Usage

The L3fsRecovery class extends L3fsReader, and is for disk images taken from failing disks where the sectors that could not be read contain only zero bytes. Rather than starting at the root directory it reads every sector looking for metadata, and builds the best directory tree it can from what survives, even if the root directory has been lost.

$oAfs = new \HomeLan\Retro\Acorn\Disk\L3fsRecovery('damaged_disk_image.img');
$aCatalogue = $oAfs->getCatalogue();
$aReport = $oAfs->getRecoveryReport();

The catalogue has the same format as the one from L3fsReader, and getFile(), getStat(), isFile() and isDir() all work on the recovered tree. Each entry has an extra 'recovery' key holding

  • source - how the entry was found: 'entry' (the normal chain of directory entries), 'salvaged' (found in a slot of a damaged directory), 'parent-pointer' (a directory placed using the pointer it holds to its parent), 'orphan' (nothing says where it belongs) or 'fake' (a directory made up to hold other entries)
  • status - 'ok', 'suspect' (a file with sectors that are all zero bytes, or a damaged map), 'damaged' (a directory with parts missing), 'lost' (the entry survives but the object does not) or 'fake'
  • zero_sectors - the number of sectors of the object that contain only zero bytes

Objects that can not be placed in the tree are put in a directory named Lost+Found in the root, with names made from their SIN (e.g. file-14199).

getRecoveryReport() returns counts of what was found and a list of the problems met. The on disk format, and the checks the recovery relies on, are described in docs/afs-disc-format.md

L3fsWriter Usage

The L3fsWriter class extends L3fsReader, adding methods that alter the disk image. The free space bitmaps, the allocation maps and the directories (with their sequence numbers) are all kept in step. Only hard disk images (one bitmap sector per cylinder) can be written to.

$oAfs = new \HomeLan\Retro\Acorn\Disk\L3fsWriter('l3_disk_image.img');

//Writes a file, replacing any existing file of the same name.  The load address, execute address, access and date are optional
$oAfs->putFile('$.Library.Fred', $sFileContents, 0xFFFF1900, 0xFFFF8023, L3fsWriter::ACCESS_OWNER_READ | L3fsWriter::ACCESS_OWNER_WRITE);

$oAfs->makeDirectory('$.Library.Games');

//Changes the load address, execute address and access, NULL leaves a value as it is
$oAfs->setAttributes('$.Library.Fred', NULL, NULL, L3fsWriter::ACCESS_OWNER_READ);

//Deletes a file or an empty directory
$oAfs->delete('$.Library.Fred');

//Free space in bytes
$iFree = $oAfs->getFreeSpace();

Each method either completes or throws an exception leaving the image exactly as it was (e.g. "Disk full"). Locked files must be unlocked with setAttributes() before they can be replaced or deleted.

Before writing to an image taken from a real disk it is worth calling checkFreeSpaceMap(), which compares the bitmaps with the sectors used by everything reachable from the root directory. If 'in_use_but_free' or 'cross_linked' are not empty the bitmaps can not be trusted, and writing may overwrite existing files.

$aCheck = $oAfs->checkFreeSpaceMap();

Tests

make test

Runs the phpunit tests in unit-tests/, which build small synthetic disk images to test against.