Skip to content

Implement Darwin (MacOS)#169

Open
lancepioch wants to merge 9 commits intomainfrom
lance/mac
Open

Implement Darwin (MacOS)#169
lancepioch wants to merge 9 commits intomainfrom
lance/mac

Conversation

@lancepioch
Copy link
Member

@lancepioch lancepioch commented Feb 7, 2026

Summary by CodeRabbit

  • New Features

    • macOS/Darwin support with native filesystem behavior and a Darwin build target.
  • Bug Fixes

    • Container resource constraints now apply only on Linux, improving Docker Desktop compatibility on macOS.
  • Documentation

    • Added comprehensive macOS setup and build guidance.
  • Chores

    • Reduced websocket log verbosity for common cleanup/error cases.
  • Tests

    • Added platform-aware tests covering filesystem behavior and feature detection.

@lancepioch lancepioch self-assigned this Feb 7, 2026
@lancepioch lancepioch requested a review from a team as a code owner February 7, 2026 19:05
@lancepioch lancepioch added the enhancement New feature or request label Feb 7, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 7, 2026

📝 Walkthrough

Walkthrough

Adds macOS/Darwin support and platform splits across filesystem, openat2 detection, system info, and build targets; introduces Darwin-specific implementations and tests, and updates build/Makefile targets and logging levels.

Changes

Cohort / File(s) Summary
Build Configuration
Makefile
Added build-darwin target, updated cross-build dependency and .PHONY list for Darwin cross-builds.
OpenAt2 Detection
config/config.go, config/config_openat_darwin.go, config/config_openat_linux.go, config/config_openat_linux_test.go, config/config_openat_test.go
Moved OpenAt2 detection to platform files: Darwin stub (always false) and Linux runtime detection with atomic caching; removed OpenAt2 logic from main config and added tests.
Filesystem Core (ufs)
internal/ufs/file.go, internal/ufs/file_darwin.go, internal/ufs/file_linux.go, internal/ufs/fs_darwin.go, internal/ufs/fs_linux.go, internal/ufs/fs_unix.go, internal/ufs/fs_unix_test.go
Split platform-specific FS behavior: per-OS fdPath, open/openat2 handling, Chtimesat, and atomic openat2 fallback. Resolved symlinks at FS init and adjusted tests.
Dirent / Directory Walking
internal/ufs/walk_darwin.go, internal/ufs/walk_linux.go, internal/ufs/walk_unix.go
Introduced OS-specific getdents/name extraction for Darwin and Linux; consolidated readDir to use new helpers and removed prior reflect/unsafe name parsing.
Platform Tests
internal/ufs/fs_platform_test.go
Added unix-tagged tests exercising fdPath, openat path validation, Chtimesat behavior with zero time, and O_LARGEFILE expectations.
File Flags Constants
internal/ufs/file_darwin.go, internal/ufs/file_linux.go, internal/ufs/file.go
Removed generic O_LARGEFILE/AT_EMPTY_PATH from common file and provided OS-specific definitions (Darwin no-op, Linux alias to unix.O_LARGEFILE).
Server Filesystem & Stat
server/filesystem/stat_darwin.go, server/filesystem/stat_test.go, server/filesystem/filesystem_test.go
Added Darwin-specific Stat.CTime() implementation and tests for CTime and JSON marshaling; tests now resolve tmpdir symlinks.
System Detection
system/system.go, config/config.go
Short-circuited OS detection for Darwin: return "macOS"/"darwin" without reading os-release; EnsurePelicanUser uses runtime user info on Darwin.
Environment Settings
environment/settings.go
Guarded BlkioWeight assignment to only apply on Linux (runtime.GOOS check).
Websocket Logging
router/router_server_ws.go, router/websocket/listeners.go
Downgraded several websocket-related log messages from Error/Warning to Info for normal disconnect/cleanup scenarios.
Documentation
macos.md
New macOS guide documenting prerequisites, build/run instructions, and rationale for platform-specific code changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • QuintenQVD0

Poem

🐰
I nibble on symlinks, sniff the air,
Darwin paths now handled with care.
Openat dreams that once were bold,
Now split by OS, tidy and told.
A rabbit cheers—cross-builds unfold!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implement Darwin (MacOS)' directly reflects the main change in the changeset: comprehensive macOS/Darwin support implementation across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch lance/mac

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
Makefile (1)

