Skip to content

Commit ba77c6e

Browse files
committed
Initial commit
Move SignalHandling part of XcodeTools in its own package.
0 parents  commit ba77c6e

File tree

17 files changed

+1504
-0
lines changed

17 files changed

+1504
-0
lines changed

.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Finder and Xcode
2+
.DS_Store
3+
xcuserdata/
4+
5+
# The SPM build folder
6+
/.build/
7+
# This folder is used by SPM when a package is put in “edit” mode (`swift
8+
# package edit some_package`): the edited package is moved in this folder, and
9+
# removed when the package is “unedited” (`swift package unedit some_package`).
10+
/Packages/
11+
12+
# Contains a bunch of stuff… should usually be ignored I think. Currently, as
13+
# far as I’m aware, it is used by Xcode 11 to put the autogenerated Xcode
14+
# project created and maintained by Xcode when opening a Package.swift file, and
15+
# by the `swift package config` command to store its config (currently, the only
16+
# config I’m aware of are the mirrors for downloading packages).
17+
/.swiftpm/

Package.resolved

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// swift-tools-version:5.3
2+
import PackageDescription
3+
4+
5+
let package = Package(
6+
name: "swift-signal-handling",
7+
platforms: [
8+
.macOS(.v10_15)
9+
],
10+
products: [
11+
.library(name: "SignalHandling", targets: ["SignalHandling"])
12+
],
13+
dependencies: [
14+
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "0.4.0"),
15+
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.2"),
16+
.package(url: "https://github.com/apple/swift-system.git", from: "0.0.1"),
17+
.package(url: "https://github.com/xcode-actions/clt-logger.git", from: "0.2.0")
18+
],
19+
targets: [
20+
.target(name: "SignalHandling", dependencies: [
21+
.product(name: "Logging", package: "swift-log"),
22+
.product(name: "SystemPackage", package: "swift-system")
23+
]),
24+
25+
.target(name: "signal-handling-test-helper", dependencies: [
26+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
27+
.product(name: "CLTLogger", package: "clt-logger"),
28+
.product(name: "Logging", package: "swift-log"),
29+
.target(name: "SignalHandling")
30+
]),
31+
.testTarget(name: "SignalHandlingTests", dependencies: [
32+
.target(name: "signal-handling-test-helper"),
33+
.product(name: "CLTLogger", package: "clt-logger"),
34+
.product(name: "Logging", package: "swift-log"),
35+
.product(name: "SystemPackage", package: "swift-system")
36+
]),
37+
]
38+
)
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import Foundation
2+
3+
import SystemPackage
4+
5+
6+
7+
public struct Sigaction : Equatable, RawRepresentable {
8+
9+
public static let ignoreAction = Sigaction(handler: .ignoreHandler)
10+
public static let defaultAction = Sigaction(handler: .defaultHandler)
11+
12+
/**
13+
Check if the given signal is ignored using `sigaction`. */
14+
public static func isSignalIgnored(_ signal: Signal) throws -> Bool {
15+
return try Sigaction(signal: signal).handler == .ignoreHandler
16+
}
17+
18+
/**
19+
Check if the given signal is handled with default action using `sigaction`. */
20+
public static func isSignalDefaultAction(_ signal: Signal) throws -> Bool {
21+
return try Sigaction(signal: signal).handler == .defaultHandler
22+
}
23+
24+
public var mask: Set<Signal> = []
25+
public var flags: SigactionFlags = []
26+
27+
public var handler: SigactionHandler
28+
29+
public init(handler: SigactionHandler) {
30+
self.mask = []
31+
switch handler {
32+
case .posix: self.flags = [.siginfo]
33+
case .ignoreHandler, .defaultHandler, .ansiC: self.flags = []
34+
}
35+
self.handler = handler
36+
}
37+
38+
/**
39+
Create a `Sigaction` from a `sigaction`.
40+
41+
If the handler of the sigaction is `SIG_IGN` or `SIG_DFL`, we check the
42+
`sa_flags` not to contains the `SA_SIGINFO` bit. If they do, we log an error,
43+
as this is invalid. */
44+
public init(rawValue: sigaction) {
45+
self.mask = Signal.set(from: rawValue.sa_mask)
46+
self.flags = SigactionFlags(rawValue: rawValue.sa_flags)
47+
48+
switch OpaquePointer(bitPattern: unsafeBitCast(rawValue.__sigaction_u.__sa_handler, to: Int.self)) {
49+
case OpaquePointer(bitPattern: unsafeBitCast(SIG_IGN, to: Int.self)): self.handler = .ignoreHandler
50+
case OpaquePointer(bitPattern: unsafeBitCast(SIG_DFL, to: Int.self)): self.handler = .defaultHandler
51+
default:
52+
if flags.contains(.siginfo) {self.handler = .posix(rawValue.__sigaction_u.__sa_sigaction)}
53+
else {self.handler = .ansiC(rawValue.__sigaction_u.__sa_handler)}
54+
}
55+
56+
if !isValid {
57+
SignalHandlingConfig.logger?.warning("Initialized an invalid Sigaction.")
58+
}
59+
}
60+
61+
public init(signal: Signal) throws {
62+
var action = sigaction()
63+
guard sigaction(signal.rawValue, nil, &action) == 0 else {
64+
throw SignalHandlingError.nonDestructiveSystemError(Errno(rawValue: errno))
65+
}
66+
self.init(rawValue: action)
67+
}
68+
69+
public var rawValue: sigaction {
70+
if !isValid {
71+
SignalHandlingConfig.logger?.warning("Getting sigaction from an invalid Sigaction.")
72+
}
73+
74+
var ret = sigaction()
75+
ret.sa_mask = Signal.sigset(from: mask)
76+
ret.sa_flags = flags.rawValue
77+
78+
switch handler {
79+
case .ignoreHandler: ret.__sigaction_u.__sa_handler = SIG_IGN
80+
case .defaultHandler: ret.__sigaction_u.__sa_handler = SIG_DFL
81+
case .ansiC(let h): ret.__sigaction_u.__sa_handler = h
82+
case .posix(let h): ret.__sigaction_u.__sa_sigaction = h
83+
}
84+
85+
return ret
86+
}
87+
88+
/**
89+
Only one check: do the flags **not** contain `siginfo` if handler is either
90+
`.ignoreHandler` or `.defaultHandler`. */
91+
public var isValid: Bool {
92+
return !flags.contains(.siginfo) || (handler != .ignoreHandler && handler != .defaultHandler)
93+
}
94+
95+
/**
96+
Installs the sigaction and returns the old one if different.
97+
98+
It is impossible for a sigaction handler to be `nil`. If the method returns
99+
`nil`, the previous handler was exactly the same as the one you installed.
100+
Note however the sigaction function is always called in this method. */
101+
@discardableResult
102+
public func install(on signal: Signal, revertIfIgnored: Bool = true) throws -> Sigaction? {
103+
var oldCAction = sigaction()
104+
var newCAction = self.rawValue
105+
guard sigaction(signal.rawValue, &newCAction, &oldCAction) == 0 else {
106+
throw SignalHandlingError.nonDestructiveSystemError(Errno(rawValue: errno))
107+
}
108+
let oldSigaction = Sigaction(rawValue: oldCAction)
109+
if revertIfIgnored && oldSigaction == .ignoreAction {
110+
guard sigaction(signal.rawValue, &newCAction, &oldCAction) == 0 else {
111+
throw SignalHandlingError.destructiveSystemError(Errno(rawValue: errno))
112+
}
113+
}
114+
if oldSigaction != self {return oldSigaction}
115+
else {return nil}
116+
}
117+
118+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import Foundation
2+
3+
4+
5+
/** Flag list is from `sigaction(2)` on macOS. */
6+
public struct SigactionFlags : OptionSet {
7+
8+
/**
9+
If this bit is set when installing a catching function for the `SIGCHLD`
10+
signal, the `SIGCHLD` signal will be generated only when a child process
11+
exits, not when a child process stops. */
12+
public static let noChildStop = SigactionFlags(rawValue: SA_NOCLDSTOP)
13+
14+
/**
15+
If this bit is set when calling `sigaction()` for the `SIGCHLD` signal, the
16+
system will not create zombie processes when children of the calling process
17+
exit. If the calling process subsequently issues a `wait(2)` (or equivalent),
18+
it blocks until all of the calling process’s child processes terminate, and
19+
then returns a value of -1 with errno set to ECHILD. */
20+
public static let noChildWait = SigactionFlags(rawValue: SA_NOCLDWAIT)
21+
22+
/**
23+
If this bit is set, the system will deliver the signal to the process on a
24+
signal stack, specified with `sigaltstack(2)`. */
25+
public static let onStack = SigactionFlags(rawValue: SA_ONSTACK)
26+
27+
/**
28+
If this bit is set, further occurrences of the delivered signal are not
29+
masked during the execution of the handler. */
30+
public static let noDefer = SigactionFlags(rawValue: SA_NODEFER)
31+
32+
/**
33+
If this bit is set, the handler is reset back to `SIG_DFL` at the moment the
34+
signal is delivered. */
35+
public static let resetHandler = SigactionFlags(rawValue: SA_RESETHAND)
36+
37+
/** See `sigaction(2)`. */
38+
public static let restart = SigactionFlags(rawValue: SA_RESTART)
39+
40+
/**
41+
If this bit is set, the handler function is assumed to be pointed to by the
42+
`sa_sigaction` member of struct sigaction and should match the matching
43+
prototype. This bit should not be set when assigning `SIG_DFL` or `SIG_IGN`. */
44+
public static let siginfo = SigactionFlags(rawValue: SA_SIGINFO)
45+
46+
public let rawValue: CInt
47+
48+
public init(rawValue: CInt) {
49+
self.rawValue = rawValue
50+
}
51+
52+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Foundation
2+
3+
4+
5+
/**
6+
A `sigaction` handler.
7+
8+
Two `SigactionHandler`s are equal iif their cases are equal and the handler
9+
they contain point to the same address (if applicable). */
10+
public enum SigactionHandler : Equatable {
11+
12+
/* The ignore and default handlers are special cases represented respectively
13+
 * by the `SIG_IGN` and `SIG_DFL` values in C.
14+
 * We choose the represent them using a special case in the enum. You should
15+
 * not (though you could) use `.ansiC(SIG_IGN)` (it is not possible with
16+
 * `SIG_DFL` because `SIG_DFL` is optional… and nil).
17+
 * In particular, `.ignoreHandler != .ansiC(SIG_IGN)` */
18+
case ignoreHandler
19+
case defaultHandler
20+
21+
case ansiC(@convention(c) (_ signalID: Int32) -> Void)
22+
case posix(@convention(c) (_ signalID: Int32, _ siginfo: UnsafeMutablePointer<__siginfo>?, _ userThreadContext: UnsafeMutableRawPointer?) -> Void)
23+
24+
public static func ==(lhs: SigactionHandler, rhs: SigactionHandler) -> Bool {
25+
switch (lhs, rhs) {
26+
case (.ignoreHandler, .ignoreHandler), (.defaultHandler, .defaultHandler):
27+
return true
28+
29+
case (.ansiC, .ansiC), (.posix, .posix):
30+
return lhs.asOpaquePointer == rhs.asOpaquePointer
31+
32+
/* Using this matching patterns instead of simply default, we force a
33+
 * compilation error in case more cases are added later. */
34+
case (.ignoreHandler, _), (.defaultHandler, _), (.ansiC, _), (.posix, _):
35+
return false
36+
}
37+
}
38+
39+
var asOpaquePointer: OpaquePointer? {
40+
switch self {
41+
case .ignoreHandler: return OpaquePointer(bitPattern: unsafeBitCast(SIG_IGN, to: Int.self))
42+
case .defaultHandler: return OpaquePointer(bitPattern: unsafeBitCast(SIG_DFL, to: Int.self))
43+
case .ansiC(let h): return OpaquePointer(bitPattern: unsafeBitCast(h, to: Int.self))
44+
case .posix(let h): return OpaquePointer(bitPattern: unsafeBitCast(h, to: Int.self))
45+
}
46+
}
47+
48+
}

0 commit comments

Comments
 (0)