From 4d656aa288a8ff2331299292a4be333714e0394b Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Thu, 1 Oct 2026 04:31:56 -0700 Subject: [PATCH 1/5] installer: a kept interface file follows the AmiNetXDuo: assign fa671b01 made a drawer install write DEVICE=AmiNetXDuo:Devs/Networks/ for its drivers, but only for files it writes. A reinstall that answers "Keep it" leaves the old DEVICE=SYS:AmiNetXDuo/Devs/Networks/ anxnet.device in place (the lab A1200's file), and that drawer goes on opening the old drawer's drivers under a library from wherever the assign points next. The Installer cannot read a line back, so InstallSameFile gains a DEVICES mode: for each kept file in drawer layout it rewrites a DEVICE line only when it names anxnet/anxgenet/anxwifipi/anxzz9000.device by an absolute path whose directory is, by SameLock, this drawer's Devs/Networks or the one AmiNetXDuo: still names. Only the value changes; spacing, quotes, comments, other pairs and every other line stay byte-identical. A third-party driver, one of ours elsewhere, a relative name, a commented line or an escaped quoted value is left alone. The file is replaced through .new.info/.old.info as NetPrefs does, and a volume that is not mounted raises no requester. The rule is pure text in devicehome.c, covered on the host by test_devicehome (ctest devicehome_rewrites_only_ours). Co-Authored-By: Claude Opus 5.5 --- install/Install-AmiNetXDuo | 27 +++++ src/tools/CMakeLists.txt | 13 ++- src/tools/devicehome.c | 139 +++++++++++++++++++++++ src/tools/devicehome.h | 28 +++++ src/tools/installsamefile.c | 184 +++++++++++++++++++++++++++---- src/tools/test/test_devicehome.c | 114 +++++++++++++++++++ 6 files changed, 485 insertions(+), 20 deletions(-) create mode 100644 src/tools/devicehome.c create mode 100644 src/tools/devicehome.h create mode 100644 src/tools/test/test_devicehome.c diff --git a/install/Install-AmiNetXDuo b/install/Install-AmiNetXDuo index 731ce1b5c..a6e9ac92c 100644 --- a/install/Install-AmiNetXDuo +++ b/install/Install-AmiNetXDuo @@ -2350,6 +2350,33 @@ (safe)) )) +; A KEPT FILE FOLLOWS THE ASSIGN TOO. One written before the drawer named +; its drivers through AmiNetXDuo: says DEVICE=SYS:AmiNetXDuo/Devs/Networks/... +; and would go on opening that drawer's drivers under a library from wherever +; the assign points next. The Installer cannot read the line, so +; InstallSameFile rewrites it: only a DEVICE line naming one of our drivers +; in this drawer, or the one the assign still names, and only its value. +; Another driver, or one of ours elsewhere, is the user's choice and stays. +(if (AND KEEP_OLD DRAWER_LAYOUT) + (foreach D_IFDIR "~(#?.info)" + (if (< @each-type 0) + ( + (set KEPT_IF (tackon D_IFDIR @each-name)) + (if (<> 0 + (run ("\"%s\" \"%s\" \"%s\" \"%s\" DEVICES" + S_SAMEFILE KEPT_IF (tackon D_DEVS "Networks") + "AmiNetXDuo:Devs/Networks") + (prompt "Pointing the kept configuration at this " + "drawer's drivers") + (help "A kept interface file that names a driver " + "of this drawer by its full path is changed to " + "name it through AmiNetXDuo:. Nothing else in " + "the file changes."))) + (message + "\"" KEPT_IF "\" could not be updated. If its DEVICE line " + "names a driver by this drawer's full path, change it to " + "AmiNetXDuo:Devs/Networks/ followed by the driver's name.")))))) + ; --- the netdb files, only where there is nothing already ---------------- ; ; A machine that has had Roadshow or AmiTCP on it already has these, very diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 9303085dd..69192a61a 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -227,7 +227,7 @@ aminetxduo_add_tool(tool_addnetinterface AddNetInterface # object; dos.library can. The archive keeps this beside the Installer and # does not copy it into C:. aminetxduo_add_tool(tool_installsamefile InstallSameFile - SOURCES installsamefile.c) + SOURCES installsamefile.c devicehome.c) # The other Installer-only primitive: identify Emu68's supported on-board # network devices from its device tree without opening either device. @@ -910,6 +910,16 @@ if(NOT CMAKE_CROSSCOMPILING) target_compile_options(test_netprefs_text PRIVATE -Wall -Wextra) set_target_properties(test_netprefs_text PROPERTIES C_STANDARD 11) + # A kept interface file's DEVICE line follows the AmiNetXDuo: assign + # (InstallSameFile DEVICES): only our drivers in this drawer move. + add_executable(test_devicehome + test/test_devicehome.c + devicehome.c) + target_include_directories(test_devicehome PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}") + target_compile_options(test_devicehome PRIVATE -Wall -Wextra) + set_target_properties(test_devicehome PROPERTIES C_STANDARD 11) + # NetPrefs lays itself out from the gadget font's metrics. Every page at # topaz 8, 11, a proportional 13 and a wide 15 keeps labels off borders # and gadgets apart; a font too wide for 640 reports that it does not fit. @@ -1149,6 +1159,7 @@ if(NOT CMAKE_CROSSCOMPILING) add_test(NAME fetchurl_rfc3986 COMMAND test_fetchurl) add_test(NAME toolsock_connect_budget COMMAND test_toolbudget) add_test(NAME netprefs_preserves_text COMMAND test_netprefs_text) + add_test(NAME devicehome_rewrites_only_ours COMMAND test_devicehome) add_test(NAME netprefs_font_layout COMMAND test_netprefs_layout) add_test(NAME httpframe_bodies COMMAND test_httpframe) add_test(NAME httplock_rfc4918 COMMAND test_httplock) diff --git a/src/tools/devicehome.c b/src/tools/devicehome.c new file mode 100644 index 000000000..7cd796941 --- /dev/null +++ b/src/tools/devicehome.c @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: MIT */ +#include "devicehome.h" + +/* Every driver an install writes into its Devs/Networks. */ +static const char *const dh_drivers[] = +{ + "anxnet.device", + "anxgenet.device", + "anxwifipi.device", + "anxzz9000.device", +}; + +#define DH_DRIVERS (sizeof(dh_drivers) / sizeof(dh_drivers[0])) + +static size_t dh_len(const char *s) +{ + size_t n = 0; + while (s[n] != '\0') n++; + return n; +} + +static char dh_lower(char c) +{ + return (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c; +} + +/* AmigaDOS names compare without case. */ +static int dh_equal(const char *a, size_t alen, const char *b) +{ + size_t i; + for (i = 0; i < alen; i++) + if (b[i] == '\0' || dh_lower(a[i]) != dh_lower(b[i])) return 0; + return b[alen] == '\0'; +} + +int dh_absolute_driver(const char *value, size_t len, char *dir, + size_t dir_cap, const char **driver) +{ + size_t colon = 0, base = 0, dir_len, i, d; + + while (colon < len && value[colon] != ':') colon++; + /* No volume (":Devs/..." is the current one), or the assign itself. */ + if (colon == 0 || colon >= len || dh_equal(value, colon, "AmiNetXDuo")) + return 0; + for (i = colon; i < len; i++) + if (value[i] == ':' || value[i] == '/') base = i + 1; + + for (d = 0; d < DH_DRIVERS; d++) + if (dh_equal(value + base, len - base, dh_drivers[d])) break; + if (d == DH_DRIVERS) + return 0; + + dir_len = (value[base - 1] == '/') ? base - 1 : base; + if (dir_len + 1 > dir_cap) + return 0; + for (i = 0; i < dir_len; i++) dir[i] = value[i]; + dir[dir_len] = '\0'; + *driver = dh_drivers[d]; + return 1; +} + +static int dh_put(char *out, size_t cap, size_t *used, const char *s, size_t n) +{ + size_t i; + if (n > cap - *used) return 0; + for (i = 0; i < n; i++) out[(*used)++] = s[i]; + return 1; +} + +/* The value of a DEVICE line, as src/config/config_text.c reads it: "DEVICE", + optional '=', then one token or a quoted string. A quoted value with an + AmigaDOS '*' escape is not offered: its bytes are not the name. */ +static int dh_device_value(const char *line, size_t len, size_t *vbeg, + size_t *vend) +{ + size_t p = 0, k; + + while (p < len && (line[p] == ' ' || line[p] == '\t')) p++; + k = p; + while (p < len && line[p] != '=' && line[p] != ' ' && line[p] != '\t' && + line[p] != '\r') + p++; + if (!dh_equal(line + k, p - k, "device")) return 0; + while (p < len && (line[p] == ' ' || line[p] == '\t')) p++; + if (p < len && line[p] == '=') p++; + while (p < len && (line[p] == ' ' || line[p] == '\t')) p++; + + if (p < len && line[p] == '"') + { + *vbeg = ++p; + while (p < len && line[p] != '"') + if (line[p++] == '*') return 0; + if (p >= len) return 0; + } + else + { + *vbeg = p; + while (p < len && line[p] != ' ' && line[p] != '\t' && line[p] != '\r' && + line[p] != '#' && line[p] != ';') + p++; + } + *vend = p; + return p > *vbeg; +} + +long dh_rehome(const char *old, size_t oldlen, char *out, size_t cap, + size_t *newlen, DhOursFn ours, void *ctx) +{ + size_t at = 0, used = 0; + long rewritten = 0; + + while (at < oldlen) + { + size_t end = at, vbeg, vend; + char dir[128]; + const char *driver; + + while (end < oldlen && old[end] != '\n') end++; + if (dh_device_value(old + at, end - at, &vbeg, &vend) && + dh_absolute_driver(old + at + vbeg, vend - vbeg, dir, sizeof(dir), + &driver) && + ours(dir, ctx)) + { + if (!dh_put(out, cap, &used, old + at, vbeg) || + !dh_put(out, cap, &used, DH_ASSIGN_NETWORKS "/", + sizeof(DH_ASSIGN_NETWORKS "/") - 1) || + !dh_put(out, cap, &used, driver, dh_len(driver))) + return -1; + at += vend; + rewritten++; + } + if (end < oldlen) end++; + if (!dh_put(out, cap, &used, old + at, end - at)) + return -1; + at = end; + } + *newlen = used; + return rewritten; +} diff --git a/src/tools/devicehome.h b/src/tools/devicehome.h new file mode 100644 index 000000000..a30e2b06e --- /dev/null +++ b/src/tools/devicehome.h @@ -0,0 +1,28 @@ +/* Where an interface file's DEVICE= finds one of this stack's drivers. Pure + text, shared by InstallSameFile and CheckNetConfig and tested on the host. */ +#ifndef AMINETXDUO_DEVICEHOME_H +#define AMINETXDUO_DEVICEHOME_H + +#include + +/* The drawer layout's own name for its drivers. */ +#define DH_ASSIGN_NETWORKS "AmiNetXDuo:Devs/Networks" + +/* For a DEVICE value naming one of OUR drivers by an absolute path that does + not go through the AmiNetXDuo: assign: returns 1, the directory part in + dir (NUL-terminated; "Vol:" or "Vol:a/b") and the driver's own name. + Returns 0 for anything else, including a dir that does not fit. */ +int dh_absolute_driver(const char *value, size_t len, char *dir, + size_t dir_cap, const char **driver); + +/* Asked for each candidate directory: nonzero when it is this drawer's. */ +typedef int (*DhOursFn)(const char *dir, void *ctx); + +/* Copy old to out, replacing the value of every DEVICE line that + dh_absolute_driver() accepts and ours() claims by + AmiNetXDuo:Devs/Networks/. Every other byte is kept. Returns the + number of lines rewritten, or -1 when out is too small. */ +long dh_rehome(const char *old, size_t oldlen, char *out, size_t cap, + size_t *newlen, DhOursFn ours, void *ctx); + +#endif diff --git a/src/tools/installsamefile.c b/src/tools/installsamefile.c index 1a22e4049..66bfe9064 100644 --- a/src/tools/installsamefile.c +++ b/src/tools/installsamefile.c @@ -10,37 +10,177 @@ * Exit status is RETURN_OK only for the same object, RETURN_WARN for two * different objects, and RETURN_ERROR when either name cannot be locked. * + * DEVICES: FIRST is a kept interface file, SECOND one or more Devs/Networks + * drawers of this installation. A DEVICE line naming one of our drivers by + * an absolute path into one of them is rewritten to name it through + * AmiNetXDuo:, as a fresh drawer install writes it; every other byte stays + * (devicehome.c). The Installer cannot read the line back, so this is the + * one place a kept file can follow the assign. RETURN_OK when the file is + * as it should be, RETURN_ERROR when it could not be read or replaced, and + * then it is unchanged. + * * SPDX-License-Identifier: MIT */ #include +#include #include +#include #include #include +#include #include "aminetxduo/version.h" +#include "devicehome.h" const char *const tool_name = "InstallSameFile"; static const char version_tag[] __attribute__((used)) = TOOL_VERSTAG("InstallSameFile"); -#define TEMPLATE "FIRST/A,SECOND/A" +#define TEMPLATE "FIRST/A,SECOND/A/M,DEVICES/S" + +/* An interface file is a few hundred bytes; the cap only bounds a stray. */ +#define DEVICES_FILE_MAX 65536L +#define DEVICES_GROWTH 256L enum { ARG_FIRST = 0, ARG_SECOND, + ARG_DEVICES, ARG_COUNT }; +static int same_object(const char *a, const char *b) +{ + BPTR first = Lock((CONST_STRPTR)a, ACCESS_READ); + BPTR second = Lock((CONST_STRPTR)b, ACCESS_READ); + LONG same = LOCK_DIFFERENT; + + if (first != 0 && second != 0) + same = SameLock(first, second); + if (second != 0) + UnLock(second); + if (first != 0) + UnLock(first); + if (first == 0 || second == 0) + return -1; + return same == LOCK_SAME; +} + +static int ours(const char *dir, void *ctx) +{ + STRPTR *drawer; + + for (drawer = (STRPTR *)ctx; *drawer != NULL; drawer++) + if (same_object(dir, (const char *)*drawer) == 1) + return 1; + return 0; +} + +static ULONG cat(char *out, ULONG cap, const char *a, const char *b) +{ + ULONG n = 0; + + while (*a != '\0' && n + 1 < cap) out[n++] = *a++; + while (*b != '\0' && n + 1 < cap) out[n++] = *b++; + out[n] = '\0'; + return (*a == '\0' && *b == '\0') ? n : 0; +} + +/* NetPrefs' order: the new bytes are whole on disk before the old name goes, + and both spare names end in .info, which the interface scan skips. */ +static BOOL replace_file(const char *path, const char *data, LONG length) +{ + char tmp[256]; + char old[256]; + BPTR fh; + LONG wrote; + + if (cat(tmp, sizeof(tmp), path, ".new.info") == 0 || + cat(old, sizeof(old), path, ".old.info") == 0) + return FALSE; + + (VOID)DeleteFile((CONST_STRPTR)tmp); + fh = Open((CONST_STRPTR)tmp, MODE_NEWFILE); + if (fh == 0) + return FALSE; + wrote = Write(fh, (APTR)data, length); + Close(fh); + if (wrote != length) + { + (VOID)DeleteFile((CONST_STRPTR)tmp); + return FALSE; + } + + (VOID)DeleteFile((CONST_STRPTR)old); + if (!Rename((CONST_STRPTR)path, (CONST_STRPTR)old)) + { + (VOID)DeleteFile((CONST_STRPTR)tmp); + return FALSE; + } + if (!Rename((CONST_STRPTR)tmp, (CONST_STRPTR)path)) + { + (VOID)Rename((CONST_STRPTR)old, (CONST_STRPTR)path); + (VOID)DeleteFile((CONST_STRPTR)tmp); + return FALSE; + } + (VOID)DeleteFile((CONST_STRPTR)old); + return TRUE; +} + +static LONG rehome_devices(const char *path, STRPTR *drawers) +{ + BPTR fh; + LONG size; + LONG got; + char *old; + char *out; + size_t newlen = 0; + long rewritten; + LONG rc = RETURN_ERROR; + + fh = Open((CONST_STRPTR)path, MODE_OLDFILE); + if (fh == 0) + return RETURN_ERROR; + (VOID)Seek(fh, 0, OFFSET_END); + size = Seek(fh, 0, OFFSET_BEGINNING); /* the previous position */ + if (size < 0 || size > DEVICES_FILE_MAX) + { + Close(fh); + return RETURN_ERROR; + } + old = (char *)AllocVec((ULONG)(2 * size + DEVICES_GROWTH + 1), MEMF_ANY); + if (old == NULL) + { + Close(fh); + return RETURN_ERROR; + } + got = Read(fh, old, size); + Close(fh); + out = old + size; + + if (got == size) + { + rewritten = dh_rehome(old, (size_t)size, out, + (size_t)(size + DEVICES_GROWTH), &newlen, + ours, drawers); + if (rewritten == 0) + rc = RETURN_OK; + else if (rewritten > 0 && replace_file(path, out, (LONG)newlen)) + rc = RETURN_OK; + } + FreeVec(old); + return rc; +} + int main(int argc, char **argv) { - LONG args[ARG_COUNT] = { 0, 0 }; + LONG args[ARG_COUNT] = { 0, 0, 0 }; struct RDArgs *rda; - BPTR first; - BPTR second; - LONG same; + STRPTR *second; + LONG rc; (VOID)argv; @@ -51,22 +191,28 @@ int main(int argc, char **argv) if (rda == NULL) return RETURN_ERROR; - first = Lock((CONST_STRPTR)args[ARG_FIRST], ACCESS_READ); - second = Lock((CONST_STRPTR)args[ARG_SECOND], ACCESS_READ); - if (first == 0 || second == 0) + second = (STRPTR *)args[ARG_SECOND]; + if (args[ARG_DEVICES] != 0) { - if (first != 0) - UnLock(first); - if (second != 0) - UnLock(second); - FreeArgs(rda); - return RETURN_ERROR; + /* A DEVICE line naming a volume that is not mounted is simply not + ours; it must not stop the Installer at "Please insert". */ + struct Process *me = (struct Process *)FindTask(NULL); + APTR win = me->pr_WindowPtr; + + me->pr_WindowPtr = (APTR)-1; + rc = rehome_devices((const char *)args[ARG_FIRST], second); + me->pr_WindowPtr = win; + } + else if (second[1] != NULL) + rc = RETURN_ERROR; + else + { + int same = same_object((const char *)args[ARG_FIRST], + (const char *)second[0]); + + rc = same < 0 ? RETURN_ERROR : same ? RETURN_OK : RETURN_WARN; } - same = SameLock(first, second); - UnLock(second); - UnLock(first); FreeArgs(rda); - - return (same == LOCK_SAME) ? RETURN_OK : RETURN_WARN; + return rc; } diff --git a/src/tools/test/test_devicehome.c b/src/tools/test/test_devicehome.c new file mode 100644 index 000000000..e28fe5788 --- /dev/null +++ b/src/tools/test/test_devicehome.c @@ -0,0 +1,114 @@ +/* A kept interface file follows the AmiNetXDuo: assign: only a DEVICE line + naming one of our drivers in this drawer changes, and only its value. */ +#include +#include + +#include "devicehome.h" + +static int failures; + +static void check(int ok, const char *what) +{ + if (!ok) + { + printf("FAIL: %s\n", what); + failures++; + } +} + +/* Stands in for SameLock: the drawers the installer names as its own. */ +static int ours(const char *dir, void *ctx) +{ + const char *const *mine = (const char *const *)ctx; + for (; *mine != NULL; mine++) + if (strcmp(dir, *mine) == 0) return 1; + return 0; +} + +static const char *const drawer[] = { + "SYS:AmiNetXDuo/Devs/Networks", "DH0:AmiNetXDuo/Devs/Networks", NULL +}; + +static void expect(const char *in, const char *want, long n, const char *what) +{ + char out[1024]; + size_t len = 0; + long got = dh_rehome(in, strlen(in), out, sizeof(out), &len, ours, + (void *)drawer); + + check(got == n, what); + check(len == strlen(want) && memcmp(out, want, len) == 0, what); + if (len != strlen(want) || memcmp(out, want, len) != 0) + printf(" got: [%.*s]\n", (int)len, out); +} + +int main(void) +{ + char dir[128]; + const char *drv; + + expect("; written by 0.30.0\n" + "DEVICE=SYS:AmiNetXDuo/Devs/Networks/anxnet.device\n" + "CARD=ne2000_pcmcia\n" + "UNIT=0\r\n" + "CONFIGURE=DHCP\n", + "; written by 0.30.0\n" + "DEVICE=AmiNetXDuo:Devs/Networks/anxnet.device\n" + "CARD=ne2000_pcmcia\n" + "UNIT=0\r\n" + "CONFIGURE=DHCP\n", + 1, "the lab A1200's file follows the assign; nothing else moves"); + + expect("DEVICE = \"DH0:AmiNetXDuo/Devs/Networks/ANXGENET.device\" ; pi\n", + "DEVICE = \"AmiNetXDuo:Devs/Networks/anxgenet.device\" ; pi\n", + 1, "spacing, quotes and the trailing comment are kept"); + + expect("device SYS:AmiNetXDuo/Devs/Networks/anxwifipi.device", + "device AmiNetXDuo:Devs/Networks/anxwifipi.device", + 1, "no '=' and no final newline"); + + expect("DEVICE=SYS:AmiNetXDuo/Devs/Networks/anxzz9000.device UNIT=0\n", + "DEVICE=AmiNetXDuo:Devs/Networks/anxzz9000.device UNIT=0\n", + 1, "a second pair on the line is kept"); + + { + static const char same[] = + "DEVICE=SYS:Other/Devs/Networks/anxnet.device\n" + "DEVICE=SYS:AmiNetXDuo/Devs/Networks/a2065.device\n" + "DEVICE=DEVS:Networks/anxnet.device\n" + "DEVICE=anxnet.device\n" + "DEVICE=AmiNetXDuo:Devs/Networks/anxnet.device\n" + "DEVICE=aminetxduo:devs/networks/anxnet.device\n" + "DEVICE=:AmiNetXDuo/Devs/Networks/anxnet.device\n" + ";DEVICE=SYS:AmiNetXDuo/Devs/Networks/anxnet.device\n" + "# DEVICE=SYS:AmiNetXDuo/Devs/Networks/anxnet.device\n" + "DEVICES=SYS:AmiNetXDuo/Devs/Networks/anxnet.device\n" + "DEVICE=\"SYS:AmiNetXDuo/Devs/Networks/anx*\"net.device\"\n" + "DEVICE=SYS:AmiNetXDuo/Devs/Networks/anxnet.device.old\n" + "DEVICE=\n"; + expect(same, same, 0, + "elsewhere, third-party, relative, assign, comments, escapes: " + "byte-identical"); + } + + check(dh_absolute_driver("Work:x/anxnet.device", 20, dir, sizeof(dir), + &drv) && strcmp(dir, "Work:x") == 0 && + strcmp(drv, "anxnet.device") == 0, "directory part"); + check(dh_absolute_driver("RAM:anxnet.device", 17, dir, sizeof(dir), + &drv) && strcmp(dir, "RAM:") == 0, + "a volume root keeps its colon"); + check(!dh_absolute_driver("RAM:anxnet.device", 17, dir, 4, &drv), + "a directory that does not fit is not offered"); + + { + static const char in[] = + "DEVICE=SYS:AmiNetXDuo/Devs/Networks/anxnet.device\n"; + char small[40]; + size_t len; + check(dh_rehome(in, sizeof(in) - 1, small, sizeof(small), &len, ours, + (void *)drawer) == -1, "overflow is reported"); + } + + if (failures == 0) printf("devicehome: all passed\n"); + return failures != 0; +} From 518404a9a78fca6f2b58b6fbebe1c4654b60136b Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Thu, 1 Oct 2026 04:31:56 -0700 Subject: [PATCH 2/5] wifipi: compile against the tree's anxs2ext.h, not the fork's copy third_party/wifipi carries include/aminetxduo/anxs2ext.h, a hand-kept copy that src/wifipi/CMakeLists.txt put first. #126's callback pin reached the driver only when #128 patched the copy, and the copy still says ABI version 2: the driver tests Version == ANXD_S2_ABI_VERSION, the library sends 3 (92773983), so every open declined the extension and the single-copy receive never ran. The tree's include/ now comes first, so the driver reads the one header the library is built from. v2 to v3 changed only the ANXD_CMD_RX_BATCH record (Version/Size prefix), which the driver does not implement; the AnxdS2Extension prefix it reads is identical, and check-anxs2ext-abi now pins those offsets (0/2/4/8/12/16/20, size 24) for every version from MIN on. Codegen against the pinned toolchain: packet.c is instruction-identical, device.c differs only in the version constant (2 -> 3). Effect: anxwifipi.device accepts RX_DIRECT, RX_LINK_HDR and RX_VERIFIED from the library and the direct receive path runs for the first time since 2026-09-20, with no hardware run yet. Co-Authored-By: Claude Opus 5.5 --- cmake/check-anxs2ext-abi.cmake | 12 ++++++++++++ src/wifipi/CMakeLists.txt | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/cmake/check-anxs2ext-abi.cmake b/cmake/check-anxs2ext-abi.cmake index 2e9a3d070..0f6286108 100644 --- a/cmake/check-anxs2ext-abi.cmake +++ b/cmake/check-anxs2ext-abi.cmake @@ -9,6 +9,7 @@ # made of its own code bytes, and on a PiStorm32 A1200 neither card came up. # This compiles a call through each typedef with the tree's flags and stops # the configure unless the caller pops what it pushed: 8, 16 and 4 bytes. +# It also pins the record's offsets, which no version from MIN on may move. # # SPDX-License-Identifier: MIT @@ -19,7 +20,18 @@ endif() set(_s2x_dir "${CMAKE_BINARY_DIR}/check-anxs2ext-abi") file(MAKE_DIRECTORY "${_s2x_dir}") file(WRITE "${_s2x_dir}/probe.c" [=[ +#include #include +/* The record's prefix is the same at every version from MIN on: a driver + accepting [MIN, VERSION] reads these offsets whichever one it is handed. */ +_Static_assert(offsetof(AnxdS2Extension, Version) == 0, "Version"); +_Static_assert(offsetof(AnxdS2Extension, Size) == 2, "Size"); +_Static_assert(offsetof(AnxdS2Extension, Request) == 4, "Request"); +_Static_assert(offsetof(AnxdS2Extension, Accepted) == 8, "Accepted"); +_Static_assert(offsetof(AnxdS2Extension, RxDirect) == 12, "RxDirect"); +_Static_assert(offsetof(AnxdS2Extension, RxFilled) == 16, "RxFilled"); +_Static_assert(offsetof(AnxdS2Extension, TxFlags) == 20, "TxFlags"); +_Static_assert(sizeof(AnxdS2Extension) == 24, "size"); UBYTE *probe_rx_direct(AnxdS2RxDirect f, APTR io, ULONG len) { return f(io, len) + 1; diff --git a/src/wifipi/CMakeLists.txt b/src/wifipi/CMakeLists.txt index b1ac64e31..6b4730e5d 100644 --- a/src/wifipi/CMakeLists.txt +++ b/src/wifipi/CMakeLists.txt @@ -75,7 +75,11 @@ if(AMINETXDUO_WIFIPI_LINK AND CMAKE_CROSSCOMPILING) "WIFIPI_VERSION_HEADER=" WIFIPI_VERSION=1 WIFIPI_REVISION=0) + # The tree's include/ FIRST: is the library's own + # header, never a copy in the fork that drifts from it (the callback pin + # of #126 reached the driver only when #128 patched the copy by hand). target_include_directories(anxwifipi_device PRIVATE + "${CMAKE_SOURCE_DIR}/include" "${WIFIPI}/include" "${_dt_inc}" "${CMAKE_BINARY_DIR}/include") From f00b33ab955830c4156dc7d9c4f5514ff903439a Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Thu, 1 Oct 2026 04:32:52 -0700 Subject: [PATCH 3/5] third_party: pin wifipi to the canonical-header fork commit WiFiPi maint/beta4 6594680 deletes its copy of anxs2ext.h, builds against ours, and accepts extension versions 2..3 as netdev_extension.c does, instead of exactly its copy's 2. Co-Authored-By: Claude Opus 5.5 --- third_party/wifipi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/wifipi b/third_party/wifipi index 3c56ee90c..65946807d 160000 --- a/third_party/wifipi +++ b/third_party/wifipi @@ -1 +1 @@ -Subproject commit 3c56ee90c8f593cd07f630193e1a3f73441b56b1 +Subproject commit 65946807da750eb189310b51b88f41a9bc1a56f9 From b6488e2148ee4b1cd046930a6e454adda822d6b8 Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Thu, 1 Oct 2026 04:36:57 -0700 Subject: [PATCH 4/5] InstallSameFile: a rewritten interface file keeps its bits and filenote replace_file() renamed a fresh file into place, so a kept interface file lost its protection bits and filenote. They are copied from the original onto the new file before the rename; a file that cannot be examined is replaced as before. devicehome.h named CheckNetConfig as a user; only InstallSameFile and its host test link it. Co-Authored-By: Claude Opus 5.5 --- src/tools/devicehome.h | 2 +- src/tools/installsamefile.c | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/tools/devicehome.h b/src/tools/devicehome.h index a30e2b06e..0ac04e52c 100644 --- a/src/tools/devicehome.h +++ b/src/tools/devicehome.h @@ -1,5 +1,5 @@ /* Where an interface file's DEVICE= finds one of this stack's drivers. Pure - text, shared by InstallSameFile and CheckNetConfig and tested on the host. */ + text, used by InstallSameFile and tested on the host (test_devicehome). */ #ifndef AMINETXDUO_DEVICEHOME_H #define AMINETXDUO_DEVICEHOME_H diff --git a/src/tools/installsamefile.c b/src/tools/installsamefile.c index 66bfe9064..be9a3ae8b 100644 --- a/src/tools/installsamefile.c +++ b/src/tools/installsamefile.c @@ -89,6 +89,29 @@ static ULONG cat(char *out, ULONG cap, const char *a, const char *b) return (*a == '\0' && *b == '\0') ? n : 0; } +/* The kept file's protection bits and filenote go with it; a file that + cannot be examined is replaced with the defaults, as before. */ +static VOID keep_attributes(const char *from, const char *to) +{ + struct FileInfoBlock *fib; + BPTR lock; + + fib = (struct FileInfoBlock *)AllocDosObject(DOS_FIB, NULL); + if (fib == NULL) + return; + lock = Lock((CONST_STRPTR)from, ACCESS_READ); + if (lock != 0) + { + if (Examine(lock, fib)) + { + (VOID)SetProtection((CONST_STRPTR)to, fib->fib_Protection); + (VOID)SetComment((CONST_STRPTR)to, (CONST_STRPTR)fib->fib_Comment); + } + UnLock(lock); + } + FreeDosObject(DOS_FIB, fib); +} + /* NetPrefs' order: the new bytes are whole on disk before the old name goes, and both spare names end in .info, which the interface scan skips. */ static BOOL replace_file(const char *path, const char *data, LONG length) @@ -113,6 +136,7 @@ static BOOL replace_file(const char *path, const char *data, LONG length) (VOID)DeleteFile((CONST_STRPTR)tmp); return FALSE; } + keep_attributes(path, tmp); (VOID)DeleteFile((CONST_STRPTR)old); if (!Rename((CONST_STRPTR)path, (CONST_STRPTR)old)) From 3ce8a6d41f8cca3f9705f3376991ea1a9c40ec44 Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Thu, 1 Oct 2026 04:52:25 -0700 Subject: [PATCH 5/5] ci.sh: count devicehome_rewrites_only_ours The new host test registers on every host: 494 expected, 466 on darwin-arm64. Co-Authored-By: Claude Opus 5.5 --- tools/ci.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/ci.sh b/tools/ci.sh index de46d070e..7b8db07a3 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -401,8 +401,9 @@ host_test_targets() { # builddir # the caller's header instead of sending it as payload (F-061), x86_64 # only # 490 with netstatus_selector, 491 with traceroute_wait (all hosts); -# 492 with deferred_release, 493 with netstatus_hostsource (x86_64 only). -HOST_TESTS_EXPECTED=493 +# 492 with deferred_release, 493 with netstatus_hostsource (x86_64 only); +# 494 with devicehome_rewrites_only_ours (all hosts). +HOST_TESTS_EXPECTED=494 case "$(uname -m)" in x86_64|amd64) ;; # test_inet, test_route, test_expunge, test_expunge_cork, test_select, @@ -419,7 +420,7 @@ case "$(uname -m)" in # tests/bsdsocket/CMakeLists.txt: # elsewhere the host's LONG is eight bytes and no structure in them has # the target's shape. - # darwin-arm64 registers 465 of 493 on the current tree. + # darwin-arm64 registers 466 of 494 on the current tree. *) HOST_TESTS_EXPECTED=$((HOST_TESTS_EXPECTED - 28)) ;; esac