Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion CHANGELOG.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,29 @@ Chinese is the primary changelog and this file is its maintained English mirror.
explicitly unapproved for distribution without changing the XCFramework
Release's RootFS exclusion policy.

## v0.4.0-abi.6 (planned Stage1 maintenance prerelease)
## v0.4.0-abi.7 (planned Stage1 maintenance prerelease)

This is a compatibility maintenance release after `v0.4.0-abi.6`. It remains a
prerelease and is **not stable v0.4.0**.

- The pinned iSH fork adds `RENAME_NOREPLACE` syscall semantics, mapping them to
`renameatx_np(RENAME_EXCL)` in the Darwin fakefs backend and `renameat2` on a
Linux host. An existing destination is never replaced, without a racy
check-then-move sequence.
- The public C API adds `ish_embed_rename_noreplace`. It executes the atomic
rename through the content-addressed guest supervisor selected at boot and
returns guest errno in a bounded decimal record; malformed protocol output or
helper failure fails closed.
- The Swift API adds `IshInstance.renameNoReplace(from:to:timeout:)` and
`IshFilesystemError`. An existing destination maps to `.destinationExists`.
While source temporarily links `v0.4.0-abi.6`, a weak fallback reports
unsupported instead of producing a missing symbol.
- The public C ABI version remains 1 and wire protocol remains v4; the new
function symbol is backward-compatible and additive. RootFS remains outside
the Release. This version does not implement a native Agent Loop or install
Codex CLI.

## v0.4.0-abi.6 (published Stage1 maintenance prerelease)

This is a compatibility maintenance release after `v0.4.0-abi.5`. It remains a
prerelease and is **not stable v0.4.0**.
Expand Down
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,24 @@
`fs.tar.gz`,环境差异仍保留在外部证据中。CI 不上传制品,候选仍明确标记为未获分发
批准,不改变 XCFramework Release 的 RootFS 排除策略。

## v0.4.0-abi.6(计划中的 Stage1 维护预发布)
## v0.4.0-abi.7(计划中的 Stage1 维护预发布)

这是 `v0.4.0-abi.6` 之后的兼容性维护版本,仍是 prerelease,**不是稳定
v0.4.0**。

- 固定的 iSH fork 新增 `RENAME_NOREPLACE` syscall 语义,并在 Darwin fakefs 后端使用
`renameatx_np(RENAME_EXCL)`、Linux host 后端使用 `renameat2`,从底层保证目标存在时
不覆盖,不采用易竞争的“先检查、再移动”。
- 公开 C API 新增 `ish_embed_rename_noreplace`。它通过启动时选定的内容寻址 guest
supervisor 执行原子重命名,以有界十进制记录返回 guest errno;协议损坏或 helper
异常会 fail closed。
- Swift API 新增 `IshInstance.renameNoReplace(from:to:timeout:)` 与
`IshFilesystemError`。目标已存在映射为 `.destinationExists`;源码临时链接
`v0.4.0-abi.6` 时通过 weak fallback 明确报告 unsupported,不会产生缺失符号。
- 公开 C ABI 版本仍为 1,wire protocol 仍为 v4;新增函数符号是向后兼容扩展。
RootFS 不进入 Release。本版本不实现原生 Agent Loop,也不会安装 Codex CLI。

## v0.4.0-abi.6(已发布的 Stage1 维护预发布)

这是 `v0.4.0-abi.5` 之后的兼容性维护版本,仍是 prerelease,**不是稳定
v0.4.0**。
Expand Down
32 changes: 21 additions & 11 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ and simulator slices are arm64.

## Current phase: native ABI transition

The default branch has published `v0.4.0-abi.5` and is preparing the compatible
maintenance prerelease `v0.4.0-abi.6`. Both belong to the Stage1 **native ABI
The default branch has published `v0.4.0-abi.6` and is preparing the compatible
maintenance prerelease `v0.4.0-abi.7`. Both belong to the Stage1 **native ABI
transition**. Neither is stable `v0.4.0` or the complete v0.4 Swift API. Keep
these four version surfaces distinct:

