Skip to content

Commit a35b0ab

Browse files
committed
Add format subcommand for formatting Amiga partitions
Introduce 'amifuse format <image> <partition> [volname]' which sends ACTION_FORMAT (packet type 1020) to the filesystem handler, allowing in-place formatting of partitions within disk images. The format sequence mirrors what the Workbench Format tool does: 1. Boot the handler with ACTION_STARTUP 2. ACTION_INHIBIT (DOSTRUE) to release the mounted volume 3. ACTION_FORMAT with volume name BSTR and DosType 4. ACTION_INHIBIT (DOSFALSE) to re-mount the new volume 5. ACTION_FLUSH to ensure all data is written to disk The inhibit step is required because the handler mounts the existing volume during startup and will reject formatting with ERROR_OBJECT_IN_USE (209) if the volume is still active. Driver resolution follows the same logic as mount: extract from the RDB's filesystem header blocks by default, or accept an explicit --driver path. The volume name defaults to "Empty" when not specified.
1 parent 883fd13 commit a35b0ab

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

amifuse/fuse_fs.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2126,6 +2126,101 @@ def _unmount_mountpoint():
21262126
temp_volicon.unlink()
21272127

21282128

2129+
def format_volume(
2130+
image: Path,
2131+
driver: Optional[Path],
2132+
block_size: Optional[int],
2133+
partition: str,
2134+
volname: str = "Empty",
2135+
debug: bool = False,
2136+
):
2137+
import amitools.fs.DosType as DosType
2138+
2139+
# Get partition dostype from RDB
2140+
part_info = get_partition_info(image, block_size, partition)
2141+
dostype = part_info["dostype"]
2142+
if dostype is None:
2143+
raise SystemExit(f"Could not determine dostype for partition '{partition}'.")
2144+
dt_str = DosType.num_to_tag_str(dostype)
2145+
2146+
# Resolve driver
2147+
temp_driver = None
2148+
if driver is None:
2149+
try:
2150+
result = extract_embedded_driver(image, block_size, partition)
2151+
except IOError as e:
2152+
raise SystemExit(f"Error: {e}")
2153+
if result is None:
2154+
raise SystemExit(
2155+
"No embedded filesystem driver found for this partition.\n"
2156+
"You need to specify a filesystem handler with --driver"
2157+
)
2158+
temp_driver, _, _ = result
2159+
driver = temp_driver
2160+
driver_desc = f"{dt_str}/0x{dostype:08x} (from RDB)"
2161+
else:
2162+
driver_desc = str(driver)
2163+
2164+
print(__banner__)
2165+
print(f"Formatting partition '{partition}' in {image}")
2166+
print(f"Filesystem driver: {driver_desc}")
2167+
print(f"Volume name: {volname}")
2168+
print(f"DOS type: {dt_str} (0x{dostype:08x})")
2169+
2170+
try:
2171+
bridge = HandlerBridge(
2172+
image,
2173+
driver,
2174+
block_size=block_size,
2175+
read_only=False,
2176+
debug=debug,
2177+
partition=partition,
2178+
)
2179+
2180+
# Inhibit the volume so the handler releases it for formatting
2181+
bridge.launcher.send_inhibit(bridge.state, True)
2182+
replies = bridge._run_until_replies()
2183+
if debug and replies:
2184+
_, _, r1, r2 = replies[-1]
2185+
print(f"[amifuse] INHIBIT reply: res1={r1} res2={r2}")
2186+
2187+
# Allocate volume name BSTR and send ACTION_FORMAT
2188+
_, volname_bptr = bridge.launcher.alloc_bstr(volname, label="FormatVolName")
2189+
bridge.launcher.send_format(bridge.state, volname_bptr, dostype)
2190+
replies = bridge._run_until_replies()
2191+
2192+
if not replies:
2193+
raise SystemExit("Format failed: no reply from handler.")
2194+
2195+
_, pkt_addr, res1, res2 = replies[-1]
2196+
if res1 == 0:
2197+
error_msgs = {
2198+
205: "Object in use",
2199+
218: "Not a valid DOS disk",
2200+
225: "Not a DOS disk",
2201+
226: "Wrong disk type",
2202+
232: "Disk is write-protected",
2203+
}
2204+
error_desc = error_msgs.get(res2, f"error code {res2}")
2205+
raise SystemExit(f"Format failed: {error_desc}")
2206+
2207+
if debug:
2208+
print(f"[amifuse] FORMAT reply: res1={res1} res2={res2}")
2209+
2210+
# Uninhibit to re-mount the newly formatted volume
2211+
bridge.launcher.send_inhibit(bridge.state, False)
2212+
bridge._run_until_replies()
2213+
2214+
# Flush to ensure data is written
2215+
bridge.launcher.send_flush(bridge.state)
2216+
bridge._run_until_replies()
2217+
2218+
print(f"Format complete. Volume '{volname}' created on partition '{partition}'.")
2219+
finally:
2220+
if temp_driver is not None and temp_driver.exists():
2221+
temp_driver.unlink()
2222+
2223+
21292224
__version__ = "v0.3.0"
21302225
__banner__ = f"amifuse {__version__} - Copyright (C) 2025-2026 by Stefan Reinauer"
21312226

