2012-01-12 03:38:24 +00:00
/*
2012-05-19 04:39:50 +00:00
Serval Rhizome file sharing
Copyright ( C ) 2012 The Serval Project
2012-01-12 03:38:24 +00:00
This program is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation ; either version 2
of the License , or ( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*/
2012-02-23 02:15:42 +00:00
# include "serval.h"
2012-01-12 03:38:24 +00:00
# include "rhizome.h"
2012-05-20 03:32:41 +00:00
# include "strbuf.h"
2012-01-12 03:38:24 +00:00
# include <stdlib.h>
long long rhizome_space = 0 ;
2012-05-15 03:26:10 +00:00
static const char * rhizome_thisdatastore_path = NULL ;
static int rhizome_enabled_flag = - 1 ; // unknown
2012-05-28 02:29:35 +00:00
# define SQLITE_CODE_OK(code) (code==SQLITE_OK || code==SQLITE_DONE)
2012-05-15 03:26:10 +00:00
int rhizome_enabled ( )
{
if ( rhizome_enabled_flag < 0 )
rhizome_enabled_flag = confValueGetBoolean ( " rhizome.enable " , 1 ) ;
return rhizome_enabled_flag ;
}
const char * rhizome_datastore_path ( )
{
if ( ! rhizome_thisdatastore_path )
rhizome_set_datastore_path ( NULL ) ;
return rhizome_thisdatastore_path ;
}
int rhizome_set_datastore_path ( const char * path )
{
2012-05-18 09:23:27 +00:00
if ( ! path )
path = confValueGet ( " rhizome.datastore_path " , NULL ) ;
2012-05-20 03:32:41 +00:00
if ( path ) {
2012-05-18 09:23:27 +00:00
rhizome_thisdatastore_path = strdup ( path ) ;
2012-05-20 03:32:41 +00:00
if ( path [ 0 ] ! = ' / ' )
WARNF ( " Dangerous rhizome.datastore_path setting: '%s' -- should be absolute " , rhizome_thisdatastore_path ) ;
} else {
2012-05-15 03:26:10 +00:00
rhizome_thisdatastore_path = serval_instancepath ( ) ;
WARNF ( " Rhizome datastore path not configured -- using instance path '%s' " , rhizome_thisdatastore_path ) ;
}
return 0 ;
}
2012-01-12 03:38:24 +00:00
2012-05-14 06:02:28 +00:00
int form_rhizome_datastore_path ( char * buf , size_t bufsiz , const char * fmt , . . . )
{
2012-05-23 08:41:34 +00:00
va_list ap ;
2012-05-20 03:32:41 +00:00
strbuf b = strbuf_local ( buf , bufsiz ) ;
2012-05-24 01:58:32 +00:00
strbuf_puts ( b , rhizome_datastore_path ( ) ) ;
if ( fmt ) {
va_start ( ap , fmt ) ;
if ( * strbuf_substr ( b , - 1 ) ! = ' / ' )
strbuf_putc ( b , ' / ' ) ;
strbuf_vsprintf ( b , fmt , ap ) ;
va_end ( ap ) ;
}
if ( strbuf_overrun ( b ) ) {
WHY ( " Path buffer overrun " ) ;
return 0 ;
}
return 1 ;
}
int form_rhizome_import_path ( char * buf , size_t bufsiz , const char * fmt , . . . )
{
va_list ap ;
strbuf b = strbuf_local ( buf , bufsiz ) ;
strbuf_sprintf ( b , " %s/import " , rhizome_datastore_path ( ) ) ;
if ( fmt ) {
va_start ( ap , fmt ) ;
strbuf_putc ( b , ' / ' ) ;
strbuf_vsprintf ( b , fmt , ap ) ;
va_end ( ap ) ;
}
2012-05-20 03:32:41 +00:00
if ( strbuf_overrun ( b ) ) {
2012-05-14 06:02:28 +00:00
WHY ( " Path buffer overrun " ) ;
return 0 ;
}
return 1 ;
}
int create_rhizome_datastore_dir ( )
{
2012-05-18 09:23:27 +00:00
if ( debug & DEBUG_RHIZOME ) DEBUGF ( " mkdirs(%s, 0700) " , rhizome_datastore_path ( ) ) ;
2012-05-15 03:26:10 +00:00
return mkdirs ( rhizome_datastore_path ( ) , 0700 ) ;
2012-05-14 06:02:28 +00:00
}
2012-05-24 01:58:32 +00:00
int create_rhizome_import_dir ( )
{
char dirname [ 1024 ] ;
if ( ! form_rhizome_import_path ( dirname , sizeof dirname , NULL ) )
return - 1 ;
if ( debug & DEBUG_RHIZOME ) DEBUGF ( " mkdirs(%s, 0700) " , dirname ) ;
return mkdirs ( dirname , 0700 ) ;
}
2012-01-12 03:38:24 +00:00
sqlite3 * rhizome_db = NULL ;
/* XXX Requires a messy join that might be slow. */
int rhizome_manifest_priority ( char * id )
{
2012-06-08 03:43:26 +00:00
long long result = 0 ;
if ( sqlite_exec_int64 ( & result ,
" select max(grouplist.priorty) from grouplist,manifests,groupmemberships "
" where manifests.id='%s' "
" and grouplist.id=groupmemberships.groupid "
" and groupmemberships.manifestid=manifests.id; " ,
id
) = = - 1
)
return - 1 ;
return ( int ) result ;
2012-01-12 03:38:24 +00:00
}
int rhizome_opendb ( )
{
if ( rhizome_db ) return 0 ;
2012-05-18 09:23:27 +00:00
if ( create_rhizome_datastore_dir ( ) = = - 1 )
2012-05-28 02:29:35 +00:00
return WHY ( " No Directory " ) ;
2012-05-14 06:02:28 +00:00
char dbname [ 1024 ] ;
if ( ! FORM_RHIZOME_DATASTORE_PATH ( dbname , " rhizome.db " ) )
2012-05-28 02:29:35 +00:00
return WHY ( " Invalid path " ) ;
2012-01-12 03:38:24 +00:00
2012-05-18 09:23:27 +00:00
if ( sqlite3_open ( dbname , & rhizome_db ) )
return WHYF ( " SQLite could not open database: %s " , sqlite3_errmsg ( rhizome_db ) ) ;
2012-01-12 03:38:24 +00:00
2012-03-04 23:05:12 +00:00
/* Read Rhizome configuration */
2012-05-18 09:23:27 +00:00
double rhizome_kb = atof ( confValueGet ( " rhizome_kb " , " 1024 " ) ) ;
rhizome_space = 1024LL * rhizome_kb ;
if ( debug & DEBUG_RHIZOME ) {
DEBUGF ( " serval.conf:rhizome_kb=%.f " , rhizome_kb ) ;
DEBUGF ( " Rhizome will use %lldB of storage for its database. " , rhizome_space ) ;
}
2012-01-12 03:38:24 +00:00
/* Create tables if required */
2012-05-28 09:54:02 +00:00
if ( sqlite3_exec ( rhizome_db ,
" PRAGMA auto_vacuum=2; "
" CREATE TABLE IF NOT EXISTS GROUPLIST(id text not null primary key, closed integer,ciphered integer,priority integer); "
" CREATE TABLE IF NOT EXISTS MANIFESTS(id text not null primary key, manifest blob, version integer,inserttime integer, bar blob); "
" CREATE TABLE IF NOT EXISTS FILES(id text not null primary key, data blob, length integer, highestpriority integer, datavalid integer); "
" DROP TABLE IF EXISTS FILEMANIFESTS; "
" CREATE TABLE IF NOT EXISTS GROUPMEMBERSHIPS(manifestid text not null, groupid text not null); "
" CREATE TABLE IF NOT EXISTS VERIFICATIONS(sid text not null, did text, name text, starttime integer, endtime integer, signature blob); "
, NULL , NULL , NULL ) )
2012-01-12 03:38:24 +00:00
{
2012-05-28 09:54:02 +00:00
return WHYF ( " Failed to create required schema: %s " , sqlite3_errmsg ( rhizome_db ) ) ;
2012-02-16 14:08:03 +00:00
}
2012-05-28 09:54:02 +00:00
// no easy way to tell if these columns already exist, should probably create some kind of schema version table
// running this a second time will fail.
sqlite3_exec ( rhizome_db ,
" ALTER TABLE MANIFESTS ADD COLUMN filesize text; "
" ALTER TABLE MANIFESTS ADD COLUMN filehash text; "
" ALTER TABLE FILES ADD inserttime integer; "
, NULL , NULL , NULL ) ;
if ( sqlite3_exec ( rhizome_db ,
" CREATE INDEX IF NOT EXISTS IDX_MANIFESTS_HASH ON MANIFESTS(filehash); "
" DELETE FROM MANIFESTS WHERE filehash IS NULL; "
" DELETE FROM FILES WHERE NOT EXISTS( SELECT 1 FROM MANIFESTS WHERE MANIFESTS.filehash = FILES.id); "
" DELETE FROM MANIFESTS WHERE NOT EXISTS( SELECT 1 FROM FILES WHERE MANIFESTS.filehash = FILES.id); "
, NULL , NULL , NULL ) ) {
return WHYF ( " Failed to create required schema: %s " , sqlite3_errmsg ( rhizome_db ) ) ;
}
2012-01-12 03:38:24 +00:00
return 0 ;
}
2012-05-25 10:12:45 +00:00
/*
Convenience wrapper for executing an SQL command that returns a no value .
Returns - 1 if an error occurs , otherwise zero .
*/
2012-05-28 02:29:35 +00:00
int sqlite_exec_void ( const char * sqlformat , . . . )
2012-05-25 10:12:45 +00:00
{
if ( ! rhizome_db ) rhizome_opendb ( ) ;
strbuf stmt = strbuf_alloca ( 8192 ) ;
va_list ap ;
va_start ( ap , sqlformat ) ;
strbuf_vsprintf ( stmt , sqlformat , ap ) ;
va_end ( ap ) ;
if ( strbuf_overrun ( stmt ) )
return WHYF ( " Sql statement overrun: %s " , strbuf_str ( stmt ) ) ;
sqlite3_stmt * statement ;
2012-05-28 02:29:35 +00:00
2012-05-25 10:12:45 +00:00
switch ( sqlite3_prepare_v2 ( rhizome_db , strbuf_str ( stmt ) , - 1 , & statement , NULL ) ) {
case SQLITE_OK : case SQLITE_DONE :
break ;
default :
WHY ( strbuf_str ( stmt ) ) ;
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
sqlite3_finalize ( statement ) ;
2012-05-28 02:29:35 +00:00
return - 1 ;
2012-05-25 10:12:45 +00:00
}
int stepcode ;
2012-06-08 03:43:26 +00:00
int rows = 0 ;
2012-05-25 10:12:45 +00:00
while ( ( stepcode = sqlite3_step ( statement ) ) = = SQLITE_ROW )
2012-06-08 03:43:26 +00:00
+ + rows ;
if ( rows ) WARNF ( " query unexpectedly returned %d row%s " , rows , rows = = 1 ? " " : " s " ) ;
2012-05-25 10:12:45 +00:00
switch ( stepcode ) {
case SQLITE_OK :
case SQLITE_DONE :
case SQLITE_ROW :
break ;
default :
WHY ( strbuf_str ( stmt ) ) ;
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
sqlite3_finalize ( statement ) ;
2012-05-28 02:29:35 +00:00
return - 1 ;
2012-05-25 10:12:45 +00:00
}
sqlite3_finalize ( statement ) ;
return 0 ;
}
2012-06-08 03:43:26 +00:00
/*
Convenience wrapper for executing an SQL command that returns a single int64 value .
Returns - 1 if an error occurs .
If no row is found , then returns 0 and does not alter * result .
If exactly one row is found , the assigns its value to * result and returns 1.
If more than one row is found , then assigns the value of the first row to * result and returns the
number of rows .
2012-01-12 03:38:24 +00:00
*/
2012-06-08 03:43:26 +00:00
int sqlite_exec_int64 ( long long * result , const char * sqlformat , . . . )
2012-01-12 03:38:24 +00:00
{
if ( ! rhizome_db ) rhizome_opendb ( ) ;
2012-05-25 10:12:45 +00:00
strbuf stmt = strbuf_alloca ( 8192 ) ;
2012-05-23 06:37:52 +00:00
va_list ap ;
va_start ( ap , sqlformat ) ;
2012-05-25 10:12:45 +00:00
strbuf_vsprintf ( stmt , sqlformat , ap ) ;
2012-01-12 03:38:24 +00:00
va_end ( ap ) ;
2012-05-25 10:12:45 +00:00
if ( strbuf_overrun ( stmt ) )
return WHYF ( " sql statement too long: %s " , strbuf_str ( stmt ) ) ;
2012-01-12 03:38:24 +00:00
sqlite3_stmt * statement ;
2012-05-25 10:12:45 +00:00
switch ( sqlite3_prepare_v2 ( rhizome_db , strbuf_str ( stmt ) , - 1 , & statement , NULL ) ) {
2012-01-12 03:38:24 +00:00
case SQLITE_OK : case SQLITE_DONE : case SQLITE_ROW :
break ;
default :
2012-05-25 10:12:45 +00:00
WHY ( strbuf_str ( stmt ) ) ;
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
2012-01-12 03:38:24 +00:00
sqlite3_finalize ( statement ) ;
2012-05-28 02:29:35 +00:00
return - 1 ;
2012-05-25 04:59:55 +00:00
}
2012-06-08 03:43:26 +00:00
int stepcode ;
int rowcount = 0 ;
if ( ( stepcode = sqlite3_step ( statement ) ) = = SQLITE_ROW ) {
2012-05-25 04:59:55 +00:00
int n = sqlite3_column_count ( statement ) ;
if ( n ! = 1 ) {
sqlite3_finalize ( statement ) ;
2012-05-25 10:12:45 +00:00
return WHYF ( " Incorrect column count %d (should be 1) : % s " , n, strbuf_str(stmt)) ;
2012-01-12 03:38:24 +00:00
}
2012-06-08 03:43:26 +00:00
* result = sqlite3_column_int64 ( statement , 0 ) ;
rowcount = 1 ;
while ( ( stepcode = sqlite3_step ( statement ) ) = = SQLITE_ROW )
+ + rowcount ;
}
switch ( stepcode ) {
case SQLITE_OK : case SQLITE_DONE :
break ;
default :
WHY ( strbuf_str ( stmt ) ) ;
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
sqlite3_finalize ( statement ) ;
return - 1 ;
2012-05-25 04:59:55 +00:00
}
sqlite3_finalize ( statement ) ;
2012-06-08 03:43:26 +00:00
return rowcount ;
2012-05-25 04:59:55 +00:00
}
/*
Convenience wrapper for executing an SQL command that returns a single text value .
Returns - 1 if an error occurs , otherwise the number of rows that were found :
0 means no rows , nothing is appended to the strbuf
1 means exactly one row , and the its column is appended to the strbuf
2 more than one row , and the first row ' s column is appended to the strbuf
@ author Andrew Bettison < andrew @ servalproject . com >
*/
2012-05-25 10:12:45 +00:00
int sqlite_exec_strbuf ( strbuf sb , const char * sqlformat , . . . )
2012-05-25 04:59:55 +00:00
{
if ( ! rhizome_db ) rhizome_opendb ( ) ;
strbuf stmt = strbuf_alloca ( 8192 ) ;
va_list ap ;
va_start ( ap , sqlformat ) ;
strbuf_vsprintf ( stmt , sqlformat , ap ) ;
va_end ( ap ) ;
2012-05-25 10:12:45 +00:00
if ( strbuf_overrun ( stmt ) )
return WHYF ( " sql statement too long: %s " , strbuf_str ( stmt ) ) ;
2012-05-25 04:59:55 +00:00
sqlite3_stmt * statement ;
switch ( sqlite3_prepare_v2 ( rhizome_db , strbuf_str ( stmt ) , - 1 , & statement , NULL ) ) {
case SQLITE_OK : case SQLITE_DONE : case SQLITE_ROW :
break ;
default :
sqlite3_finalize ( statement ) ;
WHY ( strbuf_str ( stmt ) ) ;
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
return WHY ( " Could not prepare sql statement. " ) ;
}
int rows = 0 ;
if ( sqlite3_step ( statement ) = = SQLITE_ROW ) {
int n = sqlite3_column_count ( statement ) ;
if ( n ! = 1 ) {
sqlite3_finalize ( statement ) ;
return WHYF ( " Incorrect column count %d (should be 1) " , n) ;
}
strbuf_puts ( sb , ( const char * ) sqlite3_column_text ( statement , 0 ) ) ;
sqlite3_finalize ( statement ) ;
+ + rows ;
}
if ( sqlite3_step ( statement ) = = SQLITE_ROW )
+ + rows ;
sqlite3_finalize ( statement ) ;
return rows ;
2012-01-12 03:38:24 +00:00
}
long long rhizome_database_used_bytes ( )
{
2012-05-25 10:12:45 +00:00
long long db_page_size = sqlite_exec_void ( " PRAGMA page_size; " ) ;
long long db_page_count = sqlite_exec_void ( " PRAGMA page_count; " ) ;
long long db_free_page_count = sqlite_exec_void ( " PRAGMA free_count; " ) ;
2012-01-12 03:38:24 +00:00
return db_page_size * ( db_page_count - db_free_page_count ) ;
}
int rhizome_make_space ( int group_priority , long long bytes )
{
sqlite3_stmt * statement ;
/* Asked for impossibly large amount */
if ( bytes > = ( rhizome_space - 65536 ) ) return - 1 ;
long long db_used = rhizome_database_used_bytes ( ) ;
/* If there is already enough space now, then do nothing more */
if ( db_used < = ( rhizome_space - bytes - 65536 ) ) return 0 ;
/* Okay, not enough space, so free up some. */
char sql [ 1024 ] ;
snprintf ( sql , 1024 , " select id,length from files where highestpriority<%d order by descending length " , group_priority ) ;
if ( sqlite3_prepare_v2 ( rhizome_db , sql , - 1 , & statement , NULL ) ! = SQLITE_OK )
{
2012-05-18 09:23:27 +00:00
WHYF ( " SQLite error running query '%s': %s " , sql , sqlite3_errmsg ( rhizome_db ) ) ;
2012-01-12 03:38:24 +00:00
sqlite3_finalize ( statement ) ;
sqlite3_close ( rhizome_db ) ;
rhizome_db = NULL ;
exit ( - 1 ) ;
}
while ( bytes > ( rhizome_space - 65536 - rhizome_database_used_bytes ( ) ) & & sqlite3_step ( statement ) = = SQLITE_ROW )
{
/* Make sure we can drop this blob, and if so drop it, and recalculate number of bytes required */
const unsigned char * id ;
2012-05-10 04:37:11 +00:00
//long long length;
2012-01-12 03:38:24 +00:00
/* Get values */
if ( sqlite3_column_type ( statement , 0 ) = = SQLITE_TEXT ) id = sqlite3_column_text ( statement , 0 ) ;
else {
2012-05-18 09:23:27 +00:00
WARNF ( " Incorrect type in id column of files table. " ) ;
2012-01-12 03:38:24 +00:00
continue ; }
2012-05-10 04:37:11 +00:00
if ( sqlite3_column_type ( statement , 1 ) = = SQLITE_INTEGER ) ; //length=sqlite3_column_int(statement, 1);
2012-01-12 03:38:24 +00:00
else {
2012-05-18 09:23:27 +00:00
WARNF ( " Incorrect type in length column of files table. " ) ;
2012-01-12 03:38:24 +00:00
continue ; }
/* Try to drop this file from storage, discarding any references that do not trump the priority of this
request . The query done earlier should ensure this , but it doesn ' t hurt to be paranoid , and it also
protects against inconsistency in the database . */
rhizome_drop_stored_file ( ( char * ) id , group_priority + 1 ) ;
}
sqlite3_finalize ( statement ) ;
2012-05-10 04:37:11 +00:00
//long long equal_priority_larger_file_space_used = sqlite_exec_int64("SELECT COUNT(length) FROM FILES WHERE highestpriority=%d and length>%lld",group_priority,bytes);
2012-01-12 03:38:24 +00:00
/* XXX Get rid of any equal priority files that are larger than this one */
/* XXX Get rid of any higher priority files that are not relevant in this time or location */
/* Couldn't make space */
return WHY ( " Incomplete " ) ;
}
/* Drop the specified file from storage, and any manifests that reference it,
provided that none of those manifests are being retained at a higher priority
than the maximum specified here . */
2012-05-27 06:30:51 +00:00
int rhizome_drop_stored_file ( const char * id , int maximum_priority )
2012-01-12 03:38:24 +00:00
{
char sql [ 1024 ] ;
sqlite3_stmt * statement ;
int cannot_drop = 0 ;
2012-05-26 03:16:25 +00:00
if ( strlen ( id ) > ( crypto_hash_sha512_BYTES * 2 + 1 ) ) {
WHY ( " File ID is wrong length " ) ;
return - 1 ;
}
2012-01-12 03:38:24 +00:00
2012-06-08 03:43:26 +00:00
snprintf ( sql , 1024 , " select id from manifests where filehash='%s' " , id ) ;
2012-01-12 03:38:24 +00:00
if ( sqlite3_prepare_v2 ( rhizome_db , sql , - 1 , & statement , NULL ) ! = SQLITE_OK )
{
2012-05-18 09:23:27 +00:00
WHYF ( " SQLite error running query '%s': %s " , sql , sqlite3_errmsg ( rhizome_db ) ) ;
2012-01-12 03:38:24 +00:00
sqlite3_finalize ( statement ) ;
sqlite3_close ( rhizome_db ) ;
rhizome_db = NULL ;
return WHY ( " Could not drop stored file " ) ;
}
while ( sqlite3_step ( statement ) = = SQLITE_ROW )
{
/* Find manifests for this file */
2012-05-28 02:29:35 +00:00
const unsigned char * manifestId ;
if ( sqlite3_column_type ( statement , 0 ) = = SQLITE_TEXT )
manifestId = sqlite3_column_text ( statement , 0 ) ;
2012-01-12 03:38:24 +00:00
else {
2012-05-18 09:23:27 +00:00
WHYF ( " Incorrect type in id column of manifests table. " ) ;
2012-05-28 02:29:35 +00:00
continue ;
}
2012-01-12 03:38:24 +00:00
/* Check that manifest is not part of a higher priority group.
If so , we cannot drop the manifest or the file .
However , we will keep iterating , as we can still drop any other manifests pointing to this file
that are lower priority , and thus free up a little space . */
2012-06-08 03:43:26 +00:00
int priority = rhizome_manifest_priority ( ( char * ) manifestId ) ;
if ( priority = = - 1 )
WHYF ( " Cannot drop due to error, manifestId=%s " , manifestId ) ;
else if ( priority > maximum_priority ) {
WHYF ( " Cannot drop due to manifest %s " , manifestId ) ;
2012-01-12 03:38:24 +00:00
cannot_drop = 1 ;
} else {
2012-05-28 09:54:02 +00:00
printf ( " removing stale manifests, groupmemberships \n " ) ;
sqlite_exec_void ( " delete from manifests where id='%s'; " , manifestId ) ;
2012-05-28 02:29:35 +00:00
sqlite_exec_void ( " delete from keypairs where public='%s'; " , manifestId ) ;
sqlite_exec_void ( " delete from groupmemberships where manifestid='%s'; " , manifestId ) ;
2012-01-12 03:38:24 +00:00
}
}
sqlite3_finalize ( statement ) ;
if ( ! cannot_drop ) {
2012-05-25 10:12:45 +00:00
sqlite_exec_void ( " delete from files where id='%s'; " , id ) ;
2012-01-12 03:38:24 +00:00
}
return 0 ;
}
2012-05-28 02:29:35 +00:00
int sqlite3_exec_retry ( sqlite3 * db , const char * sql , int ( * callback ) ( void * , int , char * * , char * * ) , void * arg , char * * errmsg ) {
int ret ;
do {
ret = sqlite3_exec ( db , sql , callback , arg , errmsg ) ;
} while ( ret = = SQLITE_BUSY | | ret = = SQLITE_LOCKED ) ;
if ( ! SQLITE_CODE_OK ( ret ) )
WHY ( sql ) ;
return ret ;
}
int sqlite3_prepare_v2_retry ( sqlite3 * db , const char * zSql , int nByte , sqlite3_stmt * * ppStmt , const char * * pzTail ) {
int ret ;
do {
ret = sqlite3_prepare_v2 ( db , zSql , nByte , ppStmt , pzTail ) ;
} while ( ret = = SQLITE_BUSY | | ret = = SQLITE_LOCKED ) ;
if ( ! SQLITE_CODE_OK ( ret ) )
WHY ( zSql ) ;
return ret ;
}
int sqlite3_step_retry ( sqlite3_stmt * stmt ) {
int ret ;
while ( 1 ) {
ret = sqlite3_step ( stmt ) ;
if ( ret = = SQLITE_BUSY | | ret = = SQLITE_LOCKED ) {
WHY ( " Database locked, retrying " ) ;
sqlite3_reset ( stmt ) ;
} else {
return ret ;
}
}
}
2012-01-12 03:38:24 +00:00
/*
Store the specified manifest into the sqlite database .
We assume that sufficient space has been made for us .
The manifest should be finalised , and so we don ' t need to
look at the underlying manifest file , but can just write m - > manifest_data
as a blob .
associated_filename needs to be read in and stored as a blob . Hopefully that
can be done in pieces so that we don ' t have memory exhaustion issues on small
architectures . However , we do know it ' s hash apriori from m , and so we can
skip loading the file in if it is already stored . mmap ( ) apparently works on
Linux FAT file systems , and is probably the best choice since it doesn ' t need
all pages to be in RAM at the same time .
SQLite does allow modifying of blobs once stored in the database .
The trick is to insert the blob as all zeroes using a special function , and then
substitute bytes in the blog progressively .
We need to also need to create the appropriate row ( s ) in the MANIFESTS , FILES ,
2012-05-28 09:54:02 +00:00
and GROUPMEMBERSHIPS tables , and possibly GROUPLIST as well .
2012-01-12 03:38:24 +00:00
*/
2012-05-25 15:20:48 +00:00
int rhizome_store_bundle ( rhizome_manifest * m )
2012-01-12 03:38:24 +00:00
{
2012-05-23 06:34:00 +00:00
if ( ! m - > finalised ) return WHY ( " Manifest was not finalised " ) ;
2012-01-12 03:38:24 +00:00
2012-05-27 23:33:49 +00:00
if ( m - > haveSecret ) {
/* We used to store the secret in the database, but we don't anymore, as we use
the BK field in the manifest . So nothing to do here . */
} else {
/* We don't have the secret for this manifest, so only allow updates if
the self - signature is valid */
if ( ! m - > selfSigned ) {
WHY ( " *** Insert into manifests failed (-2). " ) ;
return WHY ( " Manifest is not signed, and I don't have the key. Manifest might be forged or corrupt. " ) ;
}
}
2012-05-23 06:34:00 +00:00
char manifestid [ RHIZOME_MANIFEST_ID_STRLEN + 1 ] ;
rhizome_manifest_get ( m , " id " , manifestid , sizeof manifestid ) ;
str_toupper_inplace ( manifestid ) ;
2012-01-12 03:38:24 +00:00
2012-05-23 06:34:00 +00:00
char filehash [ RHIZOME_FILEHASH_STRLEN + 1 ] ;
strncpy ( filehash , m - > fileHexHash , sizeof filehash ) ;
str_toupper_inplace ( filehash ) ;
2012-01-12 03:38:24 +00:00
2012-05-27 23:33:49 +00:00
2012-05-28 02:29:35 +00:00
char * err ;
int sql_ret ;
sqlite3_stmt * stmt ;
2012-05-27 23:33:49 +00:00
2012-01-12 03:38:24 +00:00
/* Bind BAR to data field */
unsigned char bar [ RHIZOME_BAR_BYTES ] ;
rhizome_manifest_to_bar ( m , bar ) ;
2012-05-28 02:29:35 +00:00
// we should add the file in the same transaction, but closing the blob seems to cause some issues.
/* Store the file */
2012-06-05 06:15:53 +00:00
// TODO encrypted payloads - pass encryption key here
2012-05-28 02:29:35 +00:00
if ( m - > fileLength > 0 ) {
if ( rhizome_store_file ( m , NULL ) ) {
WHY ( " Could not store file " ) ;
sql_ret = 1 ;
}
2012-01-12 03:38:24 +00:00
}
2012-05-28 02:29:35 +00:00
if ( ! rhizome_db ) rhizome_opendb ( ) ;
sql_ret = sqlite3_exec_retry ( rhizome_db , " BEGIN TRANSACTION; " , NULL , NULL , & err ) ;
2012-05-28 09:54:02 +00:00
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_prepare_v2_retry ( rhizome_db , " INSERT OR REPLACE INTO MANIFESTS(id,manifest,version,inserttime,bar,filesize,filehash) VALUES(?,?,?,?,?,?,?); " , - 1 , & stmt , NULL ) ;
2012-05-28 02:29:35 +00:00
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_text ( stmt , 1 , manifestid , - 1 , SQLITE_TRANSIENT ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_blob ( stmt , 2 , m - > manifestdata , m - > manifest_bytes , SQLITE_TRANSIENT ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_int64 ( stmt , 3 , m - > version ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_int64 ( stmt , 4 , gettime_ms ( ) ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_blob ( stmt , 5 , bar , RHIZOME_BAR_BYTES , SQLITE_TRANSIENT ) ;
2012-05-28 09:54:02 +00:00
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_int64 ( stmt , 6 , m - > fileLength ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_text ( stmt , 7 , filehash , - 1 , SQLITE_TRANSIENT ) ;
2012-05-28 02:29:35 +00:00
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_step_retry ( stmt ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_finalize ( stmt ) ;
// we might need to leave the old file around for a bit
// clean out unreferenced files first
2012-05-28 09:54:02 +00:00
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_prepare_v2_retry ( rhizome_db , " DELETE FROM FILES WHERE inserttime < ? AND NOT EXISTS( SELECT 1 FROM MANIFESTS WHERE MANIFESTS.filehash = FILES.id); " , - 1 , & stmt , NULL ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_int64 ( stmt , 1 , gettime_ms ( ) - 60000 ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_step_retry ( stmt ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_finalize ( stmt ) ;
2012-05-28 02:29:35 +00:00
2012-01-12 03:38:24 +00:00
if ( rhizome_manifest_get ( m , " isagroup " , NULL , 0 ) ! = NULL ) {
int closed = rhizome_manifest_get_ll ( m , " closedgroup " ) ;
if ( closed < 1 ) closed = 0 ;
int ciphered = rhizome_manifest_get_ll ( m , " cipheredgroup " ) ;
if ( ciphered < 1 ) ciphered = 0 ;
2012-05-28 02:29:35 +00:00
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_prepare_v2_retry ( rhizome_db , " INSERT OR REPLACE INTO GROUPLIST(id,closed,ciphered,priority) VALUES (?,?,?,?); " , - 1 , & stmt , NULL ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_text ( stmt , 1 , manifestid , - 1 , SQLITE_TRANSIENT ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_int ( stmt , 2 , closed ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_int ( stmt , 3 , ciphered ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_int ( stmt , 4 , RHIZOME_PRIORITY_DEFAULT ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_step_retry ( stmt ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_finalize ( stmt ) ;
2012-01-12 03:38:24 +00:00
}
2012-05-28 02:29:35 +00:00
if ( m - > group_count > 0 ) {
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_prepare_v2_retry ( rhizome_db , " INSERT OR REPLACE INTO GROUPMEMBERSHIPS(manifestid,groupid) VALUES(?, ?); " , - 1 , & stmt , NULL ) ;
int i ;
for ( i = 0 ; i < m - > group_count ; i + + ) {
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_text ( stmt , 1 , manifestid , - 1 , SQLITE_TRANSIENT ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_bind_text ( stmt , 2 , m - > groups [ i ] , - 1 , SQLITE_TRANSIENT ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_step_retry ( stmt ) ;
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_reset ( stmt ) ;
}
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_finalize ( stmt ) ;
2012-01-12 03:38:24 +00:00
}
2012-05-28 02:29:35 +00:00
if ( SQLITE_CODE_OK ( sql_ret ) ) sql_ret = sqlite3_exec_retry ( rhizome_db , " COMMIT; " , NULL , NULL , & err ) ;
if ( err ! = NULL ) {
WHY ( err ) ;
sqlite3_free ( err ) ;
}
if ( SQLITE_CODE_OK ( sql_ret ) )
return 0 ;
WHYF ( " Failed to store bundle %s " , sqlite3_errmsg ( rhizome_db ) ) ;
sqlite3_exec_retry ( rhizome_db , " ROLLBACK; " , NULL , NULL , NULL ) ;
return - 1 ;
2012-01-12 03:38:24 +00:00
}
int rhizome_finish_sqlstatement ( sqlite3_stmt * statement )
{
/* Do actual insert, and abort if it fails */
int dud = 0 ;
int r ;
r = sqlite3_step ( statement ) ;
switch ( r ) {
case SQLITE_DONE : case SQLITE_ROW : case SQLITE_OK :
break ;
default :
WHY ( " sqlite3_step() failed. " ) ;
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
dud + + ;
sqlite3_finalize ( statement ) ;
}
if ( ( ! dud ) & & ( ( r = sqlite3_finalize ( statement ) ) ! = SQLITE_OK ) ) {
WHY ( " sqlite3_finalize() failed. " ) ;
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
dud + + ;
}
if ( dud ) return WHY ( " SQLite3 could not complete statement. " ) ;
return 0 ;
}
2012-05-16 04:16:42 +00:00
int rhizome_list_manifests ( const char * service , const char * sender_sid , const char * recipient_sid , int limit , int offset )
2012-04-02 08:12:40 +00:00
{
2012-05-20 14:39:14 +00:00
strbuf b = strbuf_alloca ( 1024 ) ;
2012-05-25 08:15:52 +00:00
strbuf_sprintf ( b , " SELECT id, manifest, version, inserttime FROM manifests ORDER BY inserttime DESC " ) ;
2012-05-20 14:39:14 +00:00
if ( limit )
strbuf_sprintf ( b , " LIMIT %u " , limit ) ;
if ( offset )
strbuf_sprintf ( b , " OFFSET %u " , offset ) ;
if ( strbuf_overrun ( b ) )
return WHYF ( " SQL command too long: " , strbuf_str ( b ) ) ;
2012-04-02 08:12:40 +00:00
sqlite3_stmt * statement ;
const char * cmdtail ;
int ret = 0 ;
2012-05-20 14:39:14 +00:00
if ( sqlite3_prepare_v2 ( rhizome_db , strbuf_str ( b ) , strbuf_len ( b ) + 1 , & statement , & cmdtail ) ! = SQLITE_OK ) {
2012-04-02 08:12:40 +00:00
sqlite3_finalize ( statement ) ;
ret = WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
} else {
size_t rows = 0 ;
2012-06-05 04:28:59 +00:00
cli_puts ( " 10 " ) ; cli_delim ( " \n " ) ; // number of columns
2012-05-17 07:00:30 +00:00
cli_puts ( " service " ) ; cli_delim ( " : " ) ;
2012-05-25 08:36:48 +00:00
cli_puts ( " id " ) ; cli_delim ( " : " ) ;
2012-04-24 08:20:27 +00:00
cli_puts ( " version " ) ; cli_delim ( " : " ) ;
cli_puts ( " date " ) ; cli_delim ( " : " ) ;
2012-05-25 08:36:48 +00:00
cli_puts ( " _inserttime " ) ; cli_delim ( " : " ) ;
cli_puts ( " filesize " ) ; cli_delim ( " : " ) ;
cli_puts ( " filehash " ) ; cli_delim ( " : " ) ;
2012-05-26 04:14:52 +00:00
cli_puts ( " sender " ) ; cli_delim ( " : " ) ;
2012-06-05 04:28:59 +00:00
cli_puts ( " recipient " ) ; cli_delim ( " : " ) ;
cli_puts ( " name " ) ; cli_delim ( " \n " ) ; // should be last, because name may contain ':'
2012-04-02 08:12:40 +00:00
while ( sqlite3_step ( statement ) = = SQLITE_ROW ) {
+ + rows ;
2012-05-25 08:15:52 +00:00
if ( ! ( sqlite3_column_count ( statement ) = = 4
2012-04-02 08:12:40 +00:00
& & sqlite3_column_type ( statement , 0 ) = = SQLITE_TEXT
2012-05-25 08:15:52 +00:00
& & sqlite3_column_type ( statement , 1 ) = = SQLITE_BLOB
& & sqlite3_column_type ( statement , 2 ) = = SQLITE_INTEGER
& & sqlite3_column_type ( statement , 3 ) = = SQLITE_INTEGER
2012-04-02 08:12:40 +00:00
) ) {
ret = WHY ( " Incorrect statement column " ) ;
break ;
}
2012-05-25 06:08:13 +00:00
rhizome_manifest * m = rhizome_new_manifest ( ) ;
if ( m = = NULL ) {
ret = WHY ( " Out of manifests " ) ;
break ;
}
2012-05-25 08:15:52 +00:00
const char * q_manifestid = ( const char * ) sqlite3_column_text ( statement , 0 ) ;
const char * manifestblob = ( char * ) sqlite3_column_blob ( statement , 1 ) ;
size_t manifestblobsize = sqlite3_column_bytes ( statement , 1 ) ; // must call after sqlite3_column_blob()
long long q_version = sqlite3_column_int64 ( statement , 2 ) ;
long long q_inserttime = sqlite3_column_int64 ( statement , 3 ) ;
2012-05-25 23:54:47 +00:00
if ( rhizome_read_manifest_file ( m , manifestblob , manifestblobsize ) = = - 1 ) {
2012-05-25 06:08:13 +00:00
WARNF ( " MANIFESTS row id=%s has invalid manifest blob -- skipped " , q_manifestid ) ;
} else {
2012-05-25 08:15:52 +00:00
long long blob_version = rhizome_manifest_get_ll ( m , " version " ) ;
if ( blob_version ! = q_version )
WARNF ( " MANIFESTS row id=%s version=%lld does not match manifest blob.version=%lld " , q_manifestid , q_version , blob_version ) ;
2012-05-25 06:08:13 +00:00
int match = 1 ;
const char * blob_service = rhizome_manifest_get ( m , " service " , NULL , 0 ) ;
if ( service [ 0 ] & & ! ( blob_service & & strcasecmp ( service , blob_service ) = = 0 ) )
2012-05-21 05:11:22 +00:00
match = 0 ;
2012-05-26 04:14:52 +00:00
const char * blob_sender = rhizome_manifest_get ( m , " sender " , NULL , 0 ) ;
const char * blob_recipient = rhizome_manifest_get ( m , " recipient " , NULL , 0 ) ;
2012-05-25 06:08:13 +00:00
if ( match & & sender_sid [ 0 ] ) {
if ( ! ( blob_sender & & strcasecmp ( sender_sid , blob_sender ) = = 0 ) )
match = 0 ;
}
if ( match & & recipient_sid [ 0 ] ) {
if ( ! ( blob_recipient & & strcasecmp ( recipient_sid , blob_recipient ) = = 0 ) )
match = 0 ;
}
2012-05-26 04:14:52 +00:00
2012-05-25 06:08:13 +00:00
if ( match ) {
const char * blob_name = rhizome_manifest_get ( m , " name " , NULL , 0 ) ;
long long blob_date = rhizome_manifest_get_ll ( m , " date " ) ;
2012-05-25 08:15:52 +00:00
const char * blob_filehash = rhizome_manifest_get ( m , " filehash " , NULL , 0 ) ;
long long blob_filesize = rhizome_manifest_get_ll ( m , " filesize " ) ;
2012-06-08 03:43:26 +00:00
DEBUGF ( " Manifest payload size = %lld " , blob_filesize ) ;
2012-05-25 06:08:13 +00:00
cli_puts ( blob_service ? blob_service : " " ) ; cli_delim ( " : " ) ;
cli_puts ( q_manifestid ) ; cli_delim ( " : " ) ;
2012-05-25 08:15:52 +00:00
cli_printf ( " %lld " , blob_version ) ; cli_delim ( " : " ) ;
2012-05-25 08:36:48 +00:00
cli_printf ( " %lld " , blob_date ) ; cli_delim ( " : " ) ;
2012-05-25 08:15:52 +00:00
cli_printf ( " %lld " , q_inserttime ) ; cli_delim ( " : " ) ;
2012-05-28 04:28:57 +00:00
cli_printf ( " %lld " , blob_filesize ) ; cli_delim ( " : " ) ;
2012-05-25 08:36:48 +00:00
cli_puts ( blob_filehash ? blob_filehash : " " ) ; cli_delim ( " : " ) ;
2012-05-26 04:14:52 +00:00
cli_puts ( blob_sender ? blob_sender : " " ) ; cli_delim ( " : " ) ;
2012-06-05 04:28:59 +00:00
cli_puts ( blob_recipient ? blob_recipient : " " ) ; cli_delim ( " : " ) ;
cli_puts ( blob_name ? blob_name : " " ) ; cli_delim ( " \n " ) ;
2012-05-25 06:08:13 +00:00
}
2012-05-21 05:11:22 +00:00
}
2012-05-25 06:08:13 +00:00
if ( m ) rhizome_manifest_free ( m ) ;
2012-04-02 08:12:40 +00:00
}
}
sqlite3_finalize ( statement ) ;
return ret ;
}
2012-01-12 03:38:24 +00:00
2012-04-02 08:12:40 +00:00
/* The following function just stores the file (or silently returns if it already exists).
The relationships of manifests to this file are the responsibility of the caller . */
2012-05-26 04:19:13 +00:00
int rhizome_store_file ( rhizome_manifest * m , const unsigned char * key )
2012-04-02 08:12:40 +00:00
{
2012-05-25 15:20:48 +00:00
const char * file = m - > dataFileName ;
const char * hash = m - > fileHexHash ;
2012-05-27 12:24:55 +00:00
char hash_out [ crypto_hash_sha512_BYTES * 2 + 1 ] ;
2012-05-25 15:20:48 +00:00
int priority = m - > fileHighestPriority ;
if ( m - > payloadEncryption )
return WHY ( " Writing encrypted payloads not implemented " ) ;
if ( ! m - > fileHashedP )
return WHY ( " Cannot store bundle file until it has been hashed " ) ;
2012-01-12 03:38:24 +00:00
int fd = open ( file , O_RDONLY ) ;
2012-06-05 06:15:53 +00:00
if ( fd = = - 1 ) {
WHY_perror ( " open " ) ;
return WHY ( " Could not open associated file " ) ;
}
2012-01-12 03:38:24 +00:00
struct stat stat ;
if ( fstat ( fd , & stat ) ) {
2012-06-05 06:15:53 +00:00
WHY_perror ( " fstat " ) ;
2012-01-12 03:38:24 +00:00
close ( fd ) ;
return WHY ( " Could not stat() associated file " ) ;
}
2012-05-27 12:24:55 +00:00
if ( stat . st_size < m - > fileLength ) {
return WHYF ( " File has shrunk, so cannot be stored. " ) ;
} else if ( stat . st_size > m - > fileLength ) {
WARNF ( " File has grown by %lld bytes. I will just store the original number of bytes so that the hash (hopefully) matches " , stat . st_size - m - > fileLength ) ;
}
2012-01-12 03:38:24 +00:00
2012-06-05 06:15:53 +00:00
unsigned char * addr = mmap ( NULL , m - > fileLength , PROT_READ , MAP_FILE | MAP_SHARED , fd , 0 ) ;
2012-01-12 03:38:24 +00:00
if ( addr = = MAP_FAILED ) {
2012-06-05 06:15:53 +00:00
WHY_perror ( " mmap " ) ;
2012-01-12 03:38:24 +00:00
close ( fd ) ;
return WHY ( " mmap() of associated file failed . " ) ;
}
/* INSERT INTO FILES(id as text, data blob, length integer, highestpriority integer).
BUT , we have to do this incrementally so that we can handle blobs larger than available memory .
This is possible using :
int sqlite3_bind_zeroblob ( sqlite3_stmt * , int , int n ) ;
That binds an all zeroes blob to a field . We can then populate the data by
opening a handle to the blob using :
int sqlite3_blob_write ( sqlite3_blob * , const void * z , int n , int iOffset ) ;
*/
char sqlcmd [ 1024 ] ;
const char * cmdtail ;
/* See if the file is already stored, and if so, don't bother storing it again */
2012-06-08 03:43:26 +00:00
long long count = 0 ;
if ( sqlite_exec_int64 ( & count , " SELECT COUNT(*) FROM FILES WHERE id='%s' AND datavalid<>0; " , hash ) < 1 ) {
close ( fd ) ;
return WHY ( " Failed to count stored files " ) ;
}
if ( count > = 1 ) {
2012-01-12 03:38:24 +00:00
/* File is already stored, so just update the highestPriority field if required. */
2012-06-08 03:43:26 +00:00
long long storedPriority = - 1 ;
if ( sqlite_exec_int64 ( & storedPriority , " SELECT highestPriority FROM FILES WHERE id='%s' AND datavalid!=0 " , hash ) = = - 1 ) {
close ( fd ) ;
return WHY ( " Failed to select highest priority " ) ;
}
if ( storedPriority < priority ) {
if ( sqlite_exec_void ( " UPDATE FILES SET highestPriority=%d WHERE id='%s'; " , priority , hash ) = = - 1 ) {
close ( fd ) ;
return WHY ( " SQLite failed to update highestPriority field for stored file. " ) ;
2012-01-12 03:38:24 +00:00
}
2012-06-08 03:43:26 +00:00
}
2012-01-12 03:38:24 +00:00
close ( fd ) ;
return 0 ;
}
/* Okay, so there are no records that match, but we should delete any half-baked record (with datavalid=0) so that the insert below doesn't fail.
Don ' t worry about the return result , since it might not delete any records . */
sqlite3_exec ( rhizome_db , " DELETE FROM FILES WHERE datavalid=0; " , NULL , NULL , NULL ) ;
2012-05-28 09:54:02 +00:00
snprintf ( sqlcmd , 1024 , " INSERT OR REPLACE INTO FILES(id,data,length,highestpriority,datavalid,inserttime) VALUES('%s',?,%lld,%d,0,%lld); " ,
hash , ( long long ) m - > fileLength , priority , gettime_ms ( ) ) ;
2012-01-12 03:38:24 +00:00
sqlite3_stmt * statement ;
if ( sqlite3_prepare_v2 ( rhizome_db , sqlcmd , strlen ( sqlcmd ) + 1 , & statement , & cmdtail )
! = SQLITE_OK )
{
2012-05-25 10:12:45 +00:00
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
2012-01-12 03:38:24 +00:00
sqlite3_finalize ( statement ) ;
2012-05-25 10:12:45 +00:00
close ( fd ) ;
return WHY ( " sqlite3_prepare_v2() failed " ) ;
2012-01-12 03:38:24 +00:00
}
/* Bind appropriate sized zero-filled blob to data field */
int dud = 0 ;
int r ;
2012-05-27 12:24:55 +00:00
if ( ( r = sqlite3_bind_zeroblob ( statement , 1 , m - > fileLength ) ) ! = SQLITE_OK )
2012-01-12 03:38:24 +00:00
{
dud + + ;
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
2012-05-25 10:12:45 +00:00
WHY ( " sqlite3_bind_zeroblob() failed " ) ;
2012-01-12 03:38:24 +00:00
}
/* Do actual insert, and abort if it fails */
if ( ! dud )
switch ( sqlite3_step ( statement ) ) {
case SQLITE_OK : case SQLITE_ROW : case SQLITE_DONE :
break ;
default :
dud + + ;
WHY ( " sqlite3_step() failed " ) ;
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
}
if ( sqlite3_finalize ( statement ) ) dud + + ;
if ( dud ) {
if ( sqlite3_finalize ( statement ) ! = SQLITE_OK )
{
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
2012-05-25 10:12:45 +00:00
WHY ( " sqlite3_finalize() failed " ) ;
2012-01-12 03:38:24 +00:00
}
2012-05-25 10:12:45 +00:00
close ( fd ) ;
2012-01-12 03:38:24 +00:00
return WHY ( " SQLite3 failed to insert row for file " ) ;
}
/* Get rowid for inserted row, so that we can modify the blob */
int rowid = sqlite3_last_insert_rowid ( rhizome_db ) ;
if ( rowid < 1 ) {
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
2012-05-25 10:12:45 +00:00
close ( fd ) ;
2012-01-12 03:38:24 +00:00
return WHY ( " SQLite3 failed return rowid of inserted row " ) ;
}
sqlite3_blob * blob ;
if ( sqlite3_blob_open ( rhizome_db , " main " , " FILES " , " data " , rowid ,
1 /* read/write */ ,
& blob ) ! = SQLITE_OK )
{
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
sqlite3_blob_close ( blob ) ;
2012-05-25 10:12:45 +00:00
close ( fd ) ;
2012-01-12 03:38:24 +00:00
return WHY ( " SQLite3 failed to open file blob for writing " ) ;
}
2012-05-26 04:19:13 +00:00
unsigned char nonce [ crypto_stream_xsalsa20_NONCEBYTES ] ;
bzero ( nonce , crypto_stream_xsalsa20_NONCEBYTES ) ;
unsigned char buffer [ RHIZOME_CRYPT_PAGE_SIZE ] ;
2012-05-27 12:24:55 +00:00
/* Calculate hash of file as we go, so that we can report if
the contents have changed during import . This is also why we
use the m - > fileLength instead of size returned by stat , in case
the file has been appended , e . g . , if a journal is being appended to
by a separate process . This has already been shown to happen with
Serval Maps , and it is also quite possible with MeshMS and other
services . */
2012-01-12 03:38:24 +00:00
{
2012-05-27 12:24:55 +00:00
SHA512_CTX context ;
SHA512_Init ( & context ) ;
2012-01-12 03:38:24 +00:00
long long i ;
2012-05-27 12:24:55 +00:00
for ( i = 0 ; i < m - > fileLength ; i + = RHIZOME_CRYPT_PAGE_SIZE )
2012-01-12 03:38:24 +00:00
{
2012-05-26 04:19:13 +00:00
int n = RHIZOME_CRYPT_PAGE_SIZE ;
2012-05-27 12:24:55 +00:00
if ( i + n > m - > fileLength ) n = m - > fileLength - i ;
2012-05-27 12:44:53 +00:00
SHA512_Update ( & context , & addr [ i ] , n ) ;
2012-05-26 04:19:13 +00:00
if ( key ) {
/* calculate block nonce */
int j ; for ( j = 0 ; j < 8 ; j + + ) nonce [ i ] = ( i > > ( j * 8 ) ) & 0xff ;
2012-05-27 12:44:53 +00:00
crypto_stream_xsalsa20_xor ( & addr [ i ] , & buffer [ 0 ] , n ,
2012-05-26 04:19:13 +00:00
nonce , key ) ;
if ( sqlite3_blob_write ( blob , & buffer [ 0 ] , n , i ) ! = SQLITE_OK ) dud + + ;
}
else
if ( sqlite3_blob_write ( blob , & addr [ i ] , n , i ) ! = SQLITE_OK ) dud + + ;
2012-01-12 03:38:24 +00:00
}
2012-05-27 12:24:55 +00:00
SHA512_End ( & context , ( char * ) hash_out ) ;
str_toupper_inplace ( hash_out ) ;
2012-01-12 03:38:24 +00:00
}
2012-05-28 02:29:35 +00:00
if ( sqlite3_blob_close ( blob ) ! = SQLITE_OK ) dud + + ;
2012-05-25 10:12:45 +00:00
close ( fd ) ;
2012-01-12 03:38:24 +00:00
2012-05-27 12:24:55 +00:00
if ( strcasecmp ( hash_out , hash ) )
{
2012-05-27 12:44:53 +00:00
WHYF ( " Computed hash = %s " , hash_out ) ;
2012-05-27 12:24:55 +00:00
return WHY ( " File hash does not match -- has file been modified while being stored? " ) ;
}
2012-01-12 03:38:24 +00:00
/* Mark file as up-to-date */
2012-05-28 02:29:35 +00:00
if ( sqlite_exec_void ( " UPDATE FILES SET datavalid=1 WHERE id='%s'; " , hash ) )
return WHY ( " Failed to set datavalid " ) ;
2012-01-12 03:38:24 +00:00
if ( dud ) {
WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
return WHY ( " SQLite3 failed write all blob data " ) ;
}
return 0 ;
}
2012-05-25 04:59:55 +00:00
2012-05-23 06:34:00 +00:00
void rhizome_bytes_to_hex_upper ( unsigned const char * in , char * out , int byteCount )
2012-01-12 03:38:24 +00:00
{
int i = 0 ;
2012-05-23 06:34:00 +00:00
for ( i = 0 ; i ! = byteCount * 2 ; + + i )
out [ i ] = nybltochar_upper ( ( in [ i > > 1 ] > > ( 4 - 4 * ( i & 1 ) ) ) & 0xf ) ;
out [ i ] = ' \0 ' ;
2012-01-12 03:38:24 +00:00
}
2012-05-26 04:12:33 +00:00
int rhizome_update_file_priority ( const char * fileid )
2012-01-12 03:38:24 +00:00
{
2012-05-28 09:54:02 +00:00
/* work out the highest priority of any referrer */
2012-06-08 03:43:26 +00:00
long long highestPriority = - 1 ;
if ( sqlite_exec_int64 ( & highestPriority ,
" SELECT max(grouplist.priority) FROM MANIFESTS,GROUPMEMBERSHIPS,GROUPLIST "
" where manifests.filehash='%s' "
" AND groupmemberships.manifestid=manifests.id "
" AND groupmemberships.groupid=grouplist.id; " ,
fileid ) = = - 1 )
return - 1 ;
if ( highestPriority > = 0 )
return sqlite_exec_void ( " UPDATE files set highestPriority=%lld WHERE id='%s'; " , highestPriority , fileid ) ;
2012-01-12 03:38:24 +00:00
return 0 ;
}
2012-04-12 09:00:52 +00:00
2012-05-16 06:26:17 +00:00
/* Search the database for a manifest having the same name and payload content,
and if the version is known , having the same version .
@ author Andrew Bettison < andrew @ servalproject . com >
2012-04-12 09:00:52 +00:00
*/
2012-05-25 23:02:17 +00:00
int rhizome_find_duplicate ( const rhizome_manifest * m , rhizome_manifest * * found ,
int checkVersionP )
2012-04-12 09:00:52 +00:00
{
if ( ! m - > fileHashedP )
return WHY ( " Manifest payload is not hashed " ) ;
2012-05-20 14:39:14 +00:00
const char * service = rhizome_manifest_get ( m , " service " , NULL , 0 ) ;
const char * name = NULL ;
const char * sender = NULL ;
const char * recipient = NULL ;
if ( service = = NULL ) {
return WHY ( " Manifest has no service " ) ;
} else if ( strcasecmp ( service , RHIZOME_SERVICE_FILE ) = = 0 ) {
name = rhizome_manifest_get ( m , " name " , NULL , 0 ) ;
if ( ! name ) return WHY ( " Manifest has no name " ) ;
} else if ( strcasecmp ( service , RHIZOME_SERVICE_MESHMS ) = = 0 ) {
sender = rhizome_manifest_get ( m , " sender " , NULL , 0 ) ;
recipient = rhizome_manifest_get ( m , " recipient " , NULL , 0 ) ;
if ( ! sender ) return WHY ( " Manifest has no sender " ) ;
if ( ! recipient ) return WHY ( " Manifest has no recipient " ) ;
} else {
return WHYF ( " Unsupported service '%s' " , service ) ;
}
2012-04-12 09:00:52 +00:00
char sqlcmd [ 1024 ] ;
2012-05-16 06:26:17 +00:00
char * s = sqlcmd ;
s + = snprintf ( s , & sqlcmd [ sizeof sqlcmd ] - s ,
2012-05-28 09:54:02 +00:00
" SELECT id, manifest, version FROM manifests "
" WHERE filehash = ? "
2012-04-16 02:16:58 +00:00
) ;
2012-05-25 23:02:17 +00:00
if ( checkVersionP & & s < & sqlcmd [ sizeof sqlcmd ] )
2012-05-28 09:54:02 +00:00
s + = snprintf ( s , sqlcmd + sizeof ( sqlcmd ) - s , " AND version = ? " ) ;
2012-05-16 06:26:17 +00:00
if ( s > = & sqlcmd [ sizeof sqlcmd ] )
2012-04-12 09:00:52 +00:00
return WHY ( " SQL command too long " ) ;
int ret = 0 ;
sqlite3_stmt * statement ;
const char * cmdtail ;
2012-06-08 03:43:26 +00:00
if ( debug & DEBUG_RHIZOME ) DEBUGF ( " sql query: %s " , sqlcmd ) ;
2012-04-12 09:00:52 +00:00
if ( sqlite3_prepare_v2 ( rhizome_db , sqlcmd , strlen ( sqlcmd ) + 1 , & statement , & cmdtail ) ! = SQLITE_OK ) {
ret = WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
} else {
2012-05-23 06:34:00 +00:00
char filehash [ RHIZOME_FILEHASH_STRLEN + 1 ] ;
strncpy ( filehash , m - > fileHexHash , sizeof filehash ) ;
str_toupper_inplace ( filehash ) ;
2012-05-23 09:31:07 +00:00
if ( debug & DEBUG_RHIZOME ) DEBUGF ( " filehash= \" %s \" " , filehash ) ;
2012-05-23 06:34:00 +00:00
sqlite3_bind_text ( statement , 1 , filehash , - 1 , SQLITE_STATIC ) ;
2012-05-25 23:02:17 +00:00
if ( checkVersionP )
2012-05-16 06:26:17 +00:00
sqlite3_bind_int64 ( statement , 2 , m - > version ) ;
2012-04-12 09:00:52 +00:00
size_t rows = 0 ;
while ( sqlite3_step ( statement ) = = SQLITE_ROW ) {
+ + rows ;
2012-05-18 09:23:27 +00:00
if ( debug & DEBUG_RHIZOME ) DEBUGF ( " Row %d " , rows ) ;
2012-04-16 02:16:58 +00:00
if ( ! ( sqlite3_column_count ( statement ) = = 3
2012-04-12 09:00:52 +00:00
& & sqlite3_column_type ( statement , 0 ) = = SQLITE_TEXT
& & sqlite3_column_type ( statement , 1 ) = = SQLITE_BLOB
2012-04-16 02:16:58 +00:00
& & sqlite3_column_type ( statement , 2 ) = = SQLITE_INTEGER
2012-04-12 09:00:52 +00:00
) ) {
ret = WHY ( " Incorrect statement columns " ) ;
break ;
}
2012-05-16 06:26:17 +00:00
const char * q_manifestid = ( const char * ) sqlite3_column_text ( statement , 0 ) ;
2012-04-12 09:00:52 +00:00
size_t manifestidsize = sqlite3_column_bytes ( statement , 0 ) ; // must call after sqlite3_column_text()
if ( manifestidsize ! = crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES * 2 ) {
2012-05-16 06:26:17 +00:00
ret = WHYF ( " Malformed manifest.id from query: %s " , q_manifestid ) ;
2012-04-12 09:00:52 +00:00
break ;
}
const char * manifestblob = ( char * ) sqlite3_column_blob ( statement , 1 ) ;
size_t manifestblobsize = sqlite3_column_bytes ( statement , 1 ) ; // must call after sqlite3_column_blob()
2012-05-16 06:26:17 +00:00
long long q_version = sqlite3_column_int64 ( statement , 2 ) ;
2012-05-25 06:08:13 +00:00
rhizome_manifest * blob_m = rhizome_new_manifest ( ) ;
if ( blob_m = = NULL ) {
ret = WHY ( " Out of manifests " ) ;
break ;
2012-05-20 14:39:14 +00:00
}
2012-05-25 23:54:47 +00:00
if ( rhizome_read_manifest_file ( blob_m , manifestblob , manifestblobsize ) = = - 1 ) {
2012-05-25 06:08:13 +00:00
WARNF ( " MANIFESTS row id=%s has invalid manifest blob -- skipped " , q_manifestid ) ;
2012-05-26 00:04:12 +00:00
} else if ( rhizome_manifest_verify ( blob_m ) ) {
2012-05-25 23:54:47 +00:00
WARNF ( " MANIFESTS row id=%s fails verification -- skipped " , q_manifestid ) ;
2012-05-25 06:08:13 +00:00
} else {
const char * blob_service = rhizome_manifest_get ( blob_m , " service " , NULL , 0 ) ;
const char * blob_id = rhizome_manifest_get ( blob_m , " id " , NULL , 0 ) ;
long long blob_version = rhizome_manifest_get_ll ( blob_m , " version " ) ;
const char * blob_filehash = rhizome_manifest_get ( blob_m , " filehash " , NULL , 0 ) ;
long long blob_filesize = rhizome_manifest_get_ll ( blob_m , " filesize " ) ;
if ( debug & DEBUG_RHIZOME )
DEBUGF ( " Consider manifest.service=%s manifest.id=%s manifest.version=%lld " , blob_service , q_manifestid , blob_version ) ;
/* Perform consistency checks, because we're paranoid. */
int inconsistent = 0 ;
if ( blob_id & & strcasecmp ( blob_id , q_manifestid ) ) {
WARNF ( " MANIFESTS row id=%s has inconsistent blob with id=%s -- skipped " , q_manifestid , blob_id ) ;
+ + inconsistent ;
}
2012-05-25 23:02:17 +00:00
if ( checkVersionP & & blob_version ! = q_version ) {
2012-05-25 06:08:13 +00:00
WARNF ( " MANIFESTS row id=%s has inconsistent blob: manifests.version=%lld, blob.version=%lld -- skipped " ,
q_manifestid , q_version , blob_version ) ;
+ + inconsistent ;
}
if ( ! blob_filehash & & strcasecmp ( blob_filehash , m - > fileHexHash ) ) {
WARNF ( " MANIFESTS row id=%s joined to FILES row id=%s has inconsistent blob: blob.filehash=%s -- skipped " ,
q_manifestid , m - > fileHexHash , blob_filehash ) ;
+ + inconsistent ;
}
if ( blob_filesize ! = - 1 & & blob_filesize ! = m - > fileLength ) {
WARNF ( " MANIFESTS row id=%s joined to FILES row id=%s has inconsistent blob: known file size %lld, blob.filesize=%lld -- skipped " ,
2012-05-28 02:29:35 +00:00
q_manifestid , m - > fileHexHash , m - > fileLength , blob_filesize ) ;
2012-05-25 06:08:13 +00:00
+ + inconsistent ;
}
2012-05-25 23:02:17 +00:00
if ( checkVersionP & & q_version ! = m - > version ) {
2012-05-25 06:08:13 +00:00
WARNF ( " SELECT query with version=%lld returned incorrect row: manifests.version=%lld -- skipped " , m - > version , q_version ) ;
+ + inconsistent ;
}
if ( blob_service = = NULL ) {
WARNF ( " MANIFESTS row id=%s has blob with no 'service' -- skipped " , q_manifestid , blob_id ) ;
+ + inconsistent ;
}
if ( ! inconsistent ) {
strbuf b = strbuf_alloca ( 1024 ) ;
if ( strcasecmp ( service , RHIZOME_SERVICE_FILE ) = = 0 ) {
const char * blob_name = rhizome_manifest_get ( blob_m , " name " , NULL , 0 ) ;
if ( blob_name & & ! strcmp ( blob_name , name ) ) {
if ( debug & DEBUG_RHIZOME )
strbuf_sprintf ( b , " name= \" %s \" " , blob_name ) ;
ret = 1 ;
}
} else if ( strcasecmp ( service , RHIZOME_SERVICE_FILE ) = = 0 ) {
const char * blob_sender = rhizome_manifest_get ( blob_m , " sender " , NULL , 0 ) ;
const char * blob_recipient = rhizome_manifest_get ( blob_m , " recipient " , NULL , 0 ) ;
if ( blob_sender & & ! strcasecmp ( blob_sender , sender ) & & blob_recipient & & ! strcasecmp ( blob_recipient , recipient ) ) {
if ( debug & DEBUG_RHIZOME )
strbuf_sprintf ( b , " sender=%s recipient=%s " , blob_sender , blob_recipient ) ;
ret = 1 ;
}
2012-05-20 14:39:14 +00:00
}
2012-05-25 06:08:13 +00:00
if ( ret = = 1 ) {
rhizome_hex_to_bytes ( q_manifestid , blob_m - > cryptoSignPublic , crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES * 2 ) ;
memcpy ( blob_m - > fileHexHash , m - > fileHexHash , RHIZOME_FILEHASH_STRLEN + 1 ) ;
blob_m - > fileHashedP = 1 ;
blob_m - > fileLength = m - > fileLength ;
blob_m - > version = q_version ;
* found = blob_m ;
DEBUGF ( " Found duplicate payload: service=%s%s version=%llu hexhash=%s " ,
blob_service , strbuf_str ( b ) , blob_m - > version , blob_m - > fileHexHash
) ;
break ;
2012-05-20 14:39:14 +00:00
}
}
2012-04-12 09:00:52 +00:00
}
2012-05-25 06:08:13 +00:00
if ( blob_m ) rhizome_manifest_free ( blob_m ) ;
2012-04-12 09:00:52 +00:00
}
}
sqlite3_finalize ( statement ) ;
return ret ;
}
2012-05-02 06:33:09 +00:00
/* Retrieve a manifest from the database, given its manifest ID.
2012-05-02 08:27:35 +00:00
*
* Returns 1 if manifest is found ( if mp ! = NULL then a new manifest struct is allocated , made
* finalisable and * assigned to * mp , caller is responsible for freeing ) .
* Returns 0 if manifest is not found ( * mp is unchanged ) .
* Returns - 1 on error ( * mp is unchanged ) .
2012-05-02 06:33:09 +00:00
*/
2012-05-02 08:27:35 +00:00
int rhizome_retrieve_manifest ( const char * manifestid , rhizome_manifest * * mp )
2012-05-02 06:33:09 +00:00
{
char sqlcmd [ 1024 ] ;
int n = snprintf ( sqlcmd , sizeof ( sqlcmd ) , " SELECT id, manifest, version, inserttime FROM manifests WHERE id = ? " ) ;
if ( n > = sizeof ( sqlcmd ) )
return WHY ( " SQL command too long " ) ;
sqlite3_stmt * statement ;
const char * cmdtail ;
int ret = 0 ;
rhizome_manifest * m = NULL ;
if ( sqlite3_prepare_v2 ( rhizome_db , sqlcmd , strlen ( sqlcmd ) + 1 , & statement , & cmdtail ) ! = SQLITE_OK ) {
sqlite3_finalize ( statement ) ;
ret = WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
} else {
2012-05-23 06:34:00 +00:00
char manifestIdUpper [ RHIZOME_MANIFEST_ID_STRLEN + 1 ] ;
strncpy ( manifestIdUpper , manifestid , sizeof manifestIdUpper ) ;
manifestIdUpper [ RHIZOME_MANIFEST_ID_STRLEN ] = ' \0 ' ;
str_toupper_inplace ( manifestIdUpper ) ;
sqlite3_bind_text ( statement , 1 , manifestIdUpper , - 1 , SQLITE_STATIC ) ;
2012-05-02 06:33:09 +00:00
while ( sqlite3_step ( statement ) = = SQLITE_ROW ) {
if ( ! ( sqlite3_column_count ( statement ) = = 4
& & sqlite3_column_type ( statement , 0 ) = = SQLITE_TEXT
& & sqlite3_column_type ( statement , 1 ) = = SQLITE_BLOB
& & sqlite3_column_type ( statement , 2 ) = = SQLITE_INTEGER
& & sqlite3_column_type ( statement , 3 ) = = SQLITE_INTEGER
) ) {
ret = WHY ( " Incorrect statement column " ) ;
break ;
}
2012-05-25 06:08:13 +00:00
const char * q_manifestid = ( const char * ) sqlite3_column_text ( statement , 0 ) ;
2012-05-02 06:33:09 +00:00
const char * manifestblob = ( char * ) sqlite3_column_blob ( statement , 1 ) ;
size_t manifestblobsize = sqlite3_column_bytes ( statement , 1 ) ; // must call after sqlite3_column_blob()
2012-05-02 08:27:35 +00:00
if ( mp ) {
2012-05-25 06:08:13 +00:00
m = rhizome_new_manifest ( ) ;
2012-05-02 08:27:35 +00:00
if ( m = = NULL ) {
2012-05-25 06:08:13 +00:00
WARNF ( " MANIFESTS row id=%s has invalid manifest blob -- skipped " , q_manifestid ) ;
ret = WHY ( " Out of manifests " ) ;
2012-05-25 23:54:47 +00:00
} else if ( rhizome_read_manifest_file ( m , manifestblob , manifestblobsize ) = = - 1 ) {
2012-05-25 06:08:13 +00:00
WARNF ( " MANIFESTS row id=%s has invalid manifest blob -- skipped " , q_manifestid ) ;
2012-05-02 08:27:35 +00:00
ret = WHY ( " Invalid manifest blob from database " ) ;
} else {
ret = 1 ;
rhizome_hex_to_bytes ( manifestid , m - > cryptoSignPublic , crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES * 2 ) ;
2012-05-17 06:39:57 +00:00
const char * blob_service = rhizome_manifest_get ( m , " service " , NULL , 0 ) ;
if ( blob_service = = NULL )
ret = WHY ( " Manifest is missing 'service' field " ) ;
2012-05-16 06:26:17 +00:00
const char * blob_filehash = rhizome_manifest_get ( m , " filehash " , NULL , 0 ) ;
if ( blob_filehash = = NULL )
2012-05-17 06:39:57 +00:00
ret = WHY ( " Manifest is missing 'filehash' field " ) ;
2012-05-02 08:27:35 +00:00
else {
2012-05-23 06:34:00 +00:00
memcpy ( m - > fileHexHash , blob_filehash , RHIZOME_FILEHASH_STRLEN + 1 ) ;
2012-05-02 08:27:35 +00:00
m - > fileHashedP = 1 ;
}
2012-05-16 06:26:17 +00:00
long long blob_version = rhizome_manifest_get_ll ( m , " version " ) ;
if ( blob_version = = - 1 )
2012-05-17 06:39:57 +00:00
ret = WHY ( " Manifest is missing 'version' field " ) ;
2012-05-02 08:27:35 +00:00
else
2012-05-16 06:26:17 +00:00
m - > version = blob_version ;
2012-05-17 06:39:57 +00:00
long long filesizeq = rhizome_manifest_get_ll ( m , " filesize " ) ;
if ( filesizeq = = - 1 )
ret = WHY ( " Manifest is missing 'filesize' field " ) ;
2012-05-02 08:27:35 +00:00
else
2012-05-17 06:39:57 +00:00
m - > fileLength = filesizeq ;
2012-05-03 02:46:32 +00:00
if ( ret = = 1 ) {
2012-05-17 06:39:57 +00:00
cli_puts ( " service " ) ; cli_delim ( " : " ) ;
cli_puts ( blob_service ) ; cli_delim ( " \n " ) ;
2012-05-03 02:46:32 +00:00
cli_puts ( " manifestid " ) ; cli_delim ( " : " ) ;
2012-05-25 06:08:13 +00:00
cli_puts ( q_manifestid ) ; cli_delim ( " \n " ) ;
2012-05-03 02:46:32 +00:00
cli_puts ( " version " ) ; cli_delim ( " : " ) ;
cli_printf ( " %lld " , ( long long ) sqlite3_column_int64 ( statement , 2 ) ) ; cli_delim ( " \n " ) ;
cli_puts ( " inserttime " ) ; cli_delim ( " : " ) ;
cli_printf ( " %lld " , ( long long ) sqlite3_column_int64 ( statement , 3 ) ) ; cli_delim ( " \n " ) ;
cli_puts ( " filehash " ) ; cli_delim ( " : " ) ;
cli_puts ( m - > fileHexHash ) ; cli_delim ( " \n " ) ;
cli_puts ( " filesize " ) ; cli_delim ( " : " ) ;
cli_printf ( " %lld " , ( long long ) m - > fileLength ) ; cli_delim ( " \n " ) ;
// Could write the manifest blob to the CLI output here, but that would require the output to
// support byte[] fields as well as String fields.
}
2012-05-02 06:33:09 +00:00
}
2012-05-02 08:27:35 +00:00
}
2012-05-02 06:33:09 +00:00
break ;
}
}
sqlite3_finalize ( statement ) ;
2012-05-02 08:27:35 +00:00
if ( mp & & ret = = 1 )
2012-05-02 06:33:09 +00:00
* mp = m ;
return ret ;
}
2012-05-02 08:27:35 +00:00
/* Retrieve a file from the database, given its file hash.
*
* Returns 1 if file is found ( contents are written to filepath if given ) .
* Returns 0 if file is not found .
* Returns - 1 on error .
*/
2012-05-26 04:12:33 +00:00
int rhizome_retrieve_file ( const char * fileid , const char * filepath ,
const unsigned char * key )
2012-05-02 08:27:35 +00:00
{
2012-05-26 04:12:33 +00:00
sqlite3_blob * blob = NULL ;
2012-06-08 03:43:26 +00:00
if ( rhizome_update_file_priority ( fileid ) = = - 1 ) {
WHY ( " Failed to update file priority " ) ;
return 0 ;
}
2012-05-02 08:27:35 +00:00
char sqlcmd [ 1024 ] ;
2012-05-26 04:12:33 +00:00
int n = snprintf ( sqlcmd , sizeof ( sqlcmd ) , " SELECT id, rowid, length FROM files WHERE id = ? AND datavalid != 0 " ) ;
2012-05-02 08:27:35 +00:00
if ( n > = sizeof ( sqlcmd ) )
2012-05-26 03:16:25 +00:00
{ WHY ( " SQL command too long " ) ; return 0 ; }
2012-05-02 08:27:35 +00:00
sqlite3_stmt * statement ;
const char * cmdtail ;
int ret = 0 ;
if ( sqlite3_prepare_v2 ( rhizome_db , sqlcmd , strlen ( sqlcmd ) + 1 , & statement , & cmdtail ) ! = SQLITE_OK ) {
ret = WHY ( sqlite3_errmsg ( rhizome_db ) ) ;
} else {
2012-05-23 06:34:00 +00:00
char fileIdUpper [ RHIZOME_FILEHASH_STRLEN + 1 ] ;
strncpy ( fileIdUpper , fileid , sizeof fileIdUpper ) ;
fileIdUpper [ RHIZOME_FILEHASH_STRLEN ] = ' \0 ' ;
str_toupper_inplace ( fileIdUpper ) ;
sqlite3_bind_text ( statement , 1 , fileIdUpper , - 1 , SQLITE_STATIC ) ;
2012-05-26 02:48:49 +00:00
int stepcode = sqlite3_step ( statement ) ;
if ( stepcode ! = SQLITE_ROW ) {
2012-05-28 09:54:02 +00:00
WHY ( " File not found " ) ;
2012-05-26 02:55:35 +00:00
ret = 0 ; /* no files returned */
2012-05-26 02:48:49 +00:00
} else if ( ! ( sqlite3_column_count ( statement ) = = 3
& & sqlite3_column_type ( statement , 0 ) = = SQLITE_TEXT
2012-05-26 04:12:33 +00:00
& & sqlite3_column_type ( statement , 1 ) = = SQLITE_INTEGER
2012-05-26 02:48:49 +00:00
& & sqlite3_column_type ( statement , 2 ) = = SQLITE_INTEGER
) ) {
2012-05-26 02:55:35 +00:00
WHY ( " Incorrect statement column " ) ;
ret = 0 ; /* no files returned */
2012-05-26 02:48:49 +00:00
} else {
2012-05-02 08:27:35 +00:00
long long length = sqlite3_column_int64 ( statement , 2 ) ;
2012-05-26 04:12:33 +00:00
long long rowid = sqlite3_column_int64 ( statement , 1 ) ;
if ( sqlite3_blob_open ( rhizome_db , " main " , " files " , " data " , rowid ,
0 /* read only */ , & blob ) ! = SQLITE_OK ) {
ret = 0 ;
WHY ( " Could not open blob for reading " ) ;
}
cli_puts ( " filehash " ) ; cli_delim ( " : " ) ;
cli_puts ( ( const char * ) sqlite3_column_text ( statement , 0 ) ) ; cli_delim ( " \n " ) ;
cli_puts ( " filesize " ) ; cli_delim ( " : " ) ;
cli_printf ( " %lld " , length ) ; cli_delim ( " \n " ) ;
ret = 1 ;
if ( filepath & & filepath [ 0 ] ) {
int fd = open ( filepath , O_WRONLY | O_CREAT | O_TRUNC , 0775 ) ;
if ( fd = = - 1 ) {
WHY_perror ( " open " ) ;
ret = WHYF ( " Cannot open %s for write/create " , filepath ) ;
} else {
/* read from blob and write to disk, decrypting if necessary as
we go .
Each 4 KB block of data has a nonce which is fed with the key
into crypto_stream_xsalsa20 ( ) . The nonce is the file address
divided by 4 KB . This approach is used as it allows us to append
to files easily , without having to get the XOR stream for the whole
file , and without the cipher on existing bytes having to change .
Both of these are important properties for journal bundles , such as
will be used by MeshMS . For non - journal bundles where it is important
that changing the payload changes the encryption key ( so that the XOR
between any two versions of the payload cannot be easily obtained ) .
We will do this by having journal manifests identified , causing the
key to be locked , rather than based on the version number .
But anyway , we are supplied with the key here , so all we need to do
is do the block counting and call crypto_stream_xsalsa20 ( ) .
*/
long long offset ;
unsigned char nonce [ crypto_stream_xsalsa20_NONCEBYTES ] ;
bzero ( nonce , crypto_stream_xsalsa20_NONCEBYTES ) ;
unsigned char buffer [ RHIZOME_CRYPT_PAGE_SIZE ] ;
for ( offset = 0 ; offset < length ; offset + = RHIZOME_CRYPT_PAGE_SIZE )
{
long long count = length - offset ;
if ( count > RHIZOME_CRYPT_PAGE_SIZE ) count = RHIZOME_CRYPT_PAGE_SIZE ;
if ( sqlite3_blob_read ( blob , & buffer [ 0 ] , count , offset ) ! = SQLITE_OK ) {
ret = 0 ;
WHYF ( " Error reading %lld bytes of data from blob at offset 0x%llx " ,
count , offset ) ;
WHYF ( " sqlite says: %s " , sqlite3_errmsg ( rhizome_db ) ) ;
}
if ( key ) {
/* calculate block nonce */
int i ; for ( i = 0 ; i < 8 ; i + + ) nonce [ i ] = ( offset > > ( i * 8 ) ) & 0xff ;
crypto_stream_xsalsa20_xor ( & buffer [ 0 ] , & buffer [ 0 ] , count ,
nonce , key ) ;
}
if ( write ( fd , buffer , count ) ! = count ) {
ret = 0 ;
WHY ( " Failed to write data to file " ) ;
}
}
sqlite3_blob_close ( blob ) ; blob = NULL ;
}
if ( fd ! = - 1 & & close ( fd ) = = - 1 ) {
WHY_perror ( " close " ) ;
ret = 0 ; WHYF ( " Error flushing to %s " , filepath ) ;
2012-05-02 08:27:35 +00:00
}
}
2012-05-25 10:12:45 +00:00
}
2012-05-02 08:27:35 +00:00
}
2012-05-26 04:12:33 +00:00
if ( blob ) sqlite3_blob_close ( blob ) ;
2012-05-02 08:27:35 +00:00
sqlite3_finalize ( statement ) ;
return ret ;
}
2012-05-26 02:48:49 +00:00