From 1ac94c393ada615fce026f24374782a288ba9434 Mon Sep 17 00:00:00 2001 From: Serhii Mumriak Date: Sat, 21 Oct 2023 11:38:40 -0700 Subject: [PATCH] Migrated Spoon* code from Cuising to TinyFoundation --- Package.swift | 9 +++ .../{empty.c => HijackingHacksEmpty.c} | 2 +- TinyFoundation/Sources/Spoon/SpoonEmpty.c | 21 ++++++ TinyFoundation/Sources/Spoon/include/Spoon.h | 8 +++ .../FoundationUtilities/CStringUtils.swift | 29 ++++++++ .../TinyFoundation/Process/SpoonProcess.swift | 72 +++++++++++++++++++ 6 files changed, 140 insertions(+), 1 deletion(-) rename TinyFoundation/Sources/HijackingHacks/{empty.c => HijackingHacksEmpty.c} (79%) create mode 100644 TinyFoundation/Sources/Spoon/SpoonEmpty.c create mode 100644 TinyFoundation/Sources/Spoon/include/Spoon.h create mode 100644 TinyFoundation/Sources/TinyFoundation/Process/SpoonProcess.swift diff --git a/Package.swift b/Package.swift index 80081a68..376224d4 100755 --- a/Package.swift +++ b/Package.swift @@ -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( @@ -182,6 +183,7 @@ let package = Package( .tinyFoundation, .hijackingHacks, + .spoon, .tinyFoundationTests, .cVulkan, @@ -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() @@ -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" ) @@ -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], diff --git a/TinyFoundation/Sources/HijackingHacks/empty.c b/TinyFoundation/Sources/HijackingHacks/HijackingHacksEmpty.c similarity index 79% rename from TinyFoundation/Sources/HijackingHacks/empty.c rename to TinyFoundation/Sources/HijackingHacks/HijackingHacksEmpty.c index 560a2b23..64d6b913 100644 --- a/TinyFoundation/Sources/HijackingHacks/empty.c +++ b/TinyFoundation/Sources/HijackingHacks/HijackingHacksEmpty.c @@ -1,5 +1,5 @@ // -// empty.c +// HijackingHacksEmpty.c // TinyFoundation // // Created by Serhii Mumriak on 11.01.2023 diff --git a/TinyFoundation/Sources/Spoon/SpoonEmpty.c b/TinyFoundation/Sources/Spoon/SpoonEmpty.c new file mode 100644 index 00000000..f079a7cb --- /dev/null +++ b/TinyFoundation/Sources/Spoon/SpoonEmpty.c @@ -0,0 +1,21 @@ +// +// Spoon.c +// Cuisine +// +// Created by Serhii Mumriak on 19.10.2023 +// + +#include "Spoon.h" +#include + +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; +} \ No newline at end of file diff --git a/TinyFoundation/Sources/Spoon/include/Spoon.h b/TinyFoundation/Sources/Spoon/include/Spoon.h new file mode 100644 index 00000000..d8a46918 --- /dev/null +++ b/TinyFoundation/Sources/Spoon/include/Spoon.h @@ -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); \ No newline at end of file diff --git a/TinyFoundation/Sources/TinyFoundation/FoundationUtilities/CStringUtils.swift b/TinyFoundation/Sources/TinyFoundation/FoundationUtilities/CStringUtils.swift index a215f737..b91659bf 100755 --- a/TinyFoundation/Sources/TinyFoundation/FoundationUtilities/CStringUtils.swift +++ b/TinyFoundation/Sources/TinyFoundation/FoundationUtilities/CStringUtils.swift @@ -46,6 +46,35 @@ public extension Array where Element: StringProtocol { try $0.withUnsafeBufferPointer(body) } } + + @_transparent + func withUnsafeNulllTerminatedCStringsArray(_ body: (UnsafeMutablePointer?>) throws -> (R)) rethrows -> R { + let array = nullTerminatedArrayOfCStrings + defer { + for i in 0..?> { + let size = count + 1 + let result: UnsafeMutablePointer?> = .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 { diff --git a/TinyFoundation/Sources/TinyFoundation/Process/SpoonProcess.swift b/TinyFoundation/Sources/TinyFoundation/Process/SpoonProcess.swift new file mode 100644 index 00000000..bb77d60e --- /dev/null +++ b/TinyFoundation/Sources/TinyFoundation/Process/SpoonProcess.swift @@ -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 + let arguments: UnsafeMutablePointer?> + let environment: UnsafeMutablePointer?> + let workDirectoryPath: UnsafePointer? +} + +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..