@runuai/host 0.9.152 → 0.9.154

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.
@@ -13,10 +13,20 @@
13
13
  * caveat of hostless-implementation.md decision 4; EFS is the swap if they
14
14
  * bite, and this module is the only place that would change.
15
15
  */
16
+ import type { TaskLeaseFilesAccess } from "./cloud-credential-leases";
16
17
  import type { TaskEnvironmentHandle } from "./task-environment/types";
17
18
  import { LEASE_AWS_CONFIG_FILE, currentLease } from "./lease";
19
+ import { spawnSync } from "node:child_process";
18
20
 
19
21
  export const MOUNT_S3_BIN = "/usr/bin/mount-s3";
22
+ /** Files v1 mounts only below this root-owned machine path. The task sees it
23
+ * through an atomic `/workspace/files` symlink. Root mount/umount operations
24
+ * therefore never resolve a component the task can replace. */
25
+ export const TASK_FILES_V1_MOUNT_ROOT = "/mnt/uai-files";
26
+ /** Boot-local, root-only latch armed before the first v1 Mountpoint process
27
+ * can start. Once armed, only retiring/rebooting the one-task machine can
28
+ * prove that no private namespace or detached descriptor retains Files. */
29
+ export const TASK_FILES_V1_MOUNT_ATTEMPTED = "/run/uai/files-mount-attempted";
20
30
  /** The task user the mounts are owned by. Its ids are looked up on the
21
31
  * machine, never assumed: the Debian cloud image the payload AMI is built
22
32
  * from ships an `admin` user at uid 1000, so `node` lands on 1001 there
@@ -32,10 +42,29 @@ export interface TaskUserIds {
32
42
  gid: number;
33
43
  }
34
44
 
45
+ export interface SharedFilesV1Options {
46
+ /**
47
+ * The exact run's last-moment admission fence. It rides on every call into
48
+ * the task environment, including the marker write, so a withdrawn run
49
+ * cannot start its next mount step after an awaited provider lookup.
50
+ */
51
+ admit?: () => void;
52
+ /** Stage the mounts behind the root-only fixed directory without exposing
53
+ * them in the workspace. The Files consumer publishes synchronously from
54
+ * its exact gate-open callback. */
55
+ publish?: boolean;
56
+ }
57
+
58
+ interface ResolveTaskUserIdsOptions extends SharedFilesV1Options {
59
+ /** A v1 admission refusal is an error, never a reason to use fallback ids. */
60
+ failOnExecError?: boolean;
61
+ }
62
+
35
63
  /** `id -u`/`id -g` of the task user on this machine, through the root exec. */
36
64
  export async function resolveTaskUserIds(
37
65
  handle: Pick<TaskEnvironmentHandle, "exec">,
38
66
  user: string = SHARED_FILES_USER,
67
+ options: ResolveTaskUserIdsOptions = {},
39
68
  ): Promise<TaskUserIds> {
40
69
  const fallback = { uid: SHARED_FILES_FALLBACK_UID, gid: SHARED_FILES_FALLBACK_GID };
41
70
  try {
@@ -45,16 +74,28 @@ export async function resolveTaskUserIds(
45
74
  user: "root",
46
75
  timeoutMs: 10_000,
47
76
  maxOutputBytes: 1024,
77
+ ...(options.admit ? { admit: options.admit } : {}),
48
78
  });
49
- if (result.exitCode !== 0) return fallback;
79
+ if (result.exitCode !== 0) {
80
+ if (options.failOnExecError) {
81
+ throw new Error(`could not resolve the task user's ids (exit ${result.exitCode})`);
82
+ }
83
+ return fallback;
84
+ }
50
85
  const [uid, gid] = Buffer.from(result.stdout)
51
86
  .toString("utf8")
52
87
  .trim()
53
88
  .split(/\s+/)
54
89
  .map((part) => Number.parseInt(part, 10));
55
- if (!Number.isInteger(uid) || !Number.isInteger(gid) || uid! < 0 || gid! < 0) return fallback;
90
+ if (!Number.isInteger(uid) || !Number.isInteger(gid) || uid! < 0 || gid! < 0) {
91
+ if (options.failOnExecError) {
92
+ throw new Error("could not resolve the task user's ids");
93
+ }
94
+ return fallback;
95
+ }
56
96
  return { uid: uid!, gid: gid! };
57
- } catch {
97
+ } catch (error) {
98
+ if (options.failOnExecError) throw error;
58
99
  return fallback;
59
100
  }
