/* * ZeroTier One - Global Peer to Peer Ethernet * Copyright (C) 2011-2014 ZeroTier Networks LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * -- * * ZeroTier may be used and distributed under the terms of the GPLv3, which * are available at: http://www.gnu.org/licenses/gpl-3.0.html * * If you would like to embed ZeroTier into a commercial application or * redistribute it in a modified binary form, please contact ZeroTier Networks * LLC. Start here: http://www.zerotier.com/ */ #include "Constants.hpp" #ifndef __WINDOWS__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Constants.hpp" #include "Service.hpp" #include "RuntimeEnvironment.hpp" #include "Utils.hpp" #include "Logger.hpp" namespace ZeroTier { Service::Service(const RuntimeEnvironment *renv,const char *name,const char *path,void (*handler)(void *,Service &,const Dictionary &),void *arg) : _r(renv), _path(path), _name(name), _arg(arg), _handler(handler), _pid(-1), _childStdin(0), _childStdout(0), _childStderr(0), _run(true) { _thread = Thread::start(this); } Service::~Service() { _run = false; long pid = _pid; if (pid > 0) { int st = 0; ::kill(pid,SIGTERM); for(int i=0;i<20;++i) { if (waitpid(pid,&st,WNOHANG) == pid) { pid = 0; break; } Thread::sleep(100); } if (pid > 0) { ::kill(pid,SIGKILL); waitpid(pid,&st,0); } } Thread::join(_thread); } bool Service::send(const Dictionary &msg) { if (_childStdin <= 0) return false; std::string mser(msg.toString()); mser.append(ZT_EOL_S); return ((long)::write(_childStdin,mser.data(),mser.length()) == (long)mser.length()); } void Service::threadMain() throw() { char buf[16384]; fd_set readfds,writefds,exceptfds; struct timeval tv; int eolsInARow = 0; std::string stderrBuf,stdoutBuf; while (_run) { if (_pid <= 0) { LOG("launching service %s...",_name.c_str()); int in[2],out[2],err[2]; pipe(in); pipe(out); pipe(err); long pid = vfork(); if (pid < 0) { LOG("service %s terminating: could not fork!",_name.c_str()); return; } else if (pid) { // Parent close(in[0]); close(out[1]); close(err[1]); Thread::sleep(500); // give child time to start _childStdin = in[1]; _childStdout = out[0]; _childStderr = err[0]; fcntl(_childStdout,F_SETFL,O_NONBLOCK); fcntl(_childStderr,F_SETFL,O_NONBLOCK); _pid = pid; } else { // Child close(in[1]); close(out[0]); close(err[0]); dup2(in[0],STDIN_FILENO); dup2(out[1],STDOUT_FILENO); dup2(err[1],STDERR_FILENO); setenv("ZT_HOME",_r->homePath.c_str(),1); chdir(_r->homePath.c_str()); execl(_path.c_str(),_path.c_str(),_r->homePath.c_str(),(const char *)0); exit(-1); } } else { int st = 0; if (waitpid(_pid,&st,WNOHANG) == _pid) { if (_childStdin > 0) close(_childStdin); _childStdin = 0; if (_childStdout > 0) close(_childStdout); if (_childStderr > 0) close(_childStderr); _pid = 0; if (!_run) return; LOG("service %s exited with exit code: %d, delaying 1s to attempt relaunch",_name.c_str(),st); Thread::sleep(1000); // wait to relaunch continue; } } // If we've made it here, _pid is running last we checked. FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); FD_SET(_childStdout,&readfds); FD_SET(_childStderr,&readfds); tv.tv_sec = 1; tv.tv_usec = 0; ::select(std::max(_childStdout,_childStderr)+1,&readfds,&writefds,&exceptfds,&tv); if (!_run) { if (_childStdin > 0) ::close(_childStdin); _childStdin = 0; if (_childStdout > 0) ::close(_childStdout); if (_childStderr > 0) ::close(_childStderr); return; } if ((_childStderr > 0)&&(FD_ISSET(_childStderr,&readfds))) { int n = (int)::read(_childStderr,buf,sizeof(buf)); for(int i=0;i 0)&&(FD_ISSET(_childStdout,&readfds))) { int n = (int)::read(_childStdout,buf,sizeof(buf)); for(int i=0;i= 2) { // Two CRs in a row ends a message try { _handler(_arg,*this,Dictionary(stdoutBuf)); stdoutBuf = ""; } catch ( ... ) {} // handlers should not throw } else stdoutBuf.push_back(buf[i]); } } } } } // namespace ZeroTier #endif // __WINDOWS__