code wiki / _hdl_build / nx_file_install_session_20260910.nx
nx_file_install_session_20260910.nx source
↩ module page · 413 lines · 21537 B
1// Recoverable file installation. An immutable, checksummed intent identifies
2// every pathname and both artifact digests. No process-local pointer is persisted.
3// Runtime/service health is a later acceptance step, not implied by file install.
4import "nx_fio.nx"
5
6// Versioned wire schema widths, independent of host struct layout or I/O policy.
7const FI_MAGIC: *u8="NXFINST1"
8const FI_MAGIC_BYTES: i64=8
9const FI_DIGEST_BYTES: i64=32
10const FI_WORD_BYTES: i64=8
11const FI_PATH_FIELDS: i64=4
12const FI_HEADER_BYTES: i64=FI_MAGIC_BYTES + FI_DIGEST_BYTES*2 + FI_WORD_BYTES*(1+FI_PATH_FIELDS)
13const FI_ENOENT: i64=0-2
14const FI_EACCES: i64=0-13 // Linux errno: recorded target is outside the supplied authorization.
15
16struct NxFileInstallPlan {
17 bytes: *u8,
18 length: i64,
19 source: *u8,
20 live: *u8,
21 candidate: *u8,
22 backup: *u8,
23 expected_new: *u8,
24 expected_old: *u8,
25 mode: i64,
26}
27struct NxFileInstallResult {
28 stage: *u8,
29 code: i64,
30 already_published: i64,
31 io: NxFileWriteResult,
32 replacement: NxFileReplaceResult,
33}
34func fi_len(s: *u8) -> i64 {
35 if (s as i64)==0 { return 0 }
36 var n: i64=0;while s[n]!=(0 as u8) { n=n+1 };return n
37}
38func fi_same(a: *u8,b: *u8,n: i64) -> i64 {
39 var different: i64=0;var i: i64=0
40 while i<n { different=different | ((a[i] as i64) ^ (b[i] as i64));i=i+1 }
41 return (different==0) as i64
42}
43func fi_copy(dst: *u8,src: *u8,n: i64) -> i64 {
44 var i: i64=0;while i<n { dst[i]=src[i];i=i+1 };return n
45}
46// Durable plans cannot depend on the caller's working directory. Registry path
47// resolution must additionally ensure owned parents and no symlink aliases.
48func fi_path_valid(path: *u8) -> i64 {
49 if (path as i64)==0 { return 0 }
50 if path[0]!=(47 as u8) { return 0 }
51 var start: i64=1;var i: i64=1
52 while 1 {
53 if path[i]==(47 as u8) || path[i]==(0 as u8) {
54 let n: i64=i-start
55 if n==0 { return 0 }
56 if n==1 && path[start]==(46 as u8) { return 0 }
57 if n==2 && path[start]==(46 as u8) && path[start+1]==(46 as u8) { return 0 }
58 if path[i]==(0 as u8) { return 1 }
59 start=i+1
60 }
61 i=i+1
62 }
63 return 0
64}
65func fi_paths_distinct(names: *i64) -> i64 {
66 var i: i64=0
67 while i<FI_PATH_FIELDS {
68 let a: *u8=names[i] as *u8;if fi_path_valid(a)==0 { return 0 }
69 var j: i64=i+1
70 while j<FI_PATH_FIELDS {
71 let b: *u8=names[j] as *u8
72 let n: i64=fi_len(a)
73 if n==fi_len(b) && fi_same(a,b,n)==1 { return 0 }
74 j=j+1
75 }
76 i=i+1
77 }
78 return 1
79}
80func fi_word_put(p: *u8,v: i64) -> i64 {
81 var i: i64=0;while i<FI_WORD_BYTES { p[i]=((v>>(i*8))&255) as u8;i=i+1 };return 0
82}
83func fi_word_get(p: *u8) -> i64 {
84 var v: i64=0;var i: i64=0
85 while i<FI_WORD_BYTES { v=v | ((p[i] as i64)<<(i*8));i=i+1 };return v
86}
87func fi_plan_init(p: *NxFileInstallPlan) -> i64 {
88 let raw: *u8=p as *u8;var i: i64=0
89 while i<__size_of(NxFileInstallPlan) { raw[i]=0 as u8;i=i+1 };return 0
90}
91func fi_plan_close(p: *NxFileInstallPlan) -> i64 {
92 var rc: i64=0
93 if (p.bytes as i64)!=0 { rc=sys_munmap(p.bytes,p.length) }
94 fi_plan_init(p);return rc
95}
96// Validation is over a bounded blob. Path pointers are published only after
97// checksum, length, termination and mode validation have all succeeded.
98func fi_plan_decode(bytes: *u8,n: i64,p: *NxFileInstallPlan) -> i64 {
99 if n<FI_HEADER_BYTES+FI_PATH_FIELDS+FI_DIGEST_BYTES { return FIO_EBADMSG }
100 if fi_same(bytes,FI_MAGIC,FI_MAGIC_BYTES)==0 { return FIO_EBADMSG }
101 let digest: *u8=sys_mmap(FI_DIGEST_BYTES)
102 sha256_digest(bytes,n-FI_DIGEST_BYTES,digest)
103 let checked: i64=fi_same(digest,bytes+n-FI_DIGEST_BYTES,FI_DIGEST_BYTES)
104 sys_munmap(digest,FI_DIGEST_BYTES)
105 if checked==0 { return FIO_EBADMSG }
106 let mode_at: i64=FI_MAGIC_BYTES+FI_DIGEST_BYTES*2
107 let mode: i64=fi_word_get(bytes+mode_at)
108 if mode<0 || mode>0x1ff { return FIO_EBADMSG }
109 let sizes: *u8=bytes+mode_at+FI_WORD_BYTES
110 let end: i64=n-FI_DIGEST_BYTES
111 var off: i64=FI_HEADER_BYTES;var i: i64=0
112 while i<FI_PATH_FIELDS {
113 let length: i64=fi_word_get(sizes+i*FI_WORD_BYTES)
114 if length<=1 || length>end-off { return FIO_EBADMSG }
115 var j: i64=0
116 while j<length-1 { if bytes[off+j]==(0 as u8) { return FIO_EBADMSG };j=j+1 }
117 if bytes[off+length-1]!=(0 as u8) { return FIO_EBADMSG }
118 off=off+length;i=i+1
119 }
120 if off!=end { return FIO_EBADMSG }
121 let names: *i64=sys_mmap(FI_PATH_FIELDS*__size_of(i64)) as *i64
122 off=FI_HEADER_BYTES;i=0
123 while i<FI_PATH_FIELDS { names[i]=(bytes+off) as i64;off=off+fi_word_get(sizes+i*FI_WORD_BYTES);i=i+1 }
124 let distinct: i64=fi_paths_distinct(names)
125 sys_munmap(names as *u8,FI_PATH_FIELDS*__size_of(i64))
126 if distinct==0 { return FIO_EBADMSG }
127 p.bytes=bytes;p.length=n;p.mode=mode
128 p.expected_new=bytes+FI_MAGIC_BYTES;p.expected_old=p.expected_new+FI_DIGEST_BYTES
129 off=FI_HEADER_BYTES;p.source=bytes+off
130 off=off+fi_word_get(sizes);p.live=bytes+off
131 off=off+fi_word_get(sizes+FI_WORD_BYTES);p.candidate=bytes+off
132 off=off+fi_word_get(sizes+FI_WORD_BYTES*2);p.backup=bytes+off
133 return 0
134}
135// Callers supply the admitted record budget. It is not inferred from untrusted
136// file lengths. The file region owns descriptor cleanup on every read failure.
137func fi_plan_read(path: *u8,budget: i64,p: *NxFileInstallPlan) -> i64 {
138 fi_plan_init(p)
139 let r: *NxFileReadRegion=sys_mmap(__size_of(NxFileReadRegion)) as *NxFileReadRegion
140 fio_region_init(r);var rc: i64=fio_region_open(path,r)
141 var bytes: *u8=0 as *u8
142 let n: i64=r.total
143 if rc==0 && (n<=0 || n>budget) { rc=FIO_EINVAL }
144 if rc==0 {
145 bytes=sys_mmap(n)
146 if (bytes as i64)<0 { rc=bytes as i64;bytes=0 as *u8 }
147 }
148 if rc==0 { let got: i64=fio_region_next(r,bytes,n);if got!=n { rc=got;if rc>=0 { rc=FIO_EIO } } }
149 fio_region_close(r)
150 if rc==0 { rc=r.code }
151 sys_munmap(r as *u8,__size_of(NxFileReadRegion))
152 if rc==0 { rc=fi_plan_decode(bytes,n,p) }
153 if rc!=0 && (bytes as i64)!=0 { sys_munmap(bytes,n) }
154 return rc
155}
156// Exclusive creation preserves operation identity. An interrupted partial record
157// fails checksum validation; it is retained, never silently overwritten on retry.
158func fi_plan_create(path: *u8,source: *u8,live: *u8,candidate: *u8,backup: *u8,mode: i64,new_digest: *u8,old_digest: *u8,budget: i64,out: *NxFileWriteResult) -> i64 {
159 out.stage="intent-input";out.code=FIO_EINVAL;out.written=0;out.close_code=0
160 if (new_digest as i64)==0 || (old_digest as i64)==0 || mode<0 || mode>0x1ff { return out.code }
161 let names: *i64=sys_mmap(FI_PATH_FIELDS*__size_of(i64)) as *i64
162 names[0]=source as i64;names[1]=live as i64;names[2]=candidate as i64;names[3]=backup as i64
163 if fi_paths_distinct(names)==0 { sys_munmap(names as *u8,FI_PATH_FIELDS*__size_of(i64));return out.code }
164 var n: i64=FI_HEADER_BYTES+FI_DIGEST_BYTES;var i: i64=0;var valid: i64=1
165 while i<FI_PATH_FIELDS {
166 let length: i64=fi_len(names[i] as *u8)
167 if length<=0 || n>budget || length>=budget-n { valid=0;break }
168 n=n+length+1;i=i+1
169 }
170 if valid==0 { sys_munmap(names as *u8,FI_PATH_FIELDS*__size_of(i64));return out.code }
171 let b: *u8=sys_mmap(n)
172 fi_copy(b,FI_MAGIC,FI_MAGIC_BYTES)
173 fi_copy(b+FI_MAGIC_BYTES,new_digest,FI_DIGEST_BYTES)
174 fi_copy(b+FI_MAGIC_BYTES+FI_DIGEST_BYTES,old_digest,FI_DIGEST_BYTES)
175 let mode_at: i64=FI_MAGIC_BYTES+FI_DIGEST_BYTES*2
176 fi_word_put(b+mode_at,mode)
177 var off: i64=FI_HEADER_BYTES;i=0
178 while i<FI_PATH_FIELDS {
179 let name: *u8=names[i] as *u8;let length: i64=fi_len(name)+1
180 fi_word_put(b+mode_at+FI_WORD_BYTES*(i+1),length)
181 fi_copy(b+off,name,length);off=off+length;i=i+1
182 }
183 sys_munmap(names as *u8,FI_PATH_FIELDS*__size_of(i64))
184 sha256_digest(b,n-FI_DIGEST_BYTES,b+n-FI_DIGEST_BYTES)
185 out.stage="intent-create"
186 let fd: i64=sys_openat_exclusive(path,MODE_0600)
187 if fd<0 { out.code=fd } else {
188 out.code=fio_write_sync_fd(fd,b,n,out)
189 if out.code==0 {
190 let directory: *NxFileWriteResult=sys_mmap(__size_of(NxFileWriteResult)) as *NxFileWriteResult
191 out.code=fio_sync_parent(path,directory)
192 if out.code!=0 { out.stage=directory.stage;out.close_code=directory.close_code }
193 sys_munmap(directory as *u8,__size_of(NxFileWriteResult))
194 }
195 }
196 sys_munmap(b,n)
197 if out.code==0 { out.stage="intent-durable" }
198 return out.code
199}
200func fi_sync_existing(path: *u8,out: *NxFileWriteResult) -> i64 {
201 out.stage="sync-open";out.code=0;out.written=0;out.close_code=0
202 let fd: i64=sys_openat_rd(path)
203 if fd<0 { out.code=fd;return fd }
204 return fio_write_sync_fd(fd,0 as *u8,0,out)
205}
206// Existing prepared files are reused only after digest verification. Partial or
207// conflicting files are retained and reported; there is no automatic truncation.
208func fi_prepare_expected(source: *u8,path: *u8,digest: *u8,mode: i64,buffer: *u8,capacity: i64,out: *NxFilePrepareResult) -> i64 {
209 out.stage="prepared-identity"
210 var rc: i64=fio_verify_sha256(path,digest,buffer,capacity)
211 if rc==FI_ENOENT {
212 rc=fio_prepare_copy(source,path,mode,buffer,capacity,out)
213 if rc==0 { out.stage="prepared-identity";rc=fio_verify_sha256(path,digest,buffer,capacity) }
214 }
215 if rc==0 { out.stage="prepared-mode";rc=nx_chmod(path,mode) }
216 if rc==0 {
217 let sync: *NxFileWriteResult=sys_mmap(__size_of(NxFileWriteResult)) as *NxFileWriteResult
218 rc=fi_sync_existing(path,sync);out.stage=sync.stage;out.destination_close=sync.close_code
219 sys_munmap(sync as *u8,__size_of(NxFileWriteResult))
220 }
221 out.code=rc
222 if rc!=0 { return rc }
223 out.stage="prepared";out.durable=1;return 0
224}
225func fi_install_owned(p: *NxFileInstallPlan,buffer: *u8,capacity: i64,out: *NxFileInstallResult) -> i64 {
226 out.stage="observe-live"
227 var current: i64=fio_verify_sha256(p.live,p.expected_new,buffer,capacity)
228 if current==0 {
229 out.already_published=1;out.replacement.publication.visible=1
230 out.stage="verify-existing-backup"
231 out.code=fio_verify_sha256(p.backup,p.expected_old,buffer,capacity)
232 if out.code!=0 { return out.code }
233 out.stage="reconcile-durability"
234 out.code=fi_sync_existing(p.live,&out.io)
235 if out.code==0 { out.stage="live-directory-sync";out.code=fio_sync_parent(p.live,&out.io) }
236 if out.code==0 { out.stage="candidate-directory-sync";out.code=fio_sync_parent(p.candidate,&out.io) }
237 if out.code==0 { out.stage="backup-file-sync";out.code=fi_sync_existing(p.backup,&out.io) }
238 if out.code==0 { out.stage="backup-directory-sync";out.code=fio_sync_parent(p.backup,&out.io) }
239 if out.code==0 { out.stage="published-reconciled";out.replacement.publication.durable=1 }
240 return out.code
241 }
242 if current!=FIO_EBADMSG { out.code=current;return current }
243 out.stage="verify-original-live";out.code=fio_verify_sha256(p.live,p.expected_old,buffer,capacity)
244 if out.code!=0 { return out.code }
245 out.stage="prepare-candidate"
246 out.code=fi_prepare_expected(p.source,p.candidate,p.expected_new,p.mode,buffer,capacity,&out.replacement.candidate)
247 if out.code!=0 { return out.code }
248 out.stage="prepare-backup"
249 out.code=fi_prepare_expected(p.live,p.backup,p.expected_old,p.mode,buffer,capacity,&out.replacement.backup)
250 if out.code!=0 { return out.code }
251 out.stage="backup-directory-sync";out.code=fio_sync_parent(p.backup,&out.io)
252 if out.code!=0 { return out.code }
253 out.stage="publish";out.code=fio_publish_candidate(p.candidate,p.live,&out.replacement.publication)
254 if out.code==0 { out.stage="published" };return out.code
255}
256// plan_path is the operation handle. Every retry reopens its recorded inputs;
257// it never infers success from existence or repeats a copy into the live file.
258func fi_install_core(plan_path: *u8,record_budget: i64,buffer: *u8,capacity: i64,allowed_live: *u8,expected_intent: *u8,out: *NxFileInstallResult) -> i64 {
259 out.stage="intent-read";out.code=0;out.already_published=0;fio_replace_init(&out.replacement)
260 out.io.stage="not-started";out.io.code=0;out.io.written=0;out.io.close_code=0
261 let p: *NxFileInstallPlan=sys_mmap(__size_of(NxFileInstallPlan)) as *NxFileInstallPlan
262 fi_plan_init(p)
263 out.code=fi_plan_read(plan_path,record_budget,p)
264 if out.code==0 && (expected_intent as i64)!=0 {
265 // Bind authorization to the exact decoded bytes, not a second pathname
266 // read that could observe a different intent between check and use.
267 out.stage="intent-identity"
268 let digest: *u8=sys_mmap(FI_DIGEST_BYTES)
269 sha256_digest(p.bytes,p.length,digest)
270 if fi_same(digest,expected_intent,FI_DIGEST_BYTES)==0 { out.code=FIO_EBADMSG }
271 sys_munmap(digest,FI_DIGEST_BYTES)
272 if out.code==0 {
273 out.stage="target-binding"
274 let n: i64=fi_len(allowed_live)
275 if n!=fi_len(p.live) { out.code=FI_EACCES }
276 else { if fi_same(allowed_live,p.live,n)==0 { out.code=FI_EACCES } }
277 }
278 }
279 if out.code==0 {
280 out.code=fio_target_lock_acquire(p.live,&out.replacement.lock)
281 if out.code!=0 { out.stage=out.replacement.lock.stage }
282 else {
283 // The intent must be durable even if its creator lost the response
284 // or exited between file fsync and directory fsync.
285 out.stage="intent-durability";out.code=fi_sync_existing(plan_path,&out.io)
286 if out.code==0 { out.stage="intent-directory-sync";out.code=fio_sync_parent(plan_path,&out.io) }
287 if out.code==0 { fi_install_owned(p,buffer,capacity,out) }
288 let released: i64=fio_target_lock_release(&out.replacement.lock)
289 if out.code==0 && released!=0 { out.code=released;out.stage=out.replacement.lock.stage }
290 }
291 }
292 fi_plan_close(p);sys_munmap(p as *u8,__size_of(NxFileInstallPlan))
293 return out.code
294}
295
296// Privileged local compatibility entry point. Management-facing execution must
297// use fi_install_bound with a target resolved from its authorized registry.
298func fi_install(plan_path: *u8,record_budget: i64,buffer: *u8,capacity: i64,out: *NxFileInstallResult) -> i64 {
299 return fi_install_core(plan_path,record_budget,buffer,capacity,0 as *u8,0 as *u8,out)
300}
301func fi_install_bound(plan_path: *u8,record_budget: i64,buffer: *u8,capacity: i64,allowed_live: *u8,expected_intent: *u8,out: *NxFileInstallResult) -> i64 {
302 if fi_path_valid(allowed_live)==0 || (expected_intent as i64)==0 {
303 out.stage="binding-input";out.code=FIO_EINVAL;out.already_published=0
304 fio_replace_init(&out.replacement)
305 out.io.stage="not-started";out.io.code=0;out.io.written=0;out.io.close_code=0
306 return out.code
307 }
308 return fi_install_core(plan_path,record_budget,buffer,capacity,allowed_live,expected_intent,out)
309}
310
311// A caller may keep this session across runtime acceptance and rollback. Only
312// cooperating writers using the same registry-resolved canonical live path are
313// excluded; this is not a filesystem sandbox or protection from arbitrary writers.
314struct NxFileInstallSession {
315 plan: NxFileInstallPlan,
316 lock: NxFileTargetLock,
317 stage: *u8,
318 code: i64,
319 held: i64,
320}
321func fi_session_init(s:*NxFileInstallSession)->i64 {
322 fi_plan_init(&s.plan);fio_target_lock_init(&s.lock)
323 s.stage="session-input";s.code=FIO_EINVAL;s.held=0;return 0
324}
325func fi_session_bind(p:*NxFileInstallPlan,allowed_live:*u8,expected_intent:*u8)->i64 {
326 if fi_path_valid(allowed_live)==0 || (expected_intent as i64)==0 { return FIO_EINVAL }
327 let digest:*u8=sys_mmap(FI_DIGEST_BYTES)
328 sha256_digest(p.bytes,p.length,digest)
329 let same:i64=fi_same(digest,expected_intent,FI_DIGEST_BYTES)
330 sys_munmap(digest,FI_DIGEST_BYTES)
331 if same==0 { return FIO_EBADMSG }
332 let n:i64=fi_len(allowed_live)
333 if n!=fi_len(p.live) { return FI_EACCES }
334 if fi_same(allowed_live,p.live,n)==0 { return FI_EACCES };return 0
335}
336func fi_session_close(s:*NxFileInstallSession)->i64 {
337 let rc:i64=fio_target_lock_release(&s.lock);s.held=0
338 let closed:i64=fi_plan_close(&s.plan)
339 if rc!=0 { s.code=rc;s.stage="session-unlock";return rc }
340 if closed!=0 { s.code=closed;s.stage="session-plan-close";return closed }
341 s.stage="session-closed";s.code=0;return 0
342}
343// Initialize once before first begin. Reopening an active session refuses without
344// destroying its lock or plan. On failure close is still safe and required.
345func fi_session_begin(path:*u8,budget:i64,allowed_live:*u8,expected_intent:*u8,s:*NxFileInstallSession)->i64 {
346 if s.held!=0 || s.lock.fd>=0 || (s.plan.bytes as i64)!=0 { return FIO_EEXIST }
347 s.stage="session-input";s.code=FIO_EINVAL
348 if fi_path_valid(path)==0 || budget<=0 || fi_path_valid(allowed_live)==0 || (expected_intent as i64)==0 { return s.code }
349 s.stage="intent-read";s.code=fi_plan_read(path,budget,&s.plan)
350 if s.code!=0 { return s.code }
351 s.stage="intent-binding";s.code=fi_session_bind(&s.plan,allowed_live,expected_intent)
352 if s.code!=0 { return s.code }
353 s.stage="session-lock";s.code=fio_target_lock_acquire(s.plan.live,&s.lock)
354 if s.code!=0 { return s.code };s.held=1
355 let io:*NxFileWriteResult=sys_mmap(__size_of(NxFileWriteResult)) as *NxFileWriteResult
356 s.stage="intent-durability";s.code=fi_sync_existing(path,io)
357 if s.code==0 { s.code=fio_sync_parent(path,io) }
358 sys_munmap(io as *u8,__size_of(NxFileWriteResult))
359 if s.code==0 { s.stage="session-ready" };return s.code
360}
361func fi_session_result_init(out:*NxFileInstallResult)->i64 {
362 out.stage="session-input";out.code=FIO_EINVAL;out.already_published=0
363 fio_replace_init(&out.replacement)
364 out.io.stage="not-started";out.io.code=0;out.io.written=0;out.io.close_code=0;return 0
365}
366func fi_session_publish(s:*NxFileInstallSession,buffer:*u8,capacity:i64,out:*NxFileInstallResult)->i64 {
367 fi_session_result_init(out)
368 if s.held!=1 || s.lock.fd<0 || s.code!=0 || (buffer as i64)==0 || capacity<=0 { return out.code }
369 return fi_install_owned(&s.plan,buffer,capacity,out)
370}
371// Rollback is another approved immutable intent, preserving the rejected artifact
372// in its own backup. It must invert this session's digests and keep the same target.
373func fi_session_reverse(s:*NxFileInstallSession,path:*u8,budget:i64,expected_intent:*u8,buffer:*u8,capacity:i64,out:*NxFileInstallResult)->i64 {
374 fi_session_result_init(out)
375 if fi_path_valid(path)==0 || budget<=0 || (expected_intent as i64)==0 { return out.code }
376 if s.held!=1 || s.lock.fd<0 || s.code!=0 || (buffer as i64)==0 || capacity<=0 { return out.code }
377 let reverse:*NxFileInstallPlan=sys_mmap(__size_of(NxFileInstallPlan)) as *NxFileInstallPlan
378 fi_plan_init(reverse);out.stage="reverse-intent-read";out.code=fi_plan_read(path,budget,reverse)
379 if out.code==0 { out.stage="reverse-intent-binding";out.code=fi_session_bind(reverse,s.plan.live,expected_intent) }
380 if out.code==0 {
381 out.stage="reverse-digest-pair"
382 if fi_same(reverse.expected_new,s.plan.expected_old,FI_DIGEST_BYTES)==0 || fi_same(reverse.expected_old,s.plan.expected_new,FI_DIGEST_BYTES)==0 { out.code=FIO_EBADMSG }
383 }
384 if out.code==0 { out.stage="reverse-intent-durability";out.code=fi_sync_existing(path,&out.io) }
385 if out.code==0 { out.code=fio_sync_parent(path,&out.io) }
386 if out.code==0 { fi_install_owned(reverse,buffer,capacity,out) }
387 fi_plan_close(reverse);sys_munmap(reverse as *u8,__size_of(NxFileInstallPlan));return out.code
388}
389
390// Prepare the approved rollback inputs before publishing NEW. This verifies the
391// inverse identity pair while OLD is still live; no live pathname is replaced.
392func fi_session_prepare_reverse(s:*NxFileInstallSession,path:*u8,budget:i64,expected_intent:*u8,buffer:*u8,capacity:i64,out:*NxFileInstallResult)->i64 {
393 fi_session_result_init(out)
394 if s.held!=1 || s.lock.fd<0 || s.code!=0 || fi_path_valid(path)==0 || budget<=0 || (expected_intent as i64)==0 || (buffer as i64)==0 || capacity<=0 { return out.code }
395 let p:*NxFileInstallPlan=sys_mmap(__size_of(NxFileInstallPlan)) as *NxFileInstallPlan
396 if (p as i64)<0 { out.stage="reverse-admission-allocation";out.code=p as i64;return out.code }
397 fi_plan_init(p);out.stage="reverse-admission-read";out.code=fi_plan_read(path,budget,p)
398 if out.code==0 { out.stage="reverse-admission-binding";out.code=fi_session_bind(p,s.plan.live,expected_intent) }
399 if out.code==0 {
400 out.stage="reverse-admission-identities"
401 if fi_same(p.expected_new,s.plan.expected_old,FI_DIGEST_BYTES)==0 || fi_same(p.expected_old,s.plan.expected_new,FI_DIGEST_BYTES)==0 { out.code=FIO_EBADMSG }
402 if fi_len(p.source)!=fi_len(s.plan.backup) { out.code=FI_EACCES }
403 else { if fi_same(p.source,s.plan.backup,fi_len(p.source))==0 { out.code=FI_EACCES } }
404 }
405 if out.code==0 { out.stage="reverse-intent-sync";out.code=fi_sync_existing(path,&out.io) }
406 if out.code==0 { out.code=fio_sync_parent(path,&out.io) }
407 if out.code==0 { out.stage="reverse-candidate-prepare";out.code=fi_prepare_expected(s.plan.live,p.candidate,p.expected_new,p.mode,buffer,capacity,&out.replacement.candidate) }
408 if out.code==0 { out.code=fio_sync_parent(p.candidate,&out.io) }
409 if out.code==0 { out.stage="reverse-history-prepare";out.code=fi_prepare_expected(s.plan.source,p.backup,p.expected_old,p.mode,buffer,capacity,&out.replacement.backup) }
410 if out.code==0 { out.code=fio_sync_parent(p.backup,&out.io) }
411 if out.code==0 { out.stage="reverse-ready" }
412 fi_plan_close(p);sys_munmap(p as *u8,__size_of(NxFileInstallPlan));return out.code
413}