/** * return(last insert row inode) */ tagsistant_inode tagsistant_last_insert_id(dbi_conn conn) { return(dbi_conn_sequence_last(conn, NULL)); #if 0 // -------- alternative version ----------------------------------------------- tagsistant_inode inode = 0; switch (tagsistant.sql_database_driver) { case TAGSISTANT_DBI_SQLITE_BACKEND: tagsistant_query("SELECT last_insert_rowid() ", conn, tagsistant_return_integer, &inode); break; case TAGSISTANT_DBI_MYSQL_BACKEND: tagsistant_query("SELECT last_insert_id() ", conn, tagsistant_return_integer, &inode); break; } return (inode); #endif }
/** * close() equivalent [first part, second is tagsistant_release()] * * @param path the path to be open()ed * @param fi struct fuse_file_info holding open() flags * @return(0 on success, -errno otherwise) */ int tagsistant_flush(const char *path, struct fuse_file_info *fi) { (void) fi; int res = 0, tagsistant_errno = 0, do_deduplicate = 0; gchar *deduplicate = NULL; TAGSISTANT_START("FLUSH on %s", path); // build querytree tagsistant_querytree *qtree = tagsistant_querytree_new(path, 0, 0, 1, 1); // -- malformed -- if (QTREE_IS_MALFORMED(qtree)) TAGSISTANT_ABORT_OPERATION(ENOENT); if (qtree->full_archive_path) { tagsistant_query( "select 1 from objects where objectname = '%s' and checksum = ''", qtree->dbi, tagsistant_return_integer, &do_deduplicate, qtree->object_path); if (do_deduplicate) { dbg('2', LOG_INFO, "Deduplicating %s", path); deduplicate = g_strdup(path); } else { dbg('2', LOG_INFO, "Skipping deduplication for %s", path); } } if (fi->fh) { dbg('F', LOG_INFO, "Uncaching %" PRIu64 " = open(%s)", fi->fh, path); close(fi->fh); fi->fh = 0; } TAGSISTANT_EXIT_OPERATION: if ( res == -1 ) { TAGSISTANT_STOP_ERROR("FLUSH on %s (%s) (%s): %d %d: %s", path, qtree->full_archive_path, tagsistant_querytree_type(qtree), res, tagsistant_errno, strerror(tagsistant_errno)); tagsistant_querytree_destroy(qtree, TAGSISTANT_ROLLBACK_TRANSACTION); if (do_deduplicate) tagsistant_deduplicate(deduplicate); return (-tagsistant_errno); } else { TAGSISTANT_STOP_OK("FLUSH on %s (%s): OK", path, tagsistant_querytree_type(qtree)); tagsistant_querytree_destroy(qtree, TAGSISTANT_COMMIT_TRANSACTION); if (do_deduplicate) tagsistant_deduplicate(deduplicate); return (0); } }
/** * unlink equivalent * * @param path the path to be unlinked (deleted) * @return(0 on success, -errno otherwise) */ int tagsistant_unlink(const char *path) { int res = 0, tagsistant_errno = 0; gboolean dispose = TRUE; gchar *unlink_path = NULL; TAGSISTANT_START(OPS_IN "UNLINK on %s", path); // build querytree tagsistant_querytree *qtree = tagsistant_querytree_new(path, 0, 1, 1, 0); // -- malformed -- if (QTREE_IS_MALFORMED(qtree)) TAGSISTANT_ABORT_OPERATION(ENOENT); // -- objects on disk -- if (QTREE_IS_STORE(qtree)) { tagsistant_querytree_check_tagging_consistency(qtree); if (QTREE_IS_TAGGABLE(qtree)) { if (is_all_path(qtree->full_path)) { tagsistant_query( "delete from objects where inode = %d", qtree->dbi, NULL, NULL, qtree->inode); tagsistant_query( "delete from tagging where inode = %d", qtree->dbi, NULL, NULL, qtree->inode); } else { /* * if object is pointed by a tags/ query, then untag it * from the tags included in the query path... */ tagsistant_querytree_traverse(qtree, tagsistant_sql_untag_object, qtree->inode); /* * ...then check if it's tagged elsewhere... * ...if still tagged, then avoid real unlink(): the object must survive! * ...otherwise we can delete it from the objects table */ dispose = tagsistant_dispose_object_if_untagged(qtree); } #if TAGSISTANT_ENABLE_AND_SET_CACHE /* * invalidate the and_set cache */ tagsistant_invalidate_and_set_cache_entries(qtree); #endif /* * clean the RDS library */ tagsistant_delete_rds_involved(qtree); } // unlink the object on disk if (dispose) { unlink_path = qtree->full_archive_path; res = unlink(unlink_path); tagsistant_errno = errno; } } else // -- alias -- if (QTREE_IS_ALIAS(qtree)) { tagsistant_sql_alias_delete(qtree->dbi, qtree->alias); } // -- tags -- // -- stats -- // -- relations -- // -- archive -- else TAGSISTANT_ABORT_OPERATION(EROFS); TAGSISTANT_EXIT_OPERATION: if ( res is -1 ) { TAGSISTANT_STOP_ERROR(OPS_OUT "UNLINK on %s (%s) (%s): %d %d: %s", path, unlink_path, tagsistant_querytree_type(qtree), res, tagsistant_errno, strerror(tagsistant_errno)); tagsistant_querytree_destroy(qtree, TAGSISTANT_ROLLBACK_TRANSACTION); return (-tagsistant_errno); } else { TAGSISTANT_STOP_OK(OPS_OUT "UNLINK on %s (%s): OK", path, tagsistant_querytree_type(qtree)); tagsistant_querytree_destroy(qtree, TAGSISTANT_COMMIT_TRANSACTION); return (0); } }
void tagsistant_wal_sync() { /* * compile the WAL pattern regex */ if (!wal_pattern) wal_pattern = g_regex_new("([^:]+): (.*)", G_REGEX_EXTENDED|G_REGEX_CASELESS, 0, NULL); /* * compute the wal directory path */ gchar *wal_dir = g_strdup_printf("%s/wal", tagsistant.repository); if (!wal_dir) { dbg('s', LOG_ERR, "WAL: error restoring the WAL"); exit (1); } /* * open a DB connection */ dbi_conn dbi = tagsistant_db_connection(TAGSISTANT_START_TRANSACTION); gboolean commit = FALSE; /* * load the last timestamp inserted */ gchar *last_tstamp = NULL; tagsistant_query( "select value from status where state = 'wal_timestamp'", dbi, tagsistant_return_string, &last_tstamp); if (!last_tstamp) { /* * check if this is an empty filesystem */ int entries = 0; tagsistant_query( "select sum(entries) as entries from (" "select count(*) as entries from objects " "union all " "select count(*) as entries from tags " ")", dbi, tagsistant_return_integer, &entries); if (entries) { dbg('s', LOG_ERR, "WAL: error loading last timestamp, can't proceed"); exit (1); } else { dbg('s', LOG_INFO, "WAL: skipping sync on empty repository"); tagsistant_rollback_transaction(dbi); tagsistant_db_connection_release(dbi, 1); g_free(wal_dir); return; } } /* * open the WAL directory and scan its files */ GError *error = NULL; GDir *dir = g_dir_open(wal_dir, 0, &error); if (dir) { const gchar *entry; while (1) { entry = g_dir_read_name(dir); if (!entry) { commit = TRUE; break; } if (!tagsistant_wal_apply_log(dbi, entry, last_tstamp)) break; } g_dir_close(dir); } else { dbg('s', LOG_ERR, "WAL: error opening directory: %s", error->message); g_error_free(error); } /* * on positive sync, commit the transaction, otherwise roll back */ if (commit) tagsistant_commit_transaction(dbi); else tagsistant_rollback_transaction(dbi); tagsistant_db_connection_release(dbi, 1); g_free(wal_dir); /* * if unable to sync the WAL, exit to avoid mounting a compromised database */ if (!commit) { dbg('s', LOG_ERR, "WAL: error merging write-ahead logs into DB, can't mount a compromised repository"); exit (1); } }
/** * Create DB schema */ void tagsistant_create_schema() { dbi_conn dbi = tagsistant_db_connection(TAGSISTANT_START_TRANSACTION); gchar *current_schema_version = NULL; // create database schema switch (tagsistant.sql_database_driver) { case TAGSISTANT_DBI_SQLITE_BACKEND: /* * Create schema table and check current schema compatiblity */ tagsistant_query( "create table if not exists schema_version (version varchar(32))", dbi, NULL, NULL); tagsistant_query( "select version from schema_version", dbi, tagsistant_return_string, ¤t_schema_version); if (current_schema_version && g_strcmp0(TAGSISTANT_SCHEMA_VERSION, current_schema_version) != 0) { dbg('s', LOG_ERR, "Required schema version %s differs from current schema version %s", TAGSISTANT_SCHEMA_VERSION, current_schema_version); exit(1); } /* * Tags table */ tagsistant_query( "create table if not exists tags (" "tag_id integer primary key autoincrement not null, " "tagname varchar(65) not null, " "key varchar(65) not null default '', " "value varchar(65) not null default '', " "constraint Tag_key unique (tagname, key, value))", dbi, NULL, NULL); /* * Objects table */ tagsistant_query( "create table if not exists objects (" "inode integer not null primary key autoincrement, " "objectname text(255) not null, " "last_autotag timestamp not null default 0, " "checksum text(40) not null default '', " "symlink text(1024) not null default '')", dbi, NULL, NULL); /* * Tagging table */ tagsistant_query( "create table if not exists tagging (" "inode integer not null, " "tag_id integer not null, " "constraint Tagging_key unique (inode, tag_id))", dbi, NULL, NULL); /* * Relations table */ tagsistant_query( "create table if not exists relations (" "relation_id integer primary key autoincrement not null, " "tag1_id integer not null, " "relation varchar not null, " "tag2_id integer not null)", dbi, NULL, NULL); /* * Aliases table */ tagsistant_query( "create table if not exists aliases (" "alias varchar(65) primary key not null, " "query varchar(%d) not null)", dbi, NULL, NULL, TAGSISTANT_ALIAS_MAX_LENGTH); /* * RDS table */ tagsistant_query( "create temporary table if not exists rds (" "id varchar(32) not null, " "reasoned integer not null, " "inode integer not null, " "objectname text(255) not null, " "tagset text not null, " "creation datetime not null default CURRENT_DATE)", dbi, NULL, NULL); tagsistant_query( "create table if not exists status (" "state varchar(16) primary key not null, " "value varchar(256) not null)", dbi, NULL, NULL); /* * Index declarations */ tagsistant_query("create index if not exists relations_index on relations (tag1_id, tag2_id)", dbi, NULL, NULL); tagsistant_query("create index if not exists objectname_index on objects (objectname)", dbi, NULL, NULL); tagsistant_query("create index if not exists symlink_index on objects (symlink, inode)", dbi, NULL, NULL); tagsistant_query("create index if not exists checksum_index on objects (checksum, inode)", dbi, NULL, NULL); tagsistant_query("create index if not exists relations_type_index on relations (relation)", dbi, NULL, NULL); tagsistant_query("create index if not exists aliases_index on aliases (alias)", dbi, NULL, NULL); tagsistant_query("create index if not exists rds_index1 on rds (id, reasoned, objectname, inode)", dbi, NULL, NULL); tagsistant_query("create index if not exists rds_index2 on rds (id, reasoned, inode, objectname)", dbi, NULL, NULL); tagsistant_query("delete from schema_version", dbi, NULL, NULL); tagsistant_query("insert into schema_version (version) values (\"%s\")", dbi, NULL, NULL, TAGSISTANT_SCHEMA_VERSION); break; case TAGSISTANT_DBI_MYSQL_BACKEND: /* * Create schema table and check current schema compatiblity */ tagsistant_query( "create table if not exists schema_version (version varchar(32))", dbi, NULL, NULL); tagsistant_query( "select version from schema_version", dbi, tagsistant_return_string, ¤t_schema_version); if (current_schema_version && g_strcmp0(TAGSISTANT_SCHEMA_VERSION, current_schema_version) != 0) { dbg('s', LOG_ERR, "Required schema version %s differs from current schema version %s", TAGSISTANT_SCHEMA_VERSION, current_schema_version); exit(1); } /* * Tags table */ tagsistant_query( "create table if not exists tags (" "tag_id integer primary key auto_increment not null, " "tagname varchar(65) not null, " "`key` varchar(65) not null, " "value varchar(65) not null, " "constraint Tag_key unique `key` (tagname, `key`, value))", dbi, NULL, NULL); /* * Objects table */ tagsistant_query( "create table if not exists objects (" "inode integer not null primary key auto_increment, " "objectname varchar(255) not null, " "last_autotag timestamp not null default 0, " "checksum varchar(40) not null default '', " "symlink varchar(1024) not null default '')", dbi, NULL, NULL); /* * Tagging table */ tagsistant_query( "create table if not exists tagging (" "inode integer not null, " "tag_id integer not null, " "constraint Tagging_key unique key (inode, tag_id))", dbi, NULL, NULL); /* * Relations table */ tagsistant_query( "create table if not exists relations (" "relation_id integer primary key auto_increment not null, " "tag1_id integer not null, " "relation varchar(32) not null, " "tag2_id integer not null)", dbi, NULL, NULL); /* * Aliases table */ tagsistant_query( "create table if not exists aliases (" "alias varchar(65) primary key not null, " "query varchar(%d) not null)", dbi, NULL, NULL, TAGSISTANT_ALIAS_MAX_LENGTH); /* * RDS table */ tagsistant_query( "create temporary table if not exists rds (" "id varchar(32) not null, " "reasoned integer not null, " "inode integer not null, " "objectname text(255) not null, " "tagset text not null, " "creation datetime not null) ENGINE = MEMORY", dbi, NULL, NULL); tagsistant_query( "create table if not exists status (" "state varchar(16) primary key not null, " "value varchar(256) not null)", dbi, NULL, NULL); /* * Index declarations */ tagsistant_query("create index relations_index on relations (tag1_id, tag2_id)", dbi, NULL, NULL); tagsistant_query("create index objectname_index on objects (objectname)", dbi, NULL, NULL); tagsistant_query("create index symlink_index on objects (symlink, inode)", dbi, NULL, NULL); tagsistant_query("create index checksum_index on objects (checksum, inode)", dbi, NULL, NULL); tagsistant_query("create index relations_type_index on relations (relation)", dbi, NULL, NULL); tagsistant_query("create index aliases_index on aliases (alias)", dbi, NULL, NULL); tagsistant_query("create index rds_index1 on rds (id, reasoned, objectname, inode)", dbi, NULL, NULL); tagsistant_query("create index rds_index2 on rds (id, reasoned, inode, objectname)", dbi, NULL, NULL); /* * Schema version update */ tagsistant_query("delete from schema_version", dbi, NULL, NULL); tagsistant_query("insert into schema_version (version) values (\"%s\")", dbi, NULL, NULL, TAGSISTANT_SCHEMA_VERSION); break; default: break; } tagsistant_commit_transaction(dbi); tagsistant_db_connection_release(dbi, 1); }
/** * Parse command line options, create connection object, * start the connection and finally create database schema * * @return DBI connection handle */ dbi_conn *tagsistant_db_connection(int start_transaction) { /* DBI connection handler used by subsequent calls to dbi_* functions */ dbi_conn dbi = NULL; if (start_transaction) { g_rw_lock_writer_lock(&(tagsistant_query_rwlock)); } else { g_rw_lock_reader_lock(&(tagsistant_query_rwlock)); } /* lock the pool */ g_mutex_lock(&tagsistant_connection_pool_lock); GList *pool = tagsistant_connection_pool; while (pool) { dbi = (dbi_conn) pool->data; /* check if the connection is still alive */ if (!dbi_conn_ping(dbi) && dbi_conn_connect(dbi) < 0) { dbi_conn_close(dbi); tagsistant_connection_pool = g_list_delete_link(tagsistant_connection_pool, pool); connections--; } else { tagsistant_connection_pool = g_list_remove_link(tagsistant_connection_pool, pool); g_list_free_1(pool); break; } pool = pool->next; } /* * unlock the pool mutex only if the backend is not SQLite */ g_mutex_unlock(&tagsistant_connection_pool_lock); if (!dbi) { // initialize DBI drivers if (TAGSISTANT_DBI_MYSQL_BACKEND == dboptions.backend) { if (!tagsistant_driver_is_available("mysql")) { fprintf(stderr, "MySQL driver not installed\n"); dbg('s', LOG_ERR, "MySQL driver not installed"); exit (1); } // unlucky, MySQL does not provide INTERSECT operator tagsistant.sql_backend_have_intersect = 0; // create connection #if TAGSISTANT_REENTRANT_DBI dbi = dbi_conn_new_r("mysql", tagsistant.dbi_instance); #else dbi = dbi_conn_new("mysql"); #endif if (NULL == dbi) { dbg('s', LOG_ERR, "Error creating MySQL connection"); exit (1); } // set connection options dbi_conn_set_option(dbi, "host", dboptions.host); dbi_conn_set_option(dbi, "dbname", dboptions.db); dbi_conn_set_option(dbi, "username", dboptions.username); dbi_conn_set_option(dbi, "password", dboptions.password); dbi_conn_set_option(dbi, "encoding", "UTF-8"); } else if (TAGSISTANT_DBI_SQLITE_BACKEND == dboptions.backend) { if (!tagsistant_driver_is_available("sqlite3")) { fprintf(stderr, "SQLite3 driver not installed\n"); dbg('s', LOG_ERR, "SQLite3 driver not installed"); exit(1); } // create connection #if TAGSISTANT_REENTRANT_DBI dbi = dbi_conn_new_r("sqlite3", tagsistant.dbi_instance); #else dbi = dbi_conn_new("sqlite3"); #endif if (NULL == dbi) { dbg('s', LOG_ERR, "Error connecting to SQLite3"); exit (1); } // set connection options dbi_conn_set_option(dbi, "dbname", "tags.sql"); dbi_conn_set_option(dbi, "sqlite3_dbdir", tagsistant.repository); } else { dbg('s', LOG_ERR, "No or wrong database family specified!"); exit (1); } // try to connect if (dbi_conn_connect(dbi) < 0) { int error = dbi_conn_error(dbi, NULL); dbg('s', LOG_ERR, "Could not connect to DB (error %d). Please check the --db settings", error); exit(1); } connections++; dbg('s', LOG_INFO, "SQL connection established"); } /* start a transaction */ if (start_transaction) { #if TAGSISTANT_USE_INTERNAL_TRANSACTIONS switch (tagsistant.sql_database_driver) { case TAGSISTANT_DBI_SQLITE_BACKEND: tagsistant_query("begin transaction", dbi, NULL, NULL); break; case TAGSISTANT_DBI_MYSQL_BACKEND: tagsistant_query("start transaction", dbi, NULL, NULL); break; } #else dbi_conn_transaction_begin(dbi); #endif } return(dbi); }