| Surface | Current `v0.4.0-abi.5` | Planned `v0.4.0-abi.6` |
| Surface | Current `v0.4.0-abi.6` | Planned `v0.4.0-abi.7` |
| --- | --- | --- |
| Public C ABI | `ISH_EMBED_ABI_VERSION == 1`; compatible symbols are published | Still ABI 1, with no new public symbols |
| Public C ABI | `ISH_EMBED_ABI_VERSION == 1`; compatible symbols are published | Still ABI 1, with one backward-compatible atomic-rename function symbol |
| Internal wire protocol | exact-match v4 between host and embedded supervisor | still v4; this is not the public C ABI version |
| `Package.swift` | pins the public `v0.4.0-abi.5` URL/checksum | the release transaction creates a manifest-only release commit pinned to the maintenance binary |
| Swift source | remains v0.3.3-ABI compatible and does not call retain/release | keeps the same Swift API and old-ABI usage |
| `Package.swift` | pins the public `v0.4.0-abi.6` URL/checksum | the release transaction creates a manifest-only release commit pinned to the maintenance binary |
| Swift source | remains v0.3.3-ABI compatible and does not call retain/release | adds a typed rename API with a weak fallback for old binaries |

Stage1 native code adds session retain/release, a joinable kernel thread,
soft-halt, exact wire v4, and complete session close. The existing Swift wrapper
Expand Down Expand Up @@ -88,15 +88,15 @@ gives those narrow differences independent PRs, CI, and an exact gitlink, making
PocketRoot builds and releases reproducible. We do not directly rewrite somebody
else's local upstream repository; generally useful fixes can still be contributed
to [iSH upstream](https://github.com/ish-app/ish), while the fork carries project
gates until upstream accepts and releases them. The current `v0.4.0-abi.6`
gates until upstream accepts and releases them. The current `v0.4.0-abi.7`
source change includes neither RootFS content nor any prebuilt XCFramework/guest
binary; binaries may be produced and published only by a later release
transaction after its gates pass.

## Installation status

`v0.4.0-abi.5` is public and [`Package.swift`](Package.swift) currently pins it.
Until `v0.4.0-abi.6` is published, the manifest keeps pointing at that verified
`v0.4.0-abi.6` is public and [`Package.swift`](Package.swift) currently pins it.
Until `v0.4.0-abi.7` is published, the manifest keeps pointing at that verified
asset instead of advertising a future 404 URL. Use Xcode's
**File → Add Package Dependencies…** with:

Expand All @@ -108,8 +108,9 @@ Select a version whose tag, `libIshKernel.xcframework.zip`, Corresponding Source
and manifest URL/checksum all match. Consumer projects do not need Meson, Zig,
or LLVM.

`v0.4.0-abi.6` completes Swift-marshalling and stdin-close reuse of the original
SPAWN deadline. It does not implement a native Agent Loop or
`v0.4.0-abi.7` adds guest-atomic rename without a shell or check-then-rename
race and maps an existing destination to a typed Swift error. It does not
implement a native Agent Loop or
install Codex CLI in the app.
Node.js/npm remain optional choices of the RootFS/guest package-management flow,
not runtime requirements.
Expand All @@ -131,6 +132,15 @@ let result = try instance.runOneshot(
.init(argv: ["/bin/echo", "hello"], timeout: 10)
)
print(String(decoding: result.stdoutData, as: UTF8.self))

do {
try instance.renameNoReplace(
from: "/workspace/draft.txt",
to: "/workspace/final.txt"
)
} catch IshFilesystemError.destinationExists {
// The destination remains unchanged; ask the user for another name.
}
```

`IshSpawnOptions.timeout` applies to both `runOneshot` and streaming `spawn`.
Expand Down
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ RootFS 安装、产品级命令策略、Swift Concurrency 隔离和界面。项

## 当前阶段:Native ABI 过渡

当前默认分支已发布 `v0.4.0-abi.5`,正在准备兼容性维护预发布
`v0.4.0-abi.6`。它们都属于 **Stage1 native ABI 过渡**,不是稳定 `v0.4.0`,
当前默认分支已发布 `v0.4.0-abi.6`,正在准备兼容性维护预发布
`v0.4.0-abi.7`。它们都属于 **Stage1 native ABI 过渡**,不是稳定 `v0.4.0`,
也不是完整 v0.4 Swift API。请同时区分下面四个版本面:

| 版本面 | 当前 `v0.4.0-abi.5` | 计划中的 `v0.4.0-abi.6` |
| 版本面 | 当前 `v0.4.0-abi.6` | 计划中的 `v0.4.0-abi.7` |
| --- | --- | --- |
| 公开 C ABI | `ISH_EMBED_ABI_VERSION == 1`;兼容性符号已发布 | 仍为 ABI 1,不新增公开符号 |
| 公开 C ABI | `ISH_EMBED_ABI_VERSION == 1`;兼容性符号已发布 | 仍为 ABI 1,新增向后兼容的原子 rename 函数符号 |
| 内部 wire protocol | host 与内嵌 supervisor 精确匹配 v4 | 仍为 v4;它不是公开 C ABI 版本 |
| `Package.swift` | 固定已公开的 `v0.4.0-abi.5` URL/checksum | 发布事务生成只改 manifest 的 release commit,固定到维护二进制 |
| Swift 源 | 保持 v0.3.3 ABI 兼容,不调用 retain/release | 保持相同 Swift API 与旧 ABI 用法 |
| `Package.swift` | 固定已公开的 `v0.4.0-abi.6` URL/checksum | 发布事务生成只改 manifest 的 release commit,固定到维护二进制 |
| Swift 源 | 保持 v0.3.3 ABI 兼容,不调用 retain/release | 新增类型化 rename API;通过 weak fallback 兼容旧 binary |

Stage1 的 native runtime 已加入 session retain/release、可等待 kernel 线程、soft-halt、
严格 v4 协议和完整 session close 等底层能力。现有 Swift wrapper 刻意不调用新增
Expand Down Expand Up @@ -68,13 +68,13 @@ JIT 脏页一致性必须修改模拟器核心,无法只在 outer package 或
窄差异拥有独立 PR、CI 和精确 gitlink,PocketRoot 的构建与发布也因此可复现。我们不会在
本地直接改写别人维护的上游仓库;适合通用化的修复仍可回馈
[iSH upstream](https://github.com/ish-app/ish),但在上游接受并发布前由 fork 承担项目门禁。
当前 `v0.4.0-abi.6` 源码变更不纳入 RootFS,也不提交任何预构建
当前 `v0.4.0-abi.7` 源码变更不纳入 RootFS,也不提交任何预构建
XCFramework/guest binary;二进制只能在后续发布事务通过后生成和发布。

## 安装状态

`v0.4.0-abi.5` 已公开且当前 [`Package.swift`](Package.swift) 固定到它。
`v0.4.0-abi.6` 发布前,manifest 继续指向这个已验证的资产,不会提前引用 404 URL。
`v0.4.0-abi.6` 已公开且当前 [`Package.swift`](Package.swift) 固定到它。
`v0.4.0-abi.7` 发布前,manifest 继续指向这个已验证的资产,不会提前引用 404 URL。
在 Xcode 的 **File → Add Package Dependencies…** 中使用:

```text
Expand All @@ -84,8 +84,8 @@ https://github.com/jacklv-coder/ish-arm64-pkg
请选择明确包含 `libIshKernel.xcframework.zip`、对应源码归档,并且 manifest URL/checksum
与同一标签匹配的版本。业务工程不需要安装 Meson、Zig 或 LLVM。

`v0.4.0-abi.6` 补全 Swift 参数封送和 stdin close 对原始 SPAWN deadline 的复用;
它不实现原生 Agent Loop,也不会
`v0.4.0-abi.7` 新增无 shell、无 check-then-rename 竞争窗口的 guest 原子重命名,
并把目标已存在映射为 Swift 类型化错误;它不实现原生 Agent Loop,也不会
在 App 内安装 Codex CLI。Node.js/npm 如有需要仍由
RootFS/guest 包管理流程选择,不属于 runtime 的强制依赖。

Expand All @@ -106,6 +106,15 @@ let result = try instance.runOneshot(
.init(argv: ["/bin/echo", "hello"], timeout: 10)
)
print(String(decoding: result.stdoutData, as: UTF8.self))

do {
try instance.renameNoReplace(
from: "/workspace/draft.txt",
to: "/workspace/final.txt"
)
} catch IshFilesystemError.destinationExists {
// 目标保持原样;调用方可以提示用户换一个名称。
}
```

`IshSpawnOptions.timeout` 同时适用于 `runOneshot` 与 streaming `spawn`。有限超时从
Expand Down
40 changes: 36 additions & 4 deletions Sources/CIshEmbed/CIshEmbed.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,36 @@
/* Empty translation unit so the SwiftPM target produces a real
* object file. The C ABI itself is implemented inside the prebuilt
* libIshKernel.xcframework; this target only re-exports its header
* to Swift via the modulemap in include/. */
/* Source compatibility bridge for the short interval between merging a new
* Swift API and publishing its matching XCFramework. The weak fallback keeps
* Package.swift resolvable against the previous release; the bridge reports
* unsupported until the new binary is linked. */

#include "ishembed.h"
#include "IshEmbedSwiftShim.h"

enum { ISH_SWIFT_ERR_UNSUPPORTED = -22 };

/* A weak definition keeps source builds linkable while Package.swift still
* points at the previous XCFramework. The new archive contains the strong
* implementation in the same member as the established runtime API, so it is
* loaded normally and overrides this fallback after publication. */
__attribute__((weak))
int ish_embed_rename_noreplace(ish_embed_instance_t *inst,
const char *source,
const char *destination,
uint32_t timeout_ms,
int32_t *out_guest_errno) {
(void)inst;
(void)source;
(void)destination;
(void)timeout_ms;
if (out_guest_errno) *out_guest_errno = 0;
return ISH_SWIFT_ERR_UNSUPPORTED;
}

int ish_embed_swift_rename_noreplace(ish_embed_instance_t *inst,
const char *source,
const char *destination,
uint32_t timeout_ms,
int32_t *out_guest_errno) {
return ish_embed_rename_noreplace(
inst, source, destination, timeout_ms, out_guest_errno);
}
22 changes: 22 additions & 0 deletions Sources/CIshEmbed/include/IshEmbedSwiftShim.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef ISH_EMBED_SWIFT_SHIM_H
#define ISH_EMBED_SWIFT_SHIM_H

#include "ishembed.h"

#ifdef __cplusplus
extern "C" {
#endif

/* SwiftPM source-target bridge. C embedders should call the public
* ish_embed_rename_noreplace symbol directly from abi.7 or newer. */
int ish_embed_swift_rename_noreplace(ish_embed_instance_t *inst,
const char *source,
const char *destination,
uint32_t timeout_ms,
int32_t *out_guest_errno);

#ifdef __cplusplus
}
#endif

#endif
1 change: 1 addition & 0 deletions Sources/CIshEmbed/include/module.modulemap
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module CIshEmbed {
header "ishembed.h"
header "IshEmbedSwiftShim.h"
export *
}
73 changes: 73 additions & 0 deletions Sources/IshEmbed/IshEmbed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,23 @@ public enum IshError: Error, CustomStringConvertible {
}
}

public enum IshFilesystemError: Error, Equatable, CustomStringConvertible {
/// The destination existed at the atomic rename point. Neither path was
/// replaced, and the source remains unchanged.
case destinationExists
/// A positive Linux guest errno returned by the filesystem operation.
case guestErrno(Int32)

public var description: String {
switch self {
case .destinationExists:
return "The destination already exists."
case .guestErrno(let value):
return "Guest filesystem operation failed with Linux errno \(value)."
}
}
}

public struct IshSpawnOptions {
public var argv: [String]
public var cwd: String?
Expand Down Expand Up @@ -106,6 +123,7 @@ public enum IshSessionEvent {
// The C header exposes ISH_OK / ISH_ERR_* as an unnamed enum and
// ISH_STREAM_* as #define ints; both arrive as Int32 in Swift.
private let ishOK: Int32 = ISH_OK.rawValue
private let ishUnsupported: Int32 = -22
private let ishErrNotRunning: Int32 = ISH_ERR_NOT_RUNNING.rawValue
private let ishErrAlreadyBooted: Int32 = ISH_ERR_ALREADY_BOOTED.rawValue
private let ishErrNoSession: Int32 = ISH_ERR_NO_SESSION.rawValue
Expand Down Expand Up @@ -188,6 +206,9 @@ struct IshLifecycleNativeCalls: @unchecked Sendable {
UnsafeMutablePointer<ish_embed_spawn_opts_t>,
IshSpawnTimeoutBudget) throws ->
(result: Int32, session: OpaquePointer?)
let renameNoReplace: (OpaquePointer, UnsafePointer<CChar>,
UnsafePointer<CChar>, UInt32,
UnsafeMutablePointer<Int32>) -> Int32
let sessionClose: (OpaquePointer) -> Void
let freeBuffer: (UnsafeMutablePointer<UInt8>) -> Void

Expand Down Expand Up @@ -216,6 +237,12 @@ struct IshLifecycleNativeCalls: @unchecked Sendable {
let result = ish_embed_spawn(instance, opts, &session)
return (result, session)
},
renameNoReplace: { instance, source, destination, timeoutMs,
guestErrno in
ish_embed_swift_rename_noreplace(
instance, source, destination, timeoutMs, guestErrno
)
},
sessionClose: { session in
ish_embed_session_close(session)
},
Expand Down Expand Up @@ -336,6 +363,52 @@ public final class IshInstance: @unchecked Sendable {
}
}

/// Atomically renames a guest file or directory without replacing an
/// existing destination. Both paths are absolute inside the Linux guest.
/// This operation does not invoke a shell and does not perform a racy
/// existence check before rename.
public func renameNoReplace(
from source: String,
to destination: String,
timeout: TimeInterval? = 5
) throws {
guard source.first == "/", source.count > 1,
destination.first == "/", destination.count > 1,
!source.utf8.contains(0), !destination.utf8.contains(0) else {
throw IshError.from(ishErrInvalidArg)
}
let timeoutBudget = try IshSpawnTimeoutBudget(
timeout: timeout,
startedAt: ProcessInfo.processInfo.systemUptime
)
let lease = try callGate.acquireCall()
defer { lease.release() }
let timeoutMs = try timeoutBudget.remainingMilliseconds(
at: ProcessInfo.processInfo.systemUptime
)
var guestErrno: Int32 = 0
let rc = source.withCString { sourceC in
destination.withCString { destinationC in
nativeCalls.renameNoReplace(
lease.raw, sourceC, destinationC, timeoutMs, &guestErrno
)
}
}
if rc == ishUnsupported {
throw IshError.raw(
rc,
"Atomic no-replace rename requires IshEmbed v0.4.0-abi.7 or newer."
)
}
if rc != ishOK { throw IshError.from(rc) }
if guestErrno == 17 {
throw IshFilesystemError.destinationExists
}
if guestErrno != 0 {
throw IshFilesystemError.guestErrno(guestErrno)
}
}

/// Spawn a streaming session.
public func spawn(_ opts: IshSpawnOptions) throws -> IshSession {
let timeoutBudget = try IshSpawnTimeoutBudget(
Expand Down
Loading
Loading