nx_site_publish_multi_candidate_t322.nx source
↩ module page · 156 lines · 11511 B
1// Versioned multi-dependency composition for the existing site publisher.
2// CLI/job scope, as rm_main: all allocations reclaimed on process exit.
3import "nx_site_publish_lib.nx"
4const RM2_COMMON:i64=12
5const RM2_DEP_WORDS:i64=5
6const RM2_STATE_WORDS:i64=3
7const RM2_SCHEMA:*u8="site-release/v2"
8func rm2_dep_key(k:*u8)->i64{
9 if fsx_seq(k,"source")==1{return 0};if fsx_seq(k,"path")==1{return 1}
10 if fsx_seq(k,"sha256")==1{return 2};if fsx_seq(k,"bytes")==1{return 3};return -1
11}
12func rm2_dep(t:*NxJsonTok,d:*i64)->i64{
13 var count:i64=0;while count<4{
14 let k:*u8=rm_string(t);if (k as i64)<=0{return PB_RC_USAGE};let idx:i64=rm2_dep_key(k);sys_munmap(k,vw_slen(k)+1)
15 if idx<0||d[idx]!=0{return PB_RC_USAGE};if nx_json_next(t)!=NX_JSON_COLON{return PB_RC_USAGE}
16 let val:*u8=rm_string(t);if (val as i64)<=0{return PB_RC_USAGE};d[idx]=val as i64;count=count+1
17 let sep:i64=nx_json_next(t);if sep==NX_JSON_RBRACE{if count==4{return 0};return PB_RC_USAGE};if sep!=NX_JSON_COMMA{return PB_RC_USAGE}
18 };return PB_RC_USAGE
19}
20func rm2_parse(body:*u8,n:i64,v:*i64,state:*i64)->i64{
21 let t:*NxJsonTok=nx_json_new(body,n);if (t as i64)<=0{return FSX_RC_IO}
22 if nx_json_next(t)!=NX_JSON_LBRACE{return PB_RC_USAGE};var count:i64=0;var deps:i64=0;var tail:*i64=0 as *i64
23 while count<9{
24 let k:*u8=rm_string(t);if (k as i64)<=0{return PB_RC_USAGE};let depkey:i64=fsx_seq(k,"dependencies");let idx:i64=rm_key(k);sys_munmap(k,vw_slen(k)+1)
25 if nx_json_next(t)!=NX_JSON_COLON{return PB_RC_USAGE}
26 if depkey==1{
27 if deps!=0{return PB_RC_USAGE};deps=1;if nx_json_next(t)!=NX_JSON_LBRACKET{return PB_RC_USAGE}
28 var more:i64=1;while more==1{
29 if nx_json_next(t)!=NX_JSON_LBRACE{return PB_RC_USAGE}
30 let d:*i64=sys_mmap_try(RM2_DEP_WORDS*8) as *i64;if (d as i64)<=0{return FSX_RC_IO};var j:i64=0;while j<RM2_DEP_WORDS{d[j]=0;j=j+1}
31 let rc:i64=rm2_dep(t,d);if rc!=0{return rc}
32 if (tail as i64)==0{state[0]=d as i64}else{tail[4]=d as i64};tail=d;if state[1]>=PB_I64_MAX/8{return PB_RC_USAGE};state[1]=state[1]+1
33 let sep:i64=nx_json_next(t);if sep==NX_JSON_RBRACKET{more=0}else{if sep!=NX_JSON_COMMA{return PB_RC_USAGE}}
34 }
35 }else{
36 if idx<0||(idx>=7&&idx<=10)||v[idx]!=0{return PB_RC_USAGE}
37 let val:*u8=rm_string(t);if (val as i64)<=0{return PB_RC_USAGE};v[idx]=val as i64
38 }
39 count=count+1;let sep:i64=nx_json_next(t)
40 if sep==NX_JSON_RBRACE{if count!=9||deps!=1{return PB_RC_USAGE};if nx_json_next(t)!=NX_JSON_EOF{return PB_RC_USAGE};if fsx_seq(v[0] as *u8,RM2_SCHEMA)!=1{return PB_RC_USAGE};return 0}
41 if sep!=NX_JSON_COMMA{return PB_RC_USAGE}
42 };return PB_RC_USAGE
43}
44func rm2_view(v:*i64,d:*i64)->*i64{
45 let one:*i64=sys_mmap_try(RM_VALUES_BYTES) as *i64;if (one as i64)<=0{return 0 as *i64}
46 var i:i64=0;while i<RM_FIELDS{one[i]=v[i];i=i+1};one[0]=RM_SCHEMA as i64
47 i=0;while i<4{one[7+i]=d[i];i=i+1};return one
48}
49func rm2_path(site:*u8,rel:*u8)->*u8{
50 let b:*u8=sys_mmap_try(PB_PATH);let p:*i64=sys_mmap_try(8) as *i64;if (b as i64)<=0||(p as i64)<=0{return 0 as *u8}
51 p[0]=0;pb_app(b,p,"sites/");pb_app(b,p,site);pb_app(b,p,"/");pb_app(b,p,rel);sys_munmap(p as *u8,8);return b
52}
53// Observe exact existing bytes without treating an IO failure as absence.
54func rm2_existing(path:*u8,size:*u8,sha:*u8)->i64{
55 let fd:i64=sys_openat_rd(path);if fd==-2{return PR_MISSING};if fd<0{return FSX_RC_IO}
56 if sys_close(fd)!=0{return FSX_RC_IO};let out:*i64=sys_mmap_try(16) as *i64;if (out as i64)<=0{return FSX_RC_IO}
57 let rc:i64=rm_artifact(path,size,sha,out);if rc==0{sys_munmap(out[0] as *u8,out[1]+1)};sys_munmap(out as *u8,16);return rc
58}
59func rm2_live(v:*i64)->i64{
60 let path:*u8=rm2_path(v[1] as *u8,v[3] as *u8);if (path as i64)<=0{return FSX_RC_IO}
61 let same:i64=rm2_existing(path,v[5] as *u8,v[4] as *u8);if same==0{return 0}
62 if same==PR_MISSING{if fsx_seq(v[6] as *u8,"absent")==1{return 0};return PB_RC_CONFLICT}
63 let fd:i64=sys_openat_rd(path);if fd<0{return FSX_RC_IO};let extent:i64=sys_lseek(fd,0,FSX_SEEK_END);let closed:i64=sys_close(fd)
64 if extent<0||extent>=PB_I64_MAX/2||closed!=0{return FSX_RC_IO}
65 let b:*u8=sys_mmap_try(extent+1);let h:*u8=sys_mmap_try(PB_SHA_HEX+1);if (b as i64)<=0||(h as i64)<=0{return FSX_RC_IO}
66 let got:i64=pb_read_bounded(path,b,extent+1);if got!=extent{return FSX_RC_IO};pb_hash(b,got,h)
67 let matched:i64=fsx_seq(h,v[6] as *u8);sys_munmap(b,extent+1);sys_munmap(h,PB_SHA_HEX+1);sys_munmap(path,PB_PATH)
68 if matched==1{return 0};return PB_RC_CONFLICT
69}
70func rm2_preflight(v:*i64,state:*i64,prefix:*u8)->i64{
71 if state[1]<1{return PB_RC_USAGE};var d:*i64=state[0] as *i64
72 while (d as i64)!=0{
73 var prior:*i64=state[0] as *i64;while (prior as i64)!=(d as i64){if fsx_seq(prior[1] as *u8,d[1] as *u8)==1{return PB_RC_CONFLICT};prior=prior[4] as *i64}
74 let one:*i64=rm2_view(v,d);if (one as i64)<=0{return FSX_RC_IO};let rc:i64=rm_preflight(one,prefix);sys_munmap(one as *u8,RM_VALUES_BYTES);if rc!=0{return rc}
75 let observed:i64=pr_observe(prefix,d[1] as *u8,d[2] as *u8)
76 if observed!=PR_OK{if observed!=PR_MISSING||fsx_seq(v[11] as *u8,"allow")!=1{if observed==PR_IO{return FSX_RC_IO};return PB_RC_CONFLICT}}
77 let dst:*u8=rm2_path(v[1] as *u8,d[1] as *u8);if (dst as i64)<=0{return FSX_RC_IO};let existing:i64=rm2_existing(dst,d[3] as *u8,d[2] as *u8);sys_munmap(dst,PB_PATH)
78 if existing!=0&&existing!=PR_MISSING{return existing};d=d[4] as *i64
79 };return rm2_live(v)
80}
81func rm2_phase(receipt:*u8,digest:*u8,kind:*u8,index:i64,rc:i64)->i64{
82 let phase:*u8=sys_mmap_try(PB_PATH);let pos:*i64=sys_mmap_try(8) as *i64;if (phase as i64)<=0||(pos as i64)<=0{return FSX_RC_IO}
83 pos[0]=0;pb_app(phase,pos,kind);pb_app(phase,pos,"-");rm_num(phase,pos,index);let result:i64=rm_finish(receipt,digest,phase,rc)
84 sys_munmap(phase,PB_PATH);sys_munmap(pos as *u8,8);return result
85}
86// Locks remain held for every dependency through the one consumer publish.
87// Nonblocking locks avoid deadlock between overlapping manifest collections.
88func rm2_publish(v:*i64,state:*i64,prefix:*u8)->i64{
89 if state[1]<1||state[1]>PB_I64_MAX/8{return PB_RC_USAGE}
90 let fds:*i64=sys_mmap_try(state[1]*8) as *i64;if (fds as i64)<=0{return FSX_RC_IO}
91 var held:i64=0;var rc:i64=0;var d:*i64=state[0] as *i64
92 while (d as i64)!=0&&rc==0{
93 let path:*u8=rm2_path(v[1] as *u8,d[1] as *u8);let pos:*i64=sys_mmap_try(8) as *i64;if (path as i64)<=0||(pos as i64)<=0{rc=FSX_RC_IO;break}
94 if fsx_write_denied(path)!=0{rc=FSX_RC_DENIED;break};pos[0]=vw_slen(path);pb_app(path,pos,".publish-lock")
95 let fd:i64=sys_openat_wr(path,FSX_MODE_RW);sys_munmap(path,PB_PATH);sys_munmap(pos as *u8,8)
96 if fd<0{rc=FSX_RC_IO;break};if sys_flock(fd,PB_LOCK_EX_NB)!=0{sys_close(fd);rc=PB_RC_BUSY;break};fds[held]=fd;held=held+1;d=d[4] as *i64
97 }
98 d=state[0] as *i64;while (d as i64)!=0&&rc==0{
99 if pr_check(prefix,d[1] as *u8,d[2] as *u8)!=PR_OK{rc=PB_RC_CONFLICT;break}
100 let path:*u8=rm2_path(v[1] as *u8,d[1] as *u8);if (path as i64)<=0{rc=FSX_RC_IO;break};rc=rm2_existing(path,d[3] as *u8,d[2] as *u8);sys_munmap(path,PB_PATH);if rc==PR_MISSING{rc=PB_RC_CONFLICT};d=d[4] as *i64
101 }
102 if rc==0{
103 let args:*i64=sys_mmap_try((PB_GUARDED_SIZED_ARGC+1)*8) as *i64;if (args as i64)<=0{rc=FSX_RC_IO}else{
104 args[0]="nx_site_publish" as *u8 as i64;args[1]="publish-guarded" as *u8 as i64;args[2]=v[2];args[3]=v[1];args[4]=v[3];args[5]=v[4];args[6]=v[6];args[7]=v[5];args[8]=0
105 rc=pb_main_edge(PB_GUARDED_SIZED_ARGC,args,0 as *u8,0 as *u8);sys_munmap(args as *u8,(PB_GUARDED_SIZED_ARGC+1)*8)
106 }
107 }
108 var i:i64=0;while i<held{if sys_close(fds[i])!=0{rc=FSX_RC_IO};i=i+1};sys_munmap(fds as *u8,state[1]*8);return rc
109}
110func rm2_execute(v:*i64,state:*i64,prefix:*u8,receipt:*u8,digest:*u8)->i64{
111 var rc:i64=rm2_preflight(v,state,prefix);rc=rm_finish(receipt,digest,"preflight-all",rc);if rc!=0{return rc}
112 var d:*i64=state[0] as *i64;var index:i64=0
113 while (d as i64)!=0{
114 if fsx_seq(v[11] as *u8,"allow")==1{rc=pr_register(prefix,d[1] as *u8,d[2] as *u8);if rc==PR_CREATED{rc=0}}else{rc=pr_check(prefix,d[1] as *u8,d[2] as *u8)}
115 rc=rm2_phase(receipt,digest,"registration",index,rc);if rc!=0{return rc};d=d[4] as *i64;index=index+1
116 }
117 d=state[0] as *i64;index=0;while (d as i64)!=0{
118 let args:*i64=sys_mmap_try((PB_SIZED_ARGC+1)*8) as *i64;if (args as i64)<=0{return FSX_RC_IO}
119 args[0]="nx_site_publish" as *u8 as i64;args[1]="stage-sized" as *u8 as i64;args[2]=d[0];args[3]=v[1];args[4]=d[1];args[5]=d[2];args[6]=d[3];args[7]=0
120 rc=pb_main_edge(PB_SIZED_ARGC,args,0 as *u8,0 as *u8);sys_munmap(args as *u8,(PB_SIZED_ARGC+1)*8)
121 rc=rm2_phase(receipt,digest,"stage",index,rc);if rc!=0{return rc};d=d[4] as *i64;index=index+1
122 }
123 rc=rm2_publish(v,state,prefix);rc=rm_finish(receipt,digest,"verify-all-publish-once",rc);if rc!=0{return rc}
124 return rm_receipt(receipt,digest,"delivery","published-served-review-pending",0)
125}
126// Size comes from the actual regular file; exact read and caller digest detect extent/content races.
127func rm2_load(path:*u8,out:*i64)->i64{
128 out[0]=0;out[1]=0;let fd:i64=sys_openat_rd(path);if fd<0{return FSX_RC_IO}
129 let n:i64=sys_lseek(fd,0,FSX_SEEK_END);let closed:i64=sys_close(fd)
130 if n<=0||n>=PB_I64_MAX||closed!=0{return FSX_RC_IO}
131 let b:*u8=sys_mmap_try(n+1);if (b as i64)<=0{return FSX_RC_IO}
132 let got:i64=pb_read_bounded(path,b,n+1);if got!=n{sys_munmap(b,n+1);return PB_RC_CONFLICT}
133 out[0]=b as i64;out[1]=n;return 0
134}
135func rm2_main(argc:i64,argv:*i64)->i64{
136 if argc!=4||pr_hash_ok(argv[3] as *u8)!=1{return PB_RC_USAGE}
137 let loaded:*i64=sys_mmap_try(16) as *i64;if (loaded as i64)<=0{return FSX_RC_IO};let readrc:i64=rm2_load(argv[2] as *u8,loaded);if readrc!=0{return readrc};let body:*u8=loaded[0] as *u8;let n:i64=loaded[1];let digest:*u8=sys_mmap_try(PB_SHA_HEX+1);let v:*i64=sys_mmap_try(RM_VALUES_BYTES) as *i64;let state:*i64=sys_mmap_try(RM2_STATE_WORDS*8) as *i64;let prefix:*u8=sys_mmap_try(PB_PATH)
138 if (body as i64)<=0||(digest as i64)<=0||(v as i64)<=0||(state as i64)<=0||(prefix as i64)<=0{return FSX_RC_IO}
139 var i:i64=0;while i<RM_FIELDS{v[i]=0;i=i+1};i=0;while i<RM2_STATE_WORDS{state[i]=0;i=i+1}
140 pb_hash(body,n,digest);if fsx_seq(digest,argv[3] as *u8)!=1{return PB_RC_CONFLICT}
141 // Preserve the v1 parser and executor unchanged.
142 if rm_parse(body,n,v)==0{if fsx_seq(v[0] as *u8,RM_SCHEMA)==1{if fsx_seq(argv[1] as *u8,"prepare-release")==1{return rm_prepare_main(argc,argv)};if fsx_seq(argv[1] as *u8,"release-manifest")==1{return rm_main(argc,argv)};return PB_RC_USAGE}}
143 i=0;while i<RM_FIELDS{v[i]=0;i=i+1};let parsed:i64=rm2_parse(body,n,v,state);if parsed!=0{return parsed}
144 if fsx_seq(argv[1] as *u8,"prepare-release")==1{
145 let rc:i64=rm2_preflight(v,state,prefix);fsx_puts("NX-RELEASE-V2 PREPARE dependencies=");fsx_putn(state[1]);fsx_puts(" rc=");fsx_putn(rc);fsx_puts(" registration_writes=0 publication_writes=0\n");return rc
146 }
147 if fsx_seq(argv[1] as *u8,"release-manifest")!=1{return PB_RC_USAGE}
148 let receipt:*u8=sys_mmap_try(PB_PATH);let pos:*i64=sys_mmap_try(8) as *i64;if (receipt as i64)<=0||(pos as i64)<=0{return FSX_RC_IO}
149 pos[0]=0;pb_app(receipt,pos,RM_RECEIPT_ROOT);pb_app(receipt,pos,"site-release-");pb_app(receipt,pos,digest);pb_app(receipt,pos,".jsonl")
150 fsx_puts("NX-RELEASE-V2 receipt=");fsx_puts(receipt);fsx_puts(" dependencies=");fsx_putn(state[1]);fsx_puts(" consumer_publishes_max=1\n")
151 let bank:i64=rm_bank(body,n,digest);if bank!=0{return bank};return rm2_execute(v,state,prefix,receipt,digest)
152}
153func main(argc:i64,argv:*i64)->i64{
154 if argc>1{if fsx_seq(argv[1] as *u8,"prepare-release")==1||fsx_seq(argv[1] as *u8,"release-manifest")==1{return rm2_main(argc,argv)}}
155 return sp_main(argc,argv)
156}