# !DiscMedic

A RISC OS application that checks and repairs hard discs, SD cards, NVMe
drives and USB drives: **FileCore**, which is what RISC OS formats its own
discs as, **FAT12/16/32**, which is what almost everything else uses, and -
from 0.10 - **UFS2**, RISC OS 64's own file system, with **btrfs** checked
and scrubbed.  It also shows, checks, mends and resets the **configuration
memory** (CMOS), and says whether CMOSStore can save it.

It builds for RISC OS 5 (Norcroft, the Linux DDE) and for RISC OS 64
(clang, in that tree as `modules/apps/discmedic`, in ROM as
`Resources:$.Apps.!DiscMedic`).

## UFS2 on RISC OS 64

UFSCore will not write to a UFS2 file system marked as needing a check
(`fs_clean` 0, or `FS_NEEDSFSCK`/`FS_UNCLEAN` in `fs_flags`) - which is
what a reset inside its write-back window leaves.  DiscMedic is the fsck:

    *DiscMedic BD <id>                      check (see -list for ids)
    *DiscMedic -f -u RAM::RamDisc0.$.Undo BD <id>

A disc UFSCore holds read only is repaired where it stands - that includes
the running boot disc in that state.  A clean disc UFSCore may be writing
is released first (`UFSCore_Release`) and refused if it is the running boot
disc.  Afterwards `UFSCore_Remount` (new in UFSCore 1.12) mounts it again,
writable now that it is clean.  Every write goes through the undo file.

## The configuration memory

    *DiscMedic -cmos show                   bytes, checksum, CMOSStore's file
    *DiscMedic -cmos fix
    *DiscMedic -cmos reset net,boot,screen  defaults, keeping those
    *DiscMedic -cmos save <file> | load <file>
    *DiscMedic -cmos fix -cmosimage <file>  mend a saved CMOS file

The icon bar menu's CMOS submenu does the first three.

MIT licence.  See `LICENCE`.

## The boot partition

A machine that starts from an SD card or a drive keeps a small FAT
partition beside the RISC OS one, and on a machine without battery-backed
CMOS the file `CMOS` in its root is where the configuration memory lives
between sessions (SDCMOS keeps it there; the loader puts it back).  DiscMedic
reads and writes that partition with its own FAT code - the first FAT volume
on the device, or the one `-part` names - so it does not depend on RISC OS
having mounted it:

    *DiscMedic -boot info    SDFS 0          root directory, CMOS file, its checksum
    *DiscMedic -boot list    SDFS 0
    *DiscMedic -u RAM::RamDisc0.$.Undo -boot backup SDFS 0
    *DiscMedic -boot restore SDFS 0

`backup` saves `*SaveCMOS`'s own file, refuses one whose checksum is wrong,
overwrites an existing `CMOS` in place (or makes one, growing a full FAT32
root directory if it has to), writes the data first, the table second and the
directory entry last, journals every sector through the undo file, and reads
the result back.  `restore` checks the file and loads it with `*LoadCMOS`.
`tests/boot-test.sh` runs all of it on FAT12/16/32 images and judges every
write with `fsck.vfat -n`, which is not DiscMedic's code.

## Shape of the program

Two binaries, as the old ARM Club disc checker did it, and for the same
reason:

| Binary | Role |
|---|---|
| `!DiscMedic/!RunImage` | The desktop front end.  Toolbox and gadgets throughout; follows the RISC OS Style Guide.  Never touches a disc. |
| `!DiscMedic/DMEngine` | The engine.  A command line program that does all the reading and writing.  Usable from the `*` prompt on a machine whose Toolbox modules are on the disc that will not mount. |

The front end works out what the user wants, builds a command line, and runs
the engine in a task window started with `-task`, so that the engine's report
arrives in Wimp messages and appears in the progress window as it is
produced.  There is no polling and no temporary file in the normal path.

## Building

Needs the Linux-hosted DDE at `/mnt/AI/Projects/DDE-Linux` and Python 3.
Nothing leaves the machine.

```bash
./build.sh
```

That regenerates the Toolbox resource files from `tools/mkres.py`, copies in
the sprites from `Resources/Sprites`, checks the resource files over with
`tools/checkres.py`, compiles the sources with Norcroft and links two
32-bit AIF images into the application directory.  With the DDE at
`NorcroftUniversal` (whose own `.venv` does not survive an exFAT drive):

```bash
DDE=/run/media/martin/Stuff/NorcroftUniversal ./build.sh
```

The window comes in two resource files.  `Res` uses Toolbox32's
`ProgressBar`; `Plain/Res` has a slider where that is, for a machine whose
Window module is the ROM's.  `!Run` picks one (`DiscMedic$ResDir`) by asking
for `ProgressBar 8.00`.  The layout is `make_main()` in `tools/mkres.py`; the
ResEd file it grew from is `Resources/Res,fae.user-20261018`.  The
application's sprites (`!discmedic`, `sm!discmedic`; 32-bit with alpha in
`!Sprites22`) are drawn files kept in `Resources/Sprites`.

## Testing without a RISC OS machine

