From c2b14f5d6852fb9efaca8aeec7961e9d036203e8 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 24 Sep 2020 16:38:21 +0200 Subject: [PATCH] keyboxd: New command TRANSACTION. * kbx/backend-sqlite.c (be_sqlite_rollback): New. (be_sqlite_commit): New. (be_sqlite_search): Take care of global transactions. (be_sqlite_store): Ditto. (be_sqlite_delete): Ditto. * kbx/frontend.c (kbxd_rollback, kbxd_commit): New. * kbx/keyboxd.h (opt): Add vars for transactions. * kbx/kbxserver.c (struct server_local_s): Add fields next_session and client_pid. (session_list): New var. (cmd_transaction): New. (register_commands): Register command. (kbxd_start_command_handler): Store pids and track sessions. Do a final rollback. -- This command is currently an experiment to allow a client to run everything in one session. Signed-off-by: Werner Koch --- kbx/backend-sqlite.c | 91 ++++++++++++++++++++++++++++---- kbx/backend.h | 2 + kbx/frontend.c | 15 ++++++ kbx/frontend.h | 2 + kbx/kbxserver.c | 120 +++++++++++++++++++++++++++++++++++++++++++ kbx/keyboxd.h | 10 ++++ 6 files changed, 230 insertions(+), 10 deletions(-) diff --git a/kbx/backend-sqlite.c b/kbx/backend-sqlite.c index 0b5155a8f..c31415694 100644 --- a/kbx/backend-sqlite.c +++ b/kbx/backend-sqlite.c @@ -681,6 +681,42 @@ be_sqlite_release_local (be_sqlite_local_t ctx) } +gpg_error_t +be_sqlite_rollback (void) +{ + opt.in_transaction = 0; + if (!opt.active_transaction) + return 0; /* Nothing to do. */ + + if (!database_hd) + { + log_error ("Warning: No database handle for global rollback\n"); + return gpg_error (GPG_ERR_INTERNAL); + } + + opt.active_transaction = 0; + return run_sql_statement ("rollback"); +} + + +gpg_error_t +be_sqlite_commit (void) +{ + opt.in_transaction = 0; + if (!opt.active_transaction) + return 0; /* Nothing to do. */ + + if (!database_hd) + { + log_error ("Warning: No database handle for global commit\n"); + return gpg_error (GPG_ERR_INTERNAL); + } + + opt.active_transaction = 0; + return run_sql_statement ("commit"); +} + + /* Run a select for the search given by (DESC,NDESC). The data is not * returned but stored in the request item. */ static gpg_error_t @@ -1000,6 +1036,16 @@ be_sqlite_search (ctrl_t ctrl, goto leave; } + /* Start a global transaction if needed. */ + if (!opt.active_transaction && opt.in_transaction) + { + err = run_sql_statement ("begin transaction"); + if (err) + goto leave; + opt.active_transaction = 1; + } + + again: if (!ctx->select_done) { @@ -1395,9 +1441,14 @@ be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd, goto leave; /* ctx = part->besqlite; */ - err = run_sql_statement ("begin transaction"); - if (err) - goto leave; + if (!opt.active_transaction) + { + err = run_sql_statement ("begin transaction"); + if (err) + goto leave; + if (opt.in_transaction) + opt.active_transaction = 1; + } in_transaction = 1; err = store_into_pubkey (mode, pktype, ubid, blob, bloblen); @@ -1541,10 +1592,17 @@ be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd, leave: if (in_transaction && !err) - err = run_sql_statement ("commit"); + { + if (opt.active_transaction) + ; /* We are in a global transaction. */ + else + err = run_sql_statement ("commit"); + } else if (in_transaction) { - if (run_sql_statement ("rollback")) + if (opt.active_transaction) + ; /* We are in a global transaction. */ + else if (run_sql_statement ("rollback")) log_error ("Warning: database rollback failed - should not happen!\n"); } if (got_mutex) @@ -1586,9 +1644,14 @@ be_sqlite_delete (ctrl_t ctrl, backend_handle_t backend_hd, goto leave; /* ctx = part->besqlite; */ - err = run_sql_statement ("begin transaction"); - if (err) - goto leave; + if (!opt.active_transaction) + { + err = run_sql_statement ("begin transaction"); + if (err) + goto leave; + if (opt.in_transaction) + opt.active_transaction = 1; + } in_transaction = 1; err = run_sql_statement_bind_ubid @@ -1607,11 +1670,19 @@ be_sqlite_delete (ctrl_t ctrl, backend_handle_t backend_hd, leave: if (stmt) sqlite3_finalize (stmt); + if (in_transaction && !err) - err = run_sql_statement ("commit"); + { + if (opt.active_transaction) + ; /* We are in a global transaction. */ + else + err = run_sql_statement ("commit"); + } else if (in_transaction) { - if (run_sql_statement ("rollback")) + if (opt.active_transaction) + ; /* We are in a global transaction. */ + else if (run_sql_statement ("rollback")) log_error ("Warning: database rollback failed - should not happen!\n"); } release_mutex (); diff --git a/kbx/backend.h b/kbx/backend.h index a241490a7..d6178cd01 100644 --- a/kbx/backend.h +++ b/kbx/backend.h @@ -171,6 +171,8 @@ void be_sqlite_release_resource (ctrl_t ctrl, backend_handle_t hd); gpg_error_t be_sqlite_init_local (backend_handle_t backend_hd, db_request_part_t part); void be_sqlite_release_local (be_sqlite_local_t ctx); +gpg_error_t be_sqlite_rollback (void); +gpg_error_t be_sqlite_commit (void); gpg_error_t be_sqlite_search (ctrl_t ctrl, backend_handle_t hd, db_request_t request, KEYDB_SEARCH_DESC *desc, unsigned int ndesc); diff --git a/kbx/frontend.c b/kbx/frontend.c index 48b6fffa2..c80c9fa8a 100644 --- a/kbx/frontend.c +++ b/kbx/frontend.c @@ -172,6 +172,21 @@ kbxd_release_session_info (ctrl_t ctrl) } + +gpg_error_t +kbxd_rollback (void) +{ + return be_sqlite_rollback (); +} + + +gpg_error_t +kbxd_commit (void) +{ + return be_sqlite_commit (); +} + + /* Search for the keys described by (DESC,NDESC) and return them to * the caller. If RESET is set, the search state is first reset. diff --git a/kbx/frontend.h b/kbx/frontend.h index d38d442f0..50ba4a4e4 100644 --- a/kbx/frontend.h +++ b/kbx/frontend.h @@ -28,6 +28,8 @@ gpg_error_t kbxd_set_database (ctrl_t ctrl, void kbxd_release_session_info (ctrl_t ctrl); +gpg_error_t kbxd_rollback (void); +gpg_error_t kbxd_commit (void); gpg_error_t kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc, int reset); diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c index 264c3be4e..8a75035a5 100644 --- a/kbx/kbxserver.c +++ b/kbx/kbxserver.c @@ -50,6 +50,13 @@ /* Control structure per connection. */ struct server_local_s { + /* We keep a list of all active sessions with the anchor at + * SESSION_LIST (see below). This field is used for linking. */ + struct server_local_s *next_session; + + /* The pid of the client. */ + pid_t client_pid; + /* Data used to associate an Assuan context with local server data */ assuan_context_t assuan_ctx; @@ -89,6 +96,12 @@ struct server_local_s }; +/* To keep track of all running sessions, we link all active server + * contexts and anchor them at this variable. */ +static struct server_local_s *session_list; + + + /* Return the assuan contxt from the local server info in CTRL. */ @@ -568,6 +581,75 @@ cmd_delete (assuan_context_t ctx, char *line) } + +static const char hlp_transaction[] = + "TRANSACTION [begin|commit|rollback]\n" + "\n" + "For bulk import of data it is often useful to run everything\n" + "in one transaction. This can be achieved with this command.\n" + "If the last connection of client is closed before a commit\n" + "or rollback an implicit rollback is done. With no argument\n" + "the status of the current transaction is returned."; +static gpg_error_t +cmd_transaction (assuan_context_t ctx, char *line) +{ + gpg_error_t err = 0; + + line = skip_options (line); + + if (!strcmp (line, "begin")) + { + /* Note that we delay the actual transaction until we have to + * use SQL. */ + if (opt.in_transaction) + err = set_error (GPG_ERR_CONFLICT, "already in a transaction"); + else + { + opt.in_transaction = 1; + opt.transaction_pid = assuan_get_pid (ctx); + } + } + else if (!strcmp (line, "commit")) + { + if (!opt.in_transaction) + err = set_error (GPG_ERR_CONFLICT, "not in a transaction"); + else if (opt.transaction_pid != assuan_get_pid (ctx)) + err = set_error (GPG_ERR_CONFLICT, "other client is in a transaction"); + else + err = kbxd_commit (); + } + else if (!strcmp (line, "rollback")) + { + if (!opt.in_transaction) + err = set_error (GPG_ERR_CONFLICT, "not in a transaction"); + else if (opt.transaction_pid != assuan_get_pid (ctx)) + err = set_error (GPG_ERR_CONFLICT, "other client is in a transaction"); + else + err = kbxd_rollback (); + } + else if (!*line) + { + if (opt.in_transaction && opt.transaction_pid == assuan_get_pid (ctx)) + err = assuan_set_okay_line (ctx, opt.active_transaction? + "active transaction" : + "pending transaction"); + else if (opt.in_transaction) + err = assuan_set_okay_line (ctx, opt.active_transaction? + "active transaction on other client" : + "pending transaction on other client"); + else + err = set_error (GPG_ERR_FALSE, "no transaction"); + } + else + { + err = set_error (GPG_ERR_ASS_PARAMETER, "unknown transaction command"); + } + + + return leave_cmd (ctx, err); +} + + static const char hlp_getinfo[] = "GETINFO \n" @@ -689,6 +771,7 @@ register_commands (assuan_context_t ctx) { "NEXT", cmd_next, hlp_next }, { "STORE", cmd_store, hlp_store }, { "DELETE", cmd_delete, hlp_delete }, + { "TRANSACTION",cmd_transaction,hlp_transaction }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "OUTPUT", NULL, hlp_output }, { "KILLKEYBOXD",cmd_killkeyboxd,hlp_killkeyboxd }, @@ -764,6 +847,7 @@ kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id) xfree (ctrl); return; } + ctrl->server_local->client_pid = ASSUAN_INVALID_PID; rc = assuan_new (&ctx); if (rc) @@ -825,6 +909,11 @@ kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id) ctrl->server_local->session_id = session_id; + /* Put the session int a list. */ + ctrl->server_local->next_session = session_list; + session_list = ctrl->server_local; + + /* The next call enable the use of status_printf. */ set_assuan_context_func (get_assuan_ctx_from_ctrl); @@ -850,6 +939,7 @@ kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id) (long)peercred->gid); } #endif + ctrl->server_local->client_pid = assuan_get_pid (ctx); rc = assuan_process (ctx); if (rc) @@ -859,6 +949,22 @@ kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id) } } + if (opt.in_transaction + && opt.transaction_pid == ctrl->server_local->client_pid) + { + struct server_local_s *sl; + pid_t thispid = ctrl->server_local->client_pid; + int npids = 0; + + /* Only if this is the last connection rollback the transaction. */ + for (sl = session_list; sl; sl = sl->next_session) + if (sl->client_pid == thispid) + npids++; + + if (npids == 1) + kbxd_rollback (); + } + assuan_close_output_fd (ctx); set_assuan_context_func (NULL); @@ -873,6 +979,20 @@ kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id) ctrl->refcount); else { + if (session_list == ctrl->server_local) + session_list = ctrl->server_local->next_session; + else + { + struct server_local_s *sl; + + for (sl=session_list; sl->next_session; sl = sl->next_session) + if (sl->next_session == ctrl->server_local) + break; + if (!sl->next_session) + BUG (); + sl->next_session = ctrl->server_local->next_session; + } + xfree (ctrl->server_local->multi_search_desc); xfree (ctrl->server_local); ctrl->server_local = NULL; diff --git a/kbx/keyboxd.h b/kbx/keyboxd.h index 22988bf1b..9cc2c23a6 100644 --- a/kbx/keyboxd.h +++ b/kbx/keyboxd.h @@ -45,6 +45,15 @@ struct /* True if we are running detached from the tty. */ int running_detached; + /* + * Global state variables. + */ + + /* Whether a global transaction has been requested along with the + * caller's pid and whether a transaction is active. */ + pid_t transaction_pid; + unsigned int in_transaction : 1; + unsigned int active_transaction : 1; } opt; @@ -118,6 +127,7 @@ struct server_control_s unsigned int filter_x509 : 1; /* Used by SEARCH and NEXT. */ unsigned int no_data_return : 1; + };