// filepopen.cpp // Revision 6-feb-2005 #include "file.h" #include "blassic.h" #include "error.h" #include "showerror.h" #include "sysvar.h" #include "util.h" using util::to_string; #include "trace.h" #include using std::cerr; using std::endl; #ifndef BLASSIC_USE_WINDOWS #include #include #include #include // Suggested in autoconf manual. #ifdef HAVE_SYS_WAIT_H #include #endif #ifndef WEXITSTATUS #define WEXITSTATUS(star_val) ((unsigned) (stat_val) >> 8) #endif #ifndef WIFEXITED #define WIFEXITED(stat_val) (((stat_val) & 255) == 0) #endif #include #include #else #include #undef min #undef max #endif // para strerror (errno) #include #include #include #define ASSERT assert namespace blassic { namespace file { //*********************************************** // BlFilePopen //*********************************************** class BlFilePopen : public BlFile { protected: virtual char getcharfrombuffer ()= 0; virtual void outstring (const std::string & str)= 0; virtual void outchar (char c)= 0; bool forread; bool forwrite; bool witherr; public: BlFilePopen (OpenMode nmode); bool isfile () const { return true; } virtual void closein ()= 0; virtual void closeout ()= 0; virtual bool eof ()= 0; void flush (); virtual void endline ()= 0; virtual std::string read (size_t n)= 0; virtual void getline (std::string & str, bool endline= true)= 0; virtual bool poll ()= 0; }; BlFilePopen::BlFilePopen (OpenMode nmode) : BlFile (nmode) { if (nmode & Input) forread= true; if (nmode & Output) forwrite= true; if (nmode & WithErr) witherr= true; const OpenMode checkmode= OpenMode (Input | Output | WithErr); if ( (nmode | checkmode) != checkmode) throw ErrFileMode; } void BlFilePopen::flush () { } #ifdef BLASSIC_USE_WINDOWS //*********************************************** // Auxiliary functions and classes for windows //*********************************************** namespace { class ProtectHandle { public: ProtectHandle () : handle (INVALID_HANDLE_VALUE) { } ProtectHandle (HANDLE handle) : handle (handle) { } ~ProtectHandle () { close (); } void reset (HANDLE newhandle) { close (); handle= newhandle; } void close () { if (handle != INVALID_HANDLE_VALUE) { CloseHandle (handle); handle= INVALID_HANDLE_VALUE; } } void release () { handle= INVALID_HANDLE_VALUE; } private: HANDLE handle; }; std::string makecommand (const std::string & str) { const char * comspec= getenv ("COMSPEC"); if (comspec == NULL) comspec= "C:\\COMMAND.COM"; std::string command= comspec; command+= " /C "; command+= str; return command; } void create_pipe (HANDLE & hread, HANDLE & hwrite) { if (CreatePipe (& hread, & hwrite, NULL, 0) == 0) { showlasterror ("CreatePipe failed"); throw ErrFunctionCall; } } HANDLE duplicate_inherit (HANDLE handle, HANDLE current) { HANDLE newhandle; if (DuplicateHandle (current, handle, current, & newhandle, 0, TRUE, // Inherited DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS) == 0) { showlasterror ("DuplicateHandle hread failed"); throw ErrFunctionCall; } return newhandle; } HANDLE createnull () { SECURITY_ATTRIBUTES sec; sec.nLength= sizeof sec; sec.lpSecurityDescriptor= NULL; sec.bInheritHandle= TRUE; const HANDLE hnull= CreateFile ("NUL", GENERIC_READ | GENERIC_WRITE, 0, & sec, // Inheritable. OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hnull == INVALID_HANDLE_VALUE) { showlasterror ("Error creating /dev/null handle"); throw ErrOperatingSystem; } return hnull; } void launchchild (const std::string & str, bool forread, bool forwrite, bool witherr, HANDLE & hreadpipe, HANDLE & hwritepipe, HANDLE & childprocess) { TRACEFUNC (tr, "launchchild"); const std::string command= makecommand (str); HANDLE current= GetCurrentProcess (); const HANDLE hnull= createnull (); ProtectHandle prnull (hnull); STARTUPINFO start; start.cb= sizeof start; GetStartupInfo (& start); start.dwFlags= STARTF_USESTDHANDLES; start.hStdInput= hnull; start.hStdOutput= hnull; start.hStdError= hnull; DWORD creationflags= 0; HANDLE hwritechild; ProtectHandle prreadpipe; ProtectHandle prwritechild; if (forread) { TRMESSAGE (tr, "Creating pipe input"); HANDLE hwrite; create_pipe (hreadpipe, hwrite); prreadpipe.reset (hreadpipe); hwritechild= duplicate_inherit (hwrite, current); prwritechild.reset (hwritechild); start.hStdOutput= hwritechild; if (witherr) start.hStdError= hwritechild; } HANDLE hreadchild; ProtectHandle prwritepipe; ProtectHandle prreadchild; if (forwrite) { TRMESSAGE (tr, "Creating pipe output"); // In Windows 95 or 98 detach the process from the // actual console, without that the parent process // gets blocked. // Now seems inneccessary, and conflicts with // bidirectional mode. #if 0 { OSVERSIONINFO osv; osv.dwOSVersionInfoSize= sizeof (OSVERSIONINFO); GetVersionEx (& osv); if (osv.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) creationflags|= DETACHED_PROCESS; } #endif HANDLE hread; create_pipe (hread, hwritepipe); prwritepipe.reset (hwritepipe); hreadchild= duplicate_inherit (hread, current); prreadchild.reset (hreadchild); start.hStdInput= hreadchild; } TRMESSAGE (tr, "Creating pipe process"); PROCESS_INFORMATION procinfo; if (CreateProcess ( NULL, (char *) command.c_str (), NULL, NULL, TRUE, // Inherit handles creationflags, NULL, NULL, & start, & procinfo) == 0) { showlasterror ( ("CreateProcess " + command + " failed in POPEN").c_str () ); throw ErrFunctionCall; } // No need to close here the child handles, ProtectHandle // takes care of him. // Release protection of the parent handles. prreadpipe.release (); prwritepipe.release (); childprocess= procinfo.hProcess; CloseHandle (procinfo.hThread); } class CriticalSection { public: CriticalSection () { InitializeCriticalSection (& object); } ~CriticalSection () { DeleteCriticalSection (& object); } CriticalSection (const CriticalSection &); // Forbidden void operator= (const CriticalSection &); // Forbidden void enter () { EnterCriticalSection (& object); } void leave () { LeaveCriticalSection (& object); } private: CRITICAL_SECTION object; }; class CriticalLock { public: CriticalLock (CriticalSection & crit) : crit (crit) { crit.enter (); } ~CriticalLock () { crit.leave (); } private: CriticalSection & crit; }; class Event { public: Event () { hevent= CreateEvent ( NULL, // Default security FALSE, // Automatic FALSE, // Initially not signaled NULL // Without name ); if (hevent == NULL) { showlasterror (); throw ErrOperatingSystem; } } ~Event () { CloseHandle (hevent); } void wait () { WaitForSingleObject (hevent, INFINITE); } void set () { SetEvent (hevent); } void reset () { ResetEvent (hevent); } private: HANDLE hevent; }; } // namespace //*********************************************** // BlFilePopenWindows //*********************************************** class BlFilePopenWindows : public BlFilePopen { public: BlFilePopenWindows (const std::string & str, OpenMode mode); ~BlFilePopenWindows (); void closein (); void closeout (); bool eof (); void endline (); std::string read (size_t n); void getline (std::string & str, bool endline= true); bool poll (); private: static const size_t bufsize= 4096; char buffer [bufsize]; size_t bufpos, bufread; void readbuffer (); char getcharfrombuffer (); void read_in_thread (); void outstring (const std::string & str); void outchar (char c); CriticalSection critical; Event canread; bool nowreading; Event hasread; HANDLE hreadpipe, hwritepipe; HANDLE hprocess; HANDLE hreadthread; bool finishthread; bool error_reading; bool eof_found; friend DWORD WINAPI thread_popen_read (LPVOID param); friend class ProtectThread; }; class ProtectThread { public: ProtectThread (BlFilePopenWindows & popen) : popen (popen), released (false) { } ~ProtectThread () { if (! released && popen.hreadthread != NULL) { popen.finishthread= true; ResumeThread (popen.hreadthread); CloseHandle (popen.hreadthread); } } void release () { released= true; } private: BlFilePopenWindows & popen; bool released; }; DWORD WINAPI thread_popen_read (LPVOID param) { BlFilePopenWindows & popen= * reinterpret_cast (param); if (popen.finishthread) return 0; try { popen.read_in_thread (); } catch (...) { popen.error_reading= true; popen.nowreading= false; popen.hasread.set (); } return 0; } BlFilePopenWindows::BlFilePopenWindows (const std::string & str, OpenMode nmode) : BlFilePopen (nmode) { TRACEFUNC (tr, "BlFilePopenWindows::BlFilePopenWindows"); hprocess= INVALID_HANDLE_VALUE; hreadpipe= INVALID_HANDLE_VALUE; hwritepipe= INVALID_HANDLE_VALUE; hreadthread= NULL; nowreading= false; finishthread= false; error_reading= false; eof_found= false; if (forread) { DWORD idthread; hreadthread= CreateThread (NULL, 1024, thread_popen_read, this, CREATE_SUSPENDED, & idthread); if (hreadthread == NULL) { showlasterror (); throw ErrOperatingSystem; } } ProtectThread protect (* this); launchchild (str, forread, forwrite, witherr, hreadpipe, hwritepipe, hprocess); if (forread) { ResumeThread (hreadthread); nowreading= true; canread.set (); } protect.release (); } BlFilePopenWindows::~BlFilePopenWindows () { TRACEFUNC (tr, "BlFilePopenWindows::~BlFilePopenWindows"); // This is to workaround errors in C++ Builder. if (hprocess == INVALID_HANDLE_VALUE) return; if (hreadthread != NULL) { finishthread= true; CloseHandle (hreadthread); hreadthread= NULL; nowreading= true; hasread.reset (); canread.set (); } // Close the pipe before wait for termination, // the child process can be waiting for // input. //CloseHandle (hpipe); if (hreadpipe != INVALID_HANDLE_VALUE) CloseHandle (hreadpipe); if (hwritepipe != INVALID_HANDLE_VALUE) CloseHandle (hwritepipe); WaitForSingleObject (hprocess, INFINITE); DWORD exitcode; BOOL r= GetExitCodeProcess (hprocess, & exitcode); if (r == 0) { showlasterror (); } CloseHandle (hprocess); if (r == 0) { throw ErrOperatingSystem; } TRMESSAGE (tr, "Exit code: " + to_string (exitcode) ); sysvar::set (sysvar::ShellResult, static_cast (exitcode & 0xFF) ); } char BlFilePopenWindows::getcharfrombuffer () { { CriticalLock lock (critical); if (bufpos < bufread) return buffer [bufpos++]; } if (error_reading) throw ErrOperatingSystem; if (eof_found) return 0; readbuffer (); hasread.wait (); if (error_reading) throw ErrOperatingSystem; if (eof_found) return 0; return buffer [bufpos++]; } void BlFilePopenWindows::closein () { TRACEFUNC (tr, "BlFilePopenWindows::closein"); if (! getmode () & Input) throw ErrFileMode; finishthread= true; CloseHandle (hreadpipe); hreadpipe= INVALID_HANDLE_VALUE; critical.enter (); nowreading= true; hasread.reset (); canread.set (); critical.leave (); CloseHandle (hreadthread); hreadthread= INVALID_HANDLE_VALUE; } void BlFilePopenWindows::closeout () { TRACEFUNC (tr, "BlFilePopenWindows::closeout"); if (! getmode () & Output) throw ErrFileMode; CloseHandle (hwritepipe); hwritepipe= INVALID_HANDLE_VALUE; } bool BlFilePopenWindows::eof () { TRACEFUNC (tr, "BlFilePopenWindows::eof"); if (! getmode () & Input) throw ErrFileMode; { CriticalLock lock (critical); if (bufpos < bufread) return false; } readbuffer (); hasread.wait (); if (error_reading) throw ErrOperatingSystem; if (!eof_found) { ASSERT (bufpos < bufread); } return eof_found; } void BlFilePopenWindows::endline () { outstring ("\r\n"); } std::string BlFilePopenWindows::read (size_t n) { if (! getmode () & Input) throw ErrFileMode; std::string str; for (size_t i= 0; i < n; ++i) { char c= getcharfrombuffer (); if (c == 0 && eof_found) break; str+= c; } return str; } void BlFilePopenWindows::getline (std::string & str, bool) { TRACEFUNC (tr, "BlFilePopenWindows::getline"); if (! getmode () & Input) throw ErrFileMode; str= std::string (); char c; while ( (c= getcharfrombuffer () ) != '\r' && c != '\n') { if (c == 0) { if (eof_found) { if (str.empty () ) throw ErrPastEof; else break; } } else str+= c; } if (c == '\r') getcharfrombuffer (); } void BlFilePopenWindows::readbuffer () { if (finishthread | error_reading | eof_found) return; CriticalLock lock (critical); if (nowreading) return; nowreading= true; hasread.reset (); canread.set (); } void BlFilePopenWindows::read_in_thread () { do { canread.wait (); if (finishthread) break; if (hreadpipe == INVALID_HANDLE_VALUE) break; DWORD bytesread= 0; if (ReadFile (hreadpipe, buffer, bufsize, & bytesread, NULL) == 0) { DWORD r= GetLastError (); if (r != ERROR_BROKEN_PIPE) { error_reading= true; } else { eof_found= true; } CriticalLock lock (critical); nowreading= false; } else { CriticalLock lock (critical); if (bytesread == 0) { // This is not supposed to happen // in an anonymous pipe. error_reading= true; } bufpos= 0; bufread= bytesread; nowreading= false; } hasread.set (); } while (! error_reading & ! eof_found & ! finishthread); } void BlFilePopenWindows::outstring (const std::string & str) { TRACEFUNC (tr, "BlFilePopenWindows::outstring"); if (! getmode () & Output) throw ErrFileMode; const char * to= str.data (); std::string::size_type l= str.size (); DWORD written; //WriteFile (hpipe, to, l, & written, NULL); if (WriteFile (hwritepipe, to, l, & written, NULL) == 0) { showlasterror (); throw ErrOperatingSystem; } } void BlFilePopenWindows::outchar (char c) { if (! getmode () & Output) throw ErrFileMode; DWORD written; //WriteFile (hpipe, & c, 1, & written, NULL); if (WriteFile (hwritepipe, & c, 1, & written, NULL) == 0) { showlasterror (); throw ErrOperatingSystem; } } bool BlFilePopenWindows::poll () { if (! getmode () & Input) throw ErrFileMode; { CriticalLock lock (critical); if (bufpos < bufread) return true; } readbuffer (); return false; } #else // No Windows //*********************************************** // Auxiliary functions and classes for unix //*********************************************** namespace { class ProtectHandle { public: ProtectHandle (int handle) : handle (handle) { } ~ProtectHandle () { close (); } void close () { if (handle != -1) { ::close (handle); handle= -1; } } void release () { handle= -1; } private: int handle; }; void exec_command (const std::string & str) { const char * strShell= getenv ("SHELL"); if (! strShell) strShell= "/bin/sh"; execlp (strShell, strShell, "-c", str.c_str (), (char *) 0); // If something fails: exit (127); } void createpipe (int & hread, int & hwrite) { int handlepair [2]; if (pipe (handlepair) != 0) { showlasterror ("Creating pipe"); throw ErrOperatingSystem; } hread= handlepair [0]; hwrite= handlepair [1]; } void duporfail (int oldfd, int newfd) { if (dup2 (oldfd, newfd) == -1) exit (127); } void stdtonull () { int newstd= open ("/dev/null", O_RDWR); if (newstd == -1) exit (127); duporfail (newstd, STDIN_FILENO); duporfail (newstd, STDOUT_FILENO); duporfail (newstd, STDERR_FILENO); close (newstd); } void launchchild (const std::string & str, bool forread, bool forwrite, bool witherr, int & readhandle, int & writehandle, pid_t & childpid) { TRACEFUNC (tr, "launchchild"); if (! forread && ! forwrite) { ASSERT (false); throw ErrBlassicInternal; } int writechild= -1; if (forread) createpipe (readhandle, writechild); ProtectHandle prrhandle (readhandle); ProtectHandle prwchild (writechild); int readchild= -1; if (forwrite) createpipe (readchild, writehandle); ProtectHandle prwhandle (writehandle); ProtectHandle prrchild (readchild); childpid= fork (); switch (childpid) { case pid_t (-1): // Fork has failed. showlasterror (); throw ErrOperatingSystem; case pid_t (0): // Child process. // No need to take care of handles in case of fail // here, the process will be terminated. stdtonull (); if (forread) { prrhandle.close (); duporfail (writechild, STDOUT_FILENO); if (witherr) duporfail (writechild, STDERR_FILENO); prwchild.close (); } if (forwrite) { prwhandle.close (); duporfail (readchild, STDIN_FILENO); prrchild.close (); } exec_command (str); break; // To avoid warnings. default: // Parent process. if (forread) { prwchild.close (); prrhandle.release (); } if (forwrite) { prrchild.close (); prwhandle.release (); } } } } // namespace //*********************************************** // BlFilePopenUnix //*********************************************** class BlFilePopenUnix : public BlFilePopen { public: BlFilePopenUnix (const std::string & str, OpenMode mode); ~BlFilePopenUnix (); void closein (); void closeout (); bool eof (); void endline (); std::string read (size_t n); void getline (std::string & str, bool endline= true); bool poll (); private: static const size_t bufsize= 4096; char buffer [bufsize]; size_t bufpos, bufread; void readbuffer (); char getcharfrombuffer (); size_t do_readbuffer (char * buffer, size_t bytes); void closeread (); void closewrite (); void outstring (const std::string & str); void outchar (char c); int readhandle; int writehandle; pid_t childpid; }; BlFilePopenUnix::BlFilePopenUnix (const std::string & str, OpenMode nmode) : BlFilePopen (nmode) { TRACEFUNC (tr, "BlFilePopenUnix::BlFilePopenUnix"); TRMESSAGE (tr, str); readhandle= -1; writehandle= -1; childpid= -1; launchchild (str, forread, forwrite, witherr, readhandle, writehandle, childpid); } BlFilePopenUnix::~BlFilePopenUnix () { TRACEFUNC (tr, "BlFilePopenUnix::~BlFilePopenUnix"); // First close the pipes, the child can be waiting for input. closeread (); closewrite (); int status; if (waitpid (childpid, & status, 0) == -1 || ! WIFEXITED (status) ) { showlasterror (); throw ErrOperatingSystem; } TRMESSAGE (tr, "Exit code: " + to_string (WEXITSTATUS (status) ) ); sysvar::set (sysvar::ShellResult, WEXITSTATUS (status) ); } char BlFilePopenUnix::getcharfrombuffer () { if (bufpos >= bufread) { readbuffer (); if (bufread == 0) return '\0'; } return buffer [bufpos++]; } void BlFilePopenUnix::closein () { TRACEFUNC (tr, "BlFilePopenUnix::closein"); if (! getmode () & Input) throw ErrFileMode; closeread (); } void BlFilePopenUnix::closeout () { TRACEFUNC (tr, "BlFilePopenUnix::closeout"); if (! getmode () & Output) throw ErrFileMode; closewrite (); } bool BlFilePopenUnix::eof () { TRACEFUNC (tr, "BlFilePopenUnix::eof"); if (! getmode () & Input) throw ErrFileMode; if (bufpos < bufread) return false; readbuffer (); return bufread == 0; } void BlFilePopenUnix::endline () { outchar ('\n'); } std::string BlFilePopenUnix::read (size_t n) { if (! getmode () & Input) throw ErrFileMode; std::string str; for (size_t i= 0; i < n; ++i) { char c= getcharfrombuffer (); if (c == '\0') { if (eof () ) break; else str+= c; } else str+= c; } return str; } void BlFilePopenUnix::getline (std::string & str, bool) { TRACEFUNC (tr, "BlFilePopenUnix::getline"); if (! getmode () & Input) throw ErrFileMode; str= std::string (); char c; while ( (c= getcharfrombuffer () ) != '\r' && c != '\n') { if (c == '\0') { if (eof () ) { if (str.empty () ) throw ErrPastEof; else break; } else break; } else str+= c; } } void BlFilePopenUnix::closeread () { if (readhandle != -1) { close (readhandle); readhandle= -1; } } void BlFilePopenUnix::closewrite () { if (writehandle != -1) { close (writehandle); writehandle= -1; } } void BlFilePopenUnix::readbuffer () { bufpos= 0; bufread= do_readbuffer (buffer, bufsize); } size_t BlFilePopenUnix::do_readbuffer (char * buffer, size_t bytes) { TRACEFUNC (tr, "BlFilePopenUnix::do_readbuffer"); size_t bytesread= ::read (readhandle, buffer, bytes); if (bytesread == size_t (-1) ) bytesread= 0; return bytesread; } void BlFilePopenUnix::outstring (const std::string & str) { TRACEFUNC (tr, "BlFilePopenUnix::outstring"); if (! getmode () & Output) throw ErrFileMode; const char * to= str.data (); std::string::size_type l= str.size (); if (write (writehandle, to, l) == -1) { showlasterror (); throw ErrOperatingSystem; } } void BlFilePopenUnix::outchar (char c) { if (! getmode () & Output) throw ErrFileMode; if (write (writehandle, & c, 1) == -1) { showlasterror (); throw ErrOperatingSystem; } } bool BlFilePopenUnix::poll () { TRACEFUNC (tr, "BlFilePopenUnix::poll"); if (! getmode () & Input) throw ErrFileMode; struct pollfd data [1]; data [0].fd= readhandle; data [0].events= POLLIN | POLLPRI; switch (::poll (data, 1, 0) ) { case 0: return false; case 1: return true; default: showlasterror (); throw ErrOperatingSystem; } } #endif // Windows or unix //*********************************************** // newBlFilePopen //*********************************************** BlFile * newBlFilePopen (const std::string & name, OpenMode mode) { #ifdef BLASSIC_USE_WINDOWS return new BlFilePopenWindows (name, mode); #else return new BlFilePopenUnix (name, mode); #endif } } // namespace file } // namespace blassic // End of filepopen.cpp