@@ -2248,6 +2343,16 @@ def cmd_mount(args):
22482343
stats.print_stats()
22492344

22502345

2346+
def cmd_format(args):
2347+
"""Handle the 'format' subcommand."""
2348+
format_volume(
2349+
args.image, args.driver, args.block_size,
2350+
partition=args.partition,
2351+
volname=args.volname,
2352+
debug=args.debug,
2353+
)
2354+
2355+
22512356
def main(argv=None):
22522357
parser = argparse.ArgumentParser(
22532358
description=f"{__banner__}\n\n"
@@ -2270,6 +2375,12 @@ def main(argv=None):
22702375
--debug Enable debug logging of FUSE operations.
22712376
--trace Enable vamos instruction tracing (very noisy).
22722377
--profile Enable profiling and write stats to profile.txt.
2378+
2379+
format <image> <partition> [volname]
2380+
Format an Amiga partition.
2381+
--driver PATH Filesystem binary (default: extract from RDB).
2382+
--block-size N Override block size (defaults to auto/512).
2383+
--debug Enable debug logging.
22732384
""",
22742385
)
22752386
parser.add_argument(
@@ -2328,6 +2439,22 @@ def main(argv=None):
23282439
)
23292440
mount_parser.set_defaults(func=cmd_mount)
23302441

2442+
# format subcommand
2443+
format_parser = subparsers.add_parser(
2444+
"format", help="Format an Amiga partition."
2445+
)
2446+
format_parser.add_argument("image", type=Path, help="Disk image file")
2447+
format_parser.add_argument("partition", type=str, help="Partition name (e.g. DH0) or index.")
2448+
format_parser.add_argument("volname", nargs="?", default="Empty", help="Volume name (default: Empty)")
2449+
format_parser.add_argument("--driver", type=Path, help="Filesystem binary (default: extract from RDB if available)")
2450+
format_parser.add_argument(
2451+
"--block-size", type=int, help="Override block size (defaults to auto/512)."
2452+
)
2453+
format_parser.add_argument(
2454+
"--debug", action="store_true", help="Enable debug logging."
2455+
)
2456+
format_parser.set_defaults(func=cmd_format)
2457+
23312458
args = parser.parse_args(argv)
23322459
args.func(args)
23332460

amifuse/startup_runner.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ def _clear_all_block_state():
110110
ACTION_RENAME_OBJECT = 17
111111
ACTION_CREATE_DIR = 22
112112
ACTION_FLUSH = 27
113+
ACTION_FORMAT = 1020
114+
ACTION_INHIBIT = 31
113115
OFFSET_BEGINNING = -1
114116
OFFSET_CURRENT = 0
115117
OFFSET_END = 1
@@ -1013,6 +1015,14 @@ def send_flush(self, state: HandlerLaunchState):
10131015
"""ACTION_FLUSH: flush filesystem buffers."""
10141016
return self.send_packet(state, ACTION_FLUSH, [])
10151017

1018+
def send_inhibit(self, state: HandlerLaunchState, inhibit: bool):
1019+
"""ACTION_INHIBIT: dp_Arg1=DOSTRUE (-1) to inhibit, DOSFALSE (0) to uninhibit."""
1020+
return self.send_packet(state, ACTION_INHIBIT, [-1 if inhibit else 0])
1021+
1022+
def send_format(self, state: HandlerLaunchState, volname_bptr: int, dostype: int):
1023+
"""ACTION_FORMAT: dp_Arg1=volume name BSTR (BPTR), dp_Arg2=DosType."""
1024+
return self.send_packet(state, ACTION_FORMAT, [volname_bptr, dostype])
1025+
10161026
def _alloc_signal_bit(self) -> int:
10171027
"""Reserve a signal bit from our local bitmap."""
10181028
for bit in range(32):

0 commit comments

Comments
 (0)