Swift Command Line Tool utilizing Process() and multithreading crashes after a certain number of execution rounds (~3148) - swift

I implemented a password generator script in Swift which utilizes Process() to execute Mac OS X command line tasks. The passwords themselves are just random Strings which then are encrypted (bcrypt) by the command line task as follows:
/usr/sbin/htpasswd -bnBC 10 '' this_is_the_password | /usr/bin/tr -d ':\n'
Also multiple threads are used to generate passwords with their hashes in parallel.
Note: Both the multithreading and the command line task (compared to serveral other native Swift libraries I tried) improved performance in terms of execution time drastically.
The Problem
The Programm runs fine for the first ~3148 rounds and always crashes around this number (problably correlated to the number of threads running).
For example, if I configure 2000 passwords the code executes as expected terminates without any errors.
Common Error Messages
Setting a breakpoint in Process+Pipe.swift in the catch block of the execute(...) function at BREAKPOINT_1 results in:
Thread 1: signal SIGCHLD
po error.localizedDescription
"The operation couldn\\U2019t be completed. (NSPOSIXErrorDomain error 9 - Bad file descriptor)"
When uncommenting the four //return self.hash(string, cost: cost) code snippets to ignore the error the following errors finally crash the execution (again in execute(...), but not necessarily in the catch block):
Program stops ...
Thread 32: EXC_BAD_ACCESS (code=2, address=0x700003e6bfd4)
... on manual continue ...
Thread 2: EXC_BAD_ACCESS (code=2, address=0x700007e85fd4)
po process
error: Trying to put the stack in unreadable memory at: 0x700003e6bf40.
Code
The relevant code componets are the Main.swift which initialized and starts (and later stops) the PasswordGenerator and then loops n times to get passwords via nextPassword() from the PasswordGenerator. The PasswordGenerator itself utilized execute(...) from the Process extension to run commandline tasks which generate the hash.
Main.swift
class Main {
private static func generate(...) {
...
PasswordGenerator.start()
for _ in 0..<n {
let nextPassword = PasswordGenerator.nextPassword()
let readablePassword = nextPassword.readable
let password = nextPassword.hash
...
}
PasswordGenerator.stop()
...
}
}
PasswordGenerator.swift
The PasswordGenerator runs multiple Threads in parallel.
nextPassword() tries to get a password (if there is one in the passwords Array) or else waits for 100 seconds.
struct PasswordGenerator {
typealias Password = (readable: String, hash: String)
private static let semaphore = DispatchSemaphore(value: 1)
private static var active = false
private static var passwords: [Password] = []
static func nextPassword() -> Password {
self.semaphore.wait()
if let password = self.passwords.popLast() {
self.semaphore.signal()
return password
} else {
self.semaphore.signal()
sleep(100)
return self.nextPassword()
}
}
static func start(
numberOfWorkers: UInt = 32,
passwordLength: UInt = 10,
cost: UInt = 10
) {
self.active = true
for id in 0..<numberOfWorkers {
self.runWorker(id: id, passwordLength: passwordLength, cost: cost)
}
}
static func stop() {
self.semaphore.wait()
self.active = false
self.semaphore.signal()
}
private static func runWorker(
id: UInt,
passwordLength: UInt = 10,
cost: UInt = 10
) {
DispatchQueue.global().async {
var active = true
repeat {
// Update active.
self.semaphore.wait()
active = self.active
print("numberOfPasswords: \(self.passwords.count)")
self.semaphore.signal()
// Generate Password.
// Important: The bycrypt(cost: ...) step must be done outside the Semaphore!
let readable = String.random(length: Int(passwordLength))
let password = Password(readable: readable, hash: Encryption.hash(readable, cost: cost))
// Add Password.
self.semaphore.wait()
self.passwords.append(password)
self.semaphore.signal()
} while active
}
}
}
Encryption.swift
struct Encryption {
static func hash(_ string: String, cost: UInt = 10) -> String {
// /usr/sbin/htpasswd -bnBC 10 '' this_is_the_password | /usr/bin/tr -d ':\n'
let command = "/usr/sbin/htpasswd"
let arguments: [String] = "-bnBC \(cost) '' \(string)".split(separator: " ").map(String.init)
let result1 = Process.execute(
command: command,//"/usr/sbin/htpasswd",
arguments: arguments//["-bnBC", "\(cost)", "''", string]
)
let errorString1 = String(
data: result1?.error?.fileHandleForReading.readDataToEndOfFile() ?? Data(),
encoding: String.Encoding.utf8
) ?? ""
guard errorString1.isEmpty else {
// return self.hash(string, cost: cost)
fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) failed with error: \(errorString1)")
}
guard let output1 = result1?.output else {
// return self.hash(string, cost: cost)
fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) failed! No output.")
}
let command2 = "/usr/bin/tr"
let arguments2: [String] = "-d ':\n'".split(separator: " ").map(String.init)
let result2 = Process.execute(
command: command2,
arguments: arguments2,
standardInput: output1
)
let errorString2 = String(
data: result2?.error?.fileHandleForReading.readDataToEndOfFile() ?? Data(),
encoding: String.Encoding.utf8
) ?? ""
guard errorString2.isEmpty else {
// return self.hash(string, cost: cost)
fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) failed with error: \(errorString2)")
}
guard let output2 = result2?.output else {
// return self.hash(string, cost: cost)
fatalError("Error: Command \(command) \(arguments.joined(separator: " ")) failed! No output.")
}
guard
let hash = String(
data: output2.fileHandleForReading.readDataToEndOfFile(),
encoding: String.Encoding.utf8
)?.replacingOccurrences(of: "$2y$", with: "$2a$")
else {
fatalError("Hash: String replacement failed!")
}
return hash
}
}
Process+Pipe.swift
extension Process {
static func execute(
command: String,
arguments: [String] = [],
standardInput: Any? = nil
) -> (output: Pipe?, error: Pipe?)? {
let process = Process()
process.executableURL = URL(fileURLWithPath: command)
process.arguments = arguments
let outputPipe = Pipe()
let errorPipe = Pipe()
process.standardOutput = outputPipe
process.standardError = errorPipe
if let standardInput = standardInput {
process.standardInput = standardInput
}
do {
try process.run()
} catch {
print(error.localizedDescription)
// BREAKPOINT_1
return nil
}
process.waitUntilExit()
return (output: outputPipe, error: errorPipe)
}
}
Question(s)
Why does the program crash?
Why does it not crash for also huge numbers like 2000 passwords?
Is the multithreading implemented correct?
Is there a problem in the execute(...) code?

