Using $USER in the friendly name will display which user minidlna is running as on the clients, which can be helpful for detecting problems. Using $LOGNAME on the other hand will display "root" as the username if minidlna was started using the init script, regardless of which user minidlna is currently running as. Originally added by Benoît Knecht <benoit.knecht@fsfe.org>
1380 lines
35 KiB
C
1380 lines
35 KiB
C
/* MiniDLNA project
|
|
*
|
|
* http://sourceforge.net/projects/minidlna/
|
|
*
|
|
* MiniDLNA media server
|
|
* Copyright (C) 2008-2012 Justin Maggard
|
|
*
|
|
* This file is part of MiniDLNA.
|
|
*
|
|
* MiniDLNA is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* MiniDLNA 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 MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Portions of the code from the MiniUPnP project:
|
|
*
|
|
* Copyright (c) 2006-2007, Thomas Bernard
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/file.h>
|
|
#include <sys/time.h>
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <limits.h>
|
|
#include <libgen.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef ENABLE_NLS
|
|
#include <locale.h>
|
|
#include <libintl.h>
|
|
#endif
|
|
|
|
#include "event.h"
|
|
#include "upnpglobalvars.h"
|
|
#include "sql.h"
|
|
#include "upnphttp.h"
|
|
#include "upnpdescgen.h"
|
|
#include "minidlnapath.h"
|
|
#include "getifaddr.h"
|
|
#include "upnpsoap.h"
|
|
#include "options.h"
|
|
#include "utils.h"
|
|
#include "minissdp.h"
|
|
#include "minidlnatypes.h"
|
|
#include "process.h"
|
|
#include "upnpevents.h"
|
|
#include "scanner.h"
|
|
#include "monitor.h"
|
|
#include "libav.h"
|
|
#include "log.h"
|
|
#include "tivo_beacon.h"
|
|
#include "tivo_utils.h"
|
|
#include "avahi.h"
|
|
|
|
#if SQLITE_VERSION_NUMBER < 3005001
|
|
# warning "Your SQLite3 library appears to be too old! Please use 3.5.1 or newer."
|
|
# define sqlite3_threadsafe() 0
|
|
#endif
|
|
|
|
static LIST_HEAD(httplisthead, upnphttp) upnphttphead;
|
|
|
|
/* OpenAndConfHTTPSocket() :
|
|
* setup the socket used to handle incoming HTTP connections. */
|
|
static int
|
|
OpenAndConfHTTPSocket(unsigned short port)
|
|
{
|
|
int s;
|
|
int i = 1;
|
|
struct sockaddr_in listenname;
|
|
|
|
/* Initialize client type cache */
|
|
memset(&clients, 0, sizeof(struct client_cache_s));
|
|
|
|
s = socket(PF_INET, SOCK_STREAM, 0);
|
|
if (s < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL, "socket(http): %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0)
|
|
DPRINTF(E_WARN, L_GENERAL, "setsockopt(http, SO_REUSEADDR): %s\n", strerror(errno));
|
|
|
|
memset(&listenname, 0, sizeof(struct sockaddr_in));
|
|
listenname.sin_family = AF_INET;
|
|
listenname.sin_port = htons(port);
|
|
listenname.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
|
|
if (bind(s, (struct sockaddr *)&listenname, sizeof(struct sockaddr_in)) < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL, "bind(http): %s\n", strerror(errno));
|
|
close(s);
|
|
return -1;
|
|
}
|
|
|
|
if (listen(s, 16) < 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL, "listen(http): %s\n", strerror(errno));
|
|
close(s);
|
|
return -1;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
/* ProcessListen() :
|
|
* accept incoming HTTP connection. */
|
|
static void
|
|
ProcessListen(struct event *ev)
|
|
{
|
|
int shttp;
|
|
socklen_t clientnamelen;
|
|
struct sockaddr_in clientname;
|
|
clientnamelen = sizeof(struct sockaddr_in);
|
|
|
|
shttp = accept(ev->fd, (struct sockaddr *)&clientname, &clientnamelen);
|
|
if (shttp<0)
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL, "accept(http): %s\n", strerror(errno));
|
|
}
|
|
else
|
|
{
|
|
struct upnphttp * tmp = 0;
|
|
DPRINTF(E_DEBUG, L_GENERAL, "HTTP connection from %s:%d\n",
|
|
inet_ntoa(clientname.sin_addr),
|
|
ntohs(clientname.sin_port) );
|
|
/*if (fcntl(shttp, F_SETFL, O_NONBLOCK) < 0) {
|
|
DPRINTF(E_ERROR, L_GENERAL, "fcntl F_SETFL, O_NONBLOCK\n");
|
|
}*/
|
|
/* Create a new upnphttp object and add it to
|
|
* the active upnphttp object list */
|
|
tmp = New_upnphttp(shttp);
|
|
if (tmp)
|
|
{
|
|
tmp->clientaddr = clientname.sin_addr;
|
|
LIST_INSERT_HEAD(&upnphttphead, tmp, entries);
|
|
}
|
|
else
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL, "New_upnphttp() failed\n");
|
|
close(shttp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Handler for the SIGTERM signal (kill)
|
|
* SIGINT is also handled */
|
|
static void
|
|
sigterm(int sig)
|
|
{
|
|
signal(sig, SIG_IGN); /* Ignore this signal while we are quitting */
|
|
|
|
DPRINTF(E_WARN, L_GENERAL, "received signal %d, good-bye\n", sig);
|
|
|
|
quitting = 1;
|
|
}
|
|
|
|
static void
|
|
sigusr1(int sig)
|
|
{
|
|
signal(sig, sigusr1);
|
|
DPRINTF(E_WARN, L_GENERAL, "received signal %d, clear cache\n", sig);
|
|
|
|
memset(&clients, '\0', sizeof(clients));
|
|
}
|
|
|
|
static void
|
|
sighup(int sig)
|
|
{
|
|
signal(sig, sighup);
|
|
DPRINTF(E_WARN, L_GENERAL, "received signal %d, reloading\n", sig);
|
|
|
|
reload_ifaces(1);
|
|
log_reopen();
|
|
}
|
|
|
|
/* record the startup time */
|
|
static void
|
|
set_startup_time(void)
|
|
{
|
|
startup_time = time(NULL);
|
|
}
|
|
|
|
static void
|
|
getfriendlyname(char *buf, int len)
|
|
{
|
|
char *p = NULL;
|
|
char hn[63];
|
|
int off;
|
|
|
|
if (gethostname(hn, sizeof(hn)) == 0)
|
|
{
|
|
strncpyt(buf, hn, len);
|
|
p = strchr(buf, '.');
|
|
if (p)
|
|
*p = '\0';
|
|
}
|
|
else
|
|
strcpy(buf, "Unknown");
|
|
|
|
off = strlen(buf);
|
|
off += snprintf(buf+off, len-off, ": ");
|
|
#ifdef READYNAS
|
|
FILE *info;
|
|
char ibuf[64], *key, *val;
|
|
snprintf(buf+off, len-off, "ReadyNAS");
|
|
info = fopen("/proc/sys/dev/boot/info", "r");
|
|
if (!info)
|
|
return;
|
|
while ((val = fgets(ibuf, 64, info)) != NULL)
|
|
{
|
|
key = strsep(&val, ": \t");
|
|
val = trim(val);
|
|
if (strcmp(key, "model") == 0)
|
|
{
|
|
snprintf(buf+off, len-off, "%s", val);
|
|
key = strchr(val, ' ');
|
|
if (key)
|
|
{
|
|
strncpyt(modelnumber, key+1, MODELNUMBER_MAX_LEN);
|
|
*key = '\0';
|
|
}
|
|
snprintf(modelname, MODELNAME_MAX_LEN,
|
|
"Windows Media Connect compatible (%s)", val);
|
|
}
|
|
else if (strcmp(key, "serial") == 0)
|
|
{
|
|
strncpyt(serialnumber, val, SERIALNUMBER_MAX_LEN);
|
|
if (serialnumber[0] == '\0')
|
|
{
|
|
char mac_str[13];
|
|
if (getsyshwaddr(mac_str, sizeof(mac_str)) == 0)
|
|
strcpy(serialnumber, mac_str);
|
|
else
|
|
strcpy(serialnumber, "0");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
fclose(info);
|
|
#else
|
|
char * logname;
|
|
logname = getenv("USER");
|
|
if (!logname)
|
|
{
|
|
logname = getenv("LOGNAME");
|
|
#ifndef STATIC // Disable for static linking
|
|
if (!logname)
|
|
{
|
|
struct passwd *pwent = getpwuid(geteuid());
|
|
if (pwent)
|
|
logname = pwent->pw_name;
|
|
}
|
|
#endif
|
|
}
|
|
snprintf(buf+off, len-off, "%s", logname?logname:"Unknown");
|
|
#endif
|
|
}
|
|
|
|
static time_t
|
|
_get_dbtime(void)
|
|
{
|
|
char path[PATH_MAX];
|
|
struct stat st;
|
|
|
|
snprintf(path, sizeof(path), "%s/files.db", db_path);
|
|
if (stat(path, &st) != 0)
|
|
return 0;
|
|
return st.st_mtime;
|
|
}
|
|
|
|
static int
|
|
open_db(sqlite3 **sq3)
|
|
{
|
|
char path[PATH_MAX];
|
|
int new_db = 0;
|
|
|
|
snprintf(path, sizeof(path), "%s/files.db", db_path);
|
|
if (access(path, F_OK) != 0)
|
|
{
|
|
new_db = 1;
|
|
make_dir(db_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);
|
|
}
|
|
if (sqlite3_open(path, &db) != SQLITE_OK)
|
|
DPRINTF(E_FATAL, L_GENERAL, "ERROR: Failed to open sqlite database! Exiting...\n");
|
|
if (sq3)
|
|
*sq3 = db;
|
|
sqlite3_busy_timeout(db, 5000);
|
|
sql_exec(db, "pragma page_size = 4096");
|
|
sql_exec(db, "pragma journal_mode = OFF");
|
|
sql_exec(db, "pragma synchronous = OFF;");
|
|
sql_exec(db, "pragma default_cache_size = 8192;");
|
|
|
|
return new_db;
|
|
}
|
|
|
|
static void
|
|
check_db(sqlite3 *db, int new_db, pid_t *scanner_pid)
|
|
{
|
|
struct media_dir_s *media_path = NULL;
|
|
char cmd[PATH_MAX*2];
|
|
char **result;
|
|
int i, rows = 0;
|
|
int ret;
|
|
|
|
if (!new_db)
|
|
{
|
|
/* Check if any new media dirs appeared */
|
|
media_path = media_dirs;
|
|
while (media_path)
|
|
{
|
|
ret = sql_get_int_field(db, "SELECT TIMESTAMP as TYPE from DETAILS where PATH = %Q",
|
|
media_path->path);
|
|
if (ret != media_path->types)
|
|
{
|
|
ret = 1;
|
|
goto rescan;
|
|
}
|
|
media_path = media_path->next;
|
|
}
|
|
/* Check if any media dirs disappeared */
|
|
sql_get_table(db, "SELECT VALUE from SETTINGS where KEY = 'media_dir'", &result, &rows, NULL);
|
|
for (i=1; i <= rows; i++)
|
|
{
|
|
media_path = media_dirs;
|
|
while (media_path)
|
|
{
|
|
if (strcmp(result[i], media_path->path) == 0)
|
|
break;
|
|
media_path = media_path->next;
|
|
}
|
|
if (!media_path)
|
|
{
|
|
ret = 2;
|
|
sqlite3_free_table(result);
|
|
goto rescan;
|
|
}
|
|
}
|
|
sqlite3_free_table(result);
|
|
}
|
|
|
|
ret = db_upgrade(db);
|
|
if (ret != 0)
|
|
{
|
|
rescan:
|
|
CLEARFLAG(RESCAN_MASK);
|
|
if (ret < 0)
|
|
DPRINTF(E_WARN, L_GENERAL, "Creating new database at %s/files.db\n", db_path);
|
|
else if (ret == 1)
|
|
DPRINTF(E_WARN, L_GENERAL, "New media_dir detected; rebuilding...\n");
|
|
else if (ret == 2)
|
|
DPRINTF(E_WARN, L_GENERAL, "Removed media_dir detected; rebuilding...\n");
|
|
else
|
|
DPRINTF(E_WARN, L_GENERAL, "Database version mismatch (%d => %d); need to recreate...\n",
|
|
ret, DB_VERSION);
|
|
sqlite3_close(db);
|
|
|
|
snprintf(cmd, sizeof(cmd), "rm -rf %s/files.db %s/art_cache", db_path, db_path);
|
|
if (system(cmd) != 0)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to clean old file cache! Exiting...\n");
|
|
|
|
open_db(&db);
|
|
if (CreateDatabase() != 0)
|
|
DPRINTF(E_FATAL, L_GENERAL, "ERROR: Failed to create sqlite database! Exiting...\n");
|
|
}
|
|
if (ret || GETFLAG(RESCAN_MASK))
|
|
{
|
|
#if USE_FORK
|
|
sqlite3_close(db);
|
|
*scanner_pid = fork();
|
|
open_db(&db);
|
|
if (*scanner_pid == 0) /* child (scanner) process */
|
|
{
|
|
start_scanner();
|
|
sqlite3_close(db);
|
|
log_close();
|
|
freeoptions();
|
|
free(children);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
else if (*scanner_pid < 0)
|
|
{
|
|
start_scanner();
|
|
}
|
|
else
|
|
SETFLAG(SCANNING_MASK);
|
|
#else
|
|
start_scanner();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static int
|
|
writepidfile(const char *fname, int pid, uid_t uid)
|
|
{
|
|
FILE *pidfile;
|
|
struct stat st;
|
|
char path[PATH_MAX], *dir;
|
|
int ret = 0;
|
|
|
|
if(!fname || *fname == '\0')
|
|
return -1;
|
|
|
|
/* Create parent directory if it doesn't already exist */
|
|
strncpyt(path, fname, sizeof(path));
|
|
dir = dirname(path);
|
|
if (stat(dir, &st) == 0)
|
|
{
|
|
if (!S_ISDIR(st.st_mode))
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL, "Pidfile path is not a directory: %s\n",
|
|
fname);
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (make_dir(dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) != 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL, "Unable to create pidfile directory: %s\n",
|
|
fname);
|
|
return -1;
|
|
}
|
|
if (uid > 0)
|
|
{
|
|
if (chown(dir, uid, -1) != 0)
|
|
DPRINTF(E_WARN, L_GENERAL, "Unable to change pidfile %s ownership: %s\n",
|
|
dir, strerror(errno));
|
|
}
|
|
}
|
|
|
|
pidfile = fopen(fname, "w");
|
|
if (!pidfile)
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL, "Unable to open pidfile for writing %s: %s\n",
|
|
fname, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if (fprintf(pidfile, "%d\n", pid) <= 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL,
|
|
"Unable to write to pidfile %s: %s\n", fname, strerror(errno));
|
|
ret = -1;
|
|
}
|
|
if (uid > 0)
|
|
{
|
|
if (fchown(fileno(pidfile), uid, -1) != 0)
|
|
DPRINTF(E_WARN, L_GENERAL, "Unable to change pidfile %s ownership: %s\n",
|
|
fname, strerror(errno));
|
|
}
|
|
|
|
fclose(pidfile);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int strtobool(const char *str)
|
|
{
|
|
return ((strcasecmp(str, "yes") == 0) ||
|
|
(strcasecmp(str, "true") == 0) ||
|
|
(atoi(str) == 1));
|
|
}
|
|
|
|
static void init_nls(void)
|
|
{
|
|
#ifdef ENABLE_NLS
|
|
const char *messages, *ctype, *locale_dir;
|
|
|
|
ctype = setlocale(LC_CTYPE, "");
|
|
if (!ctype || !strcmp(ctype, "C"))
|
|
ctype = setlocale(LC_CTYPE, "en_US.utf8");
|
|
if (!ctype)
|
|
DPRINTF(E_WARN, L_GENERAL, "Unset locale\n");
|
|
else if (!strstr(ctype, "utf8") && !strstr(ctype, "UTF8") &&
|
|
!strstr(ctype, "utf-8") && !strstr(ctype, "UTF-8"))
|
|
DPRINTF(E_WARN, L_GENERAL, "Using unsupported non-utf8 locale '%s'\n", ctype);
|
|
messages = setlocale(LC_MESSAGES, "");
|
|
if (!messages)
|
|
messages = "unset";
|
|
locale_dir = bindtextdomain("minidlna", getenv("TEXTDOMAINDIR"));
|
|
DPRINTF(E_DEBUG, L_GENERAL, "Using locale dir '%s' and locale langauge %s/%s\n", locale_dir, messages, ctype);
|
|
textdomain("minidlna");
|
|
#endif
|
|
}
|
|
|
|
/* init phase :
|
|
* 1) read configuration file
|
|
* 2) read command line arguments
|
|
* 3) daemonize
|
|
* 4) check and write pid file
|
|
* 5) set startup time stamp
|
|
* 6) compute presentation URL
|
|
* 7) set signal handlers */
|
|
static int
|
|
init(int argc, char **argv)
|
|
{
|
|
int i;
|
|
int pid;
|
|
int debug_flag = 0;
|
|
int verbose_flag = 0;
|
|
int options_flag = 0;
|
|
struct sigaction sa;
|
|
const char * presurl = NULL;
|
|
const char * optionsfile = "/etc/minidlna.conf";
|
|
char mac_str[13];
|
|
char *string, *word;
|
|
char *path;
|
|
char buf[PATH_MAX];
|
|
char log_str[75] = "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn";
|
|
char *log_level = NULL;
|
|
struct media_dir_s *media_dir;
|
|
int ifaces = 0;
|
|
media_types types;
|
|
uid_t uid = 0;
|
|
gid_t gid = 0;
|
|
int error;
|
|
|
|
/* first check if "-f" option is used */
|
|
for (i=2; i<argc; i++)
|
|
{
|
|
if (strcmp(argv[i-1], "-f") == 0)
|
|
{
|
|
optionsfile = argv[i];
|
|
options_flag = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* set up uuid based on mac address */
|
|
if (getsyshwaddr(mac_str, sizeof(mac_str)) < 0)
|
|
{
|
|
DPRINTF(E_OFF, L_GENERAL, "No MAC address found. Falling back to generic UUID.\n");
|
|
strcpy(mac_str, "554e4b4e4f57");
|
|
}
|
|
snprintf(uuidvalue+5, UUIDVALUE_MAX_LEN-5, "4d696e69-444c-164e-9d41-%s", mac_str);
|
|
|
|
getfriendlyname(friendly_name, FRIENDLYNAME_MAX_LEN);
|
|
|
|
runtime_vars.port = 8200;
|
|
runtime_vars.notify_interval = 895; /* seconds between SSDP announces */
|
|
runtime_vars.max_connections = 50;
|
|
runtime_vars.root_container = NULL;
|
|
runtime_vars.ifaces[0] = NULL;
|
|
|
|
/* read options file first since
|
|
* command line arguments have final say */
|
|
if (readoptionsfile(optionsfile) < 0)
|
|
{
|
|
/* only error if file exists or using -f */
|
|
if(access(optionsfile, F_OK) == 0 || options_flag)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Error reading configuration file %s\n", optionsfile);
|
|
}
|
|
|
|
for (i=0; i<num_options; i++)
|
|
{
|
|
switch (ary_options[i].id)
|
|
{
|
|
case UPNPIFNAME:
|
|
for (string = ary_options[i].value; (word = strtok(string, ",")); string = NULL)
|
|
{
|
|
if (ifaces >= MAX_LAN_ADDR)
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL, "Too many interfaces (max: %d), ignoring %s\n",
|
|
MAX_LAN_ADDR, word);
|
|
break;
|
|
}
|
|
while (isspace(*word))
|
|
word++;
|
|
runtime_vars.ifaces[ifaces++] = word;
|
|
}
|
|
break;
|
|
case UPNPPORT:
|
|
runtime_vars.port = atoi(ary_options[i].value);
|
|
break;
|
|
case UPNPPRESENTATIONURL:
|
|
presurl = ary_options[i].value;
|
|
break;
|
|
case UPNPNOTIFY_INTERVAL:
|
|
runtime_vars.notify_interval = atoi(ary_options[i].value);
|
|
break;
|
|
case UPNPSERIAL:
|
|
strncpyt(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN);
|
|
break;
|
|
case UPNPMODEL_NAME:
|
|
strncpyt(modelname, ary_options[i].value, MODELNAME_MAX_LEN);
|
|
break;
|
|
case UPNPMODEL_NUMBER:
|
|
strncpyt(modelnumber, ary_options[i].value, MODELNUMBER_MAX_LEN);
|
|
break;
|
|
case UPNPFRIENDLYNAME:
|
|
strncpyt(friendly_name, ary_options[i].value, FRIENDLYNAME_MAX_LEN);
|
|
break;
|
|
case UPNPMEDIADIR:
|
|
types = ALL_MEDIA;
|
|
path = ary_options[i].value;
|
|
word = strchr(path, ',');
|
|
if (word && (access(path, F_OK) != 0))
|
|
{
|
|
types = 0;
|
|
while (*path)
|
|
{
|
|
if (*path == ',')
|
|
{
|
|
path++;
|
|
break;
|
|
}
|
|
else if (*path == 'A' || *path == 'a')
|
|
types |= TYPE_AUDIO;
|
|
else if (*path == 'V' || *path == 'v')
|
|
types |= TYPE_VIDEO;
|
|
else if (*path == 'P' || *path == 'p')
|
|
types |= TYPE_IMAGE;
|
|
else
|
|
DPRINTF(E_FATAL, L_GENERAL, "Media directory entry not understood [%s]\n",
|
|
ary_options[i].value);
|
|
path++;
|
|
}
|
|
}
|
|
path = realpath(path, buf);
|
|
if (!path || access(path, F_OK) != 0)
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL, "Media directory \"%s\" not accessible [%s]\n",
|
|
ary_options[i].value, strerror(errno));
|
|
break;
|
|
}
|
|
media_dir = calloc(1, sizeof(struct media_dir_s));
|
|
media_dir->path = strdup(path);
|
|
media_dir->types = types;
|
|
if (media_dirs)
|
|
{
|
|
struct media_dir_s *all_dirs = media_dirs;
|
|
while( all_dirs->next )
|
|
all_dirs = all_dirs->next;
|
|
all_dirs->next = media_dir;
|
|
}
|
|
else
|
|
media_dirs = media_dir;
|
|
break;
|
|
case UPNPALBUMART_NAMES:
|
|
for (string = ary_options[i].value; (word = strtok(string, "/")); string = NULL)
|
|
{
|
|
struct album_art_name_s * this_name = calloc(1, sizeof(struct album_art_name_s));
|
|
int len = strlen(word);
|
|
if (word[len-1] == '*')
|
|
{
|
|
word[len-1] = '\0';
|
|
this_name->wildcard = 1;
|
|
}
|
|
this_name->name = strdup(word);
|
|
if (album_art_names)
|
|
{
|
|
struct album_art_name_s * all_names = album_art_names;
|
|
while( all_names->next )
|
|
all_names = all_names->next;
|
|
all_names->next = this_name;
|
|
}
|
|
else
|
|
album_art_names = this_name;
|
|
}
|
|
break;
|
|
case UPNPDBDIR:
|
|
path = realpath(ary_options[i].value, buf);
|
|
if (!path)
|
|
path = (ary_options[i].value);
|
|
make_dir(path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);
|
|
if (access(path, F_OK) != 0)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Database path not accessible! [%s]\n", path);
|
|
strncpyt(db_path, path, sizeof(db_path));
|
|
break;
|
|
case UPNPLOGDIR:
|
|
path = realpath(ary_options[i].value, buf);
|
|
if (!path)
|
|
path = ary_options[i].value;
|
|
if (snprintf(log_path, sizeof(log_path), "%s", path) > sizeof(log_path))
|
|
DPRINTF(E_FATAL, L_GENERAL, "Log path too long! [%s]\n", path);
|
|
make_dir(log_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);
|
|
break;
|
|
case UPNPLOGLEVEL:
|
|
log_level = ary_options[i].value;
|
|
break;
|
|
case UPNPINOTIFY:
|
|
if (!strtobool(ary_options[i].value))
|
|
CLEARFLAG(INOTIFY_MASK);
|
|
break;
|
|
case ENABLE_TIVO:
|
|
if (strtobool(ary_options[i].value))
|
|
SETFLAG(TIVO_MASK);
|
|
break;
|
|
case ENABLE_DLNA_STRICT:
|
|
if (strtobool(ary_options[i].value))
|
|
SETFLAG(DLNA_STRICT_MASK);
|
|
break;
|
|
case ROOT_CONTAINER:
|
|
switch (ary_options[i].value[0]) {
|
|
case '.':
|
|
runtime_vars.root_container = NULL;
|
|
break;
|
|
case 'B':
|
|
case 'b':
|
|
runtime_vars.root_container = BROWSEDIR_ID;
|
|
break;
|
|
case 'M':
|
|
case 'm':
|
|
runtime_vars.root_container = MUSIC_ID;
|
|
break;
|
|
case 'V':
|
|
case 'v':
|
|
runtime_vars.root_container = VIDEO_ID;
|
|
break;
|
|
case 'P':
|
|
case 'p':
|
|
runtime_vars.root_container = IMAGE_ID;
|
|
break;
|
|
default:
|
|
runtime_vars.root_container = ary_options[i].value;
|
|
DPRINTF(E_WARN, L_GENERAL, "Using arbitrary root container [%s]\n",
|
|
ary_options[i].value);
|
|
break;
|
|
}
|
|
break;
|
|
case UPNPMINISSDPDSOCKET:
|
|
minissdpdsocketpath = ary_options[i].value;
|
|
break;
|
|
case UPNPUUID:
|
|
strcpy(uuidvalue+5, ary_options[i].value);
|
|
break;
|
|
case USER_ACCOUNT:
|
|
uid = strtoul(ary_options[i].value, &string, 0);
|
|
if (*string)
|
|
{
|
|
/* Symbolic username given, not UID. */
|
|
struct passwd *entry = getpwnam(ary_options[i].value);
|
|
if (!entry)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Bad user '%s'.\n",
|
|
ary_options[i].value);
|
|
uid = entry->pw_uid;
|
|
if (!gid)
|
|
gid = entry->pw_gid;
|
|
}
|
|
break;
|
|
case FORCE_SORT_CRITERIA:
|
|
force_sort_criteria = ary_options[i].value;
|
|
if (force_sort_criteria[0] == '!')
|
|
{
|
|
SETFLAG(FORCE_ALPHASORT_MASK);
|
|
force_sort_criteria++;
|
|
}
|
|
break;
|
|
case MAX_CONNECTIONS:
|
|
runtime_vars.max_connections = atoi(ary_options[i].value);
|
|
break;
|
|
case MERGE_MEDIA_DIRS:
|
|
if (strtobool(ary_options[i].value))
|
|
SETFLAG(MERGE_MEDIA_DIRS_MASK);
|
|
break;
|
|
case WIDE_LINKS:
|
|
if (strtobool(ary_options[i].value))
|
|
SETFLAG(WIDE_LINKS_MASK);
|
|
break;
|
|
case TIVO_DISCOVERY:
|
|
if (strcasecmp(ary_options[i].value, "beacon") == 0)
|
|
CLEARFLAG(TIVO_BONJOUR_MASK);
|
|
break;
|
|
case ENABLE_SUBTITLES:
|
|
if (!strtobool(ary_options[i].value))
|
|
CLEARFLAG(SUBTITLES_MASK);
|
|
break;
|
|
default:
|
|
DPRINTF(E_ERROR, L_GENERAL, "Unknown option in file %s\n",
|
|
optionsfile);
|
|
}
|
|
}
|
|
if (!log_path[0])
|
|
strncpyt(log_path, DEFAULT_LOG_PATH, sizeof(log_path));
|
|
if (!db_path[0])
|
|
strncpyt(db_path, DEFAULT_DB_PATH, sizeof(db_path));
|
|
|
|
/* command line arguments processing */
|
|
for (i=1; i<argc; i++)
|
|
{
|
|
if (argv[i][0] != '-')
|
|
{
|
|
DPRINTF(E_FATAL, L_GENERAL, "Unknown option: %s\n", argv[i]);
|
|
}
|
|
else if (strcmp(argv[i], "--help") == 0)
|
|
{
|
|
runtime_vars.port = -1;
|
|
break;
|
|
}
|
|
else switch(argv[i][1])
|
|
{
|
|
case 't':
|
|
if (i+1 < argc)
|
|
runtime_vars.notify_interval = atoi(argv[++i]);
|
|
else
|
|
DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]);
|
|
break;
|
|
case 's':
|
|
if (i+1 < argc)
|
|
strncpyt(serialnumber, argv[++i], SERIALNUMBER_MAX_LEN);
|
|
else
|
|
DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]);
|
|
break;
|
|
case 'm':
|
|
if (i+1 < argc)
|
|
strncpyt(modelnumber, argv[++i], MODELNUMBER_MAX_LEN);
|
|
else
|
|
DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]);
|
|
break;
|
|
case 'p':
|
|
if (i+1 < argc)
|
|
runtime_vars.port = atoi(argv[++i]);
|
|
else
|
|
DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]);
|
|
break;
|
|
case 'P':
|
|
if (i+1 < argc)
|
|
{
|
|
if (argv[++i][0] != '/')
|
|
DPRINTF(E_FATAL, L_GENERAL, "Option -%c requires an absolute filename.\n", argv[i-1][1]);
|
|
else
|
|
pidfilename = argv[i];
|
|
}
|
|
else
|
|
DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]);
|
|
break;
|
|
case 'd':
|
|
debug_flag = 1;
|
|
case 'v':
|
|
verbose_flag = 1;
|
|
break;
|
|
case 'L':
|
|
SETFLAG(NO_PLAYLIST_MASK);
|
|
break;
|
|
case 'w':
|
|
if (i+1 < argc)
|
|
presurl = argv[++i];
|
|
else
|
|
DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]);
|
|
break;
|
|
case 'i':
|
|
if (i+1 < argc)
|
|
{
|
|
i++;
|
|
if (ifaces >= MAX_LAN_ADDR)
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL, "Too many interfaces (max: %d), ignoring %s\n",
|
|
MAX_LAN_ADDR, argv[i]);
|
|
break;
|
|
}
|
|
runtime_vars.ifaces[ifaces++] = argv[i];
|
|
}
|
|
else
|
|
DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]);
|
|
break;
|
|
case 'f':
|
|
i++; /* discarding, the config file is already read */
|
|
break;
|
|
case 'h':
|
|
runtime_vars.port = -1; // triggers help display
|
|
break;
|
|
case 'r':
|
|
SETFLAG(RESCAN_MASK);
|
|
break;
|
|
case 'R':
|
|
snprintf(buf, sizeof(buf), "rm -rf %s/files.db %s/art_cache", db_path, db_path);
|
|
if (system(buf) != 0)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to clean old file cache %s. EXITING\n", db_path);
|
|
break;
|
|
case 'u':
|
|
if (i+1 != argc)
|
|
{
|
|
i++;
|
|
uid = strtoul(argv[i], &string, 0);
|
|
if (*string)
|
|
{
|
|
/* Symbolic username given, not UID. */
|
|
struct passwd *entry = getpwnam(argv[i]);
|
|
if (!entry)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Bad user '%s'.\n", argv[i]);
|
|
uid = entry->pw_uid;
|
|
if (!gid)
|
|
gid = entry->pw_gid;
|
|
}
|
|
}
|
|
else
|
|
DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]);
|
|
break;
|
|
case 'g':
|
|
if (i+1 != argc)
|
|
{
|
|
i++;
|
|
gid = strtoul(argv[i], &string, 0);
|
|
if (*string)
|
|
{
|
|
/* Symbolic group given, not GID. */
|
|
struct group *grp = getgrnam(argv[i]);
|
|
if (!grp)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Bad group '%s'.\n", argv[i]);
|
|
gid = grp->gr_gid;
|
|
}
|
|
}
|
|
else
|
|
DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]);
|
|
break;
|
|
#ifdef __linux__
|
|
case 'S':
|
|
SETFLAG(SYSTEMD_MASK);
|
|
break;
|
|
#endif
|
|
case 'V':
|
|
printf("Version " MINIDLNA_VERSION "\n");
|
|
exit(0);
|
|
break;
|
|
default:
|
|
DPRINTF(E_ERROR, L_GENERAL, "Unknown option: %s\n", argv[i]);
|
|
runtime_vars.port = -1; // triggers help display
|
|
}
|
|
}
|
|
|
|
if (runtime_vars.port <= 0)
|
|
{
|
|
printf("Usage:\n\t"
|
|
"%s [-d] [-v] [-f config_file] [-p port]\n"
|
|
"\t\t[-i network_interface] [-u uid_to_run_as] [-g group_to_run_as]\n"
|
|
"\t\t[-t notify_interval] [-P pid_filename]\n"
|
|
"\t\t[-s serial] [-m model_number]\n"
|
|
#ifdef __linux__
|
|
"\t\t[-w url] [-r] [-R] [-L] [-S] [-V] [-h]\n"
|
|
#else
|
|
"\t\t[-w url] [-r] [-R] [-L] [-V] [-h]\n"
|
|
#endif
|
|
"\nNotes:\n\tNotify interval is in seconds. Default is 895 seconds.\n"
|
|
"\tDefault pid file is %s.\n"
|
|
"\tWith -d minidlna will run in debug mode (not daemonize).\n"
|
|
"\t-w sets the presentation url. Default is http address on port 80\n"
|
|
"\t-v enables verbose output\n"
|
|
"\t-h displays this text\n"
|
|
"\t-r forces a rescan\n"
|
|
"\t-R forces a rebuild\n"
|
|
"\t-L do not create playlists\n"
|
|
#ifdef __linux__
|
|
"\t-S changes behaviour for systemd\n"
|
|
#endif
|
|
"\t-V print the version number\n",
|
|
argv[0], pidfilename);
|
|
return 1;
|
|
}
|
|
|
|
if (verbose_flag)
|
|
{
|
|
strcpy(log_str+65, "debug");
|
|
log_level = log_str;
|
|
}
|
|
else if (!log_level)
|
|
log_level = log_str;
|
|
|
|
/* Set the default log to stdout */
|
|
if (debug_flag)
|
|
{
|
|
pid = getpid();
|
|
strcpy(log_str+65, "maxdebug");
|
|
log_level = log_str;
|
|
log_path[0] = '\0';
|
|
}
|
|
else if (GETFLAG(SYSTEMD_MASK))
|
|
{
|
|
pid = getpid();
|
|
log_path[0] = '\0';
|
|
}
|
|
else
|
|
{
|
|
pid = process_daemonize();
|
|
if (access(db_path, F_OK) != 0)
|
|
make_dir(db_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO);
|
|
}
|
|
if (log_init(log_level) < 0)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to open log file '%s/" LOGFILE_NAME "': %s\n",
|
|
log_path, strerror(errno));
|
|
|
|
if (process_check_if_running(pidfilename) < 0)
|
|
DPRINTF(E_FATAL, L_GENERAL, SERVER_NAME " is already running. EXITING.\n");
|
|
|
|
set_startup_time();
|
|
|
|
/* presentation url */
|
|
if (presurl)
|
|
strncpyt(presentationurl, presurl, PRESENTATIONURL_MAX_LEN);
|
|
else
|
|
strcpy(presentationurl, "/");
|
|
|
|
/* set signal handlers */
|
|
memset(&sa, 0, sizeof(struct sigaction));
|
|
sa.sa_handler = sigterm;
|
|
if (sigaction(SIGTERM, &sa, NULL))
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGTERM");
|
|
if (sigaction(SIGINT, &sa, NULL))
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGINT");
|
|
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGPIPE");
|
|
if (signal(SIGHUP, &sighup) == SIG_ERR)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGHUP");
|
|
if (signal(SIGUSR2, SIG_IGN) == SIG_ERR)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGUSR2");
|
|
signal(SIGUSR1, &sigusr1);
|
|
sa.sa_handler = process_handle_child_termination;
|
|
if (sigaction(SIGCHLD, &sa, NULL))
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to set %s handler. EXITING.\n", "SIGCHLD");
|
|
|
|
if (writepidfile(pidfilename, pid, uid) != 0)
|
|
pidfilename = NULL;
|
|
|
|
if (uid > 0)
|
|
{
|
|
struct stat st;
|
|
if (stat(db_path, &st) == 0 && st.st_uid != uid && chown(db_path, uid, -1) != 0)
|
|
DPRINTF(E_ERROR, L_GENERAL, "Unable to set db_path [%s] ownership to %d: %s\n",
|
|
db_path, uid, strerror(errno));
|
|
}
|
|
|
|
if (gid > 0 && setgid(gid) == -1)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to switch to gid '%d'. [%s] EXITING.\n",
|
|
gid, strerror(errno));
|
|
|
|
if (uid > 0 && setuid(uid) == -1)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to switch to uid '%d'. [%s] EXITING.\n",
|
|
uid, strerror(errno));
|
|
|
|
children = calloc(runtime_vars.max_connections, sizeof(struct child));
|
|
if (!children)
|
|
{
|
|
DPRINTF(E_ERROR, L_GENERAL, "Allocation failed\n");
|
|
return 1;
|
|
}
|
|
|
|
if ((error = event_module.init()) != 0)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to init event module. "
|
|
"[%s] EXITING.\n", strerror(error));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* === main === */
|
|
/* process HTTP or SSDP requests */
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int ret, i;
|
|
int shttpl = -1;
|
|
int smonitor = -1;
|
|
struct upnphttp * e = 0;
|
|
struct upnphttp * next;
|
|
struct timeval tv, timeofday, lastnotifytime = {0, 0};
|
|
time_t lastupdatetime = 0, lastdbtime = 0;
|
|
u_long timeout; /* in milliseconds */
|
|
int last_changecnt = 0;
|
|
pid_t scanner_pid = 0;
|
|
pthread_t inotify_thread = 0;
|
|
struct event ssdpev, httpev, monev;
|
|
#ifdef TIVO_SUPPORT
|
|
uint8_t beacon_interval = 5;
|
|
int sbeacon = -1;
|
|
struct sockaddr_in tivo_bcast;
|
|
struct timeval lastbeacontime = {0, 0};
|
|
struct event beaconev;
|
|
#endif
|
|
|
|
for (i = 0; i < L_MAX; i++)
|
|
log_level[i] = E_WARN;
|
|
|
|
ret = init(argc, argv);
|
|
if (ret != 0)
|
|
return 1;
|
|
init_nls();
|
|
|
|
DPRINTF(E_WARN, L_GENERAL, "Starting " SERVER_NAME " version " MINIDLNA_VERSION ".\n");
|
|
if (sqlite3_libversion_number() < 3005001)
|
|
{
|
|
DPRINTF(E_WARN, L_GENERAL, "SQLite library is old. Please use version 3.5.1 or newer.\n");
|
|
}
|
|
|
|
LIST_INIT(&upnphttphead);
|
|
|
|
ret = open_db(NULL);
|
|
if (ret == 0)
|
|
{
|
|
updateID = sql_get_int_field(db, "SELECT VALUE from SETTINGS where KEY = 'UPDATE_ID'");
|
|
if (updateID == -1)
|
|
ret = -1;
|
|
}
|
|
check_db(db, ret, &scanner_pid);
|
|
lastdbtime = _get_dbtime();
|
|
#ifdef HAVE_INOTIFY
|
|
if( GETFLAG(INOTIFY_MASK) )
|
|
{
|
|
if (!sqlite3_threadsafe() || sqlite3_libversion_number() < 3005001)
|
|
DPRINTF(E_ERROR, L_GENERAL, "SQLite library is not threadsafe! "
|
|
"Inotify will be disabled.\n");
|
|
else if (pthread_create(&inotify_thread, NULL, start_inotify, NULL) != 0)
|
|
DPRINTF(E_FATAL, L_GENERAL, "ERROR: pthread_create() failed for start_inotify. EXITING\n");
|
|
}
|
|
#endif /* HAVE_INOTIFY */
|
|
|
|
#ifdef HAVE_KQUEUE
|
|
if (!GETFLAG(SCANNING_MASK)) {
|
|
lav_register_all();
|
|
kqueue_monitor_start();
|
|
}
|
|
#endif /* HAVE_KQUEUE */
|
|
|
|
smonitor = OpenAndConfMonitorSocket();
|
|
if (smonitor > 0)
|
|
{
|
|
monev = (struct event ){ .fd = smonitor, .rdwr = EVENT_READ, .process = ProcessMonitorEvent };
|
|
event_module.add(&monev);
|
|
}
|
|
|
|
sssdp = OpenAndConfSSDPReceiveSocket();
|
|
if (sssdp < 0)
|
|
{
|
|
DPRINTF(E_INFO, L_GENERAL, "Failed to open socket for receiving SSDP. Trying to use MiniSSDPd\n");
|
|
reload_ifaces(0); /* populate lan_addr[0].str */
|
|
if (SubmitServicesToMiniSSDPD(lan_addr[0].str, runtime_vars.port) < 0)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to connect to MiniSSDPd. EXITING");
|
|
}
|
|
else
|
|
{
|
|
ssdpev = (struct event ){ .fd = sssdp, .rdwr = EVENT_READ, .process = ProcessSSDPRequest };
|
|
event_module.add(&ssdpev);
|
|
}
|
|
|
|
/* open socket for HTTP connections. */
|
|
shttpl = OpenAndConfHTTPSocket(runtime_vars.port);
|
|
if (shttpl < 0)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to open socket for HTTP. EXITING\n");
|
|
DPRINTF(E_WARN, L_GENERAL, "HTTP listening on port %d\n", runtime_vars.port);
|
|
httpev = (struct event ){ .fd = shttpl, .rdwr = EVENT_READ, .process = ProcessListen };
|
|
event_module.add(&httpev);
|
|
|
|
#ifdef TIVO_SUPPORT
|
|
if (GETFLAG(TIVO_MASK))
|
|
{
|
|
DPRINTF(E_WARN, L_GENERAL, "TiVo support is enabled.\n");
|
|
/* Add TiVo-specific randomize function to sqlite */
|
|
ret = sqlite3_create_function(db, "tivorandom", 1, SQLITE_UTF8, NULL, &TiVoRandomSeedFunc, NULL, NULL);
|
|
if (ret != SQLITE_OK)
|
|
DPRINTF(E_ERROR, L_TIVO, "ERROR: Failed to add sqlite randomize function for TiVo!\n");
|
|
if (GETFLAG(TIVO_BONJOUR_MASK))
|
|
{
|
|
tivo_bonjour_register();
|
|
}
|
|
else
|
|
{
|
|
/* open socket for sending Tivo notifications */
|
|
sbeacon = OpenAndConfTivoBeaconSocket();
|
|
if(sbeacon < 0)
|
|
DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending Tivo beacon notify "
|
|
"messages. EXITING\n");
|
|
beaconev = (struct event ){ .fd = sbeacon, .rdwr = EVENT_READ, .process = ProcessTiVoBeacon };
|
|
event_module.add(&beaconev);
|
|
tivo_bcast.sin_family = AF_INET;
|
|
tivo_bcast.sin_addr.s_addr = htonl(getBcastAddress());
|
|
tivo_bcast.sin_port = htons(2190);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
reload_ifaces(0);
|
|
lastnotifytime.tv_sec = time(NULL) + runtime_vars.notify_interval;
|
|
|
|
/* main loop */
|
|
while (!quitting)
|
|
{
|
|
if (gettimeofday(&timeofday, 0) < 0)
|
|
DPRINTF(E_FATAL, L_GENERAL, "gettimeofday(): %s\n", strerror(errno));
|
|
/* Check if we need to send SSDP NOTIFY messages and do it if
|
|
* needed */
|
|
tv = lastnotifytime;
|
|
tv.tv_sec += runtime_vars.notify_interval;
|
|
if (timevalcmp(&timeofday, &tv, >=))
|
|
{
|
|
DPRINTF(E_DEBUG, L_SSDP, "Sending SSDP notifies\n");
|
|
for (i = 0; i < n_lan_addr; i++)
|
|
{
|
|
SendSSDPNotifies(lan_addr[i].snotify, lan_addr[i].str,
|
|
runtime_vars.port, runtime_vars.notify_interval);
|
|
}
|
|
lastnotifytime = timeofday;
|
|
timeout = runtime_vars.notify_interval * 1000;
|
|
}
|
|
else
|
|
{
|
|
timevalsub(&tv, &timeofday);
|
|
timeout = tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
|
}
|
|
#ifdef TIVO_SUPPORT
|
|
if (sbeacon >= 0)
|
|
{
|
|
u_long beacontimeout;
|
|
|
|
tv = lastbeacontime;
|
|
tv.tv_sec += beacon_interval;
|
|
if (timevalcmp(&timeofday, &tv, >=))
|
|
{
|
|
sendBeaconMessage(sbeacon, &tivo_bcast, sizeof(struct sockaddr_in), 1);
|
|
lastbeacontime = timeofday;
|
|
beacontimeout = beacon_interval * 1000;
|
|
if (timeout > beacon_interval * 1000)
|
|
timeout = beacon_interval * 1000;
|
|
/* Beacons should be sent every 5 seconds or
|
|
* so for the first minute, then every minute
|
|
* or so thereafter. */
|
|
if (beacon_interval == 5 && (timeofday.tv_sec - startup_time) > 60)
|
|
beacon_interval = 60;
|
|
}
|
|
else
|
|
{
|
|
timevalsub(&tv, &timeofday);
|
|
beacontimeout = tv.tv_sec * 1000 +
|
|
tv.tv_usec / 1000;
|
|
}
|
|
if (timeout > beacontimeout)
|
|
timeout = beacontimeout;
|
|
}
|
|
#endif
|
|
|
|
if (GETFLAG(SCANNING_MASK) && kill(scanner_pid, 0) != 0) {
|
|
CLEARFLAG(SCANNING_MASK);
|
|
if (_get_dbtime() != lastdbtime)
|
|
updateID++;
|
|
#ifdef HAVE_KQUEUE
|
|
lav_register_all();
|
|
kqueue_monitor_start();
|
|
#endif /* HAVE_KQUEUE */
|
|
}
|
|
|
|
event_module.process(timeout);
|
|
if (quitting)
|
|
goto shutdown;
|
|
|
|
upnpevents_gc();
|
|
|
|
/* increment SystemUpdateID if the content database has changed,
|
|
* and if there is an active HTTP connection, at most once every 2 seconds */
|
|
if (!LIST_EMPTY(&upnphttphead) &&
|
|
(timeofday.tv_sec >= (lastupdatetime + 2)))
|
|
{
|
|
if (GETFLAG(SCANNING_MASK))
|
|
{
|
|
time_t dbtime = _get_dbtime();
|
|
if (dbtime != lastdbtime)
|
|
{
|
|
lastdbtime = dbtime;
|
|
last_changecnt = -1;
|
|
}
|
|
}
|
|
if (sqlite3_total_changes(db) != last_changecnt)
|
|
{
|
|
updateID++;
|
|
last_changecnt = sqlite3_total_changes(db);
|
|
upnp_event_var_change_notify(EContentDirectory);
|
|
lastupdatetime = timeofday.tv_sec;
|
|
}
|
|
}
|
|
/* delete finished HTTP connections */
|
|
for (e = upnphttphead.lh_first; e != NULL; e = next)
|
|
{
|
|
next = e->entries.le_next;
|
|
if(e->state >= 100)
|
|
{
|
|
LIST_REMOVE(e, entries);
|
|
Delete_upnphttp(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
shutdown:
|
|
/* kill the scanner */
|
|
if (GETFLAG(SCANNING_MASK) && scanner_pid)
|
|
kill(scanner_pid, SIGKILL);
|
|
|
|
/* close out open sockets */
|
|
while (upnphttphead.lh_first != NULL)
|
|
{
|
|
e = upnphttphead.lh_first;
|
|
LIST_REMOVE(e, entries);
|
|
Delete_upnphttp(e);
|
|
}
|
|
if (sssdp >= 0)
|
|
close(sssdp);
|
|
if (shttpl >= 0)
|
|
close(shttpl);
|
|
#ifdef TIVO_SUPPORT
|
|
if (sbeacon >= 0)
|
|
close(sbeacon);
|
|
#endif
|
|
if (smonitor >= 0)
|
|
close(smonitor);
|
|
|
|
for (i = 0; i < n_lan_addr; i++)
|
|
{
|
|
SendSSDPGoodbyes(lan_addr[i].snotify);
|
|
close(lan_addr[i].snotify);
|
|
}
|
|
|
|
if (inotify_thread)
|
|
{
|
|
pthread_kill(inotify_thread, SIGCHLD);
|
|
pthread_join(inotify_thread, NULL);
|
|
}
|
|
|
|
/* kill other child processes */
|
|
process_reap_children();
|
|
free(children);
|
|
|
|
event_module.fini();
|
|
|
|
sql_exec(db, "UPDATE SETTINGS set VALUE = '%u' where KEY = 'UPDATE_ID'", updateID);
|
|
sqlite3_close(db);
|
|
|
|
upnpevents_removeSubscribers();
|
|
|
|
if (pidfilename && unlink(pidfilename) < 0)
|
|
DPRINTF(E_ERROR, L_GENERAL, "Failed to remove pidfile %s: %s\n", pidfilename, strerror(errno));
|
|
|
|
log_close();
|
|
freeoptions();
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|