// RUN: %target-run-simple-swift | %FileCheck %s // REQUIRES: executable_test // REQUIRES: objc_interop // UNSUPPORTED: OS=tvos // UNSUPPORTED: OS=watchos import Foundation final class Foo: NSObject, NSCoding { var one, two: T init(one: T, two: T) { self.one = one self.two = two } @objc required convenience init(coder: NSCoder) { let one = coder.decodeObject(forKey: "one") as! T let two = coder.decodeObject(forKey :"two") as! T self.init(one: one, two: two) } @objc(encodeWithCoder:) func encode(with encoder: NSCoder) { encoder.encode(one, forKey: "one") encoder.encode(two, forKey: "two") } } // FIXME: W* macro equivalents should be in the Darwin/Glibc overlay func WIFEXITED(_ status: Int32) -> Bool { return (status & 0o177) == 0 } func WEXITSTATUS(_ status: Int32) -> Int32 { return (status >> 8) & 0xFF } // FIXME: "environ" should be in the Darwin overlay too @_silgen_name("_NSGetEnviron") func _NSGetEnviron() -> UnsafeMutablePointer?>> var environ: UnsafeMutablePointer?> { return _NSGetEnviron().pointee } func driver() { // Create a pipe to connect the archiver to the unarchiver. var pipes: [Int32] = [0, 0] guard pipe(&pipes) == 0 else { fatalError("pipe failed") } let pipeRead = pipes[0], pipeWrite = pipes[1] var archiver: pid_t = 0, unarchiver: pid_t = 0 let envp = environ do { // Set up the archiver's stdout to feed into our pipe. var archiverActions: posix_spawn_file_actions_t? guard posix_spawn_file_actions_init(&archiverActions) == 0 else { fatalError("posix_spawn_file_actions_init failed") } defer { posix_spawn_file_actions_destroy(&archiverActions) } guard posix_spawn_file_actions_adddup2(&archiverActions, pipeWrite, STDOUT_FILENO) == 0 && posix_spawn_file_actions_addclose(&archiverActions, pipeRead) == 0 else { fatalError("posix_spawn_file_actions_add failed") } // Spawn the archiver process. let str: StaticString = "-archive" let optStr = UnsafeMutableRawPointer(mutating: str.utf8Start).bindMemory( to: CChar.self, capacity: str.utf8CodeUnitCount) let archiverArgv: [UnsafeMutablePointer?] = [ CommandLine.unsafeArgv[0], optStr, nil ] guard posix_spawn(&archiver, CommandLine.unsafeArgv[0], &archiverActions, nil, archiverArgv, envp) == 0 else { fatalError("posix_spawn failed") } } do { // Set up the unarchiver's stdin to read from our pipe. var unarchiverActions: posix_spawn_file_actions_t? guard posix_spawn_file_actions_init(&unarchiverActions) == 0 else { fatalError("posix_spawn_file_actions_init failed") } defer { posix_spawn_file_actions_destroy(&unarchiverActions) } guard posix_spawn_file_actions_adddup2(&unarchiverActions, pipeRead, STDIN_FILENO) == 0 && posix_spawn_file_actions_addclose(&unarchiverActions, pipeWrite) == 0 else { fatalError("posix_spawn_file_actions_add failed") } // Spawn the unarchiver process. let str = "-unarchive" as StaticString let optStr = UnsafeMutableRawPointer(mutating: str.utf8Start).bindMemory( to: CChar.self, capacity: str.utf8CodeUnitCount) var unarchiver: pid_t = 0 let unarchiverArgv: [UnsafeMutablePointer?] = [ CommandLine.unsafeArgv[0], optStr, nil ] guard posix_spawn(&unarchiver, CommandLine.unsafeArgv[0], &unarchiverActions, nil, unarchiverArgv, envp) == 0 else { fatalError("posix_spawn failed") } } // Wash our hands of the pipe, now that the subprocesses have started. close(pipeRead) close(pipeWrite) // Wait for the subprocesses to finish. var waiting: Set = [archiver, unarchiver] while !waiting.isEmpty { var status: Int32 = 0 let pid = wait(&status) if pid == -1 { // If the error was EINTR, just wait again. if errno == EINTR { continue } // If we have no children to wait for, stop. if errno == ECHILD { break } fatalError("wait failed") } waiting.remove(pid) // Ensure the process exited successfully. guard WIFEXITED(status) && WEXITSTATUS(status) == 0 else { fatalError("subprocess exited abnormally") } } } func archive() { let data = NSMutableData() let archiver = NSKeyedArchiver(forWritingWith: data) archiver.encode(Foo(one: "one", two: "two"), forKey: "strings") archiver.encode(Foo(one: 1, two: 2), forKey: "numbers") archiver.finishEncoding() // Output the archived data over stdout, which should be piped to stdin // on the unarchiver process. while true { let status = write(STDOUT_FILENO, data.bytes, data.length) if status == data.length { break } if errno == EINTR { continue } fatalError("write failed") } } func unarchive() { // FIXME: Pre-instantiate the generic classes that were archived, since // the ObjC runtime doesn't know how. NSStringFromClass(Foo.self) NSStringFromClass(Foo.self) // Read in the data from stdin, where the archiver process should have // written it. var rawData: [UInt8] = [] var buffer = [UInt8](repeating: 0, count: 4096) while true { let count = read(STDIN_FILENO, &buffer, 4096) if count == 0 { break } if count == -1 { if errno == EINTR { continue } fatalError("read failed") } rawData += buffer[0.. else { fatalError("unable to unarchive Foo") } guard let numbers = unarchiver.decodeObject(forKey: "numbers") as? Foo else { fatalError("unable to unarchive Foo") } // CHECK-LABEL: <_TtGC4main3FooCSo8NSString_: {{0x[0-9a-f]+}}> #0 // CHECK: one: one // CHECK: two: two // CHECK-LABEL: <_TtGC4main3FooCSo8NSNumber_: {{0x[0-9a-f]+}}> #0 // CHECK: one: 1 // CHECK: two: 2 dump(strings) dump(numbers) } // Pick a mode based on the command-line arguments. // The test launches as a "driver" which then respawns itself into reader // and writer subprocesses. if CommandLine.arguments.count < 2 { driver() } else if CommandLine.arguments[1] == "-archive" { archive() } else if CommandLine.arguments[1] == "-unarchive" { unarchive() } else { fatalError("invalid commandline argument") }