/** * @param [in] reg the registry to delete the metadata from * @param [in] key the metadata key to delete * @param [out] errPtr on error, a description of the error that occurred * @return true if success; false if failure */ int reg_del_metadata(reg_registry* reg, const char* key, reg_error* errPtr) { int result = 1; sqlite3_stmt* stmt = NULL; char* query = "DELETE FROM registry.metadata WHERE key=?"; if ((sqlite3_prepare_v2(reg->db, query, -1, &stmt, NULL) == SQLITE_OK) && (sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC) == SQLITE_OK)) { int r; do { r = sqlite3_step(stmt); switch (r) { case SQLITE_DONE: if (sqlite3_changes(reg->db) == 0) { reg_throw(errPtr, REG_INVALID, "no such metadata key"); result = 0; } else { sqlite3_reset(stmt); } break; case SQLITE_BUSY: break; default: reg_sqlite_error(reg->db, errPtr, query); result = 0; break; } } while (r == SQLITE_BUSY); } else { reg_sqlite_error(reg->db, errPtr, query); result = 0; } if (stmt) { sqlite3_finalize(stmt); } return result; }
/** * Detaches a registry database from the registry object. This does some cleanup * for an attached registry, then detaches it. Allocated `reg_entry` objects are * deleted here. * * @param [in] reg registry to detach from * @param [out] errPtr on error, a description of the error that occurred * @return true if success; false if failure */ int reg_detach(reg_registry* reg, reg_error* errPtr) { sqlite3_stmt* stmt = NULL; int result = 0; char* query = "DETACH DATABASE registry"; if (!(reg->status & reg_attached)) { reg_throw(errPtr,REG_MISUSE,"no database is attached to this registry"); return 0; } if (sqlite3_prepare_v2(reg->db, query, -1, &stmt, NULL) == SQLITE_OK) { int r; reg_entry* entry; Tcl_HashEntry* curr; Tcl_HashSearch search; /* XXX: Busy waiting, consider using sqlite3_busy_handler/timeout */ do { sqlite3_step(stmt); r = sqlite3_reset(stmt); switch (r) { case SQLITE_OK: for (curr = Tcl_FirstHashEntry(®->open_entries, &search); curr != NULL; curr = Tcl_NextHashEntry(&search)) { entry = Tcl_GetHashValue(curr); if (entry->proc) { free(entry->proc); } free(entry); } Tcl_DeleteHashTable(®->open_entries); for (curr = Tcl_FirstHashEntry(®->open_files, &search); curr != NULL; curr = Tcl_NextHashEntry(&search)) { reg_file* file = Tcl_GetHashValue(curr); free(file->proc); free(file->key.path); free(file); } Tcl_DeleteHashTable(®->open_files); reg->status &= ~reg_attached; result = 1; break; case SQLITE_BUSY: break; default: reg_sqlite_error(reg->db, errPtr, query); break; } } while (r == SQLITE_BUSY); } else { reg_sqlite_error(reg->db, errPtr, query); } if (stmt) { sqlite3_finalize(stmt); } return result; }
/** * Convenience method for returning all objects of a given type from the * registry. * * @param [in] reg registry to select objects from * @param [in] query the select query to execute * @param [in] query_len length of the query (or -1 for automatic) * @param [out] objects the objects selected * @param [in] fn a function to convert sqlite3_stmts to the desired type * @param [inout] data data passed along to the cast function * @param [in] del a function to delete the desired type of object * @param [out] errPtr on error, a description of the error that occurred * @return the number of objects if success; negative if failure */ int reg_all_objects(reg_registry* reg, char* query, int query_len, void*** objects, cast_function* fn, void* castcalldata, free_function* del, reg_error* errPtr) { void** results = malloc(10*sizeof(void*)); int result_count = 0; int result_space = 10; sqlite3_stmt* stmt = NULL; if (!results || !fn) { return -1; } if (sqlite3_prepare_v2(reg->db, query, query_len, &stmt, NULL) == SQLITE_OK) { int r; void* row; do { r = sqlite3_step(stmt); switch (r) { case SQLITE_ROW: if (fn(reg, &row, stmt, castcalldata, errPtr)) { if (!reg_listcat(&results, &result_count, &result_space, row)) { r = SQLITE_ERROR; } } else { r = SQLITE_ERROR; } break; case SQLITE_DONE: break; case SQLITE_BUSY: continue; default: reg_sqlite_error(reg->db, errPtr, query); break; } } while (r == SQLITE_ROW || r == SQLITE_BUSY); sqlite3_finalize(stmt); if (r == SQLITE_DONE) { *objects = results; return result_count; } else if (del) { int i; for (i=0; i<result_count; i++) { del(NULL, results[i]); } } } else { if (stmt) { sqlite3_finalize(stmt); } reg_sqlite_error(reg->db, errPtr, query); } free(results); return -1; }
/** * Creates a new registry object. To start using a registry, one must first be * attached with `reg_attach`. * * @param [out] regPtr address of the allocated registry * @param [out] errPtr on error, a description of the error that occurred * @return true if success; false if failure */ int reg_open(reg_registry** regPtr, reg_error* errPtr) { reg_registry* reg = malloc(sizeof(reg_registry)); if (!reg) { return 0; } if (sqlite3_open(NULL, ®->db) == SQLITE_OK) { /* Enable extended result codes, requires SQLite >= 3.3.8 * Check added for compatibility with Tiger. */ #if SQLITE_VERSION_NUMBER >= 3003008 sqlite3_extended_result_codes(reg->db, 1); #endif sqlite3_busy_timeout(reg->db, 25); if (init_db(reg->db, errPtr)) { reg->status = reg_none; *regPtr = reg; return 1; } } else { reg_sqlite_error(reg->db, errPtr, NULL); } sqlite3_close(reg->db); free(reg); return 0; }
/** * Executes a null-terminated list of queries. Pass it a list of queries, it'll * execute them. This is mainly intended for initialization, when you have a * number of standard queries to execute. * * @param [in] db database to execute queries on * @param [in] queries NULL-terminated list of queries * @param [out] errPtr on error, a description of the error that occurred * @return true if success; false if failure */ int do_queries(sqlite3* db, char** queries, reg_error* errPtr) { char** query; sqlite3_stmt* stmt = NULL; int r = SQLITE_OK; for (query = queries; *query != NULL; query++) { if ((r = sqlite3_prepare_v2(db, *query, -1, &stmt, NULL)) != SQLITE_OK) { sqlite3_finalize(stmt); break; } do { r = sqlite3_step(stmt); } while (r == SQLITE_BUSY); sqlite3_finalize(stmt); /* Either execution succeeded and r == SQLITE_DONE | SQLITE_ROW, or there was an error */ if (r != SQLITE_DONE && r != SQLITE_ROW) { /* stop executing statements in case of errors */ break; } } switch (r) { case SQLITE_OK: case SQLITE_DONE: case SQLITE_ROW: return 1; default: /* handle errors */ reg_sqlite_error(db, errPtr, *query); return 0; } }
/** * @param [in] reg registry to set value in * @param [in] key metadata key to set * @param [in] value the desired value for the key * @param [out] errPtr on error, a description of the error that occurred * @return true if success; false if failure */ int reg_set_metadata(reg_registry* reg, const char* key, const char* value, reg_error* errPtr) { int result = 0; sqlite3_stmt* stmt = NULL; char* query; char *test_value; int get_returnval = reg_get_metadata(reg, key, &test_value, errPtr); if (get_returnval) { free(test_value); query = sqlite3_mprintf("UPDATE registry.metadata SET value = '%q' WHERE key='%q'", value, key); } else if (errPtr->code == REG_NOT_FOUND) { query = sqlite3_mprintf("INSERT INTO registry.metadata (key, value) VALUES ('%q', '%q')", key, value); } else { return get_returnval; } if (sqlite3_prepare_v2(reg->db, query, -1, &stmt, NULL) == SQLITE_OK) { int r; do { r = sqlite3_step(stmt); switch (r) { case SQLITE_DONE: result = 1; break; case SQLITE_BUSY: break; default: reg_sqlite_error(reg->db, errPtr, query); break; } } while (r == SQLITE_BUSY); } else { reg_sqlite_error(reg->db, errPtr, query); } if (stmt) { sqlite3_finalize(stmt); } sqlite3_free(query); return result; }
/** * @param [in] reg registry to get value from * @param [in] key metadata key to get * @param [out] value the value of the metadata * @param [out] errPtr on error, a description of the error that occurred * @return true if success; false if failure */ int reg_get_metadata(reg_registry* reg, const char* key, char** value, reg_error* errPtr) { int result = 0; sqlite3_stmt* stmt = NULL; char* query = "SELECT value FROM registry.metadata WHERE key=?"; const char *text; if (sqlite3_prepare_v2(reg->db, query, -1, &stmt, NULL) == SQLITE_OK && (sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC) == SQLITE_OK)) { int r; do { r = sqlite3_step(stmt); switch (r) { case SQLITE_ROW: text = (const char*)sqlite3_column_text(stmt, 0); if (text) { *value = strdup(text); result = 1; } else { reg_sqlite_error(reg->db, errPtr, query); } break; case SQLITE_DONE: errPtr->code = REG_NOT_FOUND; errPtr->description = "no such key in metadata"; errPtr->free = NULL; break; case SQLITE_BUSY: continue; default: reg_sqlite_error(reg->db, errPtr, query); break; } } while (r == SQLITE_BUSY); } else { reg_sqlite_error(reg->db, errPtr, query); } if (stmt) { sqlite3_finalize(stmt); } return result; }
/** * Executes a null-terminated list of queries. Pass it a list of queries, it'll * execute them. This is mainly intended for initialization, when you have a * number of standard queries to execute. * * @param [in] db database to execute queries on * @param [in] queries NULL-terminated list of queries * @param [out] errPtr on error, a description of the error that occurred * @return true if success; false if failure */ int do_queries(sqlite3* db, char** queries, reg_error* errPtr) { char** query; for (query = queries; *query != NULL; query++) { sqlite3_stmt* stmt = NULL; if ((sqlite3_prepare(db, *query, -1, &stmt, NULL) != SQLITE_OK) || (sqlite3_step(stmt) != SQLITE_DONE)) { reg_sqlite_error(db, errPtr, *query); if (stmt) { sqlite3_finalize(stmt); } return 0; } sqlite3_finalize(stmt); } return 1; }
/** * Helper function for `reg_commit` and `reg_rollback`. */ static int reg_end(reg_registry* reg, const char* query, reg_error* errPtr, int is_rollback) { if (!(reg->status & reg_transacting)) { reg_throw(errPtr, REG_MISUSE, "couldn't end transaction because no " "transaction is open"); return 0; } else { int r; do { r = sqlite3_exec(reg->db, query, NULL, NULL, NULL); if (r == SQLITE_OK) { return 1; } } while (r == SQLITE_BUSY && !is_rollback); reg_sqlite_error(reg->db, errPtr, NULL); return 0; } }
/** * Helper function for `reg_start_read` and `reg_start_write`. */ static int reg_start(reg_registry* reg, const char* query, reg_error* errPtr) { if (reg->status & reg_transacting) { reg_throw(errPtr, REG_MISUSE, "couldn't start transaction because a " "transaction is already open"); errPtr->free = NULL; return 0; } else { int r; do { r = sqlite3_exec(reg->db, query, NULL, NULL, NULL); if (r == SQLITE_OK) { return 1; } } while (r == SQLITE_BUSY); reg_sqlite_error(reg->db, errPtr, NULL); return 0; } }
/** * Creates a new registry object. To start using a registry, one must first be * attached with `reg_attach`. * * @param [out] regPtr address of the allocated registry * @param [out] errPtr on error, a description of the error that occurred * @return true if success; false if failure */ int reg_open(reg_registry** regPtr, reg_error* errPtr) { reg_registry* reg = malloc(sizeof(reg_registry)); if (!reg) { return 0; } if (sqlite3_open(NULL, ®->db) == SQLITE_OK) { if (init_db(reg->db, errPtr)) { reg->status = reg_none; *regPtr = reg; return 1; } } else { reg_sqlite_error(reg->db, errPtr, NULL); } sqlite3_close(reg->db); free(reg); return 0; }
/** * Attaches a registry database to the registry object. Prior to calling this, * the registry object is not actually connected to the registry. This function * attaches it so it can be queried and manipulated. * * @param [in] reg the registry to attach to * @param [in] path path to the registry db on disk * @param [out] errPtr on error, a description of the error that occurred * @return true if success; false if failure */ int reg_attach(reg_registry* reg, const char* path, reg_error* errPtr) { struct stat sb; int initialized = 1; /* registry already exists */ int can_write = 1; /* can write to this location */ int result = 0; if (reg->status & reg_attached) { reg_throw(errPtr, REG_MISUSE, "a database is already attached to this " "registry"); return 0; } if (stat(path, &sb) != 0) { initialized = 0; if (errno == ENOENT) { char *dirc, *dname; dirc = strdup(path); dname = dirname(dirc); if (stat(dname, &sb) != 0) { can_write = 0; } free(dirc); } else { can_write = 0; } } /* can_write is still true if one of the stat calls succeeded */ if (initialized || can_write) { sqlite3_stmt* stmt = NULL; char* query = sqlite3_mprintf("ATTACH DATABASE '%q' AS registry", path); int r; do { r = sqlite3_prepare_v2(reg->db, query, -1, &stmt, NULL); } while (r == SQLITE_BUSY); if (r == SQLITE_OK) { /* XXX: Busy waiting, consider using sqlite3_busy_handler/timeout */ do { sqlite3_step(stmt); r = sqlite3_reset(stmt); switch (r) { case SQLITE_OK: if (initialized || (create_tables(reg->db, errPtr))) { Tcl_InitHashTable(®->open_entries, sizeof(sqlite_int64)/sizeof(int)); Tcl_InitHashTable(®->open_files, TCL_STRING_KEYS); Tcl_InitHashTable(®->open_portgroups, sizeof(sqlite_int64)/sizeof(int)); reg->status |= reg_attached; result = 1; } break; case SQLITE_BUSY: break; default: reg_sqlite_error(reg->db, errPtr, query); } } while (r == SQLITE_BUSY); sqlite3_finalize(stmt); stmt = NULL; if (result) { result &= update_db(reg->db, errPtr); } } else { reg_sqlite_error(reg->db, errPtr, query); } if (stmt) { sqlite3_finalize(stmt); } sqlite3_free(query); } else { reg_throw(errPtr, REG_CANNOT_INIT, "port registry doesn't exist at " "\"%q\" and couldn't write to this location", path); } return result; }
/** * Updates the database if necessary. This function queries the current database version * from the metadata table and executes SQL to update the schema to newer versions if needed. * After that, this function updates the database version number * * @param [in] db database to update * @param [out] errPtr on error, a description of the error that occurred * @return true if success; false if failure */ int update_db(sqlite3* db, reg_error* errPtr) { const char* version; int r; int did_update = 0; /* true, if an update was done and the loop should be run again */ char* q_begin = "BEGIN"; char* q_version = "SELECT value FROM registry.metadata WHERE key = 'version'"; char* query = q_begin; sqlite3_stmt* stmt = NULL; do { did_update = 0; /* open a transaction to prevent a check-and-change race condition between * multiple port(1) instances */ if ((r = sqlite3_prepare_v2(db, query, -1, &stmt, NULL)) != SQLITE_OK) { break; } if ((r = sqlite3_step(stmt)) != SQLITE_DONE) { break; } sqlite3_finalize(stmt); stmt = NULL; /* query current version number */ query = q_version; if ((r = sqlite3_prepare_v2(db, query, -1, &stmt, NULL)) != SQLITE_OK) { break; } r = sqlite3_step(stmt); if (r == SQLITE_DONE) { /* the version number was not found */ reg_throw(errPtr, REG_INVALID, "Version number in metadata table not found."); sqlite3_finalize(stmt); rollback_db(db); return 0; } if (r != SQLITE_ROW) { /* an error occured querying */ break; } if (NULL == (version = (const char *)sqlite3_column_text(stmt, 0))) { reg_throw(errPtr, REG_INVALID, "Version number in metadata table is NULL."); sqlite3_finalize(stmt); rollback_db(db); return 0; } /* we can't call vercmp directly because it's static, but we have * sql_version, which is basically an alias */ if (sql_version(NULL, -1, version, -1, "1.1") < 0) { /* we need to update to 1.1, add binary field and index to files * table */ static char* version_1_1_queries[] = { #if SQLITE_VERSION_NUMBER >= 3002000 "ALTER TABLE registry.files ADD COLUMN binary BOOL", #else /* * SQLite < 3.2.0 doesn't support ALTER TABLE ADD COLUMN * Unfortunately, Tiger ships with SQLite < 3.2.0 (#34463) * This is taken from http://www.sqlite.org/faq.html#q11 */ /* Create a temporary table */ "CREATE TEMPORARY TABLE mp_files_backup (id INTEGER, path TEXT, " "actual_path TEXT, active INT, mtime DATETIME, md5sum TEXT, editable INT, " "FOREIGN KEY(id) REFERENCES ports(id))", /* Copy all data into the temporary table */ "INSERT INTO mp_files_backup SELECT id, path, actual_path, active, mtime, " "md5sum, editable FROM registry.files", /* Drop the original table and re-create it with the new structure */ "DROP TABLE registry.files", "CREATE TABLE registry.files (id INTEGER, path TEXT, actual_path TEXT, " "active INT, mtime DATETIME, md5sum TEXT, editable INT, binary BOOL, " "FOREIGN KEY(id) REFERENCES ports(id))", "CREATE INDEX registry.file_port ON files(id)", "CREATE INDEX registry.file_path ON files(path)", "CREATE INDEX registry.file_actual ON files(actual_path)", /* Copy all data back from temporary table */ "INSERT INTO registry.files (id, path, actual_path, active, mtime, md5sum, " "editable) SELECT id, path, actual_path, active, mtime, md5sum, " "editable FROM mp_files_backup", /* Remove temporary table */ "DROP TABLE mp_files_backup", #endif "CREATE INDEX registry.file_binary ON files(binary)", "UPDATE registry.metadata SET value = '1.100' WHERE key = 'version'", "COMMIT", NULL }; /* don't forget to finalize the version query here, or it might * cause "cannot commit transaction - SQL statements in progress", * see #32686 */ sqlite3_finalize(stmt); stmt = NULL; if (!do_queries(db, version_1_1_queries, errPtr)) { rollback_db(db); return 0; } did_update = 1; continue; } /* add new versions here, but remember to: * - finalize the version query statement and set stmt to NULL * - do _not_ use "BEGIN" in your query list, since a transaction has * already been started for you * - end your query list with "COMMIT", NULL * - set did_update = 1 and continue; */ /* if we arrive here, no update was done and we should end the * transaction. Using ROLLBACK here causes problems when rolling back * other transactions later in the program. */ sqlite3_finalize(stmt); stmt = NULL; r = sqlite3_exec(db, "COMMIT", NULL, NULL, NULL); } while (did_update); sqlite3_finalize(stmt); switch (r) { case SQLITE_OK: case SQLITE_DONE: case SQLITE_ROW: return 1; default: reg_sqlite_error(db, errPtr, query); return 0; } }