// Distributed under the MIT License.
// See the LICENSE file in the project root for more information.
+#ifndef FEATURE_PAL
+// Turn off macro definitions named max and min in <windows.h> header file
+// to avoid compile error for std::max().
+#define NOMINMAX
+#endif
+#ifdef _MSC_VER
+// Disable compiler warning about unsafe std::copy
+#define _SCL_SECURE_NO_WARNINGS
+#endif
+
#include "platform.h"
#include <cstring>
#include <set>
#include <fstream>
#include <thread>
+#include <algorithm>
+#ifdef FEATURE_PAL
#include <dirent.h>
#include <sys/stat.h>
#include <dlfcn.h>
#include <linux/limits.h>
#endif
+#else
+#include <windows.h>
+#include <winsock2.h>
+#endif
unsigned long OSPageSize()
{
static unsigned long pageSize = 0;
+#ifdef FEATURE_PAL
if (pageSize == 0)
pageSize = sysconf(_SC_PAGESIZE);
-
+#endif
return pageSize;
}
return i == std::string::npos ? path : path.substr(i + 1);
}
+// From https://stackoverflow.com/questions/13541313/handle-socket-descriptors-like-file-descriptor-fstream-c-linux
+#ifdef WIN32
+typedef HANDLE fd_t;
+#define FD_INVALID_VALUE INVALID_HANDLE_VALUE
+#else
+typedef int fd_t;
+#define FD_INVALID_VALUE (-1)
+#endif
+
+class fdbuf : public std::streambuf
+{
+private:
+ enum { bufsize = 1024 };
+ char outbuf_[bufsize];
+ char inbuf_[bufsize + 16 - sizeof(int)];
+ fd_t fd_;
+public:
+ typedef std::streambuf::traits_type traits_type;
+
+ fdbuf(fd_t fd);
+ virtual ~fdbuf();
+ void open(fd_t fd);
+ void close();
+
+protected:
+ int overflow(int c) override;
+ int underflow() override;
+ int sync() override;
+
+private:
+ int fdsync();
+
+ std::streamsize fdread(void *buf, size_t count);
+ std::streamsize fdwrite(const void *buf, size_t count);
+ void fdclose();
+};
+
+fdbuf::fdbuf(fd_t fd)
+ : fd_(FD_INVALID_VALUE) {
+ this->open(fd);
+}
+
+fdbuf::~fdbuf() {
+ if (this->fd_ != FD_INVALID_VALUE) {
+ this->fdsync();
+ this->fdclose();
+ }
+}
+
+std::streamsize fdbuf::fdread(void *buf, size_t count)
+{
+#ifdef WIN32
+ DWORD dwRead = 0;
+ BOOL bSuccess = ReadFile(this->fd_, buf, (DWORD)count, &dwRead, NULL);
+
+ if (!bSuccess)
+ dwRead = 0;
+
+ return dwRead;
+#else
+ return ::read(this->fd_, buf, count);
+#endif
+}
+
+std::streamsize fdbuf::fdwrite(const void *buf, size_t count)
+{
+#ifdef WIN32
+ DWORD dwWritten = 0;
+ BOOL bSuccess = WriteFile(this->fd_, buf, (DWORD)count, &dwWritten, NULL);
+
+ if (!bSuccess)
+ dwWritten = 0;
+
+ return dwWritten;
+#else
+ return ::write(this->fd_, buf, count);
+#endif
+}
+
+void fdbuf::fdclose()
+{
+#ifdef WIN32
+ CloseHandle(this->fd_);
+#else
+ ::close(this->fd_);
+#endif
+}
+
+void fdbuf::open(fd_t fd) {
+ this->close();
+ this->fd_ = fd;
+ this->setg(this->inbuf_, this->inbuf_, this->inbuf_);
+ this->setp(this->outbuf_, this->outbuf_ + bufsize - 1);
+}
+
+void fdbuf::close() {
+ if (!(this->fd_ < 0)) {
+ this->sync();
+ this->fdclose();
+ }
+}
+
+int fdbuf::overflow(int c) {
+ if (!traits_type::eq_int_type(c, traits_type::eof())) {
+ *this->pptr() = traits_type::to_char_type(c);
+ this->pbump(1);
+ }
+ return this->sync() == -1
+ ? traits_type::eof()
+ : traits_type::not_eof(c);
+}
+
+int fdbuf::sync() {
+ return fdsync();
+}
+
+int fdbuf::fdsync() {
+ if (this->pbase() != this->pptr()) {
+ std::streamsize size(this->pptr() - this->pbase());
+ std::streamsize done(fdwrite(this->outbuf_, size));
+ // The code below assumes that it is success if the stream made
+ // some progress. Depending on the needs it may be more
+ // reasonable to consider it a success only if it managed to
+ // write the entire buffer and, e.g., loop a couple of times
+ // to try achieving this success.
+ if (0 < done) {
+ std::copy(this->pbase() + done, this->pptr(), this->pbase());
+ this->setp(this->pbase(), this->epptr());
+ this->pbump((int)(size - done));
+ }
+ }
+ return this->pptr() != this->epptr()? 0: -1;
+}
+
+int fdbuf::underflow()
+{
+ if (this->gptr() == this->egptr()) {
+ std::streamsize pback(std::min(this->gptr() - this->eback(),
+ std::ptrdiff_t(16 - sizeof(int))));
+ std::copy(this->egptr() - pback, this->egptr(), this->eback());
+ int done((int)fdread(this->eback() + pback, bufsize));
+ this->setg(this->eback(),
+ this->eback() + pback,
+ this->eback() + pback + std::max(0, done));
+ }
+ return this->gptr() == this->egptr()
+ ? traits_type::eof()
+ : traits_type::to_int_type(*this->gptr());
+}
+
+#ifdef FEATURE_PAL
+struct IORedirectServerHandles
+{
+ int m_sockFd;
+ int m_realStdInFd;
+ int m_realStdOutFd;
+ int m_realStdErrFd;
+ int m_appStdIn;
+
+ IORedirectServerHandles() :
+ m_sockFd(-1),
+ m_realStdInFd(STDIN_FILENO),
+ m_realStdOutFd(STDOUT_FILENO),
+ m_realStdErrFd(STDERR_FILENO),
+ m_appStdIn(-1)
+ {
+ }
+
+ ~IORedirectServerHandles()
+ {
+ if (m_sockFd != -1)
+ ::close(m_sockFd);
+ }
+
+ bool IsConnected() const { return m_sockFd != -1; }
+
+ void RedirectOutput(
+ std::function<void(std::string)> onStdOut,
+ std::function<void(std::string)> onStdErr);
+
+ int WaitForConnection(uint16_t port);
+};
+#else
+struct IORedirectServerHandles
+{
+ SOCKET m_sockFd;
+ HANDLE m_realStdInFd;
+ HANDLE m_realStdOutFd;
+ HANDLE m_realStdErrFd;
+ HANDLE m_appStdIn;
+
+ IORedirectServerHandles() :
+ m_sockFd(INVALID_SOCKET),
+ m_realStdInFd(GetStdHandle(STD_INPUT_HANDLE)),
+ m_realStdOutFd(GetStdHandle(STD_OUTPUT_HANDLE)),
+ m_realStdErrFd(GetStdHandle(STD_ERROR_HANDLE)),
+ m_appStdIn(INVALID_HANDLE_VALUE)
+ {
+ }
+
+ ~IORedirectServerHandles()
+ {
+ if (m_sockFd != INVALID_SOCKET)
+ {
+ ::closesocket(m_sockFd);
+ }
+ WSACleanup();
+ }
+
+ bool IsConnected() const { return m_sockFd != INVALID_SOCKET; }
+
+ void RedirectOutput(
+ std::function<void(std::string)> onStdOut,
+ std::function<void(std::string)> onStdErr);
+
+ SOCKET WaitForConnection(uint16_t port);
+};
+#endif
+
+#ifndef FEATURE_PAL
+
+void AddFilesFromDirectoryToTpaList(const std::string &directory, std::string& tpaList)
+{
+ const char * const tpaExtensions[] = {
+ "*.ni.dll", // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir
+ "*.dll",
+ "*.ni.exe",
+ "*.exe",
+ };
+
+ std::set<std::string> addedAssemblies;
+
+ // Walk the directory for each extension separately so that we first get files with .ni.dll extension,
+ // then files with .dll extension, etc.
+ for (int extIndex = 0; extIndex < sizeof(tpaExtensions) / sizeof(tpaExtensions[0]); extIndex++)
+ {
+ const char* ext = tpaExtensions[extIndex];
+ size_t extLength = strlen(ext);
+
+ std::string assemblyPath(directory);
+ assemblyPath.append("\\");
+ assemblyPath.append(tpaExtensions[extIndex]);
+
+ WIN32_FIND_DATAA data;
+ HANDLE findHandle = FindFirstFileA(assemblyPath.c_str(), &data);
+
+ if (findHandle != INVALID_HANDLE_VALUE)
+ {
+ do
+ {
+ if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ {
+
+ std::string filename(data.cFileName);
+ size_t extPos = filename.length() - extLength;
+ std::string filenameWithoutExt(filename.substr(0, extPos));
+
+ // Make sure if we have an assembly with multiple extensions present,
+ // we insert only one version of it.
+ if (addedAssemblies.find(filenameWithoutExt) == addedAssemblies.end())
+ {
+ addedAssemblies.insert(filenameWithoutExt);
+
+ tpaList.append(directory);
+ tpaList.append("\\");
+ tpaList.append(filename);
+ tpaList.append(";");
+ }
+ }
+ }
+ while (0 != FindNextFileA(findHandle, &data));
+
+ FindClose(findHandle);
+ }
+ }
+}
+
+std::string GetExeAbsPath()
+{
+ char hostPath[MAX_LONGPATH + 1];
+ if (::GetModuleFileNameA(NULL, hostPath, MAX_LONGPATH) == 0)
+ {
+ return false;
+ }
+
+ return std::string(hostPath);
+}
+
+bool SetWorkDir(const std::string &path)
+{
+ return SetCurrentDirectoryA(path.c_str());
+}
+
+void USleep(uint32_t duration)
+{
+ HANDLE timer;
+ LARGE_INTEGER ft;
+
+ ft.QuadPart = -(10*(int32_t)duration); // Convert to 100 nanosecond interval, negative value indicates relative time
+
+ timer = CreateWaitableTimer(NULL, TRUE, NULL);
+ SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
+ WaitForSingleObject(timer, INFINITE);
+ CloseHandle(timer);
+}
+
+void *DLOpen(const std::string &path)
+{
+ return LoadLibraryA(path.c_str());
+}
+
+void *DLSym(void *handle, const std::string &name)
+{
+ return GetProcAddress((HMODULE)handle, name.c_str());
+}
+
+void UnsetCoreCLREnv()
+{
+ _putenv("CORECLR_ENABLE_PROFILING=");
+}
+
+SOCKET IORedirectServerHandles::WaitForConnection(uint16_t port)
+{
+ WSADATA wsa;
+ SOCKET newsockfd;
+ int clilen;
+ struct sockaddr_in serv_addr, cli_addr;
+
+ if (port == 0)
+ return INVALID_SOCKET;
+
+ if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
+ {
+ return INVALID_SOCKET;
+ }
+
+ m_sockFd = ::socket(AF_INET, SOCK_STREAM, 0);
+ if (m_sockFd == INVALID_SOCKET)
+ {
+ WSACleanup();
+ return INVALID_SOCKET;
+ }
+
+ BOOL enable = 1;
+ if (setsockopt(m_sockFd, SOL_SOCKET, SO_REUSEADDR, (const char *)&enable, sizeof(BOOL)) == SOCKET_ERROR)
+ {
+ ::closesocket(m_sockFd);
+ WSACleanup();
+ m_sockFd = INVALID_SOCKET;
+ return INVALID_SOCKET;
+ }
+ memset(&serv_addr, 0, sizeof(serv_addr));
+
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_addr.s_addr = INADDR_ANY;
+ serv_addr.sin_port = htons(port);
+
+ if (::bind(m_sockFd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
+ {
+ ::closesocket(m_sockFd);
+ WSACleanup();
+ m_sockFd = INVALID_SOCKET;
+ return INVALID_SOCKET;
+ }
+
+ ::listen(m_sockFd, 5);
+
+ CloseHandle(m_realStdInFd);
+ CloseHandle(m_realStdOutFd);
+ CloseHandle(m_realStdErrFd);
+ m_realStdInFd = INVALID_HANDLE_VALUE;
+ m_realStdOutFd = INVALID_HANDLE_VALUE;
+ m_realStdErrFd = INVALID_HANDLE_VALUE;
+
+ clilen = sizeof(cli_addr);
+ newsockfd = ::accept(m_sockFd, (struct sockaddr *) &cli_addr, &clilen);
+ if (newsockfd == INVALID_SOCKET)
+ {
+ ::closesocket(m_sockFd);
+ WSACleanup();
+ m_sockFd = INVALID_SOCKET;
+ return INVALID_SOCKET;
+ }
+
+ return newsockfd;
+}
+
+#define BUFSIZE 4096
+
+static std::function<void()> GetFdReadFunction(HANDLE h, std::function<void(std::string)> cb)
+{
+ return [h, cb]() {
+ char buffer[BUFSIZE];
+
+ while (true)
+ {
+ DWORD dwRead = 0;
+ BOOL bSuccess = ReadFile(h, buffer, BUFSIZE, &dwRead, NULL);
+
+ if (!bSuccess || dwRead == 0)
+ {
+ break;
+ }
+ cb(std::string(buffer, dwRead));
+ }
+ };
+}
+
+void IORedirectServerHandles::RedirectOutput(
+ std::function<void(std::string)> onStdOut,
+ std::function<void(std::string)> onStdErr)
+{
+ SECURITY_ATTRIBUTES saAttr;
+
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE;
+ saAttr.lpSecurityDescriptor = NULL;
+
+ HANDLE newStdOutRd;
+ HANDLE newStdOutWr;
+
+ if (!CreatePipe(&newStdOutRd, &newStdOutWr, &saAttr, 0))
+ return;
+ if (!SetHandleInformation(newStdOutRd, HANDLE_FLAG_INHERIT, 0))
+ return;
+ if (!SetStdHandle(STD_OUTPUT_HANDLE, newStdOutWr))
+ return;
+
+ HANDLE newStdErrRd;
+ HANDLE newStdErrWr;
+
+ if (!CreatePipe(&newStdErrRd, &newStdErrWr, &saAttr, 0))
+ return;
+ if (!SetHandleInformation(newStdErrRd, HANDLE_FLAG_INHERIT, 0))
+ return;
+ if (!SetStdHandle(STD_ERROR_HANDLE, newStdErrWr))
+ return;
+
+ HANDLE newStdInRd;
+ HANDLE newStdInWr;
+
+ if (!CreatePipe(&newStdInRd, &newStdInWr, &saAttr, 0))
+ return;
+ if (!SetHandleInformation(newStdInWr, HANDLE_FLAG_INHERIT, 0))
+ return;
+ if (!SetStdHandle(STD_INPUT_HANDLE, newStdInRd))
+ return;
+
+ m_appStdIn = newStdInWr;
+
+ std::thread(GetFdReadFunction(newStdOutRd, onStdOut)).detach();
+ std::thread(GetFdReadFunction(newStdErrRd, onStdErr)).detach();
+}
+
+#else
+
void AddFilesFromDirectoryToTpaList(const std::string &directory, std::string &tpaList)
{
const char * const tpaExtensions[] = {
unsetenv("CORECLR_ENABLE_PROFILING");
}
-// From https://stackoverflow.com/questions/13541313/handle-socket-descriptors-like-file-descriptor-fstream-c-linux
-class fdbuf : public std::streambuf
-{
-private:
- enum { bufsize = 1024 };
- char outbuf_[bufsize];
- char inbuf_[bufsize + 16 - sizeof(int)];
- int fd_;
-public:
- typedef std::streambuf::traits_type traits_type;
-
- fdbuf(int fd);
- virtual ~fdbuf();
- void open(int fd);
- void close();
-
-protected:
- int overflow(int c) override;
- int underflow() override;
- int sync() override;
-
-private:
- int fdsync();
-};
-
-fdbuf::fdbuf(int fd)
- : fd_(-1) {
- this->open(fd);
-}
-
-fdbuf::~fdbuf() {
- if (!(this->fd_ < 0)) {
- this->fdsync();
- ::close(this->fd_);
- }
-}
-
-void fdbuf::open(int fd) {
- this->close();
- this->fd_ = fd;
- this->setg(this->inbuf_, this->inbuf_, this->inbuf_);
- this->setp(this->outbuf_, this->outbuf_ + bufsize - 1);
-}
-
-void fdbuf::close() {
- if (!(this->fd_ < 0)) {
- this->sync();
- ::close(this->fd_);
- }
-}
-
-int fdbuf::overflow(int c) {
- if (!traits_type::eq_int_type(c, traits_type::eof())) {
- *this->pptr() = traits_type::to_char_type(c);
- this->pbump(1);
- }
- return this->sync() == -1
- ? traits_type::eof()
- : traits_type::not_eof(c);
-}
-
-int fdbuf::sync() {
- return fdsync();
-}
-
-int fdbuf::fdsync() {
- if (this->pbase() != this->pptr()) {
- std::streamsize size(this->pptr() - this->pbase());
- std::streamsize done(::write(this->fd_, this->outbuf_, size));
- // The code below assumes that it is success if the stream made
- // some progress. Depending on the needs it may be more
- // reasonable to consider it a success only if it managed to
- // write the entire buffer and, e.g., loop a couple of times
- // to try achieving this success.
- if (0 < done) {
- std::copy(this->pbase() + done, this->pptr(), this->pbase());
- this->setp(this->pbase(), this->epptr());
- this->pbump(size - done);
- }
- }
- return this->pptr() != this->epptr()? 0: -1;
-}
-
-int fdbuf::underflow()
-{
- if (this->gptr() == this->egptr()) {
- std::streamsize pback(std::min(this->gptr() - this->eback(),
- std::ptrdiff_t(16 - sizeof(int))));
- std::copy(this->egptr() - pback, this->egptr(), this->eback());
- int done(::read(this->fd_, this->eback() + pback, bufsize));
- this->setg(this->eback(),
- this->eback() + pback,
- this->eback() + pback + std::max(0, done));
- }
- return this->gptr() == this->egptr()
- ? traits_type::eof()
- : traits_type::to_int_type(*this->gptr());
-}
-
-IORedirectServer::IORedirectServer(
- uint16_t port,
- std::function<void(std::string)> onStdOut,
- std::function<void(std::string)> onStdErr) :
- m_in(nullptr),
- m_out(nullptr),
- m_sockfd(-1),
- m_realStdInFd(STDIN_FILENO),
- m_realStdOutFd(STDOUT_FILENO),
- m_realStdErrFd(STDERR_FILENO),
- m_appStdIn(-1)
-{
- RedirectOutput(onStdOut, onStdErr);
- int fd = WaitForConnection(port);
-
- if (fd != -1)
- {
- m_in = new fdbuf(fd);
- m_out = new fdbuf(fd);
- }
- else
- {
- m_in = new fdbuf(m_realStdInFd);
- m_out = new fdbuf(m_realStdOutFd);
- }
- m_err = new fdbuf(m_realStdErrFd);
-
- m_prevIn = std::cin.rdbuf();
- m_prevOut = std::cout.rdbuf();
- m_prevErr = std::cerr.rdbuf();
-
- std::cin.rdbuf(m_in);
- std::cout.rdbuf(m_out);
- std::cerr.rdbuf(m_err);
-}
-
-int IORedirectServer::WaitForConnection(uint16_t port)
+int IORedirectServerHandles::WaitForConnection(uint16_t port)
{
int newsockfd;
socklen_t clilen;
struct sockaddr_in serv_addr, cli_addr;
- int n;
if (port == 0)
return -1;
- m_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
- if (m_sockfd < 0)
+ m_sockFd = ::socket(AF_INET, SOCK_STREAM, 0);
+ if (m_sockFd < 0)
return -1;
int enable = 1;
- if (setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
+ if (setsockopt(m_sockFd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
{
- ::close(m_sockfd);
- m_sockfd = -1;
+ ::close(m_sockFd);
+ m_sockFd = -1;
return -1;
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(port);
- if (::bind(m_sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
+ if (::bind(m_sockFd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
- ::close(m_sockfd);
- m_sockfd = -1;
+ ::close(m_sockFd);
+ m_sockFd = -1;
return -1;
}
- ::listen(m_sockfd, 5);
+ ::listen(m_sockFd, 5);
// On Tizen, launch_app won't terminate until stdin, stdout and stderr are closed.
// But Visual Studio initiates the connection only after launch_app termination,
m_realStdErrFd = -1;
clilen = sizeof(cli_addr);
- newsockfd = ::accept(m_sockfd, (struct sockaddr *) &cli_addr, &clilen);
+ newsockfd = ::accept(m_sockFd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
{
- ::close(m_sockfd);
- m_sockfd = -1;
+ ::close(m_sockFd);
+ m_sockFd = -1;
return -1;
}
return newsockfd;
}
-IORedirectServer::~IORedirectServer()
-{
- std::cin.rdbuf(m_prevIn);
- std::cout.rdbuf(m_prevOut);
- std::cout.rdbuf(m_prevErr);
- delete m_in;
- delete m_out;
- delete m_err;
- ::close(m_sockfd);
-}
-
static std::function<void()> GetFdReadFunction(int fd, std::function<void(std::string)> cb)
{
return [fd, cb]() {
};
}
-void IORedirectServer::RedirectOutput(std::function<void(std::string)> onStdOut,
- std::function<void(std::string)> onStdErr)
+void IORedirectServerHandles::RedirectOutput(
+ std::function<void(std::string)> onStdOut,
+ std::function<void(std::string)> onStdErr)
{
// TODO: fcntl(fd, F_SETFD, FD_CLOEXEC);
m_realStdInFd = dup(STDIN_FILENO);
std::thread(GetFdReadFunction(outPipe[0], onStdOut)).detach();
std::thread(GetFdReadFunction(errPipe[0], onStdErr)).detach();
}
+
+#endif
+
+IORedirectServer::operator bool() const
+{
+ return m_handles->IsConnected();
+}
+
+IORedirectServer::IORedirectServer(
+ uint16_t port,
+ std::function<void(std::string)> onStdOut,
+ std::function<void(std::string)> onStdErr) :
+ m_in(nullptr),
+ m_out(nullptr),
+ m_handles(new IORedirectServerHandles())
+{
+ m_handles->RedirectOutput(onStdOut, onStdErr);
+
+#ifdef WIN32
+ SOCKET s = m_handles->WaitForConnection(port);
+ if (s != INVALID_SOCKET)
+ {
+ m_in = new fdbuf((HANDLE)s);
+ m_out = new fdbuf((HANDLE)s);
+ }
+#else
+ int fd = m_handles->WaitForConnection(port);
+ if (fd != -1)
+ {
+ m_in = new fdbuf(fd);
+ m_out = new fdbuf(fd);
+ }
+#endif
+ else
+ {
+ m_in = new fdbuf(m_handles->m_realStdInFd);
+ m_out = new fdbuf(m_handles->m_realStdOutFd);
+ }
+ m_err = new fdbuf(m_handles->m_realStdErrFd);
+
+ m_prevIn = std::cin.rdbuf();
+ m_prevOut = std::cout.rdbuf();
+ m_prevErr = std::cerr.rdbuf();
+
+ std::cin.rdbuf(m_in);
+ std::cout.rdbuf(m_out);
+ std::cerr.rdbuf(m_err);
+}
+
+IORedirectServer::~IORedirectServer()
+{
+ std::cin.rdbuf(m_prevIn);
+ std::cout.rdbuf(m_prevOut);
+ std::cout.rdbuf(m_prevErr);
+ delete m_in;
+ delete m_out;
+ delete m_err;
+ delete m_handles;
+}