Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
/*
* kissdx - KiSS PC-Link Daemon eXtended (based on kissd)
*
* Copyright (C)
* Portions Copyright (C) 2006 Vidar Tysse <vidar@vidartysse.net>
* Portions Copyright (C) 2007 Olivier Kahn <okahn@free.fr>
*
* Heavily based on kiss4lin,
* Copyright (C) 2004 Jacob Kolding <dacobi@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <dirent.h>
#include <limits.h>
#include <libgen.h>
#include <fnmatch.h>
#include <time.h>
#include <sysexits.h> // Standard GNU Exit code
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef USE_INTERNAL_SENDFILE
#include <sys/mman.h>
#else
#ifdef Linux
#include <sys/sendfile.h>
#endif
#endif
#include "dvdread.h"
#include "kissdx.h"
#include "connection.h"
#include "config.h"
#include "utils.h"
#include "sendfile.h"
#include "piccache.h"
#include "gdstuff/gdstuff.h"
#include "backtoback.h"
#include "cmdclient.h"
#include "cmdserver.h"
// ========================================================
// Global constants
int standard_filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; // User.RW + Grp.R + Other.R
int standard_dir_filemode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; // User.RWX + Grp.RX + Other.RX
// Prefix for socket name in /tmp : suffixe is generated by XXXXXX (mkstemp() fct)
const char *commandserver_filename_prefix = "kissdx-loccmdskt-"; // kissdx Local Command Socket
const char *admincommand_STOP = "STOP"; // stop daemon master & let child serving current media
const char *admincommand_FORCESTOP = "FORCESTOP"; // stop daemon master and any child running
const char *admincommand_RELOADCONFIG = "RELOAD_CONFIG"; // invoke a reload config to mediacenter
const char *admincommand_STATUS = "STATUS"; // give back a status of daemon (in text,html, kml ?...)
const char *admincommand_GET_TXT_CONFIG = "GET_TXT_CONFIG"; // give back a text edition of kissdx server config
// ========================================================
static void create_local_socket(int *sock, const char* path, int type) {
struct sockaddr_un sun;
int yes = 1;
/* get a local domain socket */
if ((*sock = socket(PF_LOCAL, type, 0)) < 0) {
log("local socket: %s", strerror(errno));
exit(1);
}
/* lose the pesky "address already in use" error message */
if (setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) {
log("local setsockopt: %s", strerror(errno));
exit(1);
}
/* complete the socket structure */
memset(&sun, 0, sizeof(sun));
sun.sun_family = AF_LOCAL;
strncpy (sun.sun_path, path, sizeof (sun.sun_path));
/* bind the socket to the path */
if (bind(*sock, (struct sockaddr *) &sun, SUN_LEN (&sun)) < 0) {
log("local bind: %s", strerror(errno));
exit(1);
}
/* everything done for a udp socket */
if (type == SOCK_DGRAM) {
return;
}
/* listen up to 5 incoming connection queue = simultanous TCP call */
if (listen(*sock, 5) < 0) {
log("local listen: %s", strerror(errno));
exit(1);
}
}
// ========================================================
// Daemon master receive an action : Options set change / Core action ...
void handle_local_command(int sd_child) {
int len;
int size = LOCAL_COMMAND_SIZE;
char buffer[size];
char *token;
const char* token_delimiters = " ";
if ((len = recv(sd_child, buffer, size, 0)) < 0) {
log("handle_local_command.recv: %s", strerror(errno));
return;
}
if (len == 0) {
logv("handle_local_command: EOF on socket %d", sd_child);
return;
}
logv("handle_local_command: Read %d bytes from socket %d", len, sd_child);
while (len > 0 && (buffer[len - 1] == '\r' || buffer[len - 1] == '\n'))
len--;
/* properly end the string and prevent buffer overflows */
buffer[len < size ? len : len - 1] = '\0';
logv("local command received: [%s]", buffer);
// Interpret the local command and if valid, act on it
if ((token = strtok(buffer, token_delimiters))) {
if (!strcasecmp(token, "SET")) {
if ((token = strtok(NULL, token_delimiters))) {
if (!strcasecmp(token, "LIST_HIDDEN_ENTRIES")) {
if ((token = strtok(NULL, token_delimiters))) {
if (!strcasecmp(token, "ON")) {
options.list_hidden_entries = 1;
// Save current time to automatically reset options.list_hidden_entries after a timeout period.
last_time_list_hidden_entries_on = time(NULL);
logv0("Option list_hidden_entries has been turned ON");
}
else if (!strcasecmp(token, "OFF")) {
options.list_hidden_entries = 0;
logv0("Option list_hidden_entries has been turned OFF");
}
else
log("Unknown SET LIST_HIDDEN_ENTRIES value: %s", token);
}
else
log0("Missing SET LIST_HIDDEN_ENTRIES value");
}
else
log("Unknown SET target: %s", token);
}
else
log0("Missing SET target");
}
else
log("Unknown local command: %s", token);
}
else
log0("Missing local command");
}
// ========================================================
void setup_local_commandserver_socket(int * sd_loccmd)
{
int scandir_selector(const struct dirent *entry)
{
const char *thisname = entry->d_name;
if (!strncmp(thisname, commandserver_filename_prefix, strlen(commandserver_filename_prefix)))
return 1; // This is residue from a previous session
return 0;
}
struct dirent **namelist;
int num_files, n, fd;
strcpy(commandserver_path, P_tmpdir);
if (strlen(commandserver_path) > 0 && commandserver_path[strlen(commandserver_path) - 1] != '/')
strcat(commandserver_path, "/");
if ((num_files = scandir(commandserver_path, &namelist, scandir_selector, alphasort)) < 0) {
log("setup_local_commandserver_socket: scandir %s: %s", commandserver_path, strerror(errno));
} else {
for (n = 0; n < num_files; n++) {
char fullname[PATH_MAX];
strcpy(fullname, commandserver_path);
strcat(fullname, namelist[n]->d_name);
unlink(fullname);
free(namelist[n]);
}
free(namelist);
}
strcat(commandserver_path, commandserver_filename_prefix);
strcat(commandserver_path, "XXXXXX");
if ((fd = mkstemp(commandserver_path)) < 0) {
log("Cannot create temporary file for local command socket: %s", strerror(errno));
return;
}
close(fd);
unlink(commandserver_path);
create_local_socket(sd_loccmd, commandserver_path, SOCK_STREAM);
}
// ========================================================
// Admin command : STOP
// ==> Send a gracefull SIGTERM to Master daemon (myself)
// Child processes will terminate at the end of streaming
void handle_admincommand_stop(pid_t daemon_pid) {
logv("INFO handle_admin_command STOP: SIGTERM to daemon pid: %d", daemon_pid);
kill(daemon_pid, SIGTERM);
}
// ========================================================
// Admin command : FORCESTOP
// ==> Send a gracefull SIGTERM to Master daemon process group
// Child processes will terminate right now
void handle_admincommand_forcestop(void) {
logv0("INFO handle_admin_command FORCESTOP: SIGTERM to kissdx process group ");
// if kill(pid == 0) => All processes in the same process group as the sender.
kill(0, SIGTERM);
}
// ========================================================
// Admin command : RELOAD_CONFIG
void handle_admincommand_reloadconfig(void) {
logv0("INFO handle_admin_command RELOAD_CONFIG: Parse kissdx.conf ");
config_settings_t new_config;
int config_error = parse_config(&new_config);
if (!config_error) {
memcpy(&config, &new_config, sizeof (config));
} else {
log("Loading new config failed from : %s",new_config.config_file);
log("Keep previous configuration from : %s",config.config_file);
show_current_config_Verbose(&config); // Show the old (still current) configuration in verbose mode
}
logv0("..... Ready for streaming business");
}
// ========================================================
// Admin command : GET_TXT_CONFIG
void handle_admincommand_gettxtconfig (int sd_client) {
// Should send back to socket (sd_client) the configuration settings
logv0("INFO handle_admin_command GET_TXT_CONFIG: Send back configuration settings ");
send_txt_config (sd_client, &config);
// should display : config_settings_t config;
// like show_current_config_Verbose(&config); // Show the current configuration in verbose mode
// ??? send of EOF ??
//close (sd_client) is done by kissdx.c:dodaemon() loop
}
// ========================================================
enum admin_request_t parse_admcmd_request(int sd_admcmd, char *opt_admcmd, int opt_admcmd_size) {
// Parse admin command received in TCP and launch corresponding handler
// command line received over TCP is stored in options.admincommand
// support maximum 3 parameters separeted by space : xxx yyy zzz
int len; // lenght of command line received
int size = LOCAL_COMMAND_SIZE; // maximum line size to be received
char buffer[size]; // command line read from socket
char *token01,*token02,*token03 ; // command line parsed "<token01> <token02> <token03>"
const char* token_delimiters = " "; // command line parsing delimiter = " "
// read command line from socket
if ((len = recv(sd_admcmd, buffer, size, 0)) < 0) {
log("ERR: handle_admin_command.recv: %s", strerror(errno));
return adm_err;
}
if (len == 0) {
logv("ERR: handle_admin_command: EOF on socket %d", sd_admcmd);
return adm_err;
}
logv("Parse admin command: Read %d bytes from socket %d", len, sd_admcmd);
// remove ending \r & \n
while (len > 0 && (buffer[len - 1] == '\r' || buffer[len - 1] == '\n'))
len--;
/* properly end the string and prevent buffer overflows */
buffer[len < size ? len : len - 1] = '\0';
logv("Admin command received: [%s]", buffer);
// Save command line argument (admin command) to options.admincommand[512]
memset(opt_admcmd, 0, opt_admcmd_size);
strncpy(opt_admcmd, buffer, opt_admcmd_size);
token01 = strtok(buffer, token_delimiters) ;
token02 = strtok(NULL, token_delimiters) ;
token03 = strtok(NULL, token_delimiters) ;
// DEBUG
//logv("cmdserver.c token01 [%s]",token01);
//logv("cmdserver.c token02 [%s]",token02);
//logv("cmdserver.c token03 [%s]",token03);
/*if (token01) { logv0("if (token01)= true "); }
if (token02) { logv0("if (token02)= true "); }
if (token03) { logv0("if (token03)= true "); }
*/
// ============= XXX YYY ZZZ situation ======================================
if (token01 && token02 && token03) {
// SET LIST_HIDDEN_ENTRIES ON
if ( (!strcasecmp(token01, "SET")) && (!strcasecmp(token02, "LIST_HIDDEN_ENTRIES")) && (!strcasecmp(token03, "ON")) ) {
options.list_hidden_entries = 1;
// Save current time to automatically reset options.list_hidden_entries after a timeout period.
last_time_list_hidden_entries_on = time(NULL);
logv0("Option list_hidden_entries has been turned ON");
return adm_unknown;
}
// SET LIST_HIDDEN_ENTRIES OFF
if ( (!strcasecmp(token01, "SET")) && (!strcasecmp(token02, "LIST_HIDDEN_ENTRIES")) && (!strcasecmp(token03, "OFF")) ) {
options.list_hidden_entries = 0;
logv0("Option list_hidden_entries has been turned OFF");
return adm_unknown;
}
} // endif 3 tokens exists
// ============= XXX YYY situation ======================================
if (token01 && token02 && !token03) {
// ARG1 ARG2
if ( (!strcasecmp(token01, "ARG1")) && (!strcasecmp(token02, "ARG2")) ) {
// Action for ....
logv0("Option ARG1 ARG2 detected");
return adm_unknown;
}
// GET_TXT_CONFIG (commmand received over Tcp)
if ( (!strcasecmp(token01, admincommand_GET_TXT_CONFIG)) ) {
logv("Admin command detected: %s", admincommand_GET_TXT_CONFIG);
return adm_gettxtconfig;
}
} // endif 2 tokens exists
// ============= XXX situation ======================================
if (token01 && !token02 && !token03) {
// STOP
if ( (!strcasecmp(token01, "STOP")) ) {
// action handle_admcmd_stop(..)
logv0("Admin command detected: STOP");
return adm_stop;
}
// FORCESTOP
if ( (!strcasecmp(token01, "FORCESTOP")) ) {
// action handle_admcmd_forcestop(..)
logv0("Admin command detected: FORCESTOP");
return adm_forcestop;
}
// RELOAD_CONFIG
if ( (!strcasecmp(token01, "RELOAD_CONFIG")) ) {
// action handle_admcmd_reload_config(..)
logv0("Admin command detected: RELOAD_CONFIG");
return adm_reloadconfig;
}
// RELOAD_CONFIG
if ( (!strcasecmp(token01, "SET_LIST_HIDDEN_ENTRIES_ON")) ) {
logv0("Admin command detected: SET_LIST_HIDDEN_ENTRIES_ON");
return adm_unknown;
}
// RELOAD_CONFIG
if ( (!strcasecmp(token01, "SET_LIST_HIDDEN_ENTRIES_OFF")) ) {
logv0("Admin command detected: SET_LIST_HIDDEN_ENTRIES_OFF");
return adm_unknown;
}
} // if 1 tokens exists
// Default return for non void function
return adm_unknown;
}
// ========================================================================================
Generated by GNU Enscript 1.6.5.