Skip to content

Conversation

michael-yuji
Copy link
Member

@michael-yuji michael-yuji commented Oct 6, 2025

This PR add FreeBSD support, currently build is passing against main swift branch, tests are not fully building but does not seems related to swift-nio itself

Apart from using kqueue, here's a list of non-trivial changes:

File copying

For file copying, we use copy_file_range instead of sendfile as it is available on all supported FreeBSD option, so now, copyItem's implementation is like this:

Platform syscall
Darwin copyfile(2)
FreeBSD copy_file_range(2)
Linux sendfile(2)

extended attributes

Unlike Linux and Darwin, the extended attribute apis are different in freebsd, e.g. extattr_get_fd instead of getxattr

pktinfo

FreeBSD does not have IP_PKTINFO for IPv4, instead we combine IP_RECVIF and IP_ORIGDSTADDR to get the destination address and interface index.

|swift-nio/Tests/NIOFSFoundationCompatTests/FileSystemFoundationCompatTests.swift:75:34: error: no exact matches in call to initializer
73 |         }
74 |
75 |         let contents = try await Data(contentsOf: path, maximumSizeAllowed: .bytes(1024))
   |                                  |- error: no exact matches in call to initializer
   |                                  |- note: found candidate with type '(NIOFilePath) -> Data'
   |                                  `- note: candidate has partially matching parameter list (contentsOf: __shared URL, options: Data.ReadingOptions)

@Lukasa
Copy link
Contributor

Lukasa commented Oct 6, 2025

@michael-yuji Has that issue been reported on Foundation? Not having the tests build is definitely going to be hiding bugs.

@Lukasa
Copy link
Contributor

Lukasa commented Oct 6, 2025

In the meantime, as Foundation is unlikely to fix the issue in 6.2, can I suggest we skip the test until 6.3 on FreeBSD?

@Lukasa Lukasa added the 🆕 semver/minor Adds new public API. label Oct 6, 2025
}

#if canImport(Glibc) || canImport(Musl) || canImport(Bionic)
#if !os(FreeBSD) && (canImport(Glibc) || canImport(Musl) || canImport(Bionic))
Copy link
Contributor

Choose a reason for hiding this comment

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

Under what circumstance would this check have previously compiled? Does FreeBSD report its libc as Glibc?

I see from the rest of the code that it does. Is there any way we can change that before formal support? It's going to make life very difficult, as well as being factually untrue.

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately FreeBSD does report its libc as Glibc, I do have a PR open (swiftlang/swift#79261) but we have decided that that's no bandwidth to make the overlay change into 6.2.x
cc @etcwilde

// Okay, return empty value.
return .success([])
#endif
#if !os(FreeBSD)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this here because noData is impossible or because it's unreachable?

Copy link
Member Author

Choose a reason for hiding this comment

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

it's because .nodata is not available on this platform

#endif
default:
#if os(FreeBSD)
let error = FileSystemError.extattr_get_fd(
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this behave meaningfully differently than fgetxattr? If not, can this #if os be moved into that function so it doesn't end up here?

Copy link
Member Author

Choose a reason for hiding this comment

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

fgetxattr is not available, the extended attribute apis are called differently

Copy link
Contributor

Choose a reason for hiding this comment

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

If it only has a different name, but otherwise behaves the same, please merge them into the same function.

}

#elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic)
#elseif os(FreeBSD) || canImport(Glibc) || canImport(Musl) || canImport(Bionic)
Copy link
Contributor

Choose a reason for hiding this comment

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

canImport(Glibc) covers os(FreeBSD) currently.

#if os(FreeBSD)
self.fflags = 0
self.data = 0
#else
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not clear to me that this is right: as noted above, EVFILT_EXCEPT does appear to be available on FreeBSD, and I think we actually do want it.

@inline(never)
public static func opendir(pathname: String) throws -> OpaquePointer {
#if os(FreeBSD)
sysOpendir(pathname)!
Copy link
Contributor

Choose a reason for hiding this comment

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

Please wrap this in the syscall helper.

var nonThrowingErrnos: [Errno] = []
var knownErrnos: [Errno: FileSystemError.Code] = [.notSupported: .unsupported]
#if canImport(Darwin)
#if canImport(Darwin) || os(FreeBSD)
Copy link
Contributor

Choose a reason for hiding this comment

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

This represents a behavioural change on macOS, which no longer has noData in its nonThrowingErrnos.

// These platforms require a dependency on `NIOPosix` from `NIOHTTP1` to maintain backward
// compatibility with previous NIO versions.
let historicalNIOPosixDependencyRequired: [Platform] = [.macOS, .iOS, .tvOS, .watchOS, .linux, .android]
let historicalNIOPosixDependencyRequired: [Platform] = [.macOS, .iOS, .tvOS, .watchOS, .linux, .android, .custom("freebsd"), .custom("FreeBSD")]
Copy link
Contributor

Choose a reason for hiding this comment

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

These platforms do not require a historical NIOPosix dependency, as they did not compile before.

@michael-yuji
Copy link
Member Author

@michael-yuji Has that issue been reported on Foundation? Not having the tests build is definitely going to be hiding bugs.

I don't think it's a bug in Foundation, the initializer seems to come from Sources/_NIOFileSystemFoundationCompat/Data+FileSystem.switf. I suspect it's a bug in the current toolchain at main

skipping the test now sounds good to me

@Lukasa
Copy link
Contributor

Lukasa commented Oct 6, 2025

Let's also not merge this until swiftlang/swift#79261 lands, because the compiler guards will all change at that point.

@michael-yuji
Copy link
Member Author

Let's also not merge this until swiftlang/swift#79261 lands, because the compiler guards will all change at that point.

I'm not sure that's a good idea, since we have announced support for FreeBSD and without swift-nio, anything swift on server would pretty much become useless on the platform which is less than ideal

@Lukasa
Copy link
Contributor

Lukasa commented Oct 6, 2025

Let me be more precise: if we land the change as-is, you must not change the name of the libc in FreeBSD, as doing so will be source-breaking. So, to put not too fine a point on it: if the above PR doesn't land now, it doesn't land until a source break is acceptable.

}
#endif

#if !os(FreeBSD) && (canImport(Glibc) || canImport(Musl) || canImport(Android))

Choose a reason for hiding this comment

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

Would it be stylistically better to have

#if !os(FreeBSD)
#if canImport(Glibc) ...
#endif
#endif

or retain the rather complex boolean expression here?

I ran into this when considering OpenBSD support; the stacked conditionals are easier to read but I don't know if there's a stylistic preference.

Choose a reason for hiding this comment

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

I erred on calling this just CNIOBSD in #3394. Would it be better to do the same here, so that we don't need to have one CNIO.* module per operating system? Or should I go and rename CNIOBSD to CNIOOpenBSD there?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do the two have similar-enough libcs?

Choose a reason for hiding this comment

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

There are quite similar, especially due to pedigree, but there is definitely divergence, such as functionality in FreeBSD that OpenBSD does not have.

//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Copyright (c) 2025 Apple Inc. and the SwiftNIO project authors

//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Copyright (c) 2025 Apple Inc. and the SwiftNIO project authors

elsewhere too

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

Labels

🆕 semver/minor Adds new public API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants