Subversion Repositories kissdx

Rev

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.