1019 lines
33 KiB
Bash
1019 lines
33 KiB
Bash
|
|
# Shared helpers for floppy-utils. Source from src/floppy only.
|
||
|
|
[[ -n "${_FLOPPY_COMMON_LOADED:-}" ]] && return 0
|
||
|
|
_FLOPPY_COMMON_LOADED=1
|
||
|
|
|
||
|
|
FLOPPY_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
|
FLOPPY_SCRIPT_DIR="$(cd "$FLOPPY_LIB_DIR/.." && pwd)"
|
||
|
|
|
||
|
|
floppy_load_config() {
|
||
|
|
local cfg="${XDG_CONFIG_HOME:-$HOME/.config}/floppy-utils/config"
|
||
|
|
if [[ -f "$cfg" ]]; then
|
||
|
|
# shellcheck source=/dev/null
|
||
|
|
source "$cfg"
|
||
|
|
fi
|
||
|
|
: "${FLOPPY_DISKDIR:=$HOME/Retro/BLANKS}"
|
||
|
|
: "${FLOPPY_MEDIADIR:=/media/${USER:-root}}"
|
||
|
|
: "${FLOPPY_DEFAULT_SIZE_KB:=1440}"
|
||
|
|
# USB floppy drives (e.g. Mitsumi UFDD) often appear as /dev/sda with 0B until media is inserted.
|
||
|
|
: "${FLOPPY_DEVICE_MATCH:=mitsumi|ufdd|fdd|floppy}"
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_die() {
|
||
|
|
echo "floppy: $*" >&2
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_info() {
|
||
|
|
# Must use stderr: this is called from functions whose stdout is captured (e.g. pick_device).
|
||
|
|
echo ">>> $*" >&2
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_warn() {
|
||
|
|
echo "floppy: warning: $*" >&2
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_require_cmd() {
|
||
|
|
local cmd
|
||
|
|
for cmd in "$@"; do
|
||
|
|
command -v "$cmd" >/dev/null 2>&1 || floppy_die "required command not found: $cmd"
|
||
|
|
done
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_require_root() {
|
||
|
|
[[ "$(id -u)" -eq 0 ]] && return 0
|
||
|
|
command -v sudo >/dev/null 2>&1 || floppy_die "root privileges required (install sudo or run as root)"
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_run_root() {
|
||
|
|
floppy_require_root
|
||
|
|
if [[ "$(id -u)" -eq 0 ]]; then
|
||
|
|
"$@"
|
||
|
|
else
|
||
|
|
sudo -n "$@" 2>/dev/null || sudo "$@"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_ensure_diskdir() {
|
||
|
|
if [[ ! -d "$FLOPPY_DISKDIR" ]]; then
|
||
|
|
mkdir -p "$FLOPPY_DISKDIR" || floppy_die "cannot create disk directory: $FLOPPY_DISKDIR"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_validate_size_kb() {
|
||
|
|
local size="$1"
|
||
|
|
case "$size" in
|
||
|
|
360|720|1440) return 0 ;;
|
||
|
|
*) floppy_die "size must be one of: 360, 720, 1440 (got: $size)" ;;
|
||
|
|
esac
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_random_name() {
|
||
|
|
local rando
|
||
|
|
rando="$(od -An -N4 -tu4 < /dev/urandom | tr -d ' ')"
|
||
|
|
echo "floppy${rando}"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Sanitize volume label / user text for use as a filename.
|
||
|
|
floppy_sanitize_name() {
|
||
|
|
local raw="$1"
|
||
|
|
local clean
|
||
|
|
clean="$(echo "$raw" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '_' | sed 's/^_//;s/_$//')"
|
||
|
|
[[ -n "$clean" ]] || clean="floppy"
|
||
|
|
echo "${clean:0:32}"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Resolve argument to an absolute .img path.
|
||
|
|
# Like floppy_resolve_image but returns 1 instead of exiting.
|
||
|
|
floppy_try_resolve_image() {
|
||
|
|
local arg="${1:-}"
|
||
|
|
local path base
|
||
|
|
|
||
|
|
[[ -n "$arg" ]] || return 1
|
||
|
|
|
||
|
|
if [[ -f "$arg" ]]; then
|
||
|
|
printf '%s\n' "$(readlink -f "$arg")"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
base="${arg%.img}"
|
||
|
|
path="$FLOPPY_DISKDIR/${base}.img"
|
||
|
|
if [[ -f "$path" ]]; then
|
||
|
|
printf '%s\n' "$(readlink -f "$path")"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_resolve_image() {
|
||
|
|
local arg="${1:-}"
|
||
|
|
local path base
|
||
|
|
|
||
|
|
[[ -n "$arg" ]] || floppy_die "image name or path required"
|
||
|
|
|
||
|
|
if [[ -f "$arg" ]]; then
|
||
|
|
printf '%s\n' "$(readlink -f "$arg")"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
base="${arg%.img}"
|
||
|
|
path="$FLOPPY_DISKDIR/${base}.img"
|
||
|
|
if [[ -f "$path" ]]; then
|
||
|
|
printf '%s\n' "$(readlink -f "$path")"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
floppy_die "image not found: $arg (also tried $path)"
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_image_basename() {
|
||
|
|
local image="$1"
|
||
|
|
basename "${image%.img}"
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_image_path_in_diskdir() {
|
||
|
|
local name="${1%.img}"
|
||
|
|
printf '%s/%s.img\n' "$FLOPPY_DISKDIR" "$name"
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_create_blank() {
|
||
|
|
local name="$1"
|
||
|
|
local size_kb="$2"
|
||
|
|
local image
|
||
|
|
|
||
|
|
floppy_validate_size_kb "$size_kb"
|
||
|
|
floppy_ensure_diskdir
|
||
|
|
|
||
|
|
image="$(floppy_image_path_in_diskdir "$name")"
|
||
|
|
if [[ -f "$image" ]]; then
|
||
|
|
floppy_warn "image already exists: $image"
|
||
|
|
printf '%s\n' "$image"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
floppy_info "creating ${size_kb}K image: $image"
|
||
|
|
dd if=/dev/zero of="$image" bs=1K count="$size_kb" status=progress 2>/dev/null \
|
||
|
|
|| dd if=/dev/zero of="$image" bs=1K count="$size_kb"
|
||
|
|
printf '%s\n' "$(readlink -f "$image")"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Per-device lsblk field (avoids broken parsing when LABEL is empty).
|
||
|
|
floppy_device_field() {
|
||
|
|
local dev="$1"
|
||
|
|
local field="$2"
|
||
|
|
lsblk -dno "$field" "$dev" 2>/dev/null | head -1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
|
||
|
|
}
|
||
|
|
|
||
|
|
# Volume label, or empty if unknown / invalid.
|
||
|
|
floppy_device_label() {
|
||
|
|
local dev="$1"
|
||
|
|
local label
|
||
|
|
label="$(floppy_device_field "$dev" LABEL)"
|
||
|
|
if [[ -z "$label" || "$label" == "/"* ]]; then
|
||
|
|
label="$(blkid -s LABEL -o value "$dev" 2>/dev/null | head -1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||
|
|
fi
|
||
|
|
if [[ -n "$label" && "$label" != "/"* ]]; then
|
||
|
|
printf '%s\n' "$label"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
|
||
|
|
# Score how likely a block device is a USB floppy drive (higher = better match).
|
||
|
|
floppy_device_score() {
|
||
|
|
local dev="$1"
|
||
|
|
local model size tran label
|
||
|
|
local score=0
|
||
|
|
|
||
|
|
model="$(floppy_device_field "$dev" MODEL | tr '[:upper:]' '[:lower:]')"
|
||
|
|
size="$(floppy_device_field "$dev" SIZE)"
|
||
|
|
tran="$(floppy_device_field "$dev" TRAN | tr '[:lower:]' '[:upper:]')"
|
||
|
|
label="$(floppy_device_label "$dev" 2>/dev/null || true)"
|
||
|
|
|
||
|
|
if [[ "$model" =~ ${FLOPPY_DEVICE_MATCH} ]]; then
|
||
|
|
score=$((score + 100))
|
||
|
|
fi
|
||
|
|
if [[ "$tran" == "USB" ]]; then
|
||
|
|
score=$((score + 40))
|
||
|
|
fi
|
||
|
|
case "$size" in
|
||
|
|
0|0B) score=$((score + 5)) ;;
|
||
|
|
360K|720K|1.0M|1.2M|1.4M|1.44M) score=$((score + 30)) ;;
|
||
|
|
esac
|
||
|
|
[[ -n "$label" ]] && score=$((score + 10))
|
||
|
|
printf '%s\n' "$score"
|
||
|
|
}
|
||
|
|
|
||
|
|
# List whole-disk block devices likely to be USB/removable drives (floppy, USB FDD, etc.)
|
||
|
|
floppy_list_devices() {
|
||
|
|
local verbose="${1:-0}"
|
||
|
|
local name rm type size tran model label fstype mnt
|
||
|
|
floppy_require_cmd lsblk
|
||
|
|
|
||
|
|
while IFS= read -r name; do
|
||
|
|
[[ -z "$name" ]] && continue
|
||
|
|
rm="$(floppy_device_field "$name" RM)"
|
||
|
|
type="$(floppy_device_field "$name" TYPE)"
|
||
|
|
[[ "$type" == "disk" && "$rm" == "1" ]] || continue
|
||
|
|
[[ "$name" =~ ^/dev/(sd|fd) ]] || continue
|
||
|
|
|
||
|
|
size="$(floppy_device_field "$name" SIZE)"
|
||
|
|
tran="$(floppy_device_field "$name" TRAN)"
|
||
|
|
model="$(floppy_device_field "$name" MODEL)"
|
||
|
|
label="$(floppy_device_label "$name" 2>/dev/null || echo "")"
|
||
|
|
fstype="$(floppy_device_field "$name" FSTYPE)"
|
||
|
|
mnt="$(floppy_device_field "$name" MOUNTPOINT)"
|
||
|
|
|
||
|
|
if [[ "$verbose" -eq 1 ]]; then
|
||
|
|
printf '%s rm=%s size=%s tran=%s model=%s label=%s fstype=%s mount=%s score=%s\n' \
|
||
|
|
"$name" "$rm" "$size" "$tran" "$model" "${label:-—}" "${fstype:-—}" "${mnt:-—}" \
|
||
|
|
"$(floppy_device_score "$name")"
|
||
|
|
else
|
||
|
|
local extra=""
|
||
|
|
[[ -n "$label" ]] && extra=" label=$label"
|
||
|
|
[[ -n "$mnt" ]] && extra="$extra mount=$mnt"
|
||
|
|
printf '%s %s %s %s%s\n' "$name" "$size" "${tran:-?}" "${model:-unknown}" "$extra"
|
||
|
|
fi
|
||
|
|
done < <(lsblk -dpno NAME,RM,TYPE 2>/dev/null | awk '$2==1 && $3=="disk" && $1 ~ /^\/dev\/(sd|fd)/ {print $1}')
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_device_has_media() {
|
||
|
|
local dev="$1"
|
||
|
|
local size
|
||
|
|
size="$(floppy_device_field "$dev" SIZE)"
|
||
|
|
[[ -n "$size" && "$size" != "0" && "$size" != "0B" ]]
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_device_show() {
|
||
|
|
local dev="$1"
|
||
|
|
lsblk -d -o NAME,SIZE,MODEL,TRAN,LABEL,FSTYPE,MOUNTPOINT "$dev" 2>/dev/null || true
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_device_sectors() {
|
||
|
|
local dev="$1"
|
||
|
|
local sectors
|
||
|
|
floppy_require_cmd blockdev
|
||
|
|
sectors="$(floppy_run_root blockdev --getsz "$dev" 2>/dev/null)" || floppy_die "cannot read size of $dev"
|
||
|
|
if [[ -z "$sectors" || "$sectors" -eq 0 ]]; then
|
||
|
|
if ! floppy_device_has_media "$dev"; then
|
||
|
|
floppy_die "no media detected in $dev (insert a floppy and retry)"
|
||
|
|
fi
|
||
|
|
floppy_die "device $dev reports 0 sectors"
|
||
|
|
fi
|
||
|
|
printf '%s\n' "$sectors"
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_device_default_name() {
|
||
|
|
local dev="$1"
|
||
|
|
local label mnt base
|
||
|
|
|
||
|
|
if label="$(floppy_device_label "$dev" 2>/dev/null)"; then
|
||
|
|
floppy_sanitize_name "$label"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Unlabeled FAT volumes: udisks often mounts as $FLOPPY_MEDIADIR/<name>
|
||
|
|
mnt="$(floppy_device_field "$dev" MOUNTPOINT)"
|
||
|
|
if [[ -n "$mnt" ]]; then
|
||
|
|
base="$(basename "$mnt")"
|
||
|
|
if [[ -n "$base" && "$base" != "/"* ]]; then
|
||
|
|
floppy_sanitize_name "$base"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
floppy_random_name
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_unmount_device() {
|
||
|
|
local dev="$1"
|
||
|
|
local mp unmounted=0
|
||
|
|
|
||
|
|
if command -v udisksctl >/dev/null 2>&1 && floppy_device_mounted "$dev"; then
|
||
|
|
if udisksctl unmount -b "$dev" 2>/dev/null; then
|
||
|
|
floppy_info "unmounted $dev via udisks"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
while read -r mp; do
|
||
|
|
[[ -n "$mp" ]] || continue
|
||
|
|
floppy_info "unmounting $mp"
|
||
|
|
floppy_run_root umount "$mp" && unmounted=1
|
||
|
|
done < <(lsblk -rn -o MOUNTPOINT "$dev" 2>/dev/null | sed '/^$/d')
|
||
|
|
|
||
|
|
[[ "$unmounted" -eq 1 ]] || floppy_warn "$dev does not appear to be mounted"
|
||
|
|
}
|
||
|
|
|
||
|
|
# After swapping floppies in the same USB drive: drop stale mounts and re-probe media.
|
||
|
|
floppy_cmd_refresh() {
|
||
|
|
local device="${1:-}"
|
||
|
|
local do_eject="${2:-0}"
|
||
|
|
local do_mount="${3:-0}"
|
||
|
|
local wait_secs="${4:-2}"
|
||
|
|
|
||
|
|
floppy_require_cmd lsblk blockdev
|
||
|
|
device="$(floppy_resolve_burn_device "$device")"
|
||
|
|
|
||
|
|
floppy_info "refreshing $device (unmount stale state, re-probe media)"
|
||
|
|
echo "Before:"
|
||
|
|
floppy_device_show "$device"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
if floppy_device_mounted "$device"; then
|
||
|
|
floppy_unmount_device "$device"
|
||
|
|
fi
|
||
|
|
|
||
|
|
floppy_run_root blockdev --flushbufs "$device" 2>/dev/null || true
|
||
|
|
|
||
|
|
if [[ "$do_eject" -eq 1 ]]; then
|
||
|
|
if command -v eject >/dev/null 2>&1; then
|
||
|
|
floppy_info "ejecting $device (helps some USB FDD firmware notice media change)"
|
||
|
|
floppy_run_root eject "$device" 2>/dev/null \
|
||
|
|
|| floppy_warn "eject failed — swap the disk and continue anyway"
|
||
|
|
else
|
||
|
|
floppy_warn "eject command not found; install eject(1) or omit --eject"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
if command -v udevadm >/dev/null 2>&1; then
|
||
|
|
udevadm settle --timeout=5 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ "$wait_secs" -gt 0 ]]; then
|
||
|
|
floppy_info "waiting ${wait_secs}s for drive/kernel to settle"
|
||
|
|
sleep "$wait_secs"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Reading size nudges usb-storage to re-check capacity on many drives.
|
||
|
|
floppy_run_root blockdev --getsz "$device" >/dev/null 2>&1 || true
|
||
|
|
|
||
|
|
echo "After:"
|
||
|
|
floppy_device_show "$device"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
if floppy_device_has_media "$device"; then
|
||
|
|
local label mnt
|
||
|
|
label="$(floppy_device_label "$device" 2>/dev/null || true)"
|
||
|
|
mnt="$(floppy_device_field "$device" MOUNTPOINT)"
|
||
|
|
floppy_info "media detected${label:+ (label: $label)}"
|
||
|
|
if [[ "$do_mount" -eq 1 ]] && [[ -z "$mnt" ]]; then
|
||
|
|
if command -v udisksctl >/dev/null 2>&1; then
|
||
|
|
if udisksctl mount -b "$device" 2>/dev/null; then
|
||
|
|
mnt="$(lsblk -dno MOUNTPOINT "$device" 2>/dev/null)"
|
||
|
|
floppy_info "mounted at ${mnt:-?}"
|
||
|
|
else
|
||
|
|
floppy_warn "udisks mount failed — run: udisksctl mount -b $device"
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
floppy_warn "udisksctl not found; mount manually or use a file manager"
|
||
|
|
fi
|
||
|
|
elif [[ -n "$mnt" ]]; then
|
||
|
|
floppy_info "already mounted at $mnt"
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
floppy_warn "no media detected (size 0B) — insert a disk and run: floppy refresh"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# Unmount, flush, and power off USB floppy — safe to unplug the cable afterward.
|
||
|
|
floppy_cmd_disconnect() {
|
||
|
|
local device="${1:-}"
|
||
|
|
local powered_off=0
|
||
|
|
|
||
|
|
floppy_require_cmd blockdev
|
||
|
|
device="$(floppy_resolve_burn_device "$device")"
|
||
|
|
|
||
|
|
floppy_info "preparing $device for safe USB removal"
|
||
|
|
floppy_device_show "$device"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
if floppy_device_mounted "$device"; then
|
||
|
|
floppy_unmount_device "$device"
|
||
|
|
else
|
||
|
|
floppy_info "no filesystem mounted on $device"
|
||
|
|
fi
|
||
|
|
|
||
|
|
floppy_run_root blockdev --flushbufs "$device" 2>/dev/null || true
|
||
|
|
sync
|
||
|
|
|
||
|
|
if command -v udisksctl >/dev/null 2>&1; then
|
||
|
|
if udisksctl power-off -b "$device" 2>/dev/null; then
|
||
|
|
powered_off=1
|
||
|
|
floppy_info "powered off $device via udisks"
|
||
|
|
else
|
||
|
|
floppy_warn "udisks power-off failed for $device"
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
floppy_warn "udisksctl not found — install udisks2 for USB power-off"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ "$powered_off" -eq 0 ]] && command -v eject >/dev/null 2>&1; then
|
||
|
|
if floppy_run_root eject "$device" 2>/dev/null; then
|
||
|
|
powered_off=1
|
||
|
|
floppy_info "ejected $device (fallback)"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
if command -v udevadm >/dev/null 2>&1; then
|
||
|
|
udevadm settle --timeout=5 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
if [[ "$powered_off" -eq 1 ]]; then
|
||
|
|
echo "Safe to unplug the USB floppy drive now."
|
||
|
|
else
|
||
|
|
echo "Unmount and cache flush completed."
|
||
|
|
echo "Wait a few seconds, then unplug the USB floppy drive."
|
||
|
|
echo "If the device is busy, close file managers and retry: floppy disconnect"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ -b "$device" ]]; then
|
||
|
|
floppy_info "($device still visible until the cable is removed)"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_pick_device() {
|
||
|
|
local -a entries=()
|
||
|
|
local line dev score best_dev="" best_score=-1
|
||
|
|
|
||
|
|
while IFS= read -r line; do
|
||
|
|
[[ -n "$line" ]] || continue
|
||
|
|
dev="${line%% *}"
|
||
|
|
score="$(floppy_device_score "$dev")"
|
||
|
|
entries+=("$score $dev")
|
||
|
|
if (( score > best_score )); then
|
||
|
|
best_score=$score
|
||
|
|
best_dev="$dev"
|
||
|
|
fi
|
||
|
|
done < <(floppy_list_devices 0)
|
||
|
|
|
||
|
|
if [[ ${#entries[@]} -eq 0 ]]; then
|
||
|
|
floppy_die "no removable block devices found (is the USB floppy connected?)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Auto-select a clear floppy match (e.g. Mitsumi UFDD at /dev/sda).
|
||
|
|
if [[ ${#entries[@]} -eq 1 ]]; then
|
||
|
|
floppy_info "using removable device: ${entries[0]#* }"
|
||
|
|
printf '%s\n' "${entries[0]#* }"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
if (( best_score >= 100 )); then
|
||
|
|
local floppy_like=0 d s
|
||
|
|
for entry in "${entries[@]}"; do
|
||
|
|
s="${entry%% *}"
|
||
|
|
if (( s >= 100 )); then
|
||
|
|
floppy_like=$((floppy_like + 1))
|
||
|
|
best_dev="${entry#* }"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
if (( floppy_like == 1 )); then
|
||
|
|
floppy_info "using USB floppy drive: $best_dev"
|
||
|
|
printf '%s\n' "$best_dev"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "Multiple removable devices found:"
|
||
|
|
local i=1 entry s d
|
||
|
|
for entry in "${entries[@]}"; do
|
||
|
|
s="${entry%% *}"
|
||
|
|
d="${entry#* }"
|
||
|
|
echo " $i) $d $(lsblk -dno SIZE,MODEL,LABEL,TRAN "$d" 2>/dev/null | tr -s ' ') (score=$s)"
|
||
|
|
((i++)) || true
|
||
|
|
done
|
||
|
|
echo -n "Select device number [1-${#entries[@]}]: "
|
||
|
|
read -r choice
|
||
|
|
if [[ ! "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#entries[@]} )); then
|
||
|
|
floppy_die "invalid selection"
|
||
|
|
fi
|
||
|
|
entry="${entries[$((choice - 1))]}"
|
||
|
|
printf '%s\n' "${entry#* }"
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_device_mounted() {
|
||
|
|
local dev="$1"
|
||
|
|
lsblk -n -o MOUNTPOINT "$dev" 2>/dev/null | grep -q .
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_resolve_burn_device() {
|
||
|
|
local device="${1:-}"
|
||
|
|
|
||
|
|
if [[ -n "$device" ]]; then
|
||
|
|
[[ -b "$device" ]] || floppy_die "not a block device: $device"
|
||
|
|
printf '%s\n' "$device"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ -n "${FLOPPY_DEVICE:-}" ]]; then
|
||
|
|
[[ -b "$FLOPPY_DEVICE" ]] || floppy_die "FLOPPY_DEVICE is not a block device: $FLOPPY_DEVICE"
|
||
|
|
printf '%s\n' "$FLOPPY_DEVICE"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
floppy_pick_device
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_confirm_burn() {
|
||
|
|
local image="$1"
|
||
|
|
local device="$2"
|
||
|
|
local yes="${3:-0}"
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo " image: $image ($(du -h "$image" | cut -f1))"
|
||
|
|
echo " target: $device"
|
||
|
|
lsblk -d -o NAME,SIZE,MODEL,TRAN,MOUNTPOINT "$device" 2>/dev/null || true
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
if [[ "$yes" -eq 1 ]]; then
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
if floppy_device_mounted "$device"; then
|
||
|
|
floppy_warn "$device is mounted — it will be unmounted before writing"
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo -n "Write image to $device? This will ERASE the floppy. [y/N] "
|
||
|
|
read -r ans
|
||
|
|
[[ "$ans" =~ ^[Yy]$ ]] || floppy_die "aborted"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Find loop device backing an image, if any.
|
||
|
|
floppy_loop_for_image() {
|
||
|
|
local image="$1"
|
||
|
|
losetup -j "$image" 2>/dev/null | awk -F: '{print $1; exit}'
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_mountpoint_for_loop() {
|
||
|
|
local loop="$1"
|
||
|
|
lsblk -n -o MOUNTPOINT "$loop" 2>/dev/null | grep -m1 .
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_cmd_attach() {
|
||
|
|
local image_arg="${1:-}"
|
||
|
|
local size_kb="${2:-$FLOPPY_DEFAULT_SIZE_KB}"
|
||
|
|
local do_format="${3:-auto}"
|
||
|
|
local image name mountpoint loop need_format
|
||
|
|
|
||
|
|
floppy_require_cmd losetup mount blkid
|
||
|
|
floppy_ensure_diskdir
|
||
|
|
|
||
|
|
if [[ -z "$image_arg" ]]; then
|
||
|
|
name="$(floppy_random_name)"
|
||
|
|
image="$(floppy_create_blank "$name" "$size_kb")"
|
||
|
|
need_format=1
|
||
|
|
else
|
||
|
|
if [[ -f "$image_arg" ]]; then
|
||
|
|
image="$(readlink -f "$image_arg")"
|
||
|
|
name="$(floppy_image_basename "$image")"
|
||
|
|
elif [[ -f "$(floppy_image_path_in_diskdir "$image_arg")" ]]; then
|
||
|
|
image="$(floppy_resolve_image "$image_arg")"
|
||
|
|
name="$(floppy_image_basename "$image")"
|
||
|
|
else
|
||
|
|
name="${image_arg%.img}"
|
||
|
|
image="$(floppy_create_blank "$name" "$size_kb")"
|
||
|
|
need_format=1
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ "$do_format" == "yes" ]]; then
|
||
|
|
need_format=1
|
||
|
|
elif [[ "$do_format" == "no" ]]; then
|
||
|
|
need_format=0
|
||
|
|
fi
|
||
|
|
|
||
|
|
loop="$(floppy_loop_for_image "$image")"
|
||
|
|
if [[ -n "$loop" ]]; then
|
||
|
|
floppy_warn "already attached on $loop"
|
||
|
|
else
|
||
|
|
floppy_info "attaching loop device for $image"
|
||
|
|
loop="$(floppy_run_root losetup --find --show "$image")" || floppy_die "losetup failed"
|
||
|
|
floppy_info "loop device: $loop"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ "${need_format:-0}" -eq 1 ]] || ! floppy_run_root blkid "$loop" &>/dev/null; then
|
||
|
|
if [[ "$do_format" == "no" ]]; then
|
||
|
|
floppy_warn "no filesystem on $loop; mount may fail (use --format to create vfat)"
|
||
|
|
else
|
||
|
|
floppy_info "formatting $loop as vfat"
|
||
|
|
floppy_run_root mkfs -t vfat -n "${name:0:11}" "$loop" || floppy_die "mkfs failed"
|
||
|
|
need_format=1
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
mountpoint="$FLOPPY_MEDIADIR/$name"
|
||
|
|
if [[ ! -d "$mountpoint" ]]; then
|
||
|
|
floppy_info "creating mount point: $mountpoint"
|
||
|
|
floppy_run_root mkdir -p "$mountpoint"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if mountpoint -q "$mountpoint" 2>/dev/null; then
|
||
|
|
floppy_info "already mounted at $mountpoint"
|
||
|
|
else
|
||
|
|
floppy_info "mounting $loop at $mountpoint"
|
||
|
|
floppy_run_root mount "$loop" "$mountpoint" || floppy_die "mount failed"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ "${need_format:-0}" -eq 1 ]]; then
|
||
|
|
local info_file="$mountpoint/${name}.txt"
|
||
|
|
floppy_info "saving partition info to $info_file"
|
||
|
|
floppy_run_root fdisk -l "$loop" >"${info_file}.tmp" 2>/dev/null \
|
||
|
|
&& floppy_run_root chown "${USER:-root}:${USER:-root}" "${info_file}.tmp" 2>/dev/null \
|
||
|
|
&& mv "${info_file}.tmp" "$info_file" \
|
||
|
|
|| rm -f "${info_file}.tmp"
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo " image: $image"
|
||
|
|
echo " loop: $loop"
|
||
|
|
echo " mountpoint: $mountpoint"
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_cmd_detach() {
|
||
|
|
local target="${1:-}"
|
||
|
|
local image loop mountpoint
|
||
|
|
|
||
|
|
[[ -n "$target" ]] || floppy_die "specify image name, mount point, or loop device"
|
||
|
|
|
||
|
|
if [[ -b "$target" && "$target" =~ ^/dev/loop ]]; then
|
||
|
|
loop="$target"
|
||
|
|
mountpoint="$(floppy_mountpoint_for_loop "$loop")"
|
||
|
|
elif mountpoint -q "$target" 2>/dev/null; then
|
||
|
|
mountpoint="$target"
|
||
|
|
loop="$(findmnt -n -o SOURCE "$mountpoint" 2>/dev/null)"
|
||
|
|
elif [[ -f "$target" ]] || [[ -f "$(floppy_image_path_in_diskdir "$target")" ]]; then
|
||
|
|
image="$(floppy_resolve_image "$target")"
|
||
|
|
loop="$(floppy_loop_for_image "$image")"
|
||
|
|
[[ -n "$loop" ]] || floppy_die "image is not attached: $image"
|
||
|
|
mountpoint="$(floppy_mountpoint_for_loop "$loop")"
|
||
|
|
else
|
||
|
|
# Physical floppy: udisks often mounts at $FLOPPY_MEDIADIR/<LABEL> (e.g. BOOTDISK)
|
||
|
|
if mountpoint -q "$FLOPPY_MEDIADIR/${target}" 2>/dev/null; then
|
||
|
|
mountpoint="$FLOPPY_MEDIADIR/${target}"
|
||
|
|
loop="$(findmnt -n -o SOURCE "$mountpoint" 2>/dev/null)"
|
||
|
|
elif mountpoint -q "$FLOPPY_MEDIADIR/${target%.img}" 2>/dev/null; then
|
||
|
|
mountpoint="$FLOPPY_MEDIADIR/${target%.img}"
|
||
|
|
loop="$(findmnt -n -o SOURCE "$mountpoint" 2>/dev/null)"
|
||
|
|
elif image="$(floppy_try_resolve_image "$target")"; then
|
||
|
|
loop="$(floppy_loop_for_image "$image")"
|
||
|
|
[[ -n "$loop" ]] || floppy_die "image is not attached: $image"
|
||
|
|
mountpoint="$(floppy_mountpoint_for_loop "$loop")"
|
||
|
|
else
|
||
|
|
floppy_die "cannot resolve: $target"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ -n "${mountpoint:-}" ]] && mountpoint -q "$mountpoint" 2>/dev/null; then
|
||
|
|
floppy_info "unmounting $mountpoint"
|
||
|
|
floppy_run_root umount "$mountpoint" || floppy_die "umount failed"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ -n "${loop:-}" && "$loop" =~ ^/dev/loop ]]; then
|
||
|
|
floppy_info "detaching $loop"
|
||
|
|
floppy_run_root losetup -d "$loop" || floppy_die "losetup -d failed"
|
||
|
|
elif [[ -n "${loop:-}" && "$loop" =~ ^/dev/sd ]]; then
|
||
|
|
floppy_info "physical drive $loop left attached (no loop device)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
floppy_info "done"
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_copy_to_image() {
|
||
|
|
local dev="$1"
|
||
|
|
local image="$2"
|
||
|
|
local sectors="$3"
|
||
|
|
local strict="${4:-0}"
|
||
|
|
local conv="noerror,sync,fsync"
|
||
|
|
local dd_status=0 expected_bytes=$((sectors * 512)) actual_bytes=0
|
||
|
|
|
||
|
|
[[ "$strict" -eq 1 ]] && conv="fsync"
|
||
|
|
|
||
|
|
floppy_info "reading $sectors sectors from $dev into $image (conv=$conv)"
|
||
|
|
set +e
|
||
|
|
floppy_run_root dd if="$dev" of="$image" bs=512 count="$sectors" conv="$conv" status=progress 2>/dev/null
|
||
|
|
dd_status=$?
|
||
|
|
if [[ "$dd_status" -ne 0 ]]; then
|
||
|
|
floppy_run_root dd if="$dev" of="$image" bs=512 count="$sectors" conv="$conv"
|
||
|
|
dd_status=$?
|
||
|
|
fi
|
||
|
|
set -e
|
||
|
|
sync
|
||
|
|
|
||
|
|
if [[ -f "$image" ]]; then
|
||
|
|
actual_bytes="$(wc -c < "$image" | tr -d ' ')"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ "$dd_status" -ne 0 ]]; then
|
||
|
|
floppy_warn "dd exited with status $dd_status ($actual_bytes / $expected_bytes bytes)"
|
||
|
|
if [[ "$strict" -eq 1 ]]; then
|
||
|
|
floppy_die "read failed (use default noerror,sync mode, or retry after: floppy refresh)"
|
||
|
|
fi
|
||
|
|
floppy_warn "image may be incomplete — bad sectors are common on aging floppies"
|
||
|
|
elif [[ "$actual_bytes" -lt "$expected_bytes" ]]; then
|
||
|
|
floppy_warn "short read: $actual_bytes / $expected_bytes bytes — disk may have bad sectors"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_copy_to_device() {
|
||
|
|
local image="$1"
|
||
|
|
local dev="$2"
|
||
|
|
|
||
|
|
floppy_info "writing $image to $dev"
|
||
|
|
floppy_run_root dd if="$image" of="$dev" bs=512 conv=fsync status=progress 2>/dev/null \
|
||
|
|
|| floppy_run_root dd if="$image" of="$dev" bs=512 conv=fsync
|
||
|
|
sync
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_confirm_read() {
|
||
|
|
local dev="$1"
|
||
|
|
local image="$2"
|
||
|
|
local yes="${3:-0}"
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo " source: $dev"
|
||
|
|
floppy_device_show "$dev"
|
||
|
|
echo " output: $image"
|
||
|
|
if [[ -f "$image" ]]; then
|
||
|
|
echo " note: output file exists and will be overwritten"
|
||
|
|
fi
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
[[ "$yes" -eq 1 ]] && return 0
|
||
|
|
echo -n "Archive floppy to $image? [y/N] "
|
||
|
|
read -r ans
|
||
|
|
[[ "$ans" =~ ^[Yy]$ ]] || floppy_die "aborted"
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_cmd_read() {
|
||
|
|
local name="${1:-}"
|
||
|
|
local device="${2:-}"
|
||
|
|
local yes="${3:-0}"
|
||
|
|
local force="${4:-0}"
|
||
|
|
local strict="${5:-0}"
|
||
|
|
local image sectors
|
||
|
|
|
||
|
|
floppy_require_cmd dd lsblk blockdev
|
||
|
|
floppy_ensure_diskdir
|
||
|
|
device="$(floppy_resolve_burn_device "$device")"
|
||
|
|
|
||
|
|
if ! floppy_device_has_media "$device"; then
|
||
|
|
floppy_die "no media in $device — insert a floppy (size should show as 360K/720K/1.4M, not 0B)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ -z "$name" ]]; then
|
||
|
|
name="$(floppy_device_default_name "$device")"
|
||
|
|
floppy_info "using output name: $name (from volume label or mount name)"
|
||
|
|
fi
|
||
|
|
name="${name%.img}"
|
||
|
|
image="$(floppy_image_path_in_diskdir "$name")"
|
||
|
|
|
||
|
|
if [[ -f "$image" && "$force" -ne 1 ]]; then
|
||
|
|
floppy_die "output exists: $image (use --force to overwrite)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
sectors="$(floppy_device_sectors "$device")"
|
||
|
|
floppy_confirm_read "$device" "$image" "$yes"
|
||
|
|
|
||
|
|
if floppy_device_mounted "$device"; then
|
||
|
|
floppy_unmount_device "$device"
|
||
|
|
sleep 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
floppy_copy_to_image "$device" "$image" "$sectors" "$strict"
|
||
|
|
floppy_info "read complete: $image ($(du -h "$image" | cut -f1), target ${sectors} sectors)"
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_cmd_dump() {
|
||
|
|
local output="${1:-}"
|
||
|
|
local device="${2:-}"
|
||
|
|
local yes="${3:-0}"
|
||
|
|
local force="${4:-0}"
|
||
|
|
local strict="${5:-0}"
|
||
|
|
local sectors
|
||
|
|
|
||
|
|
[[ -n "$output" ]] || floppy_die "usage: floppy dump -o FILE [-d DEVICE] [-y] [--force]"
|
||
|
|
floppy_require_cmd dd blockdev
|
||
|
|
device="$(floppy_resolve_burn_device "$device")"
|
||
|
|
|
||
|
|
if ! floppy_device_has_media "$device"; then
|
||
|
|
floppy_die "no media in $device — insert a floppy"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ "$output" != "-" ]]; then
|
||
|
|
output="$(readlink -f "$output" 2>/dev/null || echo "$output")"
|
||
|
|
if [[ -e "$output" && "$force" -ne 1 ]]; then
|
||
|
|
floppy_die "output exists: $output (use --force)"
|
||
|
|
fi
|
||
|
|
mkdir -p "$(dirname "$output")" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
|
||
|
|
sectors="$(floppy_device_sectors "$device")"
|
||
|
|
floppy_confirm_read "$device" "$output" "$yes"
|
||
|
|
|
||
|
|
if floppy_device_mounted "$device"; then
|
||
|
|
floppy_unmount_device "$device"
|
||
|
|
sleep 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ "$output" == "-" ]]; then
|
||
|
|
floppy_info "dumping $sectors sectors from $device to stdout"
|
||
|
|
floppy_run_root dd if="$device" bs=512 count="$sectors" conv=noerror,sync 2>/dev/null
|
||
|
|
else
|
||
|
|
floppy_copy_to_image "$device" "$output" "$sectors" "$strict"
|
||
|
|
floppy_info "dump complete: $output"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_cmd_burn() {
|
||
|
|
local image="$1"
|
||
|
|
local device="${2:-}"
|
||
|
|
local yes="${3:-0}"
|
||
|
|
|
||
|
|
floppy_require_cmd dd lsblk
|
||
|
|
image="$(floppy_resolve_image "$image")"
|
||
|
|
device="$(floppy_resolve_burn_device "$device")"
|
||
|
|
|
||
|
|
floppy_confirm_burn "$image" "$device" "$yes"
|
||
|
|
|
||
|
|
if floppy_device_mounted "$device"; then
|
||
|
|
floppy_unmount_device "$device"
|
||
|
|
fi
|
||
|
|
|
||
|
|
floppy_copy_to_device "$image" "$device"
|
||
|
|
floppy_info "burn complete"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Parse losetup -a lines into "loop|backing_file".
|
||
|
|
floppy_foreach_loop_backing() {
|
||
|
|
local line loop backing
|
||
|
|
while IFS= read -r line; do
|
||
|
|
[[ "$line" =~ ^/dev/loop ]] || continue
|
||
|
|
loop="${line%%:*}"
|
||
|
|
if [[ "$line" =~ \((.+)\)$ ]]; then
|
||
|
|
backing="${BASH_REMATCH[1]}"
|
||
|
|
printf '%s|%s\n' "$loop" "$backing"
|
||
|
|
fi
|
||
|
|
done < <(losetup -a 2>/dev/null)
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_cmd_list() {
|
||
|
|
local verbose="${1:-0}"
|
||
|
|
local loop backing mp name label size fstype dev
|
||
|
|
local had_loop=0 had_phys=0
|
||
|
|
local -A attached_images=()
|
||
|
|
|
||
|
|
floppy_require_cmd losetup lsblk findmnt
|
||
|
|
floppy_ensure_diskdir 2>/dev/null || true
|
||
|
|
|
||
|
|
while IFS='|' read -r loop backing; do
|
||
|
|
[[ -n "$backing" ]] && attached_images["$backing"]=1
|
||
|
|
done < <(floppy_foreach_loop_backing)
|
||
|
|
|
||
|
|
echo "Loop-attached disk images:"
|
||
|
|
while IFS='|' read -r loop backing; do
|
||
|
|
[[ -n "$loop" && -n "$backing" ]] || continue
|
||
|
|
[[ "$backing" == *.img ]] || continue
|
||
|
|
had_loop=1
|
||
|
|
name="$(floppy_image_basename "$backing")"
|
||
|
|
mp="$(floppy_mountpoint_for_loop "$loop" 2>/dev/null || true)"
|
||
|
|
if [[ -n "$mp" ]]; then
|
||
|
|
printf ' %-16s %-10s → %s\n' "$name" "$loop" "$mp"
|
||
|
|
else
|
||
|
|
printf ' %-16s %-10s (not mounted)\n' "$name" "$loop"
|
||
|
|
fi
|
||
|
|
if [[ "$verbose" -eq 1 ]]; then
|
||
|
|
printf ' %s\n' "$backing"
|
||
|
|
fi
|
||
|
|
done < <(floppy_foreach_loop_backing)
|
||
|
|
[[ "$had_loop" -eq 1 ]] || echo " (none)"
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "Physical floppy media:"
|
||
|
|
while IFS= read -r dev; do
|
||
|
|
[[ -n "$dev" ]] || continue
|
||
|
|
if ! floppy_device_has_media "$dev" && ! floppy_device_mounted "$dev"; then
|
||
|
|
continue
|
||
|
|
fi
|
||
|
|
had_phys=1
|
||
|
|
size="$(floppy_device_field "$dev" SIZE)"
|
||
|
|
label="$(floppy_device_label "$dev" 2>/dev/null || echo "—")"
|
||
|
|
mp="$(floppy_device_field "$dev" MOUNTPOINT)"
|
||
|
|
if [[ -n "$mp" ]]; then
|
||
|
|
printf ' %-16s %-6s label=%s → %s\n' "$dev" "$size" "$label" "$mp"
|
||
|
|
else
|
||
|
|
printf ' %-16s %-6s label=%s (not mounted)\n' "$dev" "$size" "$label"
|
||
|
|
fi
|
||
|
|
if [[ "$verbose" -eq 1 ]]; then
|
||
|
|
printf ' model=%s fstype=%s\n' \
|
||
|
|
"$(floppy_device_field "$dev" MODEL)" "$(floppy_device_field "$dev" FSTYPE)"
|
||
|
|
fi
|
||
|
|
done < <(lsblk -dpno NAME,RM,TYPE 2>/dev/null | awk '$2==1 && $3=="disk" && $1 ~ /^\/dev\/(sd|fd)/ {print $1}')
|
||
|
|
[[ "$had_phys" -eq 1 ]] || echo " (none)"
|
||
|
|
|
||
|
|
if [[ "$verbose" -eq 1 && -d "$FLOPPY_DISKDIR" ]]; then
|
||
|
|
echo ""
|
||
|
|
echo "Disk images in $FLOPPY_DISKDIR:"
|
||
|
|
local img
|
||
|
|
for img in "$FLOPPY_DISKDIR"/*.img; do
|
||
|
|
[[ -f "$img" ]] || continue
|
||
|
|
name="$(floppy_image_basename "$img")"
|
||
|
|
if [[ -n "${attached_images[$img]:-}" ]]; then
|
||
|
|
printf ' %-16s %s [loop-attached]\n' "$name" "$(du -h "$img" | cut -f1)"
|
||
|
|
else
|
||
|
|
printf ' %-16s %s\n' "$name" "$(du -h "$img" | cut -f1)"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_cmd_status() {
|
||
|
|
floppy_require_cmd losetup lsblk findmnt
|
||
|
|
local dev
|
||
|
|
echo "Disk directory: $FLOPPY_DISKDIR"
|
||
|
|
echo "Mount base: $FLOPPY_MEDIADIR"
|
||
|
|
if [[ -n "${FLOPPY_DEVICE:-}" ]]; then
|
||
|
|
echo "FLOPPY_DEVICE: $FLOPPY_DEVICE"
|
||
|
|
fi
|
||
|
|
echo ""
|
||
|
|
echo "Attached images (loop):"
|
||
|
|
if losetup -a 2>/dev/null | grep -q '\.img'; then
|
||
|
|
losetup -a | grep '\.img' || true
|
||
|
|
else
|
||
|
|
echo " (none)"
|
||
|
|
fi
|
||
|
|
echo ""
|
||
|
|
echo "Mounts under $FLOPPY_MEDIADIR:"
|
||
|
|
if [[ -d "$FLOPPY_MEDIADIR" ]]; then
|
||
|
|
local m found=0
|
||
|
|
for m in "$FLOPPY_MEDIADIR"/*; do
|
||
|
|
[[ -e "$m" ]] || continue
|
||
|
|
found=1
|
||
|
|
findmnt -no TARGET,SOURCE,FSTYPE,OPTIONS "$m" 2>/dev/null \
|
||
|
|
|| echo " $m (present, not mounted)"
|
||
|
|
done
|
||
|
|
[[ "$found" -eq 1 ]] || echo " (empty)"
|
||
|
|
else
|
||
|
|
echo " (directory does not exist)"
|
||
|
|
fi
|
||
|
|
echo ""
|
||
|
|
echo "Removable drives:"
|
||
|
|
floppy_list_devices 1 || echo " (none)"
|
||
|
|
}
|
||
|
|
|
||
|
|
floppy_usage() {
|
||
|
|
cat <<'EOF'
|
||
|
|
floppy — create, mount, and burn floppy disk images (USB floppy drives)
|
||
|
|
|
||
|
|
Usage:
|
||
|
|
floppy make [name] [-s SIZE] Create blank .img (default 1440K)
|
||
|
|
floppy attach [name|path] [-s SIZE] Create/open image, loop-mount for editing
|
||
|
|
floppy detach <name|mount|loop> Unmount and detach loop device
|
||
|
|
floppy refresh [-d DEV] [--mount] Re-probe drive after swapping floppies
|
||
|
|
floppy disconnect [-d DEV] Unmount, power off USB — safe to unplug
|
||
|
|
floppy read [name] [-d DEV] [-y] Archive physical floppy to FLOPPY_DISKDIR
|
||
|
|
floppy dump -o FILE [-d DEV] [-y] Raw sector dump to any path (or - for stdout)
|
||
|
|
floppy burn <image> [-d DEV] [-y] Write image to removable drive
|
||
|
|
floppy devices [-v] List candidate USB/removable block devices
|
||
|
|
floppy list [-v] List attached images and physical media
|
||
|
|
floppy status Full status (config, loops, mounts, drives)
|
||
|
|
floppy help [command] Show this help
|
||
|
|
|
||
|
|
Options:
|
||
|
|
-s, --size SIZE Image size in KB: 360, 720, or 1440 (default: 1440)
|
||
|
|
-d, --device DEV Target block device for burn (e.g. /dev/sdb)
|
||
|
|
-y, --yes Skip confirmation prompt
|
||
|
|
--force Overwrite existing output file (read/dump)
|
||
|
|
--strict Abort read/dump on first I/O error (default: noerror,sync)
|
||
|
|
-o, --output FILE Output path (dump only; use - for stdout)
|
||
|
|
--mount Mount new media via udisks after refresh
|
||
|
|
--eject Run eject(1) before re-probe (some USB FDD drives)
|
||
|
|
-w, --wait SEC Seconds to wait for media settle (default: 2)
|
||
|
|
--format Force vfat format on attach (new images only by default)
|
||
|
|
--no-format Do not format even if image is blank
|
||
|
|
|
||
|
|
Environment:
|
||
|
|
FLOPPY_DISKDIR Directory for .img files (default: ~/Retro/BLANKS)
|
||
|
|
FLOPPY_MEDIADIR Base mount directory (default: /media/$USER)
|
||
|
|
FLOPPY_DEVICE Default physical drive (e.g. /dev/sda for Mitsumi UFDD)
|
||
|
|
FLOPPY_DEVICE_MATCH Regex (extended) to prefer USB floppy models in device selection
|
||
|
|
FLOPPY_DEFAULT_SIZE_KB Default image size (default: 1440)
|
||
|
|
|
||
|
|
Config file (optional):
|
||
|
|
~/.config/floppy-utils/config Shell snippet, e.g. FLOPPY_DISKDIR=/path/to/BLANKS
|
||
|
|
|
||
|
|
Legacy wrappers (same commands):
|
||
|
|
floppy-make, floppy-attach, floppy-burn
|
||
|
|
|
||
|
|
Examples:
|
||
|
|
floppy make mydisk -s 720
|
||
|
|
floppy attach mydisk # mount ~/Retro/BLANKS/mydisk.img
|
||
|
|
floppy attach # new random 1.44M image, formatted & mounted
|
||
|
|
floppy detach mydisk
|
||
|
|
floppy devices -v
|
||
|
|
floppy refresh --mount # after swapping disks in the USB drive
|
||
|
|
floppy read # archive to ~/Retro/BLANKS/<volume-label>.img
|
||
|
|
floppy read bootdisk -d /dev/sda
|
||
|
|
floppy dump -o /tmp/bootdisk.img
|
||
|
|
floppy burn mydisk.img -d /dev/sda
|
||
|
|
FLOPPY_DEVICE=/dev/sda floppy read bootdisk -y
|
||
|
|
EOF
|
||
|
|
}
|