9d8e70b1
fix: the upgrade preflight reads Darwin's arm64 as aarch64
a73x 2026-09-04 10:16
Commit message
src/cli/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -929,11 +929,30 @@ fn remoteUpgradeCmd(alloc: std.mem.Allocator, host: []const u8, allow_same: bool | |||
| 929 | } | 929 | } |
| 930 | 930 | ||
| 931 | /// Slices into `out` — the caller keeps the buffer alive as long as the result. | 931 | /// Slices into `out` — the caller keeps the buffer alive as long as the result. |
| 932 | /// Whether the box's `uname -m` word names the machine `want_arch` does. | ||
| 933 | /// | ||
| 934 | /// The two sides spell one machine differently and neither is wrong: `uname -m` | ||
| 935 | /// on macOS prints `arm64`, and `want_arch` is `@tagName(builtin.cpu.arch)`, | ||
| 936 | /// which zig spells `aarch64` and has no `arm64` tag at all. Compared as bytes, | ||
| 937 | /// an Apple-silicon image refuses to push to an Apple-silicon box — the one | ||
| 938 | /// pairing where the check is certainly WRONG, since it is the same hardware on | ||
| 939 | /// both ends. `x86_64` is already the same word on every OS this runs on. | ||
| 940 | /// | ||
| 941 | /// One direction only, and one pair only: `aarch64` is never what a kernel | ||
| 942 | /// prints here, so accepting it in the other direction would widen the map for | ||
| 943 | /// nothing, and every other mismatch is a real refusal. | ||
| 944 | fn archMatches(reported: []const u8, want_arch: []const u8) bool { | ||
| 945 | if (std.mem.eql(u8, reported, want_arch)) return true; | ||
| 946 | return std.mem.eql(u8, reported, "arm64") and std.mem.eql(u8, want_arch, "aarch64"); | ||
| 947 | } | ||
| 948 | |||
| 932 | fn parsePreflight(out: []const u8, want_arch: []const u8) Preflight { | 949 | fn parsePreflight(out: []const u8, want_arch: []const u8) Preflight { |
| 933 | var lines = std.mem.splitScalar(u8, out, '\n'); | 950 | var lines = std.mem.splitScalar(u8, out, '\n'); |
| 934 | const arch = lines.next() orelse ""; | 951 | const arch = lines.next() orelse ""; |
| 935 | if (arch.len == 0) return .no_answer; | 952 | if (arch.len == 0) return .no_answer; |
| 936 | if (!std.mem.eql(u8, arch, want_arch)) return .{ .bad_arch = arch }; | 953 | // The REPORTED word is what comes back on a refusal, so the message names |
| 954 | // the box's own spelling and the image's own spelling side by side. | ||
| 955 | if (!archMatches(arch, want_arch)) return .{ .bad_arch = arch }; | ||
| 937 | const path = lines.next() orelse ""; | 956 | const path = lines.next() orelse ""; |
| 938 | if (path.len == 0) return .no_mux; | 957 | if (path.len == 0) return .no_mux; |
| 939 | const announce = lines.next() orelse ""; | 958 | const announce = lines.next() orelse ""; |
| @@ -1515,6 +1534,30 @@ test "parsePreflight: the line count is the diagnosis" { | |||
| 1515 | try std.testing.expect(parsePreflight("\n", "x86_64") == .no_answer); | 1534 | try std.testing.expect(parsePreflight("\n", "x86_64") == .no_answer); |
| 1516 | } | 1535 | } |
| 1517 | 1536 | ||
| 1537 | test "parsePreflight: a Mac's arm64 is the aarch64 this image is built for" { | ||
| 1538 | // The pairing this exists for: an Apple-silicon image pushing to an | ||
| 1539 | // Apple-silicon box. `uname -m` says `arm64`, `@tagName(builtin.cpu.arch)` | ||
| 1540 | // says `aarch64`, and a byte compare refused the one push that is | ||
| 1541 | // certainly safe. There is one Mac in the fixture and `mux d upgrade` | ||
| 1542 | // needs two, so `make xos` cannot reach this and the unit test is the pin. | ||
| 1543 | const mac = parsePreflight("arm64\n/Users/u/.local/bin/mux\nquic 1.2.3.4:1 k\n", "aarch64"); | ||
| 1544 | try std.testing.expectEqualStrings("/Users/u/.local/bin/mux", mac.ready.path); | ||
| 1545 | try std.testing.expect(mac.ready.daemon_up); | ||
| 1546 | |||
| 1547 | // Neither half of the map loosens a real mismatch, and both refusals still | ||
| 1548 | // carry the BOX's spelling — test/xos.sh's upgrade-refused leg reads both | ||
| 1549 | // of these sentences off the wire in the two directions it pushes. | ||
| 1550 | const mac_from_intel = parsePreflight("arm64\n/Users/u/.local/bin/mux\n", "x86_64"); | ||
| 1551 | try std.testing.expectEqualStrings("arm64", mac_from_intel.bad_arch); | ||
| 1552 | const intel_from_mac = parsePreflight("x86_64\n/home/u/.local/bin/mux\n", "aarch64"); | ||
| 1553 | try std.testing.expectEqualStrings("x86_64", intel_from_mac.bad_arch); | ||
| 1554 | |||
| 1555 | // The map is one direction: a box that reports the zig tag is not a box | ||
| 1556 | // this has ever met, and it is not what makes an x86_64 image acceptable. | ||
| 1557 | try std.testing.expect(!archMatches("aarch64", "x86_64")); | ||
| 1558 | try std.testing.expect(archMatches("aarch64", "aarch64")); | ||
| 1559 | } | ||
| 1560 | |||
| 1518 | test "parseArgs: upgrade takes one HOST word, and only upgrade does" { | 1561 | test "parseArgs: upgrade takes one HOST word, and only upgrade does" { |
| 1519 | const r = parse(&.{ "d", "upgrade", "box" }); | 1562 | const r = parse(&.{ "d", "upgrade", "box" }); |
| 1520 | try std.testing.expect(r == .command); | 1563 | try std.testing.expect(r == .command); |