From e5e577b282b9a16ef49ca836c04a85f07048e08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E8=B5=AB=E7=84=B6?= Date: Tue, 10 Oct 2023 15:05:32 +0800 Subject: [PATCH 1/5] Support dm8 database --- common/branch-mgr.c | 10 +- common/group-mgr.c | 86 ++- common/org-mgr.c | 42 ++ common/seaf-db.c | 714 +++++++++++++++++- common/seaf-db.h | 7 + common/seaf-utils.c | 112 +++ common/user-mgr.c | 62 ++ configure.ac | 13 + fuse/Makefile.am | 2 +- server/Makefile.am | 2 +- server/gc/Makefile.am | 4 +- server/gc/repo-mgr.c | 4 +- server/quota-mgr.c | 32 +- server/repo-mgr.c | 187 ++++- server/seafile-session.c | 2 +- server/share-mgr.c | 15 + server/size-sched.c | 1 + .../test_repo_manipulation.py | 3 + 18 files changed, 1249 insertions(+), 49 deletions(-) diff --git a/common/branch-mgr.c b/common/branch-mgr.c index 45ac56ce..32b17744 100644 --- a/common/branch-mgr.c +++ b/common/branch-mgr.c @@ -141,6 +141,13 @@ open_db (SeafBranchManager *mgr) char *sql; switch (seaf_db_type (mgr->seaf->db)) { + case SEAF_DB_TYPE_DM: + sql = "CREATE TABLE IF NOT EXISTS Branch (" + "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " + "name VARCHAR(10), repo_id VARCHAR(41), commit_id VARCHAR(41))"; + if (seaf_db_query (mgr->seaf->db, sql) < 0) + return -1; + break; case SEAF_DB_TYPE_MYSQL: sql = "CREATE TABLE IF NOT EXISTS Branch (" "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " @@ -200,7 +207,7 @@ seaf_branch_manager_add_branch (SeafBranchManager *mgr, SeafBranch *branch) char *sql; SeafDB *db = mgr->seaf->db; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { gboolean exists, err; int rc; @@ -382,6 +389,7 @@ seaf_branch_manager_test_and_update_branch (SeafBranchManager *mgr, switch (seaf_db_type (mgr->seaf->db)) { case SEAF_DB_TYPE_MYSQL: case SEAF_DB_TYPE_PGSQL: + case SEAF_DB_TYPE_DM: sql = "SELECT commit_id FROM Branch WHERE name=? " "AND repo_id=? FOR UPDATE"; break; diff --git a/common/group-mgr.c b/common/group-mgr.c index 2c1f915b..1ed2bb12 100644 --- a/common/group-mgr.c +++ b/common/group-mgr.c @@ -87,6 +87,7 @@ open_db (CcnetGroupManager *manager) db = open_sqlite_db (manager); break; case SEAF_DB_TYPE_PGSQL: + case SEAF_DB_TYPE_DM: case SEAF_DB_TYPE_MYSQL: db = manager->session->ccnet_db; break; @@ -225,6 +226,46 @@ static int check_db_table (CcnetGroupManager *manager, CcnetDB *db) // return -1; //} + } else if (db_type == SEAF_DB_TYPE_DM) { + g_string_printf (group_sql, + "CREATE TABLE IF NOT EXISTS \"%s\" (group_id INTEGER" + " PRIMARY KEY AUTO_INCREMENT, group_name VARCHAR(255)," + " creator_name VARCHAR(255), timestamp BIGINT," + " type VARCHAR(32), parent_group_id INTEGER)", table_name); + if (seaf_db_query (db, group_sql->str) < 0) { + g_string_free (group_sql, TRUE); + return -1; + } + + sql = "CREATE TABLE IF NOT EXISTS GroupUser (group_id INTEGER, " + "user_name VARCHAR(255), is_staff tinyint)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE UNIQUE INDEX IF NOT EXISTS groupid_username_indx on " + "GroupUser (group_id, user_name)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS username_indx on " + "GroupUser (user_name)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS GroupDNPair (group_id INTEGER," + " dn VARCHAR(255))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS GroupStructure (group_id INTEGER PRIMARY KEY, " + "path VARCHAR(1024))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS path_indx on " + "GroupStructure (path)"; + if (seaf_db_query (db, sql) < 0) + return -1; } g_string_free (group_sql, TRUE); @@ -267,7 +308,7 @@ create_group_common (CcnetGroupManager *mgr, char *user_name_l = g_ascii_strdown (user_name, -1); - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) g_string_printf (sql, "INSERT INTO \"%s\"(group_name, " "creator_name, timestamp, parent_group_id) VALUES(?, ?, ?, ?)", table_name); @@ -281,7 +322,7 @@ create_group_common (CcnetGroupManager *mgr, "int64", now, "int", parent_group_id) < 0) goto error; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) g_string_printf (sql, "SELECT group_id FROM \"%s\" WHERE " "group_name = ? AND creator_name = ? " @@ -472,7 +513,7 @@ int ccnet_group_manager_remove_group (CcnetGroupManager *mgr, * can remove group. */ if (remove_anyway != TRUE) { - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) g_string_printf (sql, "SELECT 1 FROM \"%s\" WHERE parent_group_id=?", table_name); else g_string_printf (sql, "SELECT 1 FROM `%s` WHERE parent_group_id=?", table_name); @@ -489,7 +530,7 @@ int ccnet_group_manager_remove_group (CcnetGroupManager *mgr, } } - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) g_string_printf (sql, "DELETE FROM \"%s\" WHERE group_id=?", table_name); else g_string_printf (sql, "DELETE FROM `%s` WHERE group_id=?", table_name); @@ -499,7 +540,7 @@ int ccnet_group_manager_remove_group (CcnetGroupManager *mgr, seaf_db_statement_query (db, sql->str, 1, "int", group_id); g_string_printf (sql, "DELETE FROM GroupStructure WHERE group_id=?"); - seaf_db_statement_query (db, sql->str, 1, "int", group_id); + int ret = seaf_db_statement_query (db, sql->str, 1, "int", group_id); g_string_free (sql, TRUE); @@ -513,7 +554,7 @@ check_group_exists (CcnetGroupManager *mgr, CcnetDB *db, int group_id) const char *table_name = mgr->priv->table_name; gboolean exists, err; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { g_string_printf (sql, "SELECT group_id FROM \"%s\" WHERE group_id=?", table_name); exists = seaf_db_statement_exists (db, sql->str, &err, 1, "int", group_id); } else { @@ -622,7 +663,7 @@ int ccnet_group_manager_set_group_name (CcnetGroupManager *mgr, GString *sql = g_string_new (""); CcnetDB *db = mgr->priv->db; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { g_string_printf (sql, "UPDATE \"%s\" SET group_name = ? " "WHERE group_id = ?", table_name); seaf_db_statement_query (db, sql->str, 2, "string", group_name, "int", group_id); @@ -697,7 +738,7 @@ ccnet_group_manager_get_ancestor_groups (CcnetGroupManager *mgr, int group_id) char *path = seaf_db_statement_get_string (db, sql->str, 1, "int", group_id); if (path) { - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) g_string_printf (sql, "SELECT g.group_id, group_name, creator_name, timestamp, parent_group_id FROM " "\"%s\" g WHERE g.group_id IN(%s) " "ORDER BY g.group_id", @@ -768,7 +809,7 @@ ccnet_group_manager_get_groups_by_user (CcnetGroupManager *mgr, CcnetGroup *group; int parent_group_id = 0, group_id = 0; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) g_string_printf (sql, "SELECT g.group_id, group_name, creator_name, timestamp, parent_group_id FROM " "\"%s\" g, GroupUser u WHERE g.group_id = u.group_id AND user_name=? ORDER BY g.group_id DESC", @@ -827,9 +868,14 @@ ccnet_group_manager_get_groups_by_user (CcnetGroupManager *mgr, goto out; } - g_string_printf (sql, "SELECT g.group_id, group_name, creator_name, timestamp, parent_group_id FROM " - "`%s` g WHERE g.group_id IN (%s) ORDER BY g.group_id DESC", - table_name, paths->str); + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) + g_string_printf (sql, "SELECT g.group_id, group_name, creator_name, timestamp, parent_group_id FROM " + "\"%s\" g WHERE g.group_id IN (%s) ORDER BY g.group_id DESC", + table_name, paths->str); + else + g_string_printf (sql, "SELECT g.group_id, group_name, creator_name, timestamp, parent_group_id FROM " + "`%s` g WHERE g.group_id IN (%s) ORDER BY g.group_id DESC", + table_name, paths->str); if (seaf_db_statement_foreach_row (db, sql->str, get_user_groups_cb, @@ -888,7 +934,7 @@ ccnet_group_manager_get_child_groups (CcnetGroupManager *mgr, int group_id, GList *ret = NULL; const char *table_name = mgr->priv->table_name; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) g_string_printf (sql, "SELECT group_id, group_name, creator_name, timestamp, parent_group_id FROM " "\"%s\" WHERE parent_group_id=?", table_name); @@ -916,7 +962,7 @@ ccnet_group_manager_get_descendants_groups(CcnetGroupManager *mgr, int group_id, const char *table_name = mgr->priv->table_name; GString *sql = g_string_new(""); - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) g_string_printf (sql, "SELECT g.group_id, group_name, creator_name, timestamp, " "parent_group_id FROM \"%s\" g, GroupStructure s " "WHERE g.group_id=s.group_id " @@ -952,7 +998,7 @@ ccnet_group_manager_get_group (CcnetGroupManager *mgr, int group_id, CcnetGroup *ccnetgroup = NULL; const char *table_name = mgr->priv->table_name; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) g_string_printf (sql, "SELECT group_id, group_name, creator_name, timestamp, parent_group_id FROM " "\"%s\" WHERE group_id = ?", table_name); @@ -1174,7 +1220,7 @@ ccnet_group_manager_get_top_groups (CcnetGroupManager *mgr, const char *table_name = mgr->priv->table_name; int rc; - if (seaf_db_type(mgr->priv->db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(mgr->priv->db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(mgr->priv->db) == SEAF_DB_TYPE_DM) { if (including_org) g_string_printf (sql, "SELECT group_id, group_name, " "creator_name, timestamp, parent_group_id FROM \"%s\" " @@ -1217,7 +1263,7 @@ ccnet_group_manager_list_all_departments (CcnetGroupManager *mgr, int rc; int db_type = seaf_db_type(db); - if (db_type == SEAF_DB_TYPE_PGSQL) { + if (db_type == SEAF_DB_TYPE_PGSQL || db_type == SEAF_DB_TYPE_DM) { g_string_printf (sql, "SELECT group_id, group_name, " "creator_name, timestamp, type, " "parent_group_id FROM \"%s\" " @@ -1251,7 +1297,7 @@ ccnet_group_manager_get_all_groups (CcnetGroupManager *mgr, const char *table_name = mgr->priv->table_name; int rc; - if (seaf_db_type(mgr->priv->db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(mgr->priv->db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(mgr->priv->db) == SEAF_DB_TYPE_DM) { if (start == -1 && limit == -1) { g_string_printf (sql, "SELECT group_id, group_name, " "creator_name, timestamp, parent_group_id FROM \"%s\" " @@ -1301,7 +1347,7 @@ ccnet_group_manager_set_group_creator (CcnetGroupManager *mgr, const char *table_name = mgr->priv->table_name; GString *sql = g_string_new (""); - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { g_string_printf (sql, "UPDATE \"%s\" SET creator_name = ? WHERE group_id = ?", table_name); } else { @@ -1329,7 +1375,7 @@ ccnet_group_manager_search_groups (CcnetGroupManager *mgr, int rc; char *db_patt = g_strdup_printf ("%%%s%%", keyword); - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { if (start == -1 && limit == -1) { g_string_printf (sql, "SELECT group_id, group_name, " diff --git a/common/org-mgr.c b/common/org-mgr.c index ba8cd0db..646f18d4 100644 --- a/common/org-mgr.c +++ b/common/org-mgr.c @@ -77,6 +77,7 @@ open_db (CcnetOrgManager *manager) db = open_sqlite_db (manager); break; case SEAF_DB_TYPE_PGSQL: + case SEAF_DB_TYPE_DM: case SEAF_DB_TYPE_MYSQL: db = manager->session->ccnet_db; break; @@ -203,6 +204,47 @@ static int check_db_table (CcnetDB *db) // if (seaf_db_query (db, sql) < 0) // return -1; //} + } else if (db_type == SEAF_DB_TYPE_DM) { + sql = "CREATE TABLE IF NOT EXISTS Organization (org_id INTEGER" + " PRIMARY KEY, org_name VARCHAR(255)," + " url_prefix VARCHAR(255), " + " creator VARCHAR(255), ctime BIGINT)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE UNIQUE INDEX IF NOT EXISTS url_prefix_indx on " + "Organization (url_prefix)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS OrgUser (org_id INTEGER, " + "email VARCHAR(255), is_staff INTEGER NOT NULL)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS email_indx on " + "OrgUser (email)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE UNIQUE INDEX IF NOT EXISTS orgid_email_indx on " + "OrgUser (org_id, email)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS OrgGroup (org_id INTEGER, " + "group_id INTEGER)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS groupid_indx on OrgGroup (group_id)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE UNIQUE INDEX IF NOT EXISTS org_group_indx on " + "OrgGroup (org_id, group_id)"; + if (seaf_db_query (db, sql) < 0) + return -1; } return 0; diff --git a/common/seaf-db.c b/common/seaf-db.c index 4093b444..58e246f4 100644 --- a/common/seaf-db.c +++ b/common/seaf-db.c @@ -13,6 +13,12 @@ #include #include +#ifdef HAVE_ODBC +#include +#include +#include +#endif + struct DBConnPool { GPtrArray *connections; pthread_mutex_t lock; @@ -38,6 +44,7 @@ struct SeafDBRow { struct SeafDBTrans { DBConnection *conn; gboolean need_close; + int type; }; typedef struct DBOperations { @@ -320,6 +327,210 @@ seaf_db_new_sqlite (const char *db_path, int max_connections) return db; } +#ifdef HAVE_ODBC + +#define BUFFER_NOT_ENOUGH -70018 +#define SUCCESS(rc) ((rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO || (rc) == SQL_NO_DATA || (rc) == SQL_NO_DATA_FOUND) + +typedef struct DMDBConnection { + struct DBConnection parent; + HDBC db_conn; +} DMDBConnection; + +/* MySQL Ops */ +static SeafDB * +dm_db_new (const char *user, + const char *password, + const char *db_name); +static DBConnection * +dm_db_get_connection (SeafDB *db); +static void +dm_db_release_connection (DBConnection *vconn); +static int +dm_db_execute_sql_no_stmt (DBConnection *vconn, const char *sql, gboolean *retry); +static int +dm_db_execute_sql (DBConnection *vconn, const char *sql, int n, va_list args, gboolean *retry); +static int +dm_db_query_foreach_row (DBConnection *vconn, const char *sql, + SeafDBRowFunc callback, void *data, + int n, va_list args, gboolean *retry); +static int +dm_db_row_get_column_count (SeafDBRow *row); +static const char * +dm_db_row_get_column_string (SeafDBRow *row, int idx); +static int +dm_db_row_get_column_int (SeafDBRow *row, int idx); +static gint64 +dm_db_row_get_column_int64 (SeafDBRow *row, int idx); + +static DBConnPool * +init_dm_conn_pool_common (int max_connections) +{ + DBConnPool *pool = g_new0(DBConnPool, 1); + pool->connections = g_ptr_array_sized_new (max_connections); + pthread_mutex_init (&pool->lock, NULL); + pool->max_connections = max_connections; + + return pool; +} + +static DBConnection * +dm_conn_pool_get_connection (SeafDB *db) +{ + DBConnPool *pool = db->pool; + DBConnection *conn = NULL; + DBConnection *d_conn = NULL; + + if (pool->max_connections == 0) { + conn = dm_db_get_connection (db); + conn->pool = pool; + return conn; + } + + pthread_mutex_lock (&pool->lock); + + guint i, size = pool->connections->len; + for (i = 0; i < size; ++i) { + conn = g_ptr_array_index (pool->connections, i); + if (!conn->is_available) { + continue; + } + conn->is_available = FALSE; + goto out; + } + conn = NULL; + if (size < pool->max_connections) { + conn = dm_db_get_connection (db); + if (conn) { + conn->pool = pool; + conn->is_available = FALSE; + g_ptr_array_add (pool->connections, conn); + } + } + +out: + size = pool->connections->len; + if (size > 0) { + int index; + for (index = size - 1; index >= 0; index--) { + d_conn = g_ptr_array_index (pool->connections, index); + if (d_conn->delete_pending) { + g_ptr_array_remove (conn->pool->connections, d_conn); + dm_db_release_connection (d_conn); + } + } + } + pthread_mutex_unlock (&pool->lock); + return conn; +} + +static void +dm_conn_pool_release_connection (DBConnection *conn, gboolean need_close) +{ + if (!conn) + return; + + if (conn->pool->max_connections == 0) { + dm_db_release_connection (conn); + return; + } + + if (need_close) { + pthread_mutex_lock (&conn->pool->lock); + g_ptr_array_remove (conn->pool->connections, conn); + pthread_mutex_unlock (&conn->pool->lock); + dm_db_release_connection (conn); + return; + } + + pthread_mutex_lock (&conn->pool->lock); + conn->is_available = TRUE; + pthread_mutex_unlock (&conn->pool->lock); +} + +#define KEEPALIVE_INTERVAL 30 +static void * +dm_conn_keepalive (void *arg) +{ + DBConnPool *pool = arg; + DBConnection *conn = NULL; + DBConnection *d_conn = NULL; + char *sql = "SELECT 1;"; + int rc = 0; + va_list args; + + while (1) { + pthread_mutex_lock (&pool->lock); + + guint i, size = pool->connections->len; + for (i = 0; i < size; ++i) { + conn = g_ptr_array_index (pool->connections, i); + if (conn->is_available) { + rc = db_ops.execute_sql (conn, sql, 0, args, NULL); + if (rc < 0) { + conn->is_available = FALSE; + conn->delete_pending = TRUE; + } + } + } + + if (size > 0) { + int index; + for (index = size - 1; index >= 0; index--) { + d_conn = g_ptr_array_index (pool->connections, index); + if (d_conn->delete_pending) { + g_ptr_array_remove (pool->connections, d_conn); + dm_db_release_connection (d_conn); + } + } + } + + pthread_mutex_unlock (&pool->lock); + + sleep (KEEPALIVE_INTERVAL); + } + + return NULL; +} + +SeafDB * +seaf_db_new_dm (const char *user, + const char *passwd, + const char *db_name, + int max_connections) +{ + SeafDB *db; + + db = dm_db_new (user, passwd, db_name); + if (!db) + return NULL; + db->type = SEAF_DB_TYPE_DM; + + db_ops.get_connection = dm_conn_pool_get_connection; + db_ops.release_connection = dm_conn_pool_release_connection; + db_ops.execute_sql_no_stmt = dm_db_execute_sql_no_stmt; + db_ops.execute_sql = dm_db_execute_sql; + db_ops.query_foreach_row = dm_db_query_foreach_row; + db_ops.row_get_column_count = dm_db_row_get_column_count; + db_ops.row_get_column_string = dm_db_row_get_column_string; + db_ops.row_get_column_int = dm_db_row_get_column_int; + db_ops.row_get_column_int64 = dm_db_row_get_column_int64; + + db->pool = init_dm_conn_pool_common (max_connections); + + pthread_t tid; + int ret = pthread_create (&tid, NULL, dm_conn_keepalive, db->pool); + if (ret != 0) { + seaf_warning ("Failed to create dm connection keepalive thread.\n"); + return NULL; + } + pthread_detach (tid); + + return db; +} + +#endif + int seaf_db_type (SeafDB *db) { @@ -635,13 +846,21 @@ seaf_db_begin_transaction (SeafDB *db) return trans; } - if (db_ops.execute_sql_no_stmt (conn, "BEGIN", NULL) < 0) { - db_ops.release_connection (conn, TRUE); - return trans; + if (db->type == SEAF_DB_TYPE_DM) { +#ifdef HAVE_ODBC + DMDBConnection *dm_conn = (DMDBConnection *)conn; + SQLSetConnectAttr(dm_conn->db_conn, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER); +#endif + } else { + if (db_ops.execute_sql_no_stmt (conn, "BEGIN", NULL) < 0) { + db_ops.release_connection (conn, TRUE); + return trans; + } } trans = g_new0 (SeafDBTrans, 1); trans->conn = conn; + trans->type = db->type; return trans; } @@ -649,6 +868,12 @@ seaf_db_begin_transaction (SeafDB *db) void seaf_db_trans_close (SeafDBTrans *trans) { + if (trans->type == SEAF_DB_TYPE_DM) { +#ifdef HAVE_ODBC + DMDBConnection *dm_conn = (DMDBConnection *)trans->conn; + SQLSetConnectAttr(dm_conn->db_conn, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); +#endif + } db_ops.release_connection (trans->conn, trans->need_close); g_free (trans); } @@ -658,9 +883,21 @@ seaf_db_commit (SeafDBTrans *trans) { DBConnection *conn = trans->conn; - if (db_ops.execute_sql_no_stmt (conn, "COMMIT", NULL) < 0) { - trans->need_close = TRUE; - return -1; + if (trans->type == SEAF_DB_TYPE_DM) { +#ifdef HAVE_ODBC + DMDBConnection *dm_conn = (DMDBConnection *)conn; + SQLRETURN ret; + ret = SQLEndTran (SQL_HANDLE_DBC, dm_conn->db_conn, SQL_COMMIT); + if (!SUCCESS(ret)) { + trans->need_close = TRUE; + return -1; + } +#endif + } else { + if (db_ops.execute_sql_no_stmt (conn, "COMMIT", NULL) < 0) { + trans->need_close = TRUE; + return -1; + } } return 0; @@ -671,9 +908,21 @@ seaf_db_rollback (SeafDBTrans *trans) { DBConnection *conn = trans->conn; - if (db_ops.execute_sql_no_stmt (conn, "ROLLBACK", NULL) < 0) { - trans->need_close = TRUE; - return -1; + if (trans->type == SEAF_DB_TYPE_DM) { +#ifdef HAVE_ODBC + DMDBConnection *dm_conn = (DMDBConnection *)conn; + SQLRETURN ret; + ret = SQLEndTran (SQL_HANDLE_DBC, dm_conn->db_conn, SQL_ROLLBACK); + if (!SUCCESS(ret)) { + trans->need_close = TRUE; + return -1; + } +#endif + } else { + if (db_ops.execute_sql_no_stmt (conn, "ROLLBACK", NULL) < 0) { + trans->need_close = TRUE; + return -1; + } } return 0; @@ -1575,3 +1824,450 @@ sqlite_db_row_get_column_int64 (SeafDBRow *vrow, int idx) return sqlite3_column_int64 (row->stmt, idx); } + +#ifdef HAVE_ODBC + +/* DM DB */ + +static int +dm_get_error_state (SQLSMALLINT type, SQLHANDLE handle) +{ + SQLINTEGER native_err; + unsigned char errmsg[255]; + SQLGetDiagRec(type, handle, 1, NULL, &native_err, errmsg, sizeof(errmsg), NULL); + + return native_err; +} + +char * +dm_db_error (SQLSMALLINT type, SQLHANDLE handle) +{ + SQLINTEGER native_err; + unsigned char errmsg[255]; + SQLGetDiagRec(type, handle, 1, NULL, &native_err, errmsg, sizeof(errmsg), NULL); + + return g_strdup(errmsg); +} + +typedef struct DMDB { + struct SeafDB parent; + char *user; + char *password; + char *db_name; + HENV env; +} DMDB; + +typedef struct DM_BIND { + void *buffer; + int buffer_length; + SQLLEN length; +} DM_BIND; + +static SeafDB * +dm_db_new (const char *user, + const char *password, + const char *db_name) +{ + DMDB *db = g_new0 (DMDB, 1); + + db->user = g_strdup (user); + db->password = g_strdup (password); + db->db_name = g_strdup(db_name); + + SQLAllocHandle(SQL_HANDLE_ENV, NULL, &db->env); + SQLSetEnvAttr(db->env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER); + + return (SeafDB *)db; +} + +typedef char my_bool; + +static DBConnection * +dm_db_get_connection (SeafDB *vdb) +{ + DMDB *db = (DMDB *)vdb; + HDBC db_conn; + DMDBConnection *conn = NULL; + SQLRETURN ret; + + SQLAllocHandle(SQL_HANDLE_DBC, db->env, &db_conn); + ret = SQLConnect(db_conn, (SQLCHAR *)db->db_name, SQL_NTS, (SQLCHAR *)db->user, SQL_NTS, (SQLCHAR *)db->password, SQL_NTS); + if (!SUCCESS(ret)) { + char *errmsg = dm_db_error (SQL_HANDLE_DBC, db_conn); + seaf_warning ("Failed to connect dm server: %s\n", errmsg); + g_free (errmsg); + SQLFreeHandle(SQL_HANDLE_DBC, db_conn); + return NULL; + } + + conn = g_new0 (DMDBConnection, 1); + conn->db_conn = db_conn; + + return (DBConnection *)conn; +} + +static void +dm_db_release_connection (DBConnection *vconn) +{ + if (!vconn) + return; + + DMDBConnection *conn = (DMDBConnection *)vconn; + + SQLDisconnect(conn->db_conn); + SQLFreeHandle(SQL_HANDLE_DBC, conn->db_conn); + + g_free (conn); +} + +static int +dm_db_execute_sql_no_stmt (DBConnection *vconn, const char *sql, gboolean *retry) +{ + DMDBConnection *conn = (DMDBConnection *)vconn; + HSTMT stmt; + SQLRETURN ret; + + SQLAllocHandle(SQL_HANDLE_STMT, conn->db_conn, &stmt); + + ret = SQLExecDirect(stmt, (SQLCHAR *)sql, SQL_NTS); + if (!SUCCESS(ret)) { + char *errmsg = dm_db_error (SQL_HANDLE_STMT, stmt); + seaf_warning ("Failed to execute sql %s: %s\n", sql, errmsg); + g_free (errmsg); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return -1; + } + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return 0; +} + +static HSTMT +_prepare_stmt_dm (HDBC db, const char *sql, gboolean *retry) +{ + HSTMT stmt; + SQLRETURN ret; + + SQLAllocHandle(SQL_HANDLE_STMT, db, &stmt); + ret = SQLPrepare(stmt, sql, SQL_NTS); + if (!SUCCESS(ret)) { + char * errmsg = dm_db_error (SQL_HANDLE_STMT, stmt); + seaf_warning ("Failed to prepare sql %s: %s\n", sql, errmsg); + g_free (errmsg); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return NULL; + } + + return stmt; +} + +static int +_bind_params_dm (HSTMT stmt, DM_BIND *params, int n, va_list args) +{ + int i; + const char *type; + SQLRETURN ret; + char *errmsg = NULL; + + for (i = 0; i < n; ++i) { + type = va_arg (args, const char *); + if (strcmp(type, "int") == 0) { + int x = va_arg (args, int); + int *pval = g_new (int, 1); + *pval = x; + params[i].buffer = pval; + ret = SQLBindParameter(stmt, i+1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, sizeof(x), 0, params[i].buffer, sizeof(x), NULL); + if (!SUCCESS(ret)) { + errmsg = dm_db_error (SQL_HANDLE_STMT, stmt); + seaf_warning ("Failed to bid params: %s\n", errmsg); + g_free (errmsg); + return -1; + } + } else if (strcmp (type, "int64") == 0) { + gint64 x = va_arg (args, gint64); + gint64 *pval = g_new (gint64, 1); + *pval = x; + params[i].buffer = pval; + ret = SQLBindParameter(stmt, i+1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, sizeof(x), 0, params[i].buffer, sizeof(x), NULL); + if (!SUCCESS(ret)) { + errmsg = dm_db_error (SQL_HANDLE_STMT, stmt); + seaf_warning ("Failed to bid params: %s\n", errmsg); + g_free (errmsg); + return -1; + } + } else if (strcmp (type, "string") == 0) { + const char *s = va_arg (args, const char *); + int len = 0; + if (s) { + len = strlen(s); + } + params[i].buffer = g_strdup(s); + ret = SQLBindParameter(stmt, i+1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, len, 0, params[i].buffer, len, NULL); + if (!SUCCESS(ret)) { + errmsg = dm_db_error (SQL_HANDLE_STMT, stmt); + seaf_warning ("Failed to bid params: %s\n", errmsg); + g_free (errmsg); + return -1; + } + } else { + seaf_warning ("BUG: invalid prep stmt parameter type %s.\n", type); + g_return_val_if_reached (-1); + } + } + + return 0; +} + +static int +dm_db_execute_sql (DBConnection *vconn, const char *sql, int n, va_list args, gboolean *retry) +{ + DMDBConnection *conn = (DMDBConnection *)vconn; + HDBC db = conn->db_conn; + HSTMT stmt = NULL; + DM_BIND *params = NULL; + SQLRETURN rc; + int ret = 0; + + stmt = _prepare_stmt_dm (db, sql, retry); + if (!stmt) { + return -1; + } + + if (n > 0) { + params = g_new0 (DM_BIND, n); + if (_bind_params_dm(stmt, params, n, args) < 0) { + seaf_warning ("Failed to bind parameters for %s.\n", sql); + ret = -1; + goto out; + } + } + + rc = SQLExecute (stmt); + if (!SUCCESS(rc)) { + char *errmsg = dm_db_error (SQL_HANDLE_STMT, stmt); + seaf_warning ("Failed to execute sql %s: %s\n", sql, errmsg); + g_free (errmsg); + ret = -1; + goto out; + } + +out: + if (stmt) { + SQLCloseCursor(stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + } + if (params) { + int i; + for (i = 0; i < n; ++i) { + g_free (params[i].buffer); + } + g_free (params); + } + return ret; +} + +typedef struct DMDBRow { + SeafDBRow parent; + int column_count; + HSTMT stmt; + DM_BIND *results; + gboolean over_buffer; +} DMDBRow; + +#define DEFAULT_DM_COLUMN_SIZE 1024 + +static int +dm_db_query_foreach_row (DBConnection *vconn, const char *sql, + SeafDBRowFunc callback, void *data, + int n, va_list args, gboolean *retry) +{ + DMDBConnection *conn = (DMDBConnection *)vconn; + HDBC db = conn->db_conn; + HSTMT stmt = NULL; + DM_BIND *params = NULL; + DMDBRow row; + SQLRETURN rc; + short cols; + int nrows = 0; + int i; + + memset (&row, 0, sizeof(row)); + + stmt = _prepare_stmt_dm (db, sql, retry); + if (!stmt) { + return -1; + } + + if (n > 0) { + params = g_new0 (DM_BIND, n); + if (_bind_params_dm (stmt, params, n, args) < 0) { + nrows = -1; + goto out; + } + } + + rc = SQLExecute (stmt); + if (!SUCCESS(rc)) { + char *errmsg = dm_db_error (SQL_HANDLE_STMT, stmt); + seaf_warning ("Failed to execute sql %s: error: %s\n", sql, errmsg); + g_free (errmsg); + nrows = -1; + goto out; + } + + SQLNumResultCols(stmt, &cols); + row.column_count = cols; + row.stmt = stmt; + row.results = g_new0 (DM_BIND, row.column_count); + for (i = 0; i < row.column_count; ++i) { + row.results[i].buffer = g_new0(char, DEFAULT_DM_COLUMN_SIZE + 1); + /* Ask DM to convert fields to string, to avoid the trouble of + * checking field types. + */ + row.results[i].buffer_length = DEFAULT_DM_COLUMN_SIZE; + row.results[i].length = 0; + SQLBindCol(stmt, i+1, SQL_C_CHAR, row.results[i].buffer, row.results[i].buffer_length, &row.results[i].length); + } + + gboolean next_row = TRUE; + while (1) { + rc = SQLFetch(stmt); + if (!SUCCESS(rc)) { + if (rc != SQL_ERROR || dm_get_error_state(SQL_HANDLE_STMT, stmt) != BUFFER_NOT_ENOUGH) { + char *err_msg = dm_db_error (SQL_HANDLE_STMT, stmt); + seaf_warning ("Failed to fetch result for sql %s: %s\n", + sql, err_msg); + g_free (err_msg); + nrows = -1; + goto out; + } else { + row.over_buffer = TRUE; + } + } else { + row.over_buffer = FALSE; + } + if (rc == SQL_NO_DATA_FOUND || rc == SQL_NO_DATA) + break; + + ++nrows; + if (callback) + next_row = callback ((SeafDBRow *)&row, data); + + if (!next_row) + break; + } + +out: + if (stmt) { + SQLCloseCursor(stmt); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + } + if (params) { + int i; + for (i = 0; i < n; ++i) { + g_free (params[i].buffer); + } + g_free (params); + } + if (row.results) { + for (i = 0; i < row.column_count; ++i) { + g_free (row.results[i].buffer); + } + g_free (row.results); + } + return nrows; +} + +static int +dm_db_row_get_column_count (SeafDBRow *vrow) +{ + DMDBRow *row = (DMDBRow *)vrow; + return row->column_count; +} + +static const char * +dm_db_row_get_column_string (SeafDBRow *vrow, int i) +{ + DMDBRow *row = (DMDBRow *)vrow; + + if (row->results[i].length == -1) { + return NULL; + } + + + SQLRETURN rc; + char *ret = NULL; + int j = 1; + /* If column size is larger then allocated buffer size, re-allocate a new buffer + * and fetch the column directly. + */ +alloc_buffer: + if (row->results[i].length == 0 && row->over_buffer) { + g_free (row->results[i].buffer); + row->results[i].buffer = g_new0 (char, 2*j * DEFAULT_DM_COLUMN_SIZE + 1); + row->results[i].buffer_length = 2*j * DEFAULT_DM_COLUMN_SIZE; + + rc = SQLGetData(row->stmt, i+1, SQL_C_CHAR, row->results[i].buffer, row->results[i].buffer_length, &row->results[i].length); + if (SUCCESS(rc)) { + ret = row->results[i].buffer; + } else { + if (dm_get_error_state(SQL_HANDLE_STMT, row->stmt) != BUFFER_NOT_ENOUGH) { + return NULL; + } else { + j++; + goto alloc_buffer; + } + } + } else { + ret = row->results[i].buffer; + } + ret[row->results[i].length] = 0; + + return ret; +} + +static int +dm_db_row_get_column_int (SeafDBRow *vrow, int idx) +{ + const char *str; + char *e; + int ret; + + str = dm_db_row_get_column_string (vrow, idx); + if (!str) { + return 0; + } + + errno = 0; + ret = strtol (str, &e, 10); + if (errno || (e == str)) { + seaf_warning ("Number conversion failed.\n"); + return -1; + } + + return ret; +} + +static gint64 +dm_db_row_get_column_int64 (SeafDBRow *vrow, int idx) +{ + const char *str; + char *e; + gint64 ret; + + str = dm_db_row_get_column_string (vrow, idx); + if (!str) { + return 0; + } + + errno = 0; + ret = strtoll (str, &e, 10); + if (errno || (e == str)) { + seaf_warning ("Number conversion failed.\n"); + return -1; + } + + return ret; +} + +#endif /* HAVE_ODBC */ diff --git a/common/seaf-db.h b/common/seaf-db.h index f33eab82..9d548d3a 100644 --- a/common/seaf-db.h +++ b/common/seaf-db.h @@ -5,6 +5,7 @@ enum { SEAF_DB_TYPE_SQLITE, SEAF_DB_TYPE_MYSQL, SEAF_DB_TYPE_PGSQL, + SEAF_DB_TYPE_DM, }; typedef struct SeafDB SeafDB; @@ -44,6 +45,12 @@ seaf_db_new_pgsql (const char *host, SeafDB * seaf_db_new_sqlite (const char *db_path, int max_connections); +SeafDB * +seaf_db_new_dm (const char *user, + const char *passwd, + const char *db_name, + int max_connections); + int seaf_db_type (SeafDB *db); diff --git a/common/seaf-utils.c b/common/seaf-utils.c index e8db8ee5..5cfd9ac1 100644 --- a/common/seaf-utils.c +++ b/common/seaf-utils.c @@ -145,6 +145,58 @@ mysql_db_start (SeafileSession *session) #endif +#ifdef HAVE_ODBC + +static int +dm_db_start (SeafileSession *session) +{ + char *user, *passwd, *db; + int max_connections = 0; + GError *error = NULL; + + user = seaf_key_file_get_string (session->config, "database", "user", &error); + if (!user) { + seaf_warning ("DB user not set in config.\n"); + return -1; + } + + passwd = seaf_key_file_get_string (session->config, "database", "password", &error); + if (!passwd) { + seaf_warning ("DB passwd not set in config.\n"); + return -1; + } + + db = seaf_key_file_get_string (session->config, "database", "db_name", &error); + if (!db) { + seaf_warning ("DB name not set in config.\n"); + return -1; + } + + if (error) + g_clear_error (&error); + max_connections = g_key_file_get_integer (session->config, + "database", "max_connections", + &error); + if (error || max_connections < 0) { + g_clear_error (&error); + max_connections = DEFAULT_MAX_CONNECTIONS; + } + + session->db = seaf_db_new_dm (user, passwd, db, max_connections); + if (!session->db) { + seaf_warning ("Failed to start dm db.\n"); + return -1; + } + + g_free (user); + g_free (passwd); + g_free (db); + + return 0; +} + +#endif + #ifdef HAVE_POSTGRESQL static int @@ -223,6 +275,11 @@ load_database_config (SeafileSession *session) ret = mysql_db_start (session); } #endif +#ifdef HAVE_ODBC + else if (strcasecmp (type, "dm") == 0) { + ret = dm_db_start (session); + } +#endif #ifdef HAVE_POSTGRESQL else if (strcasecmp (type, "pgsql") == 0) { ret = pgsql_db_start (session); @@ -341,6 +398,56 @@ ccnet_init_mysql_database (SeafileSession *session) #endif +#ifdef HAVE_ODBC + +static int +ccnet_init_dm_database (SeafileSession *session) +{ + char *user, *passwd, *db; + int max_connections = 0; + + user = ccnet_key_file_get_string (session->ccnet_config, "Database", "USER"); + passwd = ccnet_key_file_get_string (session->ccnet_config, "Database", "PASSWD"); + db = ccnet_key_file_get_string (session->ccnet_config, "Database", "DB"); + + if (!user) { + seaf_warning ("DB user not set in config.\n"); + return -1; + } + if (!passwd) { + seaf_warning ("DB passwd not set in config.\n"); + return -1; + } + if (!db) { + seaf_warning ("DB name not set in config.\n"); + return -1; + } + + GError *error = NULL; + + max_connections = g_key_file_get_integer (session->ccnet_config, + "Database", "MAX_CONNECTIONS", + &error); + if (error || max_connections < 0) { + max_connections = DEFAULT_MAX_CONNECTIONS; + g_clear_error (&error); + } + + session->ccnet_db = seaf_db_new_dm (user, passwd, db, max_connections); + if (!session->ccnet_db) { + seaf_warning ("Failed to open ccnet database.\n"); + return -1; + } + + g_free (user); + g_free (passwd); + g_free (db); + + return 0; +} + +#endif + int load_ccnet_database_config (SeafileSession *session) { @@ -359,6 +466,11 @@ load_ccnet_database_config (SeafileSession *session) ret = ccnet_init_mysql_database (session); } #endif +#ifdef HAVE_ODBC + else if (strcasecmp (engine, "dm") == 0) { + ret = ccnet_init_dm_database (session); + } +#endif #if 0 else if (strncasecmp (engine, DB_PGSQL, sizeof(DB_PGSQL)) == 0) { ccnet_debug ("Use database PostgreSQL\n"); diff --git a/common/user-mgr.c b/common/user-mgr.c index 74d14d13..4cb6fcfd 100644 --- a/common/user-mgr.c +++ b/common/user-mgr.c @@ -707,6 +707,67 @@ static int check_db_table (SeafDB *db) // return -1; //} + sql = "CREATE TABLE IF NOT EXISTS LDAPConfig (cfg_group VARCHAR(255) NOT NULL," + "cfg_key VARCHAR(255) NOT NULL, value VARCHAR(255), property INTEGER)"; + if (seaf_db_query (db, sql) < 0) + return -1; + } else if (db_type == SEAF_DB_TYPE_DM) { + sql = "CREATE TABLE IF NOT EXISTS EmailUser (" + "id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT," + "email VARCHAR(255), passwd VARCHAR(256), is_staff INTEGER NOT NULL, " + "is_active INTEGER NOT NULL, ctime BIGINT, " + "reference_id VARCHAR(255))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE UNIQUE INDEX IF NOT EXISTS email_index on EmailUser (email)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE UNIQUE INDEX IF NOT EXISTS reference_id_index on EmailUser (reference_id)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS Binding (email VARCHAR(255), peer_id VARCHAR(41))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS email_index on Binding (email)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE UNIQUE INDEX IF NOT EXISTS peer_index on Binding (peer_id)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS UserRole (email VARCHAR(255), role VARCHAR(255))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS userrole_email_index on UserRole (email)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE UNIQUE INDEX IF NOT EXISTS userrole_userrole_index on UserRole (email, role)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS LDAPUsers (" + "id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, " + "email VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, " + "is_staff INTEGER NOT NULL, is_active INTEGER NOT NULL, extra_attrs TEXT, " + "reference_id VARCHAR(255))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE UNIQUE INDEX IF NOT EXISTS ldapusers_email_index on LDAPUsers(email)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE UNIQUE INDEX IF NOT EXISTS ldapusers_reference_id_index on LDAPUsers(reference_id)"; + if (seaf_db_query (db, sql) < 0) + return -1; + sql = "CREATE TABLE IF NOT EXISTS LDAPConfig (cfg_group VARCHAR(255) NOT NULL," "cfg_key VARCHAR(255) NOT NULL, value VARCHAR(255), property INTEGER)"; if (seaf_db_query (db, sql) < 0) @@ -754,6 +815,7 @@ open_db (CcnetUserManager *manager) db = open_sqlite_db (manager); break; case SEAF_DB_TYPE_PGSQL: + case SEAF_DB_TYPE_DM: case SEAF_DB_TYPE_MYSQL: db = manager->session->ccnet_db; break; diff --git a/configure.ac b/configure.ac index 93c445a3..05a37de0 100644 --- a/configure.ac +++ b/configure.ac @@ -86,6 +86,11 @@ AC_ARG_ENABLE(python, [compile_python=$enableval], [compile_python=yes]) +AC_ARG_ENABLE(odbc, + AC_HELP_STRING([--enable-odbc],[build seafile with odbc]), + [compile_odbc=$enableval], + [compile_odbc=yes]) + AC_ARG_WITH(mysql, AC_HELP_STRING([--with-mysql],[path to mysql_config]), [MYSQL_CONFIG=$with_mysql], @@ -287,6 +292,14 @@ if test "${compile_fuse}" = "yes"; then AC_SUBST(FUSE_LIBS) fi +if test "${compile_odbc}" = "yes"; then + PKG_CHECK_MODULES(ODBC, [odbc]) + AC_SUBST(ODBC_FLAGS) + AC_SUBST(ODBC_LIBS) + AC_DEFINE([DM64], [1], [Define to 1 if dm support is enabled]) + AC_DEFINE([HAVE_ODBC], [1], [Define to 1 if dm support is enabled]) +fi + dnl check libarchive LIBARCHIVE_REQUIRED=2.8.5 PKG_CHECK_MODULES(LIBARCHIVE, [libarchive >= $LIBARCHIVE_REQUIRED]) diff --git a/fuse/Makefile.am b/fuse/Makefile.am index 3f3f1766..c79a152e 100644 --- a/fuse/Makefile.am +++ b/fuse/Makefile.am @@ -42,5 +42,5 @@ seaf_fuse_LDADD = @GLIB2_LIBS@ @GOBJECT_LIBS@ @SSL_LIBS@ @LIB_RT@ @LIB_UUID@ \ -lsqlite3 @LIBEVENT_LIBS@ \ $(top_builddir)/common/cdc/libcdc.la \ @SEARPC_LIBS@ @JANSSON_LIBS@ @FUSE_LIBS@ @ZLIB_LIBS@ \ - @LDAP_LIBS@ @MYSQL_LIBS@ -lsqlite3 + @LDAP_LIBS@ @MYSQL_LIBS@ -lsqlite3 @ODBC_LIBS@ diff --git a/server/Makefile.am b/server/Makefile.am index 88783c26..b69c1a35 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -86,4 +86,4 @@ seaf_server_LDADD = $(top_builddir)/lib/libseafile_common.la \ @SEARPC_LIBS@ @JANSSON_LIBS@ ${LIB_WS32} @ZLIB_LIBS@ \ @LIBARCHIVE_LIBS@ @LIB_ICONV@ \ @LDAP_LIBS@ @MYSQL_LIBS@ -lsqlite3 \ - @CURL_LIBS@ @JWT_LIBS@ + @CURL_LIBS@ @JWT_LIBS@ @ODBC_LIBS@ diff --git a/server/gc/Makefile.am b/server/gc/Makefile.am index e45a4cbb..375b8a42 100644 --- a/server/gc/Makefile.am +++ b/server/gc/Makefile.am @@ -48,7 +48,7 @@ seafserv_gc_LDADD = $(top_builddir)/common/cdc/libcdc.la \ $(top_builddir)/lib/libseafile_common.la \ @GLIB2_LIBS@ @GOBJECT_LIBS@ @SSL_LIBS@ @LIB_RT@ @LIB_UUID@ -lsqlite3 @LIBEVENT_LIBS@ \ @SEARPC_LIBS@ @JANSSON_LIBS@ ${LIB_WS32} @ZLIB_LIBS@ \ - @MYSQL_LIBS@ -lsqlite3 + @MYSQL_LIBS@ -lsqlite3 @ODBC_LIBS@ seaf_fsck_SOURCES = \ seaf-fsck.c \ @@ -59,4 +59,4 @@ seaf_fsck_LDADD = $(top_builddir)/common/cdc/libcdc.la \ $(top_builddir)/lib/libseafile_common.la \ @GLIB2_LIBS@ @GOBJECT_LIBS@ @SSL_LIBS@ @LIB_RT@ @LIB_UUID@ -lsqlite3 @LIBEVENT_LIBS@ \ @SEARPC_LIBS@ @JANSSON_LIBS@ ${LIB_WS32} @ZLIB_LIBS@ \ - @MYSQL_LIBS@ -lsqlite3 + @MYSQL_LIBS@ -lsqlite3 @ODBC_LIBS@ diff --git a/server/gc/repo-mgr.c b/server/gc/repo-mgr.c index 58f21cce..250216dd 100644 --- a/server/gc/repo-mgr.c +++ b/server/gc/repo-mgr.c @@ -427,7 +427,7 @@ seaf_repo_manager_set_repo_history_limit (SeafRepoManager *mgr, return 0; } - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { gboolean err; snprintf(sql, sizeof(sql), "SELECT repo_id FROM RepoHistoryLimit " @@ -512,7 +512,7 @@ seaf_repo_manager_set_repo_valid_since (SeafRepoManager *mgr, SeafDB *db = mgr->seaf->db; char sql[256]; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { gboolean err; snprintf(sql, sizeof(sql), "SELECT repo_id FROM RepoValidSince WHERE " diff --git a/server/quota-mgr.c b/server/quota-mgr.c index e33097f4..81dc4c6d 100644 --- a/server/quota-mgr.c +++ b/server/quota-mgr.c @@ -154,6 +154,28 @@ seaf_quota_manager_init (SeafQuotaManager *mgr) if (seaf_db_query (db, sql) < 0) return -1; + break; + case SEAF_DB_TYPE_DM: + sql = "CREATE TABLE IF NOT EXISTS UserQuota (\"user\" VARCHAR(255) PRIMARY KEY," + "quota BIGINT)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS UserShareQuota (\"user\" VARCHAR(255) PRIMARY KEY," + "quota BIGINT)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS OrgQuota (org_id INTEGER PRIMARY KEY," + "quota BIGINT)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS OrgUserQuota (org_id INTEGER," + "\"user\" VARCHAR(255), quota BIGINT, PRIMARY KEY (org_id, \"user\"))"; + if (seaf_db_query (db, sql) < 0) + return -1; + break; } @@ -166,7 +188,7 @@ seaf_quota_manager_set_user_quota (SeafQuotaManager *mgr, gint64 quota) { SeafDB *db = mgr->session->db; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { gboolean exists, err; int rc; @@ -203,7 +225,7 @@ seaf_quota_manager_get_user_quota (SeafQuotaManager *mgr, char *sql; gint64 quota; - if (seaf_db_type(mgr->session->db) != SEAF_DB_TYPE_PGSQL) + if (seaf_db_type(mgr->session->db) != SEAF_DB_TYPE_PGSQL && seaf_db_type(mgr->session->db) != SEAF_DB_TYPE_DM) sql = "SELECT quota FROM UserQuota WHERE user=?"; else sql = "SELECT quota FROM UserQuota WHERE \"user\"=?"; @@ -223,7 +245,7 @@ seaf_quota_manager_set_org_quota (SeafQuotaManager *mgr, { SeafDB *db = mgr->session->db; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { gboolean exists, err; int rc; @@ -274,7 +296,7 @@ seaf_quota_manager_set_org_user_quota (SeafQuotaManager *mgr, SeafDB *db = mgr->session->db; int rc; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { gboolean exists, err; exists = seaf_db_statement_exists (db, @@ -313,7 +335,7 @@ seaf_quota_manager_get_org_user_quota (SeafQuotaManager *mgr, char *sql; gint64 quota; - if (seaf_db_type(mgr->session->db) != SEAF_DB_TYPE_PGSQL) + if (seaf_db_type(mgr->session->db) != SEAF_DB_TYPE_PGSQL && seaf_db_type(mgr->session->db) != SEAF_DB_TYPE_DM) sql = "SELECT quota FROM OrgUserQuota WHERE org_id=? AND user=?"; else sql = "SELECT quota FROM OrgUserQuota WHERE org_id=? AND \"user\"=?"; diff --git a/server/repo-mgr.c b/server/repo-mgr.c index 84d4fe61..ca991673 100644 --- a/server/repo-mgr.c +++ b/server/repo-mgr.c @@ -380,7 +380,7 @@ seaf_repo_manager_add_repo (SeafRepoManager *manager, static int add_deleted_repo_record (SeafRepoManager *mgr, const char *repo_id) { - if (seaf_db_type(seaf->db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(seaf->db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(seaf->db) == SEAF_DB_TYPE_DM) { gboolean exists, err; exists = seaf_db_statement_exists (seaf->db, @@ -803,7 +803,7 @@ seaf_repo_manager_repo_exists (SeafRepoManager *manager, const gchar *id) static int save_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch) { - if (seaf_db_type(seaf->db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(seaf->db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(seaf->db) == SEAF_DB_TYPE_DM) { gboolean exists, err; int rc; @@ -1240,6 +1240,160 @@ create_tables_sqlite (SeafRepoManager *mgr) return 0; } +static int +create_tables_dm (SeafRepoManager *mgr) +{ + SeafDB *db = mgr->seaf->db; + char *sql; + + sql = "CREATE TABLE IF NOT EXISTS Repo (repo_id VARCHAR(37) PRIMARY KEY)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + /* Owner */ + + sql = "CREATE TABLE IF NOT EXISTS RepoOwner (" + "repo_id VARCHAR(37) PRIMARY KEY, " + "owner_id VARCHAR(255))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + /* Group repo */ + + sql = "CREATE TABLE IF NOT EXISTS RepoGroup (repo_id VARCHAR(37), " + "group_id INTEGER, user_name VARCHAR(255), permission VARCHAR(15))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE UNIQUE INDEX IF NOT EXISTS groupid_repoid_indx on " + "RepoGroup (group_id, repo_id)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS repogroup_repoid_index on " + "RepoGroup (repo_id)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS repogroup_username_indx on " + "RepoGroup (user_name)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + /* Public repo */ + + sql = "CREATE TABLE IF NOT EXISTS InnerPubRepo (" + "repo_id VARCHAR(37) PRIMARY KEY," + "permission VARCHAR(15))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS RepoUserToken (" + "repo_id VARCHAR(37), " + "email VARCHAR(255), " + "token VARCHAR(41))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE UNIQUE INDEX IF NOT EXISTS repo_token_indx on " + "RepoUserToken (repo_id, token)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS repo_token_email_indx on " + "RepoUserToken (email)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS RepoTokenPeerInfo (" + "token VARCHAR(41) PRIMARY KEY, " + "peer_id VARCHAR(41), " + "peer_ip VARCHAR(50), " + "peer_name VARCHAR(255), " + "sync_time BIGINT, " + "client_ver VARCHAR(20))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS RepoHead (" + "repo_id VARCHAR(37) PRIMARY KEY, branch_name VARCHAR(10))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS RepoSize (" + "repo_id VARCHAR(37) PRIMARY KEY," + "size BIGINT," + "head_id VARCHAR(41))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS RepoHistoryLimit (" + "repo_id VARCHAR(37) PRIMARY KEY, days INTEGER)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS RepoValidSince (" + "repo_id VARCHAR(37) PRIMARY KEY, timestamp BIGINT)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS WebAP (repo_id VARCHAR(37) PRIMARY KEY, " + "access_property VARCHAR(10))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS VirtualRepo (repo_id VARCHAR(36) PRIMARY KEY," + "origin_repo VARCHAR(36), path TEXT, base_commit VARCHAR(40))"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS virtualrepo_origin_repo_idx " + "ON VirtualRepo (origin_repo)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS GarbageRepos (repo_id VARCHAR(36) PRIMARY KEY)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS RepoTrash (repo_id VARCHAR(36) PRIMARY KEY," + "repo_name VARCHAR(255), head_id VARCHAR(40), owner_id VARCHAR(255), size BIGINT," + "org_id INTEGER, del_time BIGINT)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS repotrash_owner_id_idx ON RepoTrash(owner_id)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS repotrash_org_id_idx ON RepoTrash(org_id)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS RepoFileCount (" + "repo_id VARCHAR(36) PRIMARY KEY," + "file_count BIGINT)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS RepoInfo (repo_id VARCHAR(36) PRIMARY KEY, " + "name VARCHAR(255) NOT NULL, update_time INTEGER, version INTEGER, " + "is_encrypted INTEGER, last_modifier VARCHAR(255), status INTEGER DEFAULT 0)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE TABLE IF NOT EXISTS WebUploadTempFiles (repo_id VARCHAR(40) NOT NULL, " + "file_path TEXT NOT NULL, tmp_file_path TEXT NOT NULL)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + sql = "CREATE INDEX IF NOT EXISTS webuploadtempfiles_repo_id_idx ON WebUploadTempFiles(repo_id)"; + if (seaf_db_query (db, sql) < 0) + return -1; + + return 0; +} + /* static int */ /* create_tables_pgsql (SeafRepoManager *mgr) */ /* { */ @@ -1402,6 +1556,8 @@ create_db_tables_if_not_exist (SeafRepoManager *mgr) return create_tables_mysql (mgr); else if (db_type == SEAF_DB_TYPE_SQLITE) return create_tables_sqlite (mgr); + else if (db_type == SEAF_DB_TYPE_DM) + return create_tables_dm (mgr); /* else if (db_type == SEAF_DB_TYPE_PGSQL) */ /* return create_tables_pgsql (mgr); */ @@ -1745,7 +1901,7 @@ seaf_repo_manager_delete_repo_tokens_by_peer_id (SeafRepoManager *mgr, g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "DB error"); goto out; } - } else if (db_type == SEAF_DB_TYPE_SQLITE) { + } else if (db_type == SEAF_DB_TYPE_SQLITE || db_type == SEAF_DB_TYPE_DM) { GString *sql = g_string_new (""); GList *iter; int i = 0; @@ -1922,7 +2078,7 @@ seaf_repo_manager_set_repo_history_limit (SeafRepoManager *mgr, return 0; } - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { gboolean exists, err; int rc; @@ -2004,7 +2160,7 @@ seaf_repo_manager_set_repo_valid_since (SeafRepoManager *mgr, { SeafDB *db = mgr->seaf->db; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { gboolean exists, err; int rc; @@ -2086,7 +2242,7 @@ seaf_repo_manager_set_repo_owner (SeafRepoManager *mgr, if (g_strcmp0 (orig_owner, email) == 0) goto out; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { gboolean err; snprintf(sql, sizeof(sql), "SELECT repo_id FROM RepoOwner WHERE repo_id=?"); @@ -2463,6 +2619,21 @@ seaf_repo_manager_search_repos_by_name (SeafRepoManager *mgr, const char *name) char *db_patt = g_strdup_printf ("%%%s%%", name); switch (seaf_db_type(seaf->db)) { + case SEAF_DB_TYPE_DM: + g_free (db_patt); + db_patt = g_strdup(name); + sql = "SELECT i.repo_id, s.size, b.commit_id, i.name, i.update_time, " + "i.version, i.is_encrypted, i.last_modifier, i.status, fc.file_count FROM " + "RepoInfo i LEFT JOIN RepoSize s ON i.repo_id = s.repo_id " + "LEFT JOIN Branch b ON i.repo_id = b.repo_id " + "LEFT JOIN RepoFileCount fc ON i.repo_id = fc.repo_id " + "LEFT JOIN Repo r ON i.repo_id = r.repo_id " + "LEFT JOIN VirtualRepo v ON i.repo_id = v.repo_id " + "WHERE REGEXP_LIKE(i.name, ?, 'i') AND " + "r.repo_id IS NOT NULL AND " + "v.repo_id IS NULL " + "ORDER BY i.update_time DESC, i.repo_id"; + break; case SEAF_DB_TYPE_MYSQL: sql = "SELECT i.repo_id, s.size, b.commit_id, i.name, i.update_time, " "i.version, i.is_encrypted, i.last_modifier, i.status, fc.file_count FROM " @@ -2540,6 +2711,7 @@ seaf_repo_manager_get_repo_list (SeafRepoManager *mgr, int start, int limit, con if (start == -1 && limit == -1) { switch (seaf_db_type(mgr->seaf->db)) { + case SEAF_DB_TYPE_DM: case SEAF_DB_TYPE_MYSQL: g_string_append (sql, "SELECT i.repo_id, s.size, b.commit_id, i.name, i.update_time, " "i.version, i.is_encrypted, i.last_modifier, i.status, f.file_count FROM " @@ -2586,6 +2758,7 @@ seaf_repo_manager_get_repo_list (SeafRepoManager *mgr, int start, int limit, con 0); } else { switch (seaf_db_type(mgr->seaf->db)) { + case SEAF_DB_TYPE_DM: case SEAF_DB_TYPE_MYSQL: g_string_append (sql, "SELECT i.repo_id, s.size, b.commit_id, i.name, i.update_time, " "i.version, i.is_encrypted, i.last_modifier, i.status, f.file_count FROM " @@ -3518,7 +3691,7 @@ seaf_repo_manager_set_inner_pub_repo (SeafRepoManager *mgr, SeafDB *db = mgr->seaf->db; char sql[256]; - if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) { + if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL || seaf_db_type(db) == SEAF_DB_TYPE_DM) { gboolean err; snprintf(sql, sizeof(sql), "SELECT repo_id FROM InnerPubRepo WHERE repo_id=?"); diff --git a/server/seafile-session.c b/server/seafile-session.c index bf586f5a..feea6c58 100644 --- a/server/seafile-session.c +++ b/server/seafile-session.c @@ -470,7 +470,7 @@ schedule_create_system_default_repo (SeafileSession *session) int db_type = seaf_db_type (session->db); char *sql; - if (db_type == SEAF_DB_TYPE_MYSQL) + if (db_type == SEAF_DB_TYPE_MYSQL || db_type == SEAF_DB_TYPE_DM) sql = "CREATE TABLE IF NOT EXISTS SystemInfo " "(id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " "info_key VARCHAR(256), info_value VARCHAR(1024))"; diff --git a/server/share-mgr.c b/server/share-mgr.c index 370b9dad..3ad1bd1e 100644 --- a/server/share-mgr.c +++ b/server/share-mgr.c @@ -56,6 +56,21 @@ seaf_share_manager_start (SeafShareManager *mgr) sql = "CREATE INDEX IF NOT EXISTS ToEmailIndex on SharedRepo (to_email)"; if (seaf_db_query (db, sql) < 0) return -1; + } else if (db_type == SEAF_DB_TYPE_DM) { + sql = "CREATE TABLE IF NOT EXISTS SharedRepo " + "(repo_id VARCHAR(37) , from_email VARCHAR(255), to_email VARCHAR(255), " + "permission VARCHAR(15))"; + if (seaf_db_query (db, sql) < 0) + return -1; + sql = "CREATE INDEX IF NOT EXISTS RepoIdIndex on SharedRepo (repo_id)"; + if (seaf_db_query (db, sql) < 0) + return -1; + sql = "CREATE INDEX IF NOT EXISTS FromEmailIndex on SharedRepo (from_email)"; + if (seaf_db_query (db, sql) < 0) + return -1; + sql = "CREATE INDEX IF NOT EXISTS ToEmailIndex on SharedRepo (to_email)"; + if (seaf_db_query (db, sql) < 0) + return -1; } /* else if (db_type == SEAF_DB_TYPE_PGSQL) { */ /* sql = "CREATE TABLE IF NOT EXISTS SharedRepo " */ diff --git a/server/size-sched.c b/server/size-sched.c index 64807434..d04ed7f0 100644 --- a/server/size-sched.c +++ b/server/size-sched.c @@ -256,6 +256,7 @@ get_old_repo_info_from_db (SeafDB *db, const char *repo_id, gboolean *is_db_err) switch (seaf_db_type (db)) { case SEAF_DB_TYPE_MYSQL: case SEAF_DB_TYPE_PGSQL: + case SEAF_DB_TYPE_DM: sql = "select s.head_id,s.size,f.file_count FROM " "RepoSize s LEFT JOIN RepoFileCount f ON " "s.repo_id=f.repo_id WHERE " diff --git a/tests/test_repo_manipulation/test_repo_manipulation.py b/tests/test_repo_manipulation/test_repo_manipulation.py index 0ecdced2..a1cde90f 100644 --- a/tests/test_repo_manipulation/test_repo_manipulation.py +++ b/tests/test_repo_manipulation/test_repo_manipulation.py @@ -123,3 +123,6 @@ def test_repo_manipulation(): api.remove_repo(t_repo_id) t_repo = api.get_repo(t_repo_id) assert t_repo == None + api.remove_repo(t_enc_repo_id) + t_enc_repo = api.get_repo(t_enc_repo_id) + assert t_enc_repo == None From 88b32aaf90cefbb8b2aef1595ae4a56238d2b644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E8=B5=AB=E7=84=B6?= Date: Thu, 12 Oct 2023 17:31:29 +0800 Subject: [PATCH 2/5] Update statement of create dm tables --- common/group-mgr.c | 6 ++++-- common/org-mgr.c | 6 ++++-- common/user-mgr.c | 9 ++++++--- server/repo-mgr.c | 7 +++++-- server/share-mgr.c | 3 ++- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/common/group-mgr.c b/common/group-mgr.c index 1ed2bb12..a322f4b3 100644 --- a/common/group-mgr.c +++ b/common/group-mgr.c @@ -237,7 +237,8 @@ static int check_db_table (CcnetGroupManager *manager, CcnetDB *db) return -1; } - sql = "CREATE TABLE IF NOT EXISTS GroupUser (group_id INTEGER, " + sql = "CREATE TABLE IF NOT EXISTS GroupUser (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " + "group_id INTEGER, " "user_name VARCHAR(255), is_staff tinyint)"; if (seaf_db_query (db, sql) < 0) return -1; @@ -252,7 +253,8 @@ static int check_db_table (CcnetGroupManager *manager, CcnetDB *db) if (seaf_db_query (db, sql) < 0) return -1; - sql = "CREATE TABLE IF NOT EXISTS GroupDNPair (group_id INTEGER," + sql = "CREATE TABLE IF NOT EXISTS GroupDNPair (d BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " + "group_id INTEGER," " dn VARCHAR(255))"; if (seaf_db_query (db, sql) < 0) return -1; diff --git a/common/org-mgr.c b/common/org-mgr.c index 646f18d4..881cead6 100644 --- a/common/org-mgr.c +++ b/common/org-mgr.c @@ -217,7 +217,8 @@ static int check_db_table (CcnetDB *db) if (seaf_db_query (db, sql) < 0) return -1; - sql = "CREATE TABLE IF NOT EXISTS OrgUser (org_id INTEGER, " + sql = "CREATE TABLE IF NOT EXISTS OrgUser (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " + "org_id INTEGER, " "email VARCHAR(255), is_staff INTEGER NOT NULL)"; if (seaf_db_query (db, sql) < 0) return -1; @@ -232,7 +233,8 @@ static int check_db_table (CcnetDB *db) if (seaf_db_query (db, sql) < 0) return -1; - sql = "CREATE TABLE IF NOT EXISTS OrgGroup (org_id INTEGER, " + sql = "CREATE TABLE IF NOT EXISTS OrgGroup (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " + "org_id INTEGER, " "group_id INTEGER)"; if (seaf_db_query (db, sql) < 0) return -1; diff --git a/common/user-mgr.c b/common/user-mgr.c index 4cb6fcfd..d65ed241 100644 --- a/common/user-mgr.c +++ b/common/user-mgr.c @@ -728,7 +728,8 @@ static int check_db_table (SeafDB *db) if (seaf_db_query (db, sql) < 0) return -1; - sql = "CREATE TABLE IF NOT EXISTS Binding (email VARCHAR(255), peer_id VARCHAR(41))"; + sql = "CREATE TABLE IF NOT EXISTS Binding (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " + "email VARCHAR(255), peer_id VARCHAR(41))"; if (seaf_db_query (db, sql) < 0) return -1; @@ -740,7 +741,8 @@ static int check_db_table (SeafDB *db) if (seaf_db_query (db, sql) < 0) return -1; - sql = "CREATE TABLE IF NOT EXISTS UserRole (email VARCHAR(255), role VARCHAR(255))"; + sql = "CREATE TABLE IF NOT EXISTS UserRole (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " + "email VARCHAR(255), role VARCHAR(255))"; if (seaf_db_query (db, sql) < 0) return -1; @@ -768,7 +770,8 @@ static int check_db_table (SeafDB *db) if (seaf_db_query (db, sql) < 0) return -1; - sql = "CREATE TABLE IF NOT EXISTS LDAPConfig (cfg_group VARCHAR(255) NOT NULL," + sql = "CREATE TABLE IF NOT EXISTS LDAPConfig (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " + "cfg_group VARCHAR(255) NOT NULL," "cfg_key VARCHAR(255) NOT NULL, value VARCHAR(255), property INTEGER)"; if (seaf_db_query (db, sql) < 0) return -1; diff --git a/server/repo-mgr.c b/server/repo-mgr.c index ca991673..1d6176ac 100644 --- a/server/repo-mgr.c +++ b/server/repo-mgr.c @@ -1260,7 +1260,8 @@ create_tables_dm (SeafRepoManager *mgr) /* Group repo */ - sql = "CREATE TABLE IF NOT EXISTS RepoGroup (repo_id VARCHAR(37), " + sql = "CREATE TABLE IF NOT EXISTS RepoGroup (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT," + "repo_id VARCHAR(37), " "group_id INTEGER, user_name VARCHAR(255), permission VARCHAR(15))"; if (seaf_db_query (db, sql) < 0) return -1; @@ -1289,6 +1290,7 @@ create_tables_dm (SeafRepoManager *mgr) return -1; sql = "CREATE TABLE IF NOT EXISTS RepoUserToken (" + "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " "repo_id VARCHAR(37), " "email VARCHAR(255), " "token VARCHAR(41))"; @@ -1382,7 +1384,8 @@ create_tables_dm (SeafRepoManager *mgr) if (seaf_db_query (db, sql) < 0) return -1; - sql = "CREATE TABLE IF NOT EXISTS WebUploadTempFiles (repo_id VARCHAR(40) NOT NULL, " + sql = "CREATE TABLE IF NOT EXISTS WebUploadTempFiles (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " + "repo_id VARCHAR(40) NOT NULL, " "file_path TEXT NOT NULL, tmp_file_path TEXT NOT NULL)"; if (seaf_db_query (db, sql) < 0) return -1; diff --git a/server/share-mgr.c b/server/share-mgr.c index 3ad1bd1e..044e98aa 100644 --- a/server/share-mgr.c +++ b/server/share-mgr.c @@ -58,7 +58,8 @@ seaf_share_manager_start (SeafShareManager *mgr) return -1; } else if (db_type == SEAF_DB_TYPE_DM) { sql = "CREATE TABLE IF NOT EXISTS SharedRepo " - "(repo_id VARCHAR(37) , from_email VARCHAR(255), to_email VARCHAR(255), " + "(id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT," + "repo_id VARCHAR(37) , from_email VARCHAR(255), to_email VARCHAR(255), " "permission VARCHAR(15))"; if (seaf_db_query (db, sql) < 0) return -1; From c95fc7d9434ec18ecc2df83cf1ead0e48872f758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E8=B5=AB=E7=84=B6?= Date: Fri, 13 Oct 2023 17:05:20 +0800 Subject: [PATCH 3/5] Handle bind empty string --- common/seaf-db.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/common/seaf-db.c b/common/seaf-db.c index 58e246f4..1191abc0 100644 --- a/common/seaf-db.c +++ b/common/seaf-db.c @@ -1979,7 +1979,7 @@ _bind_params_dm (HSTMT stmt, DM_BIND *params, int n, va_list args) ret = SQLBindParameter(stmt, i+1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, sizeof(x), 0, params[i].buffer, sizeof(x), NULL); if (!SUCCESS(ret)) { errmsg = dm_db_error (SQL_HANDLE_STMT, stmt); - seaf_warning ("Failed to bid params: %s\n", errmsg); + seaf_warning ("Failed to bind params: %s\n", errmsg); g_free (errmsg); return -1; } @@ -1991,7 +1991,7 @@ _bind_params_dm (HSTMT stmt, DM_BIND *params, int n, va_list args) ret = SQLBindParameter(stmt, i+1, SQL_PARAM_INPUT, SQL_C_SBIGINT, SQL_BIGINT, sizeof(x), 0, params[i].buffer, sizeof(x), NULL); if (!SUCCESS(ret)) { errmsg = dm_db_error (SQL_HANDLE_STMT, stmt); - seaf_warning ("Failed to bid params: %s\n", errmsg); + seaf_warning ("Failed to bind params: %s\n", errmsg); g_free (errmsg); return -1; } @@ -2001,11 +2001,15 @@ _bind_params_dm (HSTMT stmt, DM_BIND *params, int n, va_list args) if (s) { len = strlen(s); } + // SQLBindParameter bind an empty string, need to set len to 1. + if (len==0) { + len=1; + } params[i].buffer = g_strdup(s); ret = SQLBindParameter(stmt, i+1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, len, 0, params[i].buffer, len, NULL); if (!SUCCESS(ret)) { errmsg = dm_db_error (SQL_HANDLE_STMT, stmt); - seaf_warning ("Failed to bid params: %s\n", errmsg); + seaf_warning ("Failed to bind params: %s\n", errmsg); g_free (errmsg); return -1; } From 74f0ef968c3b5a3faf4af45ddc3835e2c9e7a4a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E8=B5=AB=E7=84=B6?= Date: Mon, 16 Oct 2023 10:06:40 +0800 Subject: [PATCH 4/5] Go support dm dtabase --- fileserver/fileserver.go | 50 ++++++++++++++++++++++++++++++++++- fileserver/go.mod | 5 ++-- fileserver/go.sum | 10 +++++-- fileserver/quota.go | 8 +++++- fileserver/repomgr/repomgr.go | 6 ----- fileserver/share/share.go | 18 +++++++++---- fileserver/sync_api.go | 10 ++++--- 7 files changed, 87 insertions(+), 20 deletions(-) diff --git a/fileserver/fileserver.go b/fileserver/fileserver.go index 9b84ec90..66a72bbe 100644 --- a/fileserver/fileserver.go +++ b/fileserver/fileserver.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "crypto/x509" "database/sql" + _ "dm" "flag" "fmt" "io" @@ -155,6 +156,29 @@ func loadCcnetDB() { if err != nil { log.Fatalf("Failed to open database %s: %v", ccnetDBPath, err) } + } else if strings.EqualFold(dbEngine, "dm") { + if key, err = section.GetKey("HOST"); err != nil { + log.Fatal("No database host in ccnet.conf.") + } + host := key.String() + // user is required. + if key, err = section.GetKey("USER"); err != nil { + log.Fatal("No database user in ccnet.conf.") + } + user := key.String() + if key, err = section.GetKey("PASSWD"); err != nil { + log.Fatal("No database password in ccnet.conf.") + } + password := key.String() + port := 5236 + if key, err = section.GetKey("PORT"); err == nil { + port, _ = key.Int() + } + dsn := fmt.Sprintf("dm://%s:%s@%s:%d", user, password, host, port) + ccnetDB, err = sql.Open("dm", dsn) + if err != nil { + log.Fatalf("Failed to open dm database: %v", err) + } } else { log.Fatalf("Unsupported database %s.", dbEngine) } @@ -263,6 +287,30 @@ func loadSeafileDB() { if err != nil { log.Fatalf("Failed to open database %s: %v", seafileDBPath, err) } + } else if strings.EqualFold(dbEngine, "dm") { + if key, err = section.GetKey("host"); err != nil { + log.Fatal("No database host in seafile.conf.") + } + host := key.String() + // user is required. + if key, err = section.GetKey("user"); err != nil { + log.Fatal("No database user in seafile.conf.") + } + user := key.String() + + if key, err = section.GetKey("password"); err != nil { + log.Fatal("No database password in seafile.conf.") + } + password := key.String() + port := 5236 + if key, err = section.GetKey("port"); err == nil { + port, _ = key.Int() + } + dsn := fmt.Sprintf("dm://%s:%s@%s:%d", user, password, host, port) + seafileDB, err = sql.Open("dm", dsn) + if err != nil { + log.Fatalf("Failed to open dm database: %v", err) + } } else { log.Fatalf("Unsupported database %s.", dbEngine) } @@ -373,7 +421,7 @@ func main() { commitmgr.Init(centralDir, dataDir) - share.Init(ccnetDB, seafileDB, option.GroupTableName, option.CloudMode) + share.Init(ccnetDB, seafileDB, option.GroupTableName, option.CloudMode, dbType) rpcClientInit() diff --git a/fileserver/go.mod b/fileserver/go.mod index 75b78678..62c9e6ab 100644 --- a/fileserver/go.mod +++ b/fileserver/go.mod @@ -11,7 +11,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-sqlite3 v1.14.0 github.com/sirupsen/logrus v1.8.1 - golang.org/x/text v0.3.7 + golang.org/x/text v0.13.0 gopkg.in/ini.v1 v1.55.0 ) @@ -19,9 +19,10 @@ require ( github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/smartystreets/goconvey v1.6.4 // indirect - golang.org/x/sys v0.0.0-20221010170243-090e33056c14 // indirect + golang.org/x/sys v0.5.0 // indirect ) diff --git a/fileserver/go.sum b/fileserver/go.sum index f4660cdb..7cbdbddb 100644 --- a/fileserver/go.sum +++ b/fileserver/go.sum @@ -7,16 +7,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -59,9 +61,13 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/fileserver/quota.go b/fileserver/quota.go index b82c1c6a..824955af 100644 --- a/fileserver/quota.go +++ b/fileserver/quota.go @@ -3,6 +3,7 @@ package main import ( "database/sql" "fmt" + "strings" "github.com/haiwen/seafile-server/fileserver/option" "github.com/haiwen/seafile-server/fileserver/repomgr" @@ -62,7 +63,12 @@ func checkQuota(repoID string, delta int64) (int, error) { func getUserQuota(user string) (int64, error) { var quota int64 - sqlStr := "SELECT quota FROM UserQuota WHERE user=?" + sqlStr := "" + if strings.EqualFold(dbType, "mysql") { + sqlStr = "SELECT quota FROM UserQuota WHERE user=?" + } else { + sqlStr = "SELECT quota FROM UserQuota WHERE \"user\"=?" + } row := seafileDB.QueryRow(sqlStr, user) if err := row.Scan("a); err != nil { if err != sql.ErrNoRows { diff --git a/fileserver/repomgr/repomgr.go b/fileserver/repomgr/repomgr.go index a6171971..b945bf03 100644 --- a/fileserver/repomgr/repomgr.go +++ b/fileserver/repomgr/repomgr.go @@ -607,12 +607,6 @@ func removeVirtualRepoOndisk(repoID string, cloudMode bool) error { if err != nil { return err } - } else { - sqlStr = "REPLACE INTO GarbageRepos (repo_id) VALUES (?)" - _, err := seafileDB.Exec(sqlStr, repoID) - if err != nil { - return err - } } return nil diff --git a/fileserver/share/share.go b/fileserver/share/share.go index 8a77963b..510b3c2f 100644 --- a/fileserver/share/share.go +++ b/fileserver/share/share.go @@ -25,13 +25,15 @@ var ccnetDB *sql.DB var seafileDB *sql.DB var groupTableName string var cloudMode bool +var dbType string // Init ccnetDB, seafileDB, groupTableName, cloudMode -func Init(cnDB *sql.DB, seafDB *sql.DB, grpTableName string, clMode bool) { +func Init(cnDB *sql.DB, seafDB *sql.DB, grpTableName string, clMode bool, dType string) { ccnetDB = cnDB seafileDB = seafDB groupTableName = grpTableName cloudMode = clMode + dbType = dType } // CheckPerm get user's repo permission @@ -95,9 +97,15 @@ func getUserGroups(sqlStr string, args ...interface{}) ([]group, error) { } func getGroupsByUser(userName string, returnAncestors bool) ([]group, error) { + tableName := "" + if strings.EqualFold(dbType, "mysql") { + tableName = fmt.Sprintf("`%s`", groupTableName) + } else { + tableName = fmt.Sprintf("\"%s\"", groupTableName) + } sqlStr := fmt.Sprintf("SELECT g.group_id, group_name, creator_name, timestamp, parent_group_id FROM "+ - "`%s` g, GroupUser u WHERE g.group_id = u.group_id AND user_name=? ORDER BY g.group_id DESC", - groupTableName) + "%s g, GroupUser u WHERE g.group_id = u.group_id AND user_name=? ORDER BY g.group_id DESC", + tableName) groups, err := getUserGroups(sqlStr, userName) if err != nil { err := fmt.Errorf("Failed to get groups by user %s: %v", userName, err) @@ -135,8 +143,8 @@ func getGroupsByUser(userName string, returnAncestors bool) ([]group, error) { } sqlStr = fmt.Sprintf("SELECT g.group_id, group_name, creator_name, timestamp, parent_group_id FROM "+ - "`%s` g WHERE g.group_id IN (%s) ORDER BY g.group_id DESC", - groupTableName, paths) + "%s g WHERE g.group_id IN (%s) ORDER BY g.group_id DESC", + tableName, paths) groups, err := getUserGroups(sqlStr) if err != nil { return nil, err diff --git a/fileserver/sync_api.go b/fileserver/sync_api.go index 4a0161ca..16eb6e34 100644 --- a/fileserver/sync_api.go +++ b/fileserver/sync_api.go @@ -638,10 +638,14 @@ func headCommitsMultiCB(rsp http.ResponseWriter, r *http.Request) *appError { } } + mode := "" + if strings.EqualFold(dbType, "mysql") { + mode = "LOCK IN SHARE MODE" + } sqlStr := fmt.Sprintf( "SELECT repo_id, commit_id FROM Branch WHERE name='master' AND "+ - "repo_id IN (%s) LOCK IN SHARE MODE", - repoIDs.String()) + "repo_id IN (%s) %s", + repoIDs.String(), mode) rows, err := seafileDB.Query(sqlStr) if err != nil { @@ -1068,7 +1072,7 @@ func putUpdateBranchCB(rsp http.ResponseWriter, r *http.Request) *appError { func getHeadCommit(rsp http.ResponseWriter, r *http.Request) *appError { vars := mux.Vars(r) repoID := vars["repoid"] - sqlStr := "SELECT EXISTS(SELECT 1 FROM Repo WHERE repo_id=?)" + sqlStr := "SELECT 1 FROM Repo WHERE repo_id=?" var exists bool row := seafileDB.QueryRow(sqlStr, repoID) if err := row.Scan(&exists); err != nil { From 59f4251f4c9bad7d8a5e6a1ef1cebb9908af75be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E8=B5=AB=E7=84=B6?= Date: Tue, 17 Oct 2023 14:25:39 +0800 Subject: [PATCH 5/5] Add tags to build dm database --- common/group-mgr.c | 2 +- fileserver/fileserver.go | 4 +- fileserver/fileserver_dm.go | 600 ++++++++++++++++++++++++++++++++++++ 3 files changed, 604 insertions(+), 2 deletions(-) create mode 100644 fileserver/fileserver_dm.go diff --git a/common/group-mgr.c b/common/group-mgr.c index a322f4b3..7431d802 100644 --- a/common/group-mgr.c +++ b/common/group-mgr.c @@ -253,7 +253,7 @@ static int check_db_table (CcnetGroupManager *manager, CcnetDB *db) if (seaf_db_query (db, sql) < 0) return -1; - sql = "CREATE TABLE IF NOT EXISTS GroupDNPair (d BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " + sql = "CREATE TABLE IF NOT EXISTS GroupDNPair (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, " "group_id INTEGER," " dn VARCHAR(255))"; if (seaf_db_query (db, sql) < 0) diff --git a/fileserver/fileserver.go b/fileserver/fileserver.go index 66a72bbe..de6a97f2 100644 --- a/fileserver/fileserver.go +++ b/fileserver/fileserver.go @@ -1,3 +1,6 @@ +//go:build !dm +// +build !dm + // Main package for Seafile file server. package main @@ -5,7 +8,6 @@ import ( "crypto/tls" "crypto/x509" "database/sql" - _ "dm" "flag" "fmt" "io" diff --git a/fileserver/fileserver_dm.go b/fileserver/fileserver_dm.go new file mode 100644 index 00000000..ee12a246 --- /dev/null +++ b/fileserver/fileserver_dm.go @@ -0,0 +1,600 @@ +//go:build dm +// +build dm + +// Main package for Seafile file server. +package main + +import ( + "crypto/tls" + "crypto/x509" + "database/sql" + _ "dm" + "flag" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "os/signal" + "path/filepath" + "runtime/debug" + "strings" + "syscall" + + "github.com/go-sql-driver/mysql" + "github.com/gorilla/mux" + "github.com/haiwen/seafile-server/fileserver/blockmgr" + "github.com/haiwen/seafile-server/fileserver/commitmgr" + "github.com/haiwen/seafile-server/fileserver/fsmgr" + "github.com/haiwen/seafile-server/fileserver/option" + "github.com/haiwen/seafile-server/fileserver/repomgr" + "github.com/haiwen/seafile-server/fileserver/searpc" + "github.com/haiwen/seafile-server/fileserver/share" + _ "github.com/mattn/go-sqlite3" + log "github.com/sirupsen/logrus" + "gopkg.in/ini.v1" + + "net/http/pprof" +) + +var dataDir, absDataDir string +var centralDir string +var logFile, absLogFile string +var rpcPipePath string +var pidFilePath string +var logFp *os.File + +var dbType string +var seafileDB, ccnetDB *sql.DB + +// when SQLite is used, user and group db are separated. +var userDB, groupDB *sql.DB + +func init() { + flag.StringVar(¢ralDir, "F", "", "central config directory") + flag.StringVar(&dataDir, "d", "", "seafile data directory") + flag.StringVar(&logFile, "l", "", "log file path") + flag.StringVar(&rpcPipePath, "p", "", "rpc pipe path") + flag.StringVar(&pidFilePath, "P", "", "pid file path") + + log.SetFormatter(&LogFormatter{}) +} + +const ( + timestampFormat = "[2006-01-02 15:04:05] " +) + +type LogFormatter struct{} + +func (f *LogFormatter) Format(entry *log.Entry) ([]byte, error) { + buf := make([]byte, 0, len(timestampFormat)+len(entry.Message)+1) + buf = entry.Time.AppendFormat(buf, timestampFormat) + buf = append(buf, entry.Message...) + buf = append(buf, '\n') + return buf, nil +} + +func loadCcnetDB() { + ccnetConfPath := filepath.Join(centralDir, "ccnet.conf") + opts := ini.LoadOptions{} + opts.SpaceBeforeInlineComment = true + config, err := ini.LoadSources(opts, ccnetConfPath) + if err != nil { + log.Fatalf("Failed to load ccnet.conf: %v", err) + } + + section, err := config.GetSection("Database") + if err != nil { + log.Fatal("No database section in ccnet.conf.") + } + + var dbEngine string = "sqlite" + key, err := section.GetKey("ENGINE") + if err == nil { + dbEngine = key.String() + } + + if strings.EqualFold(dbEngine, "mysql") { + unixSocket := "" + if key, err = section.GetKey("UNIX_SOCKET"); err == nil { + unixSocket = key.String() + } + host := "" + if key, err = section.GetKey("HOST"); err == nil { + host = key.String() + } else if unixSocket == "" { + log.Fatal("No database host in ccnet.conf.") + } + // user is required. + if key, err = section.GetKey("USER"); err != nil { + log.Fatal("No database user in ccnet.conf.") + } + user := key.String() + password := "" + if key, err = section.GetKey("PASSWD"); err == nil { + password = key.String() + } else if unixSocket == "" { + log.Fatal("No database password in ccnet.conf.") + } + if key, err = section.GetKey("DB"); err != nil { + log.Fatal("No database db_name in ccnet.conf.") + } + dbName := key.String() + port := 3306 + if key, err = section.GetKey("PORT"); err == nil { + port, _ = key.Int() + } + useTLS := false + if key, err = section.GetKey("USE_SSL"); err == nil { + useTLS, _ = key.Bool() + } + skipVerify := false + if key, err = section.GetKey("SKIP_VERIFY"); err == nil { + skipVerify, _ = key.Bool() + } + var dsn string + if unixSocket == "" { + if useTLS && skipVerify { + dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=skip-verify", user, password, host, port, dbName) + } else if useTLS && !skipVerify { + capath := "" + if key, err = section.GetKey("CA_PATH"); err == nil { + capath = key.String() + } + registerCA(capath) + dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=custom", user, password, host, port, dbName) + } else { + dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=%t", user, password, host, port, dbName, useTLS) + } + } else { + dsn = fmt.Sprintf("%s:%s@unix(%s)/%s", user, password, unixSocket, dbName) + } + ccnetDB, err = sql.Open("mysql", dsn) + if err != nil { + log.Fatalf("Failed to open database: %v", err) + } + } else if strings.EqualFold(dbEngine, "sqlite") { + ccnetDBPath := filepath.Join(centralDir, "groupmgr.db") + ccnetDB, err = sql.Open("sqlite3", ccnetDBPath) + if err != nil { + log.Fatalf("Failed to open database %s: %v", ccnetDBPath, err) + } + } else if strings.EqualFold(dbEngine, "dm") { + if key, err = section.GetKey("HOST"); err != nil { + log.Fatal("No database host in ccnet.conf.") + } + host := key.String() + // user is required. + if key, err = section.GetKey("USER"); err != nil { + log.Fatal("No database user in ccnet.conf.") + } + user := key.String() + if key, err = section.GetKey("PASSWD"); err != nil { + log.Fatal("No database password in ccnet.conf.") + } + password := key.String() + port := 5236 + if key, err = section.GetKey("PORT"); err == nil { + port, _ = key.Int() + } + dsn := fmt.Sprintf("dm://%s:%s@%s:%d", user, password, host, port) + ccnetDB, err = sql.Open("dm", dsn) + if err != nil { + log.Fatalf("Failed to open dm database: %v", err) + } + } else { + log.Fatalf("Unsupported database %s.", dbEngine) + } +} + +// registerCA registers CA to verify server cert. +func registerCA(capath string) { + rootCertPool := x509.NewCertPool() + pem, err := ioutil.ReadFile(capath) + if err != nil { + log.Fatal(err) + } + if ok := rootCertPool.AppendCertsFromPEM(pem); !ok { + log.Fatal("Failed to append PEM.") + } + mysql.RegisterTLSConfig("custom", &tls.Config{ + RootCAs: rootCertPool, + }) +} + +func loadSeafileDB() { + seafileConfPath := filepath.Join(centralDir, "seafile.conf") + + opts := ini.LoadOptions{} + opts.SpaceBeforeInlineComment = true + config, err := ini.LoadSources(opts, seafileConfPath) + if err != nil { + log.Fatalf("Failed to load seafile.conf: %v", err) + } + + section, err := config.GetSection("database") + if err != nil { + log.Fatal("No database section in seafile.conf.") + } + + var dbEngine string = "sqlite" + key, err := section.GetKey("type") + if err == nil { + dbEngine = key.String() + } + if strings.EqualFold(dbEngine, "mysql") { + unixSocket := "" + if key, err = section.GetKey("unix_socket"); err == nil { + unixSocket = key.String() + } + host := "" + if key, err = section.GetKey("host"); err == nil { + host = key.String() + } else if unixSocket == "" { + log.Fatal("No database host in seafile.conf.") + } + // user is required. + if key, err = section.GetKey("user"); err != nil { + log.Fatal("No database user in seafile.conf.") + } + user := key.String() + + password := "" + if key, err = section.GetKey("password"); err == nil { + password = key.String() + } else if unixSocket == "" { + log.Fatal("No database password in seafile.conf.") + } + if key, err = section.GetKey("db_name"); err != nil { + log.Fatal("No database db_name in seafile.conf.") + } + dbName := key.String() + port := 3306 + if key, err = section.GetKey("port"); err == nil { + port, _ = key.Int() + } + useTLS := false + if key, err = section.GetKey("use_ssl"); err == nil { + useTLS, _ = key.Bool() + } + skipVerify := false + if key, err = section.GetKey("skip_verify"); err == nil { + skipVerify, _ = key.Bool() + } + + var dsn string + if unixSocket == "" { + if useTLS && skipVerify { + dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=skip-verify", user, password, host, port, dbName) + } else if useTLS && !skipVerify { + capath := "" + if key, err = section.GetKey("ca_path"); err == nil { + capath = key.String() + } + registerCA(capath) + dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=custom", user, password, host, port, dbName) + } else { + dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=%t", user, password, host, port, dbName, useTLS) + } + } else { + dsn = fmt.Sprintf("%s:%s@unix(%s)/%s", user, password, unixSocket, dbName) + } + + seafileDB, err = sql.Open("mysql", dsn) + if err != nil { + log.Fatalf("Failed to open database: %v", err) + } + } else if strings.EqualFold(dbEngine, "sqlite") { + seafileDBPath := filepath.Join(absDataDir, "seafile.db") + seafileDB, err = sql.Open("sqlite3", seafileDBPath) + if err != nil { + log.Fatalf("Failed to open database %s: %v", seafileDBPath, err) + } + } else if strings.EqualFold(dbEngine, "dm") { + if key, err = section.GetKey("host"); err != nil { + log.Fatal("No database host in seafile.conf.") + } + host := key.String() + // user is required. + if key, err = section.GetKey("user"); err != nil { + log.Fatal("No database user in seafile.conf.") + } + user := key.String() + + if key, err = section.GetKey("password"); err != nil { + log.Fatal("No database password in seafile.conf.") + } + password := key.String() + port := 5236 + if key, err = section.GetKey("port"); err == nil { + port, _ = key.Int() + } + dsn := fmt.Sprintf("dm://%s:%s@%s:%d", user, password, host, port) + seafileDB, err = sql.Open("dm", dsn) + if err != nil { + log.Fatalf("Failed to open dm database: %v", err) + } + } else { + log.Fatalf("Unsupported database %s.", dbEngine) + } + dbType = dbEngine +} + +func writePidFile(pid_file_path string) error { + file, err := os.OpenFile(pid_file_path, os.O_CREATE|os.O_WRONLY, 0664) + if err != nil { + return err + } + defer file.Close() + + pid := os.Getpid() + str := fmt.Sprintf("%d", pid) + _, err = file.Write([]byte(str)) + + if err != nil { + return err + } + return nil +} + +func removePidfile(pid_file_path string) error { + err := os.Remove(pid_file_path) + if err != nil { + return err + } + return nil +} + +func main() { + flag.Parse() + + if centralDir == "" { + log.Fatal("central config directory must be specified.") + } + + if pidFilePath != "" { + if writePidFile(pidFilePath) != nil { + log.Fatal("write pid file failed.") + } + } + _, err := os.Stat(centralDir) + if os.IsNotExist(err) { + log.Fatalf("central config directory %s doesn't exist: %v.", centralDir, err) + } + loadCcnetDB() + + if dataDir == "" { + log.Fatal("seafile data directory must be specified.") + } + _, err = os.Stat(dataDir) + if os.IsNotExist(err) { + log.Fatalf("seafile data directory %s doesn't exist: %v.", dataDir, err) + } + absDataDir, err = filepath.Abs(dataDir) + if err != nil { + log.Fatalf("Failed to convert seafile data dir to absolute path: %v.", err) + } + loadSeafileDB() + option.LoadFileServerOptions(centralDir) + + if logFile == "" { + absLogFile = filepath.Join(absDataDir, "fileserver.log") + fp, err := os.OpenFile(absLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + if err != nil { + log.Fatalf("Failed to open or create log file: %v", err) + } + logFp = fp + log.SetOutput(fp) + } else if logFile != "-" { + absLogFile, err = filepath.Abs(logFile) + if err != nil { + log.Fatalf("Failed to convert log file path to absolute path: %v", err) + } + fp, err := os.OpenFile(absLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + if err != nil { + log.Fatalf("Failed to open or create log file: %v", err) + } + logFp = fp + log.SetOutput(fp) + } + // When logFile is "-", use default output (StdOut) + + level, err := log.ParseLevel(option.LogLevel) + if err != nil { + log.Info("use the default log level: info") + log.SetLevel(log.InfoLevel) + } else { + log.SetLevel(level) + } + + if absLogFile != "" { + errorLogFile := filepath.Join(filepath.Dir(absLogFile), "fileserver-error.log") + fp, err := os.OpenFile(errorLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + if err != nil { + log.Fatalf("Failed to open or create error log file: %v", err) + } + syscall.Dup3(int(fp.Fd()), int(os.Stderr.Fd()), 0) + } + + repomgr.Init(seafileDB) + + fsmgr.Init(centralDir, dataDir, option.FsCacheLimit) + + blockmgr.Init(centralDir, dataDir) + + commitmgr.Init(centralDir, dataDir) + + share.Init(ccnetDB, seafileDB, option.GroupTableName, option.CloudMode, dbType) + + rpcClientInit() + + fileopInit() + + syncAPIInit() + + sizeSchedulerInit() + + virtualRepoInit() + + initUpload() + + router := newHTTPRouter() + + go handleSignals() + go handleUser1Singal() + + log.Print("Seafile file server started.") + + addr := fmt.Sprintf("%s:%d", option.Host, option.Port) + err = http.ListenAndServe(addr, router) + if err != nil { + log.Printf("File server exiting: %v", err) + } +} + +func handleSignals() { + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + <-signalChan + removePidfile(pidFilePath) + os.Exit(0) +} + +func handleUser1Singal() { + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, syscall.SIGUSR1) + <-signalChan + + for { + select { + case <-signalChan: + logRotate() + } + } +} + +func logRotate() { + // reopen fileserver log + fp, err := os.OpenFile(absLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + if err != nil { + log.Fatalf("Failed to reopen fileserver log: %v", err) + } + log.SetOutput(fp) + if logFp != nil { + logFp.Close() + logFp = fp + } +} + +var rpcclient *searpc.Client + +func rpcClientInit() { + var pipePath string + if rpcPipePath != "" { + pipePath = filepath.Join(rpcPipePath, "seafile.sock") + } else { + pipePath = filepath.Join(absDataDir, "seafile.sock") + } + rpcclient = searpc.Init(pipePath, "seafserv-threaded-rpcserver") +} + +func newHTTPRouter() *mux.Router { + r := mux.NewRouter() + r.HandleFunc("/protocol-version{slash:\\/?}", handleProtocolVersion) + r.Handle("/files/{.*}/{.*}", appHandler(accessCB)) + r.Handle("/blks/{.*}/{.*}", appHandler(accessBlksCB)) + r.Handle("/zip/{.*}", appHandler(accessZipCB)) + r.Handle("/upload-api/{.*}", appHandler(uploadAPICB)) + r.Handle("/upload-aj/{.*}", appHandler(uploadAjaxCB)) + r.Handle("/update-api/{.*}", appHandler(updateAPICB)) + r.Handle("/update-aj/{.*}", appHandler(updateAjaxCB)) + r.Handle("/upload-blks-api/{.*}", appHandler(uploadBlksAPICB)) + r.Handle("/upload-raw-blks-api/{.*}", appHandler(uploadRawBlksAPICB)) + // file syncing api + r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/permission-check{slash:\\/?}", + appHandler(permissionCheckCB)) + r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/commit/HEAD{slash:\\/?}", + appHandler(headCommitOperCB)) + r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/commit/{id:[\\da-z]{40}}", + appHandler(commitOperCB)) + r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/block/{id:[\\da-z]{40}}", + appHandler(blockOperCB)) + r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/fs-id-list{slash:\\/?}", + appHandler(getFsObjIDCB)) + r.Handle("/repo/head-commits-multi{slash:\\/?}", + appHandler(headCommitsMultiCB)) + r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/pack-fs{slash:\\/?}", + appHandler(packFSCB)) + r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/check-fs{slash:\\/?}", + appHandler(checkFSCB)) + r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/check-blocks{slash:\\/?}", + appHandler(checkBlockCB)) + r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/recv-fs{slash:\\/?}", + appHandler(recvFSCB)) + r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/quota-check{slash:\\/?}", + appHandler(getCheckQuotaCB)) + r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/jwt-token{slash:\\/?}", + appHandler(getJWTTokenCB)) + + // seadrive api + r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/block-map/{id:[\\da-z]{40}}", + appHandler(getBlockMapCB)) + r.Handle("/accessible-repos{slash:\\/?}", appHandler(getAccessibleRepoListCB)) + + // pprof + r.Handle("/debug/pprof", &profileHandler{http.HandlerFunc(pprof.Index)}) + r.Handle("/debug/pprof/cmdline", &profileHandler{http.HandlerFunc(pprof.Cmdline)}) + r.Handle("/debug/pprof/profile", &profileHandler{http.HandlerFunc(pprof.Profile)}) + r.Handle("/debug/pprof/symbol", &profileHandler{http.HandlerFunc(pprof.Symbol)}) + r.Handle("/debug/pprof/heap", &profileHandler{pprof.Handler("heap")}) + r.Handle("/debug/pprof/block", &profileHandler{pprof.Handler("block")}) + r.Handle("/debug/pprof/goroutine", &profileHandler{pprof.Handler("goroutine")}) + r.Handle("/debug/pprof/threadcreate", &profileHandler{pprof.Handler("threadcreate")}) + return r +} + +func handleProtocolVersion(rsp http.ResponseWriter, r *http.Request) { + io.WriteString(rsp, "{\"version\": 2}") +} + +type appError struct { + Error error + Message string + Code int +} + +type appHandler func(http.ResponseWriter, *http.Request) *appError + +func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if e := fn(w, r); e != nil { + if e.Error != nil && e.Code == http.StatusInternalServerError { + log.Printf("path %s internal server error: %v\n", r.URL.Path, e.Error) + } + http.Error(w, e.Message, e.Code) + } +} + +func RecoverWrapper(f func()) { + defer func() { + if err := recover(); err != nil { + log.Printf("panic: %v\n%s", err, debug.Stack()) + } + }() + + f() +} + +type profileHandler struct { + pHandler http.Handler +} + +func (p *profileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + queries := r.URL.Query() + password := queries.Get("password") + if !option.EnableProfiling || password != option.ProfilePassword { + http.Error(w, "", http.StatusUnauthorized) + return + } + + p.pHandler.ServeHTTP(w, r) +}