/* 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 . * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #ifdef ENABLE_NLS #include #include #endif #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 "inotify.h" #include "log.h" #include "tivo_beacon.h" #include "tivo_utils.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 /* 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, 6) < 0) { DPRINTF(E_ERROR, L_GENERAL, "listen(http): %s\n", strerror(errno)); close(s); return -1; } return s; } /* 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, re-read\n", sig); reload_ifaces(1); } /* record the startup time */ static void set_startup_time(void) { #if 0 startup_time = time(NULL); #else startup_time = uptime(); #endif } static void getfriendlyname(char *buf, int len) { char *p = NULL; char hn[256]; 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); #if PNPX memcpy(pnpx_hwid+4, "01F2", 4); if (strcmp(modelnumber, "NVX") == 0) memcpy(pnpx_hwid+17, "0101", 4); else if (strcmp(modelnumber, "Pro") == 0 || strcmp(modelnumber, "Pro 6") == 0 || strncmp(modelnumber, "Ultra 6", 7) == 0) memcpy(pnpx_hwid+17, "0102", 4); else if (strcmp(modelnumber, "Pro 2") == 0 || strncmp(modelnumber, "Ultra 2", 7) == 0) memcpy(pnpx_hwid+17, "0103", 4); else if (strcmp(modelnumber, "Pro 4") == 0 || strncmp(modelnumber, "Ultra 4", 7) == 0) memcpy(pnpx_hwid+17, "0104", 4); else if (strcmp(modelnumber+1, "100") == 0) memcpy(pnpx_hwid+17, "0105", 4); else if (strcmp(modelnumber+1, "200") == 0) memcpy(pnpx_hwid+17, "0106", 4); /* 0107 = Stora */ else if (strcmp(modelnumber, "Duo v2") == 0) memcpy(pnpx_hwid+17, "0108", 4); else if (strcmp(modelnumber, "NV+ v2") == 0) memcpy(pnpx_hwid+17, "0109", 4); #endif #else char * logname; logname = getenv("LOGNAME"); #ifndef STATIC // Disable for static linking if (!logname) { struct passwd * pwent; pwent = getpwuid(getuid()); if (pwent) logname = pwent->pw_name; } #endif snprintf(buf+off, len-off, "%s", logname?logname:"Unknown"); #endif } 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; int retry_times; 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 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: rescan_db = 0; 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); retry_times = 0; retry: snprintf(cmd, sizeof(cmd), "rm -rf %s/files.db %s/art_cache", db_path_spec, db_path_spec); if (system(cmd) != 0) { if (retry_times++ < 2) goto retry; 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 != 0 || rescan_db == 1) { #if USE_FORK scanning = 1; 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 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 setlocale(LC_MESSAGES, ""); setlocale(LC_CTYPE, "en_US.utf8"); DPRINTF(E_DEBUG, L_GENERAL, "Using locale dir %s\n", bindtextdomain("minidlna", getenv("TEXTDOMAINDIR"))); textdomain("minidlna"); #endif } // add for getting scanning status void create_scantag(void) { char path[PATH_MAX]; FILE *fp; snprintf(path, sizeof(path), "%s/scantag", db_path); fp=fopen(path, "w"); if(fp) fclose(fp); } void remove_scantag(void) { char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/scantag", db_path); unlink(path); } /* 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; char *ptr, *shift; int retry_times; /* first check if "-f" option is used */ for (i=2; i= 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_IMAGES; 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, PATH_MAX); break; case UPNPLOGDIR: 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, "Log path not accessible! [%s]\n", path); strncpyt(log_path, path, PATH_MAX); 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; } break; case FORCE_SORT_CRITERIA: force_sort_criteria = ary_options[i].value; 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; default: DPRINTF(E_ERROR, L_GENERAL, "Unknown option in file %s\n", optionsfile); } } if (log_path[0] == '\0') { if (db_path[0] == '\0') strncpyt(log_path, DEFAULT_LOG_PATH, PATH_MAX); else strncpyt(log_path, db_path, PATH_MAX); } if (db_path[0] == '\0') strncpyt(db_path, DEFAULT_DB_PATH, PATH_MAX); /* command line arguments processing */ for (i=1; i= 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': rescan_db = 1; break; case 'R': memset(db_path_spec, 0, 256); for(ptr = db_path, shift = db_path_spec; *ptr; ++ptr, ++shift){ if(strchr("()", *ptr)) *shift++ = '\\'; *shift = *ptr; } snprintf(buf, sizeof(buf), "rm -rf %s/files.db %s/art_cache", db_path_spec, db_path_spec); retry_times = 0; retry: if (system(buf) != 0) { if (retry_times++ < 2) goto retry; DPRINTF(E_FATAL, L_GENERAL, "Failed to clean old file cache. EXITING\n"); } 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; } } else DPRINTF(E_FATAL, L_GENERAL, "Option -%c takes one argument.\n", argv[i][1]); break; 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]\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 file path to NULL (stdout) */ path = NULL; if (debug_flag) { pid = getpid(); strcpy(log_str+65, "maxdebug"); log_level = log_str; } else if (GETFLAG(SYSTEMD_MASK)) { pid = getpid(); } else { pid = process_daemonize(); #ifdef READYNAS unlink("/ramfs/.upnp-av_scan"); path = "/var/log/upnp-av.log"; #else if (access(db_path, F_OK) != 0) make_dir(db_path, S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO); snprintf(buf, sizeof(buf), "%s/minidlna.log", log_path); path = buf; #endif } log_init(path, log_level); if (process_check_if_running(pidfilename) < 0) { DPRINTF(E_ERROR, L_GENERAL, SERVER_NAME " is already running. EXITING.\n"); return 1; } 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"); 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 (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; } // remove working flag remove_scantag(); return 0; } #if (!defined(RTN66U) && !defined(RTN56U)) #define PATH_ICON_PNG_SM "/rom/dlna/icon_sm.png" #define PATH_ICON_PNG_LRG "/rom/dlna/icon_lrg.png" #define PATH_ICON_JPEG_SM "/rom/dlna/icon_sm.jpg" #define PATH_ICON_JPEG_LRG "/rom/dlna/icon_lrg.jpg" unsigned char buf_png_sm[65536]; unsigned char buf_png_lrg[65536]; unsigned char buf_jpeg_sm[65536]; unsigned char buf_jpeg_lrg[65536]; int size_png_sm = 0; int size_png_lrg = 0; int size_jpeg_sm = 0; int size_jpeg_lrg = 0; int init_icon(const char *iconfile) { int fd = 0; int *size = NULL; unsigned char *buf = NULL; FILE *in = NULL; size_t i, offset; int ret = 0; if( strcmp(iconfile, PATH_ICON_PNG_SM) == 0 ) { buf = buf_png_sm; size = &size_png_sm; } else if( strcmp(iconfile, PATH_ICON_PNG_LRG) == 0 ) { buf = buf_png_lrg; size = &size_png_lrg; } else if( strcmp(iconfile, PATH_ICON_JPEG_SM) == 0 ) { buf = buf_jpeg_sm; size = &size_jpeg_sm; } else if( strcmp(iconfile, PATH_ICON_JPEG_LRG) == 0 ) { buf = buf_jpeg_lrg; size = &size_jpeg_lrg; } else { ret = -1; goto RETURN; } fd = open(iconfile, O_RDONLY); if (fd < 0) { fprintf(stderr, "reading icon file %s FAILED : %s\n", iconfile, strerror(errno)); ret = -1; goto RETURN; } else { struct stat filestats; int statstat; statstat = fstat(fd, &filestats); if (statstat < 0) { fprintf(stderr, "stat-ing iconfile %s FAILED : %s\n", iconfile, strerror(errno)); ret = -1; goto RETURN; } else { if (filestats.st_size > 65536) { ret = -1; goto RETURN; } else *size = filestats.st_size; } close(fd); if ((in = fopen(iconfile, "rb")) == NULL) { fprintf(stderr, "fopen icon file %s FAILED : %s\n", iconfile, strerror(errno)); *size = 0; ret = -1; goto RETURN; } /* loop through the file */ offset = 0; memset(buf, 0, *size); while ( (i = fread(buf + offset, 1, BUFSIZ, in)) != 0 ) { offset += i; } } RETURN: if (fd > 0) close(fd); if (in) fclose(in); return ret; } #endif #define NOTIFY_INTERVAL 3 /* === main === */ /* process HTTP or SSDP requests */ int main(int argc, char **argv) { int ret, i; int shttpl = -1; int smonitor = -1; LIST_HEAD(httplisthead, upnphttp) upnphttphead; struct upnphttp * e = 0; struct upnphttp * next; fd_set readset; /* for select() */ fd_set writeset; struct timeval timeout, timeofday, lastnotifytime = {0, 0}; time_t lastupdatetime = 0; int max_fd = -1; int last_changecnt = 0; pid_t scanner_pid = 0; pthread_t inotify_thread = 0; #ifdef TIVO_SUPPORT uint8_t beacon_interval = 5; int sbeacon = -1; struct sockaddr_in tivo_bcast; struct timeval lastbeacontime = {0, 0}; #endif for (i = 0; i < L_MAX; i++) log_level[i] = E_WARN; init_nls(); ret = init(argc, argv); if (ret != 0) return 1; #if (!defined(RTN66U) && !defined(RTN56U)) init_icon(PATH_ICON_PNG_SM); init_icon(PATH_ICON_PNG_LRG); init_icon(PATH_ICON_JPEG_SM); init_icon(PATH_ICON_JPEG_LRG); #endif 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); #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 smonitor = OpenAndConfMonitorSocket(); 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"); } /* 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); #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"); /* 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"); tivo_bcast.sin_family = AF_INET; tivo_bcast.sin_addr.s_addr = htonl(getBcastAddress()); tivo_bcast.sin_port = htons(2190); } #endif reload_ifaces(0); #if 0 lastnotifytime.tv_sec = time(NULL) + runtime_vars.notify_interval; #else lastnotifytime.tv_sec = uptime(); #endif /* main loop */ while (!quitting) { /* Check if we need to send SSDP NOTIFY messages and do it if * needed */ #if 0 if (gettimeofday(&timeofday, 0) < 0) { DPRINTF(E_ERROR, L_GENERAL, "gettimeofday(): %s\n", strerror(errno)); timeout.tv_sec = runtime_vars.notify_interval; timeout.tv_usec = 0; } else #else timeofday.tv_sec = uptime(); timeofday.tv_usec = 0; #endif { /* the comparison is not very precise but who cares ? */ #if 0 if (timeofday.tv_sec >= (lastnotifytime.tv_sec + runtime_vars.notify_interval)) #else if (timeofday.tv_sec >= (lastnotifytime.tv_sec + NOTIFY_INTERVAL)) #endif { DPRINTF(E_DEBUG, L_SSDP, "Sending SSDP notifies\n"); for (i = 0; i < n_lan_addr; i++) { #if 0 SendSSDPNotifies(lan_addr[i].snotify, lan_addr[i].str, runtime_vars.port, runtime_vars.notify_interval); #else SendSSDPNotifies(lan_addr[i].snotify, lan_addr[i].str, runtime_vars.port, NOTIFY_INTERVAL); #endif } memcpy(&lastnotifytime, &timeofday, sizeof(struct timeval)); #if 0 timeout.tv_sec = runtime_vars.notify_interval; #else timeout.tv_sec = NOTIFY_INTERVAL; #endif timeout.tv_usec = 0; } else { #if 0 timeout.tv_sec = lastnotifytime.tv_sec + runtime_vars.notify_interval - timeofday.tv_sec; #else timeout.tv_sec = lastnotifytime.tv_sec + NOTIFY_INTERVAL - timeofday.tv_sec; #endif if (timeofday.tv_usec > lastnotifytime.tv_usec) { timeout.tv_usec = 1000000 + lastnotifytime.tv_usec - timeofday.tv_usec; timeout.tv_sec--; } else timeout.tv_usec = lastnotifytime.tv_usec - timeofday.tv_usec; } #ifdef TIVO_SUPPORT if (sbeacon >= 0) { if (timeofday.tv_sec >= (lastbeacontime.tv_sec + beacon_interval)) { sendBeaconMessage(sbeacon, &tivo_bcast, sizeof(struct sockaddr_in), 1); memcpy(&lastbeacontime, &timeofday, sizeof(struct timeval)); if (timeout.tv_sec > beacon_interval) { timeout.tv_sec = beacon_interval; timeout.tv_usec = 0; } /* 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 if (timeout.tv_sec > (lastbeacontime.tv_sec + beacon_interval + 1 - timeofday.tv_sec)) timeout.tv_sec = lastbeacontime.tv_sec + beacon_interval - timeofday.tv_sec; } #endif } if (scanning) { if (!scanner_pid || kill(scanner_pid, 0) != 0) { scanning = 0; updateID++; } } /* select open sockets (SSDP, HTTP listen, and all HTTP soap sockets) */ FD_ZERO(&readset); if (sssdp >= 0) { FD_SET(sssdp, &readset); max_fd = MAX(max_fd, sssdp); } if (shttpl >= 0) { FD_SET(shttpl, &readset); max_fd = MAX(max_fd, shttpl); } #ifdef TIVO_SUPPORT if (sbeacon >= 0) { FD_SET(sbeacon, &readset); max_fd = MAX(max_fd, sbeacon); } #endif if (smonitor >= 0) { FD_SET(smonitor, &readset); max_fd = MAX(max_fd, smonitor); } i = 0; /* active HTTP connections count */ for (e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) { if ((e->socket >= 0) && (e->state <= 2)) { FD_SET(e->socket, &readset); max_fd = MAX(max_fd, e->socket); i++; } } FD_ZERO(&writeset); upnpevents_selectfds(&readset, &writeset, &max_fd); ret = select(max_fd+1, &readset, &writeset, 0, &timeout); if (ret < 0) { if(quitting) goto shutdown; if(errno == EINTR) continue; DPRINTF(E_ERROR, L_GENERAL, "select(all): %s\n", strerror(errno)); DPRINTF(E_FATAL, L_GENERAL, "Failed to select open sockets. EXITING\n"); } upnpevents_processfds(&readset, &writeset); /* process SSDP packets */ if (sssdp >= 0 && FD_ISSET(sssdp, &readset)) { /*DPRINTF(E_DEBUG, L_GENERAL, "Received SSDP Packet\n");*/ ProcessSSDPRequest(sssdp, (unsigned short)runtime_vars.port); } #ifdef TIVO_SUPPORT if (sbeacon >= 0 && FD_ISSET(sbeacon, &readset)) { /*DPRINTF(E_DEBUG, L_GENERAL, "Received UDP Packet\n");*/ ProcessTiVoBeacon(sbeacon); } #endif if (smonitor >= 0 && FD_ISSET(smonitor, &readset)) { ProcessMonitorEvent(smonitor); } /* increment SystemUpdateID if the content database has changed, * and if there is an active HTTP connection, at most once every 2 seconds */ if (i && (timeofday.tv_sec >= (lastupdatetime + 2))) { if (scanning || sqlite3_total_changes(db) != last_changecnt) { updateID++; last_changecnt = sqlite3_total_changes(db); upnp_event_var_change_notify(EContentDirectory); lastupdatetime = timeofday.tv_sec; } } /* process active HTTP connections */ for (e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next) { if ((e->socket >= 0) && (e->state <= 2) && (FD_ISSET(e->socket, &readset))) Process_upnphttp(e); } /* process incoming HTTP connections */ if (shttpl >= 0 && FD_ISSET(shttpl, &readset)) { int shttp; socklen_t clientnamelen; struct sockaddr_in clientname; clientnamelen = sizeof(struct sockaddr_in); shttp = accept(shttpl, (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); } } } /* 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 (scanning && scanner_pid) kill(scanner_pid, SIGKILL); /* kill other child processes */ process_reap_children(); free(children); /* 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_join(inotify_thread, NULL); 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); }