pi_async_fs 0.1.2

Runtime-agnostic asynchronous filesystem contracts for local and remote storage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
// 本地文件跨进程协调的全局放置策略与授权令牌。
//
// 本模块定义跨进程协调的公共配置与不透明授权载体;标准本地适配器的协议、
// 文件身份核验和资源准入实现位于私有本地模块。远端适配器不会自动继承
// 本地文件系统的协调能力。
//
// 跨进程协调使用目标文件以外的专属协调目录保存锁文件、协议版本和范围
// 租约记录。默认策略把协调目录放在目标文件的父目录;应用也可以在首次
// 使用前安装一个进程级自定义根目录。策略一旦由显式设置或首次协调操作
// 冻结,就不能在进程存续期间切换,否则旧资源和新资源可能进入彼此不可见
// 的两个锁域。
//
// [`CrossProcessFileAuthority`] 是绑定到单个稳定文件身份及其目标实例代次
// (target incarnation)的不透明授权令牌。目标实例代次区分同一定位符上
// 先后存在的不同文件生命周期,避免旧授权在删除、重建后静默转移到新文件。
// 它不是操作系统安全权限,也不是可以跨进程序列化的锁句柄;它只证明
// adapter 已经按照冻结的全局策略建立了本库的合作式协调环境。绕过本库
// 的进程、文件句柄或映射仍不受该令牌强制控制。

use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use std::path::PathBuf;
use std::sync::{Arc, OnceLock};

// 当前进程中所有本地跨进程协调目录的根位置策略。
//
// # 作用与使用位置
//
// 本类型只作为 [`set_cross_process_coordination_root`] 的全局配置值,以及
// 内部协调模块冻结后的只读策略。它不会出现在文件创建、打开、读取、追加、
// 刷新或映射方法的参数中。这样可以保证同一进程中的不同 namespace、文件
// 资源和映射不会各自选择互不相干的锁域。
//
// [`Self::FileParent`] 是默认策略:每个目标文件在自己的父目录下拥有专属
// 协调目录。[`Self::Custom`] 则让所有需要标准本地跨进程协调的目标文件都
// 在同一个物理根目录下派生各自的协调子目录。调用方提供的路径只是根目录,
// 不是最终锁文件、租约文件或目标文件专属子目录的完整路径。
//
// # 全局冻结与跨进程一致性
//
// 当前进程中的策略由显式设置或首次需要协调文件锁的操作原子地冻结。
// 冻结后不能从 `FileParent` 切换到 `Custom`,也不能更换自定义根目录;相同
// 表示的重复设置可以成功。此处的“全局”只覆盖当前进程加载的这一份库状态,
// 不能自动传播给其它进程或同一进程中的另一份库副本。
//
// 所有合作进程必须分别选择能够解析到相同物理锁域的策略。不同进程选择
// 不同自定义根目录不会被进程内静态状态自动发现,并会破坏
// [`CrossProcessFileAuthority`] 所依赖的外部安全承诺。
//
// # 自定义根目录
//
// `Custom` 中的路径必须拥有、非空并采用当前平台的绝对路径表示。公开枚举
// 允许先承载尚未验证的 `PathBuf`;全局设置函数只进行无需 I/O 的结构检查,
// 首次建立协调授权时才打开目录并核验存在性、目录类型、权限、文件系统
// 能力、稳定目录身份以及符号链接或目录联接点限制。
//
// 调用方负责预先创建自定义根目录。本库可以在其中创建目标文件专属协调
// 子目录和协议文件,但不会递归创建调用方给出的根目录。根目录失效时不能
// 悄悄退回 `FileParent`,也不能在活动资源存续期间自动重新绑定到同名的新
// 目录。
//
// # 所有权与基本能力
//
// 本类型刻意不实现 `Clone` 或 `Copy`;设置操作按值取得并长期拥有唯一配置
// 值。它实现 `Default`,因为本项目已经明确冻结 `FileParent` 为唯一默认
// 策略。判等、全序和哈希只比较配置表示,不证明两个不同路径指向相同物理
// 目录;哈希值也不是稳定存储编码或跨进程协议标识。
//
// `Send` 与 `Sync` 由字段自动获得,不使用手写 `unsafe impl`。本类型不声明
// 稳定 ABI、序列化格式、`AsRef<Path>` 或无条件 `From<PathBuf>` 转换;前者
// 无法合理表达无路径的 `FileParent`,后者会让无效相对路径看似已通过验证。
/// 当前进程中本地跨进程协调数据的全局放置策略。
///
/// 策略必须在第一次跨进程协调操作之前设置;首次使用后即冻结,不能在进程
/// 存续期间切换。所有合作进程必须选择能够解析到同一物理协调位置的策略。
/// 本类型只描述根位置,不是某个文件的协调数据完整路径。
pub enum CrossProcessCoordinationRoot {
    // 在每个目标文件自己的父目录中保存其专属协调目录。
    //
    // 这是进程尚未显式设置策略时,首次协调操作自动冻结的默认值。不同父
    // 目录中的文件使用不同物理根目录,但都遵守同一个全局放置规则。
    /// 在每个目标文件的父目录下放置该目标的协调数据。
    ///
    /// 这是未显式设置全局策略时使用的默认值。
    FileParent,

