floppy-utils/README.md

657 lines
18 KiB
Markdown
Raw Normal View History

# floppy-utils
Bash toolkit for retro floppy work on Linux: create and edit disk images, archive physical floppies to files, and write images back to a USB floppy drive—without memorizing `dd`, `losetup`, and mount options.
Designed for day-to-day use with a **USB floppy drive** (removable `/dev/sdX` that changes between machines). Loop mounts are kept separate from the physical drive so you can work on `.img` files offline and only touch hardware when reading or burning.
**Tested hardware:** Mitsumi SmartDisk USB UFDD (`03ee:6901`), appearing as `/dev/sda`, FAT12 volumes, automount via udisks at `/media/$USER/<LABEL>`.
---
## Table of contents
- [What it does](#what-it-does)
- [Project layout](#project-layout)
- [Requirements](#requirements)
- [Installation](#installation)
- [Configuration](#configuration)
- [Quick start](#quick-start)
- [Command reference](#command-reference)
- [Workflows](#workflows)
- [Device selection](#device-selection)
- [Physical drive vs disk images](#physical-drive-vs-disk-images)
- [Safety](#safety)
- [Troubleshooting](#troubleshooting)
- [Legacy wrappers](#legacy-wrappers)
- [Limitations](#limitations)
---
## What it does
| Area | Capability |
|------|------------|
| **Images** | Create blank 360K / 720K / 1.44M `.img` files |
| **Edit** | Loop-attach images, optional FAT format, mount under `$FLOPPY_MEDIADIR` |
| **Archive** | Sector-accurate read from physical floppy → image file |
| **Restore** | Burn image to physical floppy with confirmation |
| **USB FDD** | Detect drive, refresh after disk swap, safe USB disconnect (`power-off`) |
| **Visibility** | List devices, attached state, full status |
All operations go through a single CLI: **`floppy`** (with subcommands). Status messages go to **stderr** so command substitution (e.g. device paths) is not corrupted.
---
## Project layout
```
floppy-utils/
├── README.md This file
├── check-deps.sh Debian dependency check / apt install
├── install.sh Install scripts to ~/.local/bin
├── config.example Sample ~/.config/floppy-utils/config
└── src/
├── floppy Main CLI
├── floppy-make Legacy → floppy make
├── floppy-attach Legacy → floppy attach
├── floppy-burn Legacy → floppy burn
└── lib/
└── common.sh Shared logic (sourced by floppy)
```
There is no daemon. Scripts use `sudo` when not root for `dd`, `blockdev`, `losetup`, and mount operations.
---
## Requirements
### Supported platforms
- **Primary:** Debian-based distributions (Debian, Ubuntu, Raspberry Pi OS, Linux Mint, Pop!_OS, Kali, Devuan, etc.)
- **Others:** Commands are checked on any Linux; `check-deps.sh --install` refuses non-Debian systems
### Required commands
| Command | Debian package | Used for |
|---------|------------------|----------|
| `lsblk` | util-linux | Device size, label, mount points |
| `blockdev` | util-linux | Sector count for read/burn |
| `losetup` | util-linux | Attach/detach loop devices |
| `findmnt` | util-linux | Mount inspection |
| `blkid` | util-linux | Filesystem / label detection |
| `mount`, `umount` | mount | Loop and manual mounts |
| `dd` | coreutils | Read, burn, create images |
| `readlink` | coreutils | Resolve paths |
| `mkfs.vfat` | dosfstools | Format new images on attach |
### Recommended commands
| Command | Debian package | Used for |
|---------|------------------|----------|
| `sudo` | sudo | Non-root operation |
| `udisksctl` | udisks2 | Unmount/mount physical floppy (desktop integration) |
| `udevadm` | udev | Settle after media change (`refresh`) |
| `eject` | eject | Optional media-change hint (`refresh --eject`) |
| `fdisk` | util-linux | Partition info saved on new formats |
### Check and install (Debian)
```bash
cd /path/to/floppy-utils
./check-deps.sh # list OK / missing
sudo ./check-deps.sh --install # apt install missing packages
./check-deps.sh -q # quiet; used by install.sh
```
`install.sh` runs the quiet check first and exits if required tools are missing.
---
## Installation
### 1. Dependencies
```bash
sudo ./check-deps.sh --install
```
### 2. Scripts on PATH
**Option A — install to `~/.local/bin` (recommended)**
```bash
./install.sh
```
Ensure `~/.local/bin` is in your shell `PATH` (add to `~/.bashrc` or `~/.zshrc` if needed):
```bash
export PATH="$HOME/.local/bin:$PATH"
```
**Option B — run from source**
```bash
export PATH="/path/to/floppy-utils/src:$PATH"
floppy help
```
### 3. Optional configuration
```bash
mkdir -p ~/.config/floppy-utils
cp config.example ~/.config/floppy-utils/config
# Edit: FLOPPY_DISKDIR, FLOPPY_DEVICE, etc.
```
### Verify
```bash
floppy help
floppy devices -v
floppy list
```
---
## Configuration
Settings load in this order:
1. Built-in defaults
2. `~/.config/floppy-utils/config` (shell snippet, sourced if present)
3. Environment variables (override config file)
### Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `FLOPPY_DISKDIR` | `$HOME/Retro/BLANKS` | Directory for `.img` files (`make`, `read`, `attach` by name) |
| `FLOPPY_MEDIADIR` | `/media/$USER` | Base directory for loop mounts (`attach`) |
| `FLOPPY_DEVICE` | *(unset)* | Default block device for physical ops (`/dev/sda` on a dedicated retro box) |
| `FLOPPY_DEVICE_MATCH` | `mitsumi\|ufdd\|fdd\|floppy` | Extended regex; boosts USB floppy scoring in `devices` / auto-pick |
| `FLOPPY_DEFAULT_SIZE_KB` | `1440` | Default image size for `make` / `attach` |
### Example config file
```bash
# ~/.config/floppy-utils/config
FLOPPY_DISKDIR="$HOME/Retro/BLANKS"
FLOPPY_MEDIADIR="/media/$USER"
FLOPPY_DEVICE=/dev/sda
FLOPPY_DEFAULT_SIZE_KB=1440
```
### Example one-shot env
```bash
FLOPPY_DEVICE=/dev/sda floppy read dosboot -y
```
---
## Quick start
```bash
# 1. USB floppy connected, disk inserted
floppy refresh --mount
floppy devices -v
# 2. Archive floppy to an image file
floppy read -y # name from volume label or mount dir
# 3. Edit the image without hardware
floppy attach disk
# ... copy files under /media/$USER/disk ...
floppy detach disk
# 4. Write image back to a floppy
floppy refresh # after inserting blank or target disk
floppy burn disk.img -d /dev/sda
```
---
## Command reference
Invoke as `floppy <command> [options] [args]`. Run `floppy help` for a short summary.
### `make` — create blank image
```bash
floppy make [name] [-s 360|720|1440]
```
- Creates `$FLOPPY_DISKDIR/<name>.img` filled with zeros
- Omits `name` → random name `floppy<digits>`
- Does not mount or format
**Examples**
```bash
floppy make blank1440
floppy make dos720 -s 720
```
---
### `attach` — loop-mount an image for editing
Aliases: `mount`
```bash
floppy attach [name|path] [-s SIZE] [--format] [--no-format]
```
- **No args:** new random 1.44M image, formatted vfat, mounted
- **`name`:** use/create `$FLOPPY_DISKDIR/<name>.img`
- **`path`:** absolute or relative path to an existing `.img`
- New or raw images: formatted **vfat** unless `--no-format`
- Mount point: `$FLOPPY_MEDIADIR/<name>` (not the FAT volume label path used by udisks for physical disks)
- Uses `losetup --find --show` and mounts the **loop device** (not the file twice)
**Examples**
```bash
floppy attach
floppy attach mydisk -s 720
floppy attach /archive/old.img
```
---
### `detach` — unmount and release
Aliases: `umount`
```bash
floppy detach <name|mountpoint|loop|label>
```
Accepts:
- Image name (`bootdisk` → `$FLOPPY_MEDIADIR/bootdisk` or loop for that image)
- Full mount path (`/media/user/BOOTDISK`)
- Loop device (`/dev/loop0`)
- Physical volume label directory (`BOOTDISK` under `$FLOPPY_MEDIADIR`)
For physical `/dev/sdX` mounts, only **unmounts**; does not disconnect USB.
---
### `refresh` — re-probe after swapping floppies
Aliases: `rescan`, `reload`
```bash
floppy refresh [-d DEV] [--mount] [--eject] [-w SEC]
```
Run whenever you change disks **without** unplugging USB. For end of session, use **`floppy disconnect`** instead (see below).
1. Unmount stale filesystem (udisks or `umount`)
2. `blockdev --flushbufs`
3. Optional `eject` (`--eject`) for drives that need it
4. `udevadm settle` and short wait (`-w`, default 2 seconds)
5. Re-probe capacity; show before/after `lsblk` summary
6. With `--mount`, mount new media via udisks if detected
**When to use:** `devices` still shows old label/size, automount behaves oddly, or `read` fails after a swap.
**Examples**
```bash
floppy refresh
floppy refresh --mount
floppy refresh --eject -w 3
floppy refresh -d /dev/sda
```
---
### `disconnect` — safe USB removal (end of session)
Aliases: `power-off`, `eject-usb`
```bash
floppy disconnect [-d DEV]
```
Use when you are **done with the USB drive** and want to unplug the cable. **Not** for swapping floppies (use `refresh`).
Steps performed:
1. Unmount any volume on the device (`udisks` or `umount`)
2. `blockdev --flushbufs` and `sync`
3. `udisksctl power-off -b DEV` (falls back to `eject` if power-off fails)
```bash
floppy disconnect
floppy disconnect -d /dev/sda
```
On success you should see: **Safe to unplug the USB floppy drive now.**
---
### `read` — archive physical floppy to disk dir
Aliases: `archive`
```bash
floppy read [name] [-d DEV] [-y] [--force] [--strict]
```
- Reads **entire device** sector-by-sector into `$FLOPPY_DISKDIR/<name>.img`
- Sector count from `blockdev --getsz` (2880 × 512 bytes for 1.44M)
- Default `dd` conv: **`noerror,sync,fsync`** (tolerates bad sectors on old media)
- Unmounts drive before read; 1 second settle
- **`--strict`:** stop on first I/O error instead
**Default name** (if `name` omitted):
1. FAT volume label (sanitized, e.g. `DISK 2``disk_2`)
2. Else basename of mount point (e.g. `disk` from `/media/user/disk`)
3. Else random `floppy<digits>`
**Examples**
```bash
floppy read -y
floppy read bootdisk -d /dev/sda -y
floppy read --strict -y # fail fast on errors
```
---
### `dump` — raw dump to any path
Aliases: `copy`
```bash
floppy dump -o FILE [-d DEV] [-y] [--force] [--strict]
```
Same read path as `read`, but **`-o`** is required (any file path, or `-` for stdout). Does not use `FLOPPY_DISKDIR` naming rules.
```bash
floppy dump -o /tmp/floppy.raw -d /dev/sda -y
floppy dump -o - -d /dev/sda | sha256sum
```
---
### `burn` — write image to physical drive
Aliases: `write`
```bash
floppy burn <image> [-d DEV] [-y]
```
- Resolves image: path, or name in `FLOPPY_DISKDIR`
- **Never** hardcodes a device; uses `-d`, `FLOPPY_DEVICE`, or interactive pick
- Confirms unless `-y`; warns if mounted, then unmounts
- Writes with `dd bs=512 conv=fsync`
```bash
floppy burn disk.img -d /dev/sda
FLOPPY_DEVICE=/dev/sda floppy burn dosboot.img -y
```
---
### `devices` — list removable block devices
Aliases: `list-devices`
```bash
floppy devices [-v]
```
- Lists `/dev/sd*` and `/dev/fd*` disks with `RM=1`
- **`-v`:** label, fstype, mount, heuristic **score** (USB FDD models score higher)
Use before `burn`/`read` when unsure of device node.
---
### `list` — what is attached now
Aliases: `attached`, `ls`
```bash
floppy list [-v]
```
| Section | Shows |
|---------|--------|
| Loop-attached | `.img` → loop device → mount point |
| Physical media | Drive with disk inserted (mounted or not) |
| **`-v` only** | All `.img` files in `FLOPPY_DISKDIR` |
Lighter than `status`; focused on “what can I edit or read right now?”
---
### `status` — full diagnostic snapshot
```bash
floppy status
```
Prints: config paths, loop attachments, mounts under `$FLOPPY_MEDIADIR`, verbose removable drive list.
---
## Workflows
### A. Swap disks in the USB drive (stay on same `/dev/sdX`)
```mermaid
flowchart LR
swap[Insert new floppy] --> refresh[floppy refresh --mount]
refresh --> devices[floppy devices -v]
devices --> next[read / burn / browse]
```
```bash
floppy refresh --mount
floppy devices -v
```
You do **not** need to replug USB between floppies.
---
### B. Archive a floppy (physical → file)
```bash
floppy refresh --mount # optional: browse files first
floppy devices -v # expect size 1.4M / 720K, not 0B
floppy read -y # or: floppy read mylabel -y
floppy list -v # confirm image in FLOPPY_DISKDIR
```
Partial reads with warnings usually mean **damaged sectors**; image may still be useful. `floppy1982469877.img`-style random names meant the volume had no usable label—pass an explicit name: `floppy read disk -y`.
---
### C. Edit an image without hardware
```bash
floppy attach myproject
ls /media/$USER/myproject
floppy detach myproject
```
---
### D. Restore image to floppy
```bash
floppy refresh # new disk in drive
floppy burn myproject.img -d /dev/sda
```
### G. Done for the day — unplug USB drive
```bash
floppy disconnect # unmount + power off
# unplug USB cable
```
---
### E. Create a new blank floppy image for a retro project
```bash
floppy make blank -s 1440
floppy attach blank --format
# copy files into mount point
floppy detach blank
floppy burn blank.img -d /dev/sda
```
---
### F. Inspect current state
```bash
floppy list -v # attached loops + physical + archive dir
floppy status # everything including config
```
---
## Device selection
When `-d` and `FLOPPY_DEVICE` are unset, `floppy` picks a removable disk:
1. List removable `sd`/`fd` disks via `lsblk`
2. Score each (higher = more likely USB floppy):
- +100 if `MODEL` matches `FLOPPY_DEVICE_MATCH`
- +40 if `TRAN=usb`
- +30 if size is 360K / 720K / 1.4M
- +10 if volume label present
3. **Auto-select** if exactly one device, or exactly one with score ≥ 100
4. Otherwise **interactive menu**
Always use `floppy devices -v` when multiple USB storage devices are attached (thumb drives, SD readers, etc.).
---
## Physical drive vs disk images
| | Physical USB floppy | Loop image (`attach`) |
|--|---------------------|------------------------|
| Device | `/dev/sda` | `/dev/loopN` |
| Typical mount | `/media/$USER/<FAT_LABEL>` (udisks) | `/media/$USER/<image_name>` |
| Create | `refresh`, insert disk | `make`, `attach` |
| Archive | `read`, `dump` | copy `.img` file |
| Write | `burn` | `burn` to physical only |
| Release | `detach <label>`, `refresh` (swap disk) | `detach <name>` |
| Unplug USB | `disconnect` | — |
---
## Safety
### Burn
- Prompts: `Write image to /dev/sdX? This will ERASE the floppy. [y/N]`
- Skipped with `-y` only when you are certain
- Targets **whole disk** (`/dev/sda`), not partitions (`/dev/sda1`)
- Unmounts before write
### Read / dump
- Prompt unless `-y`
- Refuses `0B` size (no disk inserted)
- Does not overwrite existing output without `--force`
### Permissions
Non-root users need `sudo` (or polkit via `udisksctl` for mount/unmount). Configure passwordless sudo only if you accept the security tradeoff on a dedicated retro workstation.
---
## Troubleshooting
### `floppy: no media in /dev/sda` but disk is inserted
- Run `floppy refresh`, then `floppy devices -v` (size should be `1.4M`, not `0B`)
- Re-seat the disk; try `floppy refresh --eject -w 3`
### Wrong label in `devices -v` (path shown as label)
Fixed in current code (per-field `lsblk`). Update if you see `label=/media/...`.
### `floppy read` uses random name `floppy123456789`
Volume has no FAT label; udisks mounted as `/media/user/disk`. Pass a name: `floppy read disk -y`, or set label on the FAT volume.
### `dd: Input/output error` partway through read
Common on aging floppies. Default `noerror,sync` completes with holes; warnings are printed. Use `--strict` only if you want fail-fast. Try `refresh` before retry; clean drive heads if errors persist.
### `floppy: command not found`
Run `./install.sh` or add `src` to `PATH`. Use `./floppy` from `src` when testing without install.
### `>>> using removable device` mixed into error text
Status must go to stderr (fixed). Update `lib/common.sh` if you see this on old copies.
### Automount fights `read` / `burn`
`read`, `burn`, and `dump` unmount first. Run `floppy refresh` after manual file-manager mounts.
### Leftover empty mount directory after `detach`
e.g. `/media/user/bootdisk (present, not mounted)`. Safe to remove: `rmdir` if empty.
### Device is `/dev/sda` on this machine but `/dev/sdb` elsewhere
Do not rely on fixed names; use `floppy devices` or set `FLOPPY_DEVICE` per host in config.
### Mitsumi drive shows `2880` during refresh
Internal `blockdev --getsz` probe (suppressed in output on current versions).
### `udisks power-off failed` on disconnect
Close file managers using the mount. Run `floppy detach <label>`, then `floppy disconnect` again. You can still unplug after a successful unmount if power-off fails—wait a few seconds for cache flush.
---
## Legacy wrappers
Thin scripts for muscle memory; all call `floppy`:
| Wrapper | Maps to |
|---------|---------|
| `floppy-make` | `floppy make "$@"` |
| `floppy-attach` | `floppy attach` (supports old two-arg size: `floppy-attach name 1440`) |
| `floppy-burn` | `floppy burn "$1"` |
---
## Limitations
- **Linux only** (uses `losetup`, `lsblk`, udisks)
- **Debian apt helper** only in `check-deps.sh`; other distros: install packages manually
- **FAT-focused** for `attach` format (`mkfs.vfat`); raw/non-FAT images can be read/burned but not auto-mounted as vfat
- **No write-protect detection** beyond mount errors
- **No internal 5.25" / floppy controller** support beyond what the kernel exposes as `/dev/fd*`
- **Sector size** assumed 512 bytes for read/burn
- **Not a GUI**; pairs with desktop automount (udisks) when present
---
## See also
- `floppy help` — built-in usage text
- `config.example` — starter configuration
- `check-deps.sh --help` — dependency installer options