I have found a fix while researching this bug. It seems that, despite what the documentation claims, Pipe will not automatically close its reading filehandle.
So if you add a try outputPipe.fileHandleForReading.close() after reading from it, that will fix the issue.

It seems this is a Swift bug. I did some testing and could reproduce it by just running a lot of Process.run(). Filed an issue against Swift:
https://bugs.swift.org/browse/SR-15522

Related

The test runner exited with code 9 before finishing running tests

What does code 9 signify ( and how to fix the issue ) when Xcode crashes during the execution of WatchOS tests?
Scenario:
I have an XCTestCase that reads around 100 CSV test resource files. These files are comma-delimited, have approximately 6,000 lines, and have an average size of 64K. During my test case, I read these files into memory, and one my one, I verify that the input file ( after some processing ) matches the output file. Here is some code to demonstrate the flow:
import XCTest
#testable import MyWatch_Extension
class MyTest: XCTestCase {
func testMyAlgo() throws {
let testingData = discoverAvailableTestFiles(filter: "dataset_1");
XCTAssertGreaterThan(testingData.count, 0, "have data to process")
for (_, testingEntry) in testingData.enumerated() {
var input : [Double] = [];
var expectations : [Double] = [];
readInputOutputTestData(entries: &input, fileName: testingEntry + "_input");
readInputOutputTestData(entries: &expectations, fileName: testingEntry + "_expected_output");
// do something with the input, and store it into results
let results = MyAglo().doSomething();
compareResultsAndExpectations(testingEntry: testingEntry, results: results, expectations: expectations);
}
}
func discoverAvailableTestFiles(filter: String) -> Set<String> {
let bundle = Bundle(for: type(of: self))
let paths = bundle.paths(forResourcesOfType: "csv", inDirectory: nil);
var results = Set<String>()
for path in paths {
if (path.contains(filter)) {
let fileNameSubstring = path[path.index(path.lastIndex(of: "/")!, offsetBy: 1)...]
let qaFileName = fileNameSubstring[...fileNameSubstring.index(fileNameSubstring.lastIndex(of: "_")!, offsetBy: -1)]
results.insert(String(qaFileName))
}
}
return results;
}
func readInputOutputTestData(entries : inout [Double], fileName : String) {
let bundle = Bundle(for: type(of: self))
let path = bundle.path(forResource: fileName, ofType: "csv")!
do {
let data = try String(contentsOfFile: path, encoding: .utf8)
let myStrings = data.components(separatedBy: .newlines);
for idx in 0..<myStrings.count {
let parts = myStrings[idx].split(separator: ",");
if (parts.count > 0) {
for part in parts {
entries.append((part as NSString).doubleValue);
}
}
}
} catch {
print(error)
}
}
func compareResultsAndExpectations(testingEntry: String, results: [Double], expectations: [Double]) {
print("## testing \(testingEntry)");
XCTAssertEqual(results.count, expectations.count / 3, "mismatch in data count \(testingEntry)")
var counter = 0;
for idx in stride(from: 0, to: expectations.count, by: 3) {
XCTAssertEqual(results[counter], expectations[idx], accuracy: 0.5, "\(idx + 1) value mismatch")
counter += 1;
}
}
}
When I execute the testMyAlgo testcase, I might read the first 20 files, and I get the error message :
The test runner exited with code 9 before finishing running tests.
If I run each file individually or in smaller batches ( maybe only 20 of them, as opposed to the entire loop of 100 ), everything is fine. This leads me to believe that I am exhausting the memory space of the watch or should be executing the test case differently. Any idea what the problem is, or perhaps, how I should re-structure the test case to get away from this error? ( Maybe free resources or something similar before each test case )
You can create a dumb Xcode project that runs on an iPhone and copy/paste only this test to it. If it runs OK, that means you're exceeding the limits on the watch. If that's the case, you can create a framework with your algorithm and run the tests on the framework, and import the framework on your watch extension

