Skip to content

Commit

Permalink
Linux / macOS: added new option for creating processes: IProcessNoPty
Browse files Browse the repository at this point in the history
Signed-off-by: Eran Ifrah <[email protected]>
  • Loading branch information
eranif committed Nov 22, 2024
1 parent 60278f8 commit 558bfbd
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 73 deletions.
1 change: 1 addition & 0 deletions CodeLite/AsyncProcess/asyncprocess.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ enum IProcessCreateFlags {
IProcessInteractiveSSH = (1 << 9),
IProcessWrapInShell = (1 << 10), // wrap the command in the OS shell (CMD, BASH)
IProcessPseudoConsole = (1 << 11), // MSW only: use CreatePseudoConsole API for creating the process
IProcessNoPty = (1 << 12), // Unix only: do not use forkpty, use normal fork()
};

class WXDLLIMPEXP_CL IProcess;
Expand Down
173 changes: 113 additions & 60 deletions CodeLite/AsyncProcess/unixprocess_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ extern char* strdup(); /* Duplicate a string */
static void freeargv(char** vector)
{
char** scan;
if(vector != NULL) {
for(scan = vector; *scan != NULL; scan++) {
if (vector != NULL) {
for (scan = vector; *scan != NULL; scan++) {
free(*scan);
}
delete[] vector;
Expand All @@ -120,7 +120,7 @@ static void freeargv(char** vector)
static char** make_argv(const wxArrayString& args, int& argc)
{
char** argv = new char*[args.size() + 1];
for(size_t i = 0; i < args.size(); ++i) {
for (size_t i = 0; i < args.size(); ++i) {
argv[i] = strdup(args[i].mb_str(wxConvUTF8).data());
}
argv[args.size()] = 0;
Expand All @@ -141,17 +141,18 @@ void UnixProcessImpl::Cleanup()
{
close(GetReadHandle());
close(GetWriteHandle());
if(GetStderrHandle() != wxNOT_FOUND) {
if (GetStderrHandle() != wxNOT_FOUND) {
close(GetStderrHandle());
}
if(m_thr) {

if (m_thr) {
// Stop the reader thread
m_thr->Stop();
delete m_thr;
}
m_thr = NULL;

if(GetPid() != wxNOT_FOUND) {
if (GetPid() != wxNOT_FOUND) {
wxKill(GetPid(), GetHardKill() ? wxSIGKILL : wxSIGTERM, NULL, wxKILL_CHILDREN);
// The Zombie cleanup is done in app.cpp in ::ChildTerminatedSingalHandler() signal handler
int status(0);
Expand All @@ -163,28 +164,28 @@ bool UnixProcessImpl::IsAlive() { return kill(m_pid, 0) == 0; }

bool UnixProcessImpl::ReadFromFd(int fd, fd_set& rset, wxString& output, std::string& raw_output)
{
if(fd == wxNOT_FOUND) {
if (fd == wxNOT_FOUND) {
return false;
}
if(FD_ISSET(fd, &rset)) {
if (FD_ISSET(fd, &rset)) {
// there is something to read
char buffer[BUFF_SIZE + 1]; // our read buffer
int bytesRead = read(fd, buffer, sizeof(buffer));
if(bytesRead > 0) {
if (bytesRead > 0) {

buffer[bytesRead] = 0; // always place a terminator
raw_output = std::string(buffer, bytesRead);

// Remove coloring chars from the incomnig buffer
// colors are marked with ESC and terminates with lower case 'm'
if(!(this->m_flags & IProcessRawOutput)) {
if (!(this->m_flags & IProcessRawOutput)) {
std::string stripped_buffer;
StringUtils::StripTerminalColouring(raw_output, stripped_buffer);
raw_output.swap(stripped_buffer);
}

wxString convBuff = wxString(raw_output.c_str(), wxConvUTF8, raw_output.length());
if(convBuff.empty()) {
if (convBuff.empty()) {
convBuff = wxString::From8BitData(raw_output.c_str(), raw_output.length());
}

Expand All @@ -202,7 +203,7 @@ bool UnixProcessImpl::Read(wxString& buff, wxString& buffErr, std::string& raw_b

memset(&rs, 0, sizeof(rs));
FD_SET(GetReadHandle(), &rs);
if(m_stderrHandle != wxNOT_FOUND) {
if (m_stderrHandle != wxNOT_FOUND) {
FD_SET(m_stderrHandle, &rs);
}

Expand All @@ -216,19 +217,19 @@ bool UnixProcessImpl::Read(wxString& buff, wxString& buffErr, std::string& raw_b
int maxFd = wxMax(GetStderrHandle(), GetReadHandle());
int rc = select(maxFd + 1, &rs, NULL, NULL, &timeout);
errCode = errno;
if(rc == 0) {
if (rc == 0) {
// timeout
return true;

} else if(rc > 0) {
} else if (rc > 0) {
// We differentiate between stdout and stderr?
bool stderrRead = ReadFromFd(GetStderrHandle(), rs, buffErr, raw_buffErr);
bool stdoutRead = ReadFromFd(GetReadHandle(), rs, buff, raw_buff);
return stderrRead || stdoutRead;

} else {

if(errCode == EINTR || errCode == EAGAIN) {
if (errCode == EINTR || errCode == EAGAIN) {
return true;
}

Expand All @@ -253,10 +254,10 @@ bool do_write(int fd, const wxMemoryBuffer& buffer)
clDEBUG1() << "do_write() buffer:" << str << endl;
clDEBUG1() << "do_write() length:" << str.length() << endl;
}
while(bytes_left) {
while (bytes_left) {
int bytes_sent = ::write(fd, (const char*)pdata, bytes_left);
LOG_IF_TRACE { clDEBUG1() << "::do_write() completed. number of bytes sent:" << bytes_sent << endl; }
if(bytes_sent <= 0) {
if (bytes_sent <= 0) {
return false;
}
pdata += bytes_sent;
Expand All @@ -279,81 +280,117 @@ bool UnixProcessImpl::WriteRaw(const std::string& buff)
return do_write(GetWriteHandle(), mb);
}

namespace
{
bool create_pipe(int& read_end, int& write_end)
{
int fds[2] = { 0, 0 };
errno = 0;
if (::pipe(fds) < 0) {
clERROR() << "Failed to create pipe()." << strerror(errno);
return false;
}
read_end = fds[0];
write_end = fds[1];
return true;
}
} // namespace

IProcess* UnixProcessImpl::Execute(wxEvtHandler* parent, const wxArrayString& args, size_t flags,
const wxString& workingDirectory, IProcessCallback* cb)
{
int argc = 0;
char** argv = make_argv(args, argc);
if(argc == 0 || argv == nullptr) {
if (argc == 0 || argv == nullptr) {
return nullptr;
}

// fork the child process
wxString curdir = wxGetCwd();

// Prentend that we are a terminal...
int master;
int master = wxNOT_FOUND;
char pts_name[1024];
memset(pts_name, 0x0, sizeof(pts_name));

// Create a one-way communication channel (pipe).
// If successful, two file descriptors are stored in stderrPipes;
// bytes written on stderrPipes[1] can be read from stderrPipes[0].
// Returns 0 if successful, -1 if not.
int stderrPipes[2] = { 0, 0 };
if(flags & IProcessStderrEvent) {
errno = 0;
if(pipe(stderrPipes) < 0) {
clERROR() << "Failed to create pipe for stderr redirecting." << strerror(errno);
flags &= ~IProcessStderrEvent;
int stderr_read = wxNOT_FOUND, stderr_write = wxNOT_FOUND;
int stdout_read = wxNOT_FOUND, stdout_write = wxNOT_FOUND;
int stdin_read = wxNOT_FOUND, stdin_write = wxNOT_FOUND;
if (flags & (IProcessStderrEvent | IProcessNoPty)) {
if (!create_pipe(stderr_read, stderr_write)) {
return nullptr;
}
}

int rc = 0;
if (flags & IProcessNoPty) {
if (!create_pipe(stdout_read, stdout_write) || !create_pipe(stdin_read, stdin_write)) {
return nullptr;
}
rc = fork();
} else {
rc = forkpty(&master, pts_name, nullptr, nullptr);
}

int rc = forkpty(&master, pts_name, nullptr, nullptr);
if(rc == 0) {
if (rc == 0) {
//===-------------------------------------------------------
// Child process
//===-------------------------------------------------------
for(int i = 0; i < FD_SETSIZE; ++i) {
if(i != STDERR_FILENO && i != STDOUT_FILENO && i != STDIN_FILENO) {
if((i == stderrPipes[1]) && (flags & IProcessStderrEvent)) {
for (int i = 0; i < FD_SETSIZE; ++i) {
if (i != STDERR_FILENO && i != STDOUT_FILENO && i != STDIN_FILENO) {
// Do not close the interesting pipes
if (i == stderr_write || i == stdout_write || i == stdin_read) {
// skip it
} else {
::close(i);
}
}
}

// Incase the user wants to get separate events for STDERR, dup2 STDERR to the PIPE write end
// we opened earlier
if(flags & IProcessStderrEvent) {
// Dup stderrPipes[1] into stderr
dup2(stderrPipes[1], STDERR_FILENO);
close(stderrPipes[1]);
if (stderr_write != wxNOT_FOUND) {
dup2(stderr_write, STDERR_FILENO);
close(stderr_write);
}

// Make this process as its own group
setpgid(0, 0);

if (stdout_write != wxNOT_FOUND) {
dup2(stdout_write, STDOUT_FILENO);
close(stdout_write);
}

if (stdin_read != wxNOT_FOUND) {
dup2(stdin_read, STDIN_FILENO);
close(stdin_read);
}

// at this point, slave is used as stdin/stdout/stderr
// Child process
if(workingDirectory.IsEmpty() == false) {
wxSetWorkingDirectory(workingDirectory);
if (!workingDirectory.empty()) {
::wxSetWorkingDirectory(workingDirectory);
}

// execute the process
errno = 0;
if(execvp(argv[0], argv) < 0) {
if (execvp(argv[0], argv) < 0) {
wxString errmsg = strerror(errno);
clERROR() << "execvp" << args << "error:" << errmsg << endl;
// print the environment as well
clERROR() << "Dumping environment:" << endl;
for(char** scan = environ; *scan != nullptr; scan++) {
for (char** scan = environ; *scan != nullptr; scan++) {
clERROR() << *scan << endl;
}
}

// if we got here, we failed...
exit(1);

} else if(rc < 0) {
} else if (rc < 0) {
// Error

// restore the working directory
Expand All @@ -365,43 +402,59 @@ IProcess* UnixProcessImpl::Execute(wxEvtHandler* parent, const wxArrayString& ar
//===-------------------------------------------------------
// Parent
//===-------------------------------------------------------
struct termios tios;
tcgetattr(master, &tios);
tios.c_lflag &= ~(ECHO | ECHONL);
tcsetattr(master, TCSAFLUSH, &tios);
if (master != wxNOT_FOUND) {
struct termios tios;
tcgetattr(master, &tios);
tios.c_lflag &= ~(ECHO | ECHONL);
tcsetattr(master, TCSAFLUSH, &tios);
}

freeargv(argv);
argc = 0;

// disable ECHO
struct termios termio;
tcgetattr(master, &termio);
cfmakeraw(&termio);
termio.c_lflag = ICANON | ISIG; // support signals
termio.c_lflag &= ~ECHO;
termio.c_oflag = ONOCR | ONLRET;
tcsetattr(master, TCSANOW, &termio);
if (master != wxNOT_FOUND) {
struct termios termio;
tcgetattr(master, &termio);
cfmakeraw(&termio);
termio.c_lflag = ICANON | ISIG; // support signals
termio.c_lflag &= ~ECHO;
termio.c_oflag = ONOCR | ONLRET;
tcsetattr(master, TCSANOW, &termio);
}

// restore the working directory
wxSetWorkingDirectory(curdir);
::wxSetWorkingDirectory(curdir);

UnixProcessImpl* proc = new UnixProcessImpl(parent);
proc->m_callback = cb;
if(flags & IProcessStderrEvent) {
close(stderrPipes[1]); // close the write end
// Set fds. If "master" is != wxNOT_FOUND, then "stdout_write" and "stdin_read" are wxNOT_FOUND
// See above code
if (stderr_read != wxNOT_FOUND) {
close(stderr_write);
// set the stderr handle
proc->SetStderrHandle(stderrPipes[0]);
proc->SetStderrHandle(stderr_read);
}

if (master != wxNOT_FOUND) {
proc->SetReadHandle(master);
proc->SetWriteHandler(master);

} else {
close(stdout_write);
proc->SetReadHandle(stdout_read);

close(stdin_read);
proc->SetWriteHandler(stdin_write);
}

proc->SetReadHandle(master);
proc->SetWriteHandler(master);
proc->SetPid(rc);
proc->m_flags = flags; // Keep the creation flags

// Keep the terminal name, we will need it
proc->SetTty(pts_name);

if(!(proc->m_flags & IProcessCreateSync)) {
if (!(proc->m_flags & IProcessCreateSync)) {
proc->StartReaderThread();
}
return proc;
Expand Down Expand Up @@ -448,7 +501,7 @@ bool UnixProcessImpl::WriteToConsole(const wxString& buff)

void UnixProcessImpl::Detach()
{
if(m_thr) {
if (m_thr) {
// Stop the reader thread
m_thr->Stop();
delete m_thr;
Expand Down
4 changes: 1 addition & 3 deletions CodeLite/AsyncProcess/unixprocess_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

#ifndef __unixprocessimpl__
#define __unixprocessimpl__
#pragma once

#if defined(__WXMAC__) || defined(__WXGTK__)
#include "asyncprocess.h"
Expand Down Expand Up @@ -79,4 +78,3 @@ class WXDLLIMPEXP_CL UnixProcessImpl : public IProcess
void Signal(wxSignal sig) override;
};
#endif // #if defined(__WXMAC )||defined(__WXGTK__)
#endif // __unixprocessimpl__
Loading

0 comments on commit 558bfbd

Please sign in to comment.