host: system apply's syscalls behind the package's platform tags
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015QCUCpfxbCyEAVQ2m51dH5
3 files changed,
+69
−29
internal/host/systemapply.go+0 −29internal/host/systemapply_unix.go+46 −0internal/host/systemapply_windows.go+23 −0
diff --git a/internal/host/systemapply.go b/internal/host/systemapply.go| index 78e2df5ad..0d46a97fe 100644 |
| --- a/internal/host/systemapply.go |
| +++ b/internal/host/systemapply.go |
| @@ -12,7 +12,6 @@ import ( |
| "os" |
| "path/filepath" |
| "strings" |
| - "syscall" |
| "time" |
| "github.com/carlosframework/platform/internal/release" |
| @@ -204,29 +203,6 @@ func (a *systemApplier) readRequest() (systemRequest, error) { |
| return req, nil |
| } |
| -// openBeneath opens root/parts... one component at a time with |
| -// O_DIRECTORY|O_NOFOLLOW for every directory and O_NOFOLLOW for the leaf, |
| -// so no component under a user-owned tree can be a symlink. |
| -func openBeneath(root string, parts ...string) (*os.File, error) { |
| - fd, err := syscall.Open(root, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_CLOEXEC, 0) |
| - if err != nil { |
| - return nil, fmt.Errorf("open %s: %w", root, err) |
| - } |
| - for i, part := range parts { |
| - flags := syscall.O_RDONLY | syscall.O_NOFOLLOW | syscall.O_CLOEXEC |
| - if i < len(parts)-1 { |
| - flags |= syscall.O_DIRECTORY |
| - } |
| - next, err := syscall.Openat(fd, part, flags, 0) |
| - syscall.Close(fd) |
| - if err != nil { |
| - return nil, fmt.Errorf("open %s: %w", filepath.Join(append([]string{root}, parts[:i+1]...)...), err) |
| - } |
| - fd = next |
| - } |
| - return os.NewFile(uintptr(fd), filepath.Join(append([]string{root}, parts...)...)), nil |
| -} |
| - |
| // verifyPointer fetches THIS box's channel pointer and verifies it with |
| // the release public key. The channel is root's own (host.env), never the |
| // request's. A store error is deferred, a bad pointer refused. |
| @@ -303,8 +279,3 @@ func (a *systemApplier) stageCandidate(p *release.SystemPointer, req systemReque |
| } |
| return staged, nil |
| } |
| - |
| -func flockExclusive(f *os.File) error { return syscall.Flock(int(f.Fd()), syscall.LOCK_EX) } |
| -func flockExclusiveNonblock(f *os.File) error { |
| - return syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) |
| -} |
diff --git a/internal/host/systemapply_unix.go b/internal/host/systemapply_unix.go| new file mode 100644 |
| index 000000000..d906a1116 |
| --- /dev/null |
| +++ b/internal/host/systemapply_unix.go |
| @@ -0,0 +1,46 @@ |
| +//go:build !windows |
| + |
| +package host |
| + |
| +import ( |
| + "fmt" |
| + "os" |
| + "path/filepath" |
| + "syscall" |
| +) |
| + |
| +// The apply's two kernel-level needs, on the platform it actually runs on: |
| +// opening a path under a user-owned tree without following one symlink, and |
| +// an advisory lock that fails rather than waits when another apply holds it. |
| + |
| +// openBeneath opens root/parts... one component at a time with |
| +// O_DIRECTORY|O_NOFOLLOW for every directory and O_NOFOLLOW for the leaf, |
| +// so no component under a user-owned tree can be a symlink. |
| +func openBeneath(root string, parts ...string) (*os.File, error) { |
| + fd, err := syscall.Open(root, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_CLOEXEC, 0) |
| + if err != nil { |
| + return nil, fmt.Errorf("open %s: %w", root, err) |
| + } |
| + for i, part := range parts { |
| + flags := syscall.O_RDONLY | syscall.O_NOFOLLOW | syscall.O_CLOEXEC |
| + if i < len(parts)-1 { |
| + flags |= syscall.O_DIRECTORY |
| + } |
| + next, err := syscall.Openat(fd, part, flags, 0) |
| + syscall.Close(fd) |
| + if err != nil { |
| + return nil, fmt.Errorf("open %s: %w", filepath.Join(append([]string{root}, parts[:i+1]...)...), err) |
| + } |
| + fd = next |
| + } |
| + return os.NewFile(uintptr(fd), filepath.Join(append([]string{root}, parts...)...)), nil |
| +} |
| + |
| +// flockExclusive waits for the apply lock (the recovery run must not skip |
| +// its work); flockExclusiveNonblock refuses when another apply holds it, so |
| +// a path-unit-triggered run exits quietly instead of queueing. |
| +func flockExclusive(f *os.File) error { return lockFileEx(f) } |
| + |
| +func flockExclusiveNonblock(f *os.File) error { |
| + return syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) |
| +} |
diff --git a/internal/host/systemapply_windows.go b/internal/host/systemapply_windows.go| new file mode 100644 |
| index 000000000..1dec23f4d |
| --- /dev/null |
| +++ b/internal/host/systemapply_windows.go |
| @@ -0,0 +1,23 @@ |
| +//go:build windows |
| + |
| +package host |
| + |
| +import ( |
| + "errors" |
| + "os" |
| +) |
| + |
| +// The platform self-update is a root oneshot on a Linux box; the Windows |
| +// build exists only so the carlos CLI compiles for workstation use. Both |
| +// primitives refuse rather than pretend: an apply that cannot lock, or that |
| +// cannot open a path without following symlinks, must do nothing at all. |
| + |
| +var errSystemUnsupported = errors.New("carlos system apply: unsupported on this platform") |
| + |
| +func openBeneath(root string, parts ...string) (*os.File, error) { |
| + return nil, errSystemUnsupported |
| +} |
| + |
| +func flockExclusive(f *os.File) error { return errSystemUnsupported } |
| + |
| +func flockExclusiveNonblock(f *os.File) error { return errSystemUnsupported } |