60
101
  }
@@ -217,6 +258,200 @@ async function mountSharedFilesFromLeaseNow(
217
258
  return warnings.length > 0 ? warnings.join("\n") : null;
218
259
  }
219
260
 
261
+ /**
262
+ * Mount shared files from the exact task/run's v1 Files session.
263
+ *
264
+ * Unlike the transitional lease path above, this function never consults
265
+ * `currentLease`, never turns a requested mount failure into a warning, and
266
+ * does not publish the workspace marker until every requested prefix is up.
267
+ * The Files consumer owns writing the session's credential_process files;
268
+ * mount-s3 learns only their fixed config path here.
269
+ */
270
+ export async function mountSharedFilesFromAccess(
271
+ handle: Pick<TaskEnvironmentHandle<unknown>, "exec">,
272
+ workspacePath: string,
273
+ mode: SharedFilesMode,
274
+ access: TaskLeaseFilesAccess,
275
+ options: SharedFilesV1Options = {},
276
+ ): Promise<void> {
277
+ if (workspacePath !== "/workspace") {
278
+ throw new Error("shared files: task-machine workspace is not canonical");
279
+ }
280
+ return serializedByWorkspace(TASK_FILES_V1_MOUNT_ROOT, () =>
281
+ mountSharedFilesFromAccessNow(handle, workspacePath, mode, access, options),
282
+ );
283
+ }
284
+
285
+ async function mountSharedFilesFromAccessNow(
286
+ handle: Pick<TaskEnvironmentHandle<unknown>, "exec">,
287
+ workspacePath: string,
288
+ mode: SharedFilesMode,
289
+ access: TaskLeaseFilesAccess,
290
+ options: SharedFilesV1Options,
291
+ ): Promise<void> {
292
+ if (mode === "off") return;
293
+ const owner = await resolveTaskUserIds(handle, SHARED_FILES_USER, {
294
+ ...options,
295
+ failOnExecError: true,
296
+ });
297
+ // The legacy planner takes a workspace and appends `files/{org,me}`.
298
+ // Files v1's argument is already the fixed mount root, so rewrite only the
299
+ // resulting directory: `/workspace/files` exposes these exact children.
300
+ const plans = sharedFilesMountPlans(TASK_FILES_V1_MOUNT_ROOT, mode, access).map(
301
+ (plan) => ({
302
+ ...plan,
303
+ directory: `${TASK_FILES_V1_MOUNT_ROOT}/${plan.directory.endsWith("/org") ? "org" : "me"}`,
304
+ }),
305
+ );
306
+ const scopes = plans.map((plan) => plan.directory.slice(`${TASK_FILES_V1_MOUNT_ROOT}/`.length));
307
+ const prepared = await handle.exec({
308
+ projectEnvProfile: "maintenance",
309
+ argv: [
310
+ "/usr/bin/python3",
311
+ "-I",
312
+ "-c",
313
+ TASK_FILES_V1_PREPARE_HELPER,
314
+ TASK_FILES_V1_MOUNT_ROOT,
315
+ scopes.join(","),
316
+ ],
317
+ user: "root",
318
+ env: {},
319
+ timeoutMs: 10_000,
320
+ maxOutputBytes: 4 * 1024,
321
+ ...(options.admit ? { admit: options.admit } : {}),
322
+ });
323
+ if (prepared.exitCode !== 0) {
324
+ throw new Error(`shared files: could not prepare the fixed mount root (exit ${prepared.exitCode})`);
325
+ }
326
+ for (const plan of plans) {
327
+ const result = await handle.exec({
328
+ projectEnvProfile: "maintenance",
329
+ argv: [
330
+ "/usr/bin/env",
331
+ "-i",
332
+ "PATH=/usr/bin:/bin",
333
+ `AWS_CONFIG_FILE=${LEASE_AWS_CONFIG_FILE}`,
334
+ "AWS_EC2_METADATA_DISABLED=true",
335
+ "/bin/sh",
336
+ "-c",
337
+ 'if /usr/bin/mountpoint -q "$1" 2>/dev/null; then exit 40; fi; shift; exec "$@"',
338
+ "mount-fixed",
339
+ plan.directory,
340
+ ...mountS3Argv(plan, owner),
341
+ ],
342
+ user: "root",
343
+ env: {},
344
+ timeoutMs: 60_000,
345
+ maxOutputBytes: 64 * 1024,
346
+ ...(options.admit ? { admit: options.admit } : {}),
347
+ });
348
+ if (result.exitCode !== 0) {
349
+ const detail = Buffer.from(result.stderr).toString("utf8").trim();
350
+ throw new Error(
351
+ `shared files: could not mount ${plan.directory}: ${detail || `exit ${result.exitCode}`}`,
352
+ );
353
+ }
354
+ }
355
+
356
+ if (options.publish === false) return;
357
+ const linked = await handle.exec({
358
+ projectEnvProfile: "maintenance",
359
+ argv: [
360
+ "/usr/bin/python3",
361
+ "-I",
362
+ "-c",
363
+ TASK_FILES_V1_LINK_HELPER,
364
+ workspacePath,
365
+ TASK_FILES_V1_MOUNT_ROOT,
366
+ mode,
367
+ scopes.join(","),
368
+ ],
369
+ user: "root",
370
+ env: {},
371
+ timeoutMs: 10_000,
372
+ maxOutputBytes: 4 * 1024,
373
+ ...(options.admit ? { admit: options.admit } : {}),
374
+ });
375
+ if (linked.exitCode !== 0) {
376
+ throw new Error(`shared files: could not publish the fixed mount root (exit ${linked.exitCode})`);
377
+ }
378
+
379
+ }
380
+
381
+ /** Publish already-staged v1 mounts from the synchronous registry gate-open
382
+ * callback. Files v1 exists only on a root sidecar/self runtime, so this
383
+ * local fixed-interpreter operation is the final boundary before any other
384
+ * consumer gate opens. */
385
+ export function publishSharedFilesV1Sync(
386
+ workspacePath: string,
387
+ mode: SharedFilesMode,
388
+ scopes: readonly ("org" | "me")[],
389
+ ): void {
390
+ if (mode === "off") return;
391
+ if (workspacePath !== "/workspace") {
392
+ throw new Error("shared files: task-machine workspace is not canonical");
393
+ }
394
+ const result = spawnSync(
395
+ "/usr/bin/python3",
396
+ [
397
+ "-I",
398
+ "-c",
399
+ TASK_FILES_V1_LINK_HELPER,
400
+ workspacePath,
401
+ TASK_FILES_V1_MOUNT_ROOT,
402
+ mode,
403
+ scopes.join(","),
404
+ ],
405
+ {
406
+ cwd: "/",
407
+ env: {},
408
+ encoding: "utf8",
409
+ timeout: 10_000,
410
+ maxBuffer: 4 * 1024,
411
+ },
412
+ );
413
+ if (result.error || result.status !== 0 || result.signal !== null) {
414
+ const stderr = typeof result.stderr === "string" ? result.stderr.trim() : "";
415
+ throw new Error(
416
+ `shared files: could not publish the fixed mount root${
417
+ stderr ? `: ${stderr}` : ""
418
+ }`,
419
+ );
420
+ }
421
+ }
422
+
423
+ /** Synchronous half of gate close: make the fixed root non-traversable and
424
+ * remove the task-visible link/marker before any asynchronous cleanup. Open
425
+ * file descriptors still require process quiescence + ordinary unmount. */
426
+ export function hideSharedFilesV1Sync(workspacePath: string): void {
427
+ if (workspacePath !== "/workspace") {
428
+ throw new Error("shared files: task-machine workspace is not canonical");
429
+ }
430
+ const result = spawnSync(
431
+ "/usr/bin/python3",
432
+ [
433
+ "-I",
434
+ "-c",
435
+ TASK_FILES_V1_HIDE_HELPER,
436
+ workspacePath,
437
+ TASK_FILES_V1_MOUNT_ROOT,
438
+ ],
439
+ {
440
+ cwd: "/",
441
+ env: {},
442
+ encoding: "utf8",
443
+ timeout: 10_000,
444
+ maxBuffer: 4 * 1024,
445
+ },
446
+ );
447
+ if (result.error || result.status !== 0 || result.signal !== null) {
448
+ const stderr = typeof result.stderr === "string" ? result.stderr.trim() : "";
449
+ throw new Error(
450
+ `shared files: could not hide the fixed mount root${stderr ? `: ${stderr}` : ""}`,
451
+ );
452
+ }
453
+ }
454
+
220
455
  /** Unmount both directories; tolerant of never-mounted paths. */
