This commit is contained in:
DDVTech 2021-09-10 23:44:31 +02:00 committed by Thulinma
parent 5b79f296d6
commit fccf66fba2
280 changed files with 56975 additions and 71885 deletions

View file

@ -1,7 +1,6 @@
/// \file controller.cpp
/// Contains all code for the controller executable.
#include <mist/util.h>
#include "controller_api.h"
#include "controller_capabilities.h"
#include "controller_connectors.h"
@ -10,6 +9,7 @@
#include "controller_storage.h"
#include "controller_streams.h"
#include <ctime>
#include <fstream> //for ram space check
#include <iostream>
#include <mist/auth.h>
#include <mist/config.h>
@ -21,12 +21,12 @@
#include <mist/stream.h>
#include <mist/timing.h>
#include <mist/tinythread.h>
#include <mist/util.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/statvfs.h> //for shm space check
#include <sys/wait.h>
#include <vector>
#include <sys/statvfs.h> //for shm space check
#include <fstream> //for ram space check
/*LTS-START*/
#include "controller_license.h"
#include "controller_limits.h"
@ -79,8 +79,7 @@ void statusMonitor(void *np){
{
tthread::lock_guard<tthread::mutex> guard(Controller::configMutex);
// checks online protocols, reports changes to status
if (Controller::CheckProtocols(Controller::Storage["config"]["protocols"],
Controller::capabilities)){
if (Controller::CheckProtocols(Controller::Storage["config"]["protocols"], Controller::capabilities)){
Controller::writeProtocols();
}
// checks stream statuses, reports changes to status
@ -181,16 +180,12 @@ int main_loop(int argc, char **argv){
"uplink",
JSON::fromString("{\"default\":\"\", \"arg\":\"string\", \"help\":\"MistSteward uplink host "
"and port.\", \"short\":\"U\", \"long\":\"uplink\"}")); /*LTS*/
Controller::conf.addOption("uplink-name",
JSON::fromString("{\"default\":\"" COMPILED_USERNAME
"\", \"arg\":\"string\", \"help\":\"MistSteward "
"uplink username.\", \"short\":\"N\", "
"\"long\":\"uplink-name\"}")); /*LTS*/
Controller::conf.addOption("uplink-pass",
JSON::fromString("{\"default\":\"" COMPILED_PASSWORD
"\", \"arg\":\"string\", \"help\":\"MistSteward "
"uplink password.\", \"short\":\"P\", "
"\"long\":\"uplink-pass\"}")); /*LTS*/
Controller::conf.addOption("uplink-name", JSON::fromString("{\"default\":\"" COMPILED_USERNAME "\", \"arg\":\"string\", \"help\":\"MistSteward "
"uplink username.\", \"short\":\"N\", "
"\"long\":\"uplink-name\"}")); /*LTS*/
Controller::conf.addOption("uplink-pass", JSON::fromString("{\"default\":\"" COMPILED_PASSWORD "\", \"arg\":\"string\", \"help\":\"MistSteward "
"uplink password.\", \"short\":\"P\", "
"\"long\":\"uplink-pass\"}")); /*LTS*/
Controller::conf.addOption(
"prometheus",
JSON::fromString("{\"long\":\"prometheus\", \"short\":\"S\", \"arg\":\"string\" "
@ -199,8 +194,7 @@ int main_loop(int argc, char **argv){
Controller::conf.parseArgs(argc, argv);
if (Controller::conf.getString("logfile") != ""){
// open logfile, dup stdout to logfile
int output =
open(Controller::conf.getString("logfile").c_str(), O_APPEND | O_CREAT | O_WRONLY, S_IRWXU);
int output = open(Controller::conf.getString("logfile").c_str(), O_APPEND | O_CREAT | O_WRONLY, S_IRWXU);
if (output < 0){
DEBUG_MSG(DLVL_ERROR, "Could not redirect output to %s: %s",
Controller::conf.getString("logfile").c_str(), strerror(errno));
@ -225,7 +219,7 @@ int main_loop(int argc, char **argv){
Controller::Storage = JSON::fromFile(Controller::conf.getString("configFile"));
{// spawn thread that reads stderr of process
std::string logPipe = Util::getTmpFolder()+"MstLog";
std::string logPipe = Util::getTmpFolder() + "MstLog";
if (mkfifo(logPipe.c_str(), S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH) != 0){
if (errno != EEXIST){
ERROR_MSG("Could not create log message pipe %s: %s", logPipe.c_str(), strerror(errno));
@ -233,34 +227,37 @@ int main_loop(int argc, char **argv){
}
int inFD = -1;
if ((inFD = open(logPipe.c_str(), O_RDONLY | O_NONBLOCK)) == -1){
ERROR_MSG("Could not open log message pipe %s: %s; falling back to unnamed pipe", logPipe.c_str(), strerror(errno));
ERROR_MSG("Could not open log message pipe %s: %s; falling back to unnamed pipe",
logPipe.c_str(), strerror(errno));
int pipeErr[2];
if (pipe(pipeErr) >= 0){
dup2(pipeErr[1], STDERR_FILENO); // cause stderr to write to the pipe
close(pipeErr[1]); // close the unneeded pipe file descriptor
//Start reading log messages from the unnamed pipe
Util::Procs::socketList.insert(pipeErr[0]); //Mark this FD as needing to be closed before forking
// Start reading log messages from the unnamed pipe
Util::Procs::socketList.insert(pipeErr[0]); // Mark this FD as needing to be closed before forking
tthread::thread msghandler(Controller::handleMsg, (void *)(((char *)0) + pipeErr[0]));
msghandler.detach();
}
}else{
//Set the read end to blocking mode
// Set the read end to blocking mode
int inFDflags = fcntl(inFD, F_GETFL, 0);
fcntl(inFD, F_SETFL, inFDflags & (~O_NONBLOCK));
//Start reading log messages from the named pipe
Util::Procs::socketList.insert(inFD); //Mark this FD as needing to be closed before forking
// Start reading log messages from the named pipe
Util::Procs::socketList.insert(inFD); // Mark this FD as needing to be closed before forking
tthread::thread msghandler(Controller::handleMsg, (void *)(((char *)0) + inFD));
msghandler.detach();
//Attempt to open and redirect log messages to named pipe
// Attempt to open and redirect log messages to named pipe
int outFD = -1;
if ((outFD = open(logPipe.c_str(), O_WRONLY)) == -1){
ERROR_MSG("Could not open log message pipe %s for writing! %s; falling back to standard error", logPipe.c_str(), strerror(errno));
ERROR_MSG(
"Could not open log message pipe %s for writing! %s; falling back to standard error",
logPipe.c_str(), strerror(errno));
}else{
dup2(outFD, STDERR_FILENO); // cause stderr to write to the pipe
close(outFD); // close the unneeded pipe file descriptor
}
}
setenv("MIST_CONTROL", "1", 0);//Signal in the environment that the controller handles all children
setenv("MIST_CONTROL", "1", 0); // Signal in the environment that the controller handles all children
}
if (Controller::conf.getOption("debug", true).size() > 1){
@ -277,12 +274,10 @@ int main_loop(int argc, char **argv){
Controller::Storage["config"]["controller"]["port"];
}
if (Controller::Storage["config"]["controller"]["interface"]){
Controller::conf.getOption("interface", true)[0u] =
Controller::Storage["config"]["controller"]["interface"];
Controller::conf.getOption("interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"];
}
if (Controller::Storage["config"]["controller"]["username"]){
Controller::conf.getOption("username", true)[0u] =
Controller::Storage["config"]["controller"]["username"];
Controller::conf.getOption("username", true)[0u] = Controller::Storage["config"]["controller"]["username"];
}
if (Controller::Storage["config"]["controller"].isMember("prometheus")){
if (Controller::Storage["config"]["controller"]["prometheus"]){
@ -313,7 +308,6 @@ int main_loop(int argc, char **argv){
createAccount(Controller::conf.getString("account"));
Controller::conf.activate(); // activate early, so threads aren't killed.
#if !defined(__CYGWIN__) && !defined(_WIN32)
{
uint64_t mem_total = 0, mem_free = 0, mem_bufcache = 0, shm_total = 0, shm_free = 0;
@ -323,8 +317,8 @@ int main_loop(int argc, char **argv){
while (meminfo.good()){
meminfo.getline(line, 300);
if (meminfo.fail()){
//empty lines? ignore them, clear flags, continue
if ( !meminfo.eof()){
// empty lines? ignore them, clear flags, continue
if (!meminfo.eof()){
meminfo.ignore();
meminfo.clear();
}
@ -341,26 +335,47 @@ int main_loop(int argc, char **argv){
IPC::sharedPage tmpCapa(SHM_CAPA, DEFAULT_CONF_PAGE_SIZE, false, false);
if (tmpCapa.mapped && tmpCapa.handle){
fstatvfs(tmpCapa.handle, &shmd);
shm_free = (shmd.f_bfree*shmd.f_frsize)/1024;
shm_total = (shmd.f_blocks*shmd.f_frsize)/1024;
shm_free = (shmd.f_bfree * shmd.f_frsize) / 1024;
shm_total = (shmd.f_blocks * shmd.f_frsize) / 1024;
}
if (mem_free+mem_bufcache < 1024*1024){
WARN_MSG("You have very little free RAM available (%" PRIu64 " MiB). While Mist will run just fine with this amount, do note that random crashes may occur should you ever run out of free RAM. Please be pro-active and keep an eye on the RAM usage!");
if (mem_free + mem_bufcache < 1024 * 1024){
WARN_MSG("You have very little free RAM available (%" PRIu64
" MiB). While Mist will run just fine with this amount, do note that random crashes "
"may occur should you ever run out of free RAM. Please be pro-active and keep an "
"eye on the RAM usage!");
}
if (shm_free < 1024*1024 && mem_total > 1024*1024*1.12){
WARN_MSG("You have very little shared memory available (%" PRIu64 " MiB). Mist heavily relies on shared memory: please ensure your shared memory is set to a high value, preferably ~95%% of your total available RAM.", shm_free/1024);
if (shm_free < 1024 * 1024 && mem_total > 1024 * 1024 * 1.12){
WARN_MSG("You have very little shared memory available (%" PRIu64
" MiB). Mist heavily relies on shared memory: please ensure your shared memory is "
"set to a high value, preferably ~95%% of your total available RAM.",
shm_free / 1024);
if (shm_total == 65536){
WARN_MSG("Tip: If you are using docker, e.g. add the `--shm-size=%" PRIu64 "m` parameter to your `docker run` command to fix this.", (uint64_t)(mem_total*0.95/1024));
WARN_MSG("Tip: If you are using docker, e.g. add the `--shm-size=%" PRIu64
"m` parameter to your `docker run` command to fix this.",
(uint64_t)(mem_total * 0.95 / 1024));
}else{
WARN_MSG("Tip: In most cases, you can change the shared memory size by running `mount -o remount,size=%" PRIu64 "m /dev/shm` as root. Doing this automatically every boot depends on your distribution: please check your distro's documentation for instructions.", (uint64_t)(mem_total*0.95/1024));
WARN_MSG("Tip: In most cases, you can change the shared memory size by running `mount -o "
"remount,size=%" PRIu64
"m /dev/shm` as root. Doing this automatically every boot depends on your "
"distribution: please check your distro's documentation for instructions.",
(uint64_t)(mem_total * 0.95 / 1024));
}
}else if (shm_total <= mem_total/2){
WARN_MSG("Your shared memory is half or less of your RAM (%" PRIu64 " / %" PRIu64 " MiB). Mist heavily relies on shared memory: please ensure your shared memory is set to a high value, preferably ~95%% of your total available RAM.", shm_total/1024, mem_total/1024);
}else if (shm_total <= mem_total / 2){
WARN_MSG("Your shared memory is half or less of your RAM (%" PRIu64 " / %" PRIu64
" MiB). Mist heavily relies on shared memory: please ensure your shared memory is "
"set to a high value, preferably ~95%% of your total available RAM.",
shm_total / 1024, mem_total / 1024);
if (shm_total == 65536){
WARN_MSG("Tip: If you are using docker, e.g. add the `--shm-size=%" PRIu64 "m` parameter to your `docker run` command to fix this.", (uint64_t)(mem_total*0.95/1024));
WARN_MSG("Tip: If you are using docker, e.g. add the `--shm-size=%" PRIu64
"m` parameter to your `docker run` command to fix this.",
(uint64_t)(mem_total * 0.95 / 1024));
}else{
WARN_MSG("Tip: In most cases, you can change the shared memory size by running `mount -o remount,size=%" PRIu64 "m /dev/shm` as root. Doing this automatically every boot depends on your distribution: please check your distro's documentation for instructions.", (uint64_t)(mem_total*0.95/1024));
WARN_MSG("Tip: In most cases, you can change the shared memory size by running `mount -o "
"remount,size=%" PRIu64
"m /dev/shm` as root. Doing this automatically every boot depends on your "
"distribution: please check your distro's documentation for instructions.",
(uint64_t)(mem_total * 0.95 / 1024));
}
}
}
@ -379,8 +394,7 @@ int main_loop(int argc, char **argv){
case 'y':{
// create account
std::string usr_string = "";
while (!(Controller::Storage.isMember("account") &&
Controller::Storage["account"].size() > 0) &&
while (!(Controller::Storage.isMember("account") && Controller::Storage["account"].size() > 0) &&
Controller::conf.is_active){
std::cout << "Please type in the username, a colon and a password in the following "
"format; username:password"
@ -391,13 +405,11 @@ int main_loop(int argc, char **argv){
createAccount(usr_string);
}
}break;
case 'a':
return 0; // abort bootup
case 'a': return 0; // abort bootup
case 't':{
createAccount("test:test");
if ((Controller::capabilities["connectors"].size()) &&
(!Controller::Storage.isMember("config") ||
!Controller::Storage["config"].isMember("protocols") ||
(!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") ||
Controller::Storage["config"]["protocols"].size() < 1)){
// create protocols
jsonForEach(Controller::capabilities["connectors"], it){
@ -414,13 +426,12 @@ int main_loop(int argc, char **argv){
}
// check for protocols
if ((Controller::capabilities["connectors"].size()) &&
(!Controller::Storage.isMember("config") ||
!Controller::Storage["config"].isMember("protocols") ||
(!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") ||
Controller::Storage["config"]["protocols"].size() < 1)){
std::string in_string = "";
while (yna(in_string) == 'x' && Controller::conf.is_active){
std::cout
<< "Protocols not set, do you want to enable default protocols? (y)es, (n)o, (a)bort: ";
std::cout << "Protocols not set, do you want to enable default protocols? (y)es, (n)o, "
"(a)bort: ";
std::cout.flush();
std::getline(std::cin, in_string);
if (yna(in_string) == 'y'){
@ -451,20 +462,17 @@ int main_loop(int argc, char **argv){
web_port + " and follow the instructions.");
}
// check for protocols
if (!Controller::Storage.isMember("config") ||
!Controller::Storage["config"].isMember("protocols") ||
if (!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") ||
Controller::Storage["config"]["protocols"].size() < 1){
Controller::Log(
"CONF",
"No protocols enabled, remember to set them up through the web interface on port " +
web_port + " or API.");
Controller::Log("CONF", "No protocols enabled, remember to set them up through the web "
"interface on port " +
web_port + " or API.");
}
// check for streams - regardless of logfile setting
if (!Controller::Storage.isMember("streams") || Controller::Storage["streams"].size() < 1){
Controller::Log(
"CONF",
"No streams configured, remember to set up streams through the web interface on port " +
web_port + " or API.");
Controller::Log("CONF", "No streams configured, remember to set up streams through the web "
"interface on port " +
web_port + " or API.");
}
}
@ -646,4 +654,3 @@ int main(int argc, char **argv){
}
return 0;
}

View file

@ -1,48 +1,46 @@
#include <dirent.h> //for browse API call
#include <sys/stat.h> //for browse API call
#include <fstream>
#include <mist/http_parser.h>
#include <mist/url.h>
#include <mist/auth.h>
#include <mist/stream.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/timing.h>
#include <mist/procs.h>
#include <mist/bitfields.h>
#include "controller_api.h"
#include "controller_capabilities.h"
#include "controller_connectors.h"
#include "controller_statistics.h"
#include "controller_storage.h"
#include "controller_streams.h"
#include "controller_connectors.h"
#include "controller_capabilities.h"
#include "controller_statistics.h"
#include <dirent.h> //for browse API call
#include <fstream>
#include <mist/auth.h>
#include <mist/bitfields.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <mist/procs.h>
#include <mist/stream.h>
#include <mist/timing.h>
#include <mist/url.h>
#include <sys/stat.h> //for browse API call
/*LTS-START*/
#include "controller_updater.h"
#include "controller_license.h"
#include "controller_limits.h"
#include "controller_push.h"
#include "controller_license.h"
#include "controller_updater.h"
/*LTS-END*/
/// Returns the challenge string for authentication, given the socket connection.
std::string getChallenge(Socket::Connection & conn){
std::string getChallenge(Socket::Connection &conn){
time_t Time = time(0);
tm tmptime;
tm * TimeInfo = localtime_r( &Time, &tmptime);
tm *TimeInfo = localtime_r(&Time, &tmptime);
std::stringstream Date;
Date << TimeInfo->tm_mday << "-" << TimeInfo->tm_mon << "-" << TimeInfo->tm_year + 1900;
return Secure::md5(Date.str().c_str() + conn.getHost());
}
/// Executes a single Playlist-based API command. Recurses if necessary.
static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines){
static void executePlsCommand(JSON::Value &cmd, std::deque<std::string> &lines){
if (!cmd.isArray() || !cmd.size()){
FAIL_MSG("Not a valid playlist API command: %s", cmd.toString().c_str());
return;
}
if (cmd[0u].isArray()){
jsonForEach(cmd, it){
executePlsCommand(*it, lines);
}
jsonForEach(cmd, it){executePlsCommand(*it, lines);}
return;
}
if (!cmd[0u].isString()){
@ -58,11 +56,9 @@ static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines
return;
}
if (cmd[0u].asStringRef() == "remove" && cmd.size() == 2 && cmd[1u].isString()){
const std::string & toRemove = cmd[1u].asStringRef();
const std::string &toRemove = cmd[1u].asStringRef();
for (std::deque<std::string>::iterator it = lines.begin(); it != lines.end(); ++it){
if ((*it) == toRemove){
(*it) = "";
}
if ((*it) == toRemove){(*it) = "";}
}
return;
}
@ -75,11 +71,9 @@ static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines
return;
}
if (cmd[0u].asStringRef() == "replace" && cmd.size() == 3 && cmd[1u].isString() && cmd[2u].isString()){
const std::string & toReplace = cmd[1u].asStringRef();
const std::string &toReplace = cmd[1u].asStringRef();
for (std::deque<std::string>::iterator it = lines.begin(); it != lines.end(); ++it){
if ((*it) == toReplace){
(*it) = cmd[2u].asStringRef();
}
if ((*it) == toReplace){(*it) = cmd[2u].asStringRef();}
}
return;
}
@ -93,11 +87,13 @@ static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines
///\return True on successfull authorization, false otherwise.
///
/// \api
/// To login, an `"authorize"` request must be sent. Since HTTP does not use persistent connections, you are required to re-sent authentication with every API request made. To prevent plaintext sending of the password, a random challenge string is sent first, and then the password is hashed together with this challenge string to create a one-time-use string to login with.
/// To login, an `"authorize"` request must be sent. Since HTTP does not use persistent connections,
/// you are required to re-sent authentication with every API request made. To prevent plaintext sending
/// of the password, a random challenge string is sent first, and then the password is hashed together with this challenge string to create a one-time-use string to login with.
/// If the user is not authorized, this request is the only request the server will respond to until properly authorized.
/// `"authorize"` requests take the form of:
/// ~~~~~~~~~~~~~~~{.js}
/// {
///{
/// //username to login as
/// "username": "test",
/// //hash of password to login with. Send empty value when no challenge for the hash is known yet.
@ -105,38 +101,40 @@ static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines
/// // MD5( MD5("secret") + challenge)
/// //Where "secret" is the plaintext password.
/// "password": ""
/// }
///}
/// ~~~~~~~~~~~~~~~
/// and are responded to as:
/// ~~~~~~~~~~~~~~~{.js}
/// {
///{
/// //current login status. Either "OK", "CHALL", "NOACC" or "ACC_MADE".
/// "status": "CHALL",
/// //Random value to be used in hashing the password.
/// "challenge": "abcdef1234567890"
/// }
///}
/// ~~~~~~~~~~~~~~~
/// The challenge string is sent for all statuses, except `"NOACC"`, where it is left out.
/// A status of `"OK"` means you are currently logged in and have access to all other API requests.
/// A status of `"CHALL"` means you are not logged in, and a challenge has been provided to login with.
/// A status of `"NOACC"` means there are no valid accounts to login with. In this case - and ONLY in this case - it is possible to create a initial login through the API itself. To do so, send a request as follows:
/// A status of `"NOACC"` means there are no valid accounts to login with. In this case - and ONLY
/// in this case - it is possible to create a initial login through the API itself. To do so, send a request as follows:
/// ~~~~~~~~~~~~~~~{.js}
/// {
///{
/// //username to create, as plain text
/// "new_username": "test",
/// //password to set, as plain text
/// "new_password": "secret"
/// }
///}
/// ~~~~~~~~~~~~~~~
/// Please note that this is NOT secure. At all. Never use this mechanism over a public network!
/// A status of `"ACC_MADE"` indicates the account was created successfully and can now be used to login as normal.
bool Controller::authorize(JSON::Value & Request, JSON::Value & Response, Socket::Connection & conn){
bool Controller::authorize(JSON::Value &Request, JSON::Value &Response, Socket::Connection &conn){
std::string Challenge = getChallenge(conn);
std::string retval;
if (Request.isMember("authorize") && Request["authorize"]["username"].asString() != ""){
std::string UserID = Request["authorize"]["username"];
if (Storage["account"].isMember(UserID)){
if (Secure::md5(Storage["account"][UserID]["password"].asString() + Challenge) == Request["authorize"]["password"].asString()){
if (Secure::md5(Storage["account"][UserID]["password"].asString() + Challenge) ==
Request["authorize"]["password"].asString()){
Response["authorize"]["status"] = "OK";
return true;
}
@ -147,57 +145,56 @@ bool Controller::authorize(JSON::Value & Request, JSON::Value & Response, Socket
}
Response["authorize"]["status"] = "CHALL";
Response["authorize"]["challenge"] = Challenge;
//the following is used to add the first account through the LSP
// the following is used to add the first account through the LSP
if (!Storage["account"]){
Response["authorize"]["status"] = "NOACC";
if (Request["authorize"]["new_username"] && Request["authorize"]["new_password"]){
//create account
// create account
Controller::Log("CONF", "Created account " + Request["authorize"]["new_username"].asString() + " through API");
Controller::Storage["account"][Request["authorize"]["new_username"].asString()]["password"] = Secure::md5(Request["authorize"]["new_password"].asString());
Controller::Storage["account"][Request["authorize"]["new_username"].asString()]["password"] =
Secure::md5(Request["authorize"]["new_password"].asString());
Response["authorize"]["status"] = "ACC_MADE";
}else{
Response["authorize"].removeMember("challenge");
}
}
return false;
}//Authorize
}// Authorize
class streamStat{
public:
streamStat(){
status = 0;
viewers = 0;
inputs = 0;
outputs = 0;
}
streamStat(const Util::RelAccX & rlx, uint64_t entry){
status = rlx.getInt("status", entry);
viewers = rlx.getInt("viewers", entry);
inputs = rlx.getInt("inputs", entry);
outputs = rlx.getInt("outputs", entry);
}
bool operator ==(const streamStat &b) const{
return (status == b.status && viewers == b.viewers && inputs == b.inputs && outputs == b.outputs);
}
bool operator !=(const streamStat &b) const{
return !(*this == b);
}
uint8_t status;
uint64_t viewers;
uint64_t inputs;
uint64_t outputs;
public:
streamStat(){
status = 0;
viewers = 0;
inputs = 0;
outputs = 0;
}
streamStat(const Util::RelAccX &rlx, uint64_t entry){
status = rlx.getInt("status", entry);
viewers = rlx.getInt("viewers", entry);
inputs = rlx.getInt("inputs", entry);
outputs = rlx.getInt("outputs", entry);
}
bool operator==(const streamStat &b) const{
return (status == b.status && viewers == b.viewers && inputs == b.inputs && outputs == b.outputs);
}
bool operator!=(const streamStat &b) const{return !(*this == b);}
uint8_t status;
uint64_t viewers;
uint64_t inputs;
uint64_t outputs;
};
void Controller::handleWebSocket(HTTP::Parser & H, Socket::Connection & C){
void Controller::handleWebSocket(HTTP::Parser &H, Socket::Connection &C){
std::string logs = H.GetVar("logs");
std::string accs = H.GetVar("accs");
bool doStreams = H.GetVar("streams").size();
HTTP::Websocket W(C, H);
if (!W){return;}
IPC::sharedPage shmLogs(SHM_STATE_LOGS, 1024*1024);
IPC::sharedPage shmAccs(SHM_STATE_ACCS, 1024*1024);
IPC::sharedPage shmStreams(SHM_STATE_STREAMS, 1024*1024);
IPC::sharedPage shmLogs(SHM_STATE_LOGS, 1024 * 1024);
IPC::sharedPage shmAccs(SHM_STATE_ACCS, 1024 * 1024);
IPC::sharedPage shmStreams(SHM_STATE_STREAMS, 1024 * 1024);
Util::RelAccX rlxStreams(shmStreams.mapped);
Util::RelAccX rlxLog(shmLogs.mapped);
Util::RelAccX rlxAccs(shmAccs.mapped);
@ -228,7 +225,9 @@ void Controller::handleWebSocket(HTTP::Parser & H, Socket::Connection & C){
if (accs.substr(0, 6) == "since:"){
uint64_t startAccs = JSON::Value(accs.substr(6)).asInt();
accsPos = rlxAccs.getDeleted();
while (accsPos < rlxAccs.getEndPos() && rlxAccs.getInt("time", accsPos) < startAccs){++accsPos;}
while (accsPos < rlxAccs.getEndPos() && rlxAccs.getInt("time", accsPos) < startAccs){
++accsPos;
}
}else{
uint64_t numAccs = JSON::Value(accs).asInt();
if (accsPos <= numAccs){
@ -270,7 +269,8 @@ void Controller::handleWebSocket(HTTP::Parser & H, Socket::Connection & C){
accsPos++;
}
if (doStreams){
for (std::map<std::string, streamStat>::iterator it = lastStrmStat.begin(); it != lastStrmStat.end(); ++it){
for (std::map<std::string, streamStat>::iterator it = lastStrmStat.begin();
it != lastStrmStat.end(); ++it){
strmRemove.insert(it->first);
}
uint64_t startPos = rlxStreams.getDeleted();
@ -307,33 +307,31 @@ void Controller::handleWebSocket(HTTP::Parser & H, Socket::Connection & C){
lastStrmStat.erase(strm);
}
}
if (!sent){
Util::sleep(500);
}
if (!sent){Util::sleep(500);}
}
}
/// Handles a single incoming API connection.
/// Assumes the connection is unauthorized and will allow for 4 requests without authorization before disconnecting.
int Controller::handleAPIConnection(Socket::Connection & conn){
//set up defaults
int Controller::handleAPIConnection(Socket::Connection &conn){
// set up defaults
unsigned int logins = 0;
bool authorized = false;
bool isLocal = false;
HTTP::Parser H;
//while connected and not past login attempt limit
// while connected and not past login attempt limit
while (conn && logins < 4){
if ((conn.spool() || conn.Received().size()) && H.Read(conn)){
//Are we local and not forwarded? Instant-authorized.
// Are we local and not forwarded? Instant-authorized.
if (!authorized && !H.hasHeader("X-Real-IP") && conn.isLocal()){
MEDIUM_MSG("Local API access automatically authorized");
isLocal = true;
authorized = true;
}
#ifdef NOAUTH
//If auth is disabled, always allow access.
#ifdef NOAUTH
// If auth is disabled, always allow access.
authorized = true;
#endif
#endif
if (!authorized && H.hasHeader("Authorization")){
std::string auth = H.GetHeader("Authorization");
if (auth.substr(0, 5) == "json "){
@ -347,7 +345,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
H.Clean();
H.body = "Please login first or provide a valid token authentication.";
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
H.SetHeader("WWW-Authenticate", "json "+req["authorize"].toString());
H.SetHeader("WWW-Authenticate", "json " + req["authorize"].toString());
H.SendResponse("403", "Not authorized", conn);
H.Clean();
continue;
@ -355,7 +353,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
}
}
}
//Catch websocket requests
// Catch websocket requests
if (H.url == "/ws"){
if (!authorized){
H.Clean();
@ -369,14 +367,14 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
H.Clean();
continue;
}
//Catch prometheus requests
// Catch prometheus requests
if (Controller::prometheus.size()){
if (H.url == "/"+Controller::prometheus){
if (H.url == "/" + Controller::prometheus){
handlePrometheus(H, conn, PROMETHEUS_TEXT);
H.Clean();
continue;
}
if (H.url.substr(0, Controller::prometheus.size()+6) == "/"+Controller::prometheus+".json"){
if (H.url.substr(0, Controller::prometheus.size() + 6) == "/" + Controller::prometheus + ".json"){
handlePrometheus(H, conn, PROMETHEUS_JSON);
H.Clean();
continue;
@ -384,26 +382,24 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
}
JSON::Value Response;
JSON::Value Request = JSON::fromString(H.GetVar("command"));
//invalid request? send the web interface, unless requested as "/api"
if ( !Request.isObject() && H.url != "/api" && H.url != "/api2"){
#include "server.html.h"
// invalid request? send the web interface, unless requested as "/api"
if (!Request.isObject() && H.url != "/api" && H.url != "/api2"){
#include "server.html.h"
H.Clean();
H.SetHeader("Content-Type", "text/html");
H.SetHeader("X-Info", "To force an API response, request the file /api");
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
H.SetHeader("Content-Length", server_html_len);
H.SetHeader("X-UA-Compatible","IE=edge;chrome=1");
H.SetHeader("X-UA-Compatible", "IE=edge;chrome=1");
H.SendResponse("200", "OK", conn);
conn.SendNow(server_html, server_html_len);
H.Clean();
break;
}
if (H.url == "/api2"){
Request["minimal"] = true;
}
{//lock the config mutex here - do not unlock until done processing
if (H.url == "/api2"){Request["minimal"] = true;}
{// lock the config mutex here - do not unlock until done processing
tthread::lock_guard<tthread::mutex> guard(configMutex);
//if already authorized, do not re-check for authorization
// if already authorized, do not re-check for authorization
if (authorized && Storage["account"]){
Response["authorize"]["status"] = "OK";
if (isLocal){Response["authorize"]["local"] = true;}
@ -412,20 +408,16 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
}
if (authorized){
handleAPICommands(Request, Response);
}else{//unauthorized
Util::sleep(1000);//sleep a second to prevent bruteforcing
}else{// unauthorized
Util::sleep(1000); // sleep a second to prevent bruteforcing
logins++;
}
Controller::checkServerLimits(); /*LTS*/
}//config mutex lock
//send the response, either normally or through JSONP callback.
}// config mutex lock
// send the response, either normally or through JSONP callback.
std::string jsonp = "";
if (H.GetVar("callback") != ""){
jsonp = H.GetVar("callback");
}
if (H.GetVar("jsonp") != ""){
jsonp = H.GetVar("jsonp");
}
if (H.GetVar("callback") != ""){jsonp = H.GetVar("callback");}
if (H.GetVar("jsonp") != ""){jsonp = H.GetVar("jsonp");}
H.Clean();
H.SetHeader("Content-Type", "text/javascript");
H.setCORSHeaders();
@ -436,12 +428,12 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
}
H.SendResponse("200", "OK", conn);
H.Clean();
}//if HTTP request received
}//while connected
}// if HTTP request received
}// while connected
return 0;
}
void Controller::handleUDPAPI(void * np){
void Controller::handleUDPAPI(void *np){
Socket::UDPConnection uSock(true);
if (!uSock.bind(UDP_API_PORT, UDP_API_HOST)){
FAIL_MSG("Could not open local API UDP socket - not all functionality will be available");
@ -472,10 +464,8 @@ void Controller::handleUDPAPI(void * np){
/// Local-only helper function that checks for duplicate protocols and removes them
static void removeDuplicateProtocols(){
JSON::Value & P = Controller::Storage["config"]["protocols"];
jsonForEach(P, it){
it->removeNullMembers();
}
JSON::Value &P = Controller::Storage["config"]["protocols"];
jsonForEach(P, it){it->removeNullMembers();}
std::set<std::string> ignores;
ignores.insert("online");
bool reloop = true;
@ -495,13 +485,13 @@ static void removeDuplicateProtocols(){
}
}
void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response){
void Controller::handleAPICommands(JSON::Value &Request, JSON::Value &Response){
/*LTS-START*/
//These are only used internally. We abort further processing if encountered.
// These are only used internally. We abort further processing if encountered.
if (Request.isMember("trigger_stat")){
JSON::Value & tStat = Request["trigger_stat"];
JSON::Value &tStat = Request["trigger_stat"];
if (tStat.isMember("name") && tStat.isMember("ms")){
Controller::triggerLog & tLog = Controller::triggerStats[tStat["name"].asStringRef()];
Controller::triggerLog &tLog = Controller::triggerStats[tStat["name"].asStringRef()];
tLog.totalCount++;
tLog.ms += tStat["ms"].asInt();
if (!tStat.isMember("ok") || !tStat["ok"].asBool()){tLog.failCount++;}
@ -513,14 +503,14 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
return;
}
/*LTS-END*/
//Parse config and streams from the request.
// Parse config and streams from the request.
if (Request.isMember("config") && Request["config"].isObject()){
const JSON::Value & in = Request["config"];
JSON::Value & out = Controller::Storage["config"];
const JSON::Value &in = Request["config"];
JSON::Value &out = Controller::Storage["config"];
if (in.isMember("debug")){
out["debug"] = in["debug"];
if (Util::Config::printDebugLevel != (out["debug"].isInt()?out["debug"].asInt():DEBUG)){
Util::Config::printDebugLevel = (out["debug"].isInt()?out["debug"].asInt():DEBUG);
if (Util::Config::printDebugLevel != (out["debug"].isInt() ? out["debug"].asInt() : DEBUG)){
Util::Config::printDebugLevel = (out["debug"].isInt() ? out["debug"].asInt() : DEBUG);
INFO_MSG("Debug level set to %u", Util::Config::printDebugLevel);
}
}
@ -532,12 +522,8 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
out["trustedproxy"] = in["trustedproxy"];
Controller::normalizeTrustedProxies(out["trustedproxy"]);
}
if (in.isMember("controller")){
out["controller"] = in["controller"];
}
if (in.isMember("serverid")){
out["serverid"] = in["serverid"];
}
if (in.isMember("controller")){out["controller"] = in["controller"];}
if (in.isMember("serverid")){out["serverid"] = in["serverid"];}
if (in.isMember("triggers")){
out["triggers"] = in["triggers"];
if (!out["triggers"].isObject()){
@ -545,9 +531,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
}else{
jsonForEach(out["triggers"], it){
if (it->isArray()){
jsonForEach((*it), jt){
jt->removeNullMembers();
}
jsonForEach((*it), jt){jt->removeNullMembers();}
}
}
}
@ -560,9 +544,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
out["prometheus"] = in["prometheus"];
Controller::prometheus = out["prometheus"].asStringRef();
}
if (in.isMember("defaultStream")){
out["defaultStream"] = in["defaultStream"];
}
if (in.isMember("defaultStream")){out["defaultStream"] = in["defaultStream"];}
}
if (Request.isMember("bandwidth")){
if (Request["bandwidth"].isObject()){
@ -583,94 +565,72 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
Controller::AddStreams(Request["addstream"], Controller::Storage["streams"]);
}
if (Request.isMember("deletestream")){
//if array, delete all elements
//if object, delete all entries
//if string, delete just the one
// if array, delete all elements
// if object, delete all entries
// if string, delete just the one
if (Request["deletestream"].isString()){
Controller::deleteStream(Request["deletestream"].asStringRef(), Controller::Storage["streams"]);
Controller::deleteStream(Request["deletestream"].asStringRef(),
Controller::Storage["streams"]);
}
if (Request["deletestream"].isArray()){
jsonForEach(Request["deletestream"], it){
Controller::deleteStream(it->asStringRef(), Controller::Storage["streams"]);
Controller::deleteStream(it->asStringRef(), Controller::Storage["streams"]);
}
}
if (Request["deletestream"].isObject()){
jsonForEach(Request["deletestream"], it){
Controller::deleteStream(it.key(), Controller::Storage["streams"]);
Controller::deleteStream(it.key(), Controller::Storage["streams"]);
}
}
}
if (Request.isMember("deletestreamsource")){
//if array, delete all elements
//if object, delete all entries
//if string, delete just the one
// if array, delete all elements
// if object, delete all entries
// if string, delete just the one
if (Request["deletestreamsource"].isString()){
switch (Controller::deleteStream(Request["deletestreamsource"].asStringRef(), Controller::Storage["streams"], true)){
case 0:
Response["deletestreamsource"] = "0: No action taken";
break;
case 1:
Response["deletestreamsource"] = "1: Source file deleted";
break;
case 2:
Response["deletestreamsource"] = "2: Source file and dtsh deleted";
break;
case -1:
Response["deletestreamsource"] = "-1: Stream deleted, source remains";
break;
case -2:
Response["deletestreamsource"] = "-2: Stream and source file deleted";
break;
case -3:
Response["deletestreamsource"] = "-3: Stream, source file and dtsh deleted";
break;
switch (Controller::deleteStream(Request["deletestreamsource"].asStringRef(),
Controller::Storage["streams"], true)){
case 0: Response["deletestreamsource"] = "0: No action taken"; break;
case 1: Response["deletestreamsource"] = "1: Source file deleted"; break;
case 2: Response["deletestreamsource"] = "2: Source file and dtsh deleted"; break;
case -1: Response["deletestreamsource"] = "-1: Stream deleted, source remains"; break;
case -2: Response["deletestreamsource"] = "-2: Stream and source file deleted"; break;
case -3: Response["deletestreamsource"] = "-3: Stream, source file and dtsh deleted"; break;
}
}
if (Request["deletestreamsource"].isArray()){
jsonForEach(Request["deletestreamsource"], it){
switch (Controller::deleteStream(it->asStringRef(), Controller::Storage["streams"], true)){
case 0:
Response["deletestreamsource"][it.num()] = "0: No action taken";
break;
case 1:
Response["deletestreamsource"][it.num()] = "1: Source file deleted";
break;
case 2:
Response["deletestreamsource"][it.num()] = "2: Source file and dtsh deleted";
break;
case -1:
Response["deletestreamsource"][it.num()] = "-1: Stream deleted, source remains";
break;
case -2:
Response["deletestreamsource"][it.num()] = "-2: Stream and source file deleted";
break;
case -3:
Response["deletestreamsource"][it.num()] = "-3: Stream, source file and dtsh deleted";
break;
case 0: Response["deletestreamsource"][it.num()] = "0: No action taken"; break;
case 1: Response["deletestreamsource"][it.num()] = "1: Source file deleted"; break;
case 2: Response["deletestreamsource"][it.num()] = "2: Source file and dtsh deleted"; break;
case -1:
Response["deletestreamsource"][it.num()] = "-1: Stream deleted, source remains";
break;
case -2:
Response["deletestreamsource"][it.num()] = "-2: Stream and source file deleted";
break;
case -3:
Response["deletestreamsource"][it.num()] = "-3: Stream, source file and dtsh deleted";
break;
}
}
}
if (Request["deletestreamsource"].isObject()){
jsonForEach(Request["deletestreamsource"], it){
switch (Controller::deleteStream(it.key(), Controller::Storage["streams"], true)){
case 0:
Response["deletestreamsource"][it.key()] = "0: No action taken";
break;
case 1:
Response["deletestreamsource"][it.key()] = "1: Source file deleted";
break;
case 2:
Response["deletestreamsource"][it.key()] = "2: Source file and dtsh deleted";
break;
case -1:
Response["deletestreamsource"][it.key()] = "-1: Stream deleted, source remains";
break;
case -2:
Response["deletestreamsource"][it.key()] = "-2: Stream and source file deleted";
break;
case -3:
Response["deletestreamsource"][it.key()] = "-3: Stream, source file and dtsh deleted";
break;
case 0: Response["deletestreamsource"][it.key()] = "0: No action taken"; break;
case 1: Response["deletestreamsource"][it.key()] = "1: Source file deleted"; break;
case 2: Response["deletestreamsource"][it.key()] = "2: Source file and dtsh deleted"; break;
case -1:
Response["deletestreamsource"][it.key()] = "-1: Stream deleted, source remains";
break;
case -2:
Response["deletestreamsource"][it.key()] = "-2: Stream and source file deleted";
break;
case -3:
Response["deletestreamsource"][it.key()] = "-3: Stream, source file and dtsh deleted";
break;
}
}
}
@ -699,18 +659,14 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
break;
}
}
if (add){
newProtocols.append(*it);
}
if (add){newProtocols.append(*it);}
}
Controller::Storage["config"]["protocols"] = newProtocols;
}
if (Request["deleteprotocol"].isObject()){
JSON::Value newProtocols;
jsonForEach(Controller::Storage["config"]["protocols"], it){
if (!(*it).compareExcept(Request["deleteprotocol"], ignores)){
newProtocols.append(*it);
}
if (!(*it).compareExcept(Request["deleteprotocol"], ignores)){newProtocols.append(*it);}
}
Controller::Storage["config"]["protocols"] = newProtocols;
}
@ -721,7 +677,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
if (Request["updateprotocol"].isArray() && Request["updateprotocol"].size() == 2){
jsonForEach(Controller::Storage["config"]["protocols"], it){
if ((*it).compareExcept(Request["updateprotocol"][0u], ignores)){
//If the connector type didn't change, mark it as needing a reload
// If the connector type didn't change, mark it as needing a reload
if ((*it)["connector"] == Request["updateprotocol"][1u]["connector"]){
reloadProtocol(it.num());
}
@ -739,32 +695,29 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
Response["capabilities"] = capabilities;
}
if(Request.isMember("browse")){
if(Request["browse"] == ""){
Request["browse"] = ".";
}
if (Request.isMember("browse")){
if (Request["browse"] == ""){Request["browse"] = ".";}
DIR *dir;
struct dirent *ent;
struct stat filestat;
char* rpath = realpath(Request["browse"].asString().c_str(),0);
if(rpath == NULL){
char *rpath = realpath(Request["browse"].asString().c_str(), 0);
if (rpath == NULL){
Response["browse"]["path"].append(Request["browse"].asString());
}else{
Response["browse"]["path"].append(rpath);//Request["browse"].asString());
if ((dir = opendir (Request["browse"].asString().c_str())) != NULL) {
while ((ent = readdir (dir)) != NULL) {
if(strcmp(ent->d_name,".")!=0 && strcmp(ent->d_name,"..")!=0 ){
Response["browse"]["path"].append(rpath); // Request["browse"].asString());
if ((dir = opendir(Request["browse"].asString().c_str())) != NULL){
while ((ent = readdir(dir)) != NULL){
if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0){
std::string filepath = Request["browse"].asString() + "/" + std::string(ent->d_name);
if (stat( filepath.c_str(), &filestat )) continue;
if (S_ISDIR( filestat.st_mode)){
if (stat(filepath.c_str(), &filestat)) continue;
if (S_ISDIR(filestat.st_mode)){
Response["browse"]["subdirectories"].append(ent->d_name);
}else{
Response["browse"]["files"].append(ent->d_name);
}
}
}
closedir (dir);
closedir(dir);
}
}
free(rpath);
@ -781,18 +734,24 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
ERROR_MSG("Playlist API call requires object payload, no object given");
}else{
jsonForEach(Request["playlist"], it){
if (!Controller::Storage["streams"].isMember(it.key()) || !Controller::Storage["streams"][it.key()].isMember("source")){
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' not configured", it.key().c_str());
if (!Controller::Storage["streams"].isMember(it.key()) ||
!Controller::Storage["streams"][it.key()].isMember("source")){
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' not configured",
it.key().c_str());
}else{
std::string src = Controller::Storage["streams"][it.key()]["source"].asString();
if (src.substr(src.size() - 4) != ".pls"){
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' is not playlist-based", it.key().c_str());
FAIL_MSG(
"Playlist API call (partially) not executed: stream '%s' is not playlist-based",
it.key().c_str());
}else{
bool readFirst = true;
struct stat fileinfo;
if (stat(src.c_str(), &fileinfo) != 0){
if (errno == EACCES){
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' playlist '%s' cannot be accessed (no file permissions)", it.key().c_str(), src.c_str());
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' playlist '%s' "
"cannot be accessed (no file permissions)",
it.key().c_str(), src.c_str());
break;
}
if (errno == ENOENT){
@ -804,24 +763,24 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
if (readFirst){
std::ifstream plsRead(src.c_str());
if (!plsRead.good()){
FAIL_MSG("Playlist (%s) for stream '%s' could not be opened for reading; aborting command(s)", src.c_str(), it.key().c_str());
FAIL_MSG("Playlist (%s) for stream '%s' could not be opened for reading; aborting "
"command(s)",
src.c_str(), it.key().c_str());
break;
}
std::string line;
do {
do{
std::getline(plsRead, line);
if (line.size() || plsRead.good()){lines.push_back(line);}
} while(plsRead.good());
}while (plsRead.good());
}
unsigned int plsNo = 0;
for (std::deque<std::string>::iterator plsIt = lines.begin(); plsIt != lines.end(); ++plsIt){
MEDIUM_MSG("Before playlist command item %u: %s", plsNo, plsIt->c_str());
++plsNo;
}
if (!it->isBool()){
executePlsCommand(*it, lines);
}
JSON::Value & outPls = Response["playlist"][it.key()];
if (!it->isBool()){executePlsCommand(*it, lines);}
JSON::Value &outPls = Response["playlist"][it.key()];
std::ofstream plsOutFile(src.c_str(), std::ios_base::trunc);
if (!plsOutFile.good()){
FAIL_MSG("Could not open playlist for writing: %s", src.c_str());
@ -832,9 +791,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
MEDIUM_MSG("After playlist command item %u: %s", plsNo, plsIt->c_str());
++plsNo;
outPls.append(*plsIt);
if (plsNo < lines.size() || (*plsIt).size()){
plsOutFile << (*plsIt) << "\n";
}
if (plsNo < lines.size() || (*plsIt).size()){plsOutFile << (*plsIt) << "\n";}
}
}
}
@ -848,32 +805,30 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
}
if (Request.isMember("ui_settings")){
if (Request["ui_settings"].isObject()){
Storage["ui_settings"] = Request["ui_settings"];
}
if (Request["ui_settings"].isObject()){Storage["ui_settings"] = Request["ui_settings"];}
Response["ui_settings"] = Storage["ui_settings"];
}
/*LTS-START*/
///
///
/// \api
/// LTS builds will always include an `"LTS"` response, set to 1.
///
///
Response["LTS"] = 1;
///
/// \api
/// `"autoupdate"` requests (LTS-only) will cause MistServer to apply a rolling update to itself, and are not responded to.
///
#ifdef UPDATER
if (Request.isMember("autoupdate")){
Controller::checkUpdates();
}
///
/// \api
/// `"autoupdate"` requests (LTS-only) will cause MistServer to apply a rolling update to itself, and are not responded to.
///
#ifdef UPDATER
if (Request.isMember("autoupdate")){Controller::checkUpdates();}
if (Request.isMember("update") || Request.isMember("checkupdate") || Request.isMember("autoupdate")){
Controller::insertUpdateInfo(Response["update"]);
}
#endif
#endif
/*LTS-END*/
if (!Request.isMember("minimal") || Request.isMember("streams") || Request.isMember("addstream") || Request.isMember("deletestream")){
if (!Request.isMember("streams") && (Request.isMember("addstream") || Request.isMember("deletestream"))){
if (!Request.isMember("minimal") || Request.isMember("streams") ||
Request.isMember("addstream") || Request.isMember("deletestream")){
if (!Request.isMember("streams") &&
(Request.isMember("addstream") || Request.isMember("deletestream"))){
Response["streams"]["incomplete list"] = 1u;
if (Request.isMember("addstream")){
jsonForEach(Request["addstream"], jit){
@ -886,24 +841,22 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
Response["streams"] = Controller::Storage["streams"];
}
}
//sent current configuration, if not minimal or was changed/requested
// sent current configuration, if not minimal or was changed/requested
if (!Request.isMember("minimal") || Request.isMember("config")){
Response["config"] = Controller::Storage["config"];
Response["config"]["iid"] = instanceId;
Response["config"]["version"] = PACKAGE_VERSION " " RELEASE;
/*LTS-START*/
#ifdef LICENSING
/*LTS-START*/
#ifdef LICENSING
Response["config"]["license"] = getLicense();
#endif
#endif
/*LTS-END*/
//add required data to the current unix time to the config, for syncing reasons
// add required data to the current unix time to the config, for syncing reasons
Response["config"]["time"] = Util::epoch();
if ( !Response["config"].isMember("serverid")){
Response["config"]["serverid"] = "";
}
if (!Response["config"].isMember("serverid")){Response["config"]["serverid"] = "";}
}
//sent any available logs and statistics
///
// sent any available logs and statistics
///
/// \api
/// `"log"` responses are always sent, and cannot be requested:
/// ~~~~~~~~~~~~~~~{.js}
@ -911,22 +864,20 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
/// [
/// 1398978357, //unix timestamp of this log message
/// "CONF", //shortcode indicating the type of log message
/// "Starting connector: {\"connector\":\"HTTP\"}" //string containing the log message itself
/// "Starting connector:{\"connector\":\"HTTP\"}" //string containing the log message itself
/// ],
/// //the above structure repeated for all logs
/// ]
/// ~~~~~~~~~~~~~~~
/// It's possible to clear the stored logs by sending an empty `"clearstatlogs"` request.
///
///
if (Request.isMember("clearstatlogs") || Request.isMember("log") || !Request.isMember("minimal")){
tthread::lock_guard<tthread::mutex> guard(logMutex);
if (!Request.isMember("minimal") || Request.isMember("log")){
Response["log"] = Controller::Storage["log"];
}
//clear log if requested
if (Request.isMember("clearstatlogs")){
Controller::Storage["log"].null();
}
// clear log if requested
if (Request.isMember("clearstatlogs")){Controller::Storage["log"].null();}
}
if (Request.isMember("clients")){
if (Request["clients"].isArray()){
@ -991,9 +942,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
if (Request.isMember("stop_sessions")){
if (Request["stop_sessions"].isArray() || Request["stop_sessions"].isObject()){
jsonForEach(Request["stop_sessions"], it){
Controller::sessions_shutdown(it);
}
jsonForEach(Request["stop_sessions"], it){Controller::sessions_shutdown(it);}
}else{
Controller::sessions_shutdown(Request["stop_sessions"].asStringRef());
}
@ -1001,9 +950,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
if (Request.isMember("stop_sessid")){
if (Request["stop_sessid"].isArray() || Request["stop_sessid"].isObject()){
jsonForEach(Request["stop_sessid"], it){
Controller::sessId_shutdown(it->asStringRef());
}
jsonForEach(Request["stop_sessid"], it){Controller::sessId_shutdown(it->asStringRef());}
}else{
Controller::sessId_shutdown(Request["stop_sessid"].asStringRef());
}
@ -1011,9 +958,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
if (Request.isMember("stop_tag")){
if (Request["stop_tag"].isArray() || Request["stop_tag"].isObject()){
jsonForEach(Request["stop_tag"], it){
Controller::tag_shutdown(it->asStringRef());
}
jsonForEach(Request["stop_tag"], it){Controller::tag_shutdown(it->asStringRef());}
}else{
Controller::tag_shutdown(Request["stop_tag"].asStringRef());
}
@ -1027,7 +972,6 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
}
}
if (Request.isMember("push_start")){
std::string stream;
std::string target;
@ -1053,29 +997,21 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
}
}
if (Request.isMember("push_list")){
Controller::listPush(Response["push_list"]);
}
if (Request.isMember("push_list")){Controller::listPush(Response["push_list"]);}
if (Request.isMember("push_stop")){
if (Request["push_stop"].isArray()){
jsonForEach(Request["push_stop"], it){
Controller::stopPush(it->asInt());
}
jsonForEach(Request["push_stop"], it){Controller::stopPush(it->asInt());}
}else{
Controller::stopPush(Request["push_stop"].asInt());
}
}
if (Request.isMember("push_auto_add")){
Controller::addPush(Request["push_auto_add"]);
}
if (Request.isMember("push_auto_add")){Controller::addPush(Request["push_auto_add"]);}
if (Request.isMember("push_auto_remove")){
if (Request["push_auto_remove"].isArray()){
jsonForEach(Request["push_auto_remove"], it){
Controller::removePush(*it);
}
jsonForEach(Request["push_auto_remove"], it){Controller::removePush(*it);}
}else{
Controller::removePush(Request["push_auto_remove"]);
}
@ -1089,8 +1025,6 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
Controller::pushSettings(Request["push_settings"], Response["push_settings"]);
}
Controller::writeConfig();
Controller::configChanged = false;
}

View file

@ -1,12 +1,12 @@
#include <mist/socket.h>
#include <mist/json.h>
#include <mist/websocket.h>
#include <mist/http_parser.h>
#include <mist/json.h>
#include <mist/socket.h>
#include <mist/websocket.h>
namespace Controller {
bool authorize(JSON::Value & Request, JSON::Value & Response, Socket::Connection & conn);
int handleAPIConnection(Socket::Connection & conn);
void handleAPICommands(JSON::Value & Request, JSON::Value & Response);
void handleWebSocket(HTTP::Parser & H, Socket::Connection & C);
void handleUDPAPI(void * np);
}
namespace Controller{
bool authorize(JSON::Value &Request, JSON::Value &Response, Socket::Connection &conn);
int handleAPIConnection(Socket::Connection &conn);
void handleAPICommands(JSON::Value &Request, JSON::Value &Response);
void handleWebSocket(HTTP::Parser &H, Socket::Connection &C);
void handleUDPAPI(void *np);
}// namespace Controller

View file

@ -1,21 +1,21 @@
#include "controller_capabilities.h"
#include <fstream>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/procs.h>
#include <set>
#include <stdio.h>
#include <string.h>
#include <fstream>
#include <set>
#include <mist/defines.h>
#include <mist/config.h>
#include <mist/procs.h>
#include "controller_capabilities.h"
///\brief Holds everything unique to the controller.
namespace Controller {
namespace Controller{
JSON::Value capabilities;
//Converter::Converter * myConverter = 0;
///Generate list of available triggers, storing in global 'capabilities' JSON::Value.
// Converter::Converter * myConverter = 0;
/// Generate list of available triggers, storing in global 'capabilities' JSON::Value.
void checkAvailTriggers(){
JSON::Value & trgs = capabilities["triggers"];
JSON::Value &trgs = capabilities["triggers"];
trgs["SYSTEM_START"]["when"] = "After MistServer boot";
trgs["SYSTEM_START"]["stream_specific"] = false;
trgs["SYSTEM_START"]["payload"] = "";
@ -50,19 +50,24 @@ namespace Controller {
trgs["STREAM_CONFIG"]["stream_specific"] = true;
trgs["STREAM_CONFIG"]["payload"] = "stream name (string)\nnew stream configuration (JSON)";
trgs["STREAM_CONFIG"]["response"] = "always";
trgs["STREAM_CONFIG"]["response_action"] = "If false, rejects new configuration and reverts to current configuration.";
trgs["STREAM_CONFIG"]["response_action"] =
"If false, rejects new configuration and reverts to current configuration.";
trgs["STREAM_REMOVE"]["when"] = "Before an existing stream is removed";
trgs["STREAM_REMOVE"]["stream_specific"] = true;
trgs["STREAM_REMOVE"]["payload"] = "stream name (string)";
trgs["STREAM_REMOVE"]["response"] = "always";
trgs["STREAM_REMOVE"]["response_action"] = "If false, prevents removal and reverts to current configuration.";
trgs["STREAM_REMOVE"]["response_action"] =
"If false, prevents removal and reverts to current configuration.";
trgs["STREAM_SOURCE"]["when"] = "When a stream's source setting is loaded";
trgs["STREAM_SOURCE"]["stream_specific"] = true;
trgs["STREAM_SOURCE"]["payload"] = "stream name (string)";
trgs["STREAM_SOURCE"]["response"] = "when-blocking";
trgs["STREAM_SOURCE"]["response_action"] = "A non-empty response will set the stream source to the response value. An empty response will cause the stream source to not be changed from the normally configured stream source.";
trgs["STREAM_SOURCE"]["response_action"] =
"A non-empty response will set the stream source to the response value. An empty response "
"will cause the stream source to not be changed from the normally configured stream "
"source.";
trgs["STREAM_LOAD"]["when"] = "Before a stream input is loaded";
trgs["STREAM_LOAD"]["stream_specific"] = true;
@ -80,11 +85,13 @@ namespace Controller {
trgs["STREAM_UNLOAD"]["stream_specific"] = true;
trgs["STREAM_UNLOAD"]["payload"] = "stream name (string)\ninput type (string)";
trgs["STREAM_UNLOAD"]["response"] = "always";
trgs["STREAM_UNLOAD"]["response_action"] = "If false, aborts the unload and keeps the stream loaded.";
trgs["STREAM_UNLOAD"]["response_action"] =
"If false, aborts the unload and keeps the stream loaded.";
trgs["STREAM_PUSH"]["when"] = "Before an incoming push is accepted";
trgs["STREAM_PUSH"]["stream_specific"] = true;
trgs["STREAM_PUSH"]["payload"] = "stream name (string)\nconnection address (string)\nconnector (string)\nrequest url (string)";
trgs["STREAM_PUSH"]["payload"] = "stream name (string)\nconnection address (string)\nconnector "
"(string)\nrequest url (string)";
trgs["STREAM_PUSH"]["response"] = "always";
trgs["STREAM_PUSH"]["response_action"] = "If false, rejects the incoming push.";
@ -102,55 +109,79 @@ namespace Controller {
trgs["STREAM_BUFFER"]["when"] = "Every time a live stream buffer changes state";
trgs["STREAM_BUFFER"]["stream_specific"] = true;
trgs["STREAM_BUFFER"]["payload"] = "stream name (string)\nbuffer state: EMPTY, FULL, DRY or RECOVER (string)\nbuffer health information (only if not EMPTY) (JSON)";
trgs["STREAM_BUFFER"]["payload"] =
"stream name (string)\nbuffer state: EMPTY, FULL, DRY or RECOVER (string)\nbuffer health "
"information (only if not EMPTY) (JSON)";
trgs["STREAM_BUFFER"]["response"] = "ignored";
trgs["STREAM_BUFFER"]["response_action"] = "None.";
trgs["RTMP_PUSH_REWRITE"]["when"] = "On incoming RTMP pushes, allows rewriting the RTMP URL to/from custom formatting";
trgs["RTMP_PUSH_REWRITE"]["when"] =
"On incoming RTMP pushes, allows rewriting the RTMP URL to/from custom formatting";
trgs["RTMP_PUSH_REWRITE"]["stream_specific"] = false;
trgs["RTMP_PUSH_REWRITE"]["payload"] = "full current RTMP url (string)\nconnection hostname (string)";
trgs["RTMP_PUSH_REWRITE"]["payload"] =
"full current RTMP url (string)\nconnection hostname (string)";
trgs["RTMP_PUSH_REWRITE"]["response"] = "when-blocking";
trgs["RTMP_PUSH_REWRITE"]["response_action"] = "If non-empty, overrides the full RTMP url to the response value. If empty, denies the incoming RTMP push.";
trgs["RTMP_PUSH_REWRITE"]["response_action"] =
"If non-empty, overrides the full RTMP url to the response value. If empty, denies the "
"incoming RTMP push.";
trgs["PUSH_OUT_START"]["when"] = "Before a push out (to file or other target type) is started";
trgs["PUSH_OUT_START"]["stream_specific"] = true;
trgs["PUSH_OUT_START"]["payload"] = "stream name (string)\npush target (string)";
trgs["PUSH_OUT_START"]["response"] = "when-blocking";
trgs["PUSH_OUT_START"]["response_action"] = "A non-empty response will set the push target to the response value. An empty response will abort the push. Variable substitution will still take place.";
trgs["PUSH_OUT_START"]["response_action"] =
"A non-empty response will set the push target to the response value. An empty response "
"will abort the push. Variable substitution will still take place.";
trgs["RECORDING_END"]["when"] = "When a push to file finishes";
trgs["RECORDING_END"]["stream_specific"] = true;
trgs["RECORDING_END"]["payload"] = "stream name (string)\npush target (string)\nconnector / filetype (string)\nbytes recorded (integer)\nseconds spent recording (integer)\nunix time recording started (integer)\nunix time recording stopped (integer)\ntotal milliseconds of media data recorded (integer)\nmillisecond timestamp of first media packet (integer)\nmillisecond timestamp of last media packet (integer)\n";
trgs["RECORDING_END"]["payload"] =
"stream name (string)\npush target (string)\nconnector / filetype (string)\nbytes recorded "
"(integer)\nseconds spent recording (integer)\nunix time recording started (integer)\nunix "
"time recording stopped (integer)\ntotal milliseconds of media data recorded "
"(integer)\nmillisecond timestamp of first media packet (integer)\nmillisecond timestamp "
"of last media packet (integer)\n";
trgs["RECORDING_END"]["response"] = "ignored";
trgs["RECORDING_END"]["response_action"] = "None.";
trgs["CONN_OPEN"]["when"] = "After a new connection is accepted";
trgs["CONN_OPEN"]["stream_specific"] = true;
trgs["CONN_OPEN"]["payload"] = "stream name (string)\nconnection address (string)\nconnector (string)\nrequest url (string)";
trgs["CONN_OPEN"]["payload"] = "stream name (string)\nconnection address (string)\nconnector "
"(string)\nrequest url (string)";
trgs["CONN_OPEN"]["response"] = "always";
trgs["CONN_OPEN"]["response_action"] = "If false, rejects the connection.";
trgs["CONN_CLOSE"]["when"] = "After a new connection is closed";
trgs["CONN_CLOSE"]["stream_specific"] = true;
trgs["CONN_CLOSE"]["payload"] = "stream name (string)\nconnection address (string)\nconnector (string)\nrequest url (string)";
trgs["CONN_CLOSE"]["payload"] = "stream name (string)\nconnection address (string)\nconnector "
"(string)\nrequest url (string)";
trgs["CONN_CLOSE"]["response"] = "ignored";
trgs["CONN_CLOSE"]["response_action"] = "None.";
trgs["CONN_PLAY"]["when"] = "Before a connection first starts playback";
trgs["CONN_PLAY"]["stream_specific"] = true;
trgs["CONN_PLAY"]["payload"] = "stream name (string)\nconnection address (string)\nconnector (string)\nrequest url (string)";
trgs["CONN_PLAY"]["payload"] = "stream name (string)\nconnection address (string)\nconnector "
"(string)\nrequest url (string)";
trgs["CONN_PLAY"]["response"] = "always";
trgs["CONN_PLAY"]["response_action"] = "If false, rejects the playback attempt.";
trgs["USER_NEW"]["when"] = "Every time a new session is added to the session cache";
trgs["USER_NEW"]["stream_specific"] = true;
trgs["USER_NEW"]["payload"] = "stream name (string)\nconnection address (string)\nconnection identifier (integer)\nconnector (string)\nrequest url (string)\nsession identifier (integer)";
trgs["USER_NEW"]["payload"] =
"stream name (string)\nconnection address (string)\nconnection identifier "
"(integer)\nconnector (string)\nrequest url (string)\nsession identifier (integer)";
trgs["USER_NEW"]["response"] = "always";
trgs["USER_NEW"]["response_action"] = "If false, denies the session while it remains in the cache. If true, accepts the session while it remains in the cache.";
trgs["USER_NEW"]["response_action"] =
"If false, denies the session while it remains in the cache. If true, accepts the session "
"while it remains in the cache.";
trgs["USER_END"]["when"] = "Every time a session ends (same time it is written to the access log)";
trgs["USER_END"]["when"] =
"Every time a session ends (same time it is written to the access log)";
trgs["USER_END"]["stream_specific"] = true;
trgs["USER_END"]["payload"] = "session identifier (hexadecimal string)\nstream name (string)\nconnector (string)\nconnection address (string)\nduration in seconds (integer)\nuploaded bytes total (integer)\ndownloaded bytes total (integer)\ntags (string)";
trgs["USER_END"]["payload"] =
"session identifier (hexadecimal string)\nstream name (string)\nconnector "
"(string)\nconnection address (string)\nduration in seconds (integer)\nuploaded bytes "
"total (integer)\ndownloaded bytes total (integer)\ntags (string)";
trgs["USER_END"]["response"] = "ignored";
trgs["USER_END"]["response_action"] = "None.";
@ -159,31 +190,38 @@ namespace Controller {
trgs["LIVE_BANDWIDTH"]["payload"] = "stream name (string)\ncurrent bytes per second (integer)";
trgs["LIVE_BANDWIDTH"]["response"] = "always";
trgs["LIVE_BANDWIDTH"]["response_action"] = "If false, shuts down the stream buffer.";
trgs["LIVE_BANDWIDTH"]["argument"] = "Triggers only if current bytes per second exceeds this amount (integer)";
trgs["LIVE_BANDWIDTH"]["argument"] =
"Triggers only if current bytes per second exceeds this amount (integer)";
trgs["DEFAULT_STREAM"]["when"] = "When any user attempts to open a stream that cannot be opened (because it is either offline or not configured), allows rewriting the stream to a different one as fallback. Supports variable substitution.";
trgs["DEFAULT_STREAM"]["when"] =
"When any user attempts to open a stream that cannot be opened (because it is either "
"offline or not configured), allows rewriting the stream to a different one as fallback. "
"Supports variable substitution.";
trgs["DEFAULT_STREAM"]["stream_specific"] = true;
trgs["DEFAULT_STREAM"]["payload"] = "current defaultStream setting (string)\nrequested stream name (string)\nviewer host (string)\noutput type (string)\nfull request URL (string, may be blank for non-URL-based requests!)";
trgs["DEFAULT_STREAM"]["payload"] =
"current defaultStream setting (string)\nrequested stream name (string)\nviewer host "
"(string)\noutput type (string)\nfull request URL (string, may be blank for non-URL-based "
"requests!)";
trgs["DEFAULT_STREAM"]["response"] = "always";
trgs["DEFAULT_STREAM"]["response_action"] = "Overrides the default stream setting (for this view) to the response value. If empty, fails loading the stream and returns an error to the viewer/user.";
trgs["DEFAULT_STREAM"]["response_action"] =
"Overrides the default stream setting (for this view) to the response value. If empty, "
"fails loading the stream and returns an error to the viewer/user.";
}
///Aquire list of available protocols, storing in global 'capabilities' JSON::Value.
/// Aquire list of available protocols, storing in global 'capabilities' JSON::Value.
void checkAvailProtocols(){
std::deque<std::string> execs;
Util::getMyExec(execs);
std::string arg_one;
char const * conn_args[] = {0, "-j", 0};
char const *conn_args[] ={0, "-j", 0};
for (std::deque<std::string>::iterator it = execs.begin(); it != execs.end(); it++){
if ((*it).substr(0, 8) == "MistConn"){
//skip if an MistOut already existed - MistOut takes precedence!
if (capabilities["connectors"].isMember((*it).substr(8))){
continue;
}
// skip if an MistOut already existed - MistOut takes precedence!
if (capabilities["connectors"].isMember((*it).substr(8))){continue;}
arg_one = Util::getMyPath() + (*it);
conn_args[0] = arg_one.c_str();
capabilities["connectors"][(*it).substr(8)] = JSON::fromString(Util::Procs::getOutputOf((char**)conn_args));
capabilities["connectors"][(*it).substr(8)] =
JSON::fromString(Util::Procs::getOutputOf((char **)conn_args));
if (capabilities["connectors"][(*it).substr(8)].size() < 1){
capabilities["connectors"].removeMember((*it).substr(8));
}
@ -192,18 +230,21 @@ namespace Controller {
arg_one = Util::getMyPath() + (*it);
conn_args[0] = arg_one.c_str();
std::string entryName = (*it).substr(7);
capabilities["connectors"][entryName] = JSON::fromString(Util::Procs::getOutputOf((char**)conn_args));
capabilities["connectors"][entryName] =
JSON::fromString(Util::Procs::getOutputOf((char **)conn_args));
if (capabilities["connectors"][entryName].size() < 1){
capabilities["connectors"].removeMember(entryName);
}else if (capabilities["connectors"][entryName]["version"].asStringRef() != PACKAGE_VERSION){
WARN_MSG("Output %s version mismatch (%s != " PACKAGE_VERSION ")", entryName.c_str(), capabilities["connectors"][entryName]["version"].asStringRef().c_str());
WARN_MSG("Output %s version mismatch (%s != " PACKAGE_VERSION ")", entryName.c_str(),
capabilities["connectors"][entryName]["version"].asStringRef().c_str());
capabilities["connectors"].removeMember(entryName);
}
}
if ((*it).substr(0, 8) == "MistProc"){
arg_one = Util::getMyPath() + (*it);
conn_args[0] = arg_one.c_str();
capabilities["processes"][(*it).substr(8)] = JSON::fromString(Util::Procs::getOutputOf((char**)conn_args));
capabilities["processes"][(*it).substr(8)] =
JSON::fromString(Util::Procs::getOutputOf((char **)conn_args));
if (capabilities["processes"][(*it).substr(8)].size() < 1){
capabilities["processes"].removeMember((*it).substr(7));
}
@ -212,58 +253,47 @@ namespace Controller {
arg_one = Util::getMyPath() + (*it);
conn_args[0] = arg_one.c_str();
std::string entryName = (*it).substr(6);
capabilities["inputs"][entryName] = JSON::fromString(Util::Procs::getOutputOf((char**)conn_args));
capabilities["inputs"][entryName] = JSON::fromString(Util::Procs::getOutputOf((char **)conn_args));
if (capabilities["inputs"][entryName].size() < 1){
capabilities["inputs"].removeMember((*it).substr(6));
}else if (capabilities["inputs"][entryName]["version"].asStringRef() != PACKAGE_VERSION){
WARN_MSG("Input %s version mismatch (%s != " PACKAGE_VERSION ")", entryName.c_str(), capabilities["inputs"][entryName]["version"].asStringRef().c_str());
WARN_MSG("Input %s version mismatch (%s != " PACKAGE_VERSION ")", entryName.c_str(),
capabilities["inputs"][entryName]["version"].asStringRef().c_str());
capabilities["inputs"].removeMember(entryName);
}
}
}
}
///\brief A class storing information about the cpu the server is running on.
class cpudata{
public:
std::string model;///<A string describing the model of the cpu.
int cores;///<The amount of cores in the cpu.
int threads;///<The amount of threads this cpu can run.
int mhz;///<The speed of the cpu in mhz.
int id;///<The id of the cpu in the system.
///\brief The default constructor
cpudata(){
model = "Unknown";
cores = 1;
threads = 1;
mhz = 0;
id = 0;
}
;
///\brief Fills the structure by parsing a given description.
///\param data A description of the cpu.
void fill(char * data){
int i;
i = 0;
if (sscanf(data, "model name : %n", &i) != EOF && i > 0){
model = (data + i);
}
if (sscanf(data, "cpu cores : %d", &i) == 1){
cores = i;
}
if (sscanf(data, "siblings : %d", &i) == 1){
threads = i;
}
if (sscanf(data, "physical id : %d", &i) == 1){
id = i;
}
if (sscanf(data, "cpu MHz : %d", &i) == 1){
mhz = i;
}
}
;
public:
std::string model; ///< A string describing the model of the cpu.
int cores; ///< The amount of cores in the cpu.
int threads; ///< The amount of threads this cpu can run.
int mhz; ///< The speed of the cpu in mhz.
int id; ///< The id of the cpu in the system.
///\brief The default constructor
cpudata(){
model = "Unknown";
cores = 1;
threads = 1;
mhz = 0;
id = 0;
};
///\brief Fills the structure by parsing a given description.
///\param data A description of the cpu.
void fill(char *data){
int i;
i = 0;
if (sscanf(data, "model name : %n", &i) != EOF && i > 0){model = (data + i);}
if (sscanf(data, "cpu cores : %d", &i) == 1){cores = i;}
if (sscanf(data, "siblings : %d", &i) == 1){threads = i;}
if (sscanf(data, "physical id : %d", &i) == 1){id = i;}
if (sscanf(data, "cpu MHz : %d", &i) == 1){mhz = i;}
};
};
///\brief Checks the capabilities of the system.
@ -272,73 +302,78 @@ namespace Controller {
/// \api
/// `"capabilities"` requests are always empty:
/// ~~~~~~~~~~~~~~~{.js}
/// {}
///{}
/// ~~~~~~~~~~~~~~~
/// and are responded to as:
/// ~~~~~~~~~~~~~~~{.js}
/// {
/// "connectors": { // a list of installed connectors
/// "FLV": { //name of the connector. This is based on the executable filename, with the "MistIn" / "MistConn" prefix stripped.
///{
/// "connectors":{// a list of installed connectors
/// "FLV":{//name of the connector. This is based on the executable filename, with the
/// "MistIn" / "MistConn" prefix stripped.
/// "codecs": [ //supported combinations of codecs.
/// [["H264","H263","VP6"],["AAC","MP3"]] //one such combination, listing simultaneously available channels and the codec options for those channels
/// [["H264","H263","VP6"],["AAC","MP3"]] //one such combination, listing simultaneously
/// available channels and the codec options for those channels
/// ],
/// "deps": "HTTP", //dependencies on other connectors, if any.
/// "desc": "Enables HTTP protocol progressive streaming.", //human-friendly description of this connector
/// "methods": [ //list of supported request methods
/// {
/// "handler": "http", //what handler to use for this request method. The "http://" part of a URL, without the "://".
/// "priority": 5, // priority of this request method, higher is better.
/// "type": "flash/7" //type of request method - usually name of plugin followed by the minimal plugin version, or 'HTML5' for pluginless.
/// }
/// "desc": "Enables HTTP protocol progressive streaming.", //human-friendly description of
/// this connector "methods": [ //list of supported request methods
///{
/// "handler": "http", //what handler to use for this request method. The "http://" part
/// of a URL, without the "://". "priority": 5, // priority of this request method,
/// higher is better. "type": "flash/7" //type of request method - usually name of
/// plugin followed by the minimal plugin version, or 'HTML5' for pluginless.
///}
/// ],
/// "name": "HTTP_Progressive_FLV", //Full name of this connector.
/// "optional": { //optional parameters
/// "username": { //name of the parameter
/// "help": "Username to drop privileges to - default if unprovided means do not drop privileges", //human-readable help text
/// "name": "Username", //human-readable name of parameter
/// "option": "--username", //command-line option to use
/// "type": "str" //type of option - "str" or "num"
/// }
/// "optional":{//optional parameters
/// "username":{//name of the parameter
/// "help": "Username to drop privileges to - default if unprovided means do not drop
/// privileges", //human-readable help text "name": "Username", //human-readable name of
/// parameter "option": "--username", //command-line option to use "type": "str" //type
/// of option - "str" or "num"
///}
/// //above structure repeated for all (optional) parameters
/// },
///},
/// //above structure repeated, as "required" for required parameters, if any.
/// "socket": "http_progressive_flv", //unix socket this connector listens on, if any
/// "url_match": "/$.flv", //URL pattern to match, if any. The $ substitutes the stream name and may not be the first or last character.
/// "url_prefix": "/progressive/$/", //URL prefix to match, if any. The $ substitutes the stream name and may not be the first or last character.
/// "url_rel": "/$.flv" //relative URL where to access a stream through this connector.
/// }
/// "url_match": "/$.flv", //URL pattern to match, if any. The $ substitutes the stream name
/// and may not be the first or last character. "url_prefix": "/progressive/$/", //URL
/// prefix to match, if any. The $ substitutes the stream name and may not be the first or
/// last character. "url_rel": "/$.flv" //relative URL where to access a stream through this
/// connector.
///}
/// //... above structure repeated for all installed connectors.
/// },
///},
/// "cpu": [ //a list of installed CPUs
/// {
///{
/// "cores": 4, //amount of cores for this CPU
/// "mhz": 1645, //speed in MHz for this CPU
/// "model": "Intel(R) Core(TM) i7-2630QM CPU @ 2.00GHz", //model identifier, for humans
/// "threads": 8 //amount of simultaneously executing threads that are supported on this CPU
/// }
///}
/// //above structure repeated for all installed CPUs
/// ],
/// "load": {
/// "load":{
/// "fifteen": 72,
/// "five": 81,
/// "memory": 42,
/// "one": 124
/// },
/// "mem": {
///},
/// "mem":{
/// "cached": 1989, //current memory usage of system caches, in MiB
/// "free": 2539, //free memory, in MiB
/// "swapfree": 0, //free swap space, in MiB
/// "swaptotal": 0, //total swap space, in MiB
/// "total": 7898, //total memory, in MiB
/// "used": 3370 //used memory, in MiB (excluding system caches, listed separately)
/// },
///},
/// "speed": 6580, //total speed in MHz of all CPUs cores summed together
/// "threads": 8 //total count of all threads of all CPUs summed together
/// "cpu_use": 105 //Tenths of percent CPU usage - i.e. 105 = 10.5%
/// }
///}
/// ~~~~~~~~~~~~~~~
void checkCapable(JSON::Value & capa){
//capa.null();
void checkCapable(JSON::Value &capa){
// capa.null();
capa.removeMember("cpu");
std::ifstream cpuinfo("/proc/cpuinfo");
if (cpuinfo){
@ -348,29 +383,25 @@ namespace Controller {
while (cpuinfo.good()){
cpuinfo.getline(line, 300);
if (cpuinfo.fail()){
//empty lines? ignore them, clear flags, continue
if ( !cpuinfo.eof()){
// empty lines? ignore them, clear flags, continue
if (!cpuinfo.eof()){
cpuinfo.ignore();
cpuinfo.clear();
}
continue;
}
if (memcmp(line, "processor", 9) == 0){
proccount++;
}
if (memcmp(line, "processor", 9) == 0){proccount++;}
cpus[proccount].fill(line);
}
//fix wrong core counts
// fix wrong core counts
std::map<int, int> corecounts;
for (int i = 0; i <= proccount; ++i){
corecounts[cpus[i].id]++;
}
//remove double physical IDs - we only want real CPUs.
for (int i = 0; i <= proccount; ++i){corecounts[cpus[i].id]++;}
// remove double physical IDs - we only want real CPUs.
std::set<int> used_physids;
int total_speed = 0;
int total_threads = 0;
for (int i = 0; i <= proccount; ++i){
if ( !used_physids.count(cpus[i].id)){
if (!used_physids.count(cpus[i].id)){
used_physids.insert(cpus[i].id);
JSON::Value thiscpu;
thiscpu["model"] = cpus[i].model;
@ -398,43 +429,37 @@ namespace Controller {
while (meminfo.good()){
meminfo.getline(line, 300);
if (meminfo.fail()){
//empty lines? ignore them, clear flags, continue
if ( !meminfo.eof()){
// empty lines? ignore them, clear flags, continue
if (!meminfo.eof()){
meminfo.ignore();
meminfo.clear();
}
continue;
}
uint64_t i;
if (sscanf(line, "MemTotal : %" PRIu64 " kB", &i) == 1){
capa["mem"]["total"] = i / 1024;
}
if (sscanf(line, "MemFree : %" PRIu64 " kB", &i) == 1){
capa["mem"]["free"] = i / 1024;
}
if (sscanf(line, "MemTotal : %" PRIu64 " kB", &i) == 1){capa["mem"]["total"] = i / 1024;}
if (sscanf(line, "MemFree : %" PRIu64 " kB", &i) == 1){capa["mem"]["free"] = i / 1024;}
if (sscanf(line, "SwapTotal : %" PRIu64 " kB", &i) == 1){
capa["mem"]["swaptotal"] = i / 1024;
}
if (sscanf(line, "SwapFree : %" PRIu64 " kB", &i) == 1){
capa["mem"]["swapfree"] = i / 1024;
}
if (sscanf(line, "Buffers : %" PRIu64 " kB", &i) == 1){
bufcache += i / 1024;
}
if (sscanf(line, "Cached : %" PRIu64 " kB", &i) == 1){
bufcache += i / 1024;
}
if (sscanf(line, "Buffers : %" PRIu64 " kB", &i) == 1){bufcache += i / 1024;}
if (sscanf(line, "Cached : %" PRIu64 " kB", &i) == 1){bufcache += i / 1024;}
}
capa["mem"]["used"] = capa["mem"]["total"].asInt() - capa["mem"]["free"].asInt() - bufcache;
capa["mem"]["cached"] = bufcache;
capa["load"]["memory"] = ((capa["mem"]["used"].asInt() + (capa["mem"]["swaptotal"].asInt() - capa["mem"]["swapfree"].asInt())) * 100)
/ capa["mem"]["total"].asInt();
capa["load"]["memory"] = ((capa["mem"]["used"].asInt() +
(capa["mem"]["swaptotal"].asInt() - capa["mem"]["swapfree"].asInt())) *
100) /
capa["mem"]["total"].asInt();
}
std::ifstream loadavg("/proc/loadavg");
if (loadavg){
char line[300];
loadavg.getline(line, 300);
//parse lines here
// parse lines here
float onemin, fivemin, fifteenmin;
if (sscanf(line, "%f %f %f", &onemin, &fivemin, &fifteenmin) == 3){
capa["load"]["one"] = uint64_t(onemin * 100);
@ -448,7 +473,8 @@ namespace Controller {
while (cpustat.getline(line, 300)){
static uint64_t cl_total = 0, cl_idle = 0;
uint64_t c_user, c_nice, c_syst, c_idle, c_total;
if (sscanf(line, "cpu %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, &c_user, &c_nice, &c_syst, &c_idle) == 4){
if (sscanf(line, "cpu %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, &c_user, &c_nice,
&c_syst, &c_idle) == 4){
c_total = c_user + c_nice + c_syst + c_idle;
if (c_total - cl_total > 0){
capa["cpu_use"] = (1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total));
@ -461,8 +487,6 @@ namespace Controller {
}
}
}
}
}
}// namespace Controller

View file

@ -6,4 +6,3 @@ namespace Controller{
void checkAvailProtocols();
void checkAvailTriggers(); /*LTS*/
}// namespace Controller

View file

@ -1,32 +1,29 @@
#include <stdio.h> // cout, cerr
#include <string>
#include <cstring> // strcpy
#include <sys/stat.h> //stat
#include <mist/json.h>
#include "controller_connectors.h"
#include "controller_storage.h"
#include <cstring> // strcpy
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/json.h>
#include <mist/procs.h>
#include <mist/shared_memory.h>
#include <mist/timing.h>
#include <mist/tinythread.h>
#include <mist/defines.h>
#include "controller_storage.h"
#include "controller_connectors.h"
#include <mist/triggers.h>
#include <mist/shared_memory.h>
#include <mist/util.h>
#include <stdio.h> // cout, cerr
#include <string>
#include <sys/stat.h> //stat
#include <iostream>
#include <unistd.h>
///\brief Holds everything unique to the controller.
namespace Controller {
namespace Controller{
static std::set<size_t> needsReload; ///< List of connector indices that needs a reload
static std::map<std::string, pid_t> currentConnectors; ///<The currently running connectors.
static std::map<std::string, pid_t> currentConnectors; ///< The currently running connectors.
void reloadProtocol(size_t indice){
needsReload.insert(indice);
}
void reloadProtocol(size_t indice){needsReload.insert(indice);}
/// Updates the shared memory page with active connectors
void saveActiveConnectors(bool forceOverride){
@ -56,7 +53,7 @@ namespace Controller {
++count;
}
A.setRCount(count);
f.master = false;//Keep the shm page around, don't kill it
f.master = false; // Keep the shm page around, don't kill it
}
/// Reads active connectors from the shared memory pages
@ -66,7 +63,7 @@ namespace Controller {
if (A.isReady()){
INFO_MSG("Reloading existing connectors to complete rolling restart");
for (uint32_t i = 0; i < A.getRCount(); ++i){
char * p = A.getPointer("cmd", i);
char *p = A.getPointer("cmd", i);
if (p != 0 && p[0] != 0){
currentConnectors[p] = A.getInt("pid", i);
Util::Procs::remember(A.getInt("pid", i));
@ -80,9 +77,7 @@ namespace Controller {
/// in preparation of shutdown.
void prepareActiveConnectorsForShutdown(){
IPC::sharedPage f("MstCnns", 4096, false, false);
if (f){
f.master = true;
}
if (f){f.master = true;}
}
/// Forgets all active connectors, preventing them from being killed,
@ -96,45 +91,46 @@ namespace Controller {
currentConnectors.clear();
}
static inline void builPipedPart(JSON::Value & p, char * argarr[], int & argnum, const JSON::Value & argset){
jsonForEachConst(argset, it) {
static inline void builPipedPart(JSON::Value &p, char *argarr[], int &argnum, const JSON::Value &argset){
jsonForEachConst(argset, it){
if (it->isMember("option") && p.isMember(it.key())){
if (it->isMember("type")){
if ((*it)["type"].asStringRef() == "str" && !p[it.key()].isString()){
p[it.key()] = p[it.key()].asString();
}
if ((*it)["type"].asStringRef() == "uint" || (*it)["type"].asStringRef() == "int" || (*it)["type"].asStringRef() == "debug"){
if ((*it)["type"].asStringRef() == "uint" || (*it)["type"].asStringRef() == "int" ||
(*it)["type"].asStringRef() == "debug"){
p[it.key()] = JSON::Value(p[it.key()].asInt()).asString();
}
if ((*it)["type"].asStringRef() == "inputlist" && p[it.key()].isArray()){
jsonForEach(p[it.key()], iVal){
(*iVal) = iVal->asString();
argarr[argnum++] = (char*)((*it)["option"].c_str());
argarr[argnum++] = (char*)((*iVal).c_str());
argarr[argnum++] = (char *)((*it)["option"].c_str());
argarr[argnum++] = (char *)((*iVal).c_str());
}
continue;
}
}
if (p[it.key()].asStringRef().size() > 0){
argarr[argnum++] = (char*)((*it)["option"].c_str());
argarr[argnum++] = (char*)(p[it.key()].c_str());
argarr[argnum++] = (char *)((*it)["option"].c_str());
argarr[argnum++] = (char *)(p[it.key()].c_str());
}else{
if (it.key() == "debug"){
if (Util::Config::printDebugLevel != DEBUG){
static std::string debugLvlStr;
debugLvlStr = JSON::Value(Util::Config::printDebugLevel).asString();
argarr[argnum++] = (char*)((*it)["option"].asStringRef().c_str());
argarr[argnum++] = (char*)debugLvlStr.c_str();
argarr[argnum++] = (char *)((*it)["option"].asStringRef().c_str());
argarr[argnum++] = (char *)debugLvlStr.c_str();
}
continue;
}
argarr[argnum++] = (char*)((*it)["option"].c_str());
argarr[argnum++] = (char *)((*it)["option"].c_str());
}
}
}
}
static inline void buildPipedArguments(JSON::Value & p, char * argarr[], const JSON::Value & capabilities){
static inline void buildPipedArguments(JSON::Value &p, char *argarr[], const JSON::Value &capabilities){
int argnum = 0;
static std::string tmparg;
tmparg = Util::getMyPath() + std::string("MistOut") + p["connector"].asStringRef();
@ -142,21 +138,19 @@ namespace Controller {
if (::stat(tmparg.c_str(), &buf) != 0){
tmparg = Util::getMyPath() + std::string("MistConn") + p["connector"].asStringRef();
}
if (::stat(tmparg.c_str(), &buf) != 0){
return;
}
argarr[argnum++] = (char*)tmparg.c_str();
const JSON::Value & pipedCapa = capabilities["connectors"][p["connector"].asStringRef()];
if (::stat(tmparg.c_str(), &buf) != 0){return;}
argarr[argnum++] = (char *)tmparg.c_str();
const JSON::Value &pipedCapa = capabilities["connectors"][p["connector"].asStringRef()];
if (pipedCapa.isMember("required")){builPipedPart(p, argarr, argnum, pipedCapa["required"]);}
if (pipedCapa.isMember("optional")){builPipedPart(p, argarr, argnum, pipedCapa["optional"]);}
}
///\brief Checks current protocol configuration, updates state of enabled connectors if neccessary.
///\param p An object containing all protocols.
///\param capabilities An object containing the detected capabilities.
///\returns True if any action was taken
///
/// \triggers
///
/// \triggers
/// The `"OUTPUT_START"` trigger is global, and is ran whenever a new protocol listener is started. It cannot be cancelled. Its payload is:
/// ~~~~~~~~~~~~~~~
/// output listener commandline
@ -165,48 +159,49 @@ namespace Controller {
/// ~~~~~~~~~~~~~~~
/// output listener commandline
/// ~~~~~~~~~~~~~~~
bool CheckProtocols(JSON::Value & p, const JSON::Value & capabilities){
bool CheckProtocols(JSON::Value &p, const JSON::Value &capabilities){
std::set<std::string> runningConns;
// used for building args
int zero = 0;
int out = fileno(stdout);
int err = fileno(stderr);
char * argarr[15]; // approx max # of args (with a wide margin)
char *argarr[15]; // approx max # of args (with a wide margin)
int i;
std::string tmp;
jsonForEach(p, ait) {
jsonForEach(p, ait){
std::string prevOnline = (*ait)["online"].asString();
const std::string & connName = (*ait)["connector"].asStringRef();
//do not further parse if there's no connector name
if ( !(*ait).isMember("connector") || connName == ""){
( *ait)["online"] = "Missing connector name";
const std::string &connName = (*ait)["connector"].asStringRef();
// do not further parse if there's no connector name
if (!(*ait).isMember("connector") || connName == ""){
(*ait)["online"] = "Missing connector name";
continue;
}
//ignore connectors that are not installed
// ignore connectors that are not installed
if (!capabilities.isMember("connectors") || !capabilities["connectors"].isMember(connName)){
( *ait)["online"] = "Not installed";
if (( *ait)["online"].asString() != prevOnline){
Log("WARN", connName + " connector is enabled but doesn't exist on system! Ignoring connector.");
(*ait)["online"] = "Not installed";
if ((*ait)["online"].asString() != prevOnline){
Log("WARN",
connName + " connector is enabled but doesn't exist on system! Ignoring connector.");
}
continue;
}
//list connectors that go through HTTP as 'enabled' without actually running them.
const JSON::Value & connCapa = capabilities["connectors"][connName];
// list connectors that go through HTTP as 'enabled' without actually running them.
const JSON::Value &connCapa = capabilities["connectors"][connName];
if (connCapa.isMember("socket") || (connCapa.isMember("deps") && connCapa["deps"].asStringRef() == "HTTP")){
( *ait)["online"] = "Enabled";
(*ait)["online"] = "Enabled";
continue;
}
//check required parameters, skip if anything is missing
// check required parameters, skip if anything is missing
if (connCapa.isMember("required")){
bool gotAll = true;
jsonForEachConst(connCapa["required"], it) {
if ( !(*ait).isMember(it.key()) || (*ait)[it.key()].asStringRef().size() < 1){
jsonForEachConst(connCapa["required"], it){
if (!(*ait).isMember(it.key()) || (*ait)[it.key()].asStringRef().size() < 1){
gotAll = false;
( *ait)["online"] = "Invalid configuration";
if (( *ait)["online"].asString() != prevOnline){
(*ait)["online"] = "Invalid configuration";
if ((*ait)["online"].asString() != prevOnline){
Log("WARN", connName + " connector is missing required parameter " + it.key() + "! Ignoring connector.");
}
break;
@ -214,26 +209,26 @@ namespace Controller {
}
if (!gotAll){continue;}
}
//remove current online status
( *ait).removeMember("online");
// remove current online status
(*ait).removeMember("online");
/// \todo Check dependencies?
//set current online status
// set current online status
std::string myCmd = (*ait).toString();
runningConns.insert(myCmd);
if (currentConnectors.count(myCmd) && Util::Procs::isActive(currentConnectors[myCmd])){
( *ait)["online"] = 1;
//Reload connectors that need it
(*ait)["online"] = 1;
// Reload connectors that need it
if (needsReload.count(ait.num())){
kill(currentConnectors[myCmd], SIGUSR1);
needsReload.erase(ait.num());
}
}else{
( *ait)["online"] = 0;
(*ait)["online"] = 0;
}
}
bool action = false;
//shut down deleted/changed connectors
// shut down deleted/changed connectors
std::map<std::string, pid_t>::iterator it;
if (currentConnectors.size()){
for (it = currentConnectors.begin(); it != currentConnectors.end(); it++){
@ -242,30 +237,29 @@ namespace Controller {
Log("CONF", "Stopping connector " + it->first);
action = true;
Util::Procs::Stop(it->second);
Triggers::doTrigger("OUTPUT_STOP",it->first); //LTS
Triggers::doTrigger("OUTPUT_STOP", it->first); // LTS
}
currentConnectors.erase(it);
if (!currentConnectors.size()){
break;
}
if (!currentConnectors.size()){break;}
it = currentConnectors.begin();
}
}
}
//start up new/changed connectors
// start up new/changed connectors
while (runningConns.size() && conf.is_active){
if (!currentConnectors.count(*runningConns.begin()) || !Util::Procs::isActive(currentConnectors[*runningConns.begin()])){
if (!currentConnectors.count(*runningConns.begin()) ||
!Util::Procs::isActive(currentConnectors[*runningConns.begin()])){
Log("CONF", "Starting connector: " + *runningConns.begin());
action = true;
// clear out old args
for (i=0; i<15; i++){argarr[i] = 0;}
for (i = 0; i < 15; i++){argarr[i] = 0;}
// get args for this connector
JSON::Value p = JSON::fromString(*runningConns.begin());
buildPipedArguments(p, (char **)&argarr, capabilities);
// start piped w/ generated args
currentConnectors[*runningConns.begin()] = Util::Procs::StartPiped(argarr, &zero, &out, &err);
Triggers::doTrigger("OUTPUT_START", *runningConns.begin());//LTS
Triggers::doTrigger("OUTPUT_START", *runningConns.begin()); // LTS
}
runningConns.erase(runningConns.begin());
}
@ -273,5 +267,4 @@ namespace Controller {
return action;
}
}
}// namespace Controller

View file

@ -1,12 +1,12 @@
#include <mist/json.h>
namespace Controller {
namespace Controller{
/// Marks the given protocol as needing a reload (signal USR1) on next check
void reloadProtocol(size_t indice);
/// Checks current protocol configuration, updates state of enabled connectors if neccesary.
bool CheckProtocols(JSON::Value & p, const JSON::Value & capabilities);
bool CheckProtocols(JSON::Value &p, const JSON::Value &capabilities);
/// Updates the shared memory page with active connectors
void saveActiveConnectors(bool forceOverride = false);
@ -14,7 +14,6 @@ namespace Controller {
/// Reads active connectors from the shared memory pages
void loadActiveConnectors();
/// Deletes the shared memory page with connector information
/// in preparation of shutdown.
void prepareActiveConnectorsForShutdown();
@ -23,5 +22,4 @@ namespace Controller {
/// in preparation of reload.
void prepareActiveConnectorsForReload();
}
}// namespace Controller

View file

@ -1,32 +1,29 @@
#include "controller_license.h"
#include "controller_storage.h"
#include <iostream>
#include <mist/auth.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/downloader.h>
#include <mist/encode.h>
#include <mist/encryption.h>
#include <mist/http_parser.h>
#include <mist/socket.h>
#include <mist/auth.h>
#include <mist/timing.h>
#include <mist/config.h>
#include <mist/encryption.h>
#include <mist/encode.h>
#include <mist/downloader.h>
namespace Controller{
uint64_t exitDelay = 0;
static JSON::Value currentLicense;
static uint64_t lastCheck = 0;
static int32_t timeOffset = 0;
static bool everContactedServer = false;
const JSON::Value & getLicense(){
return currentLicense;
}
//PACKAGE_VERSION = MistServer version
//RELEASE = OS + user_ID
const JSON::Value &getLicense(){return currentLicense;}
// PACKAGE_VERSION = MistServer version
// RELEASE = OS + user_ID
void initLicense(){
if (Storage.isMember("license") && Storage.isMember("license_id")){
INFO_MSG("Reading license from storage")
@ -48,30 +45,30 @@ namespace Controller{
#if DEBUG >= DLVL_DEVEL
INFO_MSG("Verifying license against %" PRIu64 ": %s", now, currentLicense.toString().c_str());
#endif
//Print messages for user, if any
// Print messages for user, if any
if (currentLicense.isMember("user_msg") && currentLicense["user_msg"].asStringRef().size()){
WARN_MSG("%s", currentLicense["user_msg"].asStringRef().c_str());
}
//Check time
if (!currentLicense.isMember("valid_from") || !currentLicense.isMember("valid_till") || now < currentLicense["valid_from"].asInt() || now > currentLicense["valid_till"].asInt()){
return false;//license is expired
// Check time
if (!currentLicense.isMember("valid_from") || !currentLicense.isMember("valid_till") ||
now < currentLicense["valid_from"].asInt() || now > currentLicense["valid_till"].asInt()){
return false; // license is expired
}
//Check release/version
if (RELEASE != currentLicense["release"].asStringRef() || PACKAGE_VERSION != currentLicense["version"].asStringRef()){
// Check release/version
if (RELEASE != currentLicense["release"].asStringRef() ||
PACKAGE_VERSION != currentLicense["version"].asStringRef()){
FAIL_MSG("Could not verify license");
return false;
}
//everything seems okay
// everything seems okay
return true;
}
bool checkLicense(){
if (!conf.is_active){return true;}
INFO_MSG("Checking license validity");
if(!everContactedServer && !isLicensed()){
updateLicense("&expired=1");
}
if(!isLicensed()){
if (!everContactedServer && !isLicensed()){updateLicense("&expired=1");}
if (!isLicensed()){
FAIL_MSG("Not licensed, shutting down");
if (currentLicense.isMember("delay") && currentLicense["delay"].asInt()){
exitDelay = currentLicense["delay"].asInt();
@ -83,79 +80,84 @@ namespace Controller{
lastCheck = Util::epoch();
return true;
}
void parseKey(std::string key, char * newKey, unsigned int len){
void parseKey(std::string key, char *newKey, unsigned int len){
memset(newKey, 0, len);
for (size_t i = 0; i < key.size() && i < (len << 1); ++i){
char c = key[i];
newKey[i>>1] |= ((c&15) + (((c&64)>>6) | ((c&64)>>3))) << ((~i&1) << 2);
newKey[i >> 1] |= ((c & 15) + (((c & 64) >> 6) | ((c & 64) >> 3))) << ((~i & 1) << 2);
}
}
void updateLicense(const std::string & extra){
void updateLicense(const std::string &extra){
INFO_MSG("Running license updater %s", extra.c_str());
JSON::Value response;
HTTP::Downloader dl;
dl.dataTimeout = 25;//25-second data timeout, increased from 5s default
dl.dataTimeout = 25; // 25-second data timeout, increased from 5s default
#ifdef SSL
HTTP::URL url("https://releases.mistserver.org/license.php");
if (dl.isProxied()){url.protocol = "http";}
#else
HTTP::URL url("http://releases.mistserver.org/license.php");
#endif
url.args = "release="+Encodings::URL::encode(RELEASE)+"&version="+Encodings::URL::encode(PACKAGE_VERSION)+"&iid="+Encodings::URL::encode(instanceId)+"&hrn="+Encodings::URL::encode(Storage["config"]["serverid"])+"&lid="+currentLicense["lic_id"].asString() + extra;
url.args = "release=" + Encodings::URL::encode(RELEASE) +
"&version=" + Encodings::URL::encode(PACKAGE_VERSION) +
"&iid=" + Encodings::URL::encode(instanceId) +
"&hrn=" + Encodings::URL::encode(Storage["config"]["serverid"]) +
"&lid=" + currentLicense["lic_id"].asString() + extra;
long long currID = currentLicense["lic_id"].asInt();
if (currID){
char aesKey[16];
if (strlen(SUPER_SECRET) >= 32){
parseKey((SUPER_SECRET SUPER_SECRET)+7,aesKey,16);
parseKey((SUPER_SECRET SUPER_SECRET) + 7, aesKey, 16);
}else{
parseKey("4E56721C67306E1F473156F755FF5570",aesKey,16);
parseKey("4E56721C67306E1F473156F755FF5570", aesKey, 16);
}
for (unsigned int i = 0; i < 8; ++i){
aesKey[15-i] = ((currID >> (i*8)) + aesKey[15-i]) & 0xFF;
aesKey[15 - i] = ((currID >> (i * 8)) + aesKey[15 - i]) & 0xFF;
}
char ivec[16];
memset(ivec, 0, 16);
dl.setHeader("X-IRDGAF", Encodings::Base64::encode(Encryption::AES_Crypt(RELEASE "|" PACKAGE_VERSION, sizeof(RELEASE "|" PACKAGE_VERSION), aesKey, ivec)));
}
if (!dl.get(url) || !dl.isOk()){
return;
dl.setHeader("X-IRDGAF",
Encodings::Base64::encode(Encryption::AES_Crypt(
RELEASE "|" PACKAGE_VERSION, sizeof(RELEASE "|" PACKAGE_VERSION), aesKey, ivec)));
}
if (!dl.get(url) || !dl.isOk()){return;}
response = JSON::fromString(dl.data());
everContactedServer = true;
//read license
// read license
readLicense(response["lic_id"].asInt(), response["license"].asStringRef(), true);
}
void readLicense(uint64_t licID, const std::string & input, bool fromServer){
void readLicense(uint64_t licID, const std::string &input, bool fromServer){
char aesKey[16];
if (strlen(SUPER_SECRET) >= 32){
parseKey((SUPER_SECRET SUPER_SECRET)+ 7,aesKey,16);
parseKey((SUPER_SECRET SUPER_SECRET) + 7, aesKey, 16);
}else{
parseKey("4E56721C67306E1F473156F755FF5570",aesKey,16);
parseKey("4E56721C67306E1F473156F755FF5570", aesKey, 16);
}
for (unsigned int i = 0; i < 8; ++i){
aesKey[15-i] = ((licID >> (i*8)) + aesKey[15-i]) & 0xFF;
aesKey[15 - i] = ((licID >> (i * 8)) + aesKey[15 - i]) & 0xFF;
}
std::string cipher = Encodings::Base64::decode(input);
std::string deCrypted;
//magic ivecs, they are empty. It's secretly 16 times \0.
// magic ivecs, they are empty. It's secretly 16 times \0.
char ivec[16];
memset(ivec, 0, 16);
deCrypted = Encryption::AES_Crypt(cipher.c_str(), cipher.size(), aesKey, ivec);
//get time stamps and license.
//verify checksum
if (deCrypted.size() < 33 || Secure::md5(deCrypted.substr(32)) != deCrypted.substr(0,32)){
// get time stamps and license.
// verify checksum
if (deCrypted.size() < 33 || Secure::md5(deCrypted.substr(32)) != deCrypted.substr(0, 32)){
WARN_MSG("Could not decode license");
return;
}
JSON::Value newLicense = JSON::fromString(deCrypted.substr(32));
if (RELEASE != newLicense["release"].asStringRef() || PACKAGE_VERSION != newLicense["version"].asStringRef()){
if (RELEASE != newLicense["release"].asStringRef() ||
PACKAGE_VERSION != newLicense["version"].asStringRef()){
FAIL_MSG("Could not verify license");
return;
}
@ -164,10 +166,14 @@ namespace Controller{
uint64_t localTime = Util::epoch();
uint64_t remoteTime = newLicense["time"].asInt();
if (localTime > remoteTime + 60){
WARN_MSG("Your computer clock is %" PRIu64 " seconds ahead! Please ensure your computer clock is set correctly.", localTime - remoteTime);
WARN_MSG("Your computer clock is %" PRIu64
" seconds ahead! Please ensure your computer clock is set correctly.",
localTime - remoteTime);
}
if (localTime < remoteTime - 60){
WARN_MSG("Your computer clock is %" PRIu64 " seconds late! Please ensure your computer clock is set correctly.", remoteTime - localTime);
WARN_MSG("Your computer clock is %" PRIu64
" seconds late! Please ensure your computer clock is set correctly.",
remoteTime - localTime);
}
timeOffset = remoteTime - localTime;
@ -179,7 +185,7 @@ namespace Controller{
currentLicense = newLicense;
//Store license here.
// Store license here.
if (currentLicense["store"].asBool()){
if (Storage["license"].asStringRef() != input){
Storage["license"] = input;
@ -188,21 +194,16 @@ namespace Controller{
}
}
}
void licenseLoop(void * np){
void licenseLoop(void *np){
while (conf.is_active){
uint64_t interval = currentLicense["interval"].asInt();
if (Util::epoch() - lastCheck > (interval?interval:3600)){
if (interval){
updateLicense();
}
if (Util::epoch() - lastCheck > (interval ? interval : 3600)){
if (interval){updateLicense();}
checkLicense();
}
Util::sleep(1000);//sleep a bit
}
if (everContactedServer){
updateLicense("&shutdown=1");
Util::sleep(1000); // sleep a bit
}
if (everContactedServer){updateLicense("&shutdown=1");}
}
}
}// namespace Controller

View file

@ -3,16 +3,13 @@
namespace Controller{
extern uint64_t exitDelay;
const JSON::Value & getLicense();
const JSON::Value &getLicense();
void initLicense();
bool isLicensed(); //checks/verifies license time
bool checkLicense(); //Call from Mainloop.
void updateLicense(const std::string & extra = ""); //retrieves update from license server
void licenseLoop(void * np);
void readLicense(uint64_t licId, const std::string & input, bool fromServer = false); //checks/interprets license
}
bool isLicensed(); // checks/verifies license time
bool checkLicense(); // Call from Mainloop.
void updateLicense(const std::string &extra = ""); // retrieves update from license server
void licenseLoop(void *np);
void readLicense(uint64_t licId, const std::string &input, bool fromServer = false); // checks/interprets license
}// namespace Controller

View file

@ -2,39 +2,35 @@
#include "controller_statistics.h"
#include "controller_storage.h"
#include <iostream>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <iostream>
#include <netdb.h>
#include <sys/socket.h>
namespace Controller{
void checkStreamLimits(std::string streamName, long long currentKbps, long long connectedUsers){
if( !Storage["streams"].isMember(streamName)){
return;
}
if( !Storage["streams"][streamName].isMember("limits")){
return;
}
if( !Storage["streams"][streamName]["limits"]){
return;
}
if (!Storage["streams"].isMember(streamName)){return;}
if (!Storage["streams"][streamName].isMember("limits")){return;}
if (!Storage["streams"][streamName]["limits"]){return;}
Storage["streams"][streamName].removeMember("hardlimit_active");
if (Storage["streams"][streamName]["online"].asInt() != 1){
jsonForEach(Storage["streams"][streamName]["limits"], limitIt){
if ((*limitIt).isMember("triggered")){
if ((*limitIt)["type"].asString() == "soft"){
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset - stream unavailable.");
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() +
" for stream " + streamName + " reset - stream unavailable.");
}else{
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset - stream unavailable.");
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() +
" for stream " + streamName + " reset - stream unavailable.");
}
(*limitIt).removeMember("triggered");
}
}
return;
}
//run over all limits.
// run over all limits.
jsonForEach(Storage["streams"][streamName]["limits"], limitIt){
bool triggerLimit = false;
if ((*limitIt)["name"].asString() == "users" && connectedUsers >= (*limitIt)["value"].asInt()){
@ -47,23 +43,23 @@ namespace Controller{
if ((*limitIt)["type"].asString() == "hard"){
Storage["streams"][streamName]["hardlimit_active"] = true;
}
if ((*limitIt).isMember("triggered")){
continue;
}
if ((*limitIt).isMember("triggered")){continue;}
if ((*limitIt)["type"].asString() == "soft"){
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " triggered.");
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() +
" for stream " + streamName + " triggered.");
}else{
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " triggered.");
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() +
" for stream " + streamName + " triggered.");
}
(*limitIt)["triggered"] = true;
}else{
if ( !(*limitIt).isMember("triggered")){
continue;
}
if (!(*limitIt).isMember("triggered")){continue;}
if ((*limitIt)["type"].asString() == "soft"){
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() +
" <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
}else{
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() +
" <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
}
(*limitIt).removeMember("triggered");
}
@ -71,12 +67,12 @@ namespace Controller{
}
void checkServerLimits(){
int currentKbps = 0;
int connectedUsers = 0;
std::map<std::string, long long> strmUsers;
std::map<std::string, long long> strmBandw;
/*
if (curConns.size()){
for (std::map<unsigned long, statStorage>::iterator it = curConns.begin(); it != curConns.end(); it++){
@ -91,22 +87,18 @@ namespace Controller{
}
}
*/
//check stream limits
// check stream limits
if (Storage["streams"].size()){
jsonForEach(Storage["streams"], strmIt){
checkStreamLimits(strmIt.key(), strmBandw[strmIt.key()], strmUsers[strmIt.key()]);
}
}
Storage["config"].removeMember("hardlimit_active");
if ( !Storage["config"]["limits"].size()){
return;
}
if ( !Storage["streams"].size()){
return;
}
if (!Storage["config"]["limits"].size()){return;}
if (!Storage["streams"].size()){return;}
jsonForEach(Storage["config"]["limits"], limitIt){
bool triggerLimit = false;
if ((*limitIt)["name"].asString() == "users" && connectedUsers >= (*limitIt)["value"].asInt()){
@ -119,66 +111,52 @@ namespace Controller{
if ((*limitIt)["type"].asString() == "hard"){
Storage["config"]["hardlimit_active"] = true;
}
if ((*limitIt).isMember("triggered")){
continue;
}
if ((*limitIt).isMember("triggered")){continue;}
if ((*limitIt)["type"].asString() == "soft"){
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " triggered.");
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() +
" <= " + (*limitIt)["value"].asString() + " triggered.");
}else{
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " triggered.");
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() +
" <= " + (*limitIt)["value"].asString() + " triggered.");
}
(*limitIt)["triggered"] = true;
}else{
if ( !(*limitIt).isMember("triggered")){
continue;
}
if (!(*limitIt).isMember("triggered")){continue;}
if ((*limitIt)["type"].asString() == "soft"){
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " reset.");
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() +
" <= " + (*limitIt)["value"].asString() + " reset.");
}else{
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " reset.");
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() +
" <= " + (*limitIt)["value"].asString() + " reset.");
}
(*limitIt).removeMember("triggered");
}
}
}
bool onList(std::string ip, std::string list){
if (list == ""){
return false;
}
if (list == ""){return false;}
std::string entry;
std::string lowerIpv6;//lower-case
std::string upperIpv6;//full-caps
std::string lowerIpv6; // lower-case
std::string upperIpv6; // full-caps
do{
entry = list.substr(0,list.find(" "));//make sure we have a single entry
entry = list.substr(0, list.find(" ")); // make sure we have a single entry
lowerIpv6 = "::ffff:" + entry;
upperIpv6 = "::FFFF:" + entry;
if (entry == ip || lowerIpv6 == ip || upperIpv6 == ip){
return true;
}
if (entry == ip || lowerIpv6 == ip || upperIpv6 == ip){return true;}
long long unsigned int starPos = entry.find("*");
if (starPos == std::string::npos){
if (ip == entry){
return true;
}
if (ip == entry){return true;}
}else{
if (starPos == 0){//beginning of the filter
if (ip.substr(ip.length() - entry.size() - 1) == entry.substr(1)){
return true;
}
if (starPos == 0){// beginning of the filter
if (ip.substr(ip.length() - entry.size() - 1) == entry.substr(1)){return true;}
}else{
if (starPos == entry.size() - 1){//end of the filter
if (ip.find(entry.substr(0, entry.size() - 1)) == 0 ){
return true;
}
if (ip.find(entry.substr(0, lowerIpv6.size() - 1)) == 0 ){
return true;
}
if (ip.find(entry.substr(0, upperIpv6.size() - 1)) == 0 ){
return true;
}
if (starPos == entry.size() - 1){// end of the filter
if (ip.find(entry.substr(0, entry.size() - 1)) == 0){return true;}
if (ip.find(entry.substr(0, lowerIpv6.size() - 1)) == 0){return true;}
if (ip.find(entry.substr(0, upperIpv6.size() - 1)) == 0){return true;}
}else{
Log("CONF","Invalid list entry detected: " + entry);
Log("CONF", "Invalid list entry detected: " + entry);
}
}
}
@ -186,44 +164,38 @@ namespace Controller{
}while (list != "");
return false;
}
std::string hostLookup(std::string ip){
struct sockaddr_in6 sa;
char hostName[1024];
char service[20];
if (inet_pton(AF_INET6, ip.c_str(), &(sa.sin6_addr)) != 1){
return "\n";
}
if (inet_pton(AF_INET6, ip.c_str(), &(sa.sin6_addr)) != 1){return "\n";}
sa.sin6_family = AF_INET6;
sa.sin6_port = 0;
sa.sin6_flowinfo = 0;
sa.sin6_scope_id = 0;
int tmpRet = getnameinfo((struct sockaddr*)&sa, sizeof sa, hostName, sizeof hostName, service, sizeof service, NI_NAMEREQD );
if ( tmpRet == 0){
return hostName;
}
int tmpRet = getnameinfo((struct sockaddr *)&sa, sizeof sa, hostName, sizeof hostName, service,
sizeof service, NI_NAMEREQD);
if (tmpRet == 0){return hostName;}
return "";
}
bool isBlacklisted(std::string host, std::string streamName, int timeConnected){
std::string myHostName = hostLookup(host);
if (myHostName == "\n"){
return false;
}
if (myHostName == "\n"){return false;}
bool hasWhitelist = false;
bool hostOnWhitelist = false;
if (Storage["streams"].isMember(streamName)){
if (Storage["streams"][streamName].isMember("limits") && Storage["streams"][streamName]["limits"].size()){
if (Storage["streams"][streamName].isMember("limits") &&
Storage["streams"][streamName]["limits"].size()){
jsonForEach(Storage["streams"][streamName]["limits"], limitIt){
if ((*limitIt)["name"].asString() == "host"){
if ((*limitIt)["value"].asString()[0] == '+'){
if (!onList(host, (*limitIt)["value"].asString().substr(1))){
if (myHostName == ""){
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){
return true;
}
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){return true;}
}else{
if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){
if (!onList(myHostName, (*limitIt)["value"].asString().substr(1))){
if ((*limitIt)["type"].asString() == "hard"){
Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName);
return true;
@ -263,11 +235,9 @@ namespace Controller{
if ((*limitIt)["value"].asString()[0] == '+'){
if (!onList(host, (*limitIt)["value"].asString().substr(1))){
if (myHostName == ""){
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){
return true;
}
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){return true;}
}else{
if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){
if (!onList(myHostName, (*limitIt)["value"].asString().substr(1))){
if ((*limitIt)["type"].asString() == "hard"){
Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName);
return true;
@ -310,4 +280,4 @@ namespace Controller{
return false;
}
}
}// namespace Controller

View file

@ -1,6 +1,6 @@
#pragma once
#include <mist/json.h>
#include <map>
#include <mist/json.h>
#include <string>
/*LTS-START*/
@ -18,4 +18,4 @@ namespace Controller{
std::string hostLookup(std::string ip);
bool onList(std::string ip, std::string list);
std::string getCountry(std::string ip);
}
}// namespace Controller

View file

@ -23,7 +23,7 @@ namespace Controller{
/// Immediately starts a push for the given stream to the given target.
/// Simply calls Util::startPush and stores the resulting PID in the local activePushes map.
void startPush(const std::string &stream, std::string &target){
//Cancel if already active
// Cancel if already active
if (isPushActive(stream, target)){return;}
std::string originalTarget = target;
pid_t ret = Util::startPush(stream, target);
@ -40,13 +40,13 @@ namespace Controller{
/// Returns true if the push is currently active, false otherwise.
bool isPushActive(const std::string &streamname, const std::string &target){
while (Controller::conf.is_active && !pushListRead){
Util::sleep(100);
}
while (Controller::conf.is_active && !pushListRead){Util::sleep(100);}
std::set<pid_t> toWipe;
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
if (Util::Procs::isActive(it->first)){
if (it->second[1u].asStringRef() == streamname && it->second[2u].asStringRef() == target){return true;}
if (it->second[1u].asStringRef() == streamname && it->second[2u].asStringRef() == target){
return true;
}
}else{
toWipe.insert(it->first);
}
@ -61,13 +61,13 @@ namespace Controller{
/// Stops any pushes matching the stream name (pattern) and target
void stopActivePushes(const std::string &streamname, const std::string &target){
while (Controller::conf.is_active && !pushListRead){
Util::sleep(100);
}
while (Controller::conf.is_active && !pushListRead){Util::sleep(100);}
std::set<pid_t> toWipe;
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
if (Util::Procs::isActive(it->first)){
if (it->second[2u].asStringRef() == target && (it->second[1u].asStringRef() == streamname || (*streamname.rbegin() == '+' && it->second[1u].asStringRef().substr(0, streamname.size()) == streamname))){
if (it->second[2u].asStringRef() == target &&
(it->second[1u].asStringRef() == streamname ||
(*streamname.rbegin() == '+' && it->second[1u].asStringRef().substr(0, streamname.size()) == streamname))){
Util::Procs::Stop(it->first);
}
}else{
@ -87,31 +87,30 @@ namespace Controller{
}
/// Compactly writes the list of pushes to a pointer, assumed to be 8MiB in size
static void writePushList(char * pwo){
char * max = pwo + 8*1024*1024 - 4;
static void writePushList(char *pwo){
char *max = pwo + 8 * 1024 * 1024 - 4;
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
//check if the whole entry will fit
unsigned int entrylen = 4+2+it->second[1u].asStringRef().size()+2+it->second[2u].asStringRef().size()+2+it->second[3u].asStringRef().size();
if (pwo+entrylen >= max){return;}
//write the pid as a 32 bits unsigned integer
// check if the whole entry will fit
unsigned int entrylen = 4 + 2 + it->second[1u].asStringRef().size() + 2 +
it->second[2u].asStringRef().size() + 2 + it->second[3u].asStringRef().size();
if (pwo + entrylen >= max){return;}
// write the pid as a 32 bits unsigned integer
Bit::htobl(pwo, it->first);
pwo += 4;
//write the streamname, original target and target, 2-byte-size-prepended
// write the streamname, original target and target, 2-byte-size-prepended
for (unsigned int i = 1; i < 4; ++i){
const std::string &itm = it->second[i].asStringRef();
Bit::htobs(pwo, itm.size());
memcpy(pwo+2, itm.data(), itm.size());
pwo += 2+itm.size();
memcpy(pwo + 2, itm.data(), itm.size());
pwo += 2 + itm.size();
}
}
//if it fits, write an ending zero to indicate end of page
if (pwo <= max){
Bit::htobl(pwo, 0);
}
// if it fits, write an ending zero to indicate end of page
if (pwo <= max){Bit::htobl(pwo, 0);}
}
///Reads the list of pushes from a pointer, assumed to end in four zeroes
static void readPushList(char * pwo){
/// Reads the list of pushes from a pointer, assumed to end in four zeroes
static void readPushList(char *pwo){
activePushes.clear();
pid_t p = Bit::btohl(pwo);
HIGH_MSG("Recovering pushes: %" PRIu32, (uint32_t)p);
@ -121,8 +120,8 @@ namespace Controller{
pwo += 4;
for (uint8_t i = 0; i < 3; ++i){
uint16_t l = Bit::btohs(pwo);
push.append(std::string(pwo+2, l));
pwo += 2+l;
push.append(std::string(pwo + 2, l));
pwo += 2 + l;
}
INFO_MSG("Recovered push: %s", push.toString().c_str());
Util::Procs::remember(p);
@ -135,11 +134,11 @@ namespace Controller{
/// Loops, checking every second if any pushes need restarting.
void pushCheckLoop(void *np){
{
IPC::sharedPage pushReadPage("MstPush", 8*1024*1024, false, false);
IPC::sharedPage pushReadPage("MstPush", 8 * 1024 * 1024, false, false);
if (pushReadPage.mapped){readPushList(pushReadPage.mapped);}
}
pushListRead = true;
IPC::sharedPage pushPage("MstPush", 8*1024*1024, true, false);
IPC::sharedPage pushPage("MstPush", 8 * 1024 * 1024, true, false);
while (Controller::conf.is_active){
// this scope prevents the configMutex from being locked constantly
{
@ -149,7 +148,8 @@ namespace Controller{
long long curCount = 0;
jsonForEach(Controller::Storage["autopushes"], it){
if (it->size() > 3 && (*it)[3u].asInt() < Util::epoch()){
INFO_MSG("Deleting autopush from %s to %s because end time passed", (*it)[0u].asStringRef().c_str(), (*it)[1u].asStringRef().c_str());
INFO_MSG("Deleting autopush from %s to %s because end time passed",
(*it)[0u].asStringRef().c_str(), (*it)[1u].asStringRef().c_str());
stopActivePushes((*it)[0u], (*it)[1u]);
removePush(*it);
break;
@ -173,7 +173,8 @@ namespace Controller{
const std::string &pStr = (*it)[0u].asStringRef();
std::set<std::string> activeStreams = Controller::getActiveStreams(pStr);
if (activeStreams.size()){
for (std::set<std::string>::iterator jt = activeStreams.begin(); jt != activeStreams.end(); ++jt){
for (std::set<std::string>::iterator jt = activeStreams.begin();
jt != activeStreams.end(); ++jt){
std::string streamname = *jt;
std::string target = (*it)[1u];
if (pStr == streamname || (*pStr.rbegin() == '+' && streamname.substr(0, pStr.size()) == pStr)){
@ -201,10 +202,10 @@ namespace Controller{
}
Util::wait(1000); // wait at least a second
}
//keep the pushPage if we are restarting, so we can restore state from it
// keep the pushPage if we are restarting, so we can restore state from it
if (Util::Config::is_restarting){
pushPage.master = false;
//forget about all pushes, so they keep running
// forget about all pushes, so they keep running
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
Util::Procs::forget(it->first);
}
@ -250,7 +251,8 @@ namespace Controller{
}
long long epo = Util::epoch();
if (newPush.size() > 3 && newPush[3u].asInt() <= epo){
WARN_MSG("Automatic push not added: removal time is in the past! (%" PRId64 " <= %" PRIu64 ")", newPush[3u].asInt(), Util::epoch());
WARN_MSG("Automatic push not added: removal time is in the past! (%" PRId64 " <= %" PRIu64 ")",
newPush[3u].asInt(), Util::epoch());
return;
}
bool edited = false;
@ -333,10 +335,13 @@ namespace Controller{
void pushSettings(const JSON::Value &request, JSON::Value &response){
if (request.isObject()){
if (request.isMember("wait")){Controller::Storage["push_settings"]["wait"] = request["wait"].asInt();}
if (request.isMember("maxspeed")){Controller::Storage["push_settings"]["maxspeed"] = request["maxspeed"].asInt();}
if (request.isMember("wait")){
Controller::Storage["push_settings"]["wait"] = request["wait"].asInt();
}
if (request.isMember("maxspeed")){
Controller::Storage["push_settings"]["maxspeed"] = request["maxspeed"].asInt();
}
}
response = Controller::Storage["push_settings"];
}
}
}// namespace Controller

View file

@ -22,5 +22,4 @@ namespace Controller{
// for storing/retrieving settings
void pushSettings(const JSON::Value &request, JSON::Value &response);
}
}// namespace Controller

File diff suppressed because it is too large Load diff

View file

@ -1,126 +1,117 @@
#pragma once
#include <mist/shared_memory.h>
#include <mist/timing.h>
#include <mist/defines.h>
#include <mist/json.h>
#include <mist/tinythread.h>
#include <mist/http_parser.h>
#include <mist/socket.h>
#include <string>
#include <map>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <mist/json.h>
#include <mist/shared_memory.h>
#include <mist/socket.h>
#include <mist/timing.h>
#include <mist/tinythread.h>
#include <string>
/// The STAT_CUTOFF define sets how many seconds of statistics history is kept.
#define STAT_CUTOFF 600
namespace Controller{
namespace Controller {
extern bool killOnExit;
extern unsigned int maxConnsPerIP;
///This function is ran whenever a stream becomes active.
/// This function is ran whenever a stream becomes active.
void streamStarted(std::string stream);
///This function is ran whenever a stream becomes inactive.
/// This function is ran whenever a stream becomes inactive.
void streamStopped(std::string stream);
void updateBandwidthConfig();
struct statLog {
struct statLog{
uint64_t time;
uint64_t lastSecond;
uint64_t down;
uint64_t up;
};
enum sessType {
SESS_UNSET = 0,
SESS_INPUT,
SESS_OUTPUT,
SESS_VIEWER
};
enum sessType{SESS_UNSET = 0, SESS_INPUT, SESS_OUTPUT, SESS_VIEWER};
/// This is a comparison and storage class that keeps sessions apart from each other.
/// Whenever two of these objects are not equal, it will create a new session.
class sessIndex {
public:
sessIndex(std::string host, unsigned int crc, std::string streamName, std::string connector);
sessIndex(IPC::statExchange & data);
sessIndex();
std::string ID;
std::string host;
unsigned int crc;
std::string streamName;
std::string connector;
bool operator== (const sessIndex &o) const;
bool operator!= (const sessIndex &o) const;
bool operator> (const sessIndex &o) const;
bool operator<= (const sessIndex &o) const;
bool operator< (const sessIndex &o) const;
bool operator>= (const sessIndex &o) const;
std::string toStr();
};
class statStorage {
public:
void update(IPC::statExchange & data);
bool hasDataFor(unsigned long long);
statLog & getDataFor(unsigned long long);
std::map<unsigned long long, statLog> log;
};
/// A session class that keeps track of both current and archived connections.
/// Allows for moving of connections to another session.
class statSession {
private:
uint64_t firstActive;
uint64_t firstSec;
uint64_t lastSec;
uint64_t wipedUp;
uint64_t wipedDown;
std::deque<statStorage> oldConns;
sessType sessionType;
bool tracked;
uint8_t noBWCount;///<Set to 2 when not to count for external bandwidth
public:
statSession();
uint32_t invalidate();
uint32_t kill();
char sync;
std::map<uint64_t, statStorage> curConns;
std::set<std::string> tags;
sessType getSessType();
void wipeOld(uint64_t);
void finish(uint64_t index);
void switchOverTo(statSession & newSess, uint64_t index);
void update(uint64_t index, IPC::statExchange & data);
void ping(const sessIndex & index, uint64_t disconnectPoint);
uint64_t getStart();
uint64_t getEnd();
bool isViewerOn(uint64_t time);
bool isViewer();
bool hasDataFor(uint64_t time);
bool hasData();
uint64_t getConnTime(uint64_t time);
uint64_t getLastSecond(uint64_t time);
uint64_t getDown(uint64_t time);
uint64_t getUp();
uint64_t getDown();
uint64_t getUp(uint64_t time);
uint64_t getBpsDown(uint64_t time);
uint64_t getBpsUp(uint64_t time);
uint64_t getBpsDown(uint64_t start, uint64_t end);
uint64_t getBpsUp(uint64_t start, uint64_t end);
class sessIndex{
public:
sessIndex(std::string host, unsigned int crc, std::string streamName, std::string connector);
sessIndex(IPC::statExchange &data);
sessIndex();
std::string ID;
std::string host;
unsigned int crc;
std::string streamName;
std::string connector;
bool operator==(const sessIndex &o) const;
bool operator!=(const sessIndex &o) const;
bool operator>(const sessIndex &o) const;
bool operator<=(const sessIndex &o) const;
bool operator<(const sessIndex &o) const;
bool operator>=(const sessIndex &o) const;
std::string toStr();
};
class statStorage{
public:
void update(IPC::statExchange &data);
bool hasDataFor(unsigned long long);
statLog &getDataFor(unsigned long long);
std::map<unsigned long long, statLog> log;
};
/// A session class that keeps track of both current and archived connections.
/// Allows for moving of connections to another session.
class statSession{
private:
uint64_t firstActive;
uint64_t firstSec;
uint64_t lastSec;
uint64_t wipedUp;
uint64_t wipedDown;
std::deque<statStorage> oldConns;
sessType sessionType;
bool tracked;
uint8_t noBWCount; ///< Set to 2 when not to count for external bandwidth
public:
statSession();
uint32_t invalidate();
uint32_t kill();
char sync;
std::map<uint64_t, statStorage> curConns;
std::set<std::string> tags;
sessType getSessType();
void wipeOld(uint64_t);
void finish(uint64_t index);
void switchOverTo(statSession &newSess, uint64_t index);
void update(uint64_t index, IPC::statExchange &data);
void ping(const sessIndex &index, uint64_t disconnectPoint);
uint64_t getStart();
uint64_t getEnd();
bool isViewerOn(uint64_t time);
bool isViewer();
bool hasDataFor(uint64_t time);
bool hasData();
uint64_t getConnTime(uint64_t time);
uint64_t getLastSecond(uint64_t time);
uint64_t getDown(uint64_t time);
uint64_t getUp();
uint64_t getDown();
uint64_t getUp(uint64_t time);
uint64_t getBpsDown(uint64_t time);
uint64_t getBpsUp(uint64_t time);
uint64_t getBpsDown(uint64_t start, uint64_t end);
uint64_t getBpsUp(uint64_t start, uint64_t end);
};
extern std::map<sessIndex, statSession> sessions;
extern std::map<unsigned long, sessIndex> connToSession;
extern tthread::mutex statsMutex;
struct triggerLog {
struct triggerLog{
uint64_t totalCount;
uint64_t failCount;
uint64_t ms;
@ -128,24 +119,23 @@ namespace Controller {
extern std::map<std::string, triggerLog> triggerStats;
std::set<std::string> getActiveStreams(const std::string & prefix = "");
void parseStatistics(char * data, size_t len, unsigned int id);
void killStatistics(char * data, size_t len, unsigned int id);
void fillClients(JSON::Value & req, JSON::Value & rep);
void fillActive(JSON::Value & req, JSON::Value & rep, bool onlyNow = false);
void fillTotals(JSON::Value & req, JSON::Value & rep);
void SharedMemStats(void * config);
void sessions_invalidate(const std::string & streamname);
void sessions_shutdown(JSON::Iter & i);
void sessId_shutdown(const std::string & sessId);
void tag_shutdown(const std::string & tag);
void sessId_tag(const std::string & sessId, const std::string & tag);
void sessions_shutdown(const std::string & streamname, const std::string & protocol = "");
std::set<std::string> getActiveStreams(const std::string &prefix = "");
void parseStatistics(char *data, size_t len, unsigned int id);
void killStatistics(char *data, size_t len, unsigned int id);
void fillClients(JSON::Value &req, JSON::Value &rep);
void fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow = false);
void fillTotals(JSON::Value &req, JSON::Value &rep);
void SharedMemStats(void *config);
void sessions_invalidate(const std::string &streamname);
void sessions_shutdown(JSON::Iter &i);
void sessId_shutdown(const std::string &sessId);
void tag_shutdown(const std::string &tag);
void sessId_tag(const std::string &sessId, const std::string &tag);
void sessions_shutdown(const std::string &streamname, const std::string &protocol = "");
bool hasViewers(std::string streamName);
void writeSessionCache(); /*LTS*/
#define PROMETHEUS_TEXT 0
#define PROMETHEUS_JSON 1
void handlePrometheus(HTTP::Parser & H, Socket::Connection & conn, int mode);
}
void handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int mode);
}// namespace Controller

View file

@ -1,14 +1,13 @@
#include <sys/stat.h>
#include <iostream>
#include <fstream>
#include "controller_capabilities.h"
#include "controller_storage.h"
#include <algorithm>
#include <mist/timing.h>
#include <mist/shared_memory.h>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/shared_memory.h>
#include <mist/timing.h>
#include <mist/triggers.h> //LTS
#include "controller_storage.h"
#include "controller_capabilities.h"
#include <sys/stat.h>
///\brief Holds everything unique to the controller.
namespace Controller{
@ -26,29 +25,23 @@ namespace Controller{
uint32_t maxLogsRecs = 0;
uint32_t maxAccsRecs = 0;
uint64_t firstLog = 0;
IPC::sharedPage * shmLogs = 0;
Util::RelAccX * rlxLogs = 0;
IPC::sharedPage * shmAccs = 0;
Util::RelAccX * rlxAccs = 0;
IPC::sharedPage * shmStrm = 0;
Util::RelAccX * rlxStrm = 0;
IPC::sharedPage *shmLogs = 0;
Util::RelAccX *rlxLogs = 0;
IPC::sharedPage *shmAccs = 0;
Util::RelAccX *rlxAccs = 0;
IPC::sharedPage *shmStrm = 0;
Util::RelAccX *rlxStrm = 0;
Util::RelAccX * logAccessor(){
return rlxLogs;
}
Util::RelAccX *logAccessor(){return rlxLogs;}
Util::RelAccX * accesslogAccessor(){
return rlxAccs;
}
Util::RelAccX *accesslogAccessor(){return rlxAccs;}
Util::RelAccX * streamsAccessor(){
return rlxStrm;
}
Util::RelAccX *streamsAccessor(){return rlxStrm;}
///\brief Store and print a log message.
///\param kind The type of message.
///\param message The message to be logged.
void Log(const std::string & kind, const std::string & message, const std::string & stream, bool noWriteToLog){
void Log(const std::string &kind, const std::string &message, const std::string &stream, bool noWriteToLog){
if (noWriteToLog){
tthread::lock_guard<tthread::mutex> guard(logMutex);
JSON::Value m;
@ -61,15 +54,13 @@ namespace Controller{
Storage["log"].shrink(100); // limit to 100 log messages
logCounter++;
if (rlxLogs && rlxLogs->isReady()){
if (!firstLog){
firstLog = logCounter;
}
if (!firstLog){firstLog = logCounter;}
rlxLogs->setRCount(logCounter > maxLogsRecs ? maxLogsRecs : logCounter);
rlxLogs->setDeleted(logCounter > rlxLogs->getRCount() ? logCounter - rlxLogs->getRCount() : firstLog);
rlxLogs->setInt("time", logTime, logCounter-1);
rlxLogs->setString("kind", kind, logCounter-1);
rlxLogs->setString("msg", message, logCounter-1);
rlxLogs->setString("strm", stream, logCounter-1);
rlxLogs->setInt("time", logTime, logCounter - 1);
rlxLogs->setString("kind", kind, logCounter - 1);
rlxLogs->setString("msg", message, logCounter - 1);
rlxLogs->setString("strm", stream, logCounter - 1);
rlxLogs->setEndPos(logCounter);
}
}else{
@ -77,10 +68,12 @@ namespace Controller{
}
}
void logAccess(const std::string & sessId, const std::string & strm, const std::string & conn, const std::string & host, uint64_t duration, uint64_t up, uint64_t down, const std::string & tags){
void logAccess(const std::string &sessId, const std::string &strm, const std::string &conn,
const std::string &host, uint64_t duration, uint64_t up, uint64_t down,
const std::string &tags){
if (rlxAccs && rlxAccs->isReady()){
uint64_t newEndPos = rlxAccs->getEndPos();
rlxAccs->setRCount(newEndPos+1 > maxLogsRecs ? maxAccsRecs : newEndPos+1);
rlxAccs->setRCount(newEndPos + 1 > maxLogsRecs ? maxAccsRecs : newEndPos + 1);
rlxAccs->setDeleted(newEndPos + 1 > maxAccsRecs ? newEndPos + 1 - maxAccsRecs : 0);
rlxAccs->setInt("time", Util::epoch(), newEndPos);
rlxAccs->setString("session", sessId, newEndPos);
@ -95,16 +88,23 @@ namespace Controller{
}
if (Triggers::shouldTrigger("USER_END", strm)){
std::stringstream plgen;
plgen << sessId << "\n" << strm << "\n" << conn << "\n" << host << "\n" << duration << "\n" << up << "\n" << down << "\n" << tags;
plgen << sessId << "\n"
<< strm << "\n"
<< conn << "\n"
<< host << "\n"
<< duration << "\n"
<< up << "\n"
<< down << "\n"
<< tags;
std::string payload = plgen.str();
Triggers::doTrigger("USER_END", payload, strm);
}
}
void normalizeTrustedProxies(JSON::Value & tp){
//First normalize to arrays
void normalizeTrustedProxies(JSON::Value &tp){
// First normalize to arrays
if (!tp.isArray()){tp.append(tp.asString());}
//Now, wipe any empty entries, and convert spaces to array entries
// Now, wipe any empty entries, and convert spaces to array entries
std::set<std::string> n;
jsonForEach(tp, jit){
if (!jit->isString()){*jit = jit->asString();}
@ -116,16 +116,14 @@ namespace Controller{
while (tmp.find(' ') != std::string::npos){
size_t p = tmp.find(' ');
n.insert(tmp.substr(0, p));
tmp.erase(0, p+1);
tmp.erase(0, p + 1);
}
if (tmp.size()){n.insert(tmp);}
}
n.erase("");
//Re-write the entire array, which is now normalized
// Re-write the entire array, which is now normalized
tp.shrink(0);
for (std::set<std::string>::iterator it = n.begin(); it != n.end(); ++it){
tp.append(*it);
}
for (std::set<std::string>::iterator it = n.begin(); it != n.end(); ++it){tp.append(*it);}
}
///\brief Write contents to Filename
@ -141,7 +139,7 @@ namespace Controller{
void initState(){
tthread::lock_guard<tthread::mutex> guard(logMutex);
shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024*1024, true);//max 1M of logs cached
shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024 * 1024, true); // max 1M of logs cached
if (!shmLogs->mapped){
FAIL_MSG("Could not open memory page for logs buffer");
return;
@ -156,9 +154,9 @@ namespace Controller{
rlxLogs->addField("strm", RAX_128STRING);
rlxLogs->setReady();
}
maxLogsRecs = (1024*1024 - rlxLogs->getOffset()) / rlxLogs->getRSize();
maxLogsRecs = (1024 * 1024 - rlxLogs->getOffset()) / rlxLogs->getRSize();
shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024*1024, true);//max 1M of accesslogs cached
shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024 * 1024, true); // max 1M of accesslogs cached
if (!shmAccs->mapped){
FAIL_MSG("Could not open memory page for access logs buffer");
return;
@ -176,9 +174,9 @@ namespace Controller{
rlxAccs->addField("tags", RAX_256STRING);
rlxAccs->setReady();
}
maxAccsRecs = (1024*1024 - rlxAccs->getOffset()) / rlxAccs->getRSize();
maxAccsRecs = (1024 * 1024 - rlxAccs->getOffset()) / rlxAccs->getRSize();
shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024*1024, true);//max 1M of stream data
shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024 * 1024, true); // max 1M of stream data
if (!shmStrm->mapped){
FAIL_MSG("Could not open memory page for stream data");
return;
@ -192,7 +190,7 @@ namespace Controller{
rlxStrm->addField("outputs", RAX_64UINT);
rlxStrm->setReady();
}
rlxStrm->setRCount((1024*1024 - rlxStrm->getOffset()) / rlxStrm->getRSize());
rlxStrm->setRCount((1024 * 1024 - rlxStrm->getOffset()) / rlxStrm->getRSize());
}
void deinitState(bool leaveBehind){
@ -209,7 +207,7 @@ namespace Controller{
shmAccs->master = false;
shmStrm->master = false;
}
Util::RelAccX * tmp = rlxLogs;
Util::RelAccX *tmp = rlxLogs;
rlxLogs = 0;
delete tmp;
delete shmLogs;
@ -239,7 +237,7 @@ namespace Controller{
skip.insert("online");
skip.insert("error");
tmp.assignFrom(Controller::Storage, skip);
if ( !Controller::WriteFile(Controller::conf.getString("configFile"), tmp.toString())){
if (!Controller::WriteFile(Controller::conf.getString("configFile"), tmp.toString())){
ERROR_MSG("Error writing config to %s", Controller::conf.getString("configFile").c_str());
std::cout << "**Config**" << std::endl;
std::cout << tmp.toString() << std::endl;
@ -247,12 +245,12 @@ namespace Controller{
}
}
void writeCapabilities(){
std::string temp = capabilities.toPacked();
static IPC::sharedPage mistCapaOut(SHM_CAPA, temp.size()+100, true, false);
static IPC::sharedPage mistCapaOut(SHM_CAPA, temp.size() + 100, true, false);
if (!mistCapaOut.mapped){
FAIL_MSG("Could not open capabilities config for writing! Is shared memory enabled on your system?");
FAIL_MSG("Could not open capabilities config for writing! Is shared memory enabled on your "
"system?");
return;
}
Util::RelAccX A(mistCapaOut.mapped, false);
@ -270,7 +268,7 @@ namespace Controller{
if (Storage["config"]["trustedproxy"].isArray()){
jsonForEachConst(Storage["config"]["trustedproxy"], jit){
if (tmpProxy.size()){
tmpProxy += " "+jit->asString();
tmpProxy += " " + jit->asString();
}else{
tmpProxy = jit->asString();
}
@ -280,11 +278,12 @@ namespace Controller{
}
if (proxy_written != tmpProxy){
proxy_written = tmpProxy;
static IPC::sharedPage mistProxOut(SHM_PROXY, proxy_written.size()+100, true, false);
static IPC::sharedPage mistProxOut(SHM_PROXY, proxy_written.size() + 100, true, false);
mistProxOut.close();
mistProxOut.init(SHM_PROXY, proxy_written.size()+100, true, false);
mistProxOut.init(SHM_PROXY, proxy_written.size() + 100, true, false);
if (!mistProxOut.mapped){
FAIL_MSG("Could not open trusted proxy config for writing! Is shared memory enabled on your system?");
FAIL_MSG("Could not open trusted proxy config for writing! Is shared memory enabled on "
"your system?");
return;
}else{
Util::RelAccX A(mistProxOut.mapped, false);
@ -303,11 +302,12 @@ namespace Controller{
if (Storage["config"]["protocols"].compareExcept(proto_written, skip)){return;}
proto_written.assignFrom(Storage["config"]["protocols"], skip);
std::string temp = proto_written.toPacked();
static IPC::sharedPage mistProtoOut(SHM_PROTO, temp.size()+100, true, false);
static IPC::sharedPage mistProtoOut(SHM_PROTO, temp.size() + 100, true, false);
mistProtoOut.close();
mistProtoOut.init(SHM_PROTO, temp.size()+100, true, false);
mistProtoOut.init(SHM_PROTO, temp.size() + 100, true, false);
if (!mistProtoOut.mapped){
FAIL_MSG("Could not open protocol config for writing! Is shared memory enabled on your system?");
FAIL_MSG(
"Could not open protocol config for writing! Is shared memory enabled on your system?");
return;
}
// write config
@ -322,7 +322,7 @@ namespace Controller{
}
}
void writeStream(const std::string & sName, const JSON::Value & sConf){
void writeStream(const std::string &sName, const JSON::Value &sConf){
static std::map<std::string, JSON::Value> writtenStrms;
static std::map<std::string, IPC::sharedPage> pages;
static std::set<std::string> skip;
@ -338,12 +338,12 @@ namespace Controller{
}
if (!writtenStrms.count(sName) || !writtenStrms[sName].compareExcept(sConf, skip)){
writtenStrms[sName].assignFrom(sConf, skip);
IPC::sharedPage & P = pages[sName];
IPC::sharedPage &P = pages[sName];
std::string temp = writtenStrms[sName].toPacked();
P.close();
char tmpBuf[NAME_BUFFER_SIZE];
snprintf(tmpBuf, NAME_BUFFER_SIZE, SHM_STREAM_CONF, sName.c_str());
P.init(tmpBuf, temp.size()+100, true, false);
P.init(tmpBuf, temp.size() + 100, true, false);
if (!P){
writtenStrms.erase(sName);
pages.erase(sName);
@ -374,12 +374,10 @@ namespace Controller{
}
{
//Global configuration options, if any
// Global configuration options, if any
IPC::sharedPage globCfg;
globCfg.init(SHM_GLOBAL_CONF, 4096, false, false);
if (!globCfg.mapped){
globCfg.init(SHM_GLOBAL_CONF, 4096, true, false);
}
if (!globCfg.mapped){globCfg.init(SHM_GLOBAL_CONF, 4096, true, false);}
if (globCfg.mapped){
Util::RelAccX globAccX(globCfg.mapped, false);
if (!globAccX.isReady()){
@ -389,11 +387,10 @@ namespace Controller{
globAccX.setReady();
}
globAccX.setString("defaultStream", Storage["config"]["defaultStream"].asStringRef());
globCfg.master = false;//leave the page after closing
globCfg.master = false; // leave the page after closing
}
}
/*LTS-START*/
static std::map<std::string, IPC::sharedPage> pageForType; // should contain one page for every trigger type
static JSON::Value writtenTrigs;
@ -438,7 +435,9 @@ namespace Controller{
namesArray.append(tmpBuf, 4);
namesArray.append(shIt->asString());
}
if (namesArray.size()){memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));}
if (namesArray.size()){
memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));
}
}
}
}
@ -457,7 +456,9 @@ namespace Controller{
namesArray.append(tmpBuf, 4);
namesArray.append(shIt->asString());
}
if (namesArray.size()){memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));}
if (namesArray.size()){
memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));
}
}
}
if (triggIt->isMember("params") && !(*triggIt)["params"].isNull()){
@ -492,5 +493,4 @@ namespace Controller{
}
/*LTS-END*/
}
}
}// namespace Controller

View file

@ -1,44 +1,45 @@
#include <string>
#include <mist/json.h>
#include <mist/config.h>
#include <mist/json.h>
#include <mist/tinythread.h>
#include <mist/util.h>
#include <string>
namespace Controller {
extern std::string instanceId; ///<global storage of instanceId (previously uniqID) is set in controller.cpp
extern std::string prometheus; ///< Prometheus access string
extern std::string accesslog; ///< Where to write the access log
extern Util::Config conf;///< Global storage of configuration.
extern JSON::Value Storage; ///< Global storage of data.
extern tthread::mutex logMutex;///< Mutex for log thread.
extern tthread::mutex configMutex;///< Mutex for server config access.
extern bool configChanged; ///< Bool that indicates config must be written to SHM.
extern bool isTerminal;///< True if connected to a terminal and not a log file.
extern bool isColorized;///< True if we colorize the output
extern uint64_t logCounter; ///<Count of logged messages since boot
Util::RelAccX * logAccessor();
Util::RelAccX * accesslogAccessor();
Util::RelAccX * streamsAccessor();
namespace Controller{
extern std::string instanceId; ///< global storage of instanceId (previously uniqID) is set in controller.cpp
extern std::string prometheus; ///< Prometheus access string
extern std::string accesslog; ///< Where to write the access log
extern Util::Config conf; ///< Global storage of configuration.
extern JSON::Value Storage; ///< Global storage of data.
extern tthread::mutex logMutex; ///< Mutex for log thread.
extern tthread::mutex configMutex; ///< Mutex for server config access.
extern bool configChanged; ///< Bool that indicates config must be written to SHM.
extern bool isTerminal; ///< True if connected to a terminal and not a log file.
extern bool isColorized; ///< True if we colorize the output
extern uint64_t logCounter; ///< Count of logged messages since boot
Util::RelAccX *logAccessor();
Util::RelAccX *accesslogAccessor();
Util::RelAccX *streamsAccessor();
/// Store and print a log message.
void Log(const std::string & kind, const std::string & message, const std::string & stream = "", bool noWriteToLog = false);
void logAccess(const std::string & sessId, const std::string & strm, const std::string & conn, const std::string & host, uint64_t duration, uint64_t up, uint64_t down, const std::string & tags);
void normalizeTrustedProxies(JSON::Value & tp);
void Log(const std::string &kind, const std::string &message, const std::string &stream = "",
bool noWriteToLog = false);
void logAccess(const std::string &sessId, const std::string &strm, const std::string &conn,
const std::string &host, uint64_t duration, uint64_t up, uint64_t down,
const std::string &tags);
void normalizeTrustedProxies(JSON::Value &tp);
/// Write contents to Filename.
bool WriteFile(std::string Filename, std::string contents);
void writeConfigToDisk();
void handleMsg(void * err);
void handleMsg(void *err);
void initState();
void deinitState(bool leaveBehind);
void writeConfig();
void writeStream(const std::string & sName, const JSON::Value & sConf);
void writeStream(const std::string &sName, const JSON::Value &sConf);
void writeCapabilities();
void writeProtocols();
}
}// namespace Controller

View file

@ -1,87 +1,83 @@
#include <mist/procs.h>
#include <mist/config.h>
#include <mist/timing.h>
#include <mist/stream.h>
#include <mist/dtsc.h>
#include <mist/defines.h>
#include <mist/shared_memory.h>
#include "controller_streams.h"
#include "controller_capabilities.h"
#include "controller_storage.h"
#include "controller_statistics.h"
#include "controller_limits.h" /*LTS*/
#include "controller_statistics.h"
#include "controller_storage.h"
#include "controller_streams.h"
#include <map>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/dtsc.h>
#include <mist/procs.h>
#include <mist/shared_memory.h>
#include <mist/stream.h>
#include <mist/timing.h>
#include <mist/triggers.h> //LTS
#include <sys/stat.h>
#include <map>
///\brief Holds everything unique to the controller.
namespace Controller {
namespace Controller{
std::map<std::string, pid_t> inputProcesses;
///\brief Checks whether two streams are equal.
///\param one The first stream for the comparison.
///\param two The second stream for the comparison.
///\return True if the streams are equal, false otherwise.
bool streamsEqual(JSON::Value & one, JSON::Value & two){
bool streamsEqual(JSON::Value &one, JSON::Value &two){
if (one.isMember("source") != two.isMember("source") || one["source"] != two["source"]){
return false;
}
/// \todo Change this to use capabilities["inputs"] and only compare required/optional parameters.
/// \todo Maybe change this to check for correct source and/or required parameters.
//temporary: compare the two JSON::Value objects.
return one==two;
//nothing different? return true by default
//return true;
/// \todo Change this to use capabilities["inputs"] and only compare required/optional
/// parameters. \todo Maybe change this to check for correct source and/or required parameters.
// temporary: compare the two JSON::Value objects.
return one == two;
// nothing different? return true by default
// return true;
}
///\brief Checks the validity of a stream, updates internal stream status.
///\param name The name of the stream
///\param data The corresponding configuration values.
void checkStream(std::string name, JSON::Value & data){
void checkStream(std::string name, JSON::Value &data){
if (!data.isMember("name")){data["name"] = name;}
std::string prevState = data["error"].asStringRef();
data["online"] = (std::string)"Checking...";
data["online"] = (std::string) "Checking...";
data.removeMember("error");
data.removeNullMembers();
switch (Util::getStreamStatus(name)){
case STRMSTAT_OFF:
//Do nothing
break;
case STRMSTAT_INIT:
data["online"] = 2;
data["error"] = "Initializing...";
return;
case STRMSTAT_BOOT:
data["online"] = 2;
data["error"] = "Loading...";
return;
case STRMSTAT_WAIT:
data["online"] = 2;
data["error"] = "Waiting for data...";
return;
case STRMSTAT_READY:
data["online"] = 1;
return;
case STRMSTAT_SHUTDOWN:
data["online"] = 2;
data["error"] = "Shutting down...";
return;
default:
//Unknown state?
data["error"] = "Unrecognized stream state";
break;
case STRMSTAT_OFF:
// Do nothing
break;
case STRMSTAT_INIT:
data["online"] = 2;
data["error"] = "Initializing...";
return;
case STRMSTAT_BOOT:
data["online"] = 2;
data["error"] = "Loading...";
return;
case STRMSTAT_WAIT:
data["online"] = 2;
data["error"] = "Waiting for data...";
return;
case STRMSTAT_READY: data["online"] = 1; return;
case STRMSTAT_SHUTDOWN:
data["online"] = 2;
data["error"] = "Shutting down...";
return;
default:
// Unknown state?
data["error"] = "Unrecognized stream state";
break;
}
data["online"] = 0;
std::string URL;
if (data.isMember("channel") && data["channel"].isMember("URL")){
URL = data["channel"]["URL"].asString();
}
if (data.isMember("source")){
URL = data["source"].asString();
}
if (data.isMember("source")){URL = data["source"].asString();}
if (!URL.size()){
data["error"] = "Stream offline: Missing source parameter!";
if (data["error"].asStringRef() != prevState){
@ -89,14 +85,13 @@ namespace Controller {
}
return;
}
//Old style always on
if (data.isMember("udpport") && data["udpport"].asStringRef().size() && (!inputProcesses.count(name) || !Util::Procs::isRunning(inputProcesses[name]))){
const std::string & udpPort = data["udpport"].asStringRef();
const std::string & multicast = data["multicastinterface"].asStringRef();
URL = "tsudp://"+udpPort;
if (multicast.size()){
URL.append("/"+multicast);
}
// Old style always on
if (data.isMember("udpport") && data["udpport"].asStringRef().size() &&
(!inputProcesses.count(name) || !Util::Procs::isRunning(inputProcesses[name]))){
const std::string &udpPort = data["udpport"].asStringRef();
const std::string &multicast = data["multicastinterface"].asStringRef();
URL = "tsudp://" + udpPort;
if (multicast.size()){URL.append("/" + multicast);}
// False: start TS input
INFO_MSG("No TS input for stream %s, starting it: %s", name.c_str(), URL.c_str());
std::deque<std::string> command;
@ -108,35 +103,29 @@ namespace Controller {
int stdOut = 1;
int stdErr = 2;
pid_t program = Util::Procs::StartPiped(command, &stdIn, &stdOut, &stdErr);
if (program){
inputProcesses[name] = program;
}
if (program){inputProcesses[name] = program;}
}
//new style always on
// new style always on
if (data.isMember("always_on") && data["always_on"].asBool()){
INFO_MSG("Starting always-on input %s: %s", name.c_str(), URL.c_str());
std::map<std::string, std::string> empty_overrides;
pid_t program = 0;
Util::startInput(name, URL, true, false, empty_overrides, &program);
if (program){
inputProcesses[name] = program;
}
if (program){inputProcesses[name] = program;}
}
//non-VoD stream
// non-VoD stream
if (URL.substr(0, 1) != "/"){return;}
Util::streamVariables(URL, name, "");
//VoD-style stream
// VoD-style stream
struct stat fileinfo;
if (stat(URL.c_str(), &fileinfo) != 0){
if (stat(URL.c_str(), &fileinfo) != 0){
data["error"] = "Stream offline: Not found: " + URL;
if (data["error"].asStringRef() != prevState){
Log("BUFF", "Warning for VoD stream " + name + "! File not found: " + URL);
}
return;
}
if ( !data.isMember("error")){
data["error"] = "Available";
}
if (!data.isMember("error")){data["error"] = "Available";}
data["online"] = 2;
return;
}
@ -144,12 +133,10 @@ namespace Controller {
///\brief Checks all streams, restoring if needed.
///\param data The stream configuration for the server.
///\returns True if the server status changed
bool CheckAllStreams(JSON::Value & data){
jsonForEach(data, jit) {
checkStream(jit.key(), (*jit));
}
bool CheckAllStreams(JSON::Value &data){
jsonForEach(data, jit){checkStream(jit.key(), (*jit));}
//check for changes in config or streams
// check for changes in config or streams
static JSON::Value strlist;
if (strlist["config"] != Storage["config"] || strlist["streams"] != Storage["streams"]){
strlist["config"] = Storage["config"];
@ -158,31 +145,31 @@ namespace Controller {
}
return false;
}
///
/// \triggers
/// The `"STREAM_ADD"` trigger is stream-specific, and is ran whenever a new stream is added to the server configuration. If cancelled, the stream is not added. Its payload is:
/// \triggers
/// The `"STREAM_ADD"` trigger is stream-specific, and is ran whenever a new stream is added to
/// the server configuration. If cancelled, the stream is not added. Its payload is:
/// ~~~~~~~~~~~~~~~
/// streamname
/// configuration in JSON format
/// ~~~~~~~~~~~~~~~
/// The `"STREAM_CONFIG"` trigger is stream-specific, and is ran whenever a stream's configuration is changed. If cancelled, the configuration is not changed. Its payload is:
/// The `"STREAM_CONFIG"` trigger is stream-specific, and is ran whenever a stream's configuration
/// is changed. If cancelled, the configuration is not changed. Its payload is:
/// ~~~~~~~~~~~~~~~
/// streamname
/// configuration in JSON format
/// ~~~~~~~~~~~~~~~
///
void AddStreams(JSON::Value & in, JSON::Value & out){
//check for new streams and updates
jsonForEach(in, jit) {
///
void AddStreams(JSON::Value &in, JSON::Value &out){
// check for new streams and updates
jsonForEach(in, jit){
if (out.isMember(jit.key())){
if ( !streamsEqual((*jit), out[jit.key()])){
if (!streamsEqual((*jit), out[jit.key()])){
/*LTS-START*/
if(Triggers::shouldTrigger("STREAM_CONFIG")){
std::string payload = jit.key()+"\n"+jit->toString();
if (!Triggers::doTrigger("STREAM_CONFIG", payload, jit.key())){
continue;
}
if (Triggers::shouldTrigger("STREAM_CONFIG")){
std::string payload = jit.key() + "\n" + jit->toString();
if (!Triggers::doTrigger("STREAM_CONFIG", payload, jit.key())){continue;}
}
/*LTS-END*/
out[jit.key()] = (*jit);
@ -198,16 +185,15 @@ namespace Controller {
if (!checked.size()){
FAIL_MSG("Invalid stream name '%s'", jit.key().c_str());
}else{
FAIL_MSG("Invalid stream name '%s'. Suggested alternative: '%s'", jit.key().c_str(), checked.c_str());
FAIL_MSG("Invalid stream name '%s'. Suggested alternative: '%s'", jit.key().c_str(),
checked.c_str());
}
continue;
}
/*LTS-START*/
if(Triggers::shouldTrigger("STREAM_ADD")){
std::string payload = jit.key()+"\n"+jit->toString();
if (!Triggers::doTrigger("STREAM_ADD", payload, jit.key())){
continue;
}
if (Triggers::shouldTrigger("STREAM_ADD")){
std::string payload = jit.key() + "\n" + jit->toString();
if (!Triggers::doTrigger("STREAM_ADD", payload, jit.key())){continue;}
}
/*LTS-END*/
out[jit.key()] = (*jit);
@ -227,25 +213,28 @@ namespace Controller {
/// \api
/// `"streams"` requests take the form of:
/// ~~~~~~~~~~~~~~~{.js}
/// {
/// "streamname_here": { //name of the stream
/// "source": "/mnt/media/a.dtsc" //full path to a VoD file, or "push://" followed by the IP or hostname of the machine allowed to push live data. Empty means everyone is allowed to push live data.
/// "DVR": 30000 //optional. For live streams, indicates the requested minimum size of the available DVR buffer in milliseconds.
/// },
///{
/// "streamname_here":{//name of the stream
/// "source": "/mnt/media/a.dtsc" //full path to a VoD file, or "push://" followed by the IP
/// or hostname of the machine allowed to push live data. Empty means everyone is allowed to
/// push live data. "DVR": 30000 //optional. For live streams, indicates the requested minimum
/// size of the available DVR buffer in milliseconds.
///},
/// //the above structure repeated for all configured streams
/// }
///}
/// ~~~~~~~~~~~~~~~
/// and are responded to as:
/// ~~~~~~~~~~~~~~~{.js}
/// {
/// "streamname_here": { //name of the configured stream
/// "error": "Available", //error state, if any. "Available" is a special value for VoD streams, indicating it has no current viewers (is not active), but is available for activation.
/// "h_meta": 1398113185, //unix time the stream header (if any) was last processed for metadata
/// "l_meta": 1398115447, //unix time the stream itself was last processed for metadata
/// "meta": { //available metadata for this stream, if any
///{
/// "streamname_here":{//name of the configured stream
/// "error": "Available", //error state, if any. "Available" is a special value for VoD
/// streams, indicating it has no current viewers (is not active), but is available for
/// activation. "h_meta": 1398113185, //unix time the stream header (if any) was last
/// processed for metadata "l_meta": 1398115447, //unix time the stream itself was last
/// processed for metadata "meta":{//available metadata for this stream, if any
/// "format": "dtsc", //detected media source format
/// "tracks": { //list of tracks in this stream
/// "audio_AAC_2ch_48000hz_2": {//human-readable track name
/// "tracks":{//list of tracks in this stream
/// "audio_AAC_2ch_48000hz_2":{//human-readable track name
/// "bps": 16043,
/// "channels": 2,
/// "codec": "AAC",
@ -256,54 +245,50 @@ namespace Controller {
/// "size": 16,
/// "trackid": 2,
/// "type": "audio"
/// },
///},
/// //the above structure repeated for all tracks
/// },
///},
/// "vod": 1 //indicates VoD stream, or "live" to indicated live stream.
/// },
///},
/// "name": "a", //the stream name, guaranteed to be equal to the object name.
/// "online": 2, //online state. 0 = error, 1 = active, 2 = inactive.
/// "source": "/home/thulinma/a.dtsc" //source for this stream, as configured.
/// },
///},
/// //the above structure repeated for all configured streams
/// }
///}
/// ~~~~~~~~~~~~~~~
/// Through this request, ALL streams must always be configured. To remove a stream, simply leave it out of the request. To add a stream, simply add it to the request. To edit a stream, simply edit it in the request. The LTS edition has additional requests that allow per-stream changing of the configuration.
void CheckStreams(JSON::Value & in, JSON::Value & out){
//check for new streams and updates
/// Through this request, ALL streams must always be configured. To remove a stream, simply leave
/// it out of the request. To add a stream, simply add it to the request. To edit a stream, simply
/// edit it in the request. The LTS edition has additional requests that allow per-stream changing
/// of the configuration.
void CheckStreams(JSON::Value &in, JSON::Value &out){
// check for new streams and updates
AddStreams(in, out);
//check for deleted streams
// check for deleted streams
std::set<std::string> toDelete;
jsonForEach(out, jit) {
if ( !in.isMember(jit.key())){
toDelete.insert(jit.key());
}
jsonForEach(out, jit){
if (!in.isMember(jit.key())){toDelete.insert(jit.key());}
}
//actually delete the streams
// actually delete the streams
while (toDelete.size() > 0){
std::string deleting = *(toDelete.begin());
deleteStream(deleting, out);
toDelete.erase(deleting);
}
//update old-style configurations to new-style
jsonForEach(in, jit) {
// update old-style configurations to new-style
jsonForEach(in, jit){
if (jit->isMember("channel")){
if ( !jit->isMember("source")){
(*jit)["source"] = (*jit)["channel"]["URL"];
}
if (!jit->isMember("source")){(*jit)["source"] = (*jit)["channel"]["URL"];}
jit->removeMember("channel");
}
if (jit->isMember("preset")){
jit->removeMember("preset");
}
if (jit->isMember("preset")){jit->removeMember("preset");}
}
}
/// Deletes the stream (name) from the config (out), optionally also deleting the VoD source file if sourceFileToo is true.
int deleteStream(const std::string & name, JSON::Value & out, bool sourceFileToo) {
int deleteStream(const std::string &name, JSON::Value &out, bool sourceFileToo){
int ret = 0;
if (sourceFileToo){
std::string cleaned = name;
@ -311,9 +296,7 @@ namespace Controller {
std::string strmSource;
if (Util::getStreamStatus(cleaned) != STRMSTAT_OFF){
DTSC::Meta mData = Util::getStreamMeta(cleaned);
if (mData.sourceURI.size()){
strmSource = mData.sourceURI;
}
if (mData.sourceURI.size()){strmSource = mData.sourceURI;}
}
if (!strmSource.size()){
std::string smp = cleaned.substr(0, cleaned.find_first_of("+ "));
@ -338,60 +321,57 @@ namespace Controller {
}
}
if (noFile){
WARN_MSG("Not deleting source for stream %s, since the stream does not have an unambiguous source file.", cleaned.c_str());
WARN_MSG("Not deleting source for stream %s, since the stream does not have an unambiguous "
"source file.",
cleaned.c_str());
}else{
Util::streamVariables(strmSource, cleaned);
if (!strmSource.size()){
FAIL_MSG("Could not delete source for stream %s: unable to detect stream source URI using any method", cleaned.c_str());
FAIL_MSG("Could not delete source for stream %s: unable to detect stream source URI "
"using any method",
cleaned.c_str());
}else{
if (unlink(strmSource.c_str())){
FAIL_MSG("Could not delete source %s for %s: %s (%d)", strmSource.c_str(), cleaned.c_str(), strerror(errno), errno);
FAIL_MSG("Could not delete source %s for %s: %s (%d)", strmSource.c_str(),
cleaned.c_str(), strerror(errno), errno);
}else{
++ret;
Log("STRM", "Deleting source file for stream "+cleaned+": "+strmSource);
//Delete dtsh, ignore failures
if (!unlink((strmSource+".dtsh").c_str())){
++ret;
}
Log("STRM", "Deleting source file for stream " + cleaned + ": " + strmSource);
// Delete dtsh, ignore failures
if (!unlink((strmSource + ".dtsh").c_str())){++ret;}
}
}
}
}
if (!out.isMember(name)){
return ret;
}
if (!out.isMember(name)){return ret;}
/*LTS-START*/
if(Triggers::shouldTrigger("STREAM_REMOVE")){
if (!Triggers::doTrigger("STREAM_REMOVE", name, name)){
return ret;
}
if (Triggers::shouldTrigger("STREAM_REMOVE")){
if (!Triggers::doTrigger("STREAM_REMOVE", name, name)){return ret;}
}
/*LTS-END*/
Log("STRM", "Deleted stream " + name);
out.removeMember(name);
Controller::writeStream(name, JSON::Value());//Null JSON value = delete
Controller::writeStream(name, JSON::Value()); // Null JSON value = delete
++ret;
ret *= -1;
if (inputProcesses.count(name)){
pid_t procId = inputProcesses[name];
if (Util::Procs::isRunning(procId)){
Util::Procs::Stop(procId);
}
if (Util::Procs::isRunning(procId)){Util::Procs::Stop(procId);}
inputProcesses.erase(name);
}
return ret;
}
bool isMatch(const std::string & source, const std::string & match){
std::string front = match.substr(0,match.find('*'));
std::string back = match.substr(match.find('*')+1);
//if the length of the source is smaller than the front and back matching parts together, it can never match
if (source.size() < front.size()+back.size()){return false;}
return (source.substr(0,front.size()) == front && source.substr(source.size()-back.size()) == back);
bool isMatch(const std::string &source, const std::string &match){
std::string front = match.substr(0, match.find('*'));
std::string back = match.substr(match.find('*') + 1);
// if the length of the source is smaller than the front and back matching parts together, it can never match
if (source.size() < front.size() + back.size()){return false;}
return (source.substr(0, front.size()) == front && source.substr(source.size() - back.size()) == back);
}
void checkParameters(JSON::Value & streamObj){
JSON::Value & inpt = Controller::capabilities["inputs"];
void checkParameters(JSON::Value &streamObj){
JSON::Value &inpt = Controller::capabilities["inputs"];
std::string match;
jsonForEach(inpt, it){
if ((*it)["source_match"].isArray()){
@ -408,11 +388,8 @@ namespace Controller {
}
}
if (match != ""){
jsonForEach(inpt[match]["hardcoded"], it){
streamObj[it.key()] = *it;
}
jsonForEach(inpt[match]["hardcoded"], it){streamObj[it.key()] = *it;}
}
}
} //Controller namespace
}// namespace Controller

View file

@ -1,17 +1,17 @@
#include <mist/json.h>
namespace Controller {
bool streamsEqual(JSON::Value & one, JSON::Value & two);
void checkStream(std::string name, JSON::Value & data);
bool CheckAllStreams(JSON::Value & data);
void CheckStreams(JSON::Value & in, JSON::Value & out);
void AddStreams(JSON::Value & in, JSON::Value & out);
int deleteStream(const std::string & name, JSON::Value & out, bool sourceFileToo = false);
void checkParameters(JSON::Value & stream);
namespace Controller{
bool streamsEqual(JSON::Value &one, JSON::Value &two);
void checkStream(std::string name, JSON::Value &data);
bool CheckAllStreams(JSON::Value &data);
void CheckStreams(JSON::Value &in, JSON::Value &out);
void AddStreams(JSON::Value &in, JSON::Value &out);
int deleteStream(const std::string &name, JSON::Value &out, bool sourceFileToo = false);
void checkParameters(JSON::Value &stream);
struct liveCheck {
struct liveCheck{
long long int lastms;
long long int last_active;
};
} //Controller namespace
}// namespace Controller

View file

@ -1,19 +1,19 @@
/// \file controller_updater.cpp
/// Contains all code for the controller updater.
#include "controller_updater.h"
#include "controller_connectors.h"
#include "controller_storage.h"
#include "controller_updater.h"
#include <fstream> //for files
#include <iostream> //for stdio
#include <mist/auth.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/downloader.h>
#include <mist/http_parser.h>
#include <mist/timing.h>
#include <mist/encode.h>
#include <mist/http_parser.h>
#include <mist/procs.h>
#include <mist/timing.h>
#include <signal.h> //for raise
#include <sys/stat.h> //for chmod
#include <time.h> //for time
@ -66,8 +66,7 @@ namespace Controller{
if (Util::epoch() - updateChecker > UPDATE_INTERVAL || updatePerc){
JSON::Value result = Controller::checkUpdateInfo();
if (result.isMember("error")){
FAIL_MSG("Error retrieving update information: %s",
result["error"].asStringRef().c_str());
FAIL_MSG("Error retrieving update information: %s", result["error"].asStringRef().c_str());
}
{// Lock the mutex, update the updates object
tthread::lock_guard<tthread::mutex> guard(updaterMutex);
@ -75,7 +74,8 @@ namespace Controller{
}
if (!result["uptodate"] && updatePerc){
if (result["url"].asStringRef().find(".zip") != std::string::npos){
FAIL_MSG("Cannot auto-install update for this platform. Please download and install by hand.");
FAIL_MSG("Cannot auto-install update for this platform. Please download and install by "
"hand.");
updatePerc = 0;
continue;
}
@ -86,7 +86,7 @@ namespace Controller{
#else
HTTP::URL url("http://releases.mistserver.org/update.php");
#endif
DL.dataTimeout = 50;//only timeout if no data received for 50 seconds
DL.dataTimeout = 50; // only timeout if no data received for 50 seconds
DL.progressCallback = updaterProgressCallback;
if (!DL.get(url.link(result["url"].asStringRef())) || !DL.isOk() || !DL.data().size()){
FAIL_MSG("Download failed - aborting update");
@ -94,13 +94,13 @@ namespace Controller{
continue;
}
updatePerc = 50;
INFO_MSG("Downloaded update archive of %zuKiB", DL.data().size()/1024);
INFO_MSG("Downloaded update archive of %zuKiB", DL.data().size() / 1024);
Log("UPDR", "Installing update...");
std::string tmpDir = Util::getMyPath();
char * tarArgs[4];
tarArgs[0] = (char*)"tar";
tarArgs[1] = (char*)"-xzC";
tarArgs[2] = (char*)tmpDir.c_str();
char *tarArgs[4];
tarArgs[0] = (char *)"tar";
tarArgs[1] = (char *)"-xzC";
tarArgs[2] = (char *)tmpDir.c_str();
tarArgs[3] = 0;
int tarIn = -1;
pid_t tarPid = Util::Procs::StartPiped(tarArgs, &tarIn, 0, 0);
@ -111,13 +111,14 @@ namespace Controller{
}
size_t tarProgress = 0;
while (tarProgress < DL.data().size()){
int written = write(tarIn, DL.data().data()+tarProgress, std::min((size_t)4096, DL.data().size() - tarProgress));
int written = write(tarIn, DL.data().data() + tarProgress,
std::min((size_t)4096, DL.data().size() - tarProgress));
if (written < 0){
FAIL_MSG("Could not (fully) extract update! Aborting.");
break;
}
tarProgress += written;
updatePerc = 95 + (5*tarProgress)/DL.data().size();
updatePerc = 95 + (5 * tarProgress) / DL.data().size();
}
close(tarIn);
uint64_t waitCount = 0;
@ -176,12 +177,13 @@ namespace Controller{
#else
HTTP::URL url("http://releases.mistserver.org/update.php");
#endif
url.args = "rel=" + Encodings::URL::encode(RELEASE) + "&pass=" + Encodings::URL::encode(SHARED_SECRET) + "&iid=" + Encodings::URL::encode(instanceId);
url.args = "rel=" + Encodings::URL::encode(RELEASE) + "&pass=" + Encodings::URL::encode(SHARED_SECRET) +
"&iid=" + Encodings::URL::encode(instanceId);
if (DL.get(url) && DL.isOk()){
updrInfo = JSON::fromString(DL.data());
}else{
Log("UPDR", "Error getting update info: "+DL.getStatusText());
ret["error"] = "Error getting update info: "+DL.getStatusText();
Log("UPDR", "Error getting update info: " + DL.getStatusText());
ret["error"] = "Error getting update info: " + DL.getStatusText();
return ret;
}
if (!updrInfo){
@ -213,5 +215,4 @@ namespace Controller{
/// Causes the updater thread to download an update, if available
void checkUpdates(){updatePerc = 1;}// CheckUpdates
}
}// namespace Controller

View file

@ -10,5 +10,4 @@ namespace Controller{
JSON::Value checkUpdateInfo();
void checkUpdates();
void insertUpdateInfo(JSON::Value &ret);
}
}// namespace Controller

View file

@ -1,100 +1,97 @@
#include <stdlib.h>
#include <mist/auth.h>
#include <mist/dtsc.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/timing.h>
#include "controller_uplink.h"
#include "controller_api.h"
#include "controller_capabilities.h"
#include "controller_connectors.h"
#include "controller_limits.h"
#include "controller_statistics.h"
#include "controller_storage.h"
#include "controller_streams.h"
#include "controller_connectors.h"
#include "controller_capabilities.h"
#include "controller_statistics.h"
#include "controller_updater.h"
#include "controller_limits.h"
#include "controller_api.h"
#include "controller_uplink.h"
#include <mist/auth.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/dtsc.h>
#include <mist/timing.h>
#include <stdlib.h>
void Controller::uplinkConnection(void * np) {
void Controller::uplinkConnection(void *np){
std::string uplink_name = Controller::conf.getString("uplink-name");
std::string uplink_pass = Controller::conf.getString("uplink-pass");
std::string uplink_addr = Controller::conf.getString("uplink");
std::string uplink_host = "";
std::string uplink_chal = "";
int uplink_port = 0;
if (uplink_addr.size() > 0) {
if (uplink_addr.size() > 0){
size_t colon = uplink_addr.find(':');
if (colon != std::string::npos && colon != 0 && colon != uplink_addr.size()) {
if (colon != std::string::npos && colon != 0 && colon != uplink_addr.size()){
uplink_host = uplink_addr.substr(0, colon);
uplink_port = atoi(uplink_addr.substr(colon + 1, std::string::npos).c_str());
Controller::Log("CONF", "Connection to uplink enabled on host " + uplink_host + " and port " + uplink_addr.substr(colon + 1, std::string::npos));
Controller::Log("CONF", "Connection to uplink enabled on host " + uplink_host + " and port " +
uplink_addr.substr(colon + 1, std::string::npos));
}
}
//cancel the whole thread if no uplink is set
if (!uplink_port) {
return;
}
// cancel the whole thread if no uplink is set
if (!uplink_port){return;}
uint64_t lastSend = Util::epoch() - 5;
Socket::Connection uplink;
while (Controller::conf.is_active) {
if (!uplink) {
while (Controller::conf.is_active){
if (!uplink){
INFO_MSG("Connecting to uplink at %s:%u", uplink_host.c_str(), uplink_port);
uplink.open(uplink_host, uplink_port, true);
}
if (uplink) {
if (uplink.spool()) {
if (uplink.Received().available(9)) {
if (uplink){
if (uplink.spool()){
if (uplink.Received().available(9)){
std::string data = uplink.Received().copy(8);
if (data.substr(0, 4) != "DTSC") {
if (data.substr(0, 4) != "DTSC"){
uplink.Received().clear();
continue;
}
unsigned int size = ntohl(*(const unsigned int *)(data.data() + 4));
if (uplink.Received().available(8 + size)) {
if (uplink.Received().available(8 + size)){
std::string packet = uplink.Received().remove(8 + size);
DTSC::Scan inScan = DTSC::Packet(packet.data(), packet.size()).getScan();
if (!inScan){continue;}
JSON::Value curVal;
//Parse config and streams from the request.
// Parse config and streams from the request.
if (inScan.hasMember("authorize") && inScan.getMember("authorize").hasMember("challenge")){
uplink_chal = inScan.getMember("authorize").getMember("challenge").asString();
}
if (inScan.hasMember("streams")) {
if (inScan.hasMember("streams")){
curVal = inScan.getMember("streams").asJSON();
Controller::CheckStreams(curVal, Controller::Storage["streams"]);
}
if (inScan.hasMember("addstream")) {
if (inScan.hasMember("addstream")){
curVal = inScan.getMember("addstream").asJSON();
Controller::AddStreams(curVal, Controller::Storage["streams"]);
}
if (inScan.hasMember("deletestream")) {
if (inScan.hasMember("deletestream")){
curVal = inScan.getMember("deletestream").asJSON();
//if array, delete all elements
//if object, delete all entries
//if string, delete just the one
if (curVal.isString()) {
// if array, delete all elements
// if object, delete all entries
// if string, delete just the one
if (curVal.isString()){
Controller::Storage["streams"].removeMember(curVal.asStringRef());
}
if (curVal.isArray()) {
jsonForEach(curVal, it) {
if (curVal.isArray()){
jsonForEach(curVal, it){
Controller::Storage["streams"].removeMember(it->asString());
}
}
if (curVal.isObject()) {
jsonForEach(curVal, it) {
Controller::Storage["streams"].removeMember(it.key());
}
if (curVal.isObject()){
jsonForEach(curVal, it){Controller::Storage["streams"].removeMember(it.key());}
}
}
}
}
}
if (Util::epoch() - lastSend >= 2) {
if (Util::epoch() - lastSend >= 2){
JSON::Value data;
data["tracks"].null();//make sure the data is encoded as DTSC
data["tracks"].null(); // make sure the data is encoded as DTSC
if (uplink_chal.size()){
data["authorize"]["username"] = uplink_name;
data["authorize"]["password"] = Secure::md5( Secure::md5(uplink_pass) + uplink_chal);
data["authorize"]["password"] = Secure::md5(Secure::md5(uplink_pass) + uplink_chal);
}
JSON::Value totalsRequest;
Controller::fillClients(totalsRequest, data["clients"]);
@ -115,9 +112,9 @@ void Controller::uplinkConnection(void * np) {
data.sendTo(uplink);
lastSend = Util::epoch();
}
} else {
}else{
Controller::Log("UPLK", "Could not connect to uplink.");
}
Util::wait(2000);//wait for 2.5 seconds
Util::wait(2000); // wait for 2.5 seconds
}
}

View file

@ -1,3 +1,3 @@
namespace Controller {
void uplinkConnection(void * np);
namespace Controller{
void uplinkConnection(void *np);
}