29-29: Nit: .PHONY declares all but no all target is defined.

Consider removing all from the .PHONY list, or adding an all target (e.g., as an alias for build).

Proposed fix
-.PHONY: all build build-darwin cross-build clean test debug rmdebug
+.PHONY: build build-darwin cross-build clean test debug rmdebug
internal/ufs/fs_unix.go (1)

706-717: Dead branch: useOpenat2 can never be true at Line 709.

The Load() at Line 677 already returns early when useOpenat2 is true. Since the flag only transitions true → false, it's guaranteed to be false here, making the "openat2" op assignment unreachable.

Not a bug — error messages will always correctly say "openat" — but the conditional is misleading.

♻️ Suggested simplification
-	op := "openat"
-	if fs.useOpenat2.Load() {
-		op = "openat2"
-	}
 	return fd, &PathError{
-		Op:   op,
+		Op:   "openat",
 		Path: name,
 		Err:  ErrBadPathResolution,
 	}
internal/ufs/walk_linux.go (1)

18-33: Previous fallback panic fix is properly addressed.

The defensive branch now correctly returns name (bounded by Reclen) instead of name[:len(de.Name)].

One nit: if you're aiming for defensive programming, a guard on ml <= 0 before the unsafe.Slice call would prevent a panic on corrupted Reclen values, though the kernel should never produce that.

🛡️ Optional guard
 	ml := int(de.Reclen) - nameOffset
+	if ml <= 0 {
+		return nil
+	}
 
 	name := unsafe.Slice((*byte)(unsafe.Pointer(&de.Name[0])), ml)
📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a7a902 and 3532707.

📒 Files selected for processing (4)
  • Makefile
  • internal/ufs/fs_platform_test.go
  • internal/ufs/fs_unix.go
  • internal/ufs/walk_linux.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/ufs/fs_platform_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
internal/ufs/fs_unix.go (1)
internal/ufs/file.go (2)
  • O_DIRECTORY (169-169)
  • O_RDONLY (152-152)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build and Test (ubuntu-22.04, 1.25.1, linux, amd64)
  • GitHub Check: Build and Test (ubuntu-22.04, 1.24.7, linux, amd64)
  • GitHub Check: Analyze (go)
🔇 Additional comments (9)
Makefile (2)

20-22: LGTM!

The build-darwin target is consistent with the existing build target pattern—same flags, naming convention, and dual-architecture approach.


24-24: LGTM — previous review concerns addressed.

cross-build now correctly depends on clean build build-darwin, producing both Linux and Darwin binaries.

internal/ufs/fs_unix.go (5)

28-31: LGTM — atomic.Bool eliminates the data race on useOpenat2.

Clean fix for the previously-flagged race between concurrent openat callers.


40-45: Symlink resolution for macOS path normalization looks good.

Silently keeping the original basePath on EvalSymlinks failure is sensible — if the path can't be resolved, downstream operations will surface their own errors.


684-685: Good abstraction — fdPath(fd) cleanly replaces the Linux-specific /proc/self/fd resolution.

This enables the Darwin port while keeping the rest of the safety-check logic unchanged.


763-764: LGTM — AT_FDCWD is correct here for opening the absolute basePath.


655-662: ENOSYS fallback logic is correct — atomic store ensures subsequent calls skip openat2 entirely.

internal/ufs/walk_linux.go (2)

10-13: LGTM — thin wrapper over unix.Getdents.


