Skip to content

Commit 997d5a4

Browse files
authored
Actions: Add Linux build step (#654)
Related to #606 To make sure the project (and many linux specific libraries) builds on linux lets add a linux step to build for both musl and glibc. Eventually this will run unit tests as well. This change needs a bit more libc agnostic-ifying work to make sure things work for both libcs.
1 parent 1a91899 commit 997d5a4

File tree

15 files changed

+210
-45
lines changed

15 files changed

+210
-45
lines changed

.github/workflows/linux-build.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Linux build
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
pull_request:
8+
types: [opened, reopened, synchronize]
9+
push:
10+
branches:
11+
- main
12+
- release/*
13+
14+
jobs:
15+
build:
16+
name: Linux compile check
17+
timeout-minutes: 30
18+
runs-on: ubuntu-24.04
19+
container: swift:6.3-noble
20+
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
24+
with:
25+
fetch-depth: 0
26+
27+
- name: Install system dependencies
28+
run: apt-get update && apt-get install -y curl make libarchive-dev libbz2-dev liblzma-dev libssl-dev
29+
30+
- name: Build containerization
31+
run: make containerization
32+
33+
- name: Build vminitd (glibc)
34+
run: make -C vminitd SWIFT_CONFIGURATION="--disable-automatic-resolution -Xswiftc -warnings-as-errors"
35+
36+
- name: Install Static Linux SDK
37+
run: make -C vminitd linux-sdk
38+
39+
- name: Build vminitd (musl)
40+
run: make -C vminitd

Sources/CShim/include/linux_shim.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright © 2026 Apple Inc. and the Containerization project authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// The below fall into two main categories:
18+
// 1. Aren't exposed by Swifts glibc modulemap.
19+
// 2. Don't have syscall wrappers/definitions in glibc/musl.
20+
21+
#ifndef __LINUX_SHIM_H
22+
#define __LINUX_SHIM_H
23+
24+
#if defined(__linux__)
25+
26+
#include <sys/epoll.h>
27+
#include <sys/vfs.h>
28+
29+
#endif /* __linux__ */
30+
31+
#endif /* __LINUX_SHIM_H */

Sources/ContainerizationOS/Linux/Epoll.swift

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,43 @@
1414
// limitations under the License.
1515
//===----------------------------------------------------------------------===//
1616

17+
#if os(Linux)
18+
1719
#if canImport(Musl)
1820
import Musl
21+
#elseif canImport(Glibc)
22+
import Glibc
23+
#endif
1924

25+
import CShim
2026
import Foundation
2127
import Synchronization
2228

29+
// On glibc, epoll constants are EPOLL_EVENTS enum values. On musl they're
30+
// plain UInt32. These helpers normalize them to UInt32/Int32.
31+
private func epollMask(_ value: UInt32) -> UInt32 { value }
32+
private func epollMask(_ value: Int32) -> UInt32 { UInt32(bitPattern: value) }
33+
#if canImport(Glibc)
34+
private func epollMask(_ value: EPOLL_EVENTS) -> UInt32 { value.rawValue }
35+
private func epollFlag(_ value: EPOLL_EVENTS) -> Int32 { Int32(bitPattern: value.rawValue) }
36+
#endif
37+
2338
/// Register file descriptors to receive events via Linux's
2439
/// epoll syscall surface.
2540
public final class Epoll: Sendable {
2641
public typealias Mask = Int32
2742
public typealias Handler = (@Sendable (Mask) -> Void)
2843

44+
public static let maskIn: Int32 = Int32(bitPattern: epollMask(EPOLLIN))
45+
public static let maskOut: Int32 = Int32(bitPattern: epollMask(EPOLLOUT))
46+
public static let defaultMask: Int32 = maskIn | maskOut
47+
2948
private let epollFD: Int32
3049
private let handlers = SafeMap<Int32, Handler>()
3150
private let pipe = Pipe() // to wake up a waiting epoll_wait
3251

3352
public init() throws {
34-
let efd = epoll_create1(EPOLL_CLOEXEC)
53+
let efd = epoll_create1(Int32(EPOLL_CLOEXEC))
3554
guard efd > 0 else {
3655
throw POSIXError.fromErrno()
3756
}
@@ -41,14 +60,14 @@ public final class Epoll: Sendable {
4160

4261
public func add(
4362
_ fd: Int32,
44-
mask: Int32 = EPOLLIN | EPOLLOUT, // HUP is always added
63+
mask: Int32 = Epoll.defaultMask,
4564
handler: @escaping Handler
4665
) throws {
4766
guard fcntl(fd, F_SETFL, O_NONBLOCK) == 0 else {
4867
throw POSIXError.fromErrno()
4968
}
5069

51-
let events = EPOLLET | UInt32(bitPattern: mask)
70+
let events = epollMask(EPOLLET) | UInt32(bitPattern: mask)
5271

5372
var event = epoll_event()
5473
event.events = events
@@ -115,7 +134,7 @@ public final class Epoll: Sendable {
115134
public func delete(_ fd: Int32) throws {
116135
var event = epoll_event()
117136
let result = withUnsafeMutablePointer(to: &event) { ptr in
118-
epoll_ctl(self.epollFD, EPOLL_CTL_DEL, fd, ptr)
137+
epoll_ctl(self.epollFD, EPOLL_CTL_DEL, fd, ptr) as Int32
119138
}
120139
if result != 0 {
121140
if !acceptableDeletionErrno() {
@@ -162,20 +181,20 @@ public final class Epoll: Sendable {
162181

163182
extension Epoll.Mask {
164183
public var isHangup: Bool {
165-
(self & (EPOLLHUP | EPOLLERR)) != 0
184+
(self & Int32(bitPattern: epollMask(EPOLLHUP) | epollMask(EPOLLERR))) != 0
166185
}
167186

168187
public var isRhangup: Bool {
169-
(self & EPOLLRDHUP) != 0
188+
(self & Int32(bitPattern: epollMask(EPOLLRDHUP))) != 0
170189
}
171190

172191
public var readyToRead: Bool {
173-
(self & EPOLLIN) != 0
192+
(self & Int32(bitPattern: epollMask(EPOLLIN))) != 0
174193
}
175194

176195
public var readyToWrite: Bool {
177-
(self & EPOLLOUT) != 0
196+
(self & Int32(bitPattern: epollMask(EPOLLOUT))) != 0
178197
}
179198
}
180199

181-
#endif // canImport(Musl)
200+
#endif // os(Linux)

vminitd/Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ SWIFT_CONFIGURATION := --swift-sdk $(MUSL_ARCH)-swift-linux-musl $(SWIFT_WARNING
3131

3232
SWIFT_VERSION := 6.3
3333
SWIFT_SDK_URL := https://download.swift.org/swift-6.3-release/static-sdk/swift-6.3-RELEASE/swift-6.3-RELEASE_static-linux-0.1.0.artifactbundle.tar.gz
34+
SWIFT_SDK_CHECKSUM := d2078b69bdeb5c31202c10e9d8a11d6f66f82938b51a4b75f032ccb35c4c286c
3435
SWIFT_SDK_PATH := /tmp/$(notdir $(SWIFT_SDK_URL))
3536

3637
SYSTEM_TYPE := $(shell uname -s)
@@ -64,7 +65,7 @@ all:
6465
@install "$(BUILD_BIN_DIR)/vmexec" ./bin/
6566

6667
.PHONY: cross-prep
67-
cross-prep: linux-sdk
68+
cross-prep: swift linux-sdk
6869

6970
.PHONY: swiftly
7071
swiftly:
@@ -84,10 +85,10 @@ swift: swiftly
8485
@${SWIFTLY_BIN_DIR}/swiftly install $(SWIFT_VERSION)
8586

8687
.PHONY: linux-sdk
87-
linux-sdk: swift
88+
linux-sdk:
8889
@echo Installing Static Linux SDK...
8990
@curl -L -o $(SWIFT_SDK_PATH) $(SWIFT_SDK_URL)
90-
-@$(SWIFT) sdk install $(SWIFT_SDK_PATH)
91+
-@$(SWIFT) sdk install $(SWIFT_SDK_PATH) --checksum $(SWIFT_SDK_CHECKSUM)
9192
@rm $(SWIFT_SDK_PATH)
9293

9394
.PHONY: clean

vminitd/Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ let package = Package(
5656
.product(name: "ContainerizationOCI", package: "containerization"),
5757
.product(name: "ContainerizationOS", package: "containerization"),
5858
.product(name: "SystemPackage", package: "swift-system"),
59+
"LCShim",
5960
]
6061
),
6162
.executableTarget(

vminitd/Sources/Cgroup/Cgroup2Manager.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import Musl
2626
import Glibc
2727
#endif
2828

29+
import LCShim
2930
import ContainerizationOCI
3031
import ContainerizationOS
3132
import Foundation

vminitd/Sources/LCShim/include/syscall.h

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,87 @@
1414
* limitations under the License.
1515
*/
1616

17+
// The below fall into two main categories:
18+
// 1. Aren't exposed by Swifts glibc modulemap.
19+
// 2. Don't have syscall wrappers/definitions in glibc/musl.
20+
1721
#ifndef __SYSCALL_H
1822
#define __SYSCALL_H
1923

2024
#include <sys/types.h>
25+
#include <sys/vfs.h>
2126

22-
int CZ_pivot_root(const char *new_root, const char *put_old);
27+
// CLONE_* flags
28+
#ifndef CLONE_NEWNS
29+
#define CLONE_NEWNS 0x00020000
30+
#endif
31+
#ifndef CLONE_NEWCGROUP
32+
#define CLONE_NEWCGROUP 0x02000000
33+
#endif
34+
#ifndef CLONE_NEWUTS
35+
#define CLONE_NEWUTS 0x04000000
36+
#endif
37+
#ifndef CLONE_NEWIPC
38+
#define CLONE_NEWIPC 0x08000000
39+
#endif
40+
#ifndef CLONE_NEWUSER
41+
#define CLONE_NEWUSER 0x10000000
42+
#endif
43+
#ifndef CLONE_NEWPID
44+
#define CLONE_NEWPID 0x20000000
45+
#endif
46+
47+
extern int setns(int fd, int nstype);
48+
extern int unshare(int flags);
49+
extern int dup3(int oldfd, int newfd, int flags);
50+
extern int execvpe(const char *file, char *const argv[], char *const envp[]);
51+
extern int unlockpt(int fd);
52+
extern char *ptsname(int fd);
2353

54+
// splice(2) and flags.
55+
extern ssize_t splice(int fd_in, off_t *off_in, int fd_out, off_t *off_out,
56+
size_t len, unsigned int flags);
57+
#ifndef SPLICE_F_MOVE
58+
#define SPLICE_F_MOVE 1
59+
#endif
60+
#ifndef SPLICE_F_NONBLOCK
61+
#define SPLICE_F_NONBLOCK 2
62+
#endif
63+
64+
// RLIMIT constants as plain integers. On glibc these are __rlimit_resource
65+
// enum values which can't be used as Int32 in Swift.
66+
#define CZ_RLIMIT_CPU 0
67+
#define CZ_RLIMIT_FSIZE 1
68+
#define CZ_RLIMIT_DATA 2
69+
#define CZ_RLIMIT_STACK 3
70+
#define CZ_RLIMIT_CORE 4
71+
#define CZ_RLIMIT_RSS 5
72+
#define CZ_RLIMIT_NPROC 6
73+
#define CZ_RLIMIT_NOFILE 7
74+
#define CZ_RLIMIT_MEMLOCK 8
75+
#define CZ_RLIMIT_AS 9
76+
#define CZ_RLIMIT_LOCKS 10
77+
#define CZ_RLIMIT_SIGPENDING 11
78+
#define CZ_RLIMIT_MSGQUEUE 12
79+
#define CZ_RLIMIT_NICE 13
80+
#define CZ_RLIMIT_RTPRIO 14
81+
#define CZ_RLIMIT_RTTIME 15
82+
83+
// setrlimit wrapper that accepts plain int for the resource parameter,
84+
// avoiding glibc's __rlimit_resource enum type mismatch in Swift.
85+
int CZ_setrlimit(int resource, unsigned long long soft, unsigned long long hard);
86+
87+
int CZ_pivot_root(const char *new_root, const char *put_old);
2488
int CZ_set_sub_reaper();
2589

2690
#ifndef SYS_pidfd_open
2791
#define SYS_pidfd_open 434
2892
#endif
29-
3093
int CZ_pidfd_open(pid_t pid, unsigned int flags);
3194

3295
#ifndef SYS_pidfd_getfd
3396
#define SYS_pidfd_getfd 438
3497
#endif
35-
3698
int CZ_pidfd_getfd(int pidfd, int targetfd, unsigned int flags);
3799

38100
int CZ_prctl_set_no_new_privs();

vminitd/Sources/LCShim/syscall.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
#include <sys/prctl.h>
18+
#include <sys/resource.h>
1819
#include <sys/syscall.h>
1920
#include <unistd.h>
2021

@@ -39,3 +40,11 @@ int CZ_pidfd_getfd(int pidfd, int targetfd, unsigned int flags) {
3940
int CZ_prctl_set_no_new_privs() {
4041
return prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
4142
}
43+
44+
int CZ_setrlimit(int resource, unsigned long long soft,
45+
unsigned long long hard) {
46+
struct rlimit limit;
47+
limit.rlim_cur = (rlim_t)soft;
48+
limit.rlim_max = (rlim_t)hard;
49+
return setrlimit(resource, &limit);
50+
}

vminitd/Sources/vmexec/Console.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//===----------------------------------------------------------------------===//
1616

1717
import FoundationEssentials
18+
import LCShim
1819

1920
#if canImport(Musl)
2021
import Musl

vminitd/Sources/vmexec/RunCommand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ struct RunCommand: ParsableCommand {
8181
guard statfs("/", &s) == 0 else {
8282
throw App.Errno(stage: "statfs(/)")
8383
}
84-
flags |= s.f_flags
84+
flags |= UInt(s.f_flags)
8585

8686
guard mount("", "/", "", flags, "") == 0 else {
8787
throw App.Errno(stage: "mount rootfs ro")

0 commit comments

Comments
 (0)