Skip to content

Commit

Permalink
Migrated Spoon* code from Cuising to TinyFoundation
Browse files Browse the repository at this point in the history
  • Loading branch information
smumriak committed Oct 21, 2023
1 parent 78c9c7e commit 1ac94c3
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 1 deletion.
9 changes: 9 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ let package = Package(
.package(url: "https://github.com/smumriak/SemanticVersion.git", branch: "main"),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.6"),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-system", from: "1.0.0"),
],
targets: [
.executableTarget(
Expand Down Expand Up @@ -182,6 +183,7 @@ let package = Package(

.tinyFoundation,
.hijackingHacks,
.spoon,
.tinyFoundationTests,

.cVulkan,
Expand Down Expand Up @@ -253,6 +255,7 @@ extension Target.Dependency {

static let tinyFoundation = Target.tinyFoundation.asDependency()
static let hijackingHacks = Target.hijackingHacks.asDependency()
static let spoon = Target.spoon.asDependency()
static let tinyFoundationTests = Target.tinyFoundationTests.asDependency()

static let cVulkan = Target.cVulkan.asDependency()
Expand Down Expand Up @@ -501,8 +504,10 @@ extension Target {
dependencies: [
.product(name: "DequeModule", package: "swift-collections"),
.product(name: "Atomics", package: "swift-atomics"),
.product(name: "SystemPackage", package: "swift-system"),
.linuxSys,
.hijackingHacks,
.spoon,
],
path: "TinyFoundation/Sources/TinyFoundation"
)
Expand All @@ -515,6 +520,10 @@ extension Target {
]),
]
)
static let spoon: Target = target(
name: "Spoon",
path: "TinyFoundation/Sources/Spoon"
)
static let tinyFoundationTests: Target = testTarget(
name: "TinyFoundationTests",
dependencies: [.tinyFoundation],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// empty.c
// HijackingHacksEmpty.c
// TinyFoundation
//
// Created by Serhii Mumriak on 11.01.2023
Expand Down
21 changes: 21 additions & 0 deletions TinyFoundation/Sources/Spoon/SpoonEmpty.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// Spoon.c
// Cuisine
//
// Created by Serhii Mumriak on 19.10.2023
//

#include "Spoon.h"
#include <unistd.h>

int spoon(int (*_Nonnull child)(void *_Nullable info), void *_Nullable info) {
int result = vfork();

if (result == 0) {
// forked process
int ret = child(info);
// TODO: Handle the POSIX error here and propagate it to caller in future
}

return result;
}
8 changes: 8 additions & 0 deletions TinyFoundation/Sources/Spoon/include/Spoon.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// Spoon.h
// Cuisine
//
// Created by Serhii Mumriak on 19.10.2023
//

int spoon(int (*_Nonnull child)(void *_Nullable info), void *_Nullable info);
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,35 @@ public extension Array where Element: StringProtocol {
try $0.withUnsafeBufferPointer(body)
}
}

@_transparent
func withUnsafeNulllTerminatedCStringsArray<R>(_ body: (UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>) throws -> (R)) rethrows -> R {
let array = nullTerminatedArrayOfCStrings
defer {
for i in 0..<count {
(array + i).pointee?.deallocate()
}
nullTerminatedArrayOfCStrings.deinitialize(count: count + 1)
nullTerminatedArrayOfCStrings.deallocate()
}

return try body(array)
}

@_transparent
var nullTerminatedArrayOfCStrings: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?> {
let size = count + 1
let result: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?> = .allocate(capacity: size)
result.initialize(repeating: nil, count: size)

let buffer = UnsafeMutableBufferPointer(start: result, count: size)

for (index, element) in enumerated() {
buffer[index] = element.withCString { strdup($0) }
}

return result
}
}

public extension Set where Element: StringProtocol {
Expand Down
72 changes: 72 additions & 0 deletions TinyFoundation/Sources/TinyFoundation/Process/SpoonProcess.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// SpoonProcess.swift
// TinyFoundation
//
// Created by Serhii Mumriak on 19.10.2023
//

import Spoon
import SystemPackage
import LinuxSys
import TinyFoundation
import Foundation

fileprivate struct ForkMetadata {
// this stuff should be pre-allocated since we should not really do ANY allocations after forking due to the fact that vfork does not copy original process' memory. it's a very shady gray zone in memory management on OS side
// essentially, the only thing allocated will be the stack for the `forkedCall` function call
let executablePath: UnsafePointer<CChar>
let arguments: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>
let environment: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>
let workDirectoryPath: UnsafePointer<CChar>?
}

fileprivate func forkedCall(info: UnsafeMutableRawPointer!) -> CInt {
let metadata = info.assumingMemoryBound(to: ForkMetadata.self).pointee

if let workDirectoryPath = metadata.workDirectoryPath {
chdir(workDirectoryPath)
}

return execve(metadata.executablePath /* path */,
metadata.arguments /* argv */,
metadata.environment /* envp */ )
}

public func spoonProcess(executablePath: FilePath, arguments: [String] = [], environment: [String: String]? = nil, workDirectoryPath: FilePath? = nil) throws -> CInt {
let arguments = [executablePath.string] + arguments
let environment = (environment ?? ProcessInfo.processInfo.environment).map { "\($0)=\($1)" }
var metadata = ForkMetadata(
executablePath: arguments[0].withCString { strdup($0) },
arguments: arguments.nullTerminatedArrayOfCStrings,
environment: environment.nullTerminatedArrayOfCStrings,
workDirectoryPath: workDirectoryPath?.withCString { strdup($0) }
)

let childID = spoon(forkedCall /* child */,
&metadata /* info */ )

if childID == 0 {
// we are in forked process, tho technically this code should never be executed unless something failed
fatalError("Error happened while executing `forkedCall`. TODO: propagate errors properly")
}

defer {
metadata.workDirectoryPath?.deallocate()

for i in 0..<environment.count {
(metadata.environment + i).pointee?.deallocate()
}
metadata.arguments.deinitialize(count: environment.count + 1)
metadata.environment.deallocate()

for i in 0..<arguments.count {
(metadata.arguments + i).pointee?.deallocate()
}
metadata.arguments.deinitialize(count: arguments.count + 1)
metadata.arguments.deallocate()

metadata.executablePath.deallocate()
}

return childID
}

0 comments on commit 1ac94c3

Please sign in to comment.