@synopackageland/cli 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (578) hide show
  1. package/CHANGELOG.md +613 -0
  2. package/README.md +81 -89
  3. package/dist/add.js +39 -16
  4. package/dist/build-spk/delegate-scope.d.ts +6 -0
  5. package/dist/build-spk/delegate-scope.js +34 -0
  6. package/dist/build-spk/dsm-arch.d.ts +7 -1
  7. package/dist/build-spk/dsm-arch.js +24 -28
  8. package/dist/build-spk/dsm-platforms.json +39 -0
  9. package/dist/build-spk/dsm-version.d.ts +11 -0
  10. package/dist/build-spk/dsm-version.js +17 -0
  11. package/dist/build-spk/gateway.d.ts +1 -1
  12. package/dist/build-spk/gateway.js +2 -2
  13. package/dist/build-spk/generator.js +30 -5
  14. package/dist/build-spk/notices.js +4 -1
  15. package/dist/build-spk/resource-worker.js +3 -0
  16. package/dist/build-spk/wizard-messages.d.ts +66 -0
  17. package/dist/build-spk/wizard-messages.js +75 -9
  18. package/dist/cli.js +15 -7
  19. package/dist/contract/schema-validate.js +8 -0
  20. package/dist/contract/validate.js +30 -0
  21. package/dist/deploy/deploy.d.ts +1 -1
  22. package/dist/deploy/deploy.js +8 -0
  23. package/dist/deploy/plan.js +1 -1
  24. package/dist/deploy/spk-version.js +1 -1
  25. package/dist/error-registry.js +43 -15
  26. package/dist/host/dsm-client.d.ts +8 -4
  27. package/dist/host/dsm-client.js +10 -4
  28. package/dist/host/types.d.ts +2 -1
  29. package/dist/info.js +18 -2
  30. package/dist/init.d.ts +3 -0
  31. package/dist/init.js +49 -18
  32. package/dist/package-metadata.d.ts +21 -0
  33. package/dist/package-metadata.js +101 -0
  34. package/dist/portal/alias.d.ts +25 -0
  35. package/dist/portal/alias.js +48 -0
  36. package/dist/portal/package.d.ts +33 -0
  37. package/dist/portal/package.js +116 -0
  38. package/dist/runtime/delegate-contract.d.ts +79 -0
  39. package/dist/runtime/delegate-contract.js +27 -0
  40. package/dist/runtime/executor-build.d.ts +1 -1
  41. package/dist/runtime/executor-build.js +5 -4
  42. package/dist/runtime/executor-inspect.d.ts +3 -0
  43. package/dist/runtime/executor-inspect.js +11 -5
  44. package/dist/runtime/executor-policy.d.ts +12 -0
  45. package/dist/runtime/executor-policy.js +43 -3
  46. package/dist/runtime/executor-scope-task.d.ts +4 -0
  47. package/dist/runtime/executor-scope-task.js +21 -0
  48. package/dist/runtime/executor-tasks.d.ts +2 -1
  49. package/dist/runtime/executor-tasks.js +4 -4
  50. package/dist/skill.js +1 -0
  51. package/dist/standalone/api-permissions.d.ts +10 -0
  52. package/dist/standalone/api-permissions.js +19 -2
  53. package/dist/standalone/background.d.ts +16 -0
  54. package/dist/standalone/background.js +69 -2
  55. package/dist/standalone/binary.d.ts +1 -1
  56. package/dist/standalone/binary.js +22 -8
  57. package/dist/standalone/build.d.ts +1 -1
  58. package/dist/standalone/build.js +38 -8
  59. package/dist/standalone/daemon.d.ts +1 -1
  60. package/dist/standalone/daemon.js +2 -1
  61. package/dist/standalone/dsm-window.d.ts +3 -1
  62. package/dist/standalone/dsm-window.js +72 -15
  63. package/dist/standalone/inspect-spk.d.ts +8 -0
  64. package/dist/standalone/inspect-spk.js +105 -26
  65. package/dist/standalone/lifecycle.js +86 -8
  66. package/dist/standalone/manifest.d.ts +13 -0
  67. package/dist/standalone/manifest.js +57 -8
  68. package/dist/standalone/native-authorization-window.d.ts +1 -1
  69. package/dist/standalone/native-authorization-window.js +2 -0
  70. package/dist/standalone/native-layout.d.ts +3 -0
  71. package/dist/standalone/native-layout.js +37 -5
  72. package/dist/standalone/portal-window.d.ts +21 -0
  73. package/dist/standalone/portal-window.js +155 -0
  74. package/dist/standalone/portal.d.ts +4 -0
  75. package/dist/standalone/portal.js +4 -0
  76. package/dist/standalone/resource.d.ts +5 -3
  77. package/dist/standalone/resource.js +6 -3
  78. package/dist/standalone/root-window.js +62 -15
  79. package/dist/standalone/spk.d.ts +1 -1
  80. package/dist/standalone/spk.js +25 -7
  81. package/dist/templates/_daemon/main_test.go +61 -8
  82. package/dist/templates/_executor/README.md +127 -16
  83. package/dist/templates/_executor/anchor.go +146 -0
  84. package/dist/templates/_executor/anchor_test.go +126 -0
  85. package/dist/templates/_executor/apppriv_review_test.go +1 -1
  86. package/dist/templates/_executor/cancel_contract_test.go +236 -0
  87. package/dist/templates/_executor/copy.go +64 -0
  88. package/dist/templates/_executor/copy_test.go +88 -0
  89. package/dist/templates/_executor/dsm.go +23 -10
  90. package/dist/templates/_executor/dsm_test.go +95 -1
  91. package/dist/templates/_executor/executor.go +260 -49
  92. package/dist/templates/_executor/executor_test.go +36 -6
  93. package/dist/templates/_executor/identity.go +27 -1
  94. package/dist/templates/_executor/identity_kernel_linux_test.go +60 -0
  95. package/dist/templates/_executor/identity_linux.go +82 -0
  96. package/dist/templates/_executor/identity_security_test.go +43 -0
  97. package/dist/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
  98. package/dist/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
  99. package/dist/templates/_executor/internal/taskscript/boot.sh +9 -9
  100. package/dist/templates/_executor/internal/taskscript/cmd/main.go +2 -2
  101. package/dist/templates/_executor/internal/taskscript/control.sh +15 -10
  102. package/dist/templates/_executor/internal/taskscript/tasks.go +5 -3
  103. package/dist/templates/_executor/lifecycle_test.go +11 -0
  104. package/dist/templates/_executor/link_linux.go +28 -0
  105. package/dist/templates/_executor/long_running_test.go +530 -0
  106. package/dist/templates/_executor/main.go +72 -20
  107. package/dist/templates/_executor/move_linux.go +490 -0
  108. package/dist/templates/_executor/move_linux_test.go +460 -0
  109. package/dist/templates/_executor/move_validation.go +46 -0
  110. package/dist/templates/_executor/move_validation_test.go +187 -0
  111. package/dist/templates/_executor/operations.go +926 -0
  112. package/dist/templates/_executor/platform_linux.go +24 -173
  113. package/dist/templates/_executor/platform_linux_test.go +9 -65
  114. package/dist/templates/_executor/platform_other.go +30 -4
  115. package/dist/templates/_executor/process.go +3 -0
  116. package/dist/templates/_executor/records.go +242 -0
  117. package/dist/templates/_executor/scope_cancel_contract_test.go +91 -0
  118. package/dist/templates/_executor/scope_client.go +37 -0
  119. package/dist/templates/_executor/scope_contract.go +12 -0
  120. package/dist/templates/_executor/selected_scope.go +146 -0
  121. package/dist/templates/_executor/selected_scope_test.go +186 -0
  122. package/dist/templates/_executor/status_disabled_test.go +33 -0
  123. package/dist/templates/_executor/store.go +4 -3
  124. package/dist/templates/_executor/supervise.go +109 -0
  125. package/dist/templates/_executor/supervise_test.go +130 -0
  126. package/dist/templates/_executor/task_race_review_test.go +1 -1
  127. package/dist/templates/_executor/tasks.go +7 -8
  128. package/dist/templates/_executor/tasks_review_test.go +1 -1
  129. package/dist/templates/_executor/tasks_test.go +4 -4
  130. package/dist/templates/_executor/watch.go +8 -0
  131. package/dist/templates/_executor/watch_test.go +23 -2
  132. package/dist/templates/_executor/write_review_linux_test.go +4 -12
  133. package/dist/templates/_shared/AGENTS.md +129 -0
  134. package/dist/templates/binary/dsm-package.yaml +24 -0
  135. package/dist/templates/binary/i18n/messages/de.json +4 -0
  136. package/dist/templates/binary/i18n/messages/en.json +4 -0
  137. package/dist/templates/binary/i18n/messages/es.json +4 -0
  138. package/dist/templates/binary/i18n/messages/fr.json +4 -0
  139. package/dist/templates/binary/i18n/messages/it.json +4 -0
  140. package/dist/templates/binary/i18n/messages/ja.json +4 -0
  141. package/dist/templates/binary/i18n/messages/ko.json +4 -0
  142. package/dist/templates/binary/i18n/messages/zh-CN.json +4 -0
  143. package/dist/templates/binary/i18n/messages/zh-TW.json +4 -0
  144. package/dist/templates/binary/ui/app.js +19 -4
  145. package/dist/templates/native-authorization/AuthorizationGate.vue +5 -3
  146. package/dist/templates/native-authorization/i18n/de.json +1 -0
  147. package/dist/templates/native-authorization/i18n/en.json +1 -0
  148. package/dist/templates/native-authorization/i18n/es.json +2 -1
  149. package/dist/templates/native-authorization/i18n/fr.json +1 -0
  150. package/dist/templates/native-authorization/i18n/it.json +11 -10
  151. package/dist/templates/native-authorization/i18n/ja.json +1 -0
  152. package/dist/templates/native-authorization/i18n/ko.json +10 -9
  153. package/dist/templates/native-authorization/i18n/zh-CN.json +6 -5
  154. package/dist/templates/native-authorization/i18n/zh-TW.json +6 -5
  155. package/dist/templates/native-go/README.md +43 -10
  156. package/dist/templates/native-go/backend/cmd/app/main.go +27 -7
  157. package/dist/templates/native-go/backend/internal/api/entry_test.go +47 -0
  158. package/dist/templates/native-go/backend/internal/api/permissions_test.go +3 -3
  159. package/dist/templates/native-go/backend/internal/api/registry.go +11 -0
  160. package/dist/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
  161. package/dist/templates/native-go/backend/internal/api/server.go +18 -5
  162. package/dist/templates/native-go/backend/internal/app/delegate_config.go +57 -0
  163. package/dist/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
  164. package/dist/templates/native-go/backend/internal/app/portal.go +30 -0
  165. package/dist/templates/native-go/backend/internal/app/portal_test.go +55 -0
  166. package/dist/templates/native-go/backend/internal/app/routes.go +8 -2
  167. package/dist/templates/native-go/backend/internal/background/runner.go +1 -1
  168. package/dist/templates/native-go/backend/internal/background/store.go +16 -14
  169. package/dist/templates/native-go/backend/internal/dsm/admin.go +8 -2
  170. package/dist/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
  171. package/dist/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
  172. package/dist/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
  173. package/dist/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
  174. package/dist/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
  175. package/dist/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
  176. package/dist/templates/native-go/backend/internal/dsm/entry.go +177 -0
  177. package/dist/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
  178. package/dist/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
  179. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
  180. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
  181. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
  182. package/dist/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
  183. package/dist/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
  184. package/dist/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
  185. package/dist/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
  186. package/dist/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
  187. package/dist/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
  188. package/dist/templates/native-go/backend/internal/filestation/service.go +1 -1
  189. package/dist/templates/native-go/backend/internal/portal/portal.go +138 -0
  190. package/dist/templates/native-go/backend/internal/portal/portal_test.go +89 -0
  191. package/dist/templates/native-go/dsm-package.yaml +20 -1
  192. package/dist/templates/native-go/i18n/glossary.json +2 -1
  193. package/dist/templates/native-go/i18n/messages/de.json +1 -0
  194. package/dist/templates/native-go/i18n/messages/en.json +1 -0
  195. package/dist/templates/native-go/i18n/messages/es.json +1 -0
  196. package/dist/templates/native-go/i18n/messages/fr.json +1 -0
  197. package/dist/templates/native-go/i18n/messages/it.json +1 -0
  198. package/dist/templates/native-go/i18n/messages/ja.json +1 -0
  199. package/dist/templates/native-go/i18n/messages/ko.json +1 -0
  200. package/dist/templates/native-go/i18n/messages/zh-CN.json +1 -0
  201. package/dist/templates/native-go/i18n/messages/zh-TW.json +1 -0
  202. package/dist/templates/native-go/ui/app.css +10 -0
  203. package/dist/templates/native-go/ui/app.js +11 -1
  204. package/dist/templates/native-go-delegate/DELEGATE.md +126 -5
  205. package/dist/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
  206. package/dist/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
  207. package/dist/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
  208. package/dist/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
  209. package/dist/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
  210. package/dist/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
  211. package/dist/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
  212. package/dist/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
  213. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
  214. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
  215. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
  216. package/dist/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
  217. package/dist/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
  218. package/dist/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
  219. package/dist/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
  220. package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
  221. package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
  222. package/dist/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
  223. package/dist/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
  224. package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
  225. package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
  226. package/dist/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
  227. package/dist/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
  228. package/dist/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
  229. package/dist/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
  230. package/dist/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
  231. package/dist/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
  232. package/dist/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
  233. package/dist/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
  234. package/dist/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
  235. package/dist/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
  236. package/dist/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
  237. package/dist/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
  238. package/dist/templates/native-go-delegate/test.sh +1 -0
  239. package/dist/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
  240. package/dist/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
  241. package/dist/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
  242. package/dist/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
  243. package/dist/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
  244. package/dist/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
  245. package/dist/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
  246. package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
  247. package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
  248. package/dist/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
  249. package/dist/templates/native-go-vue/README.md +4 -0
  250. package/dist/templates/native-go-vue/frontend/package-lock.json +4 -4
  251. package/dist/templates/native-go-vue/frontend/package.json +1 -1
  252. package/dist/templates/native-go-vue/frontend/src/App.vue +18 -13
  253. package/dist/templates/native-go-vue/frontend/src/context.ts +31 -0
  254. package/dist/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +52 -8
  255. package/dist/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
  256. package/dist/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
  257. package/dist/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
  258. package/dist/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +4 -2
  259. package/dist/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
  260. package/dist/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
  261. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/de.json +46 -0
  262. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
  263. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
  264. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
  265. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
  266. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
  267. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -0
  268. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
  269. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
  270. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
  271. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
  272. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
  273. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
  274. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
  275. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
  276. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
  277. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +46 -0
  278. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -5
  279. package/dist/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
  280. package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
  281. package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
  282. package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
  283. package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
  284. package/dist/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
  285. package/dist/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
  286. package/dist/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
  287. package/dist/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
  288. package/dist/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
  289. package/dist/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
  290. package/dist/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
  291. package/dist/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
  292. package/dist/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
  293. package/dist/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
  294. package/dist/templates/native-go-vue/frontend/src/i18n.ts +13 -0
  295. package/dist/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
  296. package/dist/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
  297. package/dist/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
  298. package/dist/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
  299. package/dist/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
  300. package/dist/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
  301. package/dist/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
  302. package/dist/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
  303. package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
  304. package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
  305. package/dist/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
  306. package/dist/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
  307. package/dist/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
  308. package/dist/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
  309. package/dist/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
  310. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
  311. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
  312. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
  313. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
  314. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
  315. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
  316. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
  317. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
  318. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
  319. package/dist/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
  320. package/dist/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
  321. package/dist/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
  322. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
  323. package/dist/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
  324. package/dist/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
  325. package/package.json +5 -1
  326. package/schema/package-metadata.schema.json +101 -0
  327. package/schema/synology-app.schema.json +112 -0
  328. package/skills/SKILL.md +106 -4
  329. package/skills/reference/create.md +101 -2
  330. package/skills/reference/deploy.md +2 -2
  331. package/skills/reference/files.md +113 -2
  332. package/skills/reference/identity.md +70 -1
  333. package/skills/reference/portal.md +17 -0
  334. package/skills/reference/ui-vue.md +12 -0
  335. package/templates/_daemon/main_test.go +61 -8
  336. package/templates/_executor/README.md +127 -16
  337. package/templates/_executor/anchor.go +146 -0
  338. package/templates/_executor/anchor_test.go +126 -0
  339. package/templates/_executor/apppriv_review_test.go +1 -1
  340. package/templates/_executor/cancel_contract_test.go +236 -0
  341. package/templates/_executor/copy.go +64 -0
  342. package/templates/_executor/copy_test.go +88 -0
  343. package/templates/_executor/dsm.go +23 -10
  344. package/templates/_executor/dsm_test.go +95 -1
  345. package/templates/_executor/executor.go +260 -49
  346. package/templates/_executor/executor_test.go +36 -6
  347. package/templates/_executor/identity.go +27 -1
  348. package/templates/_executor/identity_kernel_linux_test.go +60 -0
  349. package/templates/_executor/identity_linux.go +82 -0
  350. package/templates/_executor/identity_security_test.go +43 -0
  351. package/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
  352. package/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
  353. package/templates/_executor/internal/taskscript/boot.sh +9 -9
  354. package/templates/_executor/internal/taskscript/cmd/main.go +2 -2
  355. package/templates/_executor/internal/taskscript/control.sh +15 -10
  356. package/templates/_executor/internal/taskscript/tasks.go +5 -3
  357. package/templates/_executor/lifecycle_test.go +11 -0
  358. package/templates/_executor/link_linux.go +28 -0
  359. package/templates/_executor/long_running_test.go +530 -0
  360. package/templates/_executor/main.go +72 -20
  361. package/templates/_executor/move_linux.go +490 -0
  362. package/templates/_executor/move_linux_test.go +460 -0
  363. package/templates/_executor/move_validation.go +46 -0
  364. package/templates/_executor/move_validation_test.go +187 -0
  365. package/templates/_executor/operations.go +926 -0
  366. package/templates/_executor/platform_linux.go +24 -173
  367. package/templates/_executor/platform_linux_test.go +9 -65
  368. package/templates/_executor/platform_other.go +30 -4
  369. package/templates/_executor/process.go +3 -0
  370. package/templates/_executor/records.go +242 -0
  371. package/templates/_executor/scope_cancel_contract_test.go +91 -0
  372. package/templates/_executor/scope_client.go +37 -0
  373. package/templates/_executor/scope_contract.go +12 -0
  374. package/templates/_executor/selected_scope.go +146 -0
  375. package/templates/_executor/selected_scope_test.go +186 -0
  376. package/templates/_executor/status_disabled_test.go +33 -0
  377. package/templates/_executor/store.go +4 -3
  378. package/templates/_executor/supervise.go +109 -0
  379. package/templates/_executor/supervise_test.go +130 -0
  380. package/templates/_executor/task_race_review_test.go +1 -1
  381. package/templates/_executor/tasks.go +7 -8
  382. package/templates/_executor/tasks_review_test.go +1 -1
  383. package/templates/_executor/tasks_test.go +4 -4
  384. package/templates/_executor/watch.go +8 -0
  385. package/templates/_executor/watch_test.go +23 -2
  386. package/templates/_executor/write_review_linux_test.go +4 -12
  387. package/templates/_shared/AGENTS.md +129 -0
  388. package/templates/binary/dsm-package.yaml +24 -0
  389. package/templates/binary/i18n/messages/de.json +4 -0
  390. package/templates/binary/i18n/messages/en.json +4 -0
  391. package/templates/binary/i18n/messages/es.json +4 -0
  392. package/templates/binary/i18n/messages/fr.json +4 -0
  393. package/templates/binary/i18n/messages/it.json +4 -0
  394. package/templates/binary/i18n/messages/ja.json +4 -0
  395. package/templates/binary/i18n/messages/ko.json +4 -0
  396. package/templates/binary/i18n/messages/zh-CN.json +4 -0
  397. package/templates/binary/i18n/messages/zh-TW.json +4 -0
  398. package/templates/binary/ui/app.js +19 -4
  399. package/templates/native-authorization/AuthorizationGate.vue +5 -3
  400. package/templates/native-authorization/i18n/de.json +1 -0
  401. package/templates/native-authorization/i18n/en.json +1 -0
  402. package/templates/native-authorization/i18n/es.json +2 -1
  403. package/templates/native-authorization/i18n/fr.json +1 -0
  404. package/templates/native-authorization/i18n/it.json +11 -10
  405. package/templates/native-authorization/i18n/ja.json +1 -0
  406. package/templates/native-authorization/i18n/ko.json +10 -9
  407. package/templates/native-authorization/i18n/zh-CN.json +6 -5
  408. package/templates/native-authorization/i18n/zh-TW.json +6 -5
  409. package/templates/native-go/README.md +43 -10
  410. package/templates/native-go/backend/cmd/app/main.go +27 -7
  411. package/templates/native-go/backend/internal/api/entry_test.go +47 -0
  412. package/templates/native-go/backend/internal/api/permissions_test.go +3 -3
  413. package/templates/native-go/backend/internal/api/registry.go +11 -0
  414. package/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
  415. package/templates/native-go/backend/internal/api/server.go +18 -5
  416. package/templates/native-go/backend/internal/app/delegate_config.go +57 -0
  417. package/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
  418. package/templates/native-go/backend/internal/app/portal.go +30 -0
  419. package/templates/native-go/backend/internal/app/portal_test.go +55 -0
  420. package/templates/native-go/backend/internal/app/routes.go +8 -2
  421. package/templates/native-go/backend/internal/background/runner.go +1 -1
  422. package/templates/native-go/backend/internal/background/store.go +16 -14
  423. package/templates/native-go/backend/internal/dsm/admin.go +8 -2
  424. package/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
  425. package/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
  426. package/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
  427. package/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
  428. package/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
  429. package/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
  430. package/templates/native-go/backend/internal/dsm/entry.go +177 -0
  431. package/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
  432. package/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
  433. package/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
  434. package/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
  435. package/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
  436. package/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
  437. package/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
  438. package/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
  439. package/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
  440. package/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
  441. package/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
  442. package/templates/native-go/backend/internal/filestation/service.go +1 -1
  443. package/templates/native-go/backend/internal/portal/portal.go +138 -0
  444. package/templates/native-go/backend/internal/portal/portal_test.go +89 -0
  445. package/templates/native-go/dsm-package.yaml +20 -1
  446. package/templates/native-go/i18n/glossary.json +2 -1
  447. package/templates/native-go/i18n/messages/de.json +1 -0
  448. package/templates/native-go/i18n/messages/en.json +1 -0
  449. package/templates/native-go/i18n/messages/es.json +1 -0
  450. package/templates/native-go/i18n/messages/fr.json +1 -0
  451. package/templates/native-go/i18n/messages/it.json +1 -0
  452. package/templates/native-go/i18n/messages/ja.json +1 -0
  453. package/templates/native-go/i18n/messages/ko.json +1 -0
  454. package/templates/native-go/i18n/messages/zh-CN.json +1 -0
  455. package/templates/native-go/i18n/messages/zh-TW.json +1 -0
  456. package/templates/native-go/ui/app.css +10 -0
  457. package/templates/native-go/ui/app.js +11 -1
  458. package/templates/native-go-delegate/DELEGATE.md +126 -5
  459. package/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
  460. package/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
  461. package/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
  462. package/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
  463. package/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
  464. package/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
  465. package/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
  466. package/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
  467. package/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
  468. package/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
  469. package/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
  470. package/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
  471. package/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
  472. package/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
  473. package/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
  474. package/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
  475. package/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
  476. package/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
  477. package/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
  478. package/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
  479. package/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
  480. package/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
  481. package/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
  482. package/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
  483. package/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
  484. package/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
  485. package/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
  486. package/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
  487. package/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
  488. package/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
  489. package/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
  490. package/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
  491. package/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
  492. package/templates/native-go-delegate/test.sh +1 -0
  493. package/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
  494. package/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
  495. package/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
  496. package/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
  497. package/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
  498. package/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
  499. package/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
  500. package/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
  501. package/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
  502. package/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
  503. package/templates/native-go-vue/README.md +4 -0
  504. package/templates/native-go-vue/frontend/package-lock.json +4 -4
  505. package/templates/native-go-vue/frontend/package.json +1 -1
  506. package/templates/native-go-vue/frontend/src/App.vue +18 -13
  507. package/templates/native-go-vue/frontend/src/context.ts +31 -0
  508. package/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +52 -8
  509. package/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
  510. package/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
  511. package/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
  512. package/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +4 -2
  513. package/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
  514. package/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
  515. package/templates/native-go-vue/frontend/src/delegate/i18n/de.json +46 -0
  516. package/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
  517. package/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
  518. package/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
  519. package/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
  520. package/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
  521. package/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -0
  522. package/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
  523. package/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
  524. package/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
  525. package/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
  526. package/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
  527. package/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
  528. package/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
  529. package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
  530. package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
  531. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +46 -0
  532. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -5
  533. package/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
  534. package/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
  535. package/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
  536. package/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
  537. package/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
  538. package/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
  539. package/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
  540. package/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
  541. package/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
  542. package/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
  543. package/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
  544. package/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
  545. package/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
  546. package/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
  547. package/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
  548. package/templates/native-go-vue/frontend/src/i18n.ts +13 -0
  549. package/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
  550. package/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
  551. package/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
  552. package/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
  553. package/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
  554. package/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
  555. package/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
  556. package/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
  557. package/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
  558. package/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
  559. package/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
  560. package/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
  561. package/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
  562. package/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
  563. package/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
  564. package/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
  565. package/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
  566. package/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
  567. package/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
  568. package/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
  569. package/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
  570. package/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
  571. package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
  572. package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
  573. package/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
  574. package/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
  575. package/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
  576. package/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
  577. package/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
  578. package/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
