* Fix race condition with file-serving SQL queries and scanner/inotify SQL queries.

This commit is contained in:
Justin Maggard 2009-03-22 22:21:44 +00:00
parent c4d180f80d
commit 575556ca9d
3 changed files with 107 additions and 73 deletions

View File

@ -616,14 +616,13 @@ main(int argc, char * * argv)
} }
if( sqlite3_open(DB_PATH "/files.db", &db) != SQLITE_OK ) if( sqlite3_open(DB_PATH "/files.db", &db) != SQLITE_OK )
{ {
fprintf(stderr, "ERROR: Failed to open sqlite database! Exiting...\n"); DPRINTF(E_FATAL, L_GENERAL, "ERROR: Failed to open sqlite database! Exiting...\n");
exit(-1);
} }
else else
{ {
char **result; char **result;
int rows; int rows;
sqlite3_busy_timeout(db, 2500); sqlite3_busy_timeout(db, 5000);
if( sql_get_table(db, "pragma user_version", &result, &rows, 0) == SQLITE_OK ) if( sql_get_table(db, "pragma user_version", &result, &rows, 0) == SQLITE_OK )
{ {
if( atoi(result[1]) != DB_VERSION ) { if( atoi(result[1]) != DB_VERSION ) {
@ -632,23 +631,21 @@ main(int argc, char * * argv)
unlink(DB_PATH "/files.db"); unlink(DB_PATH "/files.db");
system("rm -rf " DB_PATH "/art_cache"); system("rm -rf " DB_PATH "/art_cache");
sqlite3_open(DB_PATH "/files.db", &db); sqlite3_open(DB_PATH "/files.db", &db);
sqlite3_busy_timeout(db, 5000);
if( CreateDatabase() != 0 ) if( CreateDatabase() != 0 )
{ {
fprintf(stderr, "Error creating database!\n"); DPRINTF(E_FATAL, L_GENERAL, "ERROR: Failed to create sqlite database! Exiting...\n");
return -1;
} }
if( pthread_create(&thread[0], NULL, start_scanner, NULL) ) if( pthread_create(&thread[0], NULL, start_scanner, NULL) )
{ {
printf("ERROR: pthread_create() failed\n"); DPRINTF(E_FATAL, L_GENERAL, "ERROR: pthread_create() failed for start_scanner.\n");
exit(-1);
} }
} }
sqlite3_free_table(result); sqlite3_free_table(result);
} }
if( GETFLAG(INOTIFYMASK) && pthread_create(&thread[1], NULL, start_inotify, NULL) ) if( GETFLAG(INOTIFYMASK) && pthread_create(&thread[1], NULL, start_inotify, NULL) )
{ {
printf("ERROR: pthread_create() failed\n"); DPRINTF(E_FATAL, L_GENERAL, "ERROR: pthread_create() failed for start_inotify.\n");
exit(-1);
} }
} }

9
sql.c
View File

