diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | pgfuse.c | 39 | ||||
-rw-r--r-- | pgsql.c | 102 | ||||
-rw-r--r-- | schema.sql | 4 | ||||
-rw-r--r-- | testpgsql.c | 4 |
5 files changed, 119 insertions, 34 deletions
@@ -64,6 +64,10 @@ test: pgfuse testfsync testpgsql -rmdir mnt/dir/dir2/bfile # test fdatasync and fsync ./testfsync + # show times of dirs, files and symlinks + -stat mnt/dir/dir2/afile + -stat mnt/dir/dir3 + -stat mnt/dir/dir2/clink # show filesystem stats (statvfs) df -k mnt df -i mnt @@ -59,6 +59,8 @@ typedef struct PgFuseData { int read_only; /* whether the mount point is read-only */ } PgFuseData; +/* --- helper functions --- */ + static struct timespec now( void ) { int res; @@ -79,6 +81,8 @@ static struct timespec now( void ) return s; } +/* --- implementation of FUSE hooks --- */ + static void *pgfuse_init( struct fuse_conn_info *conn ) { PgFuseData *data = (PgFuseData *)fuse_get_context( )->private_data; @@ -179,6 +183,9 @@ static int pgfuse_getattr( const char *path, struct stat *stbuf ) /* set rights to the user running 'pgfuse' */ stbuf->st_uid = meta.uid; stbuf->st_gid = meta.gid; + stbuf->st_atime = meta.atime.tv_sec; + stbuf->st_mtime = meta.mtime.tv_sec; + stbuf->st_ctime = meta.ctime.tv_sec; return 0; } @@ -292,8 +299,8 @@ static int pgfuse_create( const char *path, mode_t mode, struct fuse_file_info * meta.uid = geteuid( ); meta.gid = getegid( ); meta.ctime = now( ); - meta.atime = now( ); - meta.mtime = now( ); + meta.mtime = meta.ctime; + meta.atime = meta.ctime; res = psql_create_file( data->conn, parent_id, path, new_file, meta ); if( res < 0 ) { @@ -506,8 +513,8 @@ static int pgfuse_mkdir( const char *path, mode_t mode ) meta.uid = geteuid( ); meta.gid = getegid( ); meta.ctime = now( ); - meta.atime = now( ); - meta.mtime = now( ); + meta.mtime = meta.ctime; + meta.atime = meta.ctime; res = psql_create_dir( data->conn, parent_id, path, new_dir, meta ); @@ -955,6 +962,9 @@ static int pgfuse_symlink( const char *from, const char *to ) /* TODO: use FUSE context */ meta.uid = geteuid( ); meta.gid = getegid( ); + meta.ctime = now( ); + meta.mtime = meta.ctime; + meta.atime = meta.ctime; res = psql_create_file( data->conn, parent_id, to, symlink, meta ); if( res < 0 ) { @@ -1185,6 +1195,7 @@ int main( int argc, char *argv[] ) struct fuse_args args = FUSE_ARGS_INIT( argc, argv ); PgFuse pgfuse; PgFuseData userdata; + const char *value; memset( &pgfuse, 0, sizeof( pgfuse ) ); @@ -1221,6 +1232,26 @@ int main( int argc, char *argv[] ) PQfinish( conn ); exit( EXIT_FAILURE ); } + + /* test storage of timestamps (expecting uint64 as it is the + * standard for PostgreSQL 8.4 or newer). Otherwise bail out + * currently.. + */ + value = PQparameterStatus( conn, "integer_datetimes" ); + if( value == NULL ) { + fprintf( stderr, "PQ param integer_datetimes not available?\n" + "You use a too old version of PostgreSQL..can't continue.\n" ); + PQfinish( conn ); + return 1; + } + + if( strcmp( value, "on" ) != 0 ) { + fprintf( stderr, "Expecting UINT64 for timestamps, not doubles. You may use an old version of PostgreSQL (<8.4)\n" + "or PostgreSQL has been compiled with the deprecated compile option '--disable-integer-datetimes'\n" ); + PQfinish( conn ); + return 1; + } + PQfinish( conn ); openlog( basename( argv[0] ), LOG_PID, LOG_USER ); @@ -23,19 +23,46 @@ #include <syslog.h> /* for ERR_XXX */ #include <errno.h> /* for ENOENT and friends */ #include <arpa/inet.h> /* for htonl, ntohl */ +#include <stdint.h> /* for uint64_t */ +#include <endian.h> /* for be64toh (GNU/BSD-ism, but easy to port if necessary) */ + +/* --- helper functions --- */ + +/* January 1, 2000, 00:00:00 UTC (in Unix epoch seconds) */ +#define POSTGRES_EPOCH_DATE 946684800 + +static uint64_t convert_to_timestamp( struct timespec t ) +{ + return htobe64( ( (uint64_t)t.tv_sec - POSTGRES_EPOCH_DATE ) * 1000000 + t.tv_nsec / 1000 ); +} + +static struct timespec convert_from_timestamp( uint64_t raw ) +{ + uint64_t t; + struct timespec ts; + + t = be64toh( raw ); + + ts.tv_sec = POSTGRES_EPOCH_DATE + t / 1000000; + ts.tv_nsec = ( t % 1000000 ) * 1000; + + return ts; +} + +/* --- postgresql implementation --- */ int psql_get_meta( PGconn *conn, const char *path, PgMeta *meta ) { PGresult *res; int idx; - char *iptr; + char *data; int id; const char *values[1] = { path }; int lengths[1] = { strlen( path ) }; int binary[1] = { 1 }; - res = PQexecParams( conn, "SELECT id, size, mode, uid, gid FROM dir WHERE path = $1::varchar", + res = PQexecParams( conn, "SELECT id, size, mode, uid, gid, ctime, mtime, atime FROM dir WHERE path = $1::varchar", 1, NULL, values, lengths, binary, 1 ); if( PQresultStatus( res ) != PGRES_TUPLES_OK ) { @@ -56,24 +83,36 @@ int psql_get_meta( PGconn *conn, const char *path, PgMeta *meta ) } idx = PQfnumber( res, "id" ); - iptr = PQgetvalue( res, 0, idx ); - id = ntohl( *( (uint32_t *)iptr ) ); + data = PQgetvalue( res, 0, idx ); + id = ntohl( *( (uint32_t *)data ) ); idx = PQfnumber( res, "size" ); - iptr = PQgetvalue( res, 0, idx ); - meta->size = ntohl( *( (uint32_t *)iptr ) ); + data = PQgetvalue( res, 0, idx ); + meta->size = ntohl( *( (uint32_t *)data ) ); idx = PQfnumber( res, "mode" ); - iptr = PQgetvalue( res, 0, idx ); - meta->mode = ntohl( *( (uint32_t *)iptr ) ); + data = PQgetvalue( res, 0, idx ); + meta->mode = ntohl( *( (uint32_t *)data ) ); idx = PQfnumber( res, "uid" ); - iptr = PQgetvalue( res, 0, idx ); - meta->uid = ntohl( *( (uint32_t *)iptr ) ); + data = PQgetvalue( res, 0, idx ); + meta->uid = ntohl( *( (uint32_t *)data ) ); idx = PQfnumber( res, "gid" ); - iptr = PQgetvalue( res, 0, idx ); - meta->gid = ntohl( *( (uint32_t *)iptr ) ); + data = PQgetvalue( res, 0, idx ); + meta->gid = ntohl( *( (uint32_t *)data ) ); + + idx = PQfnumber( res, "ctime" ); + data = PQgetvalue( res, 0, idx ); + meta->ctime = convert_from_timestamp( *( (uint64_t *)data ) ); + + idx = PQfnumber( res, "mtime" ); + data = PQgetvalue( res, 0, idx ); + meta->mtime = convert_from_timestamp( *( (uint64_t *)data ) ); + + idx = PQfnumber( res, "atime" ); + data = PQgetvalue( res, 0, idx ); + meta->atime = convert_from_timestamp( *( (uint64_t *)data ) ); PQclear( res ); @@ -87,13 +126,16 @@ int psql_write_meta( PGconn *conn, const int id, const char *path, PgMeta meta ) int param3 = htonl( meta.mode ); int param4 = htonl( meta.uid ); int param5 = htonl( meta.gid ); - const char *values[5] = { (char *)¶m1, (char *)¶m2, (char *)¶m3, (char *)¶m4, (char *)¶m5 }; - int lengths[5] = { sizeof( param1 ), sizeof( param2 ), sizeof( param3 ), sizeof( param4 ), sizeof( param5 ) }; - int binary[5] = { 1, 1, 1, 1, 1 }; + uint64_t param6 = convert_to_timestamp( meta.ctime ); + uint64_t param7 = convert_to_timestamp( meta.mtime ); + uint64_t param8 = convert_to_timestamp( meta.atime ); + const char *values[8] = { (const char *)¶m1, (const char *)¶m2, (const char *)¶m3, (const char *)¶m4, (const char *)¶m5, (const char *)¶m6, (const char *)¶m7, (const char *)¶m8 }; + int lengths[8] = { sizeof( param1 ), sizeof( param2 ), sizeof( param3 ), sizeof( param4 ), sizeof( param5 ), sizeof( param6 ), sizeof( param7 ), sizeof( param8 ) }; + int binary[8] = { 1, 1, 1, 1, 1, 1, 1, 1 }; PGresult *res; - res = PQexecParams( conn, "UPDATE dir SET size=$2::int4, mode=$3::int4, uid=$4::int4, gid=$5::int4 WHERE id=$1::int4", - 5, NULL, values, lengths, binary, 1 ); + res = PQexecParams( conn, "UPDATE dir SET size=$2::int4, mode=$3::int4, uid=$4::int4, gid=$5::int4, ctime=$6::timestamp, mtime=$7::timestamp, atime=$8::timestamp WHERE id=$1::int4", + 8, NULL, values, lengths, binary, 1 ); if( PQresultStatus( res ) != PGRES_COMMAND_OK ) { syslog( LOG_ERR, "Error in psql_write_meta for file '%s': %s", path, PQerrorMessage( conn ) ); @@ -113,13 +155,16 @@ int psql_create_file( PGconn *conn, const int parent_id, const char *path, const int param3 = htonl( meta.mode ); int param4 = htonl( meta.uid ); int param5 = htonl( meta.gid ); - const char *values[7] = { (const char *)¶m1, new_file, path, (const char *)¶m2, (const char *)¶m3, (const char *)¶m4, (const char *)¶m5 }; - int lengths[7] = { sizeof( param1 ), strlen( new_file ), strlen( path ), sizeof( param2 ), sizeof( param3 ), sizeof( param4 ), sizeof( param5 ) }; - int binary[7] = { 1, 0, 0, 1, 1, 1, 1 }; + uint64_t param6 = convert_to_timestamp( meta.ctime ); + uint64_t param7 = convert_to_timestamp( meta.mtime ); + uint64_t param8 = convert_to_timestamp( meta.atime ); + const char *values[10] = { (const char *)¶m1, new_file, path, (const char *)¶m2, (const char *)¶m3, (const char *)¶m4, (const char *)¶m5, (const char *)¶m6, (const char *)¶m7, (const char *)¶m8 }; + int lengths[10] = { sizeof( param1 ), strlen( new_file ), strlen( path ), sizeof( param2 ), sizeof( param3 ), sizeof( param4 ), sizeof( param5 ), sizeof( param6 ), sizeof( param7 ), sizeof( param8 ) }; + int binary[10] = { 1, 0, 0, 1, 1, 1, 1, 1, 1, 1 }; PGresult *res; - res = PQexecParams( conn, "INSERT INTO dir( parent_id, name, path, size, mode, uid, gid ) VALUES ($1::int4, $2::varchar, $3::varchar, $4::int4, $5::int4, $6::int4, $7::int4 )", - 7, NULL, values, lengths, binary, 1 ); + res = PQexecParams( conn, "INSERT INTO dir( parent_id, name, path, size, mode, uid, gid, ctime, mtime, atime ) VALUES ($1::int4, $2::varchar, $3::varchar, $4::int4, $5::int4, $6::int4, $7::int4, $8::timestamp, $9::timestamp, $10::timestamp )", + 10, NULL, values, lengths, binary, 1 ); if( PQresultStatus( res ) != PGRES_COMMAND_OK ) { syslog( LOG_ERR, "Error in psql_create_file for path '%s': %s", @@ -206,13 +251,16 @@ int psql_create_dir( PGconn *conn, const int parent_id, const char *path, const int param2 = htonl( meta.mode ); int param3 = htonl( meta.uid ); int param4 = htonl( meta.gid ); - const char *values[6] = { (char *)¶m1, new_dir, path, (char *)¶m2, (char *)¶m3, (char *)¶m4 }; - int lengths[6] = { sizeof( param1 ), strlen( new_dir ), strlen( path ), sizeof( param2 ), sizeof( param3 ), sizeof( param4 ) }; - int binary[6] = { 1, 0, 0, 1, 1, 1 }; + uint64_t param5 = convert_to_timestamp( meta.ctime ); + uint64_t param6 = convert_to_timestamp( meta.mtime ); + uint64_t param7 = convert_to_timestamp( meta.atime ); + const char *values[9] = { (const char *)¶m1, new_dir, path, (const char *)¶m2, (const char *)¶m3, (const char *)¶m4, (const char *)¶m5, (const char *)¶m6, (const char *)¶m7 }; + int lengths[9] = { sizeof( param1 ), strlen( new_dir ), strlen( path ), sizeof( param2 ), sizeof( param3 ), sizeof( param4 ), sizeof( param5 ), sizeof( param6 ), sizeof( param7 ) }; + int binary[9] = { 1, 0, 0, 1, 1, 1, 1, 1, 1 }; PGresult *res; - res = PQexecParams( conn, "INSERT INTO dir( parent_id, name, path, mode, uid, gid ) VALUES ($1::int4, $2::varchar, $3::varchar, $4::int4, $5::int4, $6::int4 )", - 6, NULL, values, lengths, binary, 1 ); + res = PQexecParams( conn, "INSERT INTO dir( parent_id, name, path, mode, uid, gid, ctime, mtime, atime ) VALUES ($1::int4, $2::varchar, $3::varchar, $4::int4, $5::int4, $6::int4, $7::timestamp, $8::timestamp, $9::timestamp )", + 9, NULL, values, lengths, binary, 1 ); if( PQresultStatus( res ) != PGRES_COMMAND_OK ) { syslog( LOG_ERR, "Error in psql_create_dir for path '%s': %s", path, PQerrorMessage( conn ) ); @@ -47,5 +47,5 @@ CREATE OR REPLACE RULE "dir_remove" AS ON -- self-referencing anchor for root directory -- 16895 = S_IFDIR and 0777 permissions -- TODO: should be done from outside, see note above -INSERT INTO dir( id, parent_id, name, path, size, mode, uid, gid ) - VALUES( 0, 0, '/', '/', 0, 16895, 0, 0 ); +INSERT INTO dir( id, parent_id, name, path, size, mode, uid, gid, ctime, mtime, atime ) + VALUES( 0, 0, '/', '/', 0, 16895, 0, 0, NOW( ), NOW( ), NOW( ) ); diff --git a/testpgsql.c b/testpgsql.c index 3efa383..b4e943f 100644 --- a/testpgsql.c +++ b/testpgsql.c @@ -22,7 +22,7 @@ #include <stdbool.h> /* for bool */ #include <stdint.h> /* for uint64_t */ #include <endian.h> /* for be64toh (GNU/BSD-ism, but easy to port if necessary) */ -#include <sys/time.h> /* for struct timespec */ +#include <sys/time.h> /* for struct timespec, gettimeofday */ /* January 1, 2000, 00:00:00 UTC (in Unix epoch seconds) */ #define POSTGRES_EPOCH_DATE 946684800 @@ -135,6 +135,8 @@ int main( int argc, char *argv[] ) time_select.tv_sec = POSTGRES_EPOCH_DATE + t_select / 1000000; time_select.tv_nsec = ( t_select % 1000000 ) * 1000; + + now = get_now( ); printf( "now passed as param: %lu.%lu, now from database: %lu.%lu, now computed: %lu.%lu\n", time_select.tv_sec, time_select.tv_nsec, time_db.tv_sec, time_db.tv_nsec, now.tv_sec, now.tv_nsec ); |