1 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2    Copyright (c) 2019-2021 The plumed team
3    (see the PEOPLE file at the root of the distribution for a list of names)
4 
5    See http://www.plumed.org for more information.
6 
7    This file is part of plumed, version 2.
8 
9    plumed is free software: you can redistribute it and/or modify
10    it under the terms of the GNU Lesser General Public License as published by
11    the Free Software Foundation, either version 3 of the License, or
12    (at your option) any later version.
13 
14    plumed is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU Lesser General Public License for more details.
18 
19    You should have received a copy of the GNU Lesser General Public License
20    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
21 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
22 #include "Subprocess.h"
23 #include "Exception.h"
24 #include "Tools.h"
25 #ifdef __PLUMED_HAS_SUBPROCESS
26 #include <unistd.h>
27 #include <csignal>
28 #endif
29 
30 using namespace std;
31 namespace PLMD {
32 
33 /// Retrieve PLUMED_ENABLE_SIGNALS.
34 /// Inline static so that it can store a static variable (for quicker access)
35 /// without adding a unique global symbol to a library including this header file.
SubprocessPidGetenvSignals()36 inline static bool SubprocessPidGetenvSignals() noexcept {
37   static const bool res=std::getenv("PLUMED_ENABLE_SIGNALS");
38   return res;
39 }
40 
41 /// Small utility class, used to avoid inclusion of unistd.h> in a header file.
42 class SubprocessPid {
43 #ifdef __PLUMED_HAS_SUBPROCESS
44 public:
45   pid_t pid;
SubprocessPid(pid_t pid)46   explicit SubprocessPid(pid_t pid):
47     pid(pid)
48   {
49     plumed_assert(pid!=0 && pid!=-1);
50   }
stop()51   void stop() noexcept {
52     // Signals give problems with MPI on Travis.
53     // I disable them for now.
54     if(SubprocessPidGetenvSignals()) if(pid!=0 && pid!=-1) kill(pid,SIGSTOP);
55   }
cont()56   void cont() noexcept {
57     // Signals give problems with MPI on Travis.
58     // I disable them for now.
59     if(SubprocessPidGetenvSignals()) if(pid!=0 && pid!=-1) kill(pid,SIGCONT);
60   }
~SubprocessPid()61   ~SubprocessPid() {
62     // this is apparently working also with MPI on Travis.
63     if(pid!=0 && pid!=-1) kill(pid,SIGINT);
64   }
65 #endif
66 };
67 
Subprocess(const std::string & cmd)68 Subprocess::Subprocess(const std::string & cmd) {
69 #ifdef __PLUMED_HAS_SUBPROCESS
70   char* arr [] = {
71     // const_cast are necessary here due to the declaration of execv
72     const_cast<char*>("/bin/sh"),
73     const_cast<char*>("-c"),
74     const_cast<char*>(cmd.c_str()),
75     nullptr
76   };
77   int cp[2];
78   int pc[2];
79   if(pipe(pc)<0) plumed_error()<<"error creating parent to child pipe";
80   if(pipe(cp)<0) plumed_error()<<"error creating child to parent pipe";
81   pid_t pid=fork();
82   switch(pid) {
83   case -1:
84     plumed_error()<<"error forking";
85     break;
86 // CHILD:
87   case 0:
88   {
89     if(close(1)<0) plumed_error()<<"error closing file";
90     if(dup(cp[1])<0) plumed_error()<<"error duplicating file";
91     if(close(0)<0) plumed_error()<<"error closing file";
92     if(dup(pc[0])<0) plumed_error()<<"error duplicating file";
93     if(close(pc[1])<0) plumed_error()<<"error closing file";
94     if(close(cp[0])<0) plumed_error()<<"error closing file";
95     execv(arr[0],arr);
96     plumed_error()<<"error in script file";
97   }
98 // PARENT::
99   default:
100     this->pid.reset(new SubprocessPid(pid));
101     if(close(pc[0])<0) plumed_error()<<"error closing file";
102     if(close(cp[1])<0) plumed_error()<<"error closing file";
103     fpc=pc[1];
104     fcp=cp[0];
105     fppc=fdopen(fpc,"w");
106     parent_to_child.link(fppc);
107     fpcp=fdopen(fcp,"r");
108     child_to_parent.link(fpcp);
109   }
110 #else
111   plumed_error()<<"Subprocess not supported";
112 #endif
113 }
114 
~Subprocess()115 Subprocess::~Subprocess() {
116 #ifdef __PLUMED_HAS_SUBPROCESS
117 // fpc should be closed to terminate the child executable
118   fclose(fppc);
119   close(fpc);
120 // fcp should not be closed because it could make the child executable fail
121 /// TODO: check if this is necessary and make this class exception safe!
122 #endif
123 }
124 
available()125 bool Subprocess::available() noexcept {
126 #ifdef __PLUMED_HAS_SUBPROCESS
127   return true;
128 #else
129   return false;
130 #endif
131 }
132 
stop()133 void Subprocess::stop() noexcept {
134 #ifdef __PLUMED_HAS_SUBPROCESS
135   pid->stop();
136 #endif
137 }
138 
cont()139 void Subprocess::cont() noexcept {
140 #ifdef __PLUMED_HAS_SUBPROCESS
141   pid->cont();
142 #endif
143 }
144 
flush()145 void Subprocess::flush() {
146   parent_to_child.flush();
147 }
148 
getline(std::string & line)149 Subprocess & Subprocess::getline(std::string & line) {
150   child_to_parent.getline(line);
151   if(!child_to_parent) plumed_error() <<"error reading subprocess";
152   return (*this);
153 }
154 
Handler(Subprocess * sp)155 Subprocess::Handler::Handler(Subprocess *sp) noexcept:
156   sp(sp)
157 {
158   sp->cont();
159 }
160 
~Handler()161 Subprocess::Handler::~Handler() {
162   if(sp) sp->stop();
163 }
164 
Handler(Handler && handler)165 Subprocess::Handler::Handler(Handler && handler) noexcept :
166   sp(handler.sp)
167 {
168   handler.sp=nullptr;
169 }
170 
operator =(Handler && handler)171 Subprocess::Handler & Subprocess::Handler::operator=(Handler && handler) noexcept {
172   if(this!=&handler) {
173     if(sp) sp->stop();
174     sp=handler.sp;
175     handler.sp=nullptr;
176   }
177   return *this;
178 }
179 
180 
181 }
182