Swift Process with Psuedo Terminal (PTY) - swift

I'm looking for some insight and guidance on using the Foundation.Process type with a PTY (Psuedo Terminal) so that the subprocess can accept input and behave as if it was running via a terminal.
The reason for needing a PTY is that for programs like ssh or in my case (xcodes) which ask for user input including passwords, running these via Foundation.Process does not display the prompts to the user as the output is usually buffered (this works fine in the Xcode debugger console but when running via a real terminal that is buffered the prompts are never displayed in the terminal)
Looking at other threads it seems like correct approach here is create a PTY and use the filehandles to attach to the Process.
While I've got this to work to the point where prompts are now shown, I cant seem to figure out how to pass input back to the process as these are being controlled by the PTY.
Here is my Process setup:
let process = Process()
// Setup the process with path, args, etc...
// Setup the PTY handles
var parentDescriptor: Int32 = 0
var childDescriptor: Int32 = 0
guard Darwin.openpty(&parentDescriptor, &childDescriptor, nil, nil, nil) != -1 else {
  fatalError("Failed to spawn PTY")
}
parentHandle = FileHandle(fileDescriptor: parentDescriptor, closeOnDealloc: true)
childHandle = FileHandle(fileDescriptor: childDescriptor, closeOnDealloc: true)
process.standardInput = childHandle
process.standardOutput = childHandle
process.standardError = childHandle
With this setup I then read the parent handle and output any result it gets (such as the input prompts):
parentHandle?.readabilityHandler = { handle in
  guard let line = String(data: handle.availableData, encoding: .utf8), !line.isEmpty else {
    return
  }
  logger.notice("\(line)")
}
When process.run() is executed the program runs and I can see it asks for Apple ID: input in my terminal, however, when typing input into the terminal the process does not seem to react to this input.
I've tried forwarding the FileHandle.standardInput:
FileHandle.standardInput.readabilityHandler = { handle in
  parentHandle?.write(handle.availableData)
}
But this doesn't seem to work either.
What is the recommended way to setup a PTY with Foundation.Process for executing arbitrary programs and having them behave as if they were being run in a terminal context?
Most of the resources I found online are about other languages and I'd like to stick with Foundation.Process vs. doing anything custom in C/C++ if possible as it just makes it easier to reason about / maintain. The resources for Swift on this topic are very lacking and I've checked out some open source projects that claim to do this but most require manually sending input to the PTY handle vs. accepting them from the user in a terminal.
Any insight / help is very much appreciated!

Related

Calling nettop from swift: "Unable to allocate a kernel control socket"

