/* * Carla Pipe utils based on lv2fil UI code * Copyright (C) 2009 Nedko Arnaudov * Copyright (C) 2013 Filipe Coelho * * 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 2 of * the License, or 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. * * For a full copy of the GNU General Public License see the doc/GPL.txt file. */ #ifndef CARLA_PIPE_UTILS_HPP_INCLUDED #define CARLA_PIPE_UTILS_HPP_INCLUDED #define WAIT_START_TIMEOUT 3000 /* ms */ #define WAIT_ZOMBIE_TIMEOUT 3000 /* ms */ #define WAIT_STEP 100 /* ms */ #include "CarlaUtils.hpp" #include "CarlaString.hpp" #include #include #include #include #include // ----------------------------------------------------------------------- class CarlaPipeServer { protected: CarlaPipeServer() : fPipeRecv(-1), fPipeSend(-1), fPid(-1), fReading(false) { carla_debug("CarlaPipeServer::CarlaPipeServer()"); } // ------------------------------------------------------------------- public: virtual ~CarlaPipeServer() { carla_debug("CarlaPipeServer::~CarlaPipeServer()"); stop(); } void start(const char* const filename, const char* const arg1, const char* const arg2) { CARLA_SAFE_ASSERT_RETURN(filename != nullptr,); CARLA_SAFE_ASSERT_RETURN(arg1 != nullptr,); CARLA_SAFE_ASSERT_RETURN(arg2 != nullptr,); carla_debug("CarlaPipeServer::start(\"%s\", \"%s\", \"%s\"", filename, arg1, arg2); //---------------------------------------------------------------- const char* argv[5]; //---------------------------------------------------------------- // argv[0] => filename argv[0] = filename; //---------------------------------------------------------------- // argv[1-2] => args argv[1] = arg1; argv[2] = arg2; //---------------------------------------------------------------- // argv[3-4] => pipes int pipe1[2]; // written by host process, read by plugin UI process int pipe2[2]; // written by plugin UI process, read by host process if (pipe(pipe1) != 0) { fail("pipe1 creation failed"); return; } if (pipe(pipe2) != 0) { fail("pipe2 creation failed"); return; } char uiPipeRecv[100+1]; char uiPipeSend[100+1]; std::snprintf(uiPipeRecv, 100, "%d", pipe1[0]); /* [0] means reading end */ std::snprintf(uiPipeSend, 100, "%d", pipe2[1]); /* [1] means writting end */ uiPipeRecv[100] = '\0'; uiPipeSend[100] = '\0'; argv[3] = uiPipeRecv; // reading end argv[4] = uiPipeSend; // writting end //---------------------------------------------------------------- // fork int ret = -1; if ((! fork_exec(argv, &ret)) || ret == -1) { close(pipe1[0]); close(pipe1[1]); close(pipe2[0]); close(pipe2[1]); fail("fork_exec() failed"); return; } fPid = ret; /* fork duplicated the handles, close pipe ends that are used by the child process */ close(pipe1[0]); close(pipe2[1]); fPipeSend = pipe1[1]; /* [1] means writting end */ fPipeRecv = pipe2[0]; /* [0] means reading end */ fcntl(fPipeRecv, F_SETFL, fcntl(fPipeRecv, F_GETFL) | O_NONBLOCK); //---------------------------------------------------------------- // wait a while for child process to confirm it is alive char ch; for (int i=0; ;) { ssize_t ret2 = read(fPipeRecv, &ch, 1); switch (ret2) { case -1: if (errno == EAGAIN) { if (i < WAIT_START_TIMEOUT / WAIT_STEP) { carla_msleep(WAIT_STEP); i++; continue; } carla_stderr("we have waited for child with pid %d to appear for %.1f seconds and we are giving up", (int)fPid, (float)WAIT_START_TIMEOUT / 1000.0f); } else carla_stderr("read() failed: %s", strerror(errno)); break; case 1: if (ch == '\n') // success return; carla_stderr("read() wrong first char '%c'", ch); break; default: carla_stderr("read() returned %d", ret2); break; } break; } carla_stderr("force killing misbehaved child %d (start)", (int)fPid); if (kill(fPid, SIGKILL) == -1) { carla_stderr("kill() failed: %s (start)\n", strerror(errno)); } /* wait a while child to exit, we dont like zombie processes */ wait_child(fPid); } void stop() { carla_debug("CarlaPipeServer::stop()"); if (fPipeSend == -1 || fPipeRecv == -1 || fPid == -1) return; write(fPipeSend, "quit\n", 5); waitChildClose(); close(fPipeRecv); close(fPipeSend); fPipeRecv = -1; fPipeSend = -1; fPid = -1; } void idle() { char* locale = nullptr; for (;;) { char* const msg = readline(); if (msg == nullptr) break; if (locale == nullptr) { locale = strdup(setlocale(LC_NUMERIC, nullptr)); setlocale(LC_NUMERIC, "POSIX"); } fReading = true; msgReceived(msg); fReading = false; std::free(msg); } if (locale != nullptr) { setlocale(LC_NUMERIC, locale); std::free(locale); } } // ------------------------------------------------------------------- bool readNextLineAsBool(bool& value) { if (! fReading) return false; if (char* const msg = readline()) { value = (std::strcmp(msg, "true") == 0); std::free(msg); return true; } return false; } bool readNextLineAsInt(int& value) { if (! fReading) return false; if (char* const msg = readline()) { value = std::atoi(msg); std::free(msg); return true; } return false; } bool readNextLineAsFloat(float& value) { if (! fReading) return false; if (char* const msg = readline()) { bool ret = (std::sscanf(msg, "%f", &value) == 1); std::free(msg); return ret; } return false; } bool readNextLineAsString(char*& value) { if (! fReading) return false; if (char* const msg = readline()) { value = msg; return true; } return false; } void writeMsg(const char* const msg) { ::write(fPipeSend, msg, std::strlen(msg)); } void writeMsg(const char* const msg, size_t size) { ::write(fPipeSend, msg, size); } void writeAndFixMsg(const char* const msg) { const size_t size = std::strlen(msg); char smsg[size+1]; std::strcpy(smsg, msg); smsg[size-1] = '\n'; smsg[size] = '\0'; for (size_t i=0; i