15-16: LGTM — compile-time constant for the Name field offset.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@internal/ufs/fs_darwin.go`:
- Around line 40-58: The code in UnixFS.Chtimesat uses linux-style Stat_t fields
Atim/Mtim which don't exist on macOS; replace references to st.Atim and st.Mtim
with Darwin names st.Atimespec and st.Mtimespec and convert their timespecs to
time.Time (e.g., using st.Atimespec.Unix()/UnixNano() or an equivalent
conversion) before assigning atime/mtime so the function compiles and uses the
correct Darwin fields.

In `@internal/ufs/fs_unix.go`:
- Around line 653-660: The field useOpenat2 on UnixFS is written without
synchronization causing a data race; change the UnixFS struct to use an
atomic.Bool for useOpenat2, update all reads to use fs.useOpenat2.Load() and the
write in the open path to fs.useOpenat2.Store(false) (so the branch around
fs._openat2 / fs._openat remains correct), and add the "sync/atomic" import (or
the atomic package alias used) so the race detector is silenced.

In `@internal/ufs/walk_linux.go`:
- Around line 18-33: The defensive fallback in nameFromDirent uses
name[:len(de.Name)] which can panic when ml (derived from de.Reclen) is smaller
than len(de.Name); change the fallback to return the already-sliced name (the
variable name) directly so you never slice past its length—update the fallback
in function nameFromDirent to return name instead of name[:len(de.Name)].

In `@Makefile`:
- Line 24: The cross-build target currently depends on "clean build compress"
but omits "build-darwin" and references a non-existent "compress" target; update
the cross-build dependency list to include build-darwin (so cross-build produces
darwin artifacts) and either add a well-defined compress target (e.g.,
packaging/compression step) or remove "compress" from the dependency list; edit
the Makefile entry for the cross-build target and verify the related targets
"build", "build-darwin", and "clean" are present and correctly implemented.
🧹 Nitpick comments (8)
router/router_server_ws.go (1)

78-78: Log level downgrade for websocket close failure looks intentional and consistent.

This aligns with the similar changes in router/websocket/listeners.go. However, a failed close could occasionally indicate a real issue (e.g., resource leak). Consider using Warn as a middle ground so these don't get completely buried in production, while still not alarming on expected closes.

router/websocket/listeners.go (1)

100-100: Consider keeping Warn for websocket send failures.

Unlike close errors (which are expected during shutdown), a failure to send an event over the websocket may indicate a genuine issue — broken pipe, serialization error, or resource exhaustion. Logging this at Info level risks burying actionable signals in production. Warn would be a more appropriate middle ground.

Proposed change
-		h.Logger().WithField("event", evt).WithField("error", err2).Info("failed to send event over server websocket")
+		h.Logger().WithField("event", evt).WithField("error", err2).Warn("failed to send event over server websocket")
environment/settings.go (1)

120-123: Platform guard for BlkioWeight is reasonable but may be overly conservative.

BlkioWeight is a Linux cgroup feature, so guarding it makes sense for native macOS execution. However, note that Docker Desktop on macOS runs a Linux VM, and the Docker API may accept BlkioWeight there. If users running Wings on macOS with Docker Desktop need IO weight support, this guard would silently skip it.

Consider adding a comment explaining the rationale (e.g., Darwin doesn't support cgroup blkio) so future maintainers understand the intent.

Suggested comment
+	// BlkioWeight is only supported on Linux (cgroup blkio controller).
+	// On macOS, Docker Desktop uses a Linux VM but may not expose this setting.
 	if runtime.GOOS == "linux" {
 		resources.BlkioWeight = l.IoWeight
 	}
Makefile (1)

29-29: Add test to .PHONY declaration.

The static analysis tool correctly flags that test is missing from the .PHONY list. Since test is a valid filename, not declaring it as phony could cause issues if a file named test exists.

Proposed fix
-.PHONY: all build build-darwin compress clean
+.PHONY: all build build-darwin cross-build compress clean test debug rmdebug
macos.md (1)

40-47: Docker socket path may vary by Docker Desktop version.

The path ~/.docker/run/docker.sock is the default for recent Docker Desktop versions, but some installations use /var/run/docker.sock natively or $HOME/.docker/desktop/docker.sock. Consider adding a brief note to verify the actual socket path (e.g., docker context inspect or ls ~/.docker/run/docker.sock) before creating the symlink.

config/config.go (1)

505-515: Darwin user resolution looks correct.

The approach mirrors the existing rootless-mode logic (lines 525–534). Since macOS lacks useradd/adduser, using the current user is the right call.

Note: the user.Current()MustInt(u.Uid)MustInt(u.Gid) pattern is now repeated in three places (darwin, distroless, rootless). Consider extracting a small helper like setCurrentUser() to reduce duplication, but this is not blocking.

♻️ Optional: extract helper to reduce duplication
+// setCurrentUser sets the system user configuration from the current OS user.
+func setCurrentUser() error {
+	u, err := user.Current()
+	if err != nil {
+		return err
+	}
+	_config.System.Username = u.Username
+	_config.System.User.Uid = system.MustInt(u.Uid)
+	_config.System.User.Gid = system.MustInt(u.Gid)
+	return nil
+}

Then in EnsurePelicanUser, the darwin, distroless, and rootless blocks could call setCurrentUser() (with the distroless block keeping its env-var override logic separately).

config/config_openat_linux.go (1)

15-40: Benign race on concurrent first calls to UseOpenat2().

Two goroutines can both observe openat2Set as false and enter the detection path concurrently. Because the syscall result is deterministic for a given kernel, the final cached value will be correct in practice, but the detection work (including the Openat2 syscall + Close) may execute more than once.

A sync.Once would eliminate the redundant work and make the intent clearer. Not a correctness bug given the deterministic nature of the check, so feel free to defer.

internal/ufs/fs_linux.go (1)

58-72: Nit: Sec field in the UTIME_OMIT timespec.

On Line 62, both Sec and Nsec are set to unix.UTIME_OMIT. Per utimensat(2), only the tv_nsec field is inspected for the UTIME_OMIT sentinel — tv_sec is ignored. This works correctly but is slightly misleading. Consider setting Sec: 0 for clarity.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4586d7b and 3a7a902.

📒 Files selected for processing (25)
  • Makefile
  • config/config.go
  • config/config_openat_darwin.go
  • config/config_openat_linux.go
  • config/config_openat_linux_test.go
  • config/config_openat_test.go
  • environment/settings.go
  • internal/ufs/file.go
  • internal/ufs/file_darwin.go
  • internal/ufs/file_linux.go
  • internal/ufs/fs_darwin.go
  • internal/ufs/fs_linux.go
  • internal/ufs/fs_platform_test.go
  • internal/ufs/fs_unix.go
  • internal/ufs/fs_unix_test.go
  • internal/ufs/walk_darwin.go
  • internal/ufs/walk_linux.go
  • internal/ufs/walk_unix.go
  • macos.md
  • router/router_server_ws.go
  • router/websocket/listeners.go
  • server/filesystem/filesystem_test.go
  • server/filesystem/stat_darwin.go
  • server/filesystem/stat_test.go
  • system/system.go
🧰 Additional context used
🧬 Code graph analysis (11)
server/filesystem/stat_darwin.go (1)
server/filesystem/stat.go (1)
  • Stat (14-17)
internal/ufs/file_linux.go (1)
internal/ufs/file_darwin.go (1)
  • O_LARGEFILE (4-4)
internal/ufs/fs_platform_test.go (1)
server/filesystem/stat.go (1)
  • Stat (14-17)
config/config_openat_darwin.go (1)
config/config_openat_linux.go (1)
  • UseOpenat2 (15-41)
config/config.go (2)
system/system.go (1)
  • System (60-67)
system/utils.go (1)
  • MustInt (35-41)
config/config_openat_linux.go (3)
config/config_openat_darwin.go (1)
  • UseOpenat2 (5-7)
config/config.go (1)
  • Get (439-447)
system/system.go (1)
  • System (60-67)
config/config_openat_test.go (3)
config/config.go (3)
  • Set (408-420)
  • Configuration (320-379)
  • SystemConfiguration (138-250)
config/config_openat_darwin.go (1)
  • UseOpenat2 (5-7)
config/config_openat_linux.go (1)
  • UseOpenat2 (15-41)
server/filesystem/stat_test.go (3)
server/filesystem/filesystem_test.go (1)
  • NewFs (20-55)
server/filesystem/stat.go (1)
  • Stat (14-17)
server/filesystem/errors.go (1)
  • Error (25-37)
internal/ufs/fs_unix.go (1)
internal/ufs/file.go (2)
  • O_DIRECTORY (169-169)
  • O_RDONLY (152-152)
internal/ufs/fs_linux.go (3)
internal/ufs/fs_unix.go (1)
  • UnixFS (23-30)
internal/ufs/file.go (1)
  • O_CLOEXEC (172-172)
internal/ufs/file_linux.go (1)
  • O_LARGEFILE (5-5)
internal/ufs/fs_darwin.go (1)
internal/ufs/fs_unix.go (1)
  • UnixFS (23-30)
🪛 checkmake (0.2.2)
Makefile

[warning] 29-29: Missing required phony target "test"

(minphony)

🪛 LanguageTool
macos.md

[style] ~160-~160: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... not exist on Darwin. - unix.Getdents does not exist on Darwin; `unix.Getdirentrie...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~174-~174: Ensure spelling is correct
Context: ...at_linux.go| OriginalCTime()usingunix.Stat_t.Ctim(was//go:build linux) | | stat_darw...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~175-~175: Ensure spelling is correct
Context: ...at_darwin.go|CTime()handling bothunix.Stat_t.Ctimandsyscall.Stat_t.Ctimespec| gola...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (go)
  • GitHub Check: Build and Test (ubuntu-22.04, 1.24.7, linux, amd64)
  • GitHub Check: Build and Test (ubuntu-22.04, 1.25.1, linux, amd64)
🔇 Additional comments (31)
server/filesystem/filesystem_test.go (1)

34-37: Good fix for macOS symlink resolution in test setup.

This correctly handles the /var/private/var symlink on macOS, preventing spurious ErrBadPathResolution failures in tests.

router/websocket/listeners.go (1)

30-33: Log level changes for websocket lifecycle events are reasonable.

These log during normal connection teardown, so Info is appropriate.

internal/ufs/file_linux.go (1)

1-5: Clean platform-specific constant definition.

The Linux/Darwin split for O_LARGEFILE is correct: Darwin universally supports large files (hence 0), while Linux needs the explicit flag.

internal/ufs/file.go (1)

172-173: No action needed. AT_EMPTY_PATH is not referenced anywhere in the codebase and is not defined in any platform-specific files, confirming it was safely removed and is not required for the current implementation.

system/system.go (2)

115-129: LGTM — Darwin-specific OS detection looks correct.

Clean branching: Darwin skips osrelease.Read() (which would fail on macOS) and returns a sensible hardcoded value, while the Linux path retains the existing fallback chain.


209-218: LGTM — Early return avoids reading a non-existent file on macOS.

server/filesystem/stat_darwin.go (1)

10-22: The comment and TODO are accurate — Ctim/Ctimespec is status-change time, not creation time.

On macOS, the actual file creation (birth) time is available via Birthtimespec on syscall.Stat_t. Since the TODO already flags this for removal and the Linux counterpart has the same semantics, this is consistent. Just noting for awareness that if the intent ever shifts to truly returning creation time on Darwin, Birthtimespec would be the correct field.

server/filesystem/stat_test.go (2)

9-29: Test looks good but may be fragile if CTime() returns zero time.

On line 23, the test asserts CTime() is non-zero, but the Darwin implementation falls back to time.Time{} if neither unix.Stat_t nor syscall.Stat_t is the underlying type. If a future Go or platform change alters the Sys() return type, this test would fail with a non-obvious error. Consider a more descriptive message or logging st.Sys() type on failure for easier debugging.

That said, this is minor — the current implementations should always match one of the two branches.


31-71: LGTM — Good coverage of JSON marshaling fields.

The test validates the key fields (created, modified, name) and timestamp format. Clean and readable.

macos.md (1)

1-203: Well-written documentation covering both user setup and developer context.

The separation between user-facing setup instructions and developer-focused code change documentation is clear and useful. The explanations for why specific settings are needed (Docker Desktop file sharing paths, machine_id.enable: false, etc.) are particularly helpful.

internal/ufs/file_darwin.go (1)

1-4: LGTM — Correct no-op constant for Darwin.

internal/ufs/fs_unix_test.go (1)

37-40: LGTM — Necessary symlink resolution for macOS test compatibility.

Consistent with the same pattern applied in server/filesystem/filesystem_test.go's NewFs().

internal/ufs/walk_darwin.go (1)

9-18: LGTM — Clean Darwin-specific dirent handling.

Using Namlen directly is the correct approach for Darwin and avoids the NUL-scanning complexity required on Linux. The unsafe.Slice usage is idiomatic for this pattern.

config/config_openat_test.go (1)

8-28: LGTM — reasonable cross-platform smoke test.

Clean setup and the OS-specific assertions are correct: Darwin's UseOpenat2() always returns false, and Linux's result is kernel-dependent.

One minor note: on Linux, if TestUseOpenat2ConfigOverride (in config_openat_linux_test.go) runs first, the atomic cache (openat2Set) will already be populated, so this test won't actually exercise the "auto" detection path — it'll return the cached value. Since the stated goal is just "doesn't panic," that's fine, but worth being aware of if you ever want stronger coverage of the auto path in isolation.

config/config_openat_darwin.go (1)

1-7: LGTM!

Clean platform stub. The comment accurately explains why openat2 is unavailable on Darwin.

config/config.go (1)

804-807: LGTM — prevents osrelease.Read() failure on macOS.

/etc/os-release doesn't exist on macOS, so this early return is necessary to avoid a runtime error.

config/config_openat_linux_test.go (1)

5-25: LGTM — clean config override test.

Properly resets the atomic cache before each assertion, ensuring the config mode is re-evaluated. Both forced "openat" and "openat2" paths are covered.

internal/ufs/fs_unix.go (3)

36-49: LGTM — symlink resolution in base path is essential for macOS.

On macOS, /var is a symlink to /private/var, so without this resolution, the unsafeIsPathInsideOfBase prefix check would fail for any fd whose resolved path goes through /private/var. Silently ignoring EvalSymlinks errors is reasonable here — the path may not exist yet during initialization.


682-683: Good: fdPath(fd) replaces the Linux-specific /proc/self/fd approach.

This is the correct abstraction for cross-platform fd-to-path resolution (Linux uses /proc/self/fd, Darwin uses fcntl(F_GETPATH)).


762-762: Correct: AT_FDCWD is the right choice for opening an absolute path.

Using AT_FDCWD with the absolute fs.basePath is semantically correct and portable across Unix platforms.

internal/ufs/fs_platform_test.go (4)

15-53: LGTM — clean stat validation test.

Covers the essential fields (name, size, directory flag, mod time) through the UnixFS abstraction.


55-88: Good sandbox escape test.

This exercises the critical security invariant that symlinks pointing outside the sandbox root are blocked. Using os.Symlink directly (bypassing UnixFS) correctly simulates an external actor creating the escape link.


90-130: Test depends on timing — consider a more robust assertion.

The 50ms sleep + 100ms tolerance window is reasonable for CI but could be flaky on heavily loaded machines where the filesystem timestamp granularity is coarse (e.g., some filesystems have 1-second or 2-second granularity like HFS+, which is relevant for macOS).

If the test target filesystem on macOS uses APFS (nanosecond granularity), this should be fine. But if run on HFS+ volumes, origMtime and the post-Chtimes mtime could both round to the same second, making the test pass vacuously (not actually verifying preservation). Worth noting but not blocking.


132-145: LGTM — validates platform-specific constant.

Correctly asserts O_LARGEFILE is non-zero on Linux (from unix.O_LARGEFILE) and zero on Darwin (where large file support is default).

internal/ufs/walk_unix.go (1)

187-187: LGTM — clean delegation to platform-specific getdents wrapper.

This change properly delegates OS-specific directory entry reading to the walk_linux.go and walk_darwin.go implementations, with both getdents and nameFromDirent correctly defined in each platform file.

config/config_openat_linux.go (1)

10-13: LGTM on the atomic caching variables.

The two package-level atomic booleans cleanly separate the "is set" flag from the "value" flag.

internal/ufs/fs_linux.go (2)

13-14: LGTM — standard /proc/self/fd approach for Linux.


23-54: LGTM — solid openat2 wrapper with proper flag defaults and RESOLVE_BENEATH.

The EINTR/EAGAIN pass-through without ensurePathError wrapping is correct, as callers will retry the operation.

internal/ufs/fs_darwin.go (2)

15-27: LGTM — correct use of F_GETPATH with runtime.KeepAlive to prevent premature GC of the buffer.


32-34: LGTM — clean stub returning ENOSYS.

internal/ufs/walk_linux.go (1)

10-13: LGTM — thin wrapper over Getdents.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +40 to +58
func (fs *UnixFS) Chtimesat(dirfd int, name string, atime, mtime time.Time) error {
if atime.IsZero() || mtime.IsZero() {
var st unix.Stat_t
if err := unix.Fstatat(dirfd, name, &st, 0); err != nil {
return ensurePathError(err, "chtimes", name)
}
if atime.IsZero() {
atime = time.Unix(st.Atim.Unix())
}
if mtime.IsZero() {
mtime = time.Unix(st.Mtim.Unix())
}
}

utimes := [2]unix.Timespec{
unix.NsecToTimespec(atime.UnixNano()),
unix.NsecToTimespec(mtime.UnixNano()),
}
return ensurePathError(unix.UtimesNanoAt(dirfd, name, utimes[0:], 0), "chtimes", name)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check what version of golang.org/x/sys/unix is being used
fd -t f "go.mod" -o "go.sum" | head -5 | xargs grep -E "golang.org/x/sys"

Repository: pelican-dev/wings

Length of output: 194


🌐 Web query:

golang.org/x/sys/unix Stat_t Darwin Atimespec Mtimespec field names

💡 Result:

On Darwin (GOOS=darwin), golang.org/x/sys/unix.Stat_t uses the BSD-style timestamp fields:

  • Atimespec (access time)
  • Mtimespec (modification time)
  • Ctimespec (inode change time)
  • Birthtimespec (creation/birth time) (git.linux.ucla.edu)

Each of those fields is a unix.Timespec, whose field names are:

So you access them like: st.Atimespec.Sec, st.Atimespec.Nsec, st.Mtimespec.Sec, etc. (docs.activestate.com)

Citations:


🏁 Script executed:

# Check the actual code in fs_darwin.go
cat -n internal/ufs/fs_darwin.go | sed -n '40,58p'

Repository: pelican-dev/wings

Length of output: 781


🏁 Script executed:

# Also check if there's a go.mod to understand dependency versions
cat go.mod | grep -A 5 "require"

Repository: pelican-dev/wings

Length of output: 826


Fix field names for Darwin Stat_t struct — Atim/Mtim don't exist on macOS.

On Darwin, unix.Stat_t uses Atimespec and Mtimespec (not Atim/Mtim as on Linux). Lines 47 and 50 reference nonexistent fields and will fail to compile on macOS.

Fix
 		if atime.IsZero() {
-			atime = time.Unix(st.Atim.Unix())
+			atime = time.Unix(st.Atimespec.Unix())
 		}
 		if mtime.IsZero() {
-			mtime = time.Unix(st.Mtim.Unix())
+			mtime = time.Unix(st.Mtimespec.Unix())
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (fs *UnixFS) Chtimesat(dirfd int, name string, atime, mtime time.Time) error {
if atime.IsZero() || mtime.IsZero() {
var st unix.Stat_t
if err := unix.Fstatat(dirfd, name, &st, 0); err != nil {
return ensurePathError(err, "chtimes", name)
}
if atime.IsZero() {
atime = time.Unix(st.Atim.Unix())
}
if mtime.IsZero() {
mtime = time.Unix(st.Mtim.Unix())
}
}
utimes := [2]unix.Timespec{
unix.NsecToTimespec(atime.UnixNano()),
unix.NsecToTimespec(mtime.UnixNano()),
}
return ensurePathError(unix.UtimesNanoAt(dirfd, name, utimes[0:], 0), "chtimes", name)
func (fs *UnixFS) Chtimesat(dirfd int, name string, atime, mtime time.Time) error {
if atime.IsZero() || mtime.IsZero() {
var st unix.Stat_t
if err := unix.Fstatat(dirfd, name, &st, 0); err != nil {
return ensurePathError(err, "chtimes", name)
}
if atime.IsZero() {
atime = time.Unix(st.Atimespec.Unix())
}
if mtime.IsZero() {
mtime = time.Unix(st.Mtimespec.Unix())
}
}
utimes := [2]unix.Timespec{
unix.NsecToTimespec(atime.UnixNano()),
unix.NsecToTimespec(mtime.UnixNano()),
}
return ensurePathError(unix.UtimesNanoAt(dirfd, name, utimes[0:], 0), "chtimes", name)
🤖 Prompt for AI Agents
In `@internal/ufs/fs_darwin.go` around lines 40 - 58, The code in UnixFS.Chtimesat
uses linux-style Stat_t fields Atim/Mtim which don't exist on macOS; replace
references to st.Atim and st.Mtim with Darwin names st.Atimespec and
st.Mtimespec and convert their timespecs to time.Time (e.g., using
st.Atimespec.Unix()/UnixNano() or an equivalent conversion) before assigning
atime/mtime so the function compiles and uses the correct Darwin fields.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant