From 0324818d869621c5d5e1dac779cf6de13023f6ac Mon Sep 17 00:00:00 2001 From: Justin Maggard Date: Tue, 3 Feb 2009 03:36:59 +0000 Subject: [PATCH] * Fix some possible memory leaks. * Pay attention to the specified port number. * Add support for multiple media directories, and allow content filtering on the directories. --- albumart.c | 2 + getifaddr.c | 37 ++++++++--- minidlna.c | 165 ++++++++++++++++++++++++++++++++--------------- minidlna.conf | 7 ++ minidlnatypes.h | 18 ++++++ scanner.c | 150 +++++++++++++++++++++++++----------------- scanner.h | 5 +- upnpglobalvars.c | 3 +- upnpglobalvars.h | 3 +- upnphttp.c | 10 ++- upnpsoap.c | 18 +++--- 11 files changed, 281 insertions(+), 137 deletions(-) diff --git a/albumart.c b/albumart.c index 439c019..650ca69 100644 --- a/albumart.c +++ b/albumart.c @@ -222,6 +222,7 @@ find_album_art(const char * path) } else { + sqlite3_free(sql); sql = sqlite3_mprintf( "INSERT into ALBUM_ART" " (PATH, EMBEDDED) " "VALUES" @@ -231,6 +232,7 @@ find_album_art(const char * path) if( sql_exec(db, sql) == SQLITE_OK ) ret = sqlite3_last_insert_rowid(db); } + sqlite3_free_table(result); sqlite3_free(sql); } if( album_art ) diff --git a/getifaddr.c b/getifaddr.c index b7e173e..00679b5 100644 --- a/getifaddr.c +++ b/getifaddr.c @@ -59,17 +59,34 @@ getifaddr(const char * ifname, char * buf, int len) int getsysaddr(char * buf, int len) { - char hn[256]; - struct in_addr *addr; - struct hostent *host; + int i; + int s = socket(PF_INET, SOCK_STREAM, 0); - memset(buf, '\0', len); - gethostname(hn, sizeof(hn)); - host = gethostbyname(hn); - if( !host ) - return -1; - addr = (struct in_addr*)host->h_addr; - strncpy(buf, inet_ntoa(*addr), len); + for (i=1; i > 0; i++) + { + struct ifreq ifr; + struct sockaddr_in *addr = (struct sockaddr_in *) &ifr.ifr_addr; + + ifr.ifr_ifindex = i; + if( ioctl(s, SIOCGIFNAME, &ifr) < 0 ) + break; + if(ioctl(s, SIOCGIFADDR, &ifr, sizeof(struct ifreq)) < 0) + { + syslog(LOG_ERR, "ioctl(s, SIOCGIFADDR, ...): %m"); + close(s); + return -1; + } + if(strncmp(inet_ntoa(addr->sin_addr), "127.", 4) == 0) + continue; + if(!inet_ntop(AF_INET, &addr->sin_addr, buf, len)) + { + syslog(LOG_ERR, "inet_ntop(): %m"); + close(s); + return -1; + } + break; + } + close(s); return 0; } diff --git a/minidlna.c b/minidlna.c index ed34e64..a77d769 100644 --- a/minidlna.c +++ b/minidlna.c @@ -139,17 +139,6 @@ set_startup_time(int sysuptime) } } -/* structure containing variables used during "main loop" - * that are filled during the init */ -struct runtime_vars { - /* LAN IP addresses for SSDP traffic and HTTP */ - /* moved to global vars */ - /*int n_lan_addr;*/ - /*struct lan_addr_s lan_addr[MAX_LAN_ADDR];*/ - int port; /* HTTP Port */ - int notify_interval; /* seconds between SSDP announces */ -}; - /* parselanaddr() * parse address with mask * ex: 192.168.1.1/24 @@ -224,7 +213,7 @@ getfriendlyname(char * buf, int len) * 7) compute presentation URL * 8) set signal handlers */ static int -init(int argc, char * * argv, struct runtime_vars * v) +init(int argc, char * * argv) { int i; int pid; @@ -261,7 +250,6 @@ init(int argc, char * * argv, struct runtime_vars * v) getfriendlyname(friendly_name, FRIENDLYNAME_MAX_LEN); - /*v->n_lan_addr = 0;*/ char ext_ip_addr[INET_ADDRSTRLEN]; if( (getsysaddr(ext_ip_addr, INET_ADDRSTRLEN) < 0) && (getifaddr("eth0", ext_ip_addr, INET_ADDRSTRLEN) < 0) && @@ -272,8 +260,8 @@ init(int argc, char * * argv, struct runtime_vars * v) } if( parselanaddr(&lan_addr[n_lan_addr], ext_ip_addr) == 0 ) n_lan_addr++; - v->port = -1; - v->notify_interval = 30; /* seconds between SSDP announces */ + runtime_vars.port = -1; + runtime_vars.notify_interval = 30; /* seconds between SSDP announces */ /* read options file first since * command line arguments have final say */ @@ -290,12 +278,11 @@ init(int argc, char * * argv, struct runtime_vars * v) switch(ary_options[i].id) { case UPNPLISTENING_IP: - if(n_lan_addr < MAX_LAN_ADDR)/* if(v->n_lan_addr < MAX_LAN_ADDR)*/ + if(n_lan_addr < MAX_LAN_ADDR) { - /*if(parselanaddr(&v->lan_addr[v->n_lan_addr],*/ if(parselanaddr(&lan_addr[n_lan_addr], ary_options[i].value) == 0) - n_lan_addr++; /*v->n_lan_addr++; */ + n_lan_addr++; } else { @@ -304,13 +291,13 @@ init(int argc, char * * argv, struct runtime_vars * v) } break; case UPNPPORT: - v->port = atoi(ary_options[i].value); + runtime_vars.port = atoi(ary_options[i].value); break; case UPNPPRESENTATIONURL: presurl = ary_options[i].value; break; case UPNPNOTIFY_INTERVAL: - v->notify_interval = atoi(ary_options[i].value); + runtime_vars.notify_interval = atoi(ary_options[i].value); break; case UPNPSYSTEM_UPTIME: if(strcmp(ary_options[i].value, "yes") == 0) @@ -337,8 +324,54 @@ init(int argc, char * * argv, struct runtime_vars * v) friendly_name[FRIENDLYNAME_MAX_LEN-1] = '\0'; break; case UPNPMEDIADIR: - strncpy(media_dir, ary_options[i].value, MEDIADIR_MAX_LEN); - media_dir[MEDIADIR_MAX_LEN-1] = '\0'; + usleep(1); + enum media_types type = ALL_MEDIA; + char * myval = NULL; + switch( ary_options[i].value[0] ) + { + case 'A': + case 'a': + if( ary_options[i].value[0] == 'A' || ary_options[i].value[0] == 'a' ) + type = AUDIO_ONLY; + case 'V': + case 'v': + if( ary_options[i].value[0] == 'V' || ary_options[i].value[0] == 'v' ) + type = VIDEO_ONLY; + case 'P': + case 'p': + if( ary_options[i].value[0] == 'P' || ary_options[i].value[0] == 'p' ) + type = IMAGES_ONLY; + myval = index(ary_options[i].value, '/'); + case '/': + usleep(1); + char * path = realpath(myval ? myval:ary_options[i].value, NULL); + if( access(path, F_OK) != 0 ) + { + fprintf(stderr, "Media directory not accessible! [%s]\n", + path); + free(path); + break; + } + struct media_dir_s * this_dir = calloc(1, sizeof(struct media_dir_s)); + this_dir->path = path; + this_dir->type = type; + if( !media_dirs ) + { + media_dirs = this_dir; + } + else + { + struct media_dir_s * all_dirs = media_dirs; + while( all_dirs->next ) + all_dirs = all_dirs->next; + all_dirs->next = this_dir; + } + break; + default: + fprintf(stderr, "Media directory entry not understood! [%s]\n", + ary_options[i].value); + break; + } break; default: fprintf(stderr, "Unknown option in file %s\n", @@ -358,7 +391,7 @@ init(int argc, char * * argv, struct runtime_vars * v) { case 't': if(i+1 < argc) - v->notify_interval = atoi(argv[++i]); + runtime_vars.notify_interval = atoi(argv[++i]); else fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); break; @@ -392,7 +425,7 @@ init(int argc, char * * argv, struct runtime_vars * v) break; case 'p': if(i+1 < argc) - v->port = atoi(argv[++i]); + runtime_vars.port = atoi(argv[++i]); else fprintf(stderr, "Option -%c takes one argument.\n", argv[i][1]); break; @@ -417,22 +450,19 @@ init(int argc, char * * argv, struct runtime_vars * v) int address_already_there = 0; int j; i++; - for(j=0; jn_lan_addr; j++)*/ + for(j=0; jlan_addr[j].str, tmpaddr.str))*/ if(0 == strcmp(lan_addr[j].str, tmpaddr.str)) address_already_there = 1; } if(address_already_there) break; - if(n_lan_addr < MAX_LAN_ADDR) /*if(v->n_lan_addr < MAX_LAN_ADDR)*/ + if(n_lan_addr < MAX_LAN_ADDR) { - /*v->lan_addr[v->n_lan_addr++] = argv[i];*/ - /*if(parselanaddr(&v->lan_addr[v->n_lan_addr], argv[i]) == 0)*/ if(parselanaddr(&lan_addr[n_lan_addr], argv[i]) == 0) - n_lan_addr++; /*v->n_lan_addr++;*/ + n_lan_addr++; } else { @@ -450,7 +480,7 @@ init(int argc, char * * argv, struct runtime_vars * v) fprintf(stderr, "Unknown option: %s\n", argv[i]); } } - if( (/*v->*/n_lan_addr==0) || (v->port<=0) ) + if( (n_lan_addr==0) || (runtime_vars.port<=0) ) { fprintf(stderr, "Usage:\n\t" "%s [-f config_file] [-i ext_ifname] [-o ext_ip]\n" @@ -521,8 +551,6 @@ init(int argc, char * * argv, struct runtime_vars * v) { snprintf(presentationurl, PRESENTATIONURL_MAX_LEN, "http://%s/", lan_addr[0].str); - /*"http://%s:%d/", lan_addr[0].str, 80);*/ - /*"http://%s:%d/", v->lan_addr[0].str, 80);*/ } /* set signal handler */ @@ -566,19 +594,36 @@ main(int argc, char * * argv) #endif struct timeval timeout, timeofday, lasttimeofday = {0, 0}; int max_fd = -1; - struct runtime_vars v; - if(init(argc, argv, &v) != 0) + if(init(argc, argv) != 0) return 1; LIST_INIT(&upnphttphead); if( access(DB_PATH, F_OK) ) { + struct media_dir_s * media_path = media_dirs; sqlite3_open(DB_PATH, &db); freopen("/dev/null", "a", stderr); - ScanDirectory(media_dir, NULL); + if( CreateDatabase() != 0 ) + { + fprintf(stderr, "Error creating database!\n"); + return -1; + } + #if USE_FORK + pid_t newpid = fork(); + if( newpid ) + goto fork_done; + #endif + while( media_path ) + { + ScanDirectory(media_path->path, NULL, media_path->type); + media_path = media_path->next; + } freopen("/proc/self/fd/2", "a", stderr); + #if USE_FORK + _exit(0); + #endif } else { @@ -588,21 +633,40 @@ main(int argc, char * * argv) if( sqlite3_get_table(db, "pragma user_version", &result, &rows, 0, 0) == SQLITE_OK ) { if( atoi(result[1]) != DB_VERSION ) { + struct media_dir_s * media_path = media_dirs; printf("Database version mismatch; need to recreate...\n"); sqlite3_close(db); unlink(DB_PATH); sqlite3_open(DB_PATH, &db); freopen("/dev/null", "a", stderr); - ScanDirectory(media_dir, NULL); + if( CreateDatabase() != 0 ) + { + fprintf(stderr, "Error creating database!\n"); + return -1; + } + #if USE_FORK + pid_t newpid = fork(); + if( newpid ) + goto fork_done; + #endif + while( media_path ) + { + ScanDirectory(media_path->path, NULL, media_path->type); + media_path = media_path->next; + } + ScanDirectory(media_dirs->path, NULL, media_dirs->type); freopen("/proc/self/fd/2", "a", stderr); + #if USE_FORK + _exit(0); + #endif } sqlite3_free_table(result); } } + #if USE_FORK + fork_done: + #endif - - /* open socket for SSDP connections */ - /*sudp = OpenAndConfSSDPReceiveSocket(v.n_lan_addr, v.lan_addr);*/ sudp = OpenAndConfSSDPReceiveSocket(n_lan_addr, lan_addr); if(sudp < 0) { @@ -610,13 +674,13 @@ main(int argc, char * * argv) return 1; } /* open socket for HTTP connections. Listen on the 1st LAN address */ - shttpl = OpenAndConfHTTPSocket(v.port); + shttpl = OpenAndConfHTTPSocket(runtime_vars.port); if(shttpl < 0) { syslog(LOG_ERR, "Failed to open socket for HTTP. EXITING"); return 1; } - syslog(LOG_NOTICE, "HTTP listening on port %d", v.port); + syslog(LOG_NOTICE, "HTTP listening on port %d", runtime_vars.port); /* open socket for sending notifications */ if(OpenAndConfSSDPNotifySockets(snotify) < 0) @@ -636,24 +700,24 @@ main(int argc, char * * argv) if(gettimeofday(&timeofday, 0) < 0) { syslog(LOG_ERR, "gettimeofday(): %m"); - timeout.tv_sec = v.notify_interval; + timeout.tv_sec = runtime_vars.notify_interval; timeout.tv_usec = 0; } else { /* the comparaison is not very precise but who cares ? */ - if(timeofday.tv_sec >= (lasttimeofday.tv_sec + v.notify_interval)) + if(timeofday.tv_sec >= (lasttimeofday.tv_sec + runtime_vars.notify_interval)) { SendSSDPNotifies2(snotify, - (unsigned short)v.port, - (v.notify_interval << 1)+10); + (unsigned short)runtime_vars.port, + (runtime_vars.notify_interval << 1)+10); memcpy(&lasttimeofday, &timeofday, sizeof(struct timeval)); - timeout.tv_sec = v.notify_interval; + timeout.tv_sec = runtime_vars.notify_interval; timeout.tv_usec = 0; } else { - timeout.tv_sec = lasttimeofday.tv_sec + v.notify_interval + timeout.tv_sec = lasttimeofday.tv_sec + runtime_vars.notify_interval - timeofday.tv_sec; if(timeofday.tv_usec > lasttimeofday.tv_usec) { @@ -724,8 +788,7 @@ main(int argc, char * * argv) if(sudp >= 0 && FD_ISSET(sudp, &readset)) { /*syslog(LOG_INFO, "Received UDP Packet");*/ - /*ProcessSSDPRequest(sudp, v.lan_addr, v.n_lan_addr,*/ - ProcessSSDPRequest(sudp, (unsigned short)v.port); + ProcessSSDPRequest(sudp, (unsigned short)runtime_vars.port); } /* process active HTTP connections */ /* LIST_FOREACH macro is not available under linux */ @@ -802,7 +865,7 @@ shutdown: { syslog(LOG_ERR, "Failed to broadcast good-bye notifications"); } - for(i=0; id_name); if( index(namelist[i]->d_name, '&') ) { @@ -612,16 +646,19 @@ ScanDirectory(const char * dir, const char * parent) } if( namelist[i]->d_type == DT_DIR ) { - insert_directory(name?name:namelist[i]->d_name, full_path, BROWSEDIR_ID, (parent ? parent:""), i); - sprintf(parent_id, "%s$%X", (parent ? parent:""), i); - ScanDirectory(full_path, parent_id); + insert_directory(name?name:namelist[i]->d_name, full_path, BROWSEDIR_ID, (parent ? parent:""), i+startID); + sprintf(parent_id, "%s$%X", (parent ? parent:""), i+startID); + ScanDirectory(full_path, parent_id, type); } else { - insert_file(name?name:namelist[i]->d_name, full_path, (parent ? parent:""), i); + insert_file(name?name:namelist[i]->d_name, full_path, (parent ? parent:""), i+startID); } if( name ) + { free(name); + name = NULL; + } free(namelist[i]); } free(namelist); @@ -632,8 +669,5 @@ ScanDirectory(const char * dir, const char * parent) else { printf("Scanning \"%s\" finished!\n", dir); -#if USE_FORK - _exit(0); -#endif } } diff --git a/scanner.h b/scanner.h index 0e4747c..77f3dcb 100644 --- a/scanner.h +++ b/scanner.h @@ -15,7 +15,10 @@ #define VIDEO_DIR_ID "2$21" #define IMAGE_DIR_ID "3$22" +int +CreateDatabase(void); + void -ScanDirectory(const char * dir, const char * parent); +ScanDirectory(const char * dir, const char * parent, enum media_types type); #endif diff --git a/upnpglobalvars.c b/upnpglobalvars.c index f6adbea..32053c1 100644 --- a/upnpglobalvars.c +++ b/upnpglobalvars.c @@ -26,6 +26,7 @@ int logpackets = 0; #endif +struct runtime_vars_s runtime_vars; int runtime_flags = 0; const char * pidfilename = "/var/run/minidlna.pid"; @@ -44,5 +45,5 @@ struct lan_addr_s lan_addr[MAX_LAN_ADDR]; /* UPnP-A/V [DLNA] */ sqlite3 *db; -char media_dir[MEDIADIR_MAX_LEN]; +struct media_dir_s * media_dirs = NULL; char friendly_name[FRIENDLYNAME_MAX_LEN]; diff --git a/upnpglobalvars.h b/upnpglobalvars.h index 6fe18f0..c41dc4d 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -47,6 +47,7 @@ /* statup time */ extern time_t startup_time; +extern struct runtime_vars_s runtime_vars; /* runtime boolean flags */ extern int runtime_flags; #define LOGPACKETSMASK 0x0001 @@ -81,7 +82,7 @@ extern struct lan_addr_s lan_addr[]; /* UPnP-A/V [DLNA] */ extern sqlite3 *db; #define MEDIADIR_MAX_LEN (256) -extern char media_dir[]; +extern struct media_dir_s * media_dirs; #define FRIENDLYNAME_MAX_LEN (64) extern char friendly_name[]; diff --git a/upnphttp.c b/upnphttp.c index ea76643..58b9dd8 100644 --- a/upnphttp.c +++ b/upnphttp.c @@ -1178,6 +1178,9 @@ SendResp_dlnafile(struct upnphttp * h, char * object) int sendfh; #if USE_FORK pid_t newpid = 0; + newpid = fork(); + if( newpid ) + return; #endif memset(header, 0, 1500); @@ -1192,15 +1195,10 @@ SendResp_dlnafile(struct upnphttp * h, char * object) sqlite3_free_table(result); return; } -#if USE_FORK - newpid = fork(); - if( newpid ) - return; -#endif - path = result[3]; mime = result[4]; dlna = result[5]; + printf("Serving DetailID: %s [%s]\n", object, path); if( h->reqflags & FLAG_XFERSTREAMING ) diff --git a/upnpsoap.c b/upnpsoap.c index ec17cdc..3ec9556 100644 --- a/upnpsoap.c +++ b/upnpsoap.c @@ -270,10 +270,10 @@ static int callback(void *args, int argc, char **argv, char **azColName) } if( album_art && atoi(album_art) && (!passed_args->filter || strstr(passed_args->filter, "upnp:albumArtURI")) ) { sprintf(str_buf, "<upnp:albumArtURI %s" - ">http://%s:5555/AlbumArt/%s.jpg</upnp:albumArtURI>", + ">http://%s:%d/AlbumArt/%s.jpg</upnp:albumArtURI>", (!passed_args->filter || strstr(passed_args->filter, "upnp:albumArtURI@dlna:profileID")) ? "dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlnaorg:metadata-1-0/\"" : "", - lan_addr[0].str, album_art); + lan_addr[0].str, runtime_vars.port, album_art); strcat(passed_args->resp, str_buf); } if( !passed_args->filter || strstr(passed_args->filter, "res") ) { @@ -303,26 +303,26 @@ static int callback(void *args, int argc, char **argv, char **azColName) strcat(passed_args->resp, str_buf); } sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">" - "http://%s:5555/MediaItems/%s.dat" + "http://%s:%d/MediaItems/%s.dat" "</res>", - mime, dlna_buf, lan_addr[0].str, detailID); + mime, dlna_buf, lan_addr[0].str, runtime_vars.port, detailID); #if 0 //JPEG_RESIZE if( dlna_pn && (strncmp(dlna_pn, "JPEG_LRG", 8) == 0) ) { strcat(passed_args->resp, str_buf); sprintf(str_buf, "<res " "protocolInfo=\"http-get:*:%s:%s\">" - "http://%s:5555/Resized/%s" + "http://%s:%d/Resized/%s" "</res>", - mime, "DLNA.ORG_PN=JPEG_SM", lan_addr[0].str, id); + mime, "DLNA.ORG_PN=JPEG_SM", lan_addr[0].str, runtime_vars.port, id); } #endif if( tn && atoi(tn) && dlna_pn ) { strcat(passed_args->resp, str_buf); strcat(passed_args->resp, "<res "); sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">" - "http://%s:5555/Thumbnails/%s.dat" + "http://%s:%d/Thumbnails/%s.dat" "</res>", - mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, detailID); + mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, runtime_vars.port, detailID); } strcat(passed_args->resp, str_buf); } @@ -369,7 +369,7 @@ static int callback(void *args, int argc, char **argv, char **azColName) } if( album_art && atoi(album_art) && (!passed_args->filter || strstr(passed_args->filter, "upnp:albumArtURI")) ) { sprintf(str_buf, "<upnp:albumArtURI dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlnaorg:metadata-1-0/\"" - ">http://%s:5555/AlbumArt/%s.jpg</upnp:albumArtURI>", lan_addr[0].str, album_art); + ">http://%s:%d/AlbumArt/%s.jpg</upnp:albumArtURI>", lan_addr[0].str, runtime_vars.port, album_art); strcat(passed_args->resp, str_buf); } sprintf(str_buf, "</container>");