    // 在指定全局根目录下保存所有目标文件各自的协调子目录。
    //
    // 路径必须是非空绝对路径,并由调用方在首次协调操作前创建。所有合作
    // 进程必须解析到同一个物理目录;仅仅具有相同字符串表示并不足以证明
    // 它们共享锁域。
    /// 在指定的全局根目录下放置所有目标的协调数据。
    ///
    /// 路径必须是非空绝对路径,并应在首次协调操作前由调用方创建。所有合作
    /// 进程必须解析到同一物理目录;相同文本不单独证明这一点。
    Custom(PathBuf),
}

// 默认策略是冻结的公开语义;保留显式实现和就地说明,不让派生写法隐藏它。
#[allow(clippy::derivable_impls)]
impl Default for CrossProcessCoordinationRoot {
    // 返回 [`CrossProcessCoordinationRoot::FileParent`] 默认策略。
    fn default() -> Self {
        Self::FileParent
    }
}

impl fmt::Debug for CrossProcessCoordinationRoot {
    // 显示策略变体以及自定义根目录的调试表示,不访问文件系统。
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::FileParent => formatter.write_str("FileParent"),
            Self::Custom(path) => formatter
                .debug_tuple("Custom")
                .field(path)
                .finish(),
        }
    }
}

impl fmt::Display for CrossProcessCoordinationRoot {
    // 显示供人阅读的放置策略,不把输出承诺为可反向解析的配置格式。
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::FileParent => formatter.write_str("target file parent"),
            Self::Custom(path) => {
                write!(formatter, "custom root: {}", path.display())
            }
        }
    }
}

impl PartialEq for CrossProcessCoordinationRoot {
    // 按策略变体和 `PathBuf` 的原始平台表示判等,不解析物理目录身份。
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::FileParent, Self::FileParent) => true,
            (Self::Custom(left), Self::Custom(right)) => left == right,
            _ => false,
        }
    }
}

impl Eq for CrossProcessCoordinationRoot {}

