nx_apistack_idempotency_epoch_candidate_t293.nx source
↩ module page · 218 lines · 12018 B
1// Isolated preparation increment of the existing idempotency owner.
2// PREPARED is never an execution permit. No default/latest epoch routing.
3import "nx_apistack_idempotency.nx"
4import "nx_itoa_lib.nx"
5const ID_EP_PRIVATE_MODE:i64=0x1c0 // POSIX0700 private namespace; not a resource ceiling.
6const ID_EP_O_DIRECTORY:i64=65536 // Linux open flag.
7func ie_open_directory(path:*u8)->i64{return __syscall(257,AT_FDCWD,path,ID_O_RDONLY|ID_EP_O_DIRECTORY|ID_O_NOFOLLOW|ID_O_CLOEXEC,0,0,0)}
8const ID_EP_INVALID:i64=-120
9const ID_EP_UNKNOWN:i64=-121
10const ID_EP_CONFLICT:i64=-122
11const ID_EP_INCOMPLETE:i64=-123
12const ID_EP_BUSY:i64=-124
13const ID_EP_PREPARED_OBSERVED:i64=1
14const ID_EP_PREPARED_SYNCED:i64=2
15const ID_EP_FIELD_COUNT:i64=8
16const ID_EP_LENGTH_BYTES:i64=8
17const ID_EP_HASH_BYTES:i64=32
18const ID_EP_HASH_HEX:i64=64
19struct NxIdEpochSpec{
20 domain:*u8,epoch:*u8,creation:*u8,creation_sha:*u8,
21 predecessor:*u8,snapshot_path:*u8,snapshot_sha:*u8,snapshot_bytes:i64
22}
23struct NxIdEpochPrepared{
24 state:i64,system_code:i64,created:i64,
25 definition:*u8,definition_bytes:i64,
26 directory:*u8,directory_bytes:i64,ledger:*u8,ledger_bytes:i64,
27 manifest:*u8,manifest_bytes:i64
28}
29func ie_len(p:*u8)->i64{if (p as i64)<=0{return -1};var n:i64=0;while p[n]!=0 as u8{n=n+1};return n}
30func ie_valid(p:*u8)->i64{
31 let n:i64=ie_len(p);if n<=0{return 0}
32 var i:i64=0;while i<n{if p[i]<32 as u8||p[i]>126 as u8{return 0};i=i+1};return 1
33}
34func ie_hex(p:*u8)->i64{
35 if ie_len(p)!=ID_EP_HASH_HEX{return 0}
36 var i:i64=0;while i<ID_EP_HASH_HEX{let x:i64=p[i] as i64;if (x<48||x>57)&&(x<97||x>102){return 0};i=i+1};return 1
37}
38func ie_init(r:*NxIdEpochPrepared)->i64{
39 r.state=ID_EP_INVALID;r.system_code=0;r.created=0
40 r.definition=0 as *u8;r.definition_bytes=0;r.directory=0 as *u8;r.directory_bytes=0
41 r.ledger=0 as *u8;r.ledger_bytes=0;r.manifest=0 as *u8;r.manifest_bytes=0;return 0
42}
43func ie_close(r:*NxIdEpochPrepared)->i64{
44 if (r.definition as i64)>0{sys_munmap_direct(r.definition,r.definition_bytes)}
45 if (r.directory as i64)>0{sys_munmap_direct(r.directory,r.directory_bytes)}
46 if (r.ledger as i64)>0{sys_munmap_direct(r.ledger,r.ledger_bytes)}
47 if (r.manifest as i64)>0{sys_munmap_direct(r.manifest,r.manifest_bytes)}
48 ie_init(r);return 0
49}
50func ie_fail(r:*NxIdEpochPrepared,state:i64,code:i64)->i64{r.state=state;r.system_code=code;return state}
51func ie_copy(out:*u8,at:i64,p:*u8,n:i64)->i64{var i:i64=0;while i<n{out[at+i]=p[i];i=i+1};return at+n}
52func ie_u64(out:*u8,at:i64,value:i64)->i64{
53 var v:i64=value;var i:i64=0;while i<ID_EP_LENGTH_BYTES{out[at+i]=(v&255) as u8;v=v>>8;i=i+1};return at+ID_EP_LENGTH_BYTES
54}
55func ie_hash_hex(p:*u8,n:i64,hex:*u8)->i64{
56 let digest:*u8=sys_mmap_try(ID_EP_HASH_BYTES);if (digest as i64)<=0{return ID_RESOURCE}
57 let rc:i64=sha256_digest_checked_native(p,n,digest)
58 if rc==0{let table:*u8="0123456789abcdef";var i:i64=0;while i<ID_EP_HASH_BYTES{let v:i64=digest[i] as i64;hex[i*2]=table[v>>4];hex[i*2+1]=table[v&15];i=i+1};hex[ID_EP_HASH_HEX]=0 as u8}
59 sys_munmap_direct(digest,ID_EP_HASH_BYTES);return rc
60}
61func ie_path(parent:*u8,suffix:*u8,outlen:*i64)->*u8{
62 let n:i64=ie_len(parent);let s:i64=ie_len(suffix)
63 if n<1||s<1{return 0 as *u8};if n>ID_SIGNED_MAX-s-2{return 0 as *u8}
64 let bytes:i64=n+s+2;let p:*u8=sys_mmap_try(bytes);if (p as i64)<=0{return 0 as *u8}
65 ie_copy(p,0,parent,n);p[n]=47 as u8;ie_copy(p,n+1,suffix,s);p[bytes-1]=0 as u8;outlen[0]=bytes;return p
66}
67// Canonical length framing binds all definition inputs; paths are data in the definition.
68func ie_build(root:*u8,s:*NxIdEpochSpec,r:*NxIdEpochPrepared)->i64{
69 if (r as i64)<=0{return ID_EP_INVALID}
70 ie_init(r)
71 if (s as i64)<=0{return r.state}
72 if ie_valid(root)!=1||ie_valid(s.domain)!=1||ie_valid(s.epoch)!=1||ie_valid(s.creation)!=1||ie_valid(s.predecessor)!=1||ie_valid(s.snapshot_path)!=1{return r.state}
73 if ie_hex(s.creation_sha)!=1||ie_hex(s.snapshot_sha)!=1||s.snapshot_bytes<0{return r.state}
74 let fields:*i64=sys_mmap_try(ID_EP_FIELD_COUNT*8) as *i64
75 if (fields as i64)<=0{return ie_fail(r,ID_RESOURCE,-12)}
76 fields[0]=s.domain as i64;fields[1]=s.epoch as i64;fields[2]=s.creation as i64;fields[3]=s.creation_sha as i64
77 fields[4]=s.predecessor as i64;fields[5]=s.snapshot_path as i64;fields[6]=s.snapshot_sha as i64;fields[7]="nishi.idempotency.epoch.prepared.v1" as i64
78 var total:i64=(ID_EP_FIELD_COUNT+1)*ID_EP_LENGTH_BYTES;var i:i64=0
79 while i<ID_EP_FIELD_COUNT{
80 let n:i64=ie_len(fields[i] as *u8)
81 if n>ID_SIGNED_MAX-total{sys_munmap_direct(fields as *u8,ID_EP_FIELD_COUNT*8);return ie_fail(r,ID_CAPACITY,0)}
82 total=total+n;i=i+1
83 }
84 if total>=ID_SIGNED_MAX{sys_munmap_direct(fields as *u8,ID_EP_FIELD_COUNT*8);return ie_fail(r,ID_CAPACITY,0)}
85 r.definition_bytes=total;r.definition=sys_mmap_try(total)
86 if (r.definition as i64)<=0{r.definition=0 as *u8;sys_munmap_direct(fields as *u8,ID_EP_FIELD_COUNT*8);return ie_fail(r,ID_RESOURCE,-12)}
87 var at:i64=ie_u64(r.definition,0,s.snapshot_bytes);i=0
88 while i<ID_EP_FIELD_COUNT{let p:*u8=fields[i] as *u8;let n:i64=ie_len(p);at=ie_u64(r.definition,at,n);at=ie_copy(r.definition,at,p,n);i=i+1}
89 sys_munmap_direct(fields as *u8,ID_EP_FIELD_COUNT*8)
90 let dn:i64=ie_len(s.domain);let en:i64=ie_len(s.epoch)
91 if dn>ID_SIGNED_MAX-en-2*ID_EP_LENGTH_BYTES{return ie_fail(r,ID_CAPACITY,0)}
92 let nsn:i64=dn+en+2*ID_EP_LENGTH_BYTES;let ns:*u8=sys_mmap_try(nsn);let hex:*u8=sys_mmap_try(ID_EP_HASH_HEX+1)
93 if (ns as i64)<=0||(hex as i64)<=0{
94 if (ns as i64)>0{sys_munmap_direct(ns,nsn)};if (hex as i64)>0{sys_munmap_direct(hex,ID_EP_HASH_HEX+1)}
95 return ie_fail(r,ID_RESOURCE,-12)
96 }
97 at=ie_u64(ns,0,dn);at=ie_copy(ns,at,s.domain,dn);at=ie_u64(ns,at,en);ie_copy(ns,at,s.epoch,en)
98 let hash:i64=ie_hash_hex(ns,nsn,hex);sys_munmap_direct(ns,nsn)
99 if hash!=0{sys_munmap_direct(hex,ID_EP_HASH_HEX+1);return ie_fail(r,ID_RESOURCE,hash)}
100 r.directory=ie_path(root,hex,&r.directory_bytes);sys_munmap_direct(hex,ID_EP_HASH_HEX+1)
101 if (r.directory as i64)<=0{return ie_fail(r,ID_RESOURCE,-12)}
102 r.ledger=ie_path(r.directory,"ledger",&r.ledger_bytes)
103 r.manifest=ie_path(r.directory,"definition",&r.manifest_bytes)
104 if (r.ledger as i64)<=0||(r.manifest as i64)<=0{return ie_fail(r,ID_RESOURCE,-12)}
105 r.state=0;return 0
106}
107func ie_open_regular(path:*u8,expected:i64,stat:*u8)->i64{
108 let fd:i64=__syscall(257,AT_FDCWD,path,ID_O_RDONLY|ID_O_NONBLOCK|ID_O_NOFOLLOW|ID_O_CLOEXEC,0,0,0)
109 if fd<0{return fd}
110 var rc:i64=sys_fstat_fd(fd,stat)
111 if rc==0{
112 let mode:i64=*((stat+NX_STAT_X64_MODE_OFFSET) as *i64);let n:i64=*((stat+NX_STAT_X64_SIZE_OFFSET) as *i64)
113 if (mode&NX_STAT_TYPE_MASK)!=NX_STAT_REGULAR_FILE||n!=expected{rc=FIO_EBADMSG}
114 }
115 if rc!=0{sys_close(fd);return rc};return fd
116}
117func ie_read_exact(fd:i64,p:*u8,n:i64)->i64{
118 var at:i64=0;while at<n{
119 let got:i64=sys_read(fd,p+at,n-at);if got==FIO_EINTR{continue};if got<0{return got};if got==0{return FIO_EIO};at=at+got
120 };return 0
121}
122func ie_verify_files(r:*NxIdEpochPrepared)->i64{
123 let stat:*u8=sys_mmap_try(NX_STAT_X64_BYTES);let bytes:*u8=sys_mmap_try(r.definition_bytes+1)
124 if (stat as i64)<=0||(bytes as i64)<=0{
125 if (stat as i64)>0{sys_munmap_direct(stat,NX_STAT_X64_BYTES)};if (bytes as i64)>0{sys_munmap_direct(bytes,r.definition_bytes+1)}
126 return ie_fail(r,ID_RESOURCE,-12)
127 }
128 let fd:i64=ie_open_regular(r.manifest,r.definition_bytes,stat)
129 var rc:i64=fd
130 if fd>=0{
131 rc=ie_read_exact(fd,bytes,r.definition_bytes)
132 if rc==0{let extra:i64=sys_read(fd,bytes+r.definition_bytes,1);if extra!=0{rc=FIO_EBADMSG}}
133 let closed:i64=sys_close(fd);if rc==0&&closed!=0{rc=closed}
134 if rc==0{var i:i64=0;while i<r.definition_bytes{if bytes[i]!=r.definition[i]{rc=FIO_EBADMSG;break};i=i+1}}
135 }
136 if rc==0{
137 let lf:i64=ie_open_regular(r.ledger,0,stat)
138 if lf<0{rc=lf}else{rc=sys_close(lf)}
139 }
140 sys_munmap_direct(stat,NX_STAT_X64_BYTES);sys_munmap_direct(bytes,r.definition_bytes+1)
141 if rc!=0{return ie_fail(r,ID_EP_INCOMPLETE,rc)}
142 r.state=ID_EP_PREPARED_OBSERVED;return r.state
143}
144func ie_snapshot(s:*NxIdEpochSpec)->i64{
145 if s.snapshot_bytes==ID_SIGNED_MAX{return ID_CAPACITY}
146 let stat:*u8=sys_mmap_try(NX_STAT_X64_BYTES);let bytes:*u8=sys_mmap_try(s.snapshot_bytes+1);let hex:*u8=sys_mmap_try(ID_EP_HASH_HEX+1)
147 var rc:i64=ID_RESOURCE
148 if (stat as i64)>0&&(bytes as i64)>0&&(hex as i64)>0{
149 let fd:i64=ie_open_regular(s.snapshot_path,s.snapshot_bytes,stat);rc=fd
150 if fd>=0{
151 rc=ie_read_exact(fd,bytes,s.snapshot_bytes)
152 if rc==0{let extra:i64=sys_read(fd,bytes+s.snapshot_bytes,1);if extra!=0{rc=FIO_EBADMSG}}
153 let closed:i64=sys_close(fd);if rc==0&&closed!=0{rc=closed}
154 if rc==0{rc=ie_hash_hex(bytes,s.snapshot_bytes,hex)}
155 if rc==0{var i:i64=0;while i<ID_EP_HASH_HEX{if hex[i]!=s.snapshot_sha[i]{rc=FIO_EBADMSG;break};i=i+1}}
156 }
157 }
158 if (stat as i64)>0{sys_munmap_direct(stat,NX_STAT_X64_BYTES)}
159 if (bytes as i64)>0{sys_munmap_direct(bytes,s.snapshot_bytes+1)}
160 if (hex as i64)>0{sys_munmap_direct(hex,ID_EP_HASH_HEX+1)};return rc
161}
162func ie_create_file(path:*u8,p:*u8,n:i64)->i64{
163 let fd:i64=__syscall(257,AT_FDCWD,path,193|ID_O_NOFOLLOW|ID_O_CLOEXEC,MODE_0600,0,0)
164 if fd<0{return fd}
165 let result:*NxFileWriteResult=sys_mmap_try(__size_of(NxFileWriteResult)) as *NxFileWriteResult
166 if (result as i64)<=0{sys_close(fd);return ID_RESOURCE}
167 let rc:i64=fio_write_sync_fd(fd,p,n,result);sys_munmap_direct(result as *u8,__size_of(NxFileWriteResult));return rc
168}
169// Explicit prepare retries may reconcile completed bytes after a prior sync failure.
170// Resolve does not claim durability or perform this repair.
171func ie_sync_existing(path:*u8,n:i64)->i64{
172 let stat:*u8=sys_mmap_try(NX_STAT_X64_BYTES);if (stat as i64)<=0{return ID_RESOURCE}
173 let fd:i64=ie_open_regular(path,n,stat);sys_munmap_direct(stat,NX_STAT_X64_BYTES)
174 if fd<0{return fd}
175 var rc:i64=sys_fsync(fd);while rc==FIO_EINTR{rc=sys_fsync(fd)}
176 let closed:i64=sys_close(fd);if rc==0&&closed!=0{rc=closed};return rc
177}
178// Root is a server-owned existing directory, never a caller-selected wire path.
179func id_epoch_prepare(root:*u8,s:*NxIdEpochSpec,r:*NxIdEpochPrepared)->i64{
180 if (r as i64)<=0{return ID_EP_INVALID}
181 if ie_build(root,s,r)!=0{return r.state}
182 let snapshot:i64=ie_snapshot(s);if snapshot!=0{return ie_fail(r,ID_EP_CONFLICT,snapshot)}
183 let made:i64=sys_mkdir(r.directory,ID_EP_PRIVATE_MODE)
184 if made!=0&&made!=FIO_EEXIST{return ie_fail(r,ID_EP_INCOMPLETE,made)}
185 if made==0{r.created=1}
186 let dir:i64=ie_open_directory(r.directory);if dir<0{return ie_fail(r,ID_EP_INCOMPLETE,dir)}
187 let lock:i64=sys_flock(dir,SYS_LOCK_EX|SYS_LOCK_NB)
188 if lock!=0{sys_close(dir);return ie_fail(r,ID_EP_BUSY,lock)}
189 var rc:i64=0
190 if r.created==1{
191 rc=ie_create_file(r.ledger,"",0)
192 if rc==0{rc=ie_create_file(r.manifest,r.definition,r.definition_bytes)}
193 }else{
194 let existing:i64=ie_verify_files(r)
195 if existing!=ID_EP_PREPARED_OBSERVED{rc=FIO_EBADMSG}
196 if rc==0{rc=ie_sync_existing(r.ledger,0)}
197 if rc==0{rc=ie_sync_existing(r.manifest,r.definition_bytes)}
198 }
199 if rc==0{rc=sys_fsync(dir)}
200 if rc==0{let parent:i64=ie_open_directory(root);if parent<0{rc=parent}else{rc=sys_fsync(parent);let closed:i64=sys_close(parent);if rc==0&&closed!=0{rc=closed}}}
201 let unlock:i64=sys_flock(dir,SYS_LOCK_UN);let closed:i64=sys_close(dir)
202 if rc==0&&unlock!=0{rc=unlock};if rc==0&&closed!=0{rc=closed}
203 if rc!=0{return ie_fail(r,ID_EP_INCOMPLETE,rc)}
204 r.state=ID_EP_PREPARED_SYNCED;return r.state
205}
206// Resolving never creates, repairs, activates, or changes the selected namespace.
207func id_epoch_resolve(root:*u8,s:*NxIdEpochSpec,r:*NxIdEpochPrepared)->i64{
208 if (r as i64)<=0{return ID_EP_INVALID}
209 if ie_build(root,s,r)!=0{return r.state}
210 let dir:i64=ie_open_directory(r.directory)
211 if dir<0{if dir==-2{return ie_fail(r,ID_EP_UNKNOWN,dir)};return ie_fail(r,ID_EP_INCOMPLETE,dir)}
212 let lock:i64=sys_flock(dir,SYS_LOCK_SH|SYS_LOCK_NB)
213 if lock!=0{sys_close(dir);return ie_fail(r,ID_EP_BUSY,lock)}
214 let observed:i64=ie_verify_files(r)
215 let unlock:i64=sys_flock(dir,SYS_LOCK_UN);let closed:i64=sys_close(dir)
216 if unlock!=0{return ie_fail(r,ID_EP_INCOMPLETE,unlock)};if closed!=0{return ie_fail(r,ID_EP_INCOMPLETE,closed)}
217 return observed
218}