I am writing a macOS GUI wrapper for the command-line nettop utility.
When I call nettop -P -L 1 from the Terminal app through zsh, it displays proper output, but when I launch the process from within my Swift app, it displays the following in my app:
nettop[19213:2351456] [NetworkStatistics] Unable to allocate a kernel control socket nettop: NStatManagerCreate failed
Is this some sort of permissions or sandboxing issue, and if so, how do I get my app to request the proper permissions?
Code snippet:
func shell(launchPath: String, arguments: [String] = []) -> (String? , Int32) {
let task = Process()
task.launchPath = launchPath
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)
task.waitUntilExit()
return (output, task.terminationStatus)
}
...
struct ContentView: View {
var body: some View {
Text(shell(launchPath: "/usr/bin/nettop", arguments: ["-P", "-L", "1"]).0 ?? "No Output")
}
}
This is, indeed, a sandbox issue: nettop operates by creating a com.apple.network.statistics PF_SYSTEM/SYSPROTO_CONTROL socket (an example of the source can be seen at http://newosxbook.com/code/listings/17-1-lsock.c), which can be sandboxed.
Access to this socket is restricted by two layers:
The com.apple.private.network.statistics entitlement is enforced when net.statistics_privcheck sysctl is set to 1. That's not your case, since you're execing nettop, which has the entitlement anyway
The sandbox profile prevents the creation of system sockets unless explicitly allowed by a
(allow network-outbound
(control-name "com.apple.network.statistics")
(control-name "com.apple.netsrc"))
rule.
It seems that the latter case is what happens in your case, though from your detail it's not clear if the fault is in your own app's sandbox profile or the exec'ed nettop. To determine this, try to create the socket yourself - if you can do that, then it's the exec's problem. If you can't, it's your own profile. It's possible to create your own sandbox profiles, but that's something Apple won't ever allow publicly and will probably get you kicked out of the App Store.
Another test - run your app directly from the command line. (that is, in a terminal, then /Applications/path/to/your.app/Contents/MacOS/yourApp) . This way, it is not launched by xpcproxy, and will suffer from less sandboxing constraints.

Get full list of running processes (including those owned by root) with Swift

I am developing an app for monitoring the highest consuming processes in Swift, but I'm stuck at the part of obtaining the list of processes that are currently running. I've tried a lot of things, such as:
Running the top or ps aux | less commands and parsing the output.
I tried using this code to run the top command and pass the output to a NSPipe in order to parse it later, but I can't seem to run the command because it gives the error Couldn't posix_spawn: error 13, and I couldn't find anything on the internet on how to fix this, so I had to find another way.
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.launchPath = "/usr/bin"
task.arguments = ["top"]
task.launch()
task.waitUntilExit()
let data = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)!
Using NSWorkspace.shared.runningApplications
I saw this stack overflow question regarding the same topic, but it isn't answered (one comment references another thread that answers how to do it in C, but it isn't what I actually expected). The thread's OP used the code below in order to get the full list of running processes, but it only returns the user-owned ones, so it isn't really useful.
let workspace = NSWorkspace.shared
let applications = workspace.runningApplications
for application in applications {
if let url = (application.executableURL?.absoluteString) {
os_log("%{public}s", log:scribe, type:.debug, url)
}
}
}
Conclusion
Is there a way I can get a list of running processes in macOS (including those owned by root) in Swift? If there's another way through which I could retrieve at least the two most CPU-consuming processes that would do as well.
Thanks in advance.

Update/refresh object in the VSCode watch panel programmatically from debug server

I maintain a VSCode debugger extension for Roku's BrightScript language. Roku doesn't have a formal debug protocol, so my debugger uses the telnet command-line utility to issue various debugging commands (like print variable, continue, step, etc...). Since the Roku is a network device, and we are doing string parsing, every command is fairly slow compared to debugging an on-machine program like node or C#. As such, we try to lazy load data instead of eagerly loading data whenever possible.
Take this object for example:
business = {
employeeNames: ["Michael", "Jim", "Pam", "Dwight"],
name: "Dunder Mifflin Paper Company"
}
When a user adds business to the watch panel, vscode issues an evaluateRequest for "business". Behind the scenes, I run a telnet command print business, which returns the following:
<Component: roAssociativeArray> =
{
employeeNames: <Component: roArray>
name: "Dunder Mifflin Paper Company"
}
At this point, we know that employeeNames is an array, but we don't know its size, and shouldn't eagerly look up its size because that requires a separate command that would slow down the entire debugging experience.
Next, in the VSCode watch panel, the user expands the employeeNames property, and vscode calls variablesRequest. My debug extension runs the telnet command print business.employeeNames, and it returns this:
<Component: roArray> =
[
"Michael"
"Jim"
"Pam"
"Dwight"
]
At this point, I want to update employeeNames type to show the array count, like this:
Here's the DebugProtocol.Variable that I would change in the debug extension...but I don't know how to send this to VSCode when VSCode hasn't asked for updates on this variable.
//hardcoded for the sake of this question...is dynamic in actual code
var variableName = "business.employeeNames"
var arrayValues= getArrayValues(variableName); // ["Michael", "Jim", "Pam", "Dwight"]
let variable: DebugProtocol.Variable = {
name: "employeeNames";
evaluateName: "business.employeeNames";
type: `roArray (${arrayValues})`;
value: arrayValues;
children: [];
}
I tried to trick vscode into re-requesting everything, but VSCode didn't request new variables because it was smart enough to realize that the StoppedEvent was at the same location where it was previously stopped.
//send a continue
this.sendEvent(new ContinuedEvent(0, true));
//wait a short time
setTimeout(()=>{
//send a stopped event
this.sendEvent(new StoppedEvent('exception', threadId, exception.message));
}, 10);
As a debug extension author, from the debug extension code, how do I tell VSCode that a variable in the watch panel has changed, and needs to be refreshed?