How to read from keyboard in Swift 4.1.2? [duplicate]

I know that to program in STDIN and STDOUT, we need to make an command line project in Xcode.
But how do I take a standard input in playground.
Whenever I try to run such code in playground
var input = readLine()!
I always get this error
Execution was interrupted, reason: EXC_BAD_INSTRUCTION
(Code=EXC_l386_INVOP, subcode=0x0)
Is it possible to take STDIN in playground or not?
UPDATE
I know this error is because of nil input variable but want to know how to overcome this nil value.
Fixed Solution for SWIFT 3
To make it work, Create a new command line tool project.
Go to "File" -> "New" -> "Project" -> "macOS" -> "Command Line Tool".
import Foundation
print("Hello, World!")
func solveMefirst(firstNo: Int , secondNo: Int) -> Int {
return firstNo + secondNo
}
func input() -> String {
let keyboard = FileHandle.standardInput
let inputData = keyboard.availableData
return NSString(data: inputData, encoding:String.Encoding.utf8.rawValue) as! String
}
let num1 = readLine()
let num2 = readLine()
var IntNum1 = Int(num1!)
var IntNum2 = Int(num2!)
print("Addition of numbers is : \(solveMefirst(firstNo: IntNum1!, secondNo: IntNum2!))")
And run the project using CMD + R
Playground can not read an input from the commend line.
You can use a custom "readLine()" function and a global input variable, each element in the input array is presenting a line:
import Foundation
var currentLine = 0
let input = ["5", "5 6 3"]
func readLine() -> String? {
if currentLine < input.endIndex {
let line = input[currentLine]
currentLine += 1
return line
} else {
return nil
}
}
let firstLine = readLine() // 5
let secondLine = readLine() // 5 6 3
let thirdLine = readLine() // nil
Try using Optional Chaining:
if let input = readLine() {
print("Input: \(input)")
} else {
print("No input.")
}
Go to
New > Project > MacOs > Command Line Tool
then you can apply :
let value1: String?
value1 = readLine()
print(value1 ?? "")
"" for the default value
For getting input from command line, like Console.ReadLine... Chalkers has a solution as follows.
func input() -> String {
var keyboard = NSFileHandle.fileHandleWithStandardInput()
var inputData = keyboard.availableData
return NSString(data: inputData, encoding:NSUTF8StringEncoding) as! String
}
please ask for further if it doesn't work Vinod.

