-
Notifications
You must be signed in to change notification settings - Fork 0
Phase 34 os path plan
Companion to the Phase 34 design block in Amiga port design. That section answers what and why; this file is the step-by-step ship plan — how to chunk the work into landable PRs.
Phase 34 closes the os / os.path gap that surfaces when comparing
against OoZe1911's port. The C-side os module (registered as
MP_REGISTER_EXTENSIBLE_MODULE) merges with a frozen os.py so the
existing C entries stay live and the frozen file adds the rest:
import os
os.makedirs("Work:scripts/sub/dir", exist_ok=True)
for root, dirs, files in os.walk("Work:"):
print(root, len(files))
os.chmod("DH0:foo", 0)
mask = os.getprotect("DH0:foo")
os.path.join("Work:", "scripts", "foo.py") # 'Work:scripts/foo.py'
os.path.abspath("foo.py") # uses cwd, volume-aware
os.path.isabs("Sys:Prefs") # True
os.path.normpath("Work:scripts/../bin") # 'Work:bin'Step 1: os.chmod + os.getprotect (C entries) + FIBF_* constants
↓
Step 2: Frozen os.py + _ospath.py
↓
Step 3: Docs flip + smoke tests
| # | Step | Output | On-target smoke |
|---|---|---|---|
| 1 | New port-local modosamiga.c registering _osamiga with chmod(path, mask) (SetProtection), getprotect(path) (Lock+Examine+fib_Protection), and the FIBF_* constants. Surfaced via the frozen os.py from _osamiga import ... in Step 2 (rather than amending extmod/modos.c, which would violate the port-separation rule). |
New port-local C module. | From the REPL under Amiberry: os.getprotect("S:Startup-Sequence") returns a mask; os.chmod round-trips. |
| 2 | Frozen ports/amiga/modules/os.py with makedirs (recursive mkdir, AmigaOS volume-aware) and walk (recursive listdir + stat tree generator). Frozen ports/amiga/modules/_ospath.py with join / split / splitext / basename / dirname / exists / isfile / isdir / isabs / abspath / normpath. os.py does import _ospath as path so os.path Just Works. |
Two frozen modules. |
os.makedirs + os.walk against a temp tree. os.path.normpath collapses .. correctly across volume separators. |
| 3 | Docs flip, manifest verification, smoke test. |
docs/amiga.md Phase 34 → ✅. docs/amiga-testing.md gains a short os / os.path subsection. |
tests/ports/amiga/test_os_smoke.py covers the surface and the volume-separator edge cases under vamos. |
Each step is small: Step 1 is ~50 LOC C, Step 2 is ~150 LOC Python, Step 3 is paperwork.
- Two C functions in
ports/amiga/modosamiga.c:Registered as thestatic mp_obj_t mod_osamiga_chmod(mp_obj_t path_obj, mp_obj_t mask_obj); static mp_obj_t mod_osamiga_getprotect(mp_obj_t path_obj);
_osamigamodule viaMP_REGISTER_MODULE. The frozenos.py(Step 2) doesfrom _osamiga import chmod, getprotect, FIBF_*so callers see them onos. -
os.chmod(path, mask):- Suppress AmigaDOS auto-requesters (
pr_WindowPtr = -1) around the call so a path on an unmounted volume gets a clean OSError instead of a system dialog (matchesamiga.exists()/amiga.match()). -
SetProtection(name, mask); raises OSError on failure with the errno mappingamiga_dos_errno_from.
- Suppress AmigaDOS auto-requesters (
-
os.getprotect(path):-
Lock(path, SHARED_LOCK)→Examine(lock, &fib)→fib.fib_Protection. UnLock on the way out. - Same auto-requester suppression.
- Returns the raw
fib_ProtectionULONG.
-
-
FIBF_*constants exposed on_osamigaand pulled intoos.*via the frozenos.pystar-list import:-
os.FIBF_READ,os.FIBF_WRITE,os.FIBF_EXECUTE,os.FIBF_DELETE,os.FIBF_ARCHIVE,os.FIBF_PURE,os.FIBF_SCRIPT,os.FIBF_HOLD(the NDK shipsHOLD, notHIDDEN; the plan was wrong here).
-
AmigaDOS protection flags are inverted for the four classic
RWED bits — a set bit means "denied", a clear bit means
"allowed." The bits exposed by FIBF_READ / WRITE / EXECUTE /
DELETE follow the on-disk encoding, so chmod(path, 0) grants
all four (no bits set = nothing denied). ARCHIVE, PURE,
SCRIPT, HIDDEN follow the "set means yes" convention.
Document the inversion in the module docstring so callers aren't
surprised; this is the same convention Protect from the
AmigaShell uses.
REPL on target:
>>> import os
>>> oct(os.getprotect("S:Startup-Sequence")) # readable, writable
'0o15'
>>> os.chmod("Ram Disk:test", 0) # all allowed, no flags
>>> os.getprotect("Ram Disk:test")
0Vamos: dos.library's SetProtection / Examine work end-to-end
through mp: volumes, so the call paths are testable.
# Frozen extension for the C-side `os` module. The C entries
# (chdir, getcwd, listdir, mkdir, remove, rename, rmdir, stat,
# statvfs, getenv, putenv, unsetenv, chmod, getprotect, plus the
# FIBF_* constants) are already in this module's globals when
# this file is loaded -- the extensible-module mechanism merges
# them in.
import _ospath as path # exposes amiga.os.path
def makedirs(name, exist_ok=False):
"""mkdir -p semantics, AmigaOS volume aware."""
# walk Volume:dir1/dir2/dir3 component by component
def walk(top, topdown=True):
"""Recursive listdir + stat tree generator."""
# follow same shape as CPython os.walkmakedirs handles Work:scripts/sub/dir:
- Split off
Work:as the volume prefix (must exist). - For each subsequent component, accumulate the path with
/andmkdirit, toleratingEEXISTwhenexist_ok=True.
walk follows CPython's os.walk shape — (dirpath, dirnames, filenames) triples, topdown ordering preserved. AmigaOS paths
join correctly when the parent ends with : (no extra /) vs
when it ends with anything else.
Pure-Python helpers. Volume-aware policy: the first : in a path
is the volume terminator (Sys:Prefs/Workbench); subsequent /
characters separate components. A leading : is illegal in
AmigaOS so we don't need to worry about that edge case.
Functions:
-
join(*parts)— concatenate with the right separator:-
join("Work:", "scripts")→"Work:scripts"(no separator after:) -
join("Work:scripts", "foo.py")→"Work:scripts/foo.py" - A part containing
:resets the join (it's a fresh absolute path).
-
-
split(p)→(dirname, basename). Splits at the last/or:; the separator character is retained on the dirname side. -
splitext(p)→(root, ext). Last.after the last separator. -
basename(p)→ second element ofsplit. -
dirname(p)→ first element ofsplit. -
isabs(p)— has a:(volume reference) anywhere in the path, not just at index 0. AmigaOS treatsWork:and:fooandSys:Prefs/Workbenchall as absolute. -
abspath(p)—pifisabs(p), elsejoin(getcwd(), p). -
normpath(p)— collapse.and..components without crossing the volume boundary (Work:..is meaningless and stays as-is). -
exists,isfile,isdir— wrapstat().
>>> os.path.join("Work:", "scripts", "foo.py")
'Work:scripts/foo.py'
>>> os.path.normpath("Work:scripts/../bin/./tool")
'Work:bin/tool'
>>> os.path.isabs("Sys:")
True
>>> os.path.isabs("foo.py")
False
>>> os.path.split("Work:scripts/foo.py")
('Work:scripts', 'foo.py')
>>> os.path.split("Work:foo.py")
('Work:', 'foo.py')
>>> os.makedirs("Ram Disk:a/b/c", exist_ok=True)-
docs/amiga.mdPhase 34 status → ✅; section gains the full surface matrix (which calls / which module / what they return). -
docs/amiga-testing.mdshortos/os.pathsubsection with REPL examples and a note about the AmigaDOS inverted-bit semantics. -
tests/ports/amiga/test_os_smoke.py— vamos-runnable:-
os.path.isabs("Sys:")is True;isabs("foo.py")is False -
os.path.join/split/normpathcorner cases -
os.makedirsround-trip againstmp:tree -
os.chmod/os.getprotectround-trip on a temp file -
os.walkyields the expected(dirpath, dirs, files)shape
-
-
Volume-vs-directory separator policy.
:terminates the volume once per path;/separates directories after that. Pure-Python helpers in_ospath.pycarry that invariant; the C entries already do (they pass the string straight through todos.library). -
AmigaDOS protection-bit inversion. Document loudly in
_ospath.py/os.py/docs/amiga-testing.md. Set bit ⇒ denial for RWED; set bit ⇒ assertion for APSH. -
No CPython-compat translation layer for
chmod. Posix rwx → AmigaDOS rwed is ambiguous and lossy; we take the AmigaDOS mask directly. CPython interop scripts that need cross-platformchmodshould useif sys.platform == 'amiga'. -
Extensible-module attribute lookup. When the frozen
os.pyruns, the C-side globals are already populated, so functions defined in the frozen file shadow nothing — they extend. If a future C-side change adds e.g. amkdirwith a new signature, the frozen file'smakedirscontinues to call it through the merged namespace (no name change needed).
- Posix mode translation for
chmod— ambiguous, see above. -
os.path.expanduser/expandvars— AmigaOS usesdos.library GetVarand there's no~concept. -
os.walkfollow-symlinks / on-error callback — keep minimal shape; users wrap if needed. - Full CPython
os.pathparity (commonpath,relpath,samefile,getmtime, etc.) — add later if a real call site needs them. -
os.environmapping interface — Phase 20'sgetenv/putenv/unsetenvare enough for now.