@ -17,18 +17,19 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include "sql.h" #include "sql.h"
#include "log.h"
int int
sql_exec(sqlite3 * db, const char * sql) sql_exec(sqlite3 * db, const char * sql)
{ {
int ret; int ret;
char *errMsg = NULL; char *errMsg = NULL;
//DEBUG printf("SQL: %s\n", sql); //DPRINTF(E_DEBUG, L_DB_SQL, "SQL: %s\n", sql);
ret = sqlite3_exec(db, sql, 0, 0, &errMsg); ret = sqlite3_exec(db, sql, 0, 0, &errMsg);
if( ret != SQLITE_OK ) if( ret != SQLITE_OK )
{ {
fprintf(stderr, "SQL ERROR %d [%s]\n%s\n", ret, errMsg, sql); DPRINTF(E_ERROR, L_DB_SQL, "SQL ERROR %d [%s]\n%s\n", ret, errMsg, sql);
if (errMsg) if (errMsg)
sqlite3_free(errMsg); sqlite3_free(errMsg);
} }
@ -40,12 +41,12 @@ sql_get_table(sqlite3 *db, const char *sql, char ***pazResult, int *pnRow, int *
{ {
int ret; int ret;
char *errMsg = NULL; char *errMsg = NULL;
//DEBUG printf("SQL: %s\n", sql); //DPRINTF(E_DEBUG, L_DB_SQL, "SQL: %s\n", sql);
ret = sqlite3_get_table(db, sql, pazResult, pnRow, pnColumn, &errMsg); ret = sqlite3_get_table(db, sql, pazResult, pnRow, pnColumn, &errMsg);
if( ret != SQLITE_OK ) if( ret != SQLITE_OK )
{ {
fprintf(stderr, "SQL ERROR %d [%s]\n%s\n", ret, errMsg, sql); DPRINTF(E_ERROR, L_DB_SQL, "SQL ERROR %d [%s]\n%s\n", ret, errMsg, sql);
if (errMsg) if (errMsg)
sqlite3_free(errMsg); sqlite3_free(errMsg);
} }

View File

@ -320,6 +320,21 @@ Send416(struct upnphttp * h)
CloseSocket_upnphttp(h); CloseSocket_upnphttp(h);
} }
/* very minimalistic 500 error message */
static void
Send500(struct upnphttp * h)
{
static const char body500[] =
"<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>"
"<BODY><H1>Internal Server Error</H1>Server encountered "
"and Internal Error.</BODY></HTML>\r\n";
h->respflags = FLAG_HTML;
BuildResp2_upnphttp(h, 500, "Internal Server Errror",
body500, sizeof(body500) - 1);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
/* very minimalistic 501 error message */ /* very minimalistic 501 error message */
static void static void
Send501(struct upnphttp * h) Send501(struct upnphttp * h)
@ -356,12 +371,10 @@ sendXMLdesc(struct upnphttp * h, char * (f)(int *))
desc = f(&len); desc = f(&len);
if(!desc) if(!desc)
{ {
static const char error500[] = "<HTML><HEAD><TITLE>Error 500</TITLE>"
"</HEAD><BODY>Internal Server Error</BODY></HTML>\r\n";
DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n"); DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n");
h->respflags = FLAG_HTML; Send500(h);
BuildResp2_upnphttp(h, 500, "Internal Server Error", free(desc);
error500, sizeof(error500)-1); return;
} }
else else
{ {
@ -956,7 +969,7 @@ SendResp_albumArt(struct upnphttp * h, char * object)
char header[1500]; char header[1500];
char sql_buf[256]; char sql_buf[256];
char **result; char **result;
int rows; int rows = 0;
char *path; char *path;
char date[30]; char date[30];
time_t curtime = time(NULL); time_t curtime = time(NULL);
@ -974,7 +987,7 @@ SendResp_albumArt(struct upnphttp * h, char * object)
strip_ext(object); strip_ext(object);
sprintf(sql_buf, "SELECT PATH from ALBUM_ART where ID = %s", object); sprintf(sql_buf, "SELECT PATH from ALBUM_ART where ID = %s", object);
sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); sql_get_table(db, sql_buf, &result, &rows, NULL);
if( !rows ) if( !rows )
{ {
DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object); DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object);
@ -1032,7 +1045,7 @@ SendResp_thumbnail(struct upnphttp * h, char * object)
char header[1500]; char header[1500];
char sql_buf[256]; char sql_buf[256];
char **result; char **result;
int rows; int rows = 0;
char *path; char *path;
char date[30]; char date[30];
time_t curtime = time(NULL); time_t curtime = time(NULL);
@ -1050,7 +1063,7 @@ SendResp_thumbnail(struct upnphttp * h, char * object)
strip_ext(object); strip_ext(object);
sprintf(sql_buf, "SELECT PATH from DETAILS where ID = '%s'", object); sprintf(sql_buf, "SELECT PATH from DETAILS where ID = '%s'", object);
sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0); sql_get_table(db, sql_buf, &result, &rows, NULL);
if( !rows ) if( !rows )
{ {
DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
@ -1120,18 +1133,38 @@ SendResp_resizedimg(struct upnphttp * h, char * object)
char *key, *val; char *key, *val;
char *saveptr=NULL, *item=NULL; char *saveptr=NULL, *item=NULL;
char *pixelshape=NULL; char *pixelshape=NULL;
sqlite_int64 id;
int rows=0, ret; int rows=0, ret;
ExifData *ed; ExifData *ed;
ExifLoader *l; ExifLoader *l;
image * imsrc; image * imsrc;
image * imdst; image * imdst;
id = strtoll(object, NULL, 10);
sprintf(sql_buf, "SELECT PATH, RESOLUTION, THUMBNAIL from DETAILS where ID = '%lld'", id);
ret = sql_get_table(db, sql_buf, &result, &rows, NULL);
if( (ret != SQLITE_OK) )
{
DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id);
Send500(h);
return;
}
if( !rows || (access(result[3], F_OK) != 0) )
{
DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
sqlite3_free_table(result);
Send404(h);
return;
}
#if USE_FORK #if USE_FORK
pid_t newpid = 0; pid_t newpid = 0;
newpid = fork(); newpid = fork();
if( newpid ) if( newpid )
return; goto resized_error;
#endif #endif
memset(header, 0, 1500); file_path = result[3];
resolution = result[4];
tn = result[5];
path = strdup(object); path = strdup(object);
if( strtok_r(path, "?", &saveptr) ) if( strtok_r(path, "?", &saveptr) )
@ -1164,7 +1197,7 @@ SendResp_resizedimg(struct upnphttp * h, char * object)
} }
item = strtok_r(NULL, "&", &saveptr); item = strtok_r(NULL, "&", &saveptr);
} }
strip_ext(path); free(path);
if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE )
{ {
@ -1173,19 +1206,7 @@ SendResp_resizedimg(struct upnphttp * h, char * object)
goto resized_error; goto resized_error;
} }
sprintf(sql_buf, "SELECT PATH, RESOLUTION, THUMBNAIL from DETAILS where ID = '%s'", path); DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %lld [%s]\n", id, file_path);
ret = sql_get_table(db, sql_buf, &result, &rows, NULL);
if( (ret != SQLITE_OK) || !rows || (access(result[3], F_OK) != 0) )
{
DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %s!\n", path);
free(path);
goto resized_error;
}
file_path = result[3];
resolution = result[4];
tn = result[5];
DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %s [%s]\n", path, file_path);
free(path);
/* Resizing from a thumbnail is much faster than from a large image */ /* Resizing from a thumbnail is much faster than from a large image */
#ifdef __sparc__ #ifdef __sparc__
@ -1204,7 +1225,6 @@ SendResp_resizedimg(struct upnphttp * h, char * object)
if( ed ) if( ed )
exif_data_unref(ed); exif_data_unref(ed);
Send404(h); Send404(h);
sqlite3_free_table(result);
goto resized_error; goto resized_error;
} }
imsrc = image_new_from_jpeg(NULL, 0, (char *)ed->data, ed->size); imsrc = image_new_from_jpeg(NULL, 0, (char *)ed->data, ed->size);
@ -1217,7 +1237,6 @@ SendResp_resizedimg(struct upnphttp * h, char * object)
if( !imsrc ) if( !imsrc )
{ {
Send404(h); Send404(h);
sqlite3_free_table(result);
goto resized_error; goto resized_error;
} }
/* Figure out the best destination resolution we can use */ /* Figure out the best destination resolution we can use */
@ -1260,10 +1279,11 @@ SendResp_resizedimg(struct upnphttp * h, char * object)
DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path); DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path);
image_free(imsrc); image_free(imsrc);
image_free(imdst); image_free(imdst);
sqlite3_free_table(result);
resized_error: resized_error:
sqlite3_free_table(result);
#if USE_FORK #if USE_FORK
_exit(0); if( !newpid )
_exit(0);
#endif #endif
} }
@ -1274,40 +1294,57 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
char hdr_buf[512]; char hdr_buf[512];
char sql_buf[256]; char sql_buf[256];
char **result; char **result;
int rows; int rows, ret;
char date[30]; char date[30];
time_t curtime = time(NULL); time_t curtime = time(NULL);
off_t total, offset, size; off_t total, offset, size;
char *path, *mime, *dlna; sqlite_int64 id;
int sendfh; int sendfh;
static struct { sqlite_int64 id; char path[PATH_MAX]; char mime[32]; char dlna[64]; } last_file = { 0 };
id = strtoll(object, NULL, 10);
if( id != last_file.id )
{
sprintf(sql_buf, "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", id);
ret = sql_get_table(db, sql_buf, &result, &rows, NULL);
if( (ret != SQLITE_OK) )
{
DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id);
Send500(h);
return;
}
if( !rows )
{
DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
Send404(h);
goto error;
}
/* Cache the result */
last_file.id = id;
strncpy(last_file.path, result[3], sizeof(last_file.path)-1);
if( result[4] )
strncpy(last_file.mime, result[4], sizeof(last_file.mime)-1);
else
last_file.mime[0] = '\0';
if( result[5] )
snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s", result[5]);
else
last_file.dlna[0] = '\0';
sqlite3_free_table(result);
}
#if USE_FORK #if USE_FORK
pid_t newpid = 0; pid_t newpid = 0;
newpid = fork(); newpid = fork();
if( newpid ) if( newpid )
return; return;
//goto error;
#endif #endif
memset(header, 0, 1500); DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %lld [%s]\n", id, last_file.path);
strip_ext(object);
sprintf(sql_buf, "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%s'", object);
sqlite3_get_table(db, sql_buf, &result, &rows, 0, 0);
if( !rows )
{
DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
Send404(h);
sqlite3_free_table(result);
goto done_dlna;
}
path = result[3];
mime = result[4];
dlna = result[5];
DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %s [%s]\n", object, path);
if( h->reqflags & FLAG_XFERSTREAMING ) if( h->reqflags & FLAG_XFERSTREAMING )
{ {
if( strncmp(mime, "image", 5) == 0 ) if( strncmp(last_file.mime, "image", 5) == 0 )
{ {
DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
Send406(h); Send406(h);
@ -1322,7 +1359,7 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
Send400(h); Send400(h);
goto error; goto error;
} }
if( strncmp(mime, "image", 5) != 0 ) if( strncmp(last_file.mime, "image", 5) != 0 )
{ {
DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n"); DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n");
Send406(h); Send406(h);
@ -1332,16 +1369,16 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
offset = h->req_RangeStart; offset = h->req_RangeStart;
sendfh = open(path, O_RDONLY); sendfh = open(last_file.path, O_RDONLY);
if( sendfh < 0 ) { if( sendfh < 0 ) {
DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", last_file.path);
goto error; goto error;
} }
size = lseek(sendfh, 0, SEEK_END); size = lseek(sendfh, 0, SEEK_END);
lseek(sendfh, 0, SEEK_SET); lseek(sendfh, 0, SEEK_SET);
sprintf(header, "HTTP/1.1 20%c OK\r\n" sprintf(header, "HTTP/1.1 20%c OK\r\n"
"Content-Type: %s\r\n", (h->reqflags & FLAG_RANGE ? '6' : '0'), mime); "Content-Type: %s\r\n", (h->reqflags & FLAG_RANGE ? '6' : '0'), last_file.mime);
if( h->reqflags & FLAG_RANGE ) if( h->reqflags & FLAG_RANGE )
{ {
if( !h->req_RangeEnd ) if( !h->req_RangeEnd )
@ -1391,13 +1428,13 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
} }
else if( h->reqflags & FLAG_XFERBACKGROUND ) else if( h->reqflags & FLAG_XFERBACKGROUND )
{ {
if( strncmp(mime, "image", 5) == 0 ) if( strncmp(last_file.mime, "image", 5) == 0 )
strcat(header, "transferMode.dlna.org: Background\r\n"); strcat(header, "transferMode.dlna.org: Background\r\n");
} }
else //if( h->reqflags & FLAG_XFERINTERACTIVE ) else //if( h->reqflags & FLAG_XFERINTERACTIVE )
{ {
if( (strncmp(mime, "video", 5) == 0) || if( (strncmp(last_file.mime, "video", 5) == 0) ||
(strncmp(mime, "audio", 5) == 0) ) (strncmp(last_file.mime, "audio", 5) == 0) )
{ {
strcat(header, "transferMode.dlna.org: Streaming\r\n"); strcat(header, "transferMode.dlna.org: Streaming\r\n");
} }
@ -1413,7 +1450,7 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
"EXT:\r\n" "EXT:\r\n"
"contentFeatures.dlna.org: DLNA.ORG_PN=%s\r\n" "contentFeatures.dlna.org: DLNA.ORG_PN=%s\r\n"
"Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA/1.0\r\n\r\n", "Server: RAIDiator/4.1, UPnP/1.0, MiniDLNA/1.0\r\n\r\n",
date, dlna); date, last_file.dlna);
strcat(header, hdr_buf); strcat(header, hdr_buf);
if( (send_data(h, header, strlen(header)) == 0) && (h->req_command != EHead) && (sendfh > 0) ) if( (send_data(h, header, strlen(header)) == 0) && (h->req_command != EHead) && (sendfh > 0) )
@ -1423,9 +1460,8 @@ SendResp_dlnafile(struct upnphttp * h, char * object)
close(sendfh); close(sendfh);
error: error:
sqlite3_free_table(result);
done_dlna:
#if USE_FORK #if USE_FORK
_exit(0); if( !newpid )
_exit(0);
#endif #endif
} }