code wiki / (root) / nx_apistack_idempotency_epoch_candidate_t293.nx

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}