impl PartialOrd for CrossProcessCoordinationRoot {
    // 返回与 [`Ord`] 一致的配置表示全序。
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for CrossProcessCoordinationRoot {
    // 仅为确定性诊断和容器索引比较配置表示,不表达策略优先级。
    fn cmp(&self, other: &Self) -> Ordering {
        match (self, other) {
            (Self::FileParent, Self::FileParent) => Ordering::Equal,
            (Self::FileParent, Self::Custom(_)) => Ordering::Less,
            (Self::Custom(_), Self::FileParent) => Ordering::Greater,
            (Self::Custom(left), Self::Custom(right)) => left.cmp(right),
        }
    }
}

impl Hash for CrossProcessCoordinationRoot {
    // 按与判等一致的配置表示向调用方哈希器写入状态。
    fn hash<H>(&self, state: &mut H)
    where
        H: Hasher,
    {
        match self {
            Self::FileParent => 0_u8.hash(state),
            Self::Custom(path) => {
                1_u8.hash(state);
                path.hash(state);
            }
        }
    }
}

// 为当前进程安装本地跨进程协调目录的全局根位置策略。
//
// # 调用目的与作用域
//
// 应用使用本函数在任何需要协调文件锁的操作开始前,统一决定全部标准本地
// adapter 的协调目录放置方式。配置覆盖当前进程中共享这份库全局状态的
// namespace 和文件资源,不会传播到其它进程,也不影响采用独立协议的远端
// 后端。
//
// 不调用本函数时,首次需要协调文件锁的操作会原子地冻结
// [`CrossProcessCoordinationRoot::FileParent`]。文件创建、打开和映射接口不会
// 再提供逐文件覆盖参数。
//
// # 参数、所有权与结构验证
//
// `root` 按值进入全局状态,不借用调用方路径,也不要求 `Clone`。本函数对
// `Custom` 只执行无需文件系统 I/O 的结构验证,例如拒绝空路径和相对路径。
// 无效输入返回粗粒度 [`pi_result::ErrorKind::InvalidInput`],且不会冻结全局
// 状态;调用方可以修正后重新设置。
//
// 本函数不检查目录是否存在,不创建目录,不查询文件系统类型,不解析符号
// 链接或目录联接点,也不取得文件锁。上述可能阻塞且会随时间失效的事实必须
// 在首次实际建立协调授权时重新核验。因此本函数保持同步,并且可以在启动
// 异步运行时之前调用。
//
// # 并发、重复调用与线性化
//
// 多个线程可以并发调用本函数,也可以与首次协调操作竞争。实现必须产生
// 唯一线性化结果:只有一个策略可以成为全局值,任何目标文件都不能观察到
// 中途切换。
//
// 与已冻结策略具有完全相同配置表示的重复调用返回 `Ok(())`,因此该情况
// 幂等。不同变体、不同 `PathBuf` 表示或首次操作已经冻结的不同默认策略,
// 均返回粗粒度 [`pi_result::ErrorKind::Conflict`],并保持原配置不变。路径
// 判等不访问文件系统;大小写差异、符号链接别名或其它不同表示会被保守地
// 视为冲突,即使某个平台随后可能把它们解析到同一目录。
//
// 本库不提供重置、替换或强制覆盖接口。需要验证多个全局配置的测试必须
// 使用隔离子进程,不能通过测试专用公开接口破坏生产不变量。
//
// # 返回值、错误与 panic
//
// `Ok(())` 只证明配置表示已经成为或本来就是当前全局策略,不证明自定义
// 根目录存在、受支持、安全或可写。完整物理核验失败会由首次建立协调授权
// 的操作报告;实现不得自动退回默认目录。
//
// 所有可报告的输入和配置冲突通过 `pi_result::Error` 返回,精细错误上下文
// 必须由 `thiserror` 定义并按项目统一方式转换。本函数不得因这些条件 panic。
// 全局分配器终止进程等不可恢复行为不属于普通错误保证。
//
// # 副作用与成本
//
// 成功可能永久写入进程级配置,因此本函数不是纯函数;相同配置的重复成功
// 不产生新的配置变化。它不执行文件系统 I/O、不取得文件锁、不调用用户
// 代码。时间成本至多与路径表示长度线性相关,并包含常数次进程内同步;
// 配置冻结后的普通文件 I/O 热路径不得重复复制或比较完整路径。
/// 设置当前进程的跨进程协调根位置策略。
///
/// 相同配置可以重复设置;不同配置与已经冻结的策略冲突。`Custom` 的空路径
/// 或相对路径返回非法输入错误。成功只表示配置已经接受,不证明目标目录
/// 当前存在、可写、可信或具备所需文件系统能力;这些条件会在实际建立协调
/// 关系时核验。本函数不会传播配置到其它进程,也不提供重置接口。
pub fn set_cross_process_coordination_root(
    root: CrossProcessCoordinationRoot,
) -> pi_result::Result<()> {
    if let CrossProcessCoordinationRoot::Custom(path) = &root {
        if path.as_os_str().is_empty() || !path.is_absolute() {
            return Err(pi_result::error_stack::Report::new(
                pi_result::ErrorKind::InvalidInput,
            ));
        }
    }

    match CROSS_PROCESS_COORDINATION_ROOT.set(root) {
        Ok(()) => Ok(()),
        Err(rejected) if CROSS_PROCESS_COORDINATION_ROOT.get() == Some(&rejected) => {
            Ok(())
        }
        Err(_) => Err(pi_result::error_stack::Report::new(
            pi_result::ErrorKind::Conflict,
        )),
    }
}

static CROSS_PROCESS_COORDINATION_ROOT: OnceLock<
    CrossProcessCoordinationRoot,
> = OnceLock::new();

pub(crate) fn frozen_cross_process_coordination_root(
) -> &'static CrossProcessCoordinationRoot {
    CROSS_PROCESS_COORDINATION_ROOT
        .get_or_init(CrossProcessCoordinationRoot::default)
}

