summaryrefslogtreecommitdiff
path: root/src/sqlcmd.c
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-09-04 09:05:24 +0200
committercashmere <cashmere@autistici.org>2026-09-04 09:05:24 +0200
commit4f49b16079b27d7ce633619e4cddfdf2bffbc952 (patch)
treec296c1e7f3c3e2f6859c6d3a1b1b208779b89679 /src/sqlcmd.c
downloadrelic-4f49b16079b27d7ce633619e4cddfdf2bffbc952.tar.gz
initial commitHEADmaster
Diffstat (limited to 'src/sqlcmd.c')
-rw-r--r--src/sqlcmd.c486
1 files changed, 486 insertions, 0 deletions
diff --git a/src/sqlcmd.c b/src/sqlcmd.c
new file mode 100644
index 0000000..5175ac9
--- /dev/null
+++ b/src/sqlcmd.c
@@ -0,0 +1,486 @@
+/*
+** Copyright (c) 2010 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*******************************************************************************
+**
+** This module contains the code that initializes the "sqlite3" command-line
+** shell against the repository database. The command-line shell itself
+** is a copy of the "shell.c" code from SQLite. This file contains logic
+** to initialize the code in shell.c.
+*/
+#include "config.h"
+#include "sqlcmd.h"
+#include <stdlib.h> /* atexit() */
+#include <zlib.h>
+#ifndef _WIN32
+# include "linenoise.h"
+#endif
+
+/*
+** True if the "fossil sql" command has the --test flag. False otherwise.
+*/
+static int local_bSqlCmdTest = 0;
+
+/*
+** Implementation of the "content(X)" SQL function. Return the complete
+** content of artifact identified by X as a blob.
+*/
+static void sqlcmd_content(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int rid;
+ Blob cx;
+ const char *zName;
+ assert( argc==1 );
+ zName = (const char*)sqlite3_value_text(argv[0]);
+ if( zName==0 ) return;
+ g.db = sqlite3_context_db_handle(context);
+ g.repositoryOpen = 1;
+ rid = name_to_rid(zName);
+ if( rid==0 ) return;
+ if( content_get(rid, &cx) ){
+ sqlite3_result_blob(context, blob_buffer(&cx), blob_size(&cx),
+ SQLITE_TRANSIENT);
+ blob_reset(&cx);
+ }
+}
+
+/*
+** Implementation of the "compress(X)" SQL function. The input X is
+** compressed using zLib and the output is returned.
+*/
+static void sqlcmd_compress(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const unsigned char *pIn;
+ unsigned char *pOut;
+ unsigned int nIn;
+ unsigned long int nOut;
+ int rc;
+
+ pIn = sqlite3_value_blob(argv[0]);
+ nIn = sqlite3_value_bytes(argv[0]);
+ nOut = 13 + nIn + (nIn+999)/1000;
+ pOut = sqlite3_malloc( nOut+4 );
+ pOut[0] = nIn>>24 & 0xff;
+ pOut[1] = nIn>>16 & 0xff;
+ pOut[2] = nIn>>8 & 0xff;
+ pOut[3] = nIn & 0xff;
+ rc = compress(&pOut[4], &nOut, pIn, nIn);
+ if( rc==Z_OK ){
+ sqlite3_result_blob(context, pOut, nOut+4, sqlite3_free);
+ }else if( rc==Z_MEM_ERROR ){
+ sqlite3_free(pOut);
+ sqlite3_result_error_nomem(context);
+ }else{
+ sqlite3_free(pOut);
+ sqlite3_result_error(context, "input cannot be zlib compressed", -1);
+ }
+}
+
+/*
+** Implementation of the "decompress(X)" SQL function. The argument X
+** is a blob which was obtained from compress(Y). The output will be
+** the value Y.
+*/
+static void sqlcmd_decompress(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const unsigned char *pIn;
+ unsigned char *pOut;
+ unsigned int nIn;
+ unsigned long int nOut;
+ int rc;
+
+ pIn = sqlite3_value_blob(argv[0]);
+ if( pIn==0 ) return;
+ nIn = sqlite3_value_bytes(argv[0]);
+ if( nIn<4 ) return;
+ nOut = (pIn[0]<<24) + (pIn[1]<<16) + (pIn[2]<<8) + pIn[3];
+ pOut = sqlite3_malloc( nOut+1 );
+ rc = uncompress(pOut, &nOut, &pIn[4], nIn-4);
+ if( rc==Z_OK ){
+ sqlite3_result_blob(context, pOut, nOut, sqlite3_free);
+ }else if( rc==Z_MEM_ERROR ){
+ sqlite3_free(pOut);
+ sqlite3_result_error_nomem(context);
+ }else{
+ sqlite3_free(pOut);
+ sqlite3_result_error(context, "input is not zlib compressed", -1);
+ }
+}
+
+/*
+** Implementation of the "gather_artifact_stats(X)" SQL function.
+** That function merely calls the gather_artifact_stats() function
+** in stat.c to populate the ARTSTAT temporary table.
+*/
+static void sqlcmd_gather_artifact_stats(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ gather_artifact_stats(1);
+}
+
+/*
+** Add the content(), compress(), decompress(), and
+** gather_artifact_stats() SQL functions to database connection db.
+**
+** Also add whatis().
+*/
+int add_content_sql_commands(sqlite3 *db){
+ sqlite3_create_function(db, "content", 1, SQLITE_UTF8, 0,
+ sqlcmd_content, 0, 0);
+ sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
+ sqlcmd_compress, 0, 0);
+ sqlite3_create_function(db, "decompress", 1, SQLITE_UTF8, 0,
+ sqlcmd_decompress, 0, 0);
+ sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0,
+ sqlcmd_gather_artifact_stats, 0, 0);
+ sqlite3_create_function(db, "whatis", 1, SQLITE_UTF8|SQLITE_RESULT_SUBTYPE, 0,
+ whatis_sql_function, 0, 0);
+ return SQLITE_OK;
+}
+
+/*
+** Undocumented test SQL functions:
+**
+** db_protect(X)
+** db_protect_pop(X)
+**
+** These invoke the corresponding C routines.
+**
+** WARNING:
+** Do not instantiate these functions for any Fossil webpage or command
+** method other than the "fossil sql" command. If an attacker gains access
+** to these functions, he will be able to disable other defense mechanisms.
+**
+** This routines are for interactive testing only. They are experimental
+** and undocumented (apart from this comments) and might go away or change
+** in future releases.
+**
+** 2020-11-29: These functions are now only available if the "fossil sql"
+** command is started with the --test option.
+*/
+static void sqlcmd_db_protect(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ unsigned mask = 0;
+ const char *z = (const char*)sqlite3_value_text(argv[0]);
+ if( z!=0 && local_bSqlCmdTest ){
+ if( sqlite3_stricmp(z,"user")==0 ) mask |= PROTECT_USER;
+ if( sqlite3_stricmp(z,"config")==0 ) mask |= PROTECT_CONFIG;
+ if( sqlite3_stricmp(z,"sensitive")==0 ) mask |= PROTECT_SENSITIVE;
+ if( sqlite3_stricmp(z,"readonly")==0 ) mask |= PROTECT_READONLY;
+ if( sqlite3_stricmp(z,"all")==0 ) mask |= PROTECT_ALL;
+ db_protect(mask);
+ }
+}
+static void sqlcmd_db_protect_pop(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ if( !local_bSqlCmdTest ) db_protect_pop();
+}
+
+/*
+** This is the "automatic extension" initializer that runs right after
+** the connection to the repository database is opened. Set up the
+** database connection to be more useful to the human operator.
+*/
+static int sqlcmd_autoinit(
+ sqlite3 *db,
+ const char **pzErrMsg,
+ const void *notUsed
+){
+ int mTrace = SQLITE_TRACE_CLOSE;
+ add_content_sql_commands(db);
+ db_add_aux_functions(db);
+ re_add_sql_func(db);
+ search_sql_setup(db);
+ foci_register(db);
+ deltafunc_init(db);
+ helptext_vtab_register(db);
+ builtin_vtab_register(db);
+ g.repositoryOpen = 1;
+ g.db = db;
+ sqlite3_busy_timeout(db, 10000);
+ if( g.zRepositoryName ){
+ sqlite3_db_config(db, SQLITE_DBCONFIG_MAINDBNAME, "repository");
+ db_maybe_set_encryption_key(db, g.zRepositoryName);
+ }
+ if( g.zLocalDbName ){
+ char *zSql = sqlite3_mprintf("ATTACH %Q AS 'localdb' KEY ''",
+ g.zLocalDbName);
+ sqlite3_exec(db, zSql, 0, 0, 0);
+ sqlite3_free(zSql);
+ }
+ if( g.zConfigDbName ){
+ char *zSql = sqlite3_mprintf("ATTACH %Q AS 'configdb' KEY ''",
+ g.zConfigDbName);
+ sqlite3_exec(db, zSql, 0, 0, 0);
+ sqlite3_free(zSql);
+ }
+ (void)timeline_query_for_tty(); /* Registers wiki_to_text() as side-effect */
+ /* Arrange to trace close operations so that static prepared statements
+ ** will get cleaned up when the shell closes the database connection */
+ if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE;
+ sqlite3_trace_v2(db, mTrace, db_sql_trace, 0);
+ if( g.zRepositoryName ){
+ db_protect_only(PROTECT_NONE);
+ sqlite3_set_authorizer(db, db_top_authorizer, db);
+ if( local_bSqlCmdTest ){
+ sqlite3_create_function(db, "db_protect", 1, SQLITE_UTF8, 0,
+ sqlcmd_db_protect, 0, 0);
+ sqlite3_create_function(db, "db_protect_pop", 0, SQLITE_UTF8, 0,
+ sqlcmd_db_protect_pop, 0, 0);
+ sqlite3_create_function(db, "shared_secret", 2, SQLITE_UTF8, 0,
+ sha1_shared_secret_sql_function, 0, 0);
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+** atexit() handler that cleans up global state modified by this module.
+*/
+static void sqlcmd_atexit(void) {
+ g.zConfigDbName = 0; /* prevent panic */
+}
+
+/*
+** This routine is called by the sqlite3 command-line shell to
+** to load the name the Fossil repository database.
+*/
+void sqlcmd_get_dbname(const char **pzRepoName){
+ *pzRepoName = g.zRepositoryName;
+}
+
+/*
+** This routine is called by the sqlite3 command-line shell to do
+** extra initialization prior to starting up the shell.
+*/
+void sqlcmd_init_proc(void){
+ sqlite3_initialize();
+ sqlite3_auto_extension((void(*)(void))sqlcmd_autoinit);
+}
+
+#if USE_SEE
+/*
+** This routine is called by the patched sqlite3 command-line shell in order
+** to load the encryption key for the open Fossil database. The memory that
+** is pointed to by the value placed in pzKey must be obtained from malloc.
+*/
+void fossil_key(const char **pzKey, int *pnKey){
+ char *zSavedKey = db_get_saved_encryption_key();
+ char *zKey;
+ size_t savedKeySize = db_get_saved_encryption_key_size();
+
+ if( !db_is_valid_saved_encryption_key(zSavedKey, savedKeySize) ) return;
+ zKey = (char*)malloc( savedKeySize );
+ if( zKey ){
+ memcpy(zKey, zSavedKey, savedKeySize);
+ *pzKey = zKey;
+ if( fossil_getenv("FOSSIL_USE_SEE_TEXTKEY")==0 ){
+ *pnKey = (int)strlen(zKey);
+ }else{
+ *pnKey = -1;
+ }
+ }else{
+ fossil_fatal("failed to allocate %u bytes for key", savedKeySize);
+ }
+}
+#endif
+
+/*
+** This routine closes the Fossil databases and/or invalidates the global
+** state variables that keep track of them.
+*/
+static void fossil_close(int bDb, int noRepository){
+ if( bDb ) db_close(1);
+ if( noRepository ) g.zRepositoryName = 0;
+ g.db = 0;
+ g.repositoryOpen = 0;
+ g.localOpen = 0;
+}
+
+/*
+** This routine overrides some of the prompt generation behavior in the
+** SQLite shell. Always return a static string. For invalid inputs,
+** return a zero-length string. Valid inputs:
+**
+** 1 Default main prompt.
+** 2 Default continuation prompt.
+** 3 Environment variable to override main prompt ("FOSSIL_PS1")
+** 4 Environment variable to override continuatio ("FOSSIL_PS2")
+** 'A' The name of the application. (Nominally "Fossil")
+** 'V' Version number including patch. (ex: "2.29.1")
+** 'v' Version number without patch. (ex: "2.29")
+*/
+const char *sqlcmd_ps_appdef(int c){
+ switch( c ){
+ /* Default prompt strings */
+ case 1: return "/e[1;34m/A-/v /e[1;/x33/:36/;m/f/e[0m-> ";
+ case 2: return "/B/e[1;/x33/:36/;m/C/e[0m-> ";
+
+ /* Names of environment variables to override cases 1 and 2 */
+ case 3: return "FOSSIL_PS1";
+ case 4: return "FOSSIL_PS2";
+
+ /* Application name */
+ case 'A': return "Fossil";
+
+ /* Version numbers */
+ case 'V': return RELEASE_VERSION;
+ case 'v': {
+ const char *zFull = RELEASE_VERSION;
+ const char *zD1, *zD2;
+ size_t i;
+ static char zRelease[32];
+ zD2 = strrchr(zFull,'.');
+ zD1 = strchr(zFull,'.');
+ if( zD2==0 || zD2==zD1 ){
+ return zFull;
+ }
+ i = zD2 - zFull;
+ if( i>sizeof(zRelease)-1 ) return zFull;
+ memcpy(zRelease, zFull, i);
+ zRelease[i] = 0;
+ return zRelease;
+ }
+ }
+ return "";
+}
+
+/*
+** COMMAND: sql
+** COMMAND: sqlite3*
+**
+** Usage: %fossil sql ?OPTIONS?
+**
+** Run the sqlite3 command-line shell on the Fossil repository
+** identified by the -R option, or on the current repository.
+** See https://www.sqlite.org/cli.html for additional information about
+** the sqlite3 command-line shell.
+**
+** WARNING: Careless use of this command can corrupt a Fossil repository
+** in ways that are unrecoverable. Be sure you know what you are doing before
+** running any SQL commands that modify the repository database. Use the
+** --readonly option to prevent accidental damage to the repository.
+**
+** Options:
+** --no-repository Skip opening the repository database
+** --readonly Open the repository read-only. No changes
+** are allowed. This is a recommended safety
+** precaution to prevent repository damage.
+** -R REPOSITORY Use REPOSITORY as the repository database
+** --test Enable some testing and analysis features
+** that are normally disabled.
+**
+** All of the standard sqlite3 command-line shell options should also
+** work.
+**
+** The following SQL extensions are provided with this Fossil-enhanced
+** version of the sqlite3 command-line shell:
+**
+** builtin A virtual table that contains one row for
+** each datafile that is built into the Fossil
+** binary.
+**
+** checkin_mtime(X,Y) Return the mtime for the file Y (a BLOB.RID)
+** found in check-in X (another BLOB.RID value).
+**
+** compress(X) Compress text X with the same algorithm used
+** to compress artifacts in the BLOB table.
+**
+** content(X) Return the content of artifact X. X can be an
+** artifact hash or hash prefix or a tag. Artifacts
+** are stored compressed and deltaed. This function
+** does all necessary decompression and undeltaing.
+**
+** decompress(X) Decompress text X. Undoes the work of
+** compress(X).
+**
+** delta_apply(X,D) Apply delta D to source blob X and return
+** the result.
+**
+** delta_create(X,Y) Create and return a delta that will convert
+** X into Y.
+**
+** delta_output_size(D) Return the number of bytes of output to expect
+** when applying delta D
+**
+** delta_parse(D) A table-valued function that deconstructs
+** delta D and returns rows for each element of
+** that delta.
+**
+** files_of_checkin(X) A table-valued function that returns info on
+** all files contained in check-in X. Example:
+**
+** SELECT * FROM files_of_checkin('trunk');
+**
+** helptext A virtual table with one row for each command,
+** webpage, and setting together with the built-in
+** help text.
+**
+** now() Return the number of seconds since 1970.
+**
+** obscure(T) Obfuscate the text password T so that its
+** original value is not readily visible. Fossil
+** uses this same algorithm when storing passwords
+** of remote URLs.
+**
+** regexp The REGEXP operator works, unlike in
+** standard SQLite.
+**
+** symbolic_name_to_rid(X) Return the BLOB.RID corresponding to symbolic
+** name X.
+*/
+void cmd_sqlite3(void){
+ int noRepository;
+ char *zConfigDb;
+ extern int sqlite3_shell(int, char**);
+#ifdef FOSSIL_ENABLE_TH1_HOOKS
+ g.fNoThHook = 1;
+#endif
+ noRepository = find_option("no-repository", 0, 0)!=0;
+ local_bSqlCmdTest = find_option("test",0,0)!=0;
+ if( !noRepository ){
+ db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
+ }
+ db_open_config(1,0);
+ zConfigDb = fossil_strdup(g.zConfigDbName);
+ fossil_close(1, noRepository);
+ sqlite3_shutdown();
+#ifndef _WIN32
+ linenoiseSetMultiLine(1);
+#endif
+ atexit(sqlcmd_atexit);
+ g.zConfigDbName = zConfigDb;
+ g.argv[1] = "--noinit";
+ sqlite3_shell(g.argc, g.argv);
+ sqlite3_cancel_auto_extension((void(*)(void))sqlcmd_autoinit);
+ fossil_close(0, noRepository);
+}