@@ -9,7 +9,6 @@ import (
9
9
  "log"
10
10
  "net"
11
11
  "os"
12
- "os/exec"
13
12
  "os/signal"
14
13
  "os/user"
15
14
  "path/filepath"
@@ -26,20 +25,23 @@ const socketBase = "/run/synopkgland"
26
25
  func main() {
27
26
  if err := run(os.Args[1:]); err != nil {
28
27
  fmt.Fprintln(os.Stderr, "executor:", err)
29
- os.Exit(1)
28
+ os.Exit(exitStatus(err))
30
29
  }
31
30
  }
32
31
  func run(args []string) error {
33
32
  if len(args) == 1 {
34
- if args[0] == "write-child" {
35
- return writeChild()
33
+ if args[0] == "move-child" {
34
+ return runMoveChild()
36
35
  }
37
- if args[0] == "cleanup-child" {
38
- return runWriteChild(true)
36
+ if args[0] == "part-cleanup-child" {
37
+ return runPartCleanupChild()
38
+ }
39
+ if args[0] == "source-cleanup-child" {
40
+ return runSourceCleanupChild()
39
41
  }
40
42
  }
41
43
  if len(args) < 1 {
42
- return errors.New("usage: executor serve --package <pkg> | control start|stop|purge --package <pkg>")
44
+ return errors.New("usage: executor serve|supervise --package <pkg> | control start|stop|purge --package <pkg>")
43
45
  }
44
46
  mode := args[0]
45
47
  args = args[1:]
@@ -53,14 +55,21 @@ func run(args []string) error {
53
55
  if !contains([]string{"start", "stop", "purge"}, action) {
54
56
  return errors.New("invalid control action")
55
57
  }
56
- } else if mode != "serve" {
58
+ } else if mode != "serve" && mode != "supervise" && mode != "select-scope" {
57
59
  return errors.New("invalid command")
58
60
  }
59
61
  fs := flag.NewFlagSet("executor", flag.ContinueOnError)
60
62
  pkg := fs.String("package", "", "package account")
63
+ scope := fs.String("scope", "", "administrator-confirmed scope")
64
+ actor := fs.String("actor", "", "password-confirmed administrator")
65
+ expectedGeneration := fs.Uint64("expected-generation", maxScopeGeneration+1, "protected generation observed before confirmation")
66
+ revived := fs.Bool("revived", false, "restarted by the root supervisor")
61
67
  if err := fs.Parse(args); err != nil {
62
68
  return err
63
69
  }
70
+ if *revived && mode != "serve" {
71
+ return errors.New("unexpected revive")
72
+ }
64
73
  if fs.NArg() != 0 || !tokenRE.MatchString(*pkg) {
65
74
  return errors.New("invalid package")
66
75
  }
@@ -82,6 +91,15 @@ func run(args []string) error {
82
91
  if err != nil {
83
92
  return err
84
93
  }
94
+ if mode == "select-scope" {
95
+ if e.Policy.Scope != installSelectedScope || !validSelectedScope(*scope) || *actor == "" || *expectedGeneration > maxScopeGeneration {
96
+ return errors.New("scope selection")
97
+ }
98
+ return rootSelectScope(*pkg, *scope, *actor, *expectedGeneration)
99
+ }
100
+ if *scope != "" || *actor != "" || *expectedGeneration != maxScopeGeneration+1 {
101
+ return errors.New("unexpected selection arguments")
102
+ }
85
103
  if mode == "control" {
86
104
  // If the validated service already owns its pidfile, start is idempotent.
87
105
  f, err := os.OpenFile(filepath.Join(e.Store.Dir, "executor.pid"), os.O_RDWR|os.O_CREATE, 0600)
@@ -104,9 +122,7 @@ func run(args []string) error {
104
122
  return closeErr
105
123
  }
106
124
  }
107
- cmd := exec.Command(exe, "serve", "--package", *pkg)
108
- cmd.Env = []string{"PATH=/usr/bin:/bin", "LANG=C"}
109
- cmd.Dir = "/"
125
+ cmd := supervisorCommand(exe, *pkg)
110
126
  log, err := os.OpenFile(filepath.Join(e.Store.Dir, "executor.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
111
127
  if err != nil {
112
128
  return err
@@ -114,13 +130,15 @@ func run(args []string) error {
114
130
  defer log.Close()
115
131
  cmd.Stdout = log
116
132
  cmd.Stderr = log
117
- cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
118
133
  if err = cmd.Start(); err != nil {
119
134
  return err
120
135
  }
121
136
  return cmd.Process.Release()
122
137
  }
123
- return serve(e, uid, gid)
138
+ if mode == "supervise" {
139
+ return runSupervisor(exe, *pkg)
140
+ }
141
+ return serve(e, uid, gid, *revived)
124
142
  }
125
143
 
126
144
  // Never chmod or chown an existing untrusted directory into trust. Check all
@@ -215,23 +233,42 @@ func load(pkg, exe string) (*Executor, int, int, error) {
215
233
  if err != nil {
216
234
  return nil, 0, 0, err
217
235
  }
218
- return &Executor{Policy: p, DSM: newDSM(), Writer: subprocessWriter{Staging: filepath.Join("/var/packages", pkg, "var/executor-staging"), Executable: exe, PackageUID: uid}, Runner: scriptRunner{Dir: dir}, Store: &Store{Dir: state}, Now: time.Now, ExecutorDigest: ed, PolicyDigest: pd}, uid, gid, nil
236
+ return &Executor{Policy: p, DSM: newDSM(), Writer: subprocessWriter{Staging: filepath.Join("/var/packages", pkg, "var/executor-staging"), Executable: exe, PackageUID: uid, PackageGID: gid}, Runner: scriptRunner{Dir: dir}, Store: &Store{Dir: state}, Now: time.Now, ExecutorDigest: ed, PolicyDigest: pd}, uid, gid, nil
219
237
  }
220
238
  func validatePolicy(p Policy, pkg string) error {
221
239
  if p.Version != 1 || p.Package != pkg || !tokenRE.MatchString(p.App) || len(p.Operations) == 0 {
222
240
  return errors.New("policy")
223
241
  }
242
+ if !contains([]string{"", "admin-only", "app-privilege"}, p.Entry) {
243
+ return errors.New("policy entry")
244
+ }
245
+ if p.Lifetime != "" && p.Lifetime != "until-revoked" {
246
+ return errors.New("policy lifetime")
247
+ }
248
+ if (len(p.Destinations) > 0) != (p.Lifetime == "until-revoked") {
249
+ return errors.New("policy destinations")
250
+ }
251
+ keys := map[string]bool{}
252
+ for _, key := range p.Destinations {
253
+ if !destinationRE.MatchString(key) || keys[key] {
254
+ return errors.New("policy destination")
255
+ }
256
+ keys[key] = true
257
+ }
258
+ if p.Lifetime != "" && !contains(p.Operations, "files.moveFromStaging") {
259
+ return errors.New("policy move")
260
+ }
224
261
  seen := map[string]bool{}
225
262
  for _, op := range p.Operations {
226
263
  if !contains(allOps, op) || seen[op] {
227
264
  return errors.New("policy operation")
228
265
  }
229
266
  seen[op] = true
230
- if strings.HasPrefix(op, "files.") && scopeVirtual(p.Scope) == "" {
267
+ if strings.HasPrefix(op, "files.") && scopeVirtual(p.Scope) == "" && !(p.Scope == installSelectedScope && p.Lifetime == "until-revoked") {
231
268
  return errors.New("policy scope")
232
269
  }
233
270
  }
234
- if seen["delegate.create"] && scopeVirtual(p.Scope) == "" {
271
+ if seen["delegate.create"] && scopeVirtual(p.Scope) == "" && !(p.Scope == installSelectedScope && p.Lifetime == "until-revoked") {
235
272
  return errors.New("policy scope")
236
273
  }
237
274
  for n, d := range p.Root {
@@ -241,14 +278,18 @@ func validatePolicy(p Policy, pkg string) error {
241
278
  }
242
279
  return nil
243
280
  }
244
- func serve(e *Executor, uid, gid int) error {
281
+ func serve(e *Executor, uid, gid int, revived bool) error {
245
282
  ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
246
283
  defer stop()
247
284
  actions := make(chan string, 1)
248
285
  stopControls := rootControlSignals(ctx, actions)
249
286
  defer stopControls()
250
287
  pkgDir := filepath.Join("/var/packages", e.Policy.Package)
251
- monitor := newLifecycleMonitor(e.Policy.Package, pkgDir, filepath.Join(pkgDir, "var/executor/control"), func(s string) { log.Print(s) })
288
+ newMonitor := newLifecycleMonitor
289
+ if revived {
290
+ newMonitor = newRevivedLifecycleMonitor
291
+ }
292
+ monitor := newMonitor(e.Policy.Package, pkgDir, filepath.Join(pkgDir, "var/executor/control"), func(s string) { log.Print(s) })
252
293
  pidpath := filepath.Join(e.Store.Dir, "executor.pid")
253
294
  pid, err := os.OpenFile(pidpath, os.O_RDWR|os.O_CREATE, 0600)
254
295
  if err != nil {
@@ -256,7 +297,7 @@ func serve(e *Executor, uid, gid int) error {
256
297
  }
257
298
  defer pid.Close()
258
299
  if err = lockFile(pid); err != nil {
259
- return errors.New("already running")
300
+ return errAlreadyRunning
260
301
  }
261
302
  if err = pid.Truncate(0); err != nil {
262
303
  return err
@@ -309,8 +350,12 @@ func serve(e *Executor, uid, gid int) error {
309
350
  }
310
351
  }()
311
352
  open := func() (func(), error) { return openService(ctx, e, uid, gid, sock) }
353
+ target, packageInfo := filepath.Join(pkgDir, "target/executor"), filepath.Join(pkgDir, "INFO")
312
354
  samePins := func(ctx context.Context) bool {
313
- return samePackagePins(ctx, filepath.Join(pkgDir, "target/executor"), e.ExecutorDigest, e.PolicyDigest)
355
+ state := packagePins(ctx, target, packageInfo, rootOwnedReadOnly, e.ExecutorDigest, e.PolicyDigest)
356
+ install := func() error { return installAnchored(ctx, target, packageInfo, rootOwnedReadOnly, runtime) }
357
+ start := func() error { return startInstalled(runtime, e.Policy.Package) }
358
+ return approvedPins(state, install, start, func(s string) { log.Print(s) })
314
359
  }
315
360
  purge := func() error {
316
361
  return purgeProtected(ctx, d, e.Policy.Package, filepath.Join(stateBase, e.Policy.Package), runtime, sockDir)
@@ -338,6 +383,12 @@ func openService(parent context.Context, e *Executor, uid, gid int, sock string)
338
383
  return nil, err
339
384
  }
340
385
  ctx, cancel := context.WithCancel(parent)
386
+ if err = e.startOperations(ctx); err != nil {
387
+ cancel()
388
+ ln.Close()
389
+ os.Remove(sock)
390
+ return nil, err
391
+ }
341
392
  var active sync.WaitGroup
342
393
  var mu sync.Mutex
343
394
  connections := map[*net.UnixConn]bool{}
@@ -382,6 +433,7 @@ func openService(parent context.Context, e *Executor, uid, gid int, sock string)
382
433
  }
383
434
  mu.Unlock()
384
435
  active.Wait()
436
+ e.stopOperations()
385
437
  os.Remove(sock)
386
438
  }, nil
387
439
  }
@@ -0,0 +1,490 @@
1
+ //go:build linux
2
+
3
+ package main
4
+
5
+ import (
6
+ "bufio"
7
+ "context"
8
+ "crypto/rand"
9
+ "encoding/hex"
10
+ "encoding/json"
11
+ "errors"
12
+ "io"
13
+ "os"
14
+ "path/filepath"
15
+ "runtime"
16
+ "strings"
17
+ "syscall"
18
+ "time"
19
+ )
20
+
21
+ // publishReceiptTimeout bounds the only I/O done under the commit lock.
22
+ var publishReceiptTimeout = 5 * time.Second
23
+
24
+ // syncPublished makes the child's link durable; tests slow it down.
25
+ var syncPublished = func(dir *os.File) error { return dir.Sync() }
26
+
27
+ type moveSpec struct {
28
+ SourceUID int `json:"source_uid"`
29
+ Subject Subject `json:"subject"`
30
+ Destination string `json:"destination"`
31
+ Directory directoryIdentity `json:"directory"`
32
+ Name string `json:"name"`
33
+ Size int64 `json:"size"`
34
+ Part partMetadata `json:"part"`
35
+ SourceName string `json:"source_name,omitempty"`
36
+ }
37
+ type childEvent struct {
38
+ Code string `json:"code,omitempty"`
39
+ Kind string `json:"kind"`
40
+ Part directoryIdentity `json:"part,omitempty"`
41
+ Receipt MoveReceipt `json:"receipt,omitempty"`
42
+ }
43
+
44
+ func fileIdentity(f *os.File) (directoryIdentity, error) {
45
+ info, err := f.Stat()
46
+ if err != nil {
47
+ return directoryIdentity{}, err
48
+ }
49
+ st, ok := info.Sys().(*syscall.Stat_t)
50
+ if !ok {
51
+ return directoryIdentity{}, errors.New("stat")
52
+ }
53
+ return directoryIdentity{uint64(st.Dev), st.Ino}, nil
54
+ }
55
+ func openDirectory(path string) (*os.File, error) {
56
+ if !cleanPath(path) || path == "/" {
57
+ return nil, errors.New("directory path")
58
+ }
59
+ return openBeneath("/", strings.TrimPrefix(path, "/"), syscall.O_RDONLY|syscall.O_DIRECTORY, 0)
60
+ }
61
+ func checkDirectory(path string, pinned *os.File, expected directoryIdentity) (*os.File, error) {
62
+ dir, err := openDirectory(path)
63
+ if err != nil {
64
+ return nil, err
65
+ }
66
+ got, err := fileIdentity(dir)
67
+ pin, x := fileIdentity(pinned)
68
+ if err != nil || x != nil || got != expected || got != pin {
69
+ dir.Close()
70
+ return nil, errors.New("directory replaced")
71
+ }
72
+ return dir, nil
73
+ }
74
+ func (w subprocessWriter) openSource(relative string) (*os.File, string, error) {
75
+ parent, err := filepath.EvalSymlinks(filepath.Dir(w.Staging))
76
+ if err != nil {
77
+ return nil, "", err
78
+ }
79
+ root := filepath.Join(parent, filepath.Base(w.Staging))
80
+ f, err := openBeneath(root, relative, syscall.O_RDONLY, 0)
81
+ if err != nil {
82
+ return nil, "", err
83
+ }
84
+ info, err := f.Stat()
85
+ if err != nil || !info.Mode().IsRegular() {
86
+ f.Close()
87
+ return nil, "", errors.New("staging file")
88
+ }
89
+ st, ok := info.Sys().(*syscall.Stat_t)
90
+ if !ok || int(st.Uid) != w.PackageUID || st.Nlink != 1 {
91
+ f.Close()
92
+ return nil, "", errors.New("staging owner or hardlink")
93
+ }
94
+ return f, root, nil
95
+ }
96
+ func (w subprocessWriter) Move(ctx context.Context, s Subject, op Operation, d Delegation, guard func(func() (MoveReceipt, error)) (MoveReceipt, error)) (MoveReceipt, error) {
97
+ if !tokenRE.MatchString(op.Job) || !validName(op.Name) || op.Size < 0 || op.Size > maxMoveBytes {
98
+ return MoveReceipt{}, errors.New("request")
99
+ }
100
+ f, root, err := w.openSource(op.Staging)
101
+ if err != nil {
102
+ return MoveReceipt{}, err
103
+ }
104
+ defer f.Close()
105
+ info, err := f.Stat()
106
+ if err != nil || info.Size() != op.Size {
107
+ return MoveReceipt{}, errors.New("size mismatch")
108
+ }
109
+ dir, err := openDirectory(d.Root)
110
+ if err != nil {
111
+ return MoveReceipt{}, err
112
+ }
113
+ defer dir.Close()
114
+ di, err := fileIdentity(dir)
115
+ if err != nil || di != d.Directory {
116
+ return MoveReceipt{}, errors.New("destination replaced")
117
+ }
118
+ random := make([]byte, 16)
119
+ if _, err = rand.Read(random); err != nil {
120
+ return MoveReceipt{}, err
121
+ }
122
+ spec := moveSpec{SourceUID: w.PackageUID, Subject: s, Destination: d.Root, Directory: d.Directory, Name: op.Name, Size: op.Size, Part: partMetadata{Temporary: ".synopkgland-" + op.Job + "-" + hex.EncodeToString(random) + ".part", Directory: d.Directory, Destination: d.Root}}
123
+ // Separate pipes allow a fixed child to copy first and wait for the parent's
124
+ // commit decision. There is no socket endpoint or arbitrary child payload.
125
+ input, inWrite, err := os.Pipe()
126
+ if err != nil {
127
+ return MoveReceipt{}, err
128
+ }
129
+ defer input.Close()
130
+ defer inWrite.Close()
131
+ output, outWrite, err := os.Pipe()
132
+ if err != nil {
133
+ return MoveReceipt{}, err
134
+ }
135
+ defer output.Close()
136
+ defer outWrite.Close()
137
+ cmd := command(ctx, w.Executable, []string{"move-child"})
138
+ cmd.Stdin = input
139
+ cmd.Stdout = outWrite
140
+ cmd.Stderr = io.Discard
141
+ cmd.ExtraFiles = []*os.File{f, dir}
142
+ if err = cmd.Start(); err != nil {
143
+ return MoveReceipt{}, err
144
+ }
145
+ input.Close()
146
+ outWrite.Close()
147
+ waited := false
148
+ defer func() {
149
+ if !waited {
150
+ cmd.Cancel()
151
+ cmd.Wait()
152
+ }
153
+ }()
154
+ defer func() {
155
+ if spec.Part.Part.Inode != 0 {
156
+ cleanup, cancel := context.WithTimeout(context.Background(), 5*time.Second)
157
+ defer cancel()
158
+ w.cleanupPart(cleanup, spec, dir)
159
+ }
160
+ }()
161
+ if err = json.NewEncoder(inWrite).Encode(spec); err != nil {
162
+ return MoveReceipt{}, err
163
+ }
164
+ reader := bufio.NewReader(output)
165
+ event := func() (childEvent, error) {
166
+ var event childEvent
167
+ b, err := reader.ReadBytes('\n')
168
+ if err != nil || len(b) > maxRequest {
169
+ return event, errors.New("child receipt")
170
+ }
171
+ if strictJSON(b, &event) != nil {
172
+ return event, errors.New("child receipt")
173
+ }
174
+ return event, nil
175
+ }
176
+ ev, err := event()
177
+ if err != nil || ev.Kind != "part" || ev.Part.Inode == 0 {
178
+ return MoveReceipt{}, errors.New("child part")
179
+ }
180
+ spec.Part.Part = ev.Part
181
+ if record, ok := ctx.Value(partRecordKey{}).(func(partMetadata) error); ok {
182
+ if err = record(spec.Part); err != nil {
183
+ return MoveReceipt{}, err
184
+ }
185
+ }
186
+ if _, err = inWrite.Write([]byte("C\n")); err != nil {
187
+ return MoveReceipt{}, err
188
+ }
189
+ ev, err = event()
190
+ if err != nil || ev.Kind != "ready" {
191
+ return MoveReceipt{}, errors.New("child copy")
192
+ }
193
+ receipt, err := guard(func() (MoveReceipt, error) {
194
+ // This is the only bounded I/O under the commit lock. Lost ack is uncertain.
195
+ if x := output.SetReadDeadline(time.Now().Add(publishReceiptTimeout)); x != nil {
196
+ return MoveReceipt{}, x
197
+ }
198
+ if _, x := inWrite.Write([]byte("P\n")); x != nil {
199
+ cmd.Cancel()
200
+ cmd.Wait()
201
+ waited = true
202
+ return MoveReceipt{}, x
203
+ }
204
+ ev, x := event()
205
+ if x != nil || ev.Kind != "committed" {
206
+ // Drain before releasing the protected publish lock: a lost receipt must
207
+ // never leave a live child that could publish after revoke acknowledges.
208
+ cmd.Cancel()
209
+ cmd.Wait()
210
+ waited = true
211
+ }
212
+ if x == nil && ev.Kind == "refused" && contains([]string{"destination-exists", "PERMISSION_DENIED", "NO_SPACE"}, ev.Code) {
213
+ return MoveReceipt{}, publishRejectedError{ev.Code}
214
+ }
215
+ if x != nil || ev.Kind != "committed" {
216
+ // The child may have linked before its receipt was lost, e.g. while a
217
+ // large file's directory fsync outlived the bound. With the child gone
218
+ // the final name is settled: our exact part inode there is the commit.
219
+ if waited && publishedPart(dir, spec) && dir.Sync() == nil {
220
+ return MoveReceipt{}, nil
221
+ }
222
+ return MoveReceipt{}, errors.New("publish receipt")
223
+ }
224
+ return ev.Receipt, nil
225
+ })
226
+ if err != nil {
227
+ return receipt, err
228
+ }
229
+ if !waited && cmd.Wait() != nil {
230
+ receipt.CleanupPending = true
231
+ }
232
+ waited = true
233
+ // Committed is immutable even if part/source cleanup fails.
234
+ if err = w.cleanupPart(ctx, spec, dir); err != nil {
235
+ receipt.CleanupPending = true
236
+ } else {
237
+ spec.Part.Part = directoryIdentity{}
238
+ }
239
+ if err = w.cleanupSource(ctx, f, root, op.Staging); err != nil {
240
+ receipt.CleanupPending = true
241
+ }
242
+ return receipt, nil
243
+ }
244
+
245
+ // publishedPart reports whether the final name is the recorded part inode: a
246
+ // regular file of the declared size owned by the subject.
247
+ func publishedPart(dir *os.File, spec moveSpec) bool {
248
+ fd, err := syscall.Openat(int(dir.Fd()), spec.Name, syscall.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK|syscall.O_CLOEXEC, 0)
249
+ if err != nil {
250
+ return false
251
+ }
252
+ f := os.NewFile(uintptr(fd), spec.Name)
253
+ defer f.Close()
254
+ got, err := fileIdentity(f)
255
+ info, x := f.Stat()
256
+ if err != nil || x != nil || !info.Mode().IsRegular() || info.Size() != spec.Size || spec.Part.Part.Inode == 0 || got != spec.Part.Part {
257
+ return false
258
+ }
259
+ st, ok := info.Sys().(*syscall.Stat_t)
260
+ return ok && int(st.Uid) == spec.Subject.UID
261
+ }
262
+ func (w subprocessWriter) cleanupPart(ctx context.Context, spec moveSpec, dir *os.File) error {
263
+ b, _ := json.Marshal(spec)
264
+ cmd := command(ctx, w.Executable, []string{"part-cleanup-child"})
265
+ cmd.ExtraFiles = []*os.File{dir}
266
+ cmd.Stdin = strings.NewReader(string(b))
267
+ cmd.Stdout = io.Discard
268
+ cmd.Stderr = io.Discard
269
+ return cmd.Run()
270
+ }
271
+ func (w subprocessWriter) cleanupSource(ctx context.Context, original *os.File, root, relative string) error {
272
+ parent := filepath.Dir(relative)
273
+ path := root
274
+ if parent != "." {
275
+ path = filepath.Join(root, parent)
276
+ }
277
+ dir, err := openDirectory(path)
278
+ if err != nil {
279
+ return err
280
+ }
281
+ defer dir.Close()
282
+ spec := moveSpec{Subject: Subject{UID: w.PackageUID, GID: w.PackageGID, Groups: []int{w.PackageGID}}, SourceName: filepath.Base(relative)}
283
+ b, _ := json.Marshal(spec)
284
+ cmd := command(ctx, w.Executable, []string{"source-cleanup-child"})
285
+ cmd.ExtraFiles = []*os.File{original, dir}
286
+ cmd.Stdin = strings.NewReader(string(b))
287
+ cmd.Stdout = io.Discard
288
+ cmd.Stderr = io.Discard
289
+ return cmd.Run()
290
+ }
291
+ func readMoveSpec(r io.Reader) (moveSpec, error) {
292
+ var s moveSpec
293
+ b, err := io.ReadAll(io.LimitReader(r, maxRequest+1))
294
+ if err != nil || len(b) > maxRequest || strictJSON(b, &s) != nil {
295
+ return s, errors.New("spec")
296
+ }
297
+ return s, nil
298
+ }
299
+ func runMoveChild() error {
300
+ runtime.LockOSThread()
301
+ defer runtime.UnlockOSThread()
302
+ if os.Geteuid() != 0 {
303
+ return errors.New("root child required")
304
+ }
305
+ input := bufio.NewReader(os.Stdin)
306
+ line, err := input.ReadBytes('\n')
307
+ if err != nil || len(line) > maxRequest {
308
+ return errors.New("spec")
309
+ }
310
+ var spec moveSpec
311
+ if strictJSON(line, &spec) != nil || !validName(spec.Name) || !validTemporary(spec.Part.Temporary) || spec.Size < 0 || spec.Size > maxMoveBytes {
312
+ return errors.New("spec")
313
+ }
314
+ if err = dropSealedIdentity(realIdentity{}, spec.Subject); err != nil {
315
+ return err
316
+ }
317
+ pinned := os.NewFile(4, "destination")
318
+ defer pinned.Close()
319
+ dir, err := checkDirectory(spec.Destination, pinned, spec.Directory)
320
+ if err != nil {
321
+ return err
322
+ }
323
+ defer dir.Close()
324
+ fd, err := syscall.Openat(int(dir.Fd()), spec.Part.Temporary, syscall.O_RDWR|syscall.O_CREAT|syscall.O_EXCL|syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0600)
325
+ if err != nil {
326
+ return err
327
+ }
328
+ part := os.NewFile(uintptr(fd), spec.Part.Temporary)
329
+ defer part.Close()
330
+ identity, err := fileIdentity(part)
331
+ if err != nil {
332
+ return err
333
+ }
334
+ defer unlinkExact(dir, spec.Part.Temporary, identity)
335
+ send := func(event childEvent) error { return json.NewEncoder(os.Stdout).Encode(event) }
336
+ if err = send(childEvent{Kind: "part", Part: identity}); err != nil {
337
+ return err
338
+ }
339
+ decision, err := input.ReadString('\n')
340
+ if err != nil || decision != "C\n" {
341
+ return errors.New("copy refused")
342
+ }
343
+ src := os.NewFile(3, "staging")
344
+ defer src.Close()
345
+ // The parent's death closes stdin; stop copying instead of orphaning.
346
+ control := awaitControl(input)
347
+ if err = copyUnlessParentGone(part, src, spec.Size, control); err != nil {
348
+ return err
349
+ }
350
+ info, err := src.Stat()
351
+ if err != nil || !info.Mode().IsRegular() {
352
+ return errors.New("staging changed")
353
+ }
354
+ st, ok := info.Sys().(*syscall.Stat_t)
355
+ if !ok || int(st.Uid) != spec.SourceUID || st.Nlink != 1 || info.Size() != spec.Size {
356
+ return errors.New("staging changed")
357
+ }
358
+ if err = part.Sync(); err != nil {
359
+ return err
360
+ }
361
+ if err = send(childEvent{Kind: "ready"}); err != nil {
362
+ return err
363
+ }
364
+ if next := <-control; next.err != nil || next.line != "P\n" {
365
+ return errors.New("publish refused")
366
+ }
367
+ check, err := checkDirectory(spec.Destination, pinned, spec.Directory)
368
+ if err != nil {
369
+ return err
370
+ }
371
+ check.Close()
372
+ // Link the OPEN part inode, never a possibly swapped pathname. AT_SYMLINK_FOLLOW
373
+ // is needed only for this trusted proc FD; the destination name cannot replace.
374
+ if err = linkOpenPart(part, dir, spec.Name); err != nil {
375
+ code := ""
376
+ switch err {
377
+ case syscall.EEXIST:
378
+ code = "destination-exists"
379
+ case syscall.EACCES, syscall.EPERM:
380
+ code = "PERMISSION_DENIED"
381
+ case syscall.ENOSPC:
382
+ code = "NO_SPACE"
383
+ }
384
+ if code != "" {
385
+ return send(childEvent{Kind: "refused", Code: code})
386
+ }
387
+ return err
388
+ }
389
+ // A failure to sync after link is a lost durable result, not a retryable error.
390
+ if err = syncPublished(dir); err != nil {
391
+ return err
392
+ }
393
+ cleanupPending := unlinkExact(dir, spec.Part.Temporary, identity) != nil
394
+ if !cleanupPending {
395
+ cleanupPending = dir.Sync() != nil
396
+ }
397
+ return send(childEvent{Kind: "committed", Receipt: MoveReceipt{CleanupPending: cleanupPending}})
398
+ }
399
+ func unlinkExact(dir *os.File, name string, expected directoryIdentity) error {
400
+ fd, err := syscall.Openat(int(dir.Fd()), name, syscall.O_RDONLY|syscall.O_NOFOLLOW|syscall.O_NONBLOCK|syscall.O_CLOEXEC, 0)
401
+ if err == syscall.ENOENT {
402
+ return nil
403
+ }
404
+ if err != nil {
405
+ return err
406
+ }
407
+ f := os.NewFile(uintptr(fd), name)
408
+ got, err := fileIdentity(f)
409
+ info, x := f.Stat()
410
+ f.Close()
411
+ if err != nil || x != nil || !info.Mode().IsRegular() || got != expected {
412
+ return errors.New("cleanup replaced")
413
+ }
414
+ return syscall.Unlinkat(int(dir.Fd()), name)
415
+ }
416
+ func runPartCleanupChild() error {
417
+ runtime.LockOSThread()
418
+ defer runtime.UnlockOSThread()
419
+ if os.Geteuid() != 0 {
420
+ return errors.New("root child required")
421
+ }
422
+ spec, err := readMoveSpec(os.Stdin)
423
+ if err != nil || !validTemporary(spec.Part.Temporary) || spec.Part.Part.Inode == 0 {
424
+ return errors.New("spec")
425
+ }
426
+ if err = dropSealedIdentity(realIdentity{}, spec.Subject); err != nil {
427
+ return err
428
+ }
429
+ dir := os.NewFile(3, "directory")
430
+ defer dir.Close()
431
+ got, err := fileIdentity(dir)
432
+ if err != nil || got != spec.Directory {
433
+ return errors.New("directory")
434
+ }
435
+ if err = unlinkExact(dir, spec.Part.Temporary, spec.Part.Part); err != nil {
436
+ return err
437
+ }
438
+ return dir.Sync()
439
+ }
440
+ func runSourceCleanupChild() error {
441
+ runtime.LockOSThread()
442
+ defer runtime.UnlockOSThread()
443
+ if os.Geteuid() != 0 {
444
+ return errors.New("root child required")
445
+ }
446
+ spec, err := readMoveSpec(os.Stdin)
447
+ if err != nil || !validName(spec.SourceName) {
448
+ return errors.New("spec")
449
+ }
450
+ if err = dropSealedIdentity(realIdentity{}, spec.Subject); err != nil {
451
+ return err
452
+ }
453
+ src := os.NewFile(3, "source")
454
+ defer src.Close()
455
+ dir := os.NewFile(4, "directory")
456
+ defer dir.Close()
457
+ identity, err := fileIdentity(src)
458
+ if err != nil {
459
+ return err
460
+ }
461
+ if err = unlinkExact(dir, spec.SourceName, identity); err != nil {
462
+ return err
463
+ }
464
+ return dir.Sync()
465
+ }
466
+
467
+ func (w subprocessWriter) PublishedPart(_ context.Context, op Operation) bool {
468
+ if op.Part == nil || !validName(op.Name) {
469
+ return false
470
+ }
471
+ dir, err := openDirectory(op.Part.Destination)
472
+ if err != nil {
473
+ return false
474
+ }
475
+ defer dir.Close()
476
+ got, err := fileIdentity(dir)
477
+ return err == nil && got == op.Part.Directory && publishedPart(dir, moveSpec{Subject: op.Subject, Name: op.Name, Size: op.Size, Part: *op.Part}) && dir.Sync() == nil
478
+ }
479
+ func (w subprocessWriter) CleanupRecorded(ctx context.Context, s Subject, part partMetadata) error {
480
+ dir, err := openDirectory(part.Destination)
481
+ if err != nil {
482
+ return err
483
+ }
484
+ defer dir.Close()
485
+ got, err := fileIdentity(dir)
486
+ if err != nil || got != part.Directory {
487
+ return errors.New("cleanup directory changed")
488
+ }
489
+ return w.cleanupPart(ctx, moveSpec{Subject: s, Directory: part.Directory, Part: part}, dir)
490
+ }