Simple UDP socket in VC++ MFC

I have been trying to write a working program that takes in data from a UDP socket and displays it in an edit control box as you receive the data (My exposure to c++ is also only about a week :P have only done embedded C code before). I have a working program that can send and output data on a button click but I want something that can do it in real time. The aim is scale this up into a larger GUI program that can send control data to hardware and get responses from them.
I have run into various problems including:
The program just not executing my OnReceivefunction (derived from
CAsyncSocket)
Getting the OnReceive function to run on a separate thread so that it can still run after a button has been clicked sending a control packet to the client then waiting for a response in a while loop
Not being able to output the data in the edit box (tried using both CEdit and CString)
ReplaceSel error saying that the type char is incompatible with LPCTSTR
My code is based on this codeproject.com tutorial, being almost exactly what I want but I get the error in 4.
EDIT: the error in 4. disappears when I change it to a TCHAR but then it outputs random chinese characters. The codeproject.com tutorial outputs the correct characters regardless of char or TCHAR declaration. When debugged my code has type wchar_t instead type char like the other code.
Chinese output
In the working program echoBuffer[0] the character sent and displayed was a 1
UINT ReceiveData(LPVOID pParam)
{
CTesterDlg *dlg = (CTesterDlg*)pParam;
AfxSocketInit(NULL);
CSocket echoServer;
// Create socket for sending/receiving datagrams
if (echoServer.Create(12345, SOCK_DGRAM, NULL) == 0)
{
AfxMessageBox(_T("Create() failed"));
}
for (;;)
{ // Run forever
// Client address
SOCKADDR_IN echoClntAddr;
// Set the size of the in-out parameter
int clntAddrLen = sizeof(echoClntAddr);
// Buffer for echo string
char echoBuffer[ECHOMAX];
// Block until receive message from a client
int recvMsgSize = echoServer.ReceiveFrom(echoBuffer, ECHOMAX, (SOCKADDR*)&echoClntAddr, &clntAddrLen, 0);
if (recvMsgSize < 0)
{
AfxMessageBox(_T("RecvFrom() failed"));
}
echoBuffer[recvMsgSize] = '\0';
dlg->m_edit.ReplaceSel(echoBuffer);
dlg->m_edit.ReplaceSel(_T("\r\n"));
}
}
After reading the link that #IInspectable provided about working with strings and checking the settings differences between the two programs it became clear that the issue lay with an incorrect conversion to UNICODE. My program does not require it so I disabled it.
This has cleared up the issue in 4. and provided solutions for 2 and 3.
I also think I know why another instance of my program would not run OnReceivein 1. because that file was not being defined by one that was already being run by the program, but that is now irrelevant.

Stop VFP from showing dialog boxes when errors occur

I am trying to call an existing VFP 6 application using Jacob which is a COM bridge for Java.
val vfp = new Application(new ActiveXComponent("VisualFoxPro.Application").getProperty("Application").toDispatch())
vfp.setVisible(false)
try {
vfp.doCmd("do my.exe with myconfig.txt")
} catch {
case t: Throwable => t.printStackTrace
} finally {
vfp.doCmd("close data")
vfp.doCmd("clear all")
vfp.doCmd("clear")
vfp.quit
vfp.safeRelease
}
When there are no error conditions this code executes well and generates the expected .dbfs. The problem is that when an error occurs (.dbf not found, file in use by another user, etc) a GUI window pops up and stops execution of the program until I use the mouse to cancel it. I want this program to run on a server with no user interaction so this won't work.
How can I gracefully handle the errors preferably without making a change to the VFP 6 program?
Since you have the source code for VFP6, I would suggest looking into
SYS(2335,0)
Sys 2335 is used to identify if the program is running in an "unattended" mode, any such popup dialog boxes will throw an error and prevent an actual "hit" ok/cancel/whatever button to continue. This includes popup window prompting user to pick a table.
I'm not positive of when it was made available as I had limited use of it. Like you, when dealing with a COM server under IIS and obviously nobody there to respond.
Start JVM in headless mode, catch HeadlessException or something. Or, write a Java program that will execute your GUI program using Runtime, and restart in a case of parsed errors in console.