/** * extmodule.cpp * This file is part of the YATE Project http://YATE.null.ro * * External module handler * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2017 Null Team * Portions copyright (C) 2005 Maciek Kaminski * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. * * 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. */ #include #ifdef _WINDOWS #include #else #include #include #include #endif #include #include #include #include #include using namespace TelEngine; namespace { // anonymous // Minimum length of the incoming line buffer #define MIN_INCOMING_LINE 2048 // Default length of the incoming line buffer #define DEF_INCOMING_LINE 8192 // Maximum length of the incoming line buffer #define MAX_INCOMING_LINE 65536 // Default maximum messages queued in a receiver #define DEF_MAXQUEUE 1000 // Maximum maximum messages queued in a receiver #define MAX_MAXQUEUE 10000 // Default message timeout in milliseconds #define MSG_TIMEOUT 10000 // Safety wait time after we flushed watchers, relays or messages (in ms) #define WAIT_FLUSH 5 static Configuration s_cfg; static ObjList s_chans; static ObjList s_modules; static Mutex s_mutex(true,"ExtModule"); static Mutex s_uses(false,"ExtModUse"); static int s_waitFlush = WAIT_FLUSH; static int s_timeout = MSG_TIMEOUT; static int s_maxQueue = DEF_MAXQUEUE; static bool s_settime = false; static bool s_timebomb = false; static bool s_pluginSafe = true; static const char* s_trackName = 0; static const char* s_cmds[] = { "info", "start", "stop", "restart", "execute", 0 }; static const char s_helpExternalCmd[] = "external [info] [stop scriptname] [[start|restart] scriptname [parameter]] [execute progname [parameter]]"; static const char s_helpExternalInfo[] = "List, (re)start and stop scripts or execute an external program"; class ExtModReceiver; class ExtModChan; class ExtModSource : public ThreadedSource { public: ExtModSource(Stream* str, ExtModChan* chan); ~ExtModSource(); virtual void run(); private: Stream* m_str; unsigned m_brate; unsigned m_total; ExtModChan* m_chan; }; class ExtModConsumer : public DataConsumer { public: ExtModConsumer(Stream* str); ~ExtModConsumer(); virtual unsigned long Consume(const DataBlock& data, unsigned long timestamp, unsigned long flags); private: Stream* m_str; unsigned m_total; }; class ExtModChan : public CallEndpoint { public: enum { NoChannel, DataNone, DataRead, DataWrite, DataBoth }; ExtModChan(ExtModReceiver* recv); static ExtModChan* build(const char* file, const char* args, int type); ~ExtModChan(); virtual void disconnected(bool final, const char* reason); inline ExtModReceiver* receiver() const { return m_recv; } inline void setRecv(ExtModReceiver* recv) { m_recv = recv; } inline void setRunning(bool running) { m_running = running; } inline bool running() const { return m_running; } inline void setDisconn(bool disconn) { m_disconn = disconn; } inline bool disconn() const { return m_disconn; } inline void setId(const String& id) { CallEndpoint::setId(id); } inline const Message* waitMsg() const { return m_waitRet; } inline void waitMsg(const Message* msg) { m_waitRet = msg; } inline bool waiting() const { return m_waiting; } inline void waiting(bool wait) { m_waiting = wait; } private: ExtModChan(const char* file, const char* args, int type); ExtModReceiver *m_recv; const Message* m_waitRet; int m_type; bool m_running; bool m_disconn; bool m_waiting; }; // Great idea - thanks, Maciek! class ExtMessage : public Message { YCLASS(ExtMessage,Message) public: inline ExtMessage() : Message(""), m_receiver(0), m_accepted(false) { } virtual ~ExtMessage(); void startup(ExtModReceiver* recv); virtual void dispatched(bool accepted); inline bool belongsTo(ExtModReceiver* recv) const { return m_receiver == recv; } inline int decode(const char* str) { return Message::decode(str,m_id); } inline const String& id() const { return m_id; } private: ExtModReceiver* m_receiver; String m_id; bool m_accepted; }; class MsgHolder : public GenObject, public Semaphore { public: MsgHolder(Message &msg); Message &m_msg; bool m_ret; String m_id; bool decode(const char *s); inline const Message* msg() const { return &m_msg; } }; // Yet Another of Maciek's ideas class MsgWatcher : public MessagePostHook { public: inline MsgWatcher(ExtModReceiver* receiver) : m_receiver(receiver) { } virtual void dispatched(const Message& msg, bool handled); bool addWatched(const String& name); bool delWatched(const String& name); void clear(); protected: virtual void destroyed(); private: ExtModReceiver* m_receiver; ObjList m_watched; }; class ExtModReceiver : public MessageReceiver, public Mutex, public DebugEnabler { friend class MsgWatcher; public: enum { RoleUnknown, RoleGlobal, RoleChannel }; static ExtModReceiver* build(const char *script, const char *args, bool ref = false, File* ain = 0, File* aout = 0, ExtModChan *chan = 0); static ExtModReceiver* build(const char* name, Stream* io, ExtModChan* chan = 0, int role = RoleUnknown, const char* conn = 0); static ExtModReceiver* find(const String& script, const String& arg); virtual void destruct(); virtual bool received(Message& msg, int id); bool processLine(const char* line); bool outputLine(const char* line); void reportError(const char* line); void returnMsg(const Message* msg, const char* id, bool accepted); bool addWatched(const String& name); bool delWatched(const String& name); bool start(); void run(); void cleanup(); bool flush(); void die(bool clearChan = true); bool useUnlocked(); bool use(); bool unuse(); inline const String& scriptFile() const { return m_script; } inline const String& commandArg() const { return m_args; } inline bool selfWatch() const { return m_selfWatch; } inline void setRestart(bool restart) { m_restart = restart; } inline bool dead() const { return m_dead || m_quit || (m_use <= 0); } void describe(String& rval) const; private: ExtModReceiver(const char* script, const char* args, File* ain, File* aout, ExtModChan* chan); ExtModReceiver(const char* name, Stream* io, ExtModChan* chan, int role, const char* conn); bool create(const char* script, const char* args); void closeIn(); void closeOut(); void closeAudio(); bool outputLineInternal(const char* line, int len); int m_role; bool m_dead; bool m_quit; int m_use; int m_qLength; pid_t m_pid; Stream* m_in; Stream* m_out; File* m_ain; File* m_aout; ExtModChan* m_chan; MsgWatcher* m_watcher; bool m_selfWatch; bool m_reenter; bool m_setdata; bool m_settime; bool m_writing; int m_maxQueue; int m_timeout; bool m_timebomb; bool m_restart; bool m_scripted; DataBlock m_buffer; String m_script, m_args; ObjList m_waiting; ObjList m_relays; String m_trackName; String m_reason; String m_debugName; }; class ExtThread : public Thread { public: ExtThread(ExtModReceiver* receiver) : Thread("ExtMod Receiver"), m_receiver(receiver) { } virtual void run() { m_receiver->run(); } virtual void cleanup() { m_receiver->cleanup(); } private: ExtModReceiver* m_receiver; }; class ExtModHandler; class ExtModulePlugin : public Plugin { public: ExtModulePlugin(); ~ExtModulePlugin(); virtual void initialize(); virtual bool isBusy() const; private: ExtModHandler *m_handler; }; INIT_PLUGIN(ExtModulePlugin); class ExtModHandler : public MessageHandler { public: ExtModHandler(const char *name, unsigned prio) : MessageHandler(name,prio,__plugin.name()) { } virtual bool received(Message &msg); }; class ExtModCommand : public MessageHandler { public: ExtModCommand() : MessageHandler("engine.command",100,__plugin.name()) { } virtual bool received(Message &msg); private: bool complete(const String& partLine, const String& partWord, String& rval) const; }; class ExtModStatus : public MessageHandler { public: ExtModStatus() : MessageHandler("engine.status",110,__plugin.name()) { } virtual bool received(Message &msg); }; class ExtModHelp : public MessageHandler { public: ExtModHelp() : MessageHandler("engine.help",100,__plugin.name()) { } virtual bool received(Message& msg); }; class ExtListener : public Thread { public: ExtListener(const char* name); virtual bool init(const NamedList& sect); virtual void run(); inline const String& name() const { return m_name; } static ExtListener* build(const char* name, const NamedList& sect); protected: Socket m_socket; String m_name; int m_role; }; static bool runProgram(const char *script, const char *args) { #ifdef _WINDOWS int pid = ::_spawnl(_P_DETACH,script,args,NULL); if (pid < 0) { Debug(DebugWarn, "Failed to _spawnl(): %d: %s", errno, strerror(errno)); return false; } #else int pid = ::fork(); if (pid < 0) { Debug(DebugWarn, "Failed to fork(): %d: %s", errno, strerror(errno)); return false; } if (!pid) { // In child - terminate all other threads if needed Thread::preExec(); // Try to immunize child from ^C and ^\ the console may receive ::signal(SIGINT,SIG_IGN); ::signal(SIGQUIT,SIG_IGN); // And restore default handlers for other signals ::signal(SIGTERM,SIG_DFL); ::signal(SIGHUP,SIG_DFL); // Blindly close everything but stdin/out/err for (int f=STDERR_FILENO+1;f<1024;f++) ::close(f); // Execute script if (debugAt(DebugInfo)) ::fprintf(stderr, "Execing program '%s' '%s'\n", script, args); ::execl(script, script, args, (char *)NULL); ::fprintf(stderr, "Failed to execute '%s': %d: %s\n", script, errno, strerror(errno)); // Shit happened. Die as quick and brutal as possible ::_exit(1); } #endif Debug(DebugInfo,"Launched external program %s", script); return true; } static void adjustPath(String& script) { if (script.null() || script.startsWith(Engine::pathSeparator())) return; String tmp = Engine::sharedPath(); tmp << Engine::pathSeparator() << "scripts"; tmp = s_cfg.getValue("general","scripts_dir",tmp); Engine::runParams().replaceParams(tmp); if (!tmp.endsWith(Engine::pathSeparator())) tmp += Engine::pathSeparator(); script = tmp + script; } ExtModSource::ExtModSource(Stream* str, ExtModChan* chan) : m_str(str), m_brate(16000), m_total(0), m_chan(chan) { Debug(DebugAll,"ExtModSource::ExtModSource(%p) [%p]",str,this); if (m_str) { chan->setRunning(true); start("ExtMod Source"); } } ExtModSource::~ExtModSource() { Debug(DebugAll,"ExtModSource::~ExtModSource() [%p] total=%u",this,m_total); m_chan->setRunning(false); if (m_str) { Stream* tmp = m_str; m_str = 0; delete tmp; } } void ExtModSource::run() { char data[320]; int r = 1; u_int64_t tpos = Time::now(); while ((r > 0) && looping()) { if (!m_str) { Thread::yield(); continue; } r = m_str->readData(data,sizeof(data)); if (r < 0) { if (errno == EINTR) { r = 1; continue; } break; } // TODO: allow data to provide its own rate int64_t dly = tpos - Time::now(); if (dly > 0) { XDebug("ExtModSource",DebugAll,"Sleeping for " FMT64 " usec",dly); Thread::usleep((unsigned long)dly); } if (r <= 0) continue; DataBlock buf(data,r,false); Forward(buf,m_total/2); buf.clear(false); m_total += r; tpos += (r*(u_int64_t)1000000/m_brate); } Debug(DebugAll,"ExtModSource [%p] end of data total=%u",this,m_total); m_chan->setRunning(false); } ExtModConsumer::ExtModConsumer(Stream* str) : m_str(str), m_total(0) { Debug(DebugAll,"ExtModConsumer::ExtModConsumer(%p) [%p]",str,this); } ExtModConsumer::~ExtModConsumer() { Debug(DebugAll,"ExtModConsumer::~ExtModConsumer() [%p] total=%u",this,m_total); if (m_str) { Stream* tmp = m_str; m_str = 0; delete tmp; } } unsigned long ExtModConsumer::Consume(const DataBlock& data, unsigned long timestamp, unsigned long flags) { if ((m_str) && !data.null()) { m_str->writeData(data); m_total += data.length(); return invalidStamp(); } return 0; } ExtModChan* ExtModChan::build(const char* file, const char* args, int type) { ExtModChan* chan = new ExtModChan(file,args,type); if (!chan->m_recv) { chan->destruct(); return 0; } return chan; } ExtModChan::ExtModChan(const char* file, const char* args, int type) : CallEndpoint("ExtModule"), m_recv(0), m_waitRet(0), m_type(type), m_running(false), m_disconn(false), m_waiting(false) { Debug(DebugAll,"ExtModChan::ExtModChan(%d) [%p]",type,this); File* reader = 0; File* writer = 0; switch (m_type) { case DataWrite: case DataBoth: { reader = new File; File* tmp = new File; if (File::createPipe(*reader,*tmp)) { setConsumer(new ExtModConsumer(tmp)); getConsumer()->deref(); } else delete tmp; } } switch (m_type) { case DataRead: case DataBoth: { writer = new File; File* tmp = new File; if (File::createPipe(*tmp,*writer)) { setSource(new ExtModSource(tmp,this)); getSource()->deref(); } else delete tmp; } } s_mutex.lock(); s_chans.append(this); s_mutex.unlock(); m_recv = ExtModReceiver::build(file,args,true,reader,writer,this); } ExtModChan::ExtModChan(ExtModReceiver* recv) : CallEndpoint("ExtModule"), m_recv(recv), m_waitRet(0), m_type(DataNone), m_running(false), m_disconn(false), m_waiting(false) { Debug(DebugAll,"ExtModChan::ExtModChan(%p) [%p]",recv,this); s_mutex.lock(); s_chans.append(this); s_mutex.unlock(); } ExtModChan::~ExtModChan() { Debugger debug(DebugAll,"ExtModChan::~ExtModChan()"," [%p]",this); s_mutex.lock(); s_chans.remove(this,false); ExtModReceiver* recv = m_recv; m_recv = 0; s_mutex.unlock(); setSource(); setConsumer(); if (recv) recv->die(false); } void ExtModChan::disconnected(bool final, const char *reason) { Debug(DebugAll,"ExtModChan::disconnected() '%s' [%p]",reason,this); if (final || Engine::exiting()) return; if (m_disconn) { Message* m = new Message("chan.disconnected"); m->userData(this); m->addParam("id",id()); m->addParam("module","external"); if (m_recv) m->addParam("address",m_recv->scriptFile()); if (reason) m->addParam("reason",reason); String peerId; if (getPeerId(peerId) && peerId) m->addParam("peerid",peerId); Engine::enqueue(m); } } MsgHolder::MsgHolder(Message &msg) : m_msg(msg), m_ret(false) { // the address of this object should be unique char buf[64]; ::sprintf(buf,"%p.%ld",this,Random::random()); m_id = buf; } bool MsgHolder::decode(const char *s) { return (m_msg.decode(s,m_ret,m_id) == -2); } ExtMessage::~ExtMessage() { if (m_receiver) { m_receiver->returnMsg(this,m_id,m_accepted); m_receiver->unuse(); } } void ExtMessage::startup(ExtModReceiver* recv) { if (recv && m_id && recv->use()) m_receiver = recv; Engine::enqueue(this); } void ExtMessage::dispatched(bool accepted) { m_accepted = accepted; Message::dispatched(accepted); } void MsgWatcher::destroyed() { clear(); MessagePostHook::destroyed(); } void MsgWatcher::dispatched(const Message& msg, bool handled) { Lock lock(s_uses); ExtModReceiver* recv = m_receiver; if (!recv || recv->dead() || (recv->m_watcher != this) || !recv->useUnlocked()) return; if (!lock.acquire(recv) || !m_receiver || recv->dead()) { lock.drop(); recv->unuse(); return; } if (!recv->selfWatch()) { // check if the message was generated by ourselves - avoid reentrance ExtMessage* m = YOBJECT(ExtMessage,&msg); if (m && m->belongsTo(recv)) { recv->unuse(); return; } } ObjList* l = m_watched.skipNull(); for (; l; l = l->skipNext()) { const String* s = static_cast(l->get()); if (s->null() || (*s == msg)) break; } if (l && m_receiver) { lock.drop(); recv->returnMsg(&msg,"",handled); } recv->unuse(); } bool MsgWatcher::addWatched(const String& name) { if (m_watched.find(name)) return false; // wildcard watches will be inserted first for speed reason if (name.null()) m_watched.insert(new String); else m_watched.append(new String(name)); return true; } bool MsgWatcher::delWatched(const String& name) { GenObject* obj = m_watched[name]; if (obj) { m_watched.remove(obj); return true; } return false; } void MsgWatcher::clear() { Engine::self()->setHook(this,true); if (!m_receiver) return; s_uses.lock(); ExtModReceiver* recv = m_receiver; m_receiver = 0; if (recv && (recv->m_watcher == this)) recv->m_watcher = 0; s_uses.unlock(); } ExtModReceiver* ExtModReceiver::build(const char* script, const char* args, bool ref, File* ain, File* aout, ExtModChan* chan) { ExtModReceiver* recv = new ExtModReceiver(script,args,ain,aout,chan); if (ref) { if (!recv->use()) return 0; if (recv->start()) return recv; recv->unuse(); return 0; } return recv->start() ? recv : 0; } ExtModReceiver* ExtModReceiver::build(const char* name, Stream* io, ExtModChan* chan, int role, const char* conn) { ExtModReceiver* recv = new ExtModReceiver(name,io,chan,role,conn); return recv->start() ? recv : 0; } ExtModReceiver* ExtModReceiver::find(const String& script, const String& arg) { Lock lock(s_mutex); ObjList *l = &s_modules; for (; l; l=l->next()) { ExtModReceiver *r = static_cast(l->get()); if (r && (r->scriptFile() == script)) { if (arg.null() || (r->commandArg() == arg)) return r; } } return 0; } bool ExtModReceiver::useUnlocked() { if (m_use <= 0) return false; ++m_use; return true; } bool ExtModReceiver::use() { s_uses.lock(); bool ok = (m_use > 0); if (ok) ++m_use; s_uses.unlock(); return ok; } bool ExtModReceiver::unuse() { s_uses.lock(); int u = m_use - 1; if (u >= 0) m_use = u; s_uses.unlock(); if (!u) destruct(); return (u <= 0); } ExtModReceiver::ExtModReceiver(const char* script, const char* args, File* ain, File* aout, ExtModChan* chan) : Mutex(true,"ExtModReceiver"), m_role(RoleUnknown), m_dead(false), m_quit(false), m_use(1), m_qLength(0), m_pid(-1), m_in(0), m_out(0), m_ain(ain), m_aout(aout), m_chan(chan), m_watcher(0), m_selfWatch(false), m_reenter(false), m_setdata(true), m_settime(s_settime), m_writing(false), m_maxQueue(s_maxQueue), m_timeout(s_timeout), m_timebomb(s_timebomb), m_restart(false), m_scripted(false), m_buffer(0,DEF_INCOMING_LINE), m_script(script), m_args(args), m_trackName(s_trackName) { debugChain(&__plugin); debugName(m_script); Debug(DebugAll,"ExtModReceiver::ExtModReceiver(\"%s\",\"%s\") [%p]",script,args,this); m_script.trimBlanks(); m_args.trimBlanks(); m_role = chan ? RoleChannel : RoleGlobal; s_mutex.lock(); s_modules.append(this); s_mutex.unlock(); } ExtModReceiver::ExtModReceiver(const char* name, Stream* io, ExtModChan* chan, int role, const char* conn) : Mutex(true,"ExtModReceiver"), m_role(role), m_dead(false), m_quit(false), m_use(1), m_qLength(0), m_pid(-1), m_in(io), m_out(io), m_ain(0), m_aout(0), m_chan(chan), m_watcher(0), m_selfWatch(false), m_reenter(false), m_setdata(true), m_settime(s_settime), m_writing(false), m_maxQueue(s_maxQueue), m_timeout(s_timeout), m_timebomb(s_timebomb), m_restart(false), m_scripted(false), m_buffer(0,DEF_INCOMING_LINE), m_script(name), m_args(conn), m_trackName(s_trackName) { debugChain(&__plugin); debugName(m_script); Debug(DebugAll,"ExtModReceiver::ExtModReceiver(\"%s\",%p,%p) [%p]",name,io,chan,this); m_script.trimBlanks(); m_args.trimBlanks(); if (chan) m_role = RoleChannel; s_mutex.lock(); s_modules.append(this); s_mutex.unlock(); } void ExtModReceiver::destruct() { Debug(DebugAll,"ExtModReceiver::destruct() pid=%d [%p]",m_pid,this); lock(); // One destruction is plenty enough m_use = -1; s_mutex.lock(); s_modules.remove(this,false); s_mutex.unlock(); die(); if (m_pid > 1) Debug(DebugWarn,"ExtModReceiver::destruct() pid=%d [%p]",m_pid,this); closeAudio(); Stream* tmp = m_in; m_in = 0; if (tmp == m_out) m_out = 0; delete tmp; tmp = m_out; m_out = 0; delete tmp; unlock(); Thread::yield(); GenObject::destruct(); } void ExtModReceiver::closeIn() { Stream* tmp = m_in; if (tmp) tmp->terminate(); } void ExtModReceiver::closeOut() { Stream* tmp = m_out; if (tmp) tmp->terminate(); } void ExtModReceiver::closeAudio() { if (m_ain) { delete m_ain; m_ain = 0; } if (m_aout) { delete m_aout; m_aout = 0; } } bool ExtModReceiver::start() { if (m_pid < 0) { ExtThread *ext = new ExtThread(this); if (!ext->startup()) { // self destruct here since there is no thread to do it later unuse(); return false; } while (m_pid < 0) Thread::yield(); } return (m_pid >= 0); } bool ExtModReceiver::flush() { lock(); MsgWatcher* w = m_watcher; m_watcher = 0; bool needWait = !!w; if (w) { w->clear(); Thread::yield(); TelEngine::destruct(w); } // Make sure we release all pending messages and not accept new ones needWait = needWait || (m_relays.count() != 0); if (s_pluginSafe) m_relays.clear(); else { ObjList *p = &m_relays; for (; p; p=p->next()) p->setDelete(false); } bool flushed = false; if (m_waiting.get()) { Debug(DebugInfo,"ExtModReceiver releasing %u pending messages [%p]",m_qLength,this); m_waiting.clear(); m_qLength = 0; needWait = flushed = true; } unlock(); if (needWait && s_pluginSafe) { int ms = s_waitFlush; // During shutdown longer delays are not acceptable if ((ms > WAIT_FLUSH) && Engine::exiting()) ms = WAIT_FLUSH; DDebug(DebugAll,"ExtModReceiver sleeping %d ms [%p]",ms,this); Thread::msleep(ms); } return flushed; } void ExtModReceiver::die(bool clearChan) { #ifdef DEBUG Debugger debug(DebugAll,"ExtModReceiver::die()"," pid=%d dead=%s [%p]", m_pid,m_dead ? "yes" : "no",this); #else Debug(DebugAll,"ExtModReceiver::die() pid=%d dead=%s [%p]", m_pid,m_dead ? "yes" : "no",this); #endif if (m_dead) return; Lock mylock(this); if (m_dead) { DDebug(DebugInfo,"ExtModReceiver::die() pid=%d is already dead [%p]",m_pid,this); return; } m_dead = true; m_quit = true; use(); RefPointer chan = m_chan; m_chan = 0; if (chan) chan->setRecv(0); mylock.drop(); if (m_scripted && (m_role == RoleGlobal)) Output("Unloading external module '%s' '%s'",m_script.c_str(),m_args.safe()); // Give the external script a chance to die gracefully closeOut(); if (m_pid > 1) { Debug(DebugAll,"ExtModReceiver::die() waiting for pid=%d to die [%p]",m_pid,this); for (int i=0; i<100; i++) { Thread::yield(); if (m_pid <= 0) break; } } if (m_pid > 1) Debug(DebugInfo,"ExtModReceiver::die() pid=%d did not exit? [%p]",m_pid,this); // Close the stdout pipe before terminating the process closeIn(); // Release relays and messages since no confirmation can be received anymore flush(); #ifndef _WINDOWS if (m_pid > 1) ::kill(m_pid,SIGTERM); #endif if (chan && clearChan) chan->disconnect(m_reason); if (m_restart && !Engine::exiting()) { Debug(DebugMild,"Restarting external '%s' '%s'",m_script.safe(),m_args.safe()); ExtModReceiver::build(m_script,m_args); } unuse(); } bool ExtModReceiver::received(Message &msg, int id) { if (m_dead || m_quit) return false; if (!lock(m_timeout > 0 ? ((long)m_timeout * 1000) : -1)) { Alarm("extmodule","performance",DebugWarn, "Failed to lock to queue message (%p) '%s' for %d msec [%p]", &msg,msg.c_str(),m_timeout,this); return false; } // check if we are no longer running bool ok = (m_pid > 0) && !m_dead && m_in && m_in->valid() && m_out && m_out->valid(); if (ok && !m_reenter) { // check if the message was generated by ourselves - avoid reentrance ExtMessage* m = YOBJECT(ExtMessage,&msg); if (m && m->belongsTo(this)) ok = false; } if (ok && m_maxQueue && (m_qLength >= m_maxQueue)) { Debug(DebugWarn,"ExtMod already having %u queued messages [%p]",m_qLength,this); ok = false; } if (!ok) { unlock(); return false; } use(); bool fail = false; u_int64_t tout = (m_timeout > 0) ? Time::now() + 1000 * m_timeout : 0; MsgHolder h(msg); if (outputLine(msg.encode(h.m_id))) { m_qLength++; m_waiting.append(&h)->setDelete(false); DDebug(DebugAll,"ExtMod queued message #%u %p '%s' [%p]",m_qLength,&msg,msg.c_str(),this); } else { Debug(DebugWarn,"ExtMod could not queue message %p '%s' [%p]",&msg,msg.c_str(),this); ok = false; fail = true; } unlock(); // would be nice to lock the MsgHolder and wait for it to unlock from some // other thread - unfortunately this does not work with all mutexes // sorry, Maciek - have to do it work in Windows too :-( while (ok) { h.lock(Thread::idleUsec()); lock(); ok = (m_waiting.find(&h) != 0); if (ok && tout && (Time::now() > tout)) { Alarm("extmodule","performance",DebugWarn,"Message %p '%s' did not return in %d msec [%p]", &msg,msg.c_str(),m_timeout,this); if (m_waiting.remove(&h,false) && (m_qLength > 0)) m_qLength--; ok = false; fail = true; } unlock(); } DDebug(DebugAll,"ExtMod message %p '%s' returning %s [%p]", &msg,msg.c_str(),String::boolText(h.m_ret),this); if (fail && m_timebomb) die(); unuse(); return h.m_ret; } bool ExtModReceiver::create(const char *script, const char *args) { #ifdef _WINDOWS return false; #else String tmp(script); int pid; HANDLE ext2yate[2]; HANDLE yate2ext[2]; int x; adjustPath(tmp); script = tmp.c_str(); if (::pipe(ext2yate)) { Debug(DebugWarn, "Unable to create ext->yate pipe: %s",strerror(errno)); return false; } if (pipe(yate2ext)) { Debug(DebugWarn, "unable to create yate->ext pipe: %s", strerror(errno)); ::close(ext2yate[0]); ::close(ext2yate[1]); return false; } pid = ::fork(); if (pid < 0) { Debug(DebugWarn, "Failed to fork(): %s", strerror(errno)); ::close(yate2ext[0]); ::close(yate2ext[1]); ::close(ext2yate[0]); ::close(ext2yate[1]); return false; } if (!pid) { // In child - terminate all other threads if needed Thread::preExec(); // Try to immunize child from ^C and ^\ the console may receive ::signal(SIGINT,SIG_IGN); ::signal(SIGQUIT,SIG_IGN); // And restore default handlers for other signals ::signal(SIGTERM,SIG_DFL); ::signal(SIGHUP,SIG_DFL); // Redirect stdin and out ::dup2(yate2ext[0], STDIN_FILENO); ::dup2(ext2yate[1], STDOUT_FILENO); // Set audio in/out handlers if (m_ain && m_ain->valid()) ::dup2(m_ain->handle(), STDERR_FILENO+1); else ::close(STDERR_FILENO+1); if (m_aout && m_aout->valid()) ::dup2(m_aout->handle(), STDERR_FILENO+2); else ::close(STDERR_FILENO+2); // Blindly close everything but stdin/out/err/audio for (x=STDERR_FILENO+3;x<1024;x++) ::close(x); // Execute script if (debugAt(DebugInfo)) ::fprintf(stderr, "Execing '%s' '%s'\n", script, args); ::execl(script, script, args, (char *)NULL); ::fprintf(stderr, "Failed to execute '%s': %s\n", script, strerror(errno)); // Shit happened. Die as quick and brutal as possible ::_exit(1); } if (m_role == RoleGlobal) Output("Loading external module '%s' '%s'",m_script.c_str(),args); else Debug(DebugInfo,"Launched External Script '%s' '%s'",script,args); m_in = new File(ext2yate[0]); m_out = new File(yate2ext[1]); // close what we're not using in the parent close(ext2yate[1]); close(yate2ext[0]); closeAudio(); m_scripted = true; m_pid = pid; return true; #endif } void ExtModReceiver::cleanup() { #ifdef DEBUG Debugger debug(DebugAll,"ExtModReceiver::cleanup()"," [%p]",this); #endif #ifndef _WINDOWS // We must call waitpid from here - same thread we started the child if (m_pid > 1) { // No thread switching if possible closeOut(); Thread::yield(); int w = ::waitpid(m_pid, 0, WNOHANG); if (w == 0) { Debug(DebugWarn,"Process %d has not exited on closing stdin - we'll kill it",m_pid); ::kill(m_pid,SIGTERM); Thread::yield(); w = ::waitpid(m_pid, 0, WNOHANG); } if (w == 0) Debug(DebugWarn,"Process %d has still not exited yet?",m_pid); else if ((w < 0) && (errno != ECHILD)) Debug(DebugMild,"Failed waitpid on %d: %s",m_pid,strerror(errno)); } if (m_pid > 0) m_pid = 0; #endif unuse(); } void ExtModReceiver::run() { // the i/o streams may be already allocated if (m_in && m_out) m_pid = 1; // just an indicator, not really init ;-) // we must do the forking from this thread so we can later wait() on it else if (!create(m_script.safe(),m_args.safe())) { m_pid = 0; return; } if (m_in && !m_in->setBlocking(false)) Debug("ExtModule",DebugWarn,"Failed to set nonblocking mode, expect trouble [%p]",this); int posinbuf = 0; bool invalid = true; DDebug(DebugAll,"ExtModReceiver::run() entering loop [%p]",this); for (;;) { use(); lock(); char* buffer = static_cast(m_buffer.data()); int bufspace = m_buffer.length() - posinbuf - 1; int readsize = (m_in && bufspace) ? m_in->readData(buffer+posinbuf,bufspace) : 0; unlock(); if (unuse() || m_dead) return; if (!bufspace) { Debug("ExtModule",DebugWarn,"Overflow reading in buffer of length %u, closing [%p]", m_buffer.length(),this); return; } if (!readsize) { if (m_in) Debug("ExtModule",DebugInfo,"Read EOF on %p [%p]",m_in,this); closeIn(); flush(); if (invalid) Debug("ExtModule",DebugWarn,"Never got anything valid from terminated '%s' '%s'", m_script.c_str(),m_args.safe()); if (m_chan && m_chan->running()) Thread::sleep(1); break; } else if (readsize < 0) { Lock mylock(this); if (m_in && m_in->canRetry()) { mylock.drop(); Thread::idle(); continue; } if (!m_quit) Debug("ExtModule",DebugWarn,"Read error %d on %p [%p]",errno,m_in,this); break; } XDebug(DebugAll,"ExtModReceiver::run() read %d [%p]",readsize,this); int totalsize = readsize + posinbuf; if (totalsize >= (int)m_buffer.length()) { Debug("ExtModule",DebugWarn,"Overflow reading in buffer of length %u, closing [%p]", m_buffer.length(),this); return; } buffer[totalsize] = 0; for (;;) { char *eoline = ::strchr(buffer,'\n'); if (!eoline && ((int)::strlen(buffer) < totalsize)) eoline=buffer+::strlen(buffer); if (!eoline) break; *eoline = 0; if ((eoline > buffer) && (eoline[-1] == '\r')) eoline[-1] = 0; readsize = eoline-buffer+1; if (buffer[0]) { invalid = invalid && (buffer[0] != '%' || buffer[1] != '%'); use(); bool goOut = processLine(buffer); if (unuse() || goOut) return; if (totalsize >= (int)m_buffer.length()) { Debug("ExtModule",DebugWarn,"Lost data shrinking read buffer to %u, closing [%p]", m_buffer.length(),this); return; } } totalsize -= readsize; buffer = static_cast(m_buffer.data()); ::memmove(buffer,buffer+readsize,totalsize+1); } posinbuf = totalsize; } } bool ExtModReceiver::outputLine(const char* line) { if (TelEngine::null(line)) return true; int len = ::strlen(line); if (m_dead || !m_out || !m_out->valid() || !use()) return false; uint64_t tout = (m_timeout > 0) ? (Time::now() + 1000 * (uint64_t)m_timeout) : 0; for (;;) { Lock mylock(this); if (m_dead || !m_out || !m_out->valid()) { unuse(); return false; } if (!m_writing) { m_writing = true; break; } if (tout && tout < Time::now()) { if (!m_quit) Alarm("extmodule","performance",DebugWarn,"Timeout %d msec for %d characters [%p]", m_timeout,len,this); unuse(); return false; } mylock.drop(); Thread::idle(); } bool ok = outputLineInternal(line,len); m_writing = false; unuse(); return ok; } bool ExtModReceiver::outputLineInternal(const char* line, int len) { DDebug("ExtModReceiver",DebugAll,"outputLine len=%d '%s' [%p]",len,line,this); // since m_out can be non-blocking (the socket) we have to loop while (m_out && m_out->valid() && (len > 0) && !m_dead) { int w = m_out->writeData(line,len); if (w < 0) { if (m_dead || !m_out || !m_out->canRetry()) return false; } else { line += w; len -= w; } if (len > 0) Thread::idle(); } char nl = '\n'; for (;;) { if (m_dead || !m_out) return false; int w = m_out->writeData(&nl,1); if ((w < 0) && m_out && m_out->canRetry()) w = 0; if (w > 0) return true; if (w < 0) return false; Thread::idle(); } } void ExtModReceiver::reportError(const char* line) { Debug("ExtModReceiver",DebugWarn,"Error: '%s'", line); outputLine("Error in: " + String(line)); } void ExtModReceiver::returnMsg(const Message* msg, const char* id, bool accepted) { String ret(msg->encode(accepted,id)); if (!outputLine(ret) && m_timebomb) die(); } bool ExtModReceiver::addWatched(const String& name) { Lock mylock(this); if (m_dead) return false; if (!m_watcher) { m_watcher = new MsgWatcher(this); Engine::self()->setHook(m_watcher); } return m_watcher->addWatched(name); } bool ExtModReceiver::delWatched(const String& name) { Lock mylock(this); if (m_dead) return false; return m_watcher && m_watcher->delWatched(name); } bool ExtModReceiver::processLine(const char* line) { if (m_dead) return false; if (m_quit) return true; DDebug("ExtModReceiver",DebugAll,"processLine '%s'", line); String id(line); if (m_role == RoleUnknown) { if (id.startSkip("%%>connect:",false)) { int sep = id.find(':'); String role; String chan; String type; if (sep >= 0) { role = id.substr(0,sep); id = id.substr(sep+1); sep = id.find(':'); if (sep >= 0) { chan = id.substr(0,sep); type = id.substr(sep+1); } else chan = id; } else role = id; DDebug("ExtModReceiver",DebugAll,"role '%s' chan '%s' type '%s'", role.c_str(),chan.c_str(),type.c_str()); if (role == "global") { m_role = RoleGlobal; return false; } else if (role == "channel") { m_role = RoleChannel; return false; } Debug(DebugWarn,"Unknown role '%s' received [%p]",role.c_str(),this); } else Debug(DebugWarn,"Expecting %%%%>connect, received '%s' [%p]",id.c_str(),this); return true; } else if (id.startsWith("%%next()) { MsgHolder *msg = static_cast(p->get()); if (msg && msg->decode(line)) { DDebug("ExtModReceiver",DebugInfo,"Matched message %p [%p]",msg->msg(),this); if (m_chan && (m_chan->waitMsg() == msg->msg())) { DDebug("ExtModReceiver",DebugNote,"Entering wait mode on channel %p [%p]",m_chan,this); m_chan->waitMsg(0); m_chan->waiting(true); } msg->unlock(); if (p->remove(false) && (m_qLength > 0)) m_qLength--; return false; } } Debug("ExtModReceiver",(m_dead ? DebugInfo : DebugWarn), "Unmatched%s message: %s [%p]",(m_dead ? " dead" : ""),line,this); return false; } else if (id.startSkip("%%>install:",false)) { int prio = 100; id >> prio >> ":"; String fname; String fvalue; static const Regexp r("^\\([^:]*\\):\\([^:]*\\):\\?\\(.*\\)"); if (id.matches(r)) { // a filter is specified fname = String::msgUnescape(id.matchString(2)); fvalue = String::msgUnescape(id.matchString(3)); id = id.matchString(1); } // sanity checks lock(); bool ok = id && !m_dead && !m_relays.find(id); if (ok) { MessageRelay *r = new MessageRelay(id,this,0,prio,m_trackName); if (fname) r->setFilter(fname,fvalue); m_relays.append(r); Engine::install(r); } unlock(); if (debugAt(DebugAll)) { String tmp; if (fname) tmp << "filter: '" << fname << "'='" << fvalue << "' "; tmp << (ok ? "ok" : "failed"); Debug("ExtModReceiver",DebugAll,"Install '%s', prio %d %s", id.c_str(),prio,tmp.c_str()); } String out("%%uninstall:",false)) { int prio = 0; bool ok = false; lock(); ObjList *p = &m_relays; for (; p; p=p->next()) { MessageRelay *r = static_cast(p->get()); if (r && (*r == id)) { prio = r->priority(); p->remove(); ok = true; break; } } unlock(); Debug("ExtModReceiver",DebugAll,"Uninstall '%s' %s", id.c_str(),ok ? "ok" : "failed"); String out("%%watch:",false)) { bool ok = addWatched(id); Debug("ExtModReceiver",DebugAll,"Watch '%s' %s", id.c_str(),ok ? "ok" : "failed"); String out("%%unwatch:",false)) { bool ok = delWatched(id); Debug("ExtModReceiver",DebugAll,"Unwatch '%s' %s", id.c_str(),ok ? "ok" : "failed"); String out("%%output:",false)) { id.trimBlanks(); Output("%s",id.safe()); return false; } else if (id.startSkip("%%>debug:",false)) { int pos = id.find(':'); if (pos > 0) { int level = id.substr(0,pos).toInteger(DebugAll,0,DebugTest,DebugAll); Debug(this,level,"%s",String::msgUnescape(id.substr(pos + 1)).safe()); return false; } } else if (id.startSkip("%%>setlocal:",false)) { int col = id.find(':'); if (col > 0) { String val(id.substr(col+1)); val.trimBlanks(); id = id.substr(0,col); bool ok = false; Lock mylock(this); if (m_dead) return false; if (m_chan && (id == "id")) { if (val.null()) val = m_chan->id(); else m_chan->setId(val); ok = true; } else if (m_chan && (id == "disconnected")) { m_chan->setDisconn(val.toBoolean(m_chan->disconn())); val = m_chan->disconn(); ok = true; } else if (id == "trackparam") { if (val.null()) val = m_trackName; else m_trackName = val; ok = true; } else if (id == "reason") { m_reason = val; ok = true; } else if (id == "timeout") { m_timeout = val.toInteger(m_timeout); val = m_timeout; ok = true; } else if (id == "timebomb") { m_timebomb = val.toBoolean(m_timebomb); val = m_timebomb; ok = true; } else if (id == "maxqueue") { m_maxQueue = val.toInteger(m_maxQueue,0,0,MAX_MAXQUEUE); val = m_maxQueue; ok = true; } else if (id == "bufsize") { unsigned int len = val.toInteger(m_buffer.length(),0, MIN_INCOMING_LINE,MAX_INCOMING_LINE); if (len > m_buffer.length()) m_buffer.append(DataBlock(0,len - m_buffer.length())); else if (len < m_buffer.length()) m_buffer.assign(m_buffer.data(),len); val = m_buffer.length(); ok = true; } else if (id == "restart") { m_restart = m_scripted && (RoleGlobal == m_role) && val.toBoolean(m_restart); val = m_restart; ok = true; } else if (id == "reenter") { m_reenter = val.toBoolean(m_reenter); val = m_reenter; ok = true; } else if (id == "setdata") { m_setdata = val.toBoolean(m_setdata); val = m_setdata; ok = true; } else if (id == "settime") { m_settime = val.toBoolean(m_settime); val = m_setdata; ok = true; } else if (id == "selfwatch") { m_selfWatch = val.toBoolean(m_selfWatch); val = m_selfWatch; ok = true; } else if (id.startsWith("engine.")) { // keep the index in substr in sync with length of "engine." const NamedString* param = Engine::runParams().getParam(id.substr(7)); ok = val.null() && param; val = param; } else if (id.startsWith("config.")) { ok = val.null(); // keep the index in substr in sync with length of "config." val = id.substr(7); int sep = val.find('.'); if (sep > 0) { const NamedString* key = Engine::config().getKey(val.substr(0,sep).trimBlanks(), val.substr(sep+1).trimBlanks()); if (key) val = *key; else { val.clear(); ok = false; } } else { ok = (Engine::config().getSection(val) != 0); val.clear(); } } else if (id.startsWith("loaded.")) { ok = val.null(); // keep the index in substr in sync with length of "loaded." val = Engine::self()->pluginLoaded(id.substr(7)); } else if (id == "runid") { ok = val.null(); val = Engine::runId(); } else if (id == YSTRING("debuglevel")) { ok = true; if (val) debugLevel(val.toInteger(DebugAll,0,DebugTest,DebugAll)); val = debugLevel(); } else if (id == YSTRING("debugname")) { ok = true; if (val && !m_debugName) { m_debugName = val; debugName(m_debugName); } else val = debugName(); } DDebug("ExtModReceiver",DebugAll,"Set '%s'='%s' %s", id.c_str(),val.c_str(),ok ? "ok" : "failed"); String out("%%quit") { m_quit = true; outputLine("%%decode(line) == -2) { DDebug("ExtModReceiver",DebugAll,"Created message %p '%s' [%p]",m,m->c_str(),this); lock(); bool note = true; while (!m_dead && m_chan && m_chan->waiting()) { if (note) { note = false; Debug("ExtModReceiver",DebugNote,"Waiting before enqueueing new message %p '%s' [%p]", m,m->c_str(),this); } unlock(); Thread::yield(); if (m_dead) { m->destruct(); return false; } lock(); } ExtModChan* chan = 0; if ((m_role == RoleChannel) && !m_chan && m_setdata && (*m == "call.execute")) { // we delayed channel creation as there was nothing to ref() it chan = new ExtModChan(this); m_chan = chan; m->setParam("id",chan->id()); } if (m_setdata) m->userData(m_chan); // now the newly created channel is referenced by the message if (chan) chan->deref(); id = m->id(); if (id && !chan) { // Copy the user data pointer from waiting message with same id ObjList *p = &m_waiting; for (; p; p=p->next()) { MsgHolder *h = static_cast(p->get()); if (h && (h->m_id == id)) { RefObject* ud = h->m_msg.userData(); Debug("ExtModReceiver",DebugAll,"Copying data pointer %p from %p '%s' [%p]", ud,h->msg(),h->msg()->c_str(),this); m->userData(ud); break; } } } if (m_settime || !m->msgTime()) m->msgTime() = Time::now(); m->startup(this); unlock(); return false; } m->destruct(); } reportError(line); return false; } void ExtModReceiver::describe(String& rval) const { rval << "\t"; switch (m_role) { case RoleUnknown: rval << "Unknown"; break; case RoleGlobal: rval << "Global"; break; case RoleChannel: rval << "Channel"; break; default: rval << "Invalid"; break; } if (m_dead) rval << ", dead, use=" << m_use; if (m_chan) rval << ", has channel"; if (m_restart) rval << ", autorestart"; if (m_pid > 0) rval << ", pid=" << m_pid; rval << "\r\n"; } bool ExtModHandler::received(Message& msg) { String dest(msg.getValue("callto")); if (dest.null()) return false; static const Regexp r("^external/\\([^/]*\\)/\\([^ ]*\\)\\(.*\\)$"); if (!dest.matches(r)) return false; CallEndpoint* ch = YOBJECT(CallEndpoint,msg.userData()); String t = dest.matchString(1); int typ = 0; if (t == "nochan") typ = ExtModChan::NoChannel; else if (t == "nodata") typ = ExtModChan::DataNone; else if (t == "play") typ = ExtModChan::DataRead; else if (t == "record") typ = ExtModChan::DataWrite; else if (t == "playrec") typ = ExtModChan::DataBoth; else { Debug(DebugConf,"Invalid ExtModule method '%s', use 'nochan', 'nodata', 'play', 'record' or 'playrec'", t.c_str()); return false; } if (typ == ExtModChan::NoChannel) { ExtModReceiver *r = ExtModReceiver::build(dest.matchString(2).c_str(), dest.matchString(3).trimBlanks().c_str(), true); if (!r) return false; bool ok = r->received(msg,1); r->unuse(); return ok; } ExtModChan *em = ExtModChan::build(dest.matchString(2).c_str(), dest.matchString(3).c_str(),typ); if (!em) { Debug(DebugCrit,"Failed to create ExtMod for '%s'",dest.matchString(2).c_str()); return false; } ExtModReceiver* recv = em->receiver(); // new messages must be blocked until connect() returns (if applicable) if (ch) em->waitMsg(&msg); if (!(recv && recv->received(msg,1))) { em->waitMsg(0); int level = DebugWarn; if (msg.getValue("error") || msg.getValue("reason")) level = DebugNote; Debug(level,"ExtMod '%s' did not handle call message",dest.matchString(2).c_str()); em->waiting(false); if (recv) recv->unuse(); em->deref(); return false; } recv->unuse(); if (ch) { em->waitMsg(0); ch->connect(em,msg.getValue("reason")); em->waiting(false); } em->deref(); return true; } bool ExtModCommand::received(Message& msg) { String line(msg.getValue("line")); if (!line.startsWith("external",true)) return complete(msg.getValue("partline"),msg.getValue("partword"),msg.retValue());; line >> "external"; line.trimBlanks(); if (line.null() || line == "info") { msg.retValue() = ""; int n = 0; Lock lock(s_mutex); ObjList *l = &s_modules; for (; l; l=l->next()) { ExtModReceiver *r = static_cast(l->get()); if (r) { msg.retValue() << ++n << ". " << r->scriptFile() << " " << r->commandArg() << "\r\n"; if (line) r->describe(msg.retValue()); } } return true; } int blank = line.find(' '); bool start = line.startSkip("start"); bool restart = start || line.startSkip("restart"); if (restart || line.startSkip("stop")) { if (line.null()) return false; blank = line.find(' '); String arg; if (blank >= 0) arg = line.substr(blank+1); ExtModReceiver *r = ExtModReceiver::find(line.substr(0,blank),arg); if (r) { if (start) { msg.retValue() = "External already running\r\n"; return true; } else { r->setRestart(false); r->die(); msg.retValue() = "External command stopped\r\n"; } } else msg.retValue() = "External not running\r\n"; if (!restart) return true; } else if (line.startSkip("execute")) { if (line.null()) return false; blank = line.find(' '); String exe = line.substr(0,blank); adjustPath(exe); if (blank >= 0) line = line.substr(blank+1); else line.clear(); bool ok = runProgram(exe,line); msg.retValue() = ok ? "External exec attempt\r\n" : "External exec failed\r\n"; return true; } ExtModReceiver *r = ExtModReceiver::build(line.substr(0,blank), (blank >= 0) ? line.substr(blank+1).c_str() : (const char*)0); msg.retValue() = r ? "External start attempt\r\n" : "External command failed\r\n"; return true; } bool ExtModCommand::complete(const String& partLine, const String& partWord, String& rval) const { if (partLine.null() && partWord.null()) return false; if (partLine.null() || partLine == YSTRING("status") || partLine == YSTRING("help")) Module::itemComplete(rval,"external",partWord); else if (partLine == YSTRING("external")) { for (const char** list = s_cmds; *list; list++) Module::itemComplete(rval,*list,partWord); return true; } else if (partLine == YSTRING("external restart") || partLine == YSTRING("external stop")) { ObjList mod; s_mutex.lock(); ObjList *l = &s_modules; for (; l; l=l->next()) { ExtModReceiver *r = static_cast(l->get()); if (!r) continue; if (mod.find(r->scriptFile())) continue; mod.append(new String(r->scriptFile())); } s_mutex.unlock(); for (l = mod.skipNull(); l; l = l->skipNext()) Module::itemComplete(rval,l->get()->toString(),partWord); } else if (partLine.startsWith("external ")) { String scr = partLine.substr(9); if (!(scr.startSkip("restart") || scr.startSkip("stop"))) return false; if (scr.null() || (scr.find(' ') >= 0)) return false; ObjList arg; s_mutex.lock(); ObjList *l = &s_modules; for (; l; l=l->next()) { ExtModReceiver *r = static_cast(l->get()); if (!r || r->commandArg().null() || (r->scriptFile() != scr)) continue; if (arg.find(r->commandArg())) continue; arg.append(new String(r->commandArg())); } s_mutex.unlock(); for (l = arg.skipNull(); l; l = l->skipNext()) Module::itemComplete(rval,l->get()->toString(),partWord); } return false; } bool ExtModStatus::received(Message& msg) { const String& dest = msg[YSTRING("module")]; if (dest && (dest != YSTRING("external"))) return false; s_mutex.lock(); msg.retValue() << "name=" << __plugin.name() << ",type=misc;scripts=" << s_modules.count() << ",chans=" << s_chans.count() << "\r\n"; s_mutex.unlock(); return !dest.null(); } bool ExtModHelp::received(Message& msg) { const String& line = msg[YSTRING("line")]; if (line && (line != YSTRING("external"))) return false; msg.retValue() << " " << s_helpExternalCmd << "\r\n"; if (line) msg.retValue() << s_helpExternalInfo << "\r\n"; return !line.null(); } ExtListener::ExtListener(const char* name) : Thread("ExtMod Listener"), m_name(name), m_role(ExtModReceiver::RoleUnknown) { } bool ExtListener::init(const NamedList& sect) { String role(sect.getValue("role")); if (role == "global") m_role = ExtModReceiver::RoleGlobal; else if (role == "channel") m_role = ExtModReceiver::RoleChannel; else if (role) { Debug(DebugConf,"Unknown role '%s' of listener '%s'",role.c_str(),m_name.c_str()); return false; } String type(sect.getValue("type")); SocketAddr addr; if (type.null()) return false; else if (type == "unix") { String path(sect.getValue("path")); if (path.null() || !addr.assign(AF_UNIX) || !addr.host(path)) return false; File::remove(path); } else if (type == "tcp") { String host(sect.getValue("addr","127.0.0.1")); int port = sect.getIntValue("port"); if (host.null() || !port || !addr.assign(AF_INET) || !addr.host(host) || !addr.port(port)) return false; } else { Debug(DebugConf,"Unknown type '%s' of listener '%s'",type.c_str(),m_name.c_str()); return false; } if (!m_socket.create(addr.family(),SOCK_STREAM)) { Debug(DebugWarn,"Could not create socket for listener '%s' error %d: %s", m_name.c_str(),m_socket.error(),strerror(m_socket.error())); return false; } m_socket.setReuse(); if (!m_socket.bind(addr)) { Debug(DebugWarn,"Could not bind listener '%s' error %d: %s", m_name.c_str(),m_socket.error(),strerror(m_socket.error())); return false; } if (!m_socket.setBlocking(false) || !m_socket.listen()) return false; return startup(); } void ExtListener::run() { SocketAddr addr; for (;;) { Thread::idle(); if (Thread::check(false)) break; Socket* skt = m_socket.accept(addr); if (!skt) { if (m_socket.canRetry()) continue; Alarm("extmodule","socket",DebugWarn,"Error on accept(), shutting down ExtListener '%s'",m_name.c_str()); break; } String tmp = addr.host(); if (addr.port()) tmp << ":" << addr.port(); Debug(DebugInfo,"Listener '%s' got connection from '%s'",m_name.c_str(),tmp.c_str()); switch (m_role) { case ExtModReceiver::RoleUnknown: case ExtModReceiver::RoleGlobal: case ExtModReceiver::RoleChannel: ExtModReceiver::build(m_name,skt,0,m_role,tmp); break; default: Debug(DebugWarn,"Listener '%s' hit invalid role %d",m_name.c_str(),m_role); delete skt; } } } ExtListener* ExtListener::build(const char* name, const NamedList& sect) { if (null(name)) return 0; ExtListener* ext = new ExtListener(name); if (!ext->init(sect)) { Alarm("extmodule","config",DebugWarn,"Could not start listener '%s'",name); delete ext; ext = 0; } return ext; } ExtModulePlugin::ExtModulePlugin() : Plugin("extmodule"), m_handler(0) { Output("Loaded module ExtModule"); } ExtModulePlugin::~ExtModulePlugin() { Output("Unloading module ExtModule"); s_mutex.lock(); s_pluginSafe = false; s_modules.clear(); // the receivers destroyed above should also clear chans but better be sure s_chans.clear(); s_mutex.unlock(); } bool ExtModulePlugin::isBusy() const { Lock lock(s_mutex); return (s_chans.count() != 0); } void ExtModulePlugin::initialize() { Output("Initializing module ExtModule"); s_cfg = Engine::configFile("extmodule"); s_cfg.load(); s_maxQueue = s_cfg.getIntValue("general","maxqueue",DEF_MAXQUEUE,0,MAX_MAXQUEUE); s_timeout = s_cfg.getIntValue("general","timeout",MSG_TIMEOUT); s_timebomb = s_cfg.getBoolValue("general","timebomb",false); s_settime = s_cfg.getBoolValue("general","settime",false); s_trackName = s_cfg.getBoolValue("general","trackparam",false) ? name().c_str() : (const char*)0; int wf = s_cfg.getIntValue("general","waitflush",WAIT_FLUSH); if (wf < 1) wf = 1; else if (wf > 100) wf = 100; s_waitFlush = wf; if (!m_handler) { m_handler = new ExtModHandler("call.execute",s_cfg.getIntValue("general","priority",100)); Engine::install(m_handler); Engine::install(new ExtModCommand); Engine::install(new ExtModStatus); Engine::install(new ExtModHelp); NamedList *sect = 0; int n = s_cfg.sections(); for (int i = 0; i < n; i++) { sect = s_cfg.getSection(i); if (!sect) continue; String s(*sect); if (s.startSkip("listener",true) && s) ExtListener::build(s,*sect); } // start any scripts only after the listeners sect = s_cfg.getSection("scripts"); if (sect) { unsigned int len = sect->length(); for (unsigned int i=0; igetParam(i); if (n) { String arg = *n; Engine::runParams().replaceParams(arg); ExtModReceiver::build(n->name(),arg); } } } // and now start additional programs sect = s_cfg.getSection("execute"); if (sect) { unsigned int len = sect->length(); for (unsigned int i=0; igetParam(i); if (n) { String tmp = n->name(); String arg = *n; adjustPath(tmp); Engine::runParams().replaceParams(arg); if (tmp) runProgram(tmp,arg); } } } } } }; // anonymous namespace /* vi: set ts=8 sw=4 sts=4 noet: */