Files
ShadowsocksX-NG/ShadowsocksX-NG2/Domain/AtomicFileWriter.swift
T
Qiu Yuzhou 9439e597e2 feat(ng2): activation semantics and runtime config derivation (#26)
激活状态机与派生层(spec #21 D3 激活族 + D5 派生):

- ActivationStateMachine:持久化活动目标(服务器或分组);激活原子成功
  或点名失败(保留原目标与运行状态);目录提交后立即重展开,失效即清除
  并发出停止意图(删除/禁用/变空/无效叶子各路径,无静默回退)
- 组激活按显式子序递归展开有效启用叶子;组目标保持组 UUID
- SslocalRuntimeDocument:完整 sslocal JSON 契约文档,凭据派生时解析,
  插件字段无插件时整体省略;ManagedPluginProviding 缝按「未提供」参与
  无效判定(真实受管集 #38 接通)
- ActivationStateFileStore:activation.json 持久化(0700/0600、原子替换,
  损坏按安全侧恢复为无活动目标);与 CatalogFileStore 共享 AtomicFileWriter
2026-09-20 18:50:58 +08:00

44 lines
1.7 KiB
Swift

import Foundation
/// 敏感配置文件的原子写盘基线(spec #21 D5):目录 0700(含自愈)、临时文件
/// 创建即 0600、写全校验后原子替换;任一步失败保留原文件。catalog.json、
/// activation.json 与 #27 的运行时文件共用同一物理安全基线。
enum AtomicFileWriter {
enum WriteError: Error, Equatable {
case failure(detail: String)
}
/// 把 `data` 原子写入 `fileURL`(含父目录创建与 0700 基线自愈)。
static func write(_ data: Data, to fileURL: URL) throws {
let directory = fileURL.deletingLastPathComponent()
do {
if !FileManager.default.fileExists(atPath: directory.path) {
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
}
try FileManager.default.setAttributes(
[.posixPermissions: 0o700], ofItemAtPath: directory.path)
} catch {
throw WriteError.failure(detail: String(describing: error))
}
let temporaryURL = directory.appendingPathComponent(
".\(fileURL.lastPathComponent).tmp-\(UUID().uuidString)")
if !FileManager.default.createFile(
atPath: temporaryURL.path,
contents: data,
attributes: [.posixPermissions: 0o600]
) {
throw WriteError.failure(detail: "无法创建临时文件 \(temporaryURL.path)")
}
do {
if FileManager.default.fileExists(atPath: fileURL.path) {
_ = try FileManager.default.replaceItemAt(fileURL, withItemAt: temporaryURL)
} else {
try FileManager.default.moveItem(at: temporaryURL, to: fileURL)
}
} catch {
try? FileManager.default.removeItem(at: temporaryURL)
throw WriteError.failure(detail: String(describing: error))
}
}
}