pub(crate) fn configured_cross_process_coordination_root(
) -> Option<&'static CrossProcessCoordinationRoot> {
    CROSS_PROCESS_COORDINATION_ROOT.get()
}

// 绑定到单个稳定本地文件身份的跨进程协调授权令牌。
//
// # 作用与使用位置
//
// 本类型是标准本地 adapter 建立协调式文件资源时使用的不透明 capability。
// 它把稳定文件身份、目标实例代次(target incarnation)、后端实例、协调
// 协议版本、协调目录的持久代次、全局根目录的稳定身份和内部协调核心绑定
// 在一起。后续独立打开的文件资源可以借用同一个公开授权,并在内部共享其
// 拥有型核心;读取、追加、刷新和映射方法无需重复接收本令牌。
//
// 它不是文件内容 buffer、操作系统访问权限、长期独占文件锁或能够强制不
// 合作进程服从的安全凭据。令牌也不证明路径始终指向原文件;每次建立资源
// 或执行命名空间变更仍必须同时核验稳定身份、目标实例代次和协议状态,防止
// 删除重建、替换、硬链接或路径别名使授权静默转移到另一对象。
//
// 目标实例代次只在显式协调式删除已经把旧实例发布为 `Removed`,并由显式
// 协调式创建协议建立新目标时更换。最后一个授权或资源的 `Drop`、普通打开、
// 进程重启和暂时没有参与者都不得自动轮换它。旧实例对应的授权可以安全
// `Drop`,但后续打开、映射和命名空间变更必须明确拒绝该授权。
//
// # 安全边界
//
// 建立本令牌的后续公共入口必须是 `unsafe`:调用方需要承诺所有可能破坏
// 文件稳定性或操作矩阵的相关进程、句柄和映射都遵守相同协调协议,并选择
// 相同物理协调根目录。不合作进程、库外文件句柄、另一份未共享全局状态的
// 库实例和未经验证的网络或用户态文件系统不受本令牌自动控制。
//
// 该 `unsafe` 建立点允许后续绑定了授权的协调式映射方法采用安全 Rust
// interface;它没有消除 `memmap2` 对外部文件稳定性的实际要求,只是把无法
// 由操作系统完全证明的责任集中到一个明确入口。
//
// # 生命周期、线程与清理
//
// 公开令牌刻意不实现 `Clone`、`Copy` 或 `Default`。调用方可以通过共享引用
// 并发独立打开多个资源;内部 adapter 可以零成本共享私有引用计数核心,但
// 不向调用方暴露该实现。令牌先行 `Drop` 不得使已经打开的资源、映射或在途
// 操作失效,因为它们各自拥有必要内部状态。
//
// 公开授权值本身不算活动文件资源,也不持有阻止协调式删除的跨进程共享
// 生命周期租约。否则借用授权执行删除会被自身阻塞,多个独立建立的授权也会
// 让目标无法在授权仍可安全 `Drop` 时退役。授权只能提供建立新受管资源所需
// 的身份和协议依据;每次资源建立仍须重新经过状态、实例代次和身份核验。
//
// 授权也不永久持有目标文件的原生句柄。它拥有的是不可变的稳定身份值、受管
// 定位信息、协调目录代次、目标实例代次和 sidecar 协调状态,而不是隐藏的
// 文件占用。建立授权时用于验证身份的临时目标句柄必须在返回前关闭;协调式
// 创建同时返回的首个文件资源持有自己的独立句柄,不能把授权验证句柄同时
// 当成授权字段和文件资源。只有实际文件资源、映射和在途完成所有者持有其
// 生命周期所需的目标句柄。
//
// 后续打开或命名空间操作必须先加入正确的生命周期锁域,再重新打开目标,
// 并把新句柄取得的稳定身份与授权及当前 `Published` 记录同时核对。由此,
// 仅有旧授权存活不会在 Windows 上把删除长期维持为待处理状态,也不会阻止
// 同一定位符在旧实例完整退役后由显式协调协议建立新的目标实例。
//
// 标准本地 adapter 必须在私有协调核心中按“当前进程 + 稳定文件身份 + 目标
// 实例代次”聚合活动所有者。第一个文件资源、映射、候选获取或取消后的在途
// 完成所有者进入时取得一把跨进程共享生命周期租约,最后一个退出时释放;
// 同进程后续所有者只增加私有计数,不为每个公开对象重复打开锁文件。只读
// 映射的零成本克隆通过共享私有所有者继续维持同一租约。
//
// 协调式删除必须先关闭当前进程的新资源准入并证明私有活动计数为零,再非
// 阻塞地尝试取得同一锁域的排它生命周期租约。取得失败立即报告冲突,不能
// 等待其它进程释放资源;取得成功才证明所有合作进程当前均无活动资源。
// 生命周期租约不替代追加串行锁、MMAP 范围锁或其它操作级协调。
//
// `Drop` 不执行强刷新、阻塞等待或协调目录删除。协调目录不是普通临时目录,
// 只能由以后单独设计的离线垃圾回收流程在证明无活动参与者后清理。
//
// `Send` 与 `Sync` 由私有字段自动获得,不使用手写 `unsafe impl`。类型拥有
// 全部状态并满足 `'static`;它不实现判等、排序、哈希、序列化、`AsRef`、
// `Borrow`、`Deref` 或公开转换,因为这些能力容易把令牌表示误认为稳定文件
// 身份、跨进程传输格式或底层锁句柄。
/// 绑定到单个文件目标实例的跨进程协调授权。
///
/// 本类型不透明、不可克隆,也不是操作系统权限、文件内容缓冲区或可序列化的
/// 跨进程凭据。它只适用于建立时核验的 namespace、目标实例与协调配置;
/// 删除后同名重建的文件不得复用旧授权。授权可在线程间移动和共享借用,且
/// 可以先于由它打开的文件资源释放,不会使既有资源失效。
///
/// 该值本身不算活动文件资源,也不会单独阻止协调式删除。其安全保证依赖
/// 建立授权的 `unsafe` 前提持续成立:所有相关进程均合作,协调位置可信,
/// 并且同一物理目标在所有合作进程中始终使用相同的物理父目录和原生末级
/// 文件名。不同硬链接名不能作为同一协调目标交替使用。
pub struct CrossProcessFileAuthority {
    pub(crate) core: Arc<crate::local::CrossProcessAuthorityCore>,
}

impl CrossProcessFileAuthority {
    pub(crate) fn from_local_core(
        core: Arc<crate::local::CrossProcessAuthorityCore>,
    ) -> Self {
        Self { core }
    }

    pub(crate) fn local_core(
        &self,
    ) -> &Arc<crate::local::CrossProcessAuthorityCore> {
        &self.core
    }
}

impl fmt::Debug for CrossProcessFileAuthority {
    // 显示脱敏后的后端、协议版本和诊断编号,不泄漏底层锁句柄。
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("CrossProcessFileAuthority")
            .field("backend", &"local")
            .field("protocol_version", &self.core.protocol_version())
            .field("diagnostic_id", &self.core.diagnostic_id())
            .finish_non_exhaustive()
    }
}

impl fmt::Display for CrossProcessFileAuthority {
    // 显示供人阅读的不透明授权摘要,不作为文件身份或序列化格式。
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "local cross-process file authority #{} (protocol v{})",
            self.core.diagnostic_id(),
            self.core.protocol_version(),
        )
    }
}