221
456
  export async function unmountSharedFiles(
222
457
  handle: TaskEnvironmentHandle<unknown>,
@@ -234,3 +469,508 @@ export async function unmountSharedFiles(
234
469
  .catch(() => {});
235
470
  }
236
471
  }
472
+
473
+ /**
474
+ * Strict v1 cleanup. Both mountpoints are attempted even when one fails, and
475
+ * success proves that neither remains mounted. It shares the mount queue, so
476
+ * cleanup cannot overtake a mount/marker pass for the same workspace.
477
+ */
478
+ export async function unmountSharedFilesStrict(
479
+ handle: Pick<TaskEnvironmentHandle<unknown>, "exec">,
480
+ workspacePath: string,
481
+ options: SharedFilesV1Options = {},
482
+ ): Promise<void> {
483
+ if (workspacePath !== "/workspace") {
484
+ throw new Error("shared files: task-machine workspace is not canonical");
485
+ }
486
+ return serializedByWorkspace(TASK_FILES_V1_MOUNT_ROOT, async () => {
487
+ const result = await handle.exec({
488
+ argv: [
489
+ "/usr/bin/python3",
490
+ "-I",
491
+ "-c",
492
+ TASK_FILES_V1_UNMOUNT_HELPER,
493
+ workspacePath,
494
+ TASK_FILES_V1_MOUNT_ROOT,
495
+ ],
496
+ projectEnvProfile: "maintenance",
497
+ user: "root",
498
+ env: {},
499
+ timeoutMs: 30_000,
500
+ maxOutputBytes: 4 * 1024,
501
+ ...(options.admit ? { admit: options.admit } : {}),
502
+ });
503
+ if (result.exitCode !== 0) {
504
+ const detail = Buffer.from(result.stderr).toString("utf8").trim();
505
+ throw new Error(
506
+ `shared files: could not prove the fixed mounts absent: ${detail || `exit ${result.exitCode}`}`,
507
+ );
508
+ }
509
+ });
510
+ }
511
+
512
+ /** Create only the fixed, root-controlled mount directories. Neither the
513
+ * workspace nor any task-owned path is resolved here. */
514
+ export const TASK_FILES_V1_PREPARE_HELPER = String.raw`
515
+ import os
516
+ import stat
517
+ import sys
518
+
519
+ root, encoded = sys.argv[1:3]
520
+ if root != "/mnt/uai-files":
521
+ raise RuntimeError("unexpected Files mount root")
522
+ scopes = [part for part in encoded.split(",") if part]
523
+ if any(part not in ("org", "me") for part in scopes) or "me" not in scopes:
524
+ raise RuntimeError("invalid Files mount scope")
525
+ flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC
526
+
527
+ def arm_mount_attempt():
528
+ runtime = os.open("/run/uai", flags)
529
+ try:
530
+ runtime_info = os.fstat(runtime)
531
+ if runtime_info.st_uid != 0:
532
+ raise RuntimeError("the Files runtime directory is not root-owned")
533
+ try:
534
+ attempted = os.open(
535
+ "files-mount-attempted",
536
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW | os.O_CLOEXEC,
537
+ 0o600,
538
+ dir_fd=runtime,
539
+ )
540
+ except FileExistsError:
541
+ raise RuntimeError("a Files mount was already attempted in this boot; retire this task machine")
542
+ try:
543
+ marker_info = os.fstat(attempted)
544
+ if marker_info.st_uid != 0 or not stat.S_ISREG(marker_info.st_mode) or stat.S_IMODE(marker_info.st_mode) != 0o600:
545
+ raise RuntimeError("the Files mount-attempt marker is unsafe")
546
+ os.write(attempted, b"1\n")
547
+ os.fsync(attempted)
548
+ finally:
549
+ os.close(attempted)
550
+ finally:
551
+ os.close(runtime)
552
+
553
+ def decode_mount_path(value):
554
+ result = bytearray()
555
+ index = 0
556
+ while index < len(value):
557
+ if value[index:index + 1] == b"\\" and index + 3 < len(value) and all(48 <= part <= 55 for part in value[index + 1:index + 4]):
558
+ result.append(int(value[index + 1:index + 4], 8))
559
+ index += 4
560
+ else:
561
+ result.append(value[index])
562
+ index += 1
563
+ return os.fsdecode(bytes(result))
564
+
565
+ with open("/proc/self/mountinfo", "rb") as stream:
566
+ for line in stream:
567
+ before = line.rstrip(b"\n").split(b" - ", 1)[0].split(b" ")
568
+ path = decode_mount_path(before[4])
569
+ if path == root or path.startswith(root + "/"):
570
+ arm_mount_attempt()
571
+ raise RuntimeError("the Files mount root is not empty")
572
+
573
+ parent_path, root_name = os.path.split(root)
574
+ if not parent_path or not root_name:
575
+ raise RuntimeError("invalid Files mount root")
576
+ parent = os.open(parent_path, flags)
577
+ try:
578
+ try:
579
+ os.mkdir(root_name, 0o755, dir_fd=parent)
580
+ except FileExistsError:
581
+ pass
582
+ directory = os.open(root_name, flags, dir_fd=parent)
583
+ finally:
584
+ os.close(parent)
585
+ try:
586
+ info = os.fstat(directory)
587
+ if info.st_uid != 0:
588
+ raise RuntimeError("Files mount root is not root-owned")
589
+ # Staged mounts are deliberately unreachable to the task until the exact
590
+ # registry open publishes them.
591
+ os.fchmod(directory, 0o700)
592
+ for scope in scopes:
593
+ try:
594
+ os.mkdir(scope, 0o755, dir_fd=directory)
595
+ except FileExistsError:
596
+ pass
597
+ child = os.open(scope, flags, dir_fd=directory)
598
+ try:
599
+ if os.fstat(child).st_uid != 0:
600
+ raise RuntimeError("Files mountpoint is not root-owned")
601
+ os.fchmod(child, 0o755)
602
+ finally:
603
+ os.close(child)
604
+ for unused in set(("org", "me")) - set(scopes):
605
+ try:
606
+ os.rmdir(unused, dir_fd=directory)
607
+ except FileNotFoundError:
608
+ pass
609
+ finally:
610
+ os.close(directory)
611
+
612
+ # Arm the one-way boot latch only after the fixed root is ready, but before
613
+ # the caller can spawn mount-s3. A lost helper response is deliberately
614
+ # terminal: the caller cannot distinguish it from a mount launch that raced
615
+ # ahead in another attempt.
616
+ arm_mount_attempt()
617
+ `;
618
+
619
+ /** Publish the fixed root without ever asking root to follow the task's
620
+ * `files` entry. A stray task entry is atomically moved aside, never read or
621
+ * recursively deleted. */
622
+ export const TASK_FILES_V1_LINK_HELPER = String.raw`
623
+ import os
624
+ import stat
625
+ import sys
626
+
627
+ workspace, target, mode, encoded = sys.argv[1:5]
628
+ if workspace != "/workspace" or target != "/mnt/uai-files" or mode not in ("ro", "rw"):
629
+ raise RuntimeError("invalid Files link")
630
+ scopes = [part for part in encoded.split(",") if part]
631
+ if any(part not in ("org", "me") for part in scopes) or "me" not in scopes or len(set(scopes)) != len(scopes):
632
+ raise RuntimeError("invalid Files scopes")
633
+ flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC
634
+
635
+ def decode_mount_path(value):
636
+ result = bytearray()
637
+ index = 0
638
+ while index < len(value):
639
+ if value[index:index + 1] == b"\\" and index + 3 < len(value) and all(48 <= part <= 55 for part in value[index + 1:index + 4]):
640
+ result.append(int(value[index + 1:index + 4], 8))
641
+ index += 4
642
+ else:
643
+ result.append(value[index])
644
+ index += 1
645
+ return os.fsdecode(bytes(result))
646
+
647
+ expected = {(target + "/" + scope, "fuse", "mountpoint-s3") for scope in scopes}
648
+
649
+ def exact_topology():
650
+ observed = []
651
+ with open("/proc/self/mountinfo", "rb") as stream:
652
+ for line in stream:
653
+ before, after = line.rstrip(b"\n").split(b" - ", 1)
654
+ fields = before.split(b" ")
655
+ tail = after.split(b" ")
656
+ path = decode_mount_path(fields[4])
657
+ if path == target or path.startswith(target + "/"):
658
+ observed.append((path, tail[0].decode("ascii"), tail[1].decode("ascii")))
659
+ return len(observed) == len(expected) and set(observed) == expected
660
+
661
+ if not exact_topology():
662
+ raise RuntimeError("the staged Files mount topology is not exact")
663
+
664
+ mount_root = os.open(target, flags)
665
+ directory = os.open(workspace, flags)
666
+ try:
667
+ workspace_owner = os.fstat(directory)
668
+ # The files entry belongs to the task until this exact publication. Preserve any
669
+ # stray entry by moving it aside in the same directory; never traverse or
670
+ # recursively remove it. The bounded loop also prevents a task racing the
671
+ # name from holding the root helper forever.
672
+ for attempt in range(64):
673
+ try:
674
+ info = os.stat("files", dir_fd=directory, follow_symlinks=False)
675
+ except FileNotFoundError:
676
+ info = None
677
+ if info is not None and stat.S_ISLNK(info.st_mode):
678
+ try:
679
+ if os.readlink("files", dir_fd=directory) == target:
680
+ break
681
+ except OSError:
682
+ pass
683
+ if info is not None:
684
+ quarantine = ".uai-files-stray-" + os.urandom(16).hex()
685
+ try:
686
+ os.mkdir(quarantine, 0o700, dir_fd=directory)
687
+ except FileExistsError:
688
+ continue
689
+ quarantine_fd = os.open(quarantine, flags, dir_fd=directory)
690
+ try:
691
+ try:
692
+ os.rename("files", "entry", src_dir_fd=directory, dst_dir_fd=quarantine_fd)
693
+ except FileNotFoundError:
694
+ os.close(quarantine_fd)
695
+ quarantine_fd = None
696
+ os.rmdir(quarantine, dir_fd=directory)
697
+ continue
698
+ os.fchown(quarantine_fd, workspace_owner.st_uid, workspace_owner.st_gid)
699
+ os.fchmod(quarantine_fd, 0o700)
700
+ finally:
701
+ if quarantine_fd is not None:
702
+ os.close(quarantine_fd)
703
+ try:
704
+ os.symlink(target, "files", dir_fd=directory)
705
+ break
706
+ except FileExistsError:
707
+ continue
708
+ else:
709
+ raise RuntimeError("workspace Files link could not be published")
710
+ if os.readlink("files", dir_fd=directory) != target:
711
+ raise RuntimeError("workspace Files link was replaced")
712
+ try:
713
+ os.mkdir(".uai", 0o700, dir_fd=directory)
714
+ os.chown(".uai", workspace_owner.st_uid, workspace_owner.st_gid, dir_fd=directory, follow_symlinks=False)
715
+ except FileExistsError:
716
+ pass
717
+ # The marker is advisory task metadata, not part of the visibility proof.
718
+ # A task-controlled non-directory .uai, a wrong owner, or a non-regular
719
+ # marker must not turn a successfully hidden root into machine retirement.
720
+ try:
721
+ uai = os.open(".uai", flags, dir_fd=directory)
722
+ except OSError:
723
+ uai = None
724
+ if uai is not None:
725
+ try:
726
+ uai_owner = os.fstat(uai)
727
+ if (uai_owner.st_uid, uai_owner.st_gid) == (workspace_owner.st_uid, workspace_owner.st_gid):
728
+ try:
729
+ descriptor = os.open(
730
+ "files-mounted",
731
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW | os.O_NONBLOCK | os.O_CLOEXEC,
732
+ 0o600,
733
+ dir_fd=uai,
734
+ )
735
+ except OSError:
736
+ descriptor = None
737
+ if descriptor is not None:
738
+ try:
739
+ marker_info = os.fstat(descriptor)
740
+ if stat.S_ISREG(marker_info.st_mode):
741
+ os.ftruncate(descriptor, 0)
742
+ os.write(descriptor, mode.encode("ascii"))
743
+ os.fchmod(descriptor, 0o600)
744
+ os.fchown(descriptor, uai_owner.st_uid, uai_owner.st_gid)
745
+ finally:
746
+ os.close(descriptor)
747
+ finally:
748
+ os.close(uai)
749
+ # The workspace mutations above are task-controlled and may take time.
750
+ # Prove every expected mount again at the final visibility boundary.
751
+ if not exact_topology():
752
+ raise RuntimeError("the staged Files mount topology changed before publish")
753
+ # Last: only now can the task traverse the fixed root, after the link and
754
+ # any safe advisory marker work are complete.
755
+ os.fchmod(mount_root, 0o755)
756
+ finally:
757
+ os.close(directory)
758
+ os.close(mount_root)
759
+ `;
760
+
761
+ /** Path visibility only. Physical mount retirement deliberately remains in
762
+ * the asynchronous strict helper, which quiesces exact task-user holders. */
763
+ export const TASK_FILES_V1_HIDE_HELPER = String.raw`
764
+ import os
765
+ import stat
766
+ import sys
767
+
768
+ workspace, root = sys.argv[1:3]
769
+ if workspace != "/workspace" or root != "/mnt/uai-files":
770
+ raise RuntimeError("invalid Files hide root")
771
+ flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC
772
+ try:
773
+ mount_root = os.open(root, flags)
774
+ except FileNotFoundError:
775
+ mount_root = None
776
+ if mount_root is not None:
777
+ try:
778
+ os.fchmod(mount_root, 0o700)
779
+ finally:
780
+ os.close(mount_root)
781
+ try:
782
+ directory = os.open(workspace, flags)
783
+ except FileNotFoundError:
784
+ directory = None
785
+ if directory is not None:
786
+ try:
787
+ try:
788
+ info = os.stat("files", dir_fd=directory, follow_symlinks=False)
789
+ except OSError:
790
+ info = None
791
+ if info is not None and stat.S_ISLNK(info.st_mode):
792
+ try:
793
+ if os.readlink("files", dir_fd=directory) == root:
794
+ os.unlink("files", dir_fd=directory)
795
+ except OSError:
796
+ pass
797
+ try:
798
+ uai = os.open(".uai", flags, dir_fd=directory)
799
+ except OSError:
800
+ uai = None
801
+ if uai is not None:
802
+ try:
803
+ try:
804
+ os.unlink("files-mounted", dir_fd=uai)
805
+ except OSError:
806
+ pass
807
+ finally:
808
+ os.close(uai)
809
+ finally:
810
+ os.close(directory)
811
+ `;
812
+
813
+ /** Detach only the fixed root-owned mounts. The workspace operation is an
814
+ * exact symlink unlink through its opened parent; no root unmount ever sees
815
+ * task-controlled path components. */
816
+ export const TASK_FILES_V1_UNMOUNT_HELPER = String.raw`
817
+ import os
818
+ import stat
819
+ import subprocess
820
+ import sys
821
+
822
+ workspace, root = sys.argv[1:3]
823
+ if workspace != "/workspace" or root != "/mnt/uai-files":
824
+ raise RuntimeError("invalid Files cleanup root")
825
+ flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC
826
+ # Hide the staged root before any awaited unmount. Open handles are dealt with
827
+ # by the ordinary unmount proof below; no lazy detach is accepted as cleanup.
828
+ try:
829
+ mount_root = os.open(root, flags)
830
+ except FileNotFoundError:
831
+ mount_root = None
832
+ if mount_root is not None:
833
+ try:
834
+ os.fchmod(mount_root, 0o700)
835
+ finally:
836
+ os.close(mount_root)
837
+ try:
838
+ directory = os.open(workspace, flags)
839
+ except FileNotFoundError:
840
+ directory = None
841
+ if directory is not None:
842
+ try:
843
+ try:
844
+ info = os.stat("files", dir_fd=directory, follow_symlinks=False)
845
+ except OSError:
846
+ info = None
847
+ if info is not None and stat.S_ISLNK(info.st_mode):
848
+ try:
849
+ if os.readlink("files", dir_fd=directory) == root:
850
+ os.unlink("files", dir_fd=directory)
851
+ except OSError:
852
+ pass
853
+ try:
854
+ uai = os.open(".uai", flags, dir_fd=directory)
855
+ except OSError:
856
+ uai = None
857
+ if uai is not None:
858
+ try:
859
+ try:
860
+ os.unlink("files-mounted", dir_fd=uai)
861
+ except OSError:
862
+ pass
863
+ finally:
864
+ os.close(uai)
865
+ finally:
866
+ os.close(directory)
867
+
868
+ def decode_mount_path(value):
869
+ result = bytearray()
870
+ index = 0
871
+ while index < len(value):
872
+ if value[index:index + 1] == b"\\" and index + 3 < len(value) and all(48 <= part <= 55 for part in value[index + 1:index + 4]):
873
+ result.append(int(value[index + 1:index + 4], 8))
874
+ index += 4
875
+ else:
876
+ result.append(value[index])
877
+ index += 1
878
+ return os.fsdecode(bytes(result))
879
+
880
+ def mounts(path="/proc/self/mountinfo"):
881
+ found = []
882
+ with open(path, "rb") as stream:
883
+ for line in stream:
884
+ before, after = line.rstrip(b"\n").split(b" - ", 1)
885
+ fields = before.split(b" ")
886
+ tail = after.split(b" ")
887
+ found.append((
888
+ int(fields[0]),
889
+ decode_mount_path(fields[4]),
890
+ tail[0].decode("ascii"),
891
+ tail[1].decode("ascii"),
892
+ tuple(int(part) for part in fields[2].split(b":")),
893
+ ))
894
+ return found
895
+
896
+ targets = {root + "/org", root + "/me"}
897
+ legacy = {workspace.rstrip("/") + "/files/org", workspace.rstrip("/") + "/files/me"}
898
+
899
+ def mount_attempted(create=False):
900
+ runtime = os.open("/run/uai", flags)
901
+ try:
902
+ runtime_info = os.fstat(runtime)
903
+ if runtime_info.st_uid != 0:
904
+ raise RuntimeError("the Files runtime directory is not root-owned")
905
+ try:
906
+ info = os.stat("files-mount-attempted", dir_fd=runtime, follow_symlinks=False)
907
+ except FileNotFoundError:
908
+ if not create:
909
+ return False
910
+ try:
911
+ descriptor = os.open(
912
+ "files-mount-attempted",
913
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW | os.O_CLOEXEC,
914
+ 0o600,
915
+ dir_fd=runtime,
916
+ )
917
+ except FileExistsError:
918
+ # Another strict-cleanup attempt armed the same one-way latch.
919
+ # Revalidate its directory entry and continue best-effort
920
+ # unmounting rather than losing the retirement witness.
921
+ info = os.stat("files-mount-attempted", dir_fd=runtime, follow_symlinks=False)
922
+ else:
923
+ try:
924
+ info = os.fstat(descriptor)
925
+ os.write(descriptor, b"1\n")
926
+ os.fsync(descriptor)
927
+ finally:
928
+ os.close(descriptor)
929
+ if info.st_uid != 0 or not stat.S_ISREG(info.st_mode) or stat.S_IMODE(info.st_mode) != 0o600:
930
+ raise RuntimeError("the Files mount-attempt marker is unsafe")
931
+ return True
932
+ finally:
933
+ os.close(runtime)
934
+
935
+ initial = mounts()
936
+ under_root = [(path, fs_type, source) for _mid, path, fs_type, source, _device in initial if path == root or path.startswith(root + "/")]
937
+ counts = {path: sum(1 for observed, _fs_type, _source in under_root if observed == path) for path in targets}
938
+ unexpected = [
939
+ path for path, fs_type, source in under_root
940
+ if path not in targets or fs_type != "fuse" or source != "mountpoint-s3"
941
+ ]
942
+ unexpected += [path for path, count in counts.items() if count > 1]
943
+ unexpected += [
944
+ path for _mid, path, fs_type, source, _device in initial
945
+ if path in legacy or (fs_type == "fuse" and source == "mountpoint-s3" and path not in targets)
946
+ ]
947
+ attempted = mount_attempted()
948
+ if unexpected or any(counts.values()):
949
+ # An older/lost attempt may have reached mount-s3 before its marker was
950
+ # observed. Latch retirement before changing the local mount topology.
951
+ attempted = mount_attempted(create=True)
952
+ if unexpected:
953
+ raise RuntimeError("legacy or unexpected Files mount remains; retire this task machine")
954
+
955
+ failures = []
956
+ for path in sorted(targets):
957
+ records = [record for record in mounts() if record[1] == path]
958
+ if not records:
959
+ continue
960
+ if len(records) != 1 or records[0][2:4] != ("fuse", "mountpoint-s3"):
961
+ failures.append(path + ":identity-changed")
962
+ continue
963
+ ended = subprocess.run(["/usr/bin/umount", path], env={}, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
964
+ if ended.returncode != 0:
965
+ failures.append(path + ":unmount")
966
+ if any(mount_path == path for _mid, mount_path, _fs_type, _source, _device in mounts()):
967
+ failures.append(path + ":still-mounted")
968
+ if attempted:
969
+ # A /proc scan cannot prove every private mount namespace or detached fd
970
+ # gone: a task can fork during the scan, retain a namespace fd, or become
971
+ # root through the task image's sudo. The boot-local latch makes runtime
972
+ # destruction the sole success proof once mount-s3 could have started.
973
+ failures.append("task-machine-retirement-required")
974
+ if failures:
975
+ raise RuntimeError(";".join(failures))
976
+ `;