The engine's only contact with the outside world is `c/disc`, so everything
above it can be built and run on the desktop against disc image files.  The
tests do exactly that, from the shipped sources - nothing is copied, so they
cannot drift from what is built for RISC OS:

```bash
./tests/run-tests.sh      # FAT, refusals, GPT, FileCore
./tests/ufs-test.sh       # UFS2: 26 kinds of damage, 512 and 4096 byte sectors
./tests/cmos-test.sh      # the configuration memory, on saved files
./tests/boot-test.sh      # the boot partition: CMOS backup/restore on FAT images
```

`ufs-test.sh` needs the RISC OS 64 tree (`RO64=`, default the
discmedic64 worktree): it makes images with that tree's own UFS2 formatter
and writer, damages them (`tests/ufs/corrupt.py`), checks that a check
writes nothing, repairs with an undo file, and then has DiscMedic's second
pass, the tree's independent checker, the handoff `ufscheck` and UFSCore's
own reader accept the result, with every file's CRC unchanged wherever the
repair could keep it, and the undo file restoring the damage exactly.

Twenty-three tests, in seconds.  The two that matter most come first: a
check of a healthy image and a check of a damaged one both leave the image
**byte for byte identical**, because a check must never write.  The rest
cover repair, the undo file, the refusals, and - for FileCore - a directory
that contains itself, which used to make the check run away.

FileCore cannot be formatted on the host (nothing here produces what RISC OS
produces), so `tests/fcore-test.sh` runs against a real disc image you
supply, named by `DM_FCORE_IMAGE` or left at `tests/images/fcore.img`.  It
puts back every byte it touches, so the image is reusable.

Two more tools, for when something is wrong rather than as part of the run:

```bash
./tests/frontend-lint.sh    # gcc -Wall -Wextra -fanalyzer over the front end
./tests/fuzz/run.sh         # AddressSanitizer over the front end's own logic
```

Norcroft is built with `-Wp`, which hides nearly everything; gcc found a
stack buffer overrun in the engine that it had missed.  `CCEXTRA=... ./build.sh`
passes flags through for the diagnostic builds `Docs/Limits` describes.

`Docs/Limits` lists what has actually been tested, and with what - including
what has not been.

**If you are testing under the Titanium emulator**, its environment must
have `QEMU_RISCOS_PTFIX=1`, along with `TITANIUM_CMOS` and `TITANIUM_HOSTFS`.
`run-titanium.sh` sets all three.  Without the first, QEMU goes on executing
stale code in the application slot when a slot page is remapped, which shows
up as an undefined instruction at a perfectly valid address inside an
application - and looks exactly like a bug in the application.

## Source

| File | What it is |
|---|---|
| `c/dm` | Sizes, allocation, the small shared helpers |
| `c/report` | The output channel, its line prefixes and the progress protocol |
| `c/disc` | Sector access to a drive or an image; retries and error classification |
| `c/journal` | The undo file |
| `c/volume` | Partition tables, and working out what is on a drive |
| `c/fcore` | FileCore: boot block, disc record, map |
| `c/fcmap` | FileCore: the map's fragments, object ids and disc addresses |
| `c/fcdir` | FileCore: the directory tree, objects nothing points at, and Lost+Found |
| `c/fat` | FAT: boot sector, FSInfo, the allocation table |
| `c/fatdir` | FAT: directories, long names, cluster chains, lost clusters |
| `c/bootpart` | The boot partition: its root directory, and the CMOS file in it |
| `c/cmos` | The configuration memory: show, mend, reset, save, load, export/import |
| `c/main` | The engine's command line and the order things run in |
| `c/gui` | The front end: startup, icon bar, event loop |
| `c/window` | The main window: disc list, options, progress and report |
| `c/task` | Running the engine and collecting its output |

There is no floating point anywhere in the engine, and no sector buffer on
the stack: RISC OS gives a program a small stack out of its Wimp slot, and
both of those were found the hard way by running the ARM build.

`tools/mksprites.py` made the earlier 4 bpp icon from `Docs/Icon.png`; it is
no longer part of the build.  `tools/showsprite.py` decodes a 4 bpp sprite
file back to a PNG.

## Sources consulted

- The RISC OS sources' own FileCore documentation, which is the authority
  for the on-disc format: `FileSys/FileCore/Doc/EMaps` (the map, fragments
  and the ZoneCheck algorithm), `Doc/EAddrs` (disc addresses), `Doc/Dirs`
  (directory layout and the check byte) and `s/Defns` (the big directory
  structure and the 15-bit free link limit).
- RISC OS PRM vol 2 ch 28 and vol 5a ch 110, FileCore; ch 58, TaskWindow.
- ROOL wiki: *FileCore_DiscOp64*, *FileCore_DiscOp64 Address*, *FileCore Disc
  Record Large*, *FileCore Disc Map New*.
- Microsoft's FAT specification, cross-checked field for field against the
  Linux kernel's `include/uapi/linux/msdos_fs.h`.
- The DDE's own `tboxlibs` headers for every Toolbox gadget template.