How to read a specific file's line in swift4?

Testing in Playground I read a whole file in an array of String, one string per line.
But what I need is a specific line only:
let dir = try? FileManager.default.url(for: .documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL = dir!.appendingPathComponent("test").appendingPathExtension("txt")
let text: [String] = try String(contentsOf: fileURL).components(separatedBy: NSCharacterSet.newlines)
let i = 2 // computed before, here to simplify
print(text[i])
There is a way to avoid reading the complete big file?
I'm guessing you mean that you want to retrieve the index without manually searching the array with, say, a for-in loop.
In Swift 4 you can use Array.index(where:) in combination with the StringProtocol's generic contains(_:) function to find what you're looking for.
Let's imagine you're looking for the first line containing the text "important stuff" in your text: [String] array.
You could use:
text.index(where: { $0.contains("important stuff") })
Behind the scenes, Swift is looping to find the text, but with built-in enhancements, this should perform better than manually looping through the text array.
Note that the result of this search could be nil if no matching lines are present. Therefore, you'll need to ensure it's not nil before using the result:
Force unwrap the result (risking the dreaded fatal error: unexpectedly found nil while unwrapping an Optional value):
print(text[lineIndex!)
Or, use an if let statement:
if let lineIndex = stringArray.index(where: { $0.contains("important stuff") }) {
print(text[lineIndex])
}
else {
print("Sorry; didn't find any 'important stuff' in the array.")
}
Or, use a guard statement:
guard let lineIndex = text.index(where: {$0.contains("important stuff")}) else {
print("Sorry; didn't find any 'important stuff' in the array.")
return
}
print(text[lineIndex])
To find a specific line without reading the entire file in, you could use this StreamReader answer. It contains code that worked in Swift 3. I tested it in Swift 4, as well: see my GitHub repo, TEST-StreamReader, for my test code.
You would still have to loop to get to the right line, but then break the loop once you've retrieved that line.
Here's the StreamReader class from that SO answer:
class StreamReader {
let encoding : String.Encoding
let chunkSize : Int
var fileHandle : FileHandle!
let delimData : Data
var buffer : Data
var atEof : Bool
init?(path: String, delimiter: String = "\n", encoding: String.Encoding = .utf8,
chunkSize: Int = 4096) {
guard let fileHandle = FileHandle(forReadingAtPath: path),
let delimData = delimiter.data(using: encoding) else {
return nil
}
self.encoding = encoding
self.chunkSize = chunkSize
self.fileHandle = fileHandle
self.delimData = delimData
self.buffer = Data(capacity: chunkSize)
self.atEof = false
}
deinit {
self.close()
}
/// Return next line, or nil on EOF.
func nextLine() -> String? {
precondition(fileHandle != nil, "Attempt to read from closed file")
// Read data chunks from file until a line delimiter is found:
while !atEof {
if let range = buffer.range(of: delimData) {
// Convert complete line (excluding the delimiter) to a string:
let line = String(data: buffer.subdata(in: 0..<range.lowerBound), encoding: encoding)
// Remove line (and the delimiter) from the buffer:
buffer.removeSubrange(0..<range.upperBound)
return line
}
let tmpData = fileHandle.readData(ofLength: chunkSize)
if tmpData.count > 0 {
buffer.append(tmpData)
} else {
// EOF or read error.
atEof = true
if buffer.count > 0 {
// Buffer contains last line in file (not terminated by delimiter).
let line = String(data: buffer as Data, encoding: encoding)
buffer.count = 0
return line
}
}
}
return nil
}
/// Start reading from the beginning of file.
func rewind() -> Void {
fileHandle.seek(toFileOffset: 0)
buffer.count = 0
atEof = false
}
/// Close the underlying file. No reading must be done after calling this method.
func close() -> Void {
fileHandle?.closeFile()
fileHandle = nil
}
}
extension StreamReader : Sequence {
func makeIterator() -> AnyIterator<String> {
return AnyIterator {
return self.nextLine()
}
}
}

Getting list of preferred wifi networks on Mac with Swift 3

Is there a way to get the list of preferred ( = saved) wifi's ssid on MacOS with Swift 3.0 ?
I found some example which are deprecated and are (surprisingly) only runnable on iOS.
Preferred Networks are stored in a plist as part of System Preferences NSUserDefaults. While I don't see an API to access these names directly, you can use the defaults shell command or NSTask to access the values:
defaults read /Library/Preferences/SystemConfiguration/com.apple.airport.preferences | grep SSIDString
Note that in this list are not only all of the SSIDs that the computer has connected to, but the list synced with any iCloud-enabled device.
Related discussion here: OS X Daily - See a List of All Wi-Fi Networks a Mac Has Previously Connected To
It might not be the most beautiful code ever, but it works in Swift 3.0.
func shell(arguments: [String] = []) -> (String? , Int32) {
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
let terminationStatus = task.terminationStatus
return (output, terminationStatus)
}
Extensions:
extension String {
func stringByReplacingFirstOccurrenceOfString(
target: String, withString replaceString: String) -> String
{
if let range = self.range(of: target) {
return self.replacingCharacters(in: range, with: replaceString)
}
return self
}
}
extension String {
func stringByReplacingLastOccurrenceOfString(
target: String, withString replaceString: String) -> String
{
if let range = self.range(of: target, options: String.CompareOptions.backwards) {
return self.replacingCharacters(in: range, with: replaceString)
}
return self
}
}
Get and clean the wifi's SSIDs
let (output, terminationStatus) = shell(arguments: ["-c", "defaults read /Library/Preferences/SystemConfiguration/com.apple.airport.preferences | grep SSIDString"])
if (terminationStatus == 0) {
let arrayOfWifi = output?.components(separatedBy: CharacterSet.newlines)
for var aWifi in arrayOfWifi! {
aWifi = aWifi.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if (aWifi.hasPrefix("SSIDString = ")) {
aWifi = aWifi.stringByReplacingFirstOccurrenceOfString(target: "SSIDString = ", withString: "")
}
if (aWifi.hasPrefix("\"")) {
aWifi = aWifi.stringByReplacingFirstOccurrenceOfString(target: "\"", withString: "")
}
if (aWifi.hasSuffix("\";")) {
aWifi = aWifi.stringByReplacingLastOccurrenceOfString(target: "\";", withString: "")
}
if (aWifi.hasSuffix(";")) {
aWifi = aWifi.stringByReplacingLastOccurrenceOfString(target: ";", withString: "")
}
aWifi = aWifi.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
print(aWifi)
}
}
}

Swift - Execute multiple commands as root without asking password multiple times

I want to write a litte program that checks a couple of directories as root.
I use the following code:
func execute(sudoCommands: String) -> String {
return(execute(command: "osascript -e \"do shell script \\\"" + sudoCommands + "\\\" with administrator privileges\""))
}
func execute(command: String) -> String {
var arguments:[String] = []
arguments.append("-c")
arguments.append( command )
let task = Process()
task.launchPath = "/bin/sh"
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
task.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
return(NSString(data: data, encoding: String.Encoding.utf8.rawValue) as! String)
}
func getSize(path: String, asRoot: Bool = false) -> String {
var result: String!
if(asRoot){
result = execute(sudoCommands: "sudo du -shm " + path)
} else {
result = execute(command: "du -shm " + path)
}
var sizesMB = 0
for dirResult in result.components(separatedBy: "\n") where dirResult != "" {
let currentDirSize = dirResult.components(separatedBy: "\t")[0]
sizesMB = sizesMB + Int(currentDirSize)!
}
return( "\(sizesMB) MB")
}
But if I run getSize("/Library/Caches/", asRoot: True) multiple times in a row, it (osascript) will always ask for my password again and again...
Is there some way (that's secure) to execute the commands as root without being prompt to enter the password?
I could ask for the password, save it in a variable and use it when necessary, but that really isn't safe or secure and I rather let Apple handle this one for me...