From d0345cb4fa39ae70feac68f1a124534db2338c7c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 18 Nov 2023 12:48:47 +0100 Subject: [PATCH 01/27] Validate domains before adding the to the database via /api/list Signed-off-by: DL6ER --- src/api/list.c | 15 +++++++++++++++ src/tools/gravity-parseList.c | 2 +- src/tools/gravity-parseList.h | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/api/list.c b/src/api/list.c index 3c0b6dc0..276cea6d 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -17,6 +17,8 @@ #include "shmem.h" // getNameFromIP() #include "database/network-table.h" +// valid_domain() +#include "tools/gravity-parseList.h" static int api_list_read(struct ftl_conn *api, const int code, @@ -400,6 +402,19 @@ static int api_list_write(struct ftl_conn *api, "Spaces, newlines and tabs are not allowed in domains and URLs", it->valuestring); } + + // Validate domains + if((listtype == GRAVITY_DOMAINLIST_ALLOW_EXACT || + listtype == GRAVITY_DOMAINLIST_DENY_EXACT) && + !valid_domain(it->valuestring, strlen(it->valuestring), false)) + { + if(allocated_json) + cJSON_free(row.items); + return send_json_error(api, 400, // 400 Bad Request + "bad_request", + "Invalid domain", + it->valuestring); + } } } diff --git a/src/tools/gravity-parseList.c b/src/tools/gravity-parseList.c index f519cd35..0af75345 100644 --- a/src/tools/gravity-parseList.c +++ b/src/tools/gravity-parseList.c @@ -40,7 +40,7 @@ static const char *false_positives[] = { #define MAX_INVALID_DOMAINS 5 // Validate domain name -static inline bool __attribute__((pure)) valid_domain(const char *domain, const size_t len, const bool abp) +inline bool __attribute__((pure)) valid_domain(const char *domain, const size_t len, const bool abp) { // Domain must not be NULL or empty, and they should not be longer than // 255 characters diff --git a/src/tools/gravity-parseList.h b/src/tools/gravity-parseList.h index 93e6b488..560fbd9b 100644 --- a/src/tools/gravity-parseList.h +++ b/src/tools/gravity-parseList.h @@ -11,3 +11,4 @@ #include "FTL.h" int gravity_parseList(const char *infile, const char *outfile, const char *adlistID, const bool checkOnly, const bool antigravity); +bool __attribute__((pure)) valid_domain(const char *domain, const size_t len, const bool abp); From 04742405abaf7fa9e195cee15354185e794808d4 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 18 Nov 2023 16:56:31 +0100 Subject: [PATCH 02/27] Hostnames (non-FQDN) are valid domains in the context of allow/deny domains, too Signed-off-by: DL6ER --- src/api/list.c | 6 +++--- src/tools/gravity-parseList.c | 10 +++++----- src/tools/gravity-parseList.h | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 276cea6d..780a0754 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -398,9 +398,9 @@ static int api_list_write(struct ftl_conn *api, if(allocated_json) cJSON_free(row.items); return send_json_error(api, 400, // 400 Bad Request - "bad_request", - "Spaces, newlines and tabs are not allowed in domains and URLs", - it->valuestring); + "bad_request", + "Spaces, newlines and tabs are not allowed in domains and URLs", + it->valuestring); } // Validate domains diff --git a/src/tools/gravity-parseList.c b/src/tools/gravity-parseList.c index 0af75345..8f7ad683 100644 --- a/src/tools/gravity-parseList.c +++ b/src/tools/gravity-parseList.c @@ -40,7 +40,7 @@ static const char *false_positives[] = { #define MAX_INVALID_DOMAINS 5 // Validate domain name -inline bool __attribute__((pure)) valid_domain(const char *domain, const size_t len, const bool abp) +inline bool __attribute__((pure)) valid_domain(const char *domain, const size_t len, const bool fqdn_only) { // Domain must not be NULL or empty, and they should not be longer than // 255 characters @@ -87,7 +87,7 @@ inline bool __attribute__((pure)) valid_domain(const char *domain, const size_t // e.g., "example.com" but not "localhost" for exact domain // We do not enforce this for ABP domains // (see https://github.com/pi-hole/pi-hole/pull/5240) - if(last_dot == -1 && !abp) + if(last_dot == -1 && fqdn_only) return false; // TLD must not start or end with a hyphen @@ -123,7 +123,7 @@ static inline bool __attribute__((pure)) valid_abp_domain(const char *line, cons return false; // Domain must be valid - return valid_domain(line+4, len-5, true); + return valid_domain(line+4, len-5, false); } else { @@ -140,7 +140,7 @@ static inline bool __attribute__((pure)) valid_abp_domain(const char *line, cons return false; // Domain must be valid - return valid_domain(line+2, len-3, true); + return valid_domain(line+2, len-3, false); } } @@ -281,7 +281,7 @@ int gravity_parseList(const char *infile, const char *outfile, const char *adlis // Validate line if(line[0] != (antigravity ? '@' : '|') && // <- Not an ABP-style match - valid_domain(line, read, false)) + valid_domain(line, read, true)) { // Exact match found if(checkOnly) diff --git a/src/tools/gravity-parseList.h b/src/tools/gravity-parseList.h index 560fbd9b..e9e54ac4 100644 --- a/src/tools/gravity-parseList.h +++ b/src/tools/gravity-parseList.h @@ -11,4 +11,4 @@ #include "FTL.h" int gravity_parseList(const char *infile, const char *outfile, const char *adlistID, const bool checkOnly, const bool antigravity); -bool __attribute__((pure)) valid_domain(const char *domain, const size_t len, const bool abp); +bool __attribute__((pure)) valid_domain(const char *domain, const size_t len, const bool fqdn_only); From d8a452f8f3af3d1b8d75e2e76b39f35b3a310e94 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 18 Nov 2023 21:58:06 +0100 Subject: [PATCH 03/27] Update src/tools/gravity-parseList.c Co-authored-by: yubiuser Signed-off-by: DL6ER --- src/tools/gravity-parseList.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/gravity-parseList.c b/src/tools/gravity-parseList.c index 8f7ad683..912ad358 100644 --- a/src/tools/gravity-parseList.c +++ b/src/tools/gravity-parseList.c @@ -85,7 +85,7 @@ inline bool __attribute__((pure)) valid_domain(const char *domain, const size_t // There must be at least two labels (i.e. one dot) // e.g., "example.com" but not "localhost" for exact domain - // We do not enforce this for ABP domains + // We do not enforce this for ABP domains and domainlist input // (see https://github.com/pi-hole/pi-hole/pull/5240) if(last_dot == -1 && fqdn_only) return false; From ad5d07819376e363ec718b1442dd2b4d125dec32 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 19 Nov 2023 07:46:51 +0100 Subject: [PATCH 04/27] The group table's column is called "description" but we expose it in the API as "comment". Adjust internally used SQL to translate between them (this was already implemented and working for comment editing) Signed-off-by: DL6ER --- src/database/gravity-db.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 802b57b4..7e66df2e 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1626,7 +1626,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, // The item is the item for all POST requests if(listtype == GRAVITY_GROUPS) { - querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:item,:enabled,:description);"; + querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:item,:enabled,:comment);"; } else if(listtype == GRAVITY_ADLISTS) { @@ -1648,8 +1648,8 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, if(row->name == NULL) { // Name is not to be changed - querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:item,:enabled,:description) " - "ON CONFLICT(name) DO UPDATE SET enabled = :enabled, description = :description;"; + querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:item,:enabled,:comment) " + "ON CONFLICT(name) DO UPDATE SET enabled = :enabled, description = :comment;"; } else { From 5926c5b24604696a53f3c724dbb60e84514fd3bb Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 19 Nov 2023 09:33:36 +0100 Subject: [PATCH 05/27] Set civetweb's authentication_domain to config.webserver.domain Signed-off-by: DL6ER --- src/webserver/webserver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index 95940e49..b5c3c6bf 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -304,13 +304,13 @@ void http_init(void) "decode_url", "yes", "enable_directory_listing", "no", "num_threads", num_threads, + "authentication_domain", config.webserver.domain.v.s, "additional_header", "Content-Security-Policy: default-src 'self' 'unsafe-inline';\r\n" "X-Frame-Options: DENY\r\n" "X-XSS-Protection: 0\r\n" "X-Content-Type-Options: nosniff\r\n" "Referrer-Policy: strict-origin-when-cross-origin", "index_files", "index.html,index.htm,index.lp", - "enable_auth_domain_check", "no", NULL, NULL, NULL, NULL, // Leave slots for access control list (ACL) and TLS configuration at the end NULL From 262965375adea579113d8c81aa986099a20a5aff Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 19 Nov 2023 09:34:38 +0100 Subject: [PATCH 06/27] Require restarting after domain change - this also re-reads the TLS certificate file Signed-off-by: DL6ER --- src/config/config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config/config.c b/src/config/config.c index 3d1cf57c..85578a95 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -833,6 +833,7 @@ void initConfig(struct config *conf) conf->webserver.domain.h = "On which domain is the web interface served?"; conf->webserver.domain.a = cJSON_CreateStringReference(""); conf->webserver.domain.t = CONF_STRING; + conf->webserver.domain.f = FLAG_ADVANCED_SETTING | FLAG_RESTART_FTL; conf->webserver.domain.d.s = (char*)"pi.hole"; conf->webserver.acl.k = "webserver.acl"; From c97239acedb92b64930357e51f17a460bef6f9c6 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 19 Nov 2023 11:20:40 +0100 Subject: [PATCH 07/27] Remove CivetWeb patch which is not needed when authentication_domain is set Signed-off-by: DL6ER --- patch/civetweb.sh | 1 - ...ess-server-hostname-in-Civetweb-when.patch | 29 ------------------- src/webserver/civetweb/civetweb.c | 4 +-- 3 files changed, 1 insertion(+), 33 deletions(-) delete mode 100644 patch/civetweb/0001-Do-not-try-to-guess-server-hostname-in-Civetweb-when.patch diff --git a/patch/civetweb.sh b/patch/civetweb.sh index e6684515..f33dc8de 100644 --- a/patch/civetweb.sh +++ b/patch/civetweb.sh @@ -7,7 +7,6 @@ patch -p1 < patch/civetweb/0001-Always-Kepler-syntax-for-Lua-server-pages.patch patch -p1 < patch/civetweb/0001-Add-FTL-URI-rewriting-changes-to-CivetWeb.patch patch -p1 < patch/civetweb/0001-Add-mbedTLS-debug-logging-hook.patch patch -p1 < patch/civetweb/0001-Register-CSRF-token-in-conn-request_info.patch -patch -p1 < patch/civetweb/0001-Do-not-try-to-guess-server-hostname-in-Civetweb-when.patch patch -p1 < patch/civetweb/0001-Log-debug-messages-to-webserver.log-when-debug.webse.patch patch -p1 < patch/civetweb/0001-Allow-extended-ASCII-characters-in-URIs.patch diff --git a/patch/civetweb/0001-Do-not-try-to-guess-server-hostname-in-Civetweb-when.patch b/patch/civetweb/0001-Do-not-try-to-guess-server-hostname-in-Civetweb-when.patch deleted file mode 100644 index 862cc421..00000000 --- a/patch/civetweb/0001-Do-not-try-to-guess-server-hostname-in-Civetweb-when.patch +++ /dev/null @@ -1,29 +0,0 @@ -From e41d902b5b01896360e1235aebabd3eb352158aa Mon Sep 17 00:00:00 2001 -From: DL6ER -Date: Sun, 8 Oct 2023 14:31:20 +0200 -Subject: [PATCH] Do not try to guess server hostname in Civetweb when - redirecting directory URIs to end with a slash - -Signed-off-by: DL6ER ---- - src/webserver/civetweb/civetweb.c | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c -index f44b17ba..3df8eab9 100644 ---- a/src/webserver/civetweb/civetweb.c -+++ b/src/webserver/civetweb/civetweb.c -@@ -15306,7 +15306,9 @@ handle_request(struct mg_connection *conn) - if (!new_path) { - mg_send_http_error(conn, 500, "out or memory"); - } else { -- mg_get_request_link(conn, new_path, buflen - 1); -+ /* Pi-hole modification */ -+ //mg_get_request_link(conn, new_path, buflen - 1); -+ strcpy(new_path, ri->local_uri_raw); - strcat(new_path, "/"); - if (ri->query_string) { - /* Append ? and query string */ --- -2.34.1 - diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c index 5320c4d4..367e19ae 100644 --- a/src/webserver/civetweb/civetweb.c +++ b/src/webserver/civetweb/civetweb.c @@ -15307,9 +15307,7 @@ handle_request(struct mg_connection *conn) if (!new_path) { mg_send_http_error(conn, 500, "out or memory"); } else { - /* Pi-hole modification */ - //mg_get_request_link(conn, new_path, buflen - 1); - strcpy(new_path, ri->local_uri_raw); + mg_get_request_link(conn, new_path, buflen - 1); strcat(new_path, "/"); if (ri->query_string) { /* Append ? and query string */ From abbcbdaf5add889fc894a5e2baae531106cb85a2 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 19 Nov 2023 22:34:43 +0100 Subject: [PATCH 08/27] Convert IDNs to punycode before validation Signed-off-by: DL6ER --- src/api/list.c | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 780a0754..8e1e7c11 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -20,6 +20,8 @@ // valid_domain() #include "tools/gravity-parseList.h" +#include + static int api_list_read(struct ftl_conn *api, const int code, const enum gravity_list_type listtype, @@ -403,17 +405,42 @@ static int api_list_write(struct ftl_conn *api, it->valuestring); } - // Validate domains - if((listtype == GRAVITY_DOMAINLIST_ALLOW_EXACT || - listtype == GRAVITY_DOMAINLIST_DENY_EXACT) && - !valid_domain(it->valuestring, strlen(it->valuestring), false)) + if(listtype == GRAVITY_DOMAINLIST_ALLOW_EXACT || + listtype == GRAVITY_DOMAINLIST_DENY_EXACT) { - if(allocated_json) - cJSON_free(row.items); - return send_json_error(api, 400, // 400 Bad Request - "bad_request", - "Invalid domain", - it->valuestring); + char *punycode = NULL; + const Idna_rc rc = idna_to_ascii_lz(it->valuestring, &punycode, 0); + if (rc != IDNA_SUCCESS) + { + // Invalid domain name + return send_json_error(api, 400, + "bad_request", + "Invalid request: Invalid domain name", + idna_strerror(rc)); + } + // Convert punycode domain to lowercase + for(unsigned int i = 0u; i < strlen(punycode); i++) + punycode[i] = tolower(punycode[i]); + + // Validate punycode domain + // This will reject domains like äöü{{{.com + // which convert to xn--{{{-pla4gpb.com + if(!valid_domain(punycode, strlen(punycode), false)) + { + if(allocated_json) + cJSON_free(row.items); + return send_json_error(api, 400, // 400 Bad Request + "bad_request", + "Invalid domain", + it->valuestring); + } + + // Replace domain with punycode version + if(!(it->type & cJSON_IsReference)) + free(it->valuestring); + it->valuestring = punycode; + // Remove reference flag + it->type &= ~cJSON_IsReference; } } } From 7f0a0a77da263c47e6388fa9ec8951a4a79d37a6 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 19 Nov 2023 22:54:19 +0100 Subject: [PATCH 09/27] Add "unicode" field to API response for GET /api/domain Signed-off-by: DL6ER --- src/api/docs/content/specs/domains.yaml | 12 +++++++++++- src/api/list.c | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/api/docs/content/specs/domains.yaml b/src/api/docs/content/specs/domains.yaml index c63149c6..773cbeba 100644 --- a/src/api/docs/content/specs/domains.yaml +++ b/src/api/docs/content/specs/domains.yaml @@ -223,6 +223,7 @@ components: items: allOf: - $ref: 'domains.yaml#/components/schemas/domain' + - $ref: 'domains.yaml#/components/schemas/unicode' - $ref: 'domains.yaml#/components/schemas/type' - $ref: 'domains.yaml#/components/schemas/kind' - $ref: 'domains.yaml#/components/schemas/comment' @@ -249,6 +250,13 @@ components: description: Domain type: string example: testdomain.com + unicode: + type: object + properties: + unicode: + description: Unicode domain (may be different from `domain` if punycode-encoding is used) + type: string + example: "äbc.com" domain_array: type: object properties: @@ -368,6 +376,7 @@ components: value: domains: - domain: "allowed.com" + unicode: "allowed.com" type: allow kind: exact comment: null @@ -377,7 +386,8 @@ components: id: 299 date_added: 1611239095 date_modified: 1612163756 - - domain: "allowed2.comm" + - domain: "xn--4ca.com" + unicode: "ä.com" type: allow kind: regex comment: "Some text" diff --git a/src/api/list.c b/src/api/list.c index 8e1e7c11..0502f5ec 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -74,10 +74,18 @@ static int api_list_read(struct ftl_conn *api, } else // domainlists { + char *unicode = NULL; + const Idna_rc rc = idna_to_unicode_lzlz(table.domain, &unicode, 0); JSON_COPY_STR_TO_OBJECT(row, "domain", table.domain); + if(rc == IDNA_SUCCESS) + JSON_COPY_STR_TO_OBJECT(row, "unicode", unicode); + else + JSON_COPY_STR_TO_OBJECT(row, "unicode", table.domain); JSON_REF_STR_IN_OBJECT(row, "type", table.type); JSON_REF_STR_IN_OBJECT(row, "kind", table.kind); JSON_COPY_STR_TO_OBJECT(row, "comment", table.comment); + if(unicode != NULL) + free(unicode); } // Groups don't have the groups property From 07f040ea0baf97bceddc8da01c21b49ea4e143f9 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 20 Nov 2023 08:58:48 +0100 Subject: [PATCH 10/27] Switch from libidn to libidn2 to get IDN conversion conforming to IDNA2008 + TR46 specifications (RFC 5890, RFC 5891, RFC 5892, RFC 5893, TR 46) Signed-off-by: DL6ER --- src/CMakeLists.txt | 7 +++++-- src/api/list.c | 13 ++++++------- src/api/search.c | 10 +++++----- src/dnsmasq/config.h | 2 +- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4b02c2e9..3428cedb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -283,9 +283,12 @@ find_package(Threads REQUIRED) find_library(LIBHOGWEED NAMES libhogweed${CMAKE_STATIC_LIBRARY_SUFFIX} hogweed HINTS /usr/local/lib64) find_library(LIBGMP NAMES libgmp${CMAKE_STATIC_LIBRARY_SUFFIX} gmp) find_library(LIBNETTLE NAMES libnettle${CMAKE_STATIC_LIBRARY_SUFFIX} nettle HINTS /usr/local/lib64) -find_library(LIBIDN NAMES libidn${CMAKE_STATIC_LIBRARY_SUFFIX} idn) -target_link_libraries(pihole-FTL rt Threads::Threads ${LIBHOGWEED} ${LIBGMP} ${LIBNETTLE} ${LIBIDN}) +# for IDN2 we need the idn2 library which in turn depends on the unistring library +find_library(LIBIDN2 NAMES libidn2${CMAKE_STATIC_LIBRARY_SUFFIX} idn) +find_library(LIBUNISTRING NAMES libunistring${CMAKE_STATIC_LIBRARY_SUFFIX} unistring) + +target_link_libraries(pihole-FTL rt Threads::Threads ${LIBHOGWEED} ${LIBGMP} ${LIBNETTLE} ${LIBIDN2} ${LIBUNISTRING}) if(LUA_DL STREQUAL "true") find_library(LIBDL dl) diff --git a/src/api/list.c b/src/api/list.c index 0502f5ec..42f71441 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -19,8 +19,7 @@ #include "database/network-table.h" // valid_domain() #include "tools/gravity-parseList.h" - -#include +#include static int api_list_read(struct ftl_conn *api, const int code, @@ -75,9 +74,9 @@ static int api_list_read(struct ftl_conn *api, else // domainlists { char *unicode = NULL; - const Idna_rc rc = idna_to_unicode_lzlz(table.domain, &unicode, 0); + const int rc = idn2_to_unicode_lzlz(table.domain, &unicode, IDN2_NONTRANSITIONAL); JSON_COPY_STR_TO_OBJECT(row, "domain", table.domain); - if(rc == IDNA_SUCCESS) + if(rc == IDN2_OK) JSON_COPY_STR_TO_OBJECT(row, "unicode", unicode); else JSON_COPY_STR_TO_OBJECT(row, "unicode", table.domain); @@ -417,14 +416,14 @@ static int api_list_write(struct ftl_conn *api, listtype == GRAVITY_DOMAINLIST_DENY_EXACT) { char *punycode = NULL; - const Idna_rc rc = idna_to_ascii_lz(it->valuestring, &punycode, 0); - if (rc != IDNA_SUCCESS) + const int rc = idn2_to_ascii_lz(it->valuestring, &punycode, IDN2_NONTRANSITIONAL); + if (rc != IDN2_OK) { // Invalid domain name return send_json_error(api, 400, "bad_request", "Invalid request: Invalid domain name", - idna_strerror(rc)); + idn2_strerror(rc)); } // Convert punycode domain to lowercase for(unsigned int i = 0u; i < strlen(punycode); i++) diff --git a/src/api/search.c b/src/api/search.c index 0ed5a3bf..511d9c97 100644 --- a/src/api/search.c +++ b/src/api/search.c @@ -15,7 +15,7 @@ #include "database/gravity-db.h" // match_regex() #include "regex_r.h" -#include +#include #define MAX_SEARCH_RESULTS 10000u @@ -182,18 +182,18 @@ int api_search(struct ftl_conn *api) // use characters drawn from a large repertoire (Unicode), but IDNA // allows the non-ASCII characters to be represented using only the // ASCII characters already allowed in so-called host names today. - // idna_to_ascii_lz() convert domain name in the locale’s encoding to an + // idn2_to_ascii_lz() convert domain name in the locale’s encoding to an // ASCII string. The domain name may contain several labels, separated // by dots. The output buffer must be deallocated by the caller. char *punycode = NULL; - const Idna_rc rc = idna_to_ascii_lz(domain, &punycode, 0); - if (rc != IDNA_SUCCESS) + const int rc = idn2_to_ascii_lz(domain, &punycode, IDN2_NONTRANSITIONAL); + if (rc != IDN2_OK) { // Invalid domain name return send_json_error(api, 400, "bad_request", "Invalid request: Invalid domain name", - idna_strerror(rc)); + idn2_strerror(rc)); } // Convert punycode domain to lowercase diff --git a/src/dnsmasq/config.h b/src/dnsmasq/config.h index df302fbd..d73359be 100644 --- a/src/dnsmasq/config.h +++ b/src/dnsmasq/config.h @@ -205,7 +205,7 @@ RESOLVFILE /* Pi-hole definitions */ #define HAVE_LUASCRIPT -#define HAVE_IDN +#define HAVE_LIBIDN2 #define HAVE_DNSSEC #ifdef DNSMASQ_ALL_OPTS #define HAVE_DBUS From b85f85d73cfebf3a4c771b5b155fdcc19ad36216 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 20 Nov 2023 09:11:42 +0100 Subject: [PATCH 11/27] Add CLI IDN2 conversion interface Signed-off-by: DL6ER --- src/args.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/args.c b/src/args.c index 4696b537..972ba657 100644 --- a/src/args.c +++ b/src/args.c @@ -60,6 +60,8 @@ #include "tools/arp-scan.h" // run_performance_test() #include "config/password.h" +// idn2_to_ascii_lz() +#include // defined in dnsmasq.c extern void print_dnsmasq_version(const char *yellow, const char *green, const char *bold, const char *normal); @@ -427,6 +429,51 @@ void parse_args(int argc, char* argv[]) exit(run_arp_scan(scan_all, extreme_mode)); } + // IDN2 conversion mode + if(argc > 1 && strcmp(argv[1], "idn2") == 0) + { + // Enable stdout printing + cli_mode = true; + if(argc == 3) + { + char *punycode = NULL; + const int rc = idn2_to_ascii_lz(argv[2], &punycode, IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); + if (rc != IDN2_OK) + { + // Invalid domain name + printf("Invalid domain name: %s\n", argv[2]); + exit(EXIT_FAILURE); + } + + // Convert punycode domain to lowercase + for(unsigned int i = 0u; i < strlen(punycode); i++) + punycode[i] = tolower(punycode[i]); + + printf("%s\n", punycode); + exit(EXIT_SUCCESS); + + } + else if(argc == 4 && (strcmp(argv[2], "-d") == 0 || strcmp(argv[2], "--decode") == 0)) + { + char *unicode = NULL; + const int rc = idn2_to_unicode_lzlz(argv[3], &unicode, IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); + if (rc != IDN2_OK) + { + // Invalid domain name + printf("Invalid domain name: %s\n", argv[3]); + exit(EXIT_FAILURE); + } + + printf("%s\n", unicode); + exit(EXIT_SUCCESS); + } + else + { + printf("Usage: %s idn2 \n", argv[0]); + exit(EXIT_FAILURE); + } + } + // start from 1, as argv[0] is the executable name for(int i = 1; i < argc; i++) { @@ -878,6 +925,12 @@ void parse_args(int argc, char* argv[]) printf(" per line (no HOSTS lists, etc.)\n\n"); printf(" Usage: %spihole-FTL gravity checkList %sinfile%s\n\n", green, cyan, normal); + printf("%sIDN2 conversion:%s\n", yellow, normal); + printf(" Convert a given internationalized domain name (IDN) to\n"); + printf(" punycode or vice versa.\n\n"); + printf(" Encoding: %spihole-FTL idn2 %sdomain%s\n", green, cyan, normal); + printf(" Decoding: %spihole-FTL idn2 -d %spunycode%s\n\n", green, cyan, normal); + printf("%sOther:%s\n", yellow, normal); printf("\t%sdhcp-discover%s Discover DHCP servers in the local\n", green, normal); printf("\t network\n"); From 93d0c557e8033d11c81665029b357d44eaf67034 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 20 Nov 2023 09:15:25 +0100 Subject: [PATCH 12/27] Add CI test for IDN2 conversion Signed-off-by: DL6ER --- src/api/list.c | 2 +- src/api/search.c | 5 ++++- src/args.c | 4 +++- test/test_suite.bats | 25 ++++++++++++++++++------- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index 42f71441..c584a334 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -416,7 +416,7 @@ static int api_list_write(struct ftl_conn *api, listtype == GRAVITY_DOMAINLIST_DENY_EXACT) { char *punycode = NULL; - const int rc = idn2_to_ascii_lz(it->valuestring, &punycode, IDN2_NONTRANSITIONAL); + const int rc = idn2_to_ascii_lz(it->valuestring, &punycode, IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); if (rc != IDN2_OK) { // Invalid domain name diff --git a/src/api/search.c b/src/api/search.c index 511d9c97..7358a612 100644 --- a/src/api/search.c +++ b/src/api/search.c @@ -185,8 +185,11 @@ int api_search(struct ftl_conn *api) // idn2_to_ascii_lz() convert domain name in the locale’s encoding to an // ASCII string. The domain name may contain several labels, separated // by dots. The output buffer must be deallocated by the caller. + // Used flags: + // - IDN2_NFC_INPUT: Input is in Unicode Normalization Form C (NFC) + // - IDN2_NONTRANSITIONAL: Use Unicode TR46 non-transitional processing char *punycode = NULL; - const int rc = idn2_to_ascii_lz(domain, &punycode, IDN2_NONTRANSITIONAL); + const int rc = idn2_to_ascii_lz(domain, &punycode, IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); if (rc != IDN2_OK) { // Invalid domain name diff --git a/src/args.c b/src/args.c index 972ba657..57550e52 100644 --- a/src/args.c +++ b/src/args.c @@ -436,6 +436,7 @@ void parse_args(int argc, char* argv[]) cli_mode = true; if(argc == 3) { + // Convert unicode domain to punycode char *punycode = NULL; const int rc = idn2_to_ascii_lz(argv[2], &punycode, IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); if (rc != IDN2_OK) @@ -455,6 +456,7 @@ void parse_args(int argc, char* argv[]) } else if(argc == 4 && (strcmp(argv[2], "-d") == 0 || strcmp(argv[2], "--decode") == 0)) { + // Convert punycode domain to unicode char *unicode = NULL; const int rc = idn2_to_unicode_lzlz(argv[3], &unicode, IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); if (rc != IDN2_OK) @@ -469,7 +471,7 @@ void parse_args(int argc, char* argv[]) } else { - printf("Usage: %s idn2 \n", argv[0]); + printf("Usage: %s idn2 [--decode] \n", argv[0]); exit(EXIT_FAILURE); } } diff --git a/test/test_suite.bats b/test/test_suite.bats index 3ff73043..0fa48c67 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -35,11 +35,7 @@ @test "dnsmasq options as expected" { run bash -c './pihole-FTL -vv | grep "cryptohash"' printf "%s\n" "${lines[@]}" - if [[ "${CI_ARCH}" == "x86_64_full" ]]; then - [[ ${lines[0]} == "Features: IPv6 GNU-getopt DBus no-UBus no-i18n IDN DHCP DHCPv6 Lua TFTP conntrack ipset nftset auth cryptohash DNSSEC loop-detect inotify dumpfile" ]] - else - [[ ${lines[0]} == "Features: IPv6 GNU-getopt no-DBus no-UBus no-i18n IDN DHCP DHCPv6 Lua TFTP no-conntrack ipset no-nftset auth cryptohash DNSSEC loop-detect inotify dumpfile" ]] - fi + [[ ${lines[0]} == "Features: IPv6 GNU-getopt no-DBus no-UBus no-i18n IDN2 DHCP DHCPv6 Lua TFTP no-conntrack ipset no-nftset auth cryptohash DNSSEC loop-detect inotify dumpfile" ]] [[ ${lines[1]} == "" ]] } @@ -1262,7 +1258,7 @@ [[ "${lines[0]}" == "1.1.1.1" ]] } -@test "Custom DNS records: International domains are converted to IDNA form" { +@test "Custom DNS records: International domains are converted to IDN form" { # äste.com ---> xn--ste-pla.com run bash -c "dig A xn--ste-pla.com +short @127.0.0.1" printf "%s\n" "${lines[@]}" @@ -1273,7 +1269,7 @@ [[ "${lines[0]}" == "2.2.2.2" ]] } -@test "Local CNAME records: International domains are converted to IDNA form" { +@test "Local CNAME records: International domains are converted to IDN form" { # brücke.com ---> xn--brcke-lva.com run bash -c "dig A xn--brcke-lva.com +short @127.0.0.1" printf "%s\n" "${lines[@]}" @@ -1282,6 +1278,21 @@ [[ "${lines[1]}" == "2.2.2.2" ]] } +@test "IDN2 CLI interface correctly encodes/decodes domain according to IDNA2008 + TR46" { + run bash -c './pihole-FTL idn2 äste.com' + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "xn--ste-pla.com" ]] + run bash -c './pihole-FTL idn2 -d xn--ste-pla.com' + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "äste.com" ]] + run bash -c './pihole-FTL idn2 ß.de' + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "xn--zca.de" ]] + run bash -c './pihole-FTL idn2 -d xn--zca.de' + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "ß.de" ]] +} + @test "Environmental variable is favored over config file" { # The config file has -10 but we set FTLCONF_misc_nice="-11" run bash -c 'grep -B1 "nice = -11" /etc/pihole/pihole.toml' From d901dae526659b9349157d4c38cd697b9ef993ca Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 20 Nov 2023 15:38:45 +0100 Subject: [PATCH 13/27] Use new ftl-build container v2.4.1 Signed-off-by: DL6ER --- .devcontainer/devcontainer.json | 2 +- .github/Dockerfile | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2710e65a..6c42c2d0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "FTL x86_64 Build Env", - "image": "ghcr.io/pi-hole/ftl-build:v2.3-alpine", + "image": "ghcr.io/pi-hole/ftl-build:v2.4.1", "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], "extensions": [ "jetmartin.bats", diff --git a/.github/Dockerfile b/.github/Dockerfile index d857ec40..c0f6e7b6 100644 --- a/.github/Dockerfile +++ b/.github/Dockerfile @@ -1,5 +1,4 @@ -ARG BUILDER="alpine" -FROM ghcr.io/pi-hole/ftl-build:v2.3-${BUILDER} AS builder +FROM ghcr.io/pi-hole/ftl-build:v2.4.1 AS builder WORKDIR /app From d6f30b393185a98102ffbdf8859d3da486f83168 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 20 Nov 2023 22:26:44 +0100 Subject: [PATCH 14/27] Install safe-guards for string-related functions to not crash when accidentially supplied with a NULL pointer Signed-off-by: DL6ER --- src/FTL.h | 16 +++ src/database/gravity-db.c | 4 +- src/syscalls/CMakeLists.txt | 1 + src/syscalls/string.c | 251 ++++++++++++++++++++++++++++++++++++ src/syscalls/syscalls.h | 18 +++ 5 files changed, 288 insertions(+), 2 deletions(-) create mode 100644 src/syscalls/string.c diff --git a/src/FTL.h b/src/FTL.h index 2b11b32e..03fe34b0 100644 --- a/src/FTL.h +++ b/src/FTL.h @@ -161,6 +161,22 @@ #define pthread_mutex_lock(mutex) FTLpthread_mutex_lock(mutex, __FILE__, __FUNCTION__, __LINE__) #define fopen(pathname, mode) FTLfopen(pathname, mode, __FILE__, __FUNCTION__, __LINE__) #define ftlallocate(fd, offset, len) FTLfallocate(fd, offset, len, __FILE__, __FUNCTION__, __LINE__) +#define strlen(str) FTLstrlen(str, __FILE__, __FUNCTION__, __LINE__) +#define strnlen(str, maxlen) FTLstrnlen(str, maxlen, __FILE__, __FUNCTION__, __LINE__) +#define strcpy(dest, src) FTLstrcpy(dest, src, __FILE__, __FUNCTION__, __LINE__) +#define strncpy(dest, src, n) FTLstrncpy(dest, src, n, __FILE__, __FUNCTION__, __LINE__) +#define memset(s, c, n) FTLmemset(s, c, n, __FILE__, __FUNCTION__, __LINE__) +#define memcpy(dest, src, n) FTLmemcpy(dest, src, n, __FILE__, __FUNCTION__, __LINE__) +#define memmove(dest, src, n) FTLmemmove(dest, src, n, __FILE__, __FUNCTION__, __LINE__) +#define strstr(haystack, needle) FTLstrstr(haystack, needle, __FILE__, __FUNCTION__, __LINE__) +#define strcmp(s1, s2) FTLstrcmp(s1, s2, __FILE__, __FUNCTION__, __LINE__) +#define strncmp(s1, s2, n) FTLstrncmp(s1, s2, n, __FILE__, __FUNCTION__, __LINE__) +#define strcasecmp(s1, s2) FTLstrcasecmp(s1, s2, __FILE__, __FUNCTION__, __LINE__) +#define strncasecmp(s1, s2, n) FTLstrncasecmp(s1, s2, n, __FILE__, __FUNCTION__, __LINE__) +#define strcat(dest, src) FTLstrcat(dest, src, __FILE__, __FUNCTION__, __LINE__) +#define strncat(dest, src, n) FTLstrncat(dest, src, n, __FILE__, __FUNCTION__, __LINE__) +#define memcmp(s1, s2, n) FTLmemcmp(s1, s2, n, __FILE__, __FUNCTION__, __LINE__) +#define memmem(haystack, haystacklen, needle, needlelen) FTLmemmem(haystack, haystacklen, needle, needlelen, __FILE__, __FUNCTION__, __LINE__) #include "syscalls/syscalls.h" // Preprocessor help functions diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 7e66df2e..b2dec109 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -259,8 +259,8 @@ static char* get_client_querystr(const char *table, const char *column, const ch } // Determine whether to show IP or hardware address -static inline const char *show_client_string(const char *hwaddr, const char *hostname, - const char *ip) +static const char *show_client_string(const char *hwaddr, const char *hostname, + const char *ip) { if(hostname != NULL && strlen(hostname) > 0) { diff --git a/src/syscalls/CMakeLists.txt b/src/syscalls/CMakeLists.txt index 8582ac51..10103094 100644 --- a/src/syscalls/CMakeLists.txt +++ b/src/syscalls/CMakeLists.txt @@ -25,6 +25,7 @@ set(sources snprintf.c sprintf.c strdup.c + string.c syscalls.h vasprintf.c vfprintf.c diff --git a/src/syscalls/string.c b/src/syscalls/string.c new file mode 100644 index 00000000..88254e35 --- /dev/null +++ b/src/syscalls/string.c @@ -0,0 +1,251 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2023 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* Pi-hole syscall implementation for string-related functions +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "../FTL.h" +//#include "syscalls.h" is implicitly done in FTL.h +#include "../log.h" + +#undef strlen +size_t FTLstrlen(const char *s, const char *file, const char *func, const int line) +{ + // The strlen() function calculates the length of the string s, not + // including the terminating '\0' character. + if(s == NULL) + { + log_err("Trying to get the length of a NULL string in %s() (%s:%i)", func, file, line); + return 0; + } + return strlen(s); +} + +#undef strnlen +size_t FTLstrnlen(const char *s, const size_t maxlen, const char *file, const char *func, const int line) +{ + // The strnlen() function returns the number of characters in the string s, + // not including the terminating '\0' character, but at most maxlen. In + // doing this, strnlen() looks only at the first maxlen characters at s and + // never beyond s+maxlen. + if(s == NULL) + { + log_err("Trying to get the length of a NULL string in %s() (%s:%i)", func, file, line); + return 0; + } + return strnlen(s, maxlen); +} + +#undef strstr +char *FTLstrstr(const char *haystack, const char *needle, const char *file, const char *func, const int line) +{ + // The strstr() function finds the first occurrence of the substring needle + // in the string haystack. The terminating '\0' characters are not + // compared. + if(haystack == NULL || needle == NULL) + { + log_err("Trying to find a NULL (%s%s) string in %s() (%s:%i)", + haystack == NULL ? "L" : "", needle == NULL ? "R" : "", func, file, line); + return NULL; + } + return strstr(haystack, needle); +} + +#undef strcmp +int FTLstrcmp(const char *s1, const char *s2, const char *file, const char *func, const int line) +{ + // The strcmp() function compares the two strings s1 and s2. It returns an + // integer less than, equal to, or greater than zero if s1 is found, + // respectively, to be less than, to match, or be greater than s2. + if(s1 == NULL || s2 == NULL) + { + log_err("Trying to compare a NULL (%s%s) string in %s() (%s:%i)", + s1 == NULL ? "L" : "", s2 == NULL ? "R" : "", func, file, line); + return -1; + } + return strcmp(s1, s2); +} + +#undef strncmp +int FTLstrncmp(const char *s1, const char *s2, const size_t n, const char *file, const char *func, const int line) +{ + // The strncmp() function is similar, except it compares only the first (at + // most) n bytes of s1 and s2. + if(s1 == NULL || s2 == NULL) + { + log_err("Trying to compare a NULL (%s%s) string in %s() (%s:%i)", + s1 == NULL ? "L" : "", s2 == NULL ? "R" : "", func, file, line); + return -1; + } + return strncmp(s1, s2, n); +} + +#undef strcasecmp +int FTLstrcasecmp(const char *s1, const char *s2, const char *file, const char *func, const int line) +{ + // The strcasecmp() function performs a byte-by-byte comparison of the + // strings s1 and s2, ignoring the case of the characters. It returns an + // integer less than, equal to, or greater than zero if s1 is found, + // respectively, to be less than, to match, or be greater than s2. + if(s1 == NULL || s2 == NULL) + { + log_err("Trying to compare a NULL (%s%s) string in %s() (%s:%i)", + s1 == NULL ? "L" : "", s2 == NULL ? "R" : "", func, file, line); + return -1; + } + return strcasecmp(s1, s2); +} + +#undef strncasecmp +int FTLstrncasecmp(const char *s1, const char *s2, const size_t n, const char *file, const char *func, const int line) +{ + // The strncasecmp() function is similar, except it compares only the first + // (at most) n bytes of s1 and s2. + if(s1 == NULL || s2 == NULL) + { + log_err("Trying to compare a NULL (%s%s) string in %s() (%s:%i)", + s1 == NULL ? "L" : "", s2 == NULL ? "R" : "", func, file, line); + return -1; + } + return strncasecmp(s1, s2, n); +} + +#undef strcat +char *FTLstrcat(char *dest, const char *src, const char *file, const char *func, const int line) +{ + // The strcat() function appends the src string to the dest string, + // overwriting the terminating null byte ('\0') at the end of dest, and then + // adds a terminating null byte. The strings may not overlap, and the dest + // string must have enough space for the result. If dest is not large enough, + // program behavior is unpredictable; buffer overruns are a favorite avenue + // for attacking secure programs. + if(dest == NULL || src == NULL) + { + log_err("Trying to concatenate a NULL (%s%s) string in %s() (%s:%i)", + dest == NULL ? "L" : "", src == NULL ? "R" : "", func, file, line); + return NULL; + } + return strcat(dest, src); +} + +#undef strncat +char *FTLstrncat(char *dest, const char *src, const size_t n, const char *file, const char *func, const int line) +{ + // The strncat() function is similar, except that it will use at most n bytes + // from src; and src does not need to be null-terminated if it contains n or + // more bytes. + if(dest == NULL || src == NULL) + { + log_err("Trying to concatenate a NULL (%s%s) string in %s() (%s:%i)", + dest == NULL ? "L" : "", src == NULL ? "R" : "", func, file, line); + return NULL; + } + return strncat(dest, src, n); +} + +#undef strcpy +char *FTLstrcpy(char *dest, const char *src, const char *file, const char *func, const int line) +{ + // The strcpy() function copies the string src to dest (including the + // terminating '\0' character.) + if(dest == NULL || src == NULL) + { + log_err("Trying to copy a NULL (%s%s) string in %s() (%s:%i)", + dest == NULL ? "L" : "", src == NULL ? "R" : "", func, file, line); + return NULL; + } + return strcpy(dest, src); +} + +#undef strncpy +char *FTLstrncpy(char *dest, const char *src, const size_t n, const char *file, const char *func, const int line) +{ + // The strncpy() function is similar, except that at most n bytes of src are + // copied. Warning: If there is no null byte among the first n bytes of src, + // the string placed in dest will not be null-terminated. + if(dest == NULL || src == NULL) + { + log_err("Trying to copy a NULL (%s%s) string in %s() (%s:%i)", + dest == NULL ? "L" : "", src == NULL ? "R" : "", func, file, line); + return NULL; + } + return strncpy(dest, src, n); +} + +#undef memset +void *FTLmemset(void *s, const int c, const size_t n, const char *file, const char *func, const int line) +{ + // The memset() function fills the first n bytes of the memory area pointed + // to by s with the constant byte c. + if(s == NULL) + { + log_err("Trying to fill a NULL memory area in %s() (%s:%i)", func, file, line); + return NULL; + } + return memset(s, c, n); +} + +#undef memcpy +void *FTLmemcpy(void *dest, const void *src, const size_t n, const char *file, const char *func, const int line) +{ + // The memcpy() function copies n bytes from memory area src to memory area + // dest. The memory areas must not overlap. Use memmove(3) if the memory + // areas do overlap. + if(dest == NULL || src == NULL) + { + log_err("Trying to copy a NULL (%s%s) memory area in %s() (%s:%i)", + dest == NULL ? "L" : "", src == NULL ? "R" : "", func, file, line); + return NULL; + } + return memcpy(dest, src, n); +} + +#undef memmove +void *FTLmemmove(void *dest, const void *src, const size_t n, const char *file, const char *func, const int line) +{ + // The memmove() function copies n bytes from memory area src to memory area + // dest. The memory areas may overlap: copying takes place as though the + // bytes in src are first copied into a temporary array that does not + // overlap src or dest, and the bytes are then copied from the temporary + // array to dest. + if(dest == NULL || src == NULL) + { + log_err("Trying to move a NULL (%s%s) memory area in %s() (%s:%i)", + dest == NULL ? "L" : "", src == NULL ? "R" : "", func, file, line); + return NULL; + } + return memmove(dest, src, n); +} + +#undef memcmp +int FTLmemcmp(const void *s1, const void *s2, const size_t n, const char *file, const char *func, const int line) +{ + // The memcmp() function compares the first n bytes (each interpreted as + // unsigned char) of the memory areas s1 and s2. + if(s1 == NULL || s2 == NULL) + { + log_err("Trying to compare a NULL (%s%s) memory area in %s() (%s:%i)", + s1 == NULL ? "L" : "", s2 == NULL ? "R" : "", func, file, line); + return -1; + } + return memcmp(s1, s2, n); +} + +#undef memmem +void *FTLmemmem(const void *haystack, const size_t haystacklen, const void *needle, const size_t needlelen, const char *file, const char *func, const int line) +{ + // The memmem() function finds the start of the first occurrence of the + // substring needle of length needlelen in the memory area haystack of + // length haystacklen. + if(haystack == NULL || needle == NULL) + { + log_err("Trying to find a NULL (%s%s) memory area in %s() (%s:%i)", + haystack == NULL ? "L" : "", needle == NULL ? "R" : "", func, file, line); + return NULL; + } + return memmem(haystack, haystacklen, needle, needlelen); +} diff --git a/src/syscalls/syscalls.h b/src/syscalls/syscalls.h index dfe51003..3d77ebe7 100644 --- a/src/syscalls/syscalls.h +++ b/src/syscalls/syscalls.h @@ -50,4 +50,22 @@ FILE *FTLfopen(const char *pathname, const char *mode, const char *file, const c // Syscall helpers void syscalls_report_error(const char *error, FILE *stream, const int _errno, const char *format, const char *func, const char *file, const int line); +// String-related functions +size_t FTLstrlen(const char *s, const char *file, const char *func, const int line); +size_t FTLstrnlen(const char *s, const size_t maxlen, const char *file, const char *func, const int line); +char *FTLstrcpy(char *dest, const char *src, const char *file, const char *func, const int line); +char *FTLstrncpy(char *dest, const char *src, const size_t n, const char *file, const char *func, const int line); +void *FTLmemset(void *s, const int c, const size_t n, const char *file, const char *func, const int line); +void *FTLmemcpy(void *dest, const void *src, const size_t n, const char *file, const char *func, const int line); +void *FTLmemmove(void *dest, const void *src, const size_t n, const char *file, const char *func, const int line); +char *FTLstrstr(const char *haystack, const char *needle, const char *file, const char *func, const int line); +int FTLstrcmp(const char *s1, const char *s2, const char *file, const char *func, const int line); +int FTLstrncmp(const char *s1, const char *s2, const size_t n, const char *file, const char *func, const int line); +int FTLstrcasecmp(const char *s1, const char *s2, const char *file, const char *func, const int line); +int FTLstrncasecmp(const char *s1, const char *s2, const size_t n, const char *file, const char *func, const int line); +char *FTLstrcat(char *dest, const char *src, const char *file, const char *func, const int line); +char *FTLstrncat(char *dest, const char *src, const size_t n, const char *file, const char *func, const int line); +int FTLmemcmp(const void *s1, const void *s2, const size_t n, const char *file, const char *func, const int line); +void *FTLmemmem(const void *haystack, const size_t haystacklen, const void *needle, const size_t needlelen, const char *file, const char *func, const int line); + #endif //SYSCALLS_H From 5320dc3a848ce3d19541203bc0a6fcfdccb2d35e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 20 Nov 2023 22:30:33 +0100 Subject: [PATCH 15/27] Verify we have no default string pointers to NULL Signed-off-by: DL6ER --- src/config/config.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/config/config.c b/src/config/config.c index 85578a95..7de3a7c4 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -1333,6 +1333,13 @@ void initConfig(struct config *conf) log_err("Config option %s has no type!", conf_item->k); continue; } + + // Verify we have no default string pointers to NULL + if((conf_item->t == CONF_STRING || conf_item->t == CONF_STRING_ALLOCATED) && conf_item->d.s == NULL) + { + log_err("Config option %s has NULL default string!", conf_item->k); + continue; + } } } From 015708226eeb40b12affabcc926c3bd4b47ff680 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 20 Nov 2023 22:35:30 +0100 Subject: [PATCH 16/27] Add dedicated function to reset config values to their defaults Signed-off-by: DL6ER --- src/config/config.c | 48 +++++++++++++++++++++++++++++++++------------ src/config/config.h | 1 + 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/config/config.c b/src/config/config.c index 7de3a7c4..7d20c57c 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -1304,18 +1304,7 @@ void initConfig(struct config *conf) // Initialize config value with default one for all *except* the log file path if(conf_item != &conf->files.log.ftl) - { - if(conf_item->t == CONF_JSON_STRING_ARRAY) - // JSON objects really need to be duplicated as the config - // structure stores only a pointer to memory somewhere else - conf_item->v.json = cJSON_Duplicate(conf_item->d.json, true); - else if(conf_item->t == CONF_STRING_ALLOCATED) - // Allocated string: Make our own copy - conf_item->v.s = strdup(conf_item->d.s); - else - // Ordinary value: Simply copy the union over - memcpy(&conf_item->v, &conf_item->d, sizeof(conf_item->d)); - } + reset_config(conf_item); // Parse and split paths conf_item->p = gen_config_path(conf_item->k, '.'); @@ -1340,6 +1329,41 @@ void initConfig(struct config *conf) log_err("Config option %s has NULL default string!", conf_item->k); continue; } + + // Verify we have no default JSON pointers to NULL + if(conf_item->t == CONF_JSON_STRING_ARRAY && conf_item->d.json == NULL) + { + log_err("Config option %s has NULL default JSON array!", conf_item->k); + continue; + } + } +} + +void reset_config(struct conf_item *conf_item) +{ + if(conf_item->t == CONF_JSON_STRING_ARRAY) + { + // Free allocated memory (if any) + if(conf_item->v.json != NULL) + cJSON_Delete(conf_item->v.json); + + // JSON objects really need to be duplicated as the config + // structure stores only a pointer to memory somewhere else + conf_item->v.json = cJSON_Duplicate(conf_item->d.json, true); + } + else if(conf_item->t == CONF_STRING_ALLOCATED) + { + // Free allocated memory (if any) + if(conf_item->v.s != NULL) + free(conf_item->v.s); + + // Allocated string: Make our own copy + conf_item->v.s = strdup(conf_item->d.s); + } + else + { + // Ordinary value: Simply copy the union over + memcpy(&conf_item->v, &conf_item->d, sizeof(conf_item->d)); } } diff --git a/src/config/config.h b/src/config/config.h index dc493739..a66d8c3e 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -323,6 +323,7 @@ extern struct config config; void set_debug_flags(struct config *conf); void set_all_debug(struct config *conf, const bool status); void initConfig(struct config *conf); +void reset_config(struct conf_item *conf_item); void readFTLconf(struct config *conf, const bool rewrite); bool getLogFilePath(void); struct conf_item *get_conf_item(struct config *conf, const unsigned int n); From e2f35017c04bafd882f17a095755f882ad0d54ba Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 21 Nov 2023 09:30:35 +0100 Subject: [PATCH 17/27] Fix legacy settings parsing: All the DHCP-related IP addresses are not of type INADDR, not STRING! Signed-off-by: DL6ER --- src/config/legacy_reader.c | 12 +++---- src/setupVars.c | 69 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/config/legacy_reader.c b/src/config/legacy_reader.c index bf4600f8..9ae6e664 100644 --- a/src/config/legacy_reader.c +++ b/src/config/legacy_reader.c @@ -401,7 +401,7 @@ const char *readFTLlegacy(struct config *conf) conf->dns.reply.host.force4.v.b = false; conf->dns.reply.host.v4.v.in_addr.s_addr = 0; buffer = parseFTLconf(fp, "LOCAL_IPV4"); - if(buffer != NULL && inet_pton(AF_INET, buffer, &conf->dns.reply.host.v4.v.in_addr)) + if(buffer != NULL && strlen(buffer) > 0 && inet_pton(AF_INET, buffer, &conf->dns.reply.host.v4.v.in_addr)) conf->dns.reply.host.force4.v.b = true; // LOCAL_IPV6 @@ -411,7 +411,7 @@ const char *readFTLlegacy(struct config *conf) conf->dns.reply.host.force6.v.b = false; memset(&conf->dns.reply.host.v6.v.in6_addr, 0, sizeof(conf->dns.reply.host.v6.v.in6_addr)); buffer = parseFTLconf(fp, "LOCAL_IPV6"); - if(buffer != NULL && inet_pton(AF_INET6, buffer, &conf->dns.reply.host.v6.v.in6_addr)) + if(buffer != NULL && strlen(buffer) > 0 && inet_pton(AF_INET6, buffer, &conf->dns.reply.host.v6.v.in6_addr)) conf->dns.reply.host.force6.v.b = true; // BLOCK_IPV4 @@ -420,7 +420,7 @@ const char *readFTLlegacy(struct config *conf) conf->dns.reply.blocking.force4.v.b = false; conf->dns.reply.blocking.v4.v.in_addr.s_addr = 0; buffer = parseFTLconf(fp, "BLOCK_IPV4"); - if(buffer != NULL && inet_pton(AF_INET, buffer, &conf->dns.reply.blocking.v4.v.in_addr)) + if(buffer != NULL && strlen(buffer) > 0 && inet_pton(AF_INET, buffer, &conf->dns.reply.blocking.v4.v.in_addr)) conf->dns.reply.blocking.force4.v.b = true; // BLOCK_IPV6 @@ -429,7 +429,7 @@ const char *readFTLlegacy(struct config *conf) conf->dns.reply.blocking.force6.v.b = false; memset(&conf->dns.reply.blocking.v6.v.in6_addr, 0, sizeof(conf->dns.reply.host.v6.v.in6_addr)); buffer = parseFTLconf(fp, "BLOCK_IPV6"); - if(buffer != NULL && inet_pton(AF_INET6, buffer, &conf->dns.reply.blocking.v6.v.in6_addr)) + if(buffer != NULL && strlen(buffer) > 0 && inet_pton(AF_INET6, buffer, &conf->dns.reply.blocking.v6.v.in6_addr)) conf->dns.reply.blocking.force6.v.b = true; // REPLY_ADDR4 (deprecated setting) @@ -438,7 +438,7 @@ const char *readFTLlegacy(struct config *conf) // defaults to: not set struct in_addr reply_addr4; buffer = parseFTLconf(fp, "REPLY_ADDR4"); - if(buffer != NULL && inet_pton(AF_INET, buffer, &reply_addr4)) + if(buffer != NULL && strlen(buffer) > 0 && inet_pton(AF_INET, buffer, &reply_addr4)) { if(conf->dns.reply.host.force4.v.b || conf->dns.reply.blocking.force4.v.b) { @@ -459,7 +459,7 @@ const char *readFTLlegacy(struct config *conf) // defaults to: not set struct in6_addr reply_addr6; buffer = parseFTLconf(fp, "REPLY_ADDR6"); - if(buffer != NULL && inet_pton(AF_INET, buffer, &reply_addr6)) + if(buffer != NULL && strlen(buffer) > 0 && inet_pton(AF_INET, buffer, &reply_addr6)) { if(conf->dns.reply.host.force6.v.b || conf->dns.reply.blocking.force6.v.b) { diff --git a/src/setupVars.c b/src/setupVars.c index ca069d5f..e0040571 100644 --- a/src/setupVars.c +++ b/src/setupVars.c @@ -19,6 +19,13 @@ char ** setupVarsArray = NULL; static void get_conf_string_from_setupVars(const char *key, struct conf_item *conf_item) { + // Verify we are allowed to use this function + if(conf_item->t != CONF_STRING && conf_item->t != CONF_STRING_ALLOCATED) + { + log_err("get_conf_string_from_setupVars(%s) failed: conf_item->t is neither CONF_STRING nor CONF_STRING_ALLOCATED", key); + return; + } + const char *setupVarsValue = read_setupVarsconf(key); if(setupVarsValue == NULL) { @@ -43,8 +50,50 @@ static void get_conf_string_from_setupVars(const char *key, struct conf_item *co log_debug(DEBUG_CONFIG, "setupVars.conf:%s -> Setting %s to %s", key, conf_item->k, conf_item->v.s); } +static void get_conf_ipv4_from_setupVars(const char *key, struct conf_item *conf_item) +{ + // Verify we are allowed to use this function + if(conf_item->t != CONF_STRUCT_IN_ADDR) + { + log_err("get_conf_ipv4_from_setupVars(%s) failed: conf_item->t != CONF_STRUCT_IN_ADDR", key); + return; + } + + const char *setupVarsValue = read_setupVarsconf(key); + if(setupVarsValue == NULL) + { + // Do not change default value, this value is not set in setupVars.conf + log_debug(DEBUG_CONFIG, "setupVars.conf:%s -> Not set", key); + + // Free memory, harmless to call if read_setupVarsconf() didn't return a result + clearSetupVarsArray(); + return; + } + + if(strlen(setupVarsValue) == 0) + memset(&conf_item->v.in_addr, 0, sizeof(struct in_addr)); + else if(inet_pton(AF_INET, setupVarsValue, &conf_item->v.in_addr) != 1) + { + log_debug(DEBUG_CONFIG, "setupVars.conf:%s -> Invalid IPv4 address: %s", key, setupVarsValue); + memset(&conf_item->v.in_addr, 0, sizeof(struct in_addr)); + } + + // Free memory, harmless to call if read_setupVarsconf() didn't return a result + clearSetupVarsArray(); + + // Parameter present in setupVars.conf + log_debug(DEBUG_CONFIG, "setupVars.conf:%s -> Setting %s to %s", key, conf_item->k, inet_ntoa(conf_item->v.in_addr)); +} + static void get_conf_bool_from_setupVars(const char *key, struct conf_item *conf_item) { + // Verify we are allowed to use this function + if(conf_item->t != CONF_BOOL) + { + log_err("get_conf_bool_from_setupVars(%s) failed: conf_item->t != CONF_BOOL", key); + return; + } + const char *boolean = read_setupVarsconf(key); if(boolean == NULL) @@ -72,6 +121,13 @@ static void get_conf_bool_from_setupVars(const char *key, struct conf_item *conf static void get_conf_string_array_from_setupVars(const char *key, struct conf_item *conf_item) { + // Verify we are allowed to use this function + if(conf_item->t != CONF_JSON_STRING_ARRAY) + { + log_err("get_conf_string_array_from_setupVars(%s) failed: conf_item->t != CONF_JSON_STRING_ARRAY", key); + return; + } + // Get clients which the user doesn't want to see const char *array = read_setupVarsconf(key); @@ -95,6 +151,13 @@ static void get_conf_string_array_from_setupVars(const char *key, struct conf_it static void get_conf_upstream_servers_from_setupVars(struct conf_item *conf_item) { + // Verify we are allowed to use this function + if(conf_item->t != CONF_JSON_STRING_ARRAY) + { + log_err("get_conf_upstream_servers_from_setupVars() failed: conf_item->t != CONF_JSON_STRING_ARRAY"); + return; + } + // Try to import up to 50 servers... #define MAX_SERVERS 50 for(unsigned int j = 0; j < MAX_SERVERS; j++) @@ -360,9 +423,9 @@ void importsetupVarsConf(void) // Try to obtain DHCP settings get_conf_bool_from_setupVars("DHCP_ACTIVE", &config.dhcp.active); - get_conf_string_from_setupVars("DHCP_START", &config.dhcp.start); - get_conf_string_from_setupVars("DHCP_END", &config.dhcp.end); - get_conf_string_from_setupVars("DHCP_ROUTER", &config.dhcp.router); + get_conf_ipv4_from_setupVars("DHCP_START", &config.dhcp.start); + get_conf_ipv4_from_setupVars("DHCP_END", &config.dhcp.end); + get_conf_ipv4_from_setupVars("DHCP_ROUTER", &config.dhcp.router); get_conf_string_from_setupVars("DHCP_LEASETIME", &config.dhcp.leaseTime); // If the DHCP lease time is set to "24", it is interpreted as "24h". From 46eca50db033e5720934a828002c796f9ce33312 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 21 Nov 2023 09:43:08 +0100 Subject: [PATCH 18/27] Fix logging when reading the TLS certificate Signed-off-by: DL6ER --- src/webserver/x509.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/webserver/x509.c b/src/webserver/x509.c index cb154c4a..e4d224fe 100644 --- a/src/webserver/x509.c +++ b/src/webserver/x509.c @@ -316,26 +316,26 @@ enum cert_check read_certificate(const char* certfile, const char *domain, const mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_init(&ctr_drbg); - printf("Reading certificate from %s ...\n\n", certfile); + log_info("Reading certificate from %s ...", certfile); // Check if the file exists and is readable if(access(certfile, R_OK) != 0) { - log_err("Could not read certificate file: %s\n", strerror(errno)); + log_err("Could not read certificate file: %s", strerror(errno)); return CERT_FILE_NOT_FOUND; } int rc = mbedtls_pk_parse_keyfile(&key, certfile, NULL, mbedtls_ctr_drbg_random, &ctr_drbg); if (rc != 0) { - log_err("Cannot parse key: Error code %d\n", rc); + log_err("Cannot parse key: Error code %d", rc); return CERT_CANNOT_PARSE_KEY; } rc = mbedtls_x509_crt_parse_file(&crt, certfile); if (rc != 0) { - log_err("Cannot parse certificate: Error code %d\n", rc); + log_err("Cannot parse certificate: Error code %d", rc); return CERT_CANNOT_PARSE_CERT; } From f4e830ca17ec4cad6f1ba57c6a33be77ea71c4a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Tue, 21 Nov 2023 12:36:54 +0100 Subject: [PATCH 19/27] Only log how many sessions have been restored MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Christian König --- src/database/session-table.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/database/session-table.c b/src/database/session-table.c index d185a55c..c84eb097 100644 --- a/src/database/session-table.c +++ b/src/database/session-table.c @@ -296,8 +296,8 @@ bool restore_db_sessions(struct session *sessions, const uint16_t max_sessions) i++; } - log_info("Restored %u/%u API session%s from the database", - i, max_sessions, max_sessions == 1 ? "" : "s"); + log_info("Restored %u API session%s from the database", + i, i == 1 ? "" : "s"); // Finalize statement if(sqlite3_finalize(stmt) != SQLITE_OK) From 1cde68bce76da3327fb08fd59dbe35d24e376273 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 21 Nov 2023 14:10:02 +0100 Subject: [PATCH 20/27] Add ability to get most recent client hostname from network table if specified by MAC address Signed-off-by: DL6ER --- src/api/list.c | 4 +- src/database/network-table.c | 105 +++++++++++++++++++++++++++++++++++ src/database/network-table.h | 8 ++- src/database/sqlite3-ext.c | 26 --------- 4 files changed, 113 insertions(+), 30 deletions(-) diff --git a/src/api/list.c b/src/api/list.c index c584a334..f0185846 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -58,9 +58,11 @@ static int api_list_read(struct ftl_conn *api, char *name = NULL; if(table.client != NULL) { - // Try to obtain hostname if this is a valid IP address + // Try to obtain hostname if(isValidIPv4(table.client) || isValidIPv6(table.client)) name = getNameFromIP(NULL, table.client); + else if(isMAC(table.client)) + name = getNameFromMAC(table.client); } JSON_COPY_STR_TO_OBJECT(row, "client", table.client); diff --git a/src/database/network-table.c b/src/database/network-table.c index 169ba9c8..92837b31 100644 --- a/src/database/network-table.c +++ b/src/database/network-table.c @@ -2144,6 +2144,85 @@ char *__attribute__((malloc)) getNameFromIP(sqlite3 *db, const char *ipaddr) return name; } +// Get most recently seen host name of device identified by MAC address +char *__attribute__((malloc)) getNameFromMAC(const char *client) +{ + // Return early if database is known to be broken + if(FTLDBerror()) + return NULL; + + log_info("Looking up host name for %s", client); + + // Open pihole-FTL.db database file + sqlite3 *db = NULL; + if((db = dbopen(false, false)) == NULL) + { + log_warn("getNameFromMAC(\"%s\") - Failed to open DB", client); + return NULL; + } + + // Nothing found for the same device + // Check for a host name associated with the given client as MAC address + // COLLATE NOCASE: Case-insensitive comparison + const char *querystr = "SELECT name FROM network_addresses " + "WHERE name IS NOT NULL AND " + "network_id = (SELECT id FROM network WHERE hwaddr = ? COLLATE NOCASE) " + "ORDER BY lastSeen DESC LIMIT 1"; + sqlite3_stmt *stmt = NULL; + int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL); + if(rc != SQLITE_OK) + { + log_err("getNameFromMAC(\"%s\") - SQL error prepare: %s", + client, sqlite3_errstr(rc)); + dbclose(&db); + return NULL; + } + + // Bind client to prepared statement + if((rc = sqlite3_bind_text(stmt, 1, client, -1, SQLITE_STATIC)) != SQLITE_OK) + { + log_warn("getNameFromMAC(\"%s\"): Failed to bind ip: %s", + client, sqlite3_errstr(rc)); + checkFTLDBrc(rc); + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + + dbclose(&db); + return NULL; + } + + char *name = NULL; + rc = sqlite3_step(stmt); + if(rc == SQLITE_ROW) + { + // Database record found (result might be empty) + name = strdup((char*)sqlite3_column_text(stmt, 0)); + + if(config.debug.resolver.v.b) + log_debug(DEBUG_RESOLVER, "Found database host name (by MAC) %s -> %s", + client, name); + } + else if(rc == SQLITE_DONE) + { + // Not found + if(config.debug.resolver.v.b) + log_debug(DEBUG_RESOLVER, " ---> not found"); + } + else + { + // Error + checkFTLDBrc(rc); + return NULL; + } + + // Finalize statement and close database handle + sqlite3_reset(stmt); + sqlite3_finalize(stmt); + + dbclose(&db); + return name; +} + // Get interface of device identified by IP address char *__attribute__((malloc)) getIfaceFromIP(sqlite3 *db, const char *ipaddr) { @@ -2425,3 +2504,29 @@ bool networkTable_deleteDevice(sqlite3 *db, const int id, const char **message) return true; } + +// Counting number of occurrences of a specific char in a string +static size_t __attribute__ ((pure)) count_char(const char *haystack, const char needle) +{ + size_t count = 0u; + while(*haystack) + if (*haystack++ == needle) + ++count; + return count; +} + +// Identify MAC addresses using a set of suitable criteria +bool __attribute__ ((pure)) isMAC(const char *input) +{ + if(input != NULL && // Valid input + strlen(input) == 17u && // MAC addresses are always 17 chars long (6 bytes + 5 colons) + count_char(input, ':') == 5u && // MAC addresses always have 5 colons + strstr(input, "::") == NULL) // No double-colons (IPv6 address abbreviation) + { + // This is a MAC address of the form AA:BB:CC:DD:EE:FF + return true; + } + + // Not a MAC address + return false; +} diff --git a/src/database/network-table.h b/src/database/network-table.h index 4b9f6952..1cb4697d 100644 --- a/src/database/network-table.h +++ b/src/database/network-table.h @@ -18,12 +18,14 @@ bool create_network_addresses_with_names_table(sqlite3 *db); void parse_neighbor_cache(sqlite3 *db); void updateMACVendorRecords(sqlite3 *db); bool unify_hwaddr(sqlite3 *db); -char* __attribute__((malloc)) getMACfromIP(sqlite3 *db, const char* ipaddr); +char *getMACfromIP(sqlite3 *db, const char* ipaddr) __attribute__((malloc)); int getAliasclientIDfromIP(sqlite3 *db, const char *ipaddr); -char* __attribute__((malloc)) getNameFromIP(sqlite3 *db, const char* ipaddr); -char* __attribute__((malloc)) getIfaceFromIP(sqlite3 *db, const char* ipaddr); +char *getNameFromIP(sqlite3 *db, const char* ipaddr) __attribute__((malloc)); +char *getNameFromMAC(const char *client) __attribute__((malloc)); +char *getIfaceFromIP(sqlite3 *db, const char* ipaddr) __attribute__((malloc)); void resolveNetworkTableNames(void); bool flush_network_table(void); +bool isMAC(const char *input) __attribute__ ((pure)); typedef struct { unsigned int id; diff --git a/src/database/sqlite3-ext.c b/src/database/sqlite3-ext.c index 67059ff7..f52ef029 100644 --- a/src/database/sqlite3-ext.c +++ b/src/database/sqlite3-ext.c @@ -29,32 +29,6 @@ // isMAC() #include "network-table.h" -// Counting number of occurrences of a specific char in a string -static size_t __attribute__ ((pure)) count_char(const char *haystack, const char needle) -{ - size_t count = 0u; - while(*haystack) - if (*haystack++ == needle) - ++count; - return count; -} - -// Identify MAC addresses using a set of suitable criteria -static bool __attribute__ ((pure)) isMAC(const char *input) -{ - if(input != NULL && // Valid input - strlen(input) == 17u && // MAC addresses are always 17 chars long (6 bytes + 5 colons) - count_char(input, ':') == 5u && // MAC addresses always have 5 colons - strstr(input, "::") == NULL) // No double-colons (IPv6 address abbreviation) - { - // This is a MAC address of the form AA:BB:CC:DD:EE:FF - return true; - } - - // Not a MAC address - return false; -} - static void subnet_match_impl(sqlite3_context *context, int argc, sqlite3_value **argv) { // Exactly two arguments should be submitted to this routine From 7c53ce8d345543978681f5652b31737aab0913d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Tue, 21 Nov 2023 16:55:17 +0000 Subject: [PATCH 21/27] Log successful processing of pihole.toml on startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Christian König --- src/config/config.c | 8 +++++--- src/config/config.h | 2 +- src/main.c | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/config/config.c b/src/config/config.c index 7d20c57c..d8b23097 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -1367,7 +1367,7 @@ void reset_config(struct conf_item *conf_item) } } -void readFTLconf(struct config *conf, const bool rewrite) +bool readFTLconf(struct config *conf, const bool rewrite) { // Initialize config with default values initConfig(conf); @@ -1384,7 +1384,7 @@ void readFTLconf(struct config *conf, const bool rewrite) write_dnsmasq_config(conf, false, NULL); write_custom_list(); } - return; + return true; } // On error, try to read legacy (pre-v6.0) config file. If successful, @@ -1428,7 +1428,7 @@ void readFTLconf(struct config *conf, const bool rewrite) if(ports == NULL) { log_err("Unable to allocate memory for default ports string"); - return; + return false; } // Create the string snprintf(ports, 32, "%d,%ds", http_port, https_port); @@ -1452,6 +1452,8 @@ void readFTLconf(struct config *conf, const bool rewrite) writeFTLtoml(true); write_dnsmasq_config(conf, false, NULL); write_custom_list(); + + return true; } bool getLogFilePath(void) diff --git a/src/config/config.h b/src/config/config.h index a66d8c3e..cdacdbe3 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -324,7 +324,7 @@ void set_debug_flags(struct config *conf); void set_all_debug(struct config *conf, const bool status); void initConfig(struct config *conf); void reset_config(struct conf_item *conf_item); -void readFTLconf(struct config *conf, const bool rewrite); +bool readFTLconf(struct config *conf, const bool rewrite); bool getLogFilePath(void); struct conf_item *get_conf_item(struct config *conf, const unsigned int n); struct conf_item *get_debug_item(struct config *conf, const enum debug_flag debug); diff --git a/src/main.c b/src/main.c index 22e57fe5..6a989838 100644 --- a/src/main.c +++ b/src/main.c @@ -77,7 +77,8 @@ int main (int argc, char *argv[]) // Process pihole.toml configuration file // The file is rewritten after parsing to ensure that all // settings are present and have a valid value - readFTLconf(&config, true); + if(readFTLconf(&config, true)) + log_info("Parsed config file "GLOBALTOMLPATH" successfully"); // Set process priority set_nice(); From 247a3c4669f7ebbb921f94016f51f5cc80d88c45 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 21 Nov 2023 22:37:10 +0100 Subject: [PATCH 22/27] Apply suggestions from code review Co-authored-by: yubiuser Signed-off-by: DL6ER --- src/database/network-table.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/database/network-table.c b/src/database/network-table.c index 92837b31..c5e26920 100644 --- a/src/database/network-table.c +++ b/src/database/network-table.c @@ -2151,7 +2151,7 @@ char *__attribute__((malloc)) getNameFromMAC(const char *client) if(FTLDBerror()) return NULL; - log_info("Looking up host name for %s", client); + log_debug(DEBUG_DATABASE,"Looking up host name for %s", client); // Open pihole-FTL.db database file sqlite3 *db = NULL; @@ -2161,7 +2161,6 @@ char *__attribute__((malloc)) getNameFromMAC(const char *client) return NULL; } - // Nothing found for the same device // Check for a host name associated with the given client as MAC address // COLLATE NOCASE: Case-insensitive comparison const char *querystr = "SELECT name FROM network_addresses " From c341b73d0f30623c9c34926223523266e984bcf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Wed, 22 Nov 2023 13:50:34 +0100 Subject: [PATCH 23/27] Only log number of stored sessions not total number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Christian König --- src/database/session-table.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/database/session-table.c b/src/database/session-table.c index c84eb097..d8b7d190 100644 --- a/src/database/session-table.c +++ b/src/database/session-table.c @@ -198,8 +198,8 @@ bool backup_db_sessions(struct session *sessions, const uint16_t max_sessions) return false; } - log_info("Stored %u/%u API session%s in the database", - api_sessions, max_sessions, max_sessions == 1 ? "" : "s"); + log_info("Stored %u API session%s in the database", + api_sessions, api_sessions == 1 ? "" : "s"); // Close database connection dbclose(&db); From b6aa531bbb6ab29843c4f6b7d35ebb22924fb436 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 22 Nov 2023 21:25:11 +0100 Subject: [PATCH 24/27] Also monitor when file was moved to overwrite our watched file Signed-off-by: DL6ER --- src/config/inotify.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/config/inotify.c b/src/config/inotify.c index e02d2f04..17767350 100644 --- a/src/config/inotify.c +++ b/src/config/inotify.c @@ -106,18 +106,38 @@ bool check_inotify_event(void) // Check if this is the event we are looking for if(event->mask & IN_CLOSE_WRITE) { + // File opened for writing was closed log_debug(DEBUG_INOTIFY, "File written: "WATCHDIR"/%s", event->name); if(strcmp(event->name, "pihole.toml") == 0) config_changed = true; } else if(event->mask & IN_CREATE) + { + // File was created log_debug(DEBUG_INOTIFY, "File created: "WATCHDIR"/%s", event->name); - else if(event->mask & IN_MOVE) - log_debug(DEBUG_INOTIFY, "File moved: "WATCHDIR"/%s", event->name); + } + else if(event->mask & IN_MOVED_FROM) + { + // File was moved (source) + log_debug(DEBUG_INOTIFY, "File moved from: "WATCHDIR"/%s", event->name); + } + else if(event->mask & IN_MOVED_TO) + { + // File was moved (target) + log_debug(DEBUG_INOTIFY, "File moved to: "WATCHDIR"/%s", event->name); + if(strcmp(event->name, "pihole.toml") == 0) + config_changed = true; + } else if(event->mask & IN_DELETE) + { + // File was deleted log_debug(DEBUG_INOTIFY, "File deleted: "WATCHDIR"/%s", event->name); + } else if(event->mask & IN_IGNORED) + { + // Watch descriptor was removed log_warn("Inotify watch descriptor for "WATCHDIR" was removed (directory deleted or unmounted?)"); + } else log_debug(DEBUG_INOTIFY, "Unknown event (%X) on watched file: "WATCHDIR"/%s", event->mask, event->name); } From 3f5fb989563ecb579a42de0b1a1b63f906a12010 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 22 Nov 2023 21:42:00 +0100 Subject: [PATCH 25/27] Update tests Signed-off-by: DL6ER --- test/test_suite.bats | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_suite.bats b/test/test_suite.bats index 0fa48c67..aca80a55 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -1575,7 +1575,7 @@ [[ ${lines[0]} == "3" ]] run bash -c 'grep -c "DEBUG_CONFIG: pihole.toml unchanged" /var/log/pihole/FTL.log' printf "%s\n" "${lines[@]}" - [[ ${lines[0]} == "3" ]] + [[ ${lines[0]} == "4" ]] run bash -c 'grep -c "DEBUG_CONFIG: Config file written to /etc/pihole/dnsmasq.conf" /var/log/pihole/FTL.log' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "1" ]] @@ -1587,5 +1587,5 @@ [[ ${lines[0]} == "1" ]] run bash -c 'grep -c "DEBUG_CONFIG: custom.list unchanged" /var/log/pihole/FTL.log' printf "%s\n" "${lines[@]}" - [[ ${lines[0]} == "3" ]] + [[ ${lines[0]} == "4" ]] } From 3ea9ce2bbcd97fd3e5d60a2401d5b67c6632721a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 22 Nov 2023 21:47:17 +0100 Subject: [PATCH 26/27] Update embedded SQLite3 engine to version 3.44.1 Signed-off-by: DL6ER --- ...int-FTL-version-in-interactive-shell.patch | 6 +- src/database/shell.c | 3030 ++++++++++------- src/database/sqlite3.c | 255 +- src/database/sqlite3.h | 67 +- 4 files changed, 2093 insertions(+), 1265 deletions(-) diff --git a/patch/sqlite3/0001-print-FTL-version-in-interactive-shell.patch b/patch/sqlite3/0001-print-FTL-version-in-interactive-shell.patch index 5d096e21..1efac968 100644 --- a/patch/sqlite3/0001-print-FTL-version-in-interactive-shell.patch +++ b/patch/sqlite3/0001-print-FTL-version-in-interactive-shell.patch @@ -25,6 +25,6 @@ index 6280ebf6..a5e82f70 100644 char *zHistory; int nHistory; + print_FTL_version(); - #if SHELL_WIN_UTF8_OPT - switch( console_utf8_in+2*console_utf8_out ){ - default: case 0: break; + #if CIO_WIN_WC_XLATE + # define SHELL_CIO_CHAR_SET (stdout_is_console? " (UTF-16 console I/O)" : "") + #else diff --git a/src/database/shell.c b/src/database/shell.c index 249050c2..8b63b854 100644 --- a/src/database/shell.c +++ b/src/database/shell.c @@ -254,29 +254,1019 @@ typedef unsigned char u8; /* string conversion routines only needed on Win32 */ extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR); -extern char *sqlite3_win32_mbcs_to_utf8_v2(const char *, int); -extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int); extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); #endif -/* On Windows, we normally run with output mode of TEXT so that \n characters -** are automatically translated into \r\n. However, this behavior needs -** to be disabled in some cases (ex: when generating CSV output and when -** rendering quoted strings that contain \n characters). The following -** routines take care of that. +/* Use console I/O package as a direct INCLUDE. */ +#define SQLITE_INTERNAL_LINKAGE static + +#ifdef SQLITE_SHELL_FIDDLE +/* Deselect most features from the console I/O package for Fiddle. */ +# define SQLITE_CIO_NO_REDIRECT +# define SQLITE_CIO_NO_CLASSIFY +# define SQLITE_CIO_NO_TRANSLATE +# define SQLITE_CIO_NO_SETMODE +#endif +/************************* Begin ../ext/consio/console_io.h ******************/ +/* +** 2023 November 1 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +******************************************************************************** +** This file exposes various interfaces used for console and other I/O +** by the SQLite project command-line tools. These interfaces are used +** at either source conglomeration time, compilation time, or run time. +** This source provides for either inclusion into conglomerated, +** "single-source" forms or separate compilation then linking. +** +** Platform dependencies are "hidden" here by various stratagems so +** that, provided certain conditions are met, the programs using this +** source or object code compiled from it need no explicit conditional +** compilation in their source for their console and stream I/O. +** +** The symbols and functionality exposed here are not a public API. +** This code may change in tandem with other project code as needed. +** +** When this .h file and its companion .c are directly incorporated into +** a source conglomeration (such as shell.c), the preprocessor symbol +** CIO_WIN_WC_XLATE is defined as 0 or 1, reflecting whether console I/O +** translation for Windows is effected for the build. */ -#if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT -static void setBinaryMode(FILE *file, int isOutput){ - if( isOutput ) fflush(file); - _setmode(_fileno(file), _O_BINARY); -} -static void setTextMode(FILE *file, int isOutput){ - if( isOutput ) fflush(file); - _setmode(_fileno(file), _O_TEXT); -} + +#ifndef SQLITE_INTERNAL_LINKAGE +# define SQLITE_INTERNAL_LINKAGE extern /* external to translation unit */ +# include #else -# define setBinaryMode(X,Y) -# define setTextMode(X,Y) +# define SHELL_NO_SYSINC /* Better yet, modify mkshellc.tcl for this. */ +#endif + +#ifndef SQLITE3_H +/* # include "sqlite3.h" */ +#endif + +#ifndef SQLITE_CIO_NO_CLASSIFY + +/* Define enum for use with following function. */ +typedef enum StreamsAreConsole { + SAC_NoConsole = 0, + SAC_InConsole = 1, SAC_OutConsole = 2, SAC_ErrConsole = 4, + SAC_AnyConsole = 0x7 +} StreamsAreConsole; + +/* +** Classify the three standard I/O streams according to whether +** they are connected to a console attached to the process. +** +** Returns the bit-wise OR of SAC_{In,Out,Err}Console values, +** or SAC_NoConsole if none of the streams reaches a console. +** +** This function should be called before any I/O is done with +** the given streams. As a side-effect, the given inputs are +** recorded so that later I/O operations on them may be done +** differently than the C library FILE* I/O would be done, +** iff the stream is used for the I/O functions that follow, +** and to support the ones that use an implicit stream. +** +** On some platforms, stream or console mode alteration (aka +** "Setup") may be made which is undone by consoleRestore(). +*/ +SQLITE_INTERNAL_LINKAGE StreamsAreConsole +consoleClassifySetup( FILE *pfIn, FILE *pfOut, FILE *pfErr ); +/* A usual call for convenience: */ +#define SQLITE_STD_CONSOLE_INIT() consoleClassifySetup(stdin,stdout,stderr) + +/* +** After an initial call to consoleClassifySetup(...), renew +** the same setup it effected. (A call not after is an error.) +** This will restore state altered by consoleRestore(); +** +** Applications which run an inferior (child) process which +** inherits the same I/O streams may call this function after +** such a process exits to guard against console mode changes. +*/ +SQLITE_INTERNAL_LINKAGE void consoleRenewSetup(void); + +/* +** Undo any side-effects left by consoleClassifySetup(...). +** +** This should be called after consoleClassifySetup() and +** before the process terminates normally. It is suitable +** for use with the atexit() C library procedure. After +** this call, no console I/O should be done until one of +** console{Classify or Renew}Setup(...) is called again. +** +** Applications which run an inferior (child) process that +** inherits the same I/O streams might call this procedure +** before so that said process will have a console setup +** however users have configured it or come to expect. +*/ +SQLITE_INTERNAL_LINKAGE void SQLITE_CDECL consoleRestore( void ); + +#else /* defined(SQLITE_CIO_NO_CLASSIFY) */ +# define consoleClassifySetup(i,o,e) +# define consoleRenewSetup() +# define consoleRestore() +#endif /* defined(SQLITE_CIO_NO_CLASSIFY) */ + +#ifndef SQLITE_CIO_NO_REDIRECT +/* +** Set stream to be used for the functions below which write +** to "the designated X stream", where X is Output or Error. +** Returns the previous value. +** +** Alternatively, pass the special value, invalidFileStream, +** to get the designated stream value without setting it. +** +** Before the designated streams are set, they default to +** those passed to consoleClassifySetup(...), and before +** that is called they default to stdout and stderr. +** +** It is error to close a stream so designated, then, without +** designating another, use the corresponding {o,e}Emit(...). +*/ +SQLITE_INTERNAL_LINKAGE FILE *invalidFileStream; +SQLITE_INTERNAL_LINKAGE FILE *setOutputStream(FILE *pf); +# ifdef CONSIO_SET_ERROR_STREAM +SQLITE_INTERNAL_LINKAGE FILE *setErrorStream(FILE *pf); +# endif +#else +# define setOutputStream(pf) +# define setErrorStream(pf) +#endif /* !defined(SQLITE_CIO_NO_REDIRECT) */ + +#ifndef SQLITE_CIO_NO_TRANSLATE +/* +** Emit output like fprintf(). If the output is going to the +** console and translation from UTF-8 is necessary, perform +** the needed translation. Otherwise, write formatted output +** to the provided stream almost as-is, possibly with newline +** translation as specified by set{Binary,Text}Mode(). +*/ +SQLITE_INTERNAL_LINKAGE int fPrintfUtf8(FILE *pfO, const char *zFormat, ...); +/* Like fPrintfUtf8 except stream is always the designated output. */ +SQLITE_INTERNAL_LINKAGE int oPrintfUtf8(const char *zFormat, ...); +/* Like fPrintfUtf8 except stream is always the designated error. */ +SQLITE_INTERNAL_LINKAGE int ePrintfUtf8(const char *zFormat, ...); + +/* +** Emit output like fputs(). If the output is going to the +** console and translation from UTF-8 is necessary, perform +** the needed translation. Otherwise, write given text to the +** provided stream almost as-is, possibly with newline +** translation as specified by set{Binary,Text}Mode(). +*/ +SQLITE_INTERNAL_LINKAGE int fPutsUtf8(const char *z, FILE *pfO); +/* Like fPutsUtf8 except stream is always the designated output. */ +SQLITE_INTERNAL_LINKAGE int oPutsUtf8(const char *z); +/* Like fPutsUtf8 except stream is always the designated error. */ +SQLITE_INTERNAL_LINKAGE int ePutsUtf8(const char *z); + +/* +** Emit output like fPutsUtf8(), except that the length of the +** accepted char or character sequence is limited by nAccept. +** +** Returns the number of accepted char values. +*/ +#ifdef CONSIO_SPUTB +SQLITE_INTERNAL_LINKAGE int +fPutbUtf8(FILE *pfOut, const char *cBuf, int nAccept); +#endif +/* Like fPutbUtf8 except stream is always the designated output. */ +SQLITE_INTERNAL_LINKAGE int +oPutbUtf8(const char *cBuf, int nAccept); +/* Like fPutbUtf8 except stream is always the designated error. */ +#ifdef CONSIO_EPUTB +SQLITE_INTERNAL_LINKAGE int +ePutbUtf8(const char *cBuf, int nAccept); +#endif + +/* +** Collect input like fgets(...) with special provisions for input +** from the console on platforms that require same. Defers to the +** C library fgets() when input is not from the console. Newline +** translation may be done as set by set{Binary,Text}Mode(). As a +** convenience, pfIn==NULL is treated as stdin. +*/ +SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn); +/* Like fGetsUtf8 except stream is always the designated input. */ +/* SQLITE_INTERNAL_LINKAGE char* iGetsUtf8(char *cBuf, int ncMax); */ + +#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ + +#ifndef SQLITE_CIO_NO_SETMODE +/* +** Set given stream for binary mode, where newline translation is +** not done, or for text mode where, for some platforms, newlines +** are translated to the platform's conventional char sequence. +** If bFlush true, flush the stream. +** +** An additional side-effect is that if the stream is one passed +** to consoleClassifySetup() as an output, it is flushed first. +** +** Note that binary/text mode has no effect on console I/O +** translation. On all platforms, newline to the console starts +** a new line and CR,LF chars from the console become a newline. +*/ +SQLITE_INTERNAL_LINKAGE void setBinaryMode(FILE *, short bFlush); +SQLITE_INTERNAL_LINKAGE void setTextMode(FILE *, short bFlush); +#endif + +#ifdef SQLITE_CIO_PROMPTED_IN +typedef struct Prompts { + int numPrompts; + const char **azPrompts; +} Prompts; + +/* +** Macros for use of a line editor. +** +** The following macros define operations involving use of a +** line-editing library or simple console interaction. +** A "T" argument is a text (char *) buffer or filename. +** A "N" argument is an integer. +** +** SHELL_ADD_HISTORY(T) // Record text as line(s) of history. +** SHELL_READ_HISTORY(T) // Read history from file named by T. +** SHELL_WRITE_HISTORY(T) // Write history to file named by T. +** SHELL_STIFLE_HISTORY(N) // Limit history to N entries. +** +** A console program which does interactive console input is +** expected to call: +** SHELL_READ_HISTORY(T) before collecting such input; +** SHELL_ADD_HISTORY(T) as record-worthy input is taken; +** SHELL_STIFLE_HISTORY(N) after console input ceases; then +** SHELL_WRITE_HISTORY(T) before the program exits. +*/ + +/* +** Retrieve a single line of input text from an input stream. +** +** If pfIn is the input stream passed to consoleClassifySetup(), +** and azPrompt is not NULL, then a prompt is issued before the +** line is collected, as selected by the isContinuation flag. +** Array azPrompt[{0,1}] holds the {main,continuation} prompt. +** +** If zBufPrior is not NULL then it is a buffer from a prior +** call to this routine that can be reused, or will be freed. +** +** The result is stored in space obtained from malloc() and +** must either be freed by the caller or else passed back to +** this function as zBufPrior for reuse. +** +** This function may call upon services of a line-editing +** library to interactively collect line edited input. +*/ +SQLITE_INTERNAL_LINKAGE char * +shellGetLine(FILE *pfIn, char *zBufPrior, int nLen, + short isContinuation, Prompts azPrompt); +#endif /* defined(SQLITE_CIO_PROMPTED_IN) */ +/* +** TBD: Define an interface for application(s) to generate +** completion candidates for use by the line-editor. +** +** This may be premature; the CLI is the only application +** that does this. Yet, getting line-editing melded into +** console I/O is desirable because a line-editing library +** may have to establish console operating mode, possibly +** in a way that interferes with the above functionality. +*/ + +#if !(defined(SQLITE_CIO_NO_UTF8SCAN)&&defined(SQLITE_CIO_NO_TRANSLATE)) +/* Skip over as much z[] input char sequence as is valid UTF-8, +** limited per nAccept char's or whole characters and containing +** no char cn such that ((1<=0 => char count, nAccept<0 => character + */ +SQLITE_INTERNAL_LINKAGE const char* +zSkipValidUtf8(const char *z, int nAccept, long ccm); + +#endif + +/************************* End ../ext/consio/console_io.h ********************/ +/************************* Begin ../ext/consio/console_io.c ******************/ +/* +** 2023 November 4 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +******************************************************************************** +** This file implements various interfaces used for console and stream I/O +** by the SQLite project command-line tools, as explained in console_io.h . +** Functions prefixed by "SQLITE_INTERNAL_LINKAGE" behave as described there. +*/ + +#ifndef SQLITE_CDECL +# define SQLITE_CDECL +#endif + +#ifndef SHELL_NO_SYSINC +# include +# include +# include +# include +# include +# include "console_io.h" +/* # include "sqlite3.h" */ +#endif + +#ifndef SQLITE_CIO_NO_TRANSLATE +# if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT +# ifndef SHELL_NO_SYSINC +# include +# include +# undef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# include +# endif +# define CIO_WIN_WC_XLATE 1 /* Use WCHAR Windows APIs for console I/O */ +# else +# ifndef SHELL_NO_SYSINC +# include +# endif +# define CIO_WIN_WC_XLATE 0 /* Use plain C library stream I/O at console */ +# endif +#else +# define CIO_WIN_WC_XLATE 0 /* Not exposing translation routines at all */ +#endif + +#if CIO_WIN_WC_XLATE +/* Character used to represent a known-incomplete UTF-8 char group (�) */ +static WCHAR cBadGroup = 0xfffd; +#endif + +#if CIO_WIN_WC_XLATE +static HANDLE handleOfFile(FILE *pf){ + int fileDesc = _fileno(pf); + union { intptr_t osfh; HANDLE fh; } fid = { + (fileDesc>=0)? _get_osfhandle(fileDesc) : (intptr_t)INVALID_HANDLE_VALUE + }; + return fid.fh; +} +#endif + +#ifndef SQLITE_CIO_NO_TRANSLATE +typedef struct PerStreamTags { +# if CIO_WIN_WC_XLATE + HANDLE hx; + DWORD consMode; + char acIncomplete[4]; +# else + short reachesConsole; +# endif + FILE *pf; +} PerStreamTags; + +/* Define NULL-like value for things which can validly be 0. */ +# define SHELL_INVALID_FILE_PTR ((FILE *)~0) +# if CIO_WIN_WC_XLATE +# define SHELL_INVALID_CONS_MODE 0xFFFF0000 +# endif + +# if CIO_WIN_WC_XLATE +# define PST_INITIALIZER { INVALID_HANDLE_VALUE, SHELL_INVALID_CONS_MODE, \ + {0,0,0,0}, SHELL_INVALID_FILE_PTR } +# else +# define PST_INITIALIZER { 0, SHELL_INVALID_FILE_PTR } +# endif + +/* Quickly say whether a known output is going to the console. */ +# if CIO_WIN_WC_XLATE +static short pstReachesConsole(PerStreamTags *ppst){ + return (ppst->hx != INVALID_HANDLE_VALUE); +} +# else +# define pstReachesConsole(ppst) 0 +# endif + +# if CIO_WIN_WC_XLATE +static void restoreConsoleArb(PerStreamTags *ppst){ + if( pstReachesConsole(ppst) ) SetConsoleMode(ppst->hx, ppst->consMode); +} +# else +# define restoreConsoleArb(ppst) +# endif + +/* Say whether FILE* appears to be a console, collect associated info. */ +static short streamOfConsole(FILE *pf, /* out */ PerStreamTags *ppst){ +# if CIO_WIN_WC_XLATE + short rv = 0; + DWORD dwCM = SHELL_INVALID_CONS_MODE; + HANDLE fh = handleOfFile(pf); + ppst->pf = pf; + if( INVALID_HANDLE_VALUE != fh ){ + rv = (GetFileType(fh) == FILE_TYPE_CHAR && GetConsoleMode(fh,&dwCM)); + } + ppst->hx = (rv)? fh : INVALID_HANDLE_VALUE; + ppst->consMode = dwCM; + return rv; +# else + ppst->pf = pf; + ppst->reachesConsole = ( (short)isatty(fileno(pf)) ); + return ppst->reachesConsole; +# endif +} + +# if CIO_WIN_WC_XLATE +/* Define console modes for use with the Windows Console API. */ +# define SHELL_CONI_MODE \ + (ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | 0x80 \ + | ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS | ENABLE_PROCESSED_INPUT) +# define SHELL_CONO_MODE (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT \ + | ENABLE_VIRTUAL_TERMINAL_PROCESSING) +# endif + +typedef struct ConsoleInfo { + PerStreamTags pstSetup[3]; + PerStreamTags pstDesignated[3]; + StreamsAreConsole sacSetup; +} ConsoleInfo; + +static short isValidStreamInfo(PerStreamTags *ppst){ + return (ppst->pf != SHELL_INVALID_FILE_PTR); +} + +static ConsoleInfo consoleInfo = { + { /* pstSetup */ PST_INITIALIZER, PST_INITIALIZER, PST_INITIALIZER }, + { /* pstDesignated[] */ PST_INITIALIZER, PST_INITIALIZER, PST_INITIALIZER }, + SAC_NoConsole /* sacSetup */ +}; + +SQLITE_INTERNAL_LINKAGE FILE* invalidFileStream = (FILE *)~0; + +# if CIO_WIN_WC_XLATE +static void maybeSetupAsConsole(PerStreamTags *ppst, short odir){ + if( pstReachesConsole(ppst) ){ + DWORD cm = odir? SHELL_CONO_MODE : SHELL_CONI_MODE; + SetConsoleMode(ppst->hx, cm); + } +} +# else +# define maybeSetupAsConsole(ppst,odir) +# endif + +SQLITE_INTERNAL_LINKAGE void consoleRenewSetup(void){ +# if CIO_WIN_WC_XLATE + int ix = 0; + while( ix < 6 ){ + PerStreamTags *ppst = (ix<3)? + &consoleInfo.pstSetup[ix] : &consoleInfo.pstDesignated[ix-3]; + maybeSetupAsConsole(ppst, (ix % 3)>0); + ++ix; + } +# endif +} + +SQLITE_INTERNAL_LINKAGE StreamsAreConsole +consoleClassifySetup( FILE *pfIn, FILE *pfOut, FILE *pfErr ){ + StreamsAreConsole rv = SAC_NoConsole; + FILE* apf[3] = { pfIn, pfOut, pfErr }; + int ix; + for( ix = 2; ix >= 0; --ix ){ + PerStreamTags *ppst = &consoleInfo.pstSetup[ix]; + if( streamOfConsole(apf[ix], ppst) ){ + rv |= (SAC_InConsole< 0 ) fflush(apf[ix]); + } + consoleInfo.sacSetup = rv; + consoleRenewSetup(); + return rv; +} + +SQLITE_INTERNAL_LINKAGE void SQLITE_CDECL consoleRestore( void ){ +# if CIO_WIN_WC_XLATE + static ConsoleInfo *pci = &consoleInfo; + if( pci->sacSetup ){ + int ix; + for( ix=0; ix<3; ++ix ){ + if( pci->sacSetup & (SAC_InConsole<pstSetup[ix]; + SetConsoleMode(ppst->hx, ppst->consMode); + } + } + } +# endif +} +#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ + +#ifdef SQLITE_CIO_INPUT_REDIR +/* Say whether given FILE* is among those known, via either +** consoleClassifySetup() or set{Output,Error}Stream, as +** readable, and return an associated PerStreamTags pointer +** if so. Otherwise, return 0. +*/ +static PerStreamTags * isKnownReadable(FILE *pf){ + static PerStreamTags *apst[] = { + &consoleInfo.pstDesignated[0], &consoleInfo.pstSetup[0], 0 + }; + int ix = 0; + do { + if( apst[ix]->pf == pf ) break; + } while( apst[++ix] != 0 ); + return apst[ix]; +} +#endif + +#ifndef SQLITE_CIO_NO_TRANSLATE +/* Say whether given FILE* is among those known, via either +** consoleClassifySetup() or set{Output,Error}Stream, as +** writable, and return an associated PerStreamTags pointer +** if so. Otherwise, return 0. +*/ +static PerStreamTags * isKnownWritable(FILE *pf){ + static PerStreamTags *apst[] = { + &consoleInfo.pstDesignated[1], &consoleInfo.pstDesignated[2], + &consoleInfo.pstSetup[1], &consoleInfo.pstSetup[2], 0 + }; + int ix = 0; + do { + if( apst[ix]->pf == pf ) break; + } while( apst[++ix] != 0 ); + return apst[ix]; +} + +static FILE *designateEmitStream(FILE *pf, unsigned chix){ + FILE *rv = consoleInfo.pstDesignated[chix].pf; + if( pf == invalidFileStream ) return rv; + else{ + /* Setting a possibly new output stream. */ + PerStreamTags *ppst = isKnownWritable(pf); + if( ppst != 0 ){ + PerStreamTags pst = *ppst; + consoleInfo.pstDesignated[chix] = pst; + }else streamOfConsole(pf, &consoleInfo.pstDesignated[chix]); + } + return rv; +} + +SQLITE_INTERNAL_LINKAGE FILE *setOutputStream(FILE *pf){ + return designateEmitStream(pf, 1); +} +# ifdef CONSIO_SET_ERROR_STREAM +SQLITE_INTERNAL_LINKAGE FILE *setErrorStream(FILE *pf){ + return designateEmitStream(pf, 2); +} +# endif +#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ + +#ifndef SQLITE_CIO_NO_SETMODE +# if CIO_WIN_WC_XLATE +static void setModeFlushQ(FILE *pf, short bFlush, int mode){ + if( bFlush ) fflush(pf); + _setmode(_fileno(pf), mode); +} +# else +# define setModeFlushQ(f, b, m) if(b) fflush(f) +# endif + +SQLITE_INTERNAL_LINKAGE void setBinaryMode(FILE *pf, short bFlush){ + setModeFlushQ(pf, bFlush, _O_BINARY); +} +SQLITE_INTERNAL_LINKAGE void setTextMode(FILE *pf, short bFlush){ + setModeFlushQ(pf, bFlush, _O_TEXT); +} +# undef setModeFlushQ + +#else /* defined(SQLITE_CIO_NO_SETMODE) */ +# define setBinaryMode(f, bFlush) do{ if((bFlush)) fflush(f); }while(0) +# define setTextMode(f, bFlush) do{ if((bFlush)) fflush(f); }while(0) +#endif /* defined(SQLITE_CIO_NO_SETMODE) */ + +#ifndef SQLITE_CIO_NO_TRANSLATE +# if CIO_WIN_WC_XLATE +/* Write buffer cBuf as output to stream known to reach console, +** limited to ncTake char's. Return ncTake on success, else 0. */ +static int conZstrEmit(PerStreamTags *ppst, const char *z, int ncTake){ + int rv = 0; + if( z!=NULL ){ + int nwc = MultiByteToWideChar(CP_UTF8,0, z,ncTake, 0,0); + if( nwc > 0 ){ + WCHAR *zw = sqlite3_malloc64(nwc*sizeof(WCHAR)); + if( zw!=NULL ){ + nwc = MultiByteToWideChar(CP_UTF8,0, z,ncTake, zw,nwc); + if( nwc > 0 ){ + /* Translation from UTF-8 to UTF-16, then WCHARs out. */ + if( WriteConsoleW(ppst->hx, zw,nwc, 0, NULL) ){ + rv = ncTake; + } + } + sqlite3_free(zw); + } + } + } + return rv; +} + +/* For {f,o,e}PrintfUtf8() when stream is known to reach console. */ +static int conioVmPrintf(PerStreamTags *ppst, const char *zFormat, va_list ap){ + char *z = sqlite3_vmprintf(zFormat, ap); + if( z ){ + int rv = conZstrEmit(ppst, z, (int)strlen(z)); + sqlite3_free(z); + return rv; + }else return 0; +} +# endif /* CIO_WIN_WC_XLATE */ + +# ifdef CONSIO_GET_EMIT_STREAM +static PerStreamTags * getDesignatedEmitStream(FILE *pf, unsigned chix, + PerStreamTags *ppst){ + PerStreamTags *rv = isKnownWritable(pf); + short isValid = (rv!=0)? isValidStreamInfo(rv) : 0; + if( rv != 0 && isValid ) return rv; + streamOfConsole(pf, ppst); + return ppst; +} +# endif + +/* Get stream info, either for designated output or error stream when +** chix equals 1 or 2, or for an arbitrary stream when chix == 0. +** In either case, ppst references a caller-owned PerStreamTags +** struct which may be filled in if none of the known writable +** streams is being held by consoleInfo. The ppf parameter is an +** output when chix!=0 and an input when chix==0. + */ +static PerStreamTags * +getEmitStreamInfo(unsigned chix, PerStreamTags *ppst, + /* in/out */ FILE **ppf){ + PerStreamTags *ppstTry; + FILE *pfEmit; + if( chix > 0 ){ + ppstTry = &consoleInfo.pstDesignated[chix]; + if( !isValidStreamInfo(ppstTry) ){ + ppstTry = &consoleInfo.pstSetup[chix]; + pfEmit = ppst->pf; + }else pfEmit = ppstTry->pf; + if( !isValidStreamInfo(ppst) ){ + pfEmit = (chix > 1)? stderr : stdout; + ppstTry = ppst; + streamOfConsole(pfEmit, ppstTry); + } + *ppf = pfEmit; + }else{ + ppstTry = isKnownWritable(*ppf); + if( ppstTry != 0 ) return ppstTry; + streamOfConsole(*ppf, ppst); + return ppst; + } + return ppstTry; +} + +SQLITE_INTERNAL_LINKAGE int oPrintfUtf8(const char *zFormat, ...){ + va_list ap; + int rv; + FILE *pfOut; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(1, &pst, &pfOut); +# else + getEmitStreamInfo(1, &pst, &pfOut); +# endif + assert(zFormat!=0); + va_start(ap, zFormat); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ){ + rv = conioVmPrintf(ppst, zFormat, ap); + }else{ +# endif + rv = vfprintf(pfOut, zFormat, ap); +# if CIO_WIN_WC_XLATE + } +# endif + va_end(ap); + return rv; +} + +SQLITE_INTERNAL_LINKAGE int ePrintfUtf8(const char *zFormat, ...){ + va_list ap; + int rv; + FILE *pfErr; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(2, &pst, &pfErr); +# else + getEmitStreamInfo(2, &pst, &pfErr); +# endif + assert(zFormat!=0); + va_start(ap, zFormat); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ){ + rv = conioVmPrintf(ppst, zFormat, ap); + }else{ +# endif + rv = vfprintf(pfErr, zFormat, ap); +# if CIO_WIN_WC_XLATE + } +# endif + va_end(ap); + return rv; +} + +SQLITE_INTERNAL_LINKAGE int fPrintfUtf8(FILE *pfO, const char *zFormat, ...){ + va_list ap; + int rv; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); +# else + getEmitStreamInfo(0, &pst, &pfO); +# endif + assert(zFormat!=0); + va_start(ap, zFormat); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ){ + maybeSetupAsConsole(ppst, 1); + rv = conioVmPrintf(ppst, zFormat, ap); + if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); + }else{ +# endif + rv = vfprintf(pfO, zFormat, ap); +# if CIO_WIN_WC_XLATE + } +# endif + va_end(ap); + return rv; +} + +SQLITE_INTERNAL_LINKAGE int fPutsUtf8(const char *z, FILE *pfO){ + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); +# else + getEmitStreamInfo(0, &pst, &pfO); +# endif + assert(z!=0); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ){ + int rv; + maybeSetupAsConsole(ppst, 1); + rv = conZstrEmit(ppst, z, (int)strlen(z)); + if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); + return rv; + }else { +# endif + return (fputs(z, pfO)<0)? 0 : (int)strlen(z); +# if CIO_WIN_WC_XLATE + } +# endif +} + +SQLITE_INTERNAL_LINKAGE int ePutsUtf8(const char *z){ + FILE *pfErr; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(2, &pst, &pfErr); +# else + getEmitStreamInfo(2, &pst, &pfErr); +# endif + assert(z!=0); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ) return conZstrEmit(ppst, z, (int)strlen(z)); + else { +# endif + return (fputs(z, pfErr)<0)? 0 : (int)strlen(z); +# if CIO_WIN_WC_XLATE + } +# endif +} + +SQLITE_INTERNAL_LINKAGE int oPutsUtf8(const char *z){ + FILE *pfOut; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(1, &pst, &pfOut); +# else + getEmitStreamInfo(1, &pst, &pfOut); +# endif + assert(z!=0); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ) return conZstrEmit(ppst, z, (int)strlen(z)); + else { +# endif + return (fputs(z, pfOut)<0)? 0 : (int)strlen(z); +# if CIO_WIN_WC_XLATE + } +# endif +} + +#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ + +#if !(defined(SQLITE_CIO_NO_UTF8SCAN) && defined(SQLITE_CIO_NO_TRANSLATE)) +/* Skip over as much z[] input char sequence as is valid UTF-8, +** limited per nAccept char's or whole characters and containing +** no char cn such that ((1<=0 => char count, nAccept<0 => character + */ +SQLITE_INTERNAL_LINKAGE const char* +zSkipValidUtf8(const char *z, int nAccept, long ccm){ + int ng = (nAccept<0)? -nAccept : 0; + const char *pcLimit = (nAccept>=0)? z+nAccept : 0; + assert(z!=0); + while( (pcLimit)? (z= pcLimit ) return z; + else{ + char ct = *zt++; + if( ct==0 || (zt-z)>4 || (ct & 0xC0)!=0x80 ){ + /* Trailing bytes are too few, too many, or invalid. */ + return z; + } + } + } while( ((c <<= 1) & 0x40) == 0x40 ); /* Eat lead byte's count. */ + z = zt; + } + } + return z; +} +#endif /*!(defined(SQLITE_CIO_NO_UTF8SCAN)&&defined(SQLITE_CIO_NO_TRANSLATE))*/ + +#ifndef SQLITE_CIO_NO_TRANSLATE + +#ifdef CONSIO_SPUTB +SQLITE_INTERNAL_LINKAGE int +fPutbUtf8(FILE *pfO, const char *cBuf, int nAccept){ + assert(pfO!=0); +# if CIO_WIN_WC_XLATE + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ + PerStreamTags *ppst = getEmitStreamInfo(0, &pst, &pfO); + if( pstReachesConsole(ppst) ){ + int rv; + maybeSetupAsConsole(ppst, 1); + rv = conZstrEmit(ppst, cBuf, nAccept); + if( 0 == isKnownWritable(ppst->pf) ) restoreConsoleArb(ppst); + return rv; + }else { +# endif + return (int)fwrite(cBuf, 1, nAccept, pfO); +# if CIO_WIN_WC_XLATE + } +# endif +} +#endif /* defined(CONSIO_SPUTB) */ + +SQLITE_INTERNAL_LINKAGE int +oPutbUtf8(const char *cBuf, int nAccept){ + FILE *pfOut; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ +# if CIO_WIN_WC_XLATE + PerStreamTags *ppst = getEmitStreamInfo(1, &pst, &pfOut); +# else + getEmitStreamInfo(1, &pst, &pfOut); +# endif +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ){ + return conZstrEmit(ppst, cBuf, nAccept); + }else { +# endif + return (int)fwrite(cBuf, 1, nAccept, pfOut); +# if CIO_WIN_WC_XLATE + } +# endif +} + +# ifdef CONSIO_EPUTB +SQLITE_INTERNAL_LINKAGE int +ePutbUtf8(const char *cBuf, int nAccept){ + FILE *pfErr; + PerStreamTags pst = PST_INITIALIZER; /* for unknown streams */ + PerStreamTags *ppst = getEmitStreamInfo(2, &pst, &pfErr); +# if CIO_WIN_WC_XLATE + if( pstReachesConsole(ppst) ){ + return conZstrEmit(ppst, cBuf, nAccept); + }else { +# endif + return (int)fwrite(cBuf, 1, nAccept, pfErr); +# if CIO_WIN_WC_XLATE + } +# endif +} +# endif /* defined(CONSIO_EPUTB) */ + +SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn){ + if( pfIn==0 ) pfIn = stdin; +# if CIO_WIN_WC_XLATE + if( pfIn == consoleInfo.pstSetup[0].pf + && (consoleInfo.sacSetup & SAC_InConsole)!=0 ){ +# if CIO_WIN_WC_XLATE==1 +# define SHELL_GULP 150 /* Count of WCHARS to be gulped at a time */ + WCHAR wcBuf[SHELL_GULP+1]; + int lend = 0, noc = 0; + if( ncMax > 0 ) cBuf[0] = 0; + while( noc < ncMax-8-1 && !lend ){ + /* There is room for at least 2 more characters and a 0-terminator. */ + int na = (ncMax > SHELL_GULP*4+1 + noc)? SHELL_GULP : (ncMax-1 - noc)/4; +# undef SHELL_GULP + DWORD nbr = 0; + BOOL bRC = ReadConsoleW(consoleInfo.pstSetup[0].hx, wcBuf, na, &nbr, 0); + if( bRC && nbr>0 && (wcBuf[nbr-1]&0xF800)==0xD800 ){ + /* Last WHAR read is first of a UTF-16 surrogate pair. Grab its mate. */ + DWORD nbrx; + bRC &= ReadConsoleW(consoleInfo.pstSetup[0].hx, wcBuf+nbr, 1, &nbrx, 0); + if( bRC ) nbr += nbrx; + } + if( !bRC || (noc==0 && nbr==0) ) return 0; + if( nbr > 0 ){ + int nmb = WideCharToMultiByte(CP_UTF8, 0, wcBuf,nbr,0,0,0,0); + if( nmb != 0 && noc+nmb <= ncMax ){ + int iseg = noc; + nmb = WideCharToMultiByte(CP_UTF8, 0, wcBuf,nbr,cBuf+noc,nmb,0,0); + noc += nmb; + /* Fixup line-ends as coded by Windows for CR (or "Enter".) + ** This is done without regard for any setMode{Text,Binary}() + ** call that might have been done on the interactive input. + */ + if( noc > 0 ){ + if( cBuf[noc-1]=='\n' ){ + lend = 1; + if( noc > 1 && cBuf[noc-2]=='\r' ) cBuf[--noc-1] = '\n'; + } + } + /* Check for ^Z (anywhere in line) too, to act as EOF. */ + while( iseg < noc ){ + if( cBuf[iseg]=='\x1a' ){ + noc = iseg; /* Chop ^Z and anything following. */ + lend = 1; /* Counts as end of line too. */ + break; + } + ++iseg; + } + }else break; /* Drop apparent garbage in. (Could assert.) */ + }else break; + } + /* If got nothing, (after ^Z chop), must be at end-of-file. */ + if( noc > 0 ){ + cBuf[noc] = 0; + return cBuf; + }else return 0; +# endif + }else{ +# endif + return fgets(cBuf, ncMax, pfIn); +# if CIO_WIN_WC_XLATE + } +# endif +} +#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */ + +#undef SHELL_INVALID_FILE_PTR + +/************************* End ../ext/consio/console_io.c ********************/ + +#ifndef SQLITE_SHELL_FIDDLE +/* From here onward, fgets() is redirected to the console_io library. */ +# define fgets(b,n,f) fGetsUtf8(b,n,f) +/* + * Define macros for emitting output text in various ways: + * sputz(s, z) => emit 0-terminated string z to given stream s + * sputf(s, f, ...) => emit varargs per format f to given stream s + * oputz(z) => emit 0-terminated string z to default stream + * oputf(f, ...) => emit varargs per format f to default stream + * eputz(z) => emit 0-terminated string z to error stream + * eputf(f, ...) => emit varargs per format f to error stream + * oputb(b, n) => emit char buffer b[0..n-1] to default stream + * + * Note that the default stream is whatever has been last set via: + * setOutputStream(FILE *pf) + * This is normally the stream that CLI normal output goes to. + * For the stand-alone CLI, it is stdout with no .output redirect. + */ +# define sputz(s,z) fPutsUtf8(z,s) +# define sputf fPrintfUtf8 +# define oputz(z) oPutsUtf8(z) +# define oputf oPrintfUtf8 +# define eputz(z) ePutsUtf8(z) +# define eputf ePrintfUtf8 +# define oputb(buf,na) oPutbUtf8(buf,na) +#else +/* For Fiddle, all console handling and emit redirection is omitted. */ +# define sputz(fp,z) fputs(z,fp) +# define sputf(fp,fmt, ...) fprintf(fp,fmt,__VA_ARGS__) +# define oputz(z) fputs(z,stdout) +# define oputf(fmt, ...) printf(fmt,__VA_ARGS__) +# define eputz(z) fputs(z,stderr) +# define eputf(fmt, ...) fprintf(stderr,fmt,__VA_ARGS__) +# define oputb(buf,na) fwrite(buf,1,na,stdout) #endif /* True if the timer is enabled */ @@ -351,10 +1341,10 @@ static void endTimer(void){ sqlite3_int64 iEnd = timeOfDay(); struct rusage sEnd; getrusage(RUSAGE_SELF, &sEnd); - printf("Run Time: real %.3f user %f sys %f\n", - (iEnd - iBegin)*0.001, - timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), - timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); + oputf("Run Time: real %.3f user %f sys %f\n", + (iEnd - iBegin)*0.001, + timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), + timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); } } @@ -430,10 +1420,10 @@ static void endTimer(void){ FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; sqlite3_int64 ftWallEnd = timeOfDay(); getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); - printf("Run Time: real %.3f user %f sys %f\n", - (ftWallEnd - ftWallBegin)*0.001, - timeDiff(&ftUserBegin, &ftUserEnd), - timeDiff(&ftKernelBegin, &ftKernelEnd)); + oputf("Run Time: real %.3f user %f sys %f\n", + (ftWallEnd - ftWallBegin)*0.001, + timeDiff(&ftUserBegin, &ftUserEnd), + timeDiff(&ftKernelBegin, &ftKernelEnd)); } } @@ -470,28 +1460,9 @@ static int bail_on_error = 0; static int stdin_is_interactive = 1; /* -** If build is for non-RT Windows, without 3rd-party line editing, -** console input and output may be done in a UTF-8 compatible way, -** if the OS is capable of it and the --no-utf8 option is not seen. -*/ -#if (defined(_WIN32) || defined(WIN32)) && SHELL_USE_LOCAL_GETLINE \ - && !defined(SHELL_OMIT_WIN_UTF8) && !SQLITE_OS_WINRT -# define SHELL_WIN_UTF8_OPT 1 -/* Record whether to do UTF-8 console I/O translation per stream. */ - static int console_utf8_in = 0; - static int console_utf8_out = 0; -/* Record whether can do UTF-8 or --no-utf8 seen in invocation. */ - static int mbcs_opted = 1; /* Assume cannot do until shown otherwise. */ -#else -# define console_utf8_in 0 -# define console_utf8_out 0 -# define SHELL_WIN_UTF8_OPT 0 -#endif - -/* -** On Windows systems we have to know if standard output is a console -** in order to translate UTF-8 into MBCS. The following variable is -** true if translation is required. +** On Windows systems we need to know if standard output is a console +** in order to show that UTF-16 translation is done in the sign-on +** banner. The following variable is true if it is the console. */ static int stdout_is_console = 1; @@ -613,251 +1584,17 @@ static char *dynamicContinuePrompt(void){ shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4); dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel); } - shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, PROMPT_LEN_MAX-4); + shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, + PROMPT_LEN_MAX-4); } } return dynPrompt.dynamicPrompt; } #endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */ -#if SHELL_WIN_UTF8_OPT -/* Following struct is used for UTF-8 console I/O. */ -static struct ConsoleState { - int stdinEof; /* EOF has been seen on console input */ - int infsMode; /* Input file stream mode upon shell start */ - UINT inCodePage; /* Input code page upon shell start */ - UINT outCodePage; /* Output code page upon shell start */ - HANDLE hConsole; /* Console input or output handle */ - DWORD consoleMode; /* Console mode upon shell start */ -} conState = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 }; - -#ifndef _O_U16TEXT /* For build environments lacking this constant: */ -# define _O_U16TEXT 0x20000 -#endif - -/* -** If given stream number is a console, return 1 and get some attributes, -** else return 0 and set the output attributes to invalid values. -*/ -static short console_attrs(unsigned stnum, HANDLE *pH, DWORD *pConsMode){ - static int stid[3] = { STD_INPUT_HANDLE,STD_OUTPUT_HANDLE,STD_ERROR_HANDLE }; - HANDLE h; - *pH = INVALID_HANDLE_VALUE; - *pConsMode = 0; - if( stnum > 2 ) return 0; - h = GetStdHandle(stid[stnum]); - if( h!=*pH && GetFileType(h)==FILE_TYPE_CHAR && GetConsoleMode(h,pConsMode) ){ - *pH = h; - return 1; - } - return 0; -} - -/* -** Perform a runtime test of Windows console to determine if it can -** do char-stream I/O correctly when the code page is set to CP_UTF8. -** Returns are: 1 => yes it can, 0 => no it cannot -** -** The console's output code page is momentarily set, then restored. -** So this should only be run when the process is given use of the -** console for either input or output. -*/ -static short ConsoleDoesUTF8(void){ - UINT ocp = GetConsoleOutputCP(); - const char TrialUtf8[] = { '\xC8', '\xAB' }; /* "ȫ" or 2 MBCS characters */ - WCHAR aReadBack[1] = { 0 }; /* Read back as 0x022B when decoded as UTF-8. */ - CONSOLE_SCREEN_BUFFER_INFO csbInfo = {0}; - /* Create an inactive screen buffer with which to do the experiment. */ - HANDLE hCSB = CreateConsoleScreenBuffer(GENERIC_READ|GENERIC_WRITE, 0, 0, - CONSOLE_TEXTMODE_BUFFER, NULL); - if( hCSB!=INVALID_HANDLE_VALUE ){ - COORD cpos = {0,0}; - DWORD rbc; - SetConsoleCursorPosition(hCSB, cpos); - SetConsoleOutputCP(CP_UTF8); - /* Write 2 chars which are a single character in UTF-8 but more in MBCS. */ - WriteConsoleA(hCSB, TrialUtf8, sizeof(TrialUtf8), NULL, NULL); - ReadConsoleOutputCharacterW(hCSB, &aReadBack[0], 1, cpos, &rbc); - GetConsoleScreenBufferInfo(hCSB, &csbInfo); - SetConsoleOutputCP(ocp); - CloseHandle(hCSB); - } - /* Return 1 if cursor advanced by 1 position, else 0. */ - return (short)(csbInfo.dwCursorPosition.X == 1 && aReadBack[0] == 0x022B); -} - -static short in_console = 0; -static short out_console = 0; - -/* -** Determine whether either normal I/O stream is the console, -** and whether it can do UTF-8 translation, setting globals -** in_console, out_console and mbcs_opted accordingly. -*/ -static void probe_console(void){ - HANDLE h; - DWORD cMode; - in_console = console_attrs(0, &h, &cMode); - out_console = console_attrs(1, &h, &cMode); - if( in_console || out_console ) mbcs_opted = !ConsoleDoesUTF8(); -} - -/* -** If console is used for normal I/O, absent a --no-utf8 option, -** prepare console for UTF-8 input (from either typing or suitable -** paste operations) and/or for UTF-8 output rendering. -** -** The console state upon entry is preserved, in conState, so that -** console_restore() can later restore the same console state. -** -** The globals console_utf8_in and console_utf8_out are set, for -** later use in selecting UTF-8 or MBCS console I/O translations. -** This routine depends upon globals set by probe_console(). -*/ -static void console_prepare_utf8(void){ - struct ConsoleState csWork = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 }; - - console_utf8_in = console_utf8_out = 0; - if( (!in_console && !out_console) || mbcs_opted ) return; - console_attrs((in_console)? 0 : 1, &conState.hConsole, &conState.consoleMode); - conState.inCodePage = GetConsoleCP(); - conState.outCodePage = GetConsoleOutputCP(); - if( in_console ){ - SetConsoleCP(CP_UTF8); - DWORD newConsoleMode = conState.consoleMode - | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; - SetConsoleMode(conState.hConsole, newConsoleMode); - conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT); - console_utf8_in = 1; - } - if( out_console ){ - SetConsoleOutputCP(CP_UTF8); - console_utf8_out = 1; - } -} - -/* -** Undo the effects of console_prepare_utf8(), if any. -*/ -static void SQLITE_CDECL console_restore(void){ - if( (console_utf8_in||console_utf8_out) - && conState.hConsole!=INVALID_HANDLE_VALUE ){ - if( console_utf8_in ){ - SetConsoleCP(conState.inCodePage); - _setmode(_fileno(stdin), conState.infsMode); - } - if( console_utf8_out ) SetConsoleOutputCP(conState.outCodePage); - SetConsoleMode(conState.hConsole, conState.consoleMode); - /* Avoid multiple calls. */ - conState.hConsole = INVALID_HANDLE_VALUE; - conState.consoleMode = 0; - console_utf8_in = 0; - console_utf8_out = 0; - } -} - -/* -** Collect input like fgets(...) with special provisions for input -** from the Windows console to get around its strange coding issues. -** Defers to plain fgets() when input is not interactive or when the -** UTF-8 input is unavailable or opted out. -*/ -static char* utf8_fgets(char *buf, int ncmax, FILE *fin){ - if( fin==0 ) fin = stdin; - if( fin==stdin && stdin_is_interactive && console_utf8_in ){ -# define SQLITE_IALIM 150 - wchar_t wbuf[SQLITE_IALIM]; - int lend = 0; - int noc = 0; - if( ncmax==0 || conState.stdinEof ) return 0; - buf[0] = 0; - while( noc SQLITE_IALIM*4+1 + noc) - ? SQLITE_IALIM : (ncmax-1 - noc)/4; -# undef SQLITE_IALIM - DWORD nbr = 0; - BOOL bRC = ReadConsoleW(conState.hConsole, wbuf, na, &nbr, 0); - if( !bRC || (noc==0 && nbr==0) ) return 0; - if( nbr > 0 ){ - int nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR, - wbuf,nbr,0,0,0,0); - if( nmb !=0 && noc+nmb <= ncmax ){ - int iseg = noc; - nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR, - wbuf,nbr,buf+noc,nmb,0,0); - noc += nmb; - /* Fixup line-ends as coded by Windows for CR (or "Enter".)*/ - if( noc > 0 ){ - if( buf[noc-1]=='\n' ){ - lend = 1; - if( noc > 1 && buf[noc-2]=='\r' ){ - buf[noc-2] = '\n'; - --noc; - } - } - } - /* Check for ^Z (anywhere in line) too. */ - while( iseg < noc ){ - if( buf[iseg]==0x1a ){ - conState.stdinEof = 1; - noc = iseg; /* Chop ^Z and anything following. */ - break; - } - ++iseg; - } - }else break; /* Drop apparent garbage in. (Could assert.) */ - }else break; - } - /* If got nothing, (after ^Z chop), must be at end-of-file. */ - if( noc == 0 ) return 0; - buf[noc] = 0; - return buf; - }else{ - return fgets(buf, ncmax, fin); - } -} - -# define fgets(b,n,f) utf8_fgets(b,n,f) -#endif /* SHELL_WIN_UTF8_OPT */ - -/* -** Render output like fprintf(). Except, if the output is going to the -** console and if this is running on a Windows machine, and if UTF-8 -** output unavailable (or available but opted out), translate the -** output from UTF-8 into MBCS for output through 8-bit stdout stream. -** (Without -no-utf8, no translation is needed and must not be done.) -*/ -#if defined(_WIN32) || defined(WIN32) -void utf8_printf(FILE *out, const char *zFormat, ...){ - va_list ap; - va_start(ap, zFormat); - if( stdout_is_console && (out==stdout || out==stderr) && !console_utf8_out ){ - char *z1 = sqlite3_vmprintf(zFormat, ap); - char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0); - sqlite3_free(z1); - fputs(z2, out); - sqlite3_free(z2); - }else{ - vfprintf(out, zFormat, ap); - } - va_end(ap); -} -#elif !defined(utf8_printf) -# define utf8_printf fprintf -#endif - -/* -** Render output like fprintf(). This should not be used on anything that -** includes string formatting (e.g. "%s"). -*/ -#if !defined(raw_printf) -# define raw_printf fprintf -#endif - /* Indicate out-of-memory and exit. */ static void shell_out_of_memory(void){ - raw_printf(stderr,"Error: out of memory\n"); + eputz("Error: out of memory\n"); exit(1); } @@ -889,18 +1626,18 @@ static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){ va_start(ap, zFormat); z = sqlite3_vmprintf(zFormat, ap); va_end(ap); - utf8_printf(iotrace, "%s", z); + sputf(iotrace, "%s", z); sqlite3_free(z); } #endif /* -** Output string zUtf to stream pOut as w characters. If w is negative, +** Output string zUtf to Out stream as w characters. If w is negative, ** then right-justify the text. W is the width in UTF-8 characters, not ** in bytes. This is different from the %*.*s specification in printf ** since with %*.*s the width is measured in bytes, not characters. */ -static void utf8_width_print(FILE *pOut, int w, const char *zUtf){ +static void utf8_width_print(int w, const char *zUtf){ int i; int n; int aw = w<0 ? -w : w; @@ -915,11 +1652,11 @@ static void utf8_width_print(FILE *pOut, int w, const char *zUtf){ } } if( n>=aw ){ - utf8_printf(pOut, "%.*s", i, zUtf); + oputf("%.*s", i, zUtf); }else if( w<0 ){ - utf8_printf(pOut, "%*s%s", aw-n, "", zUtf); + oputf("%*s%s", aw-n, "", zUtf); }else{ - utf8_printf(pOut, "%s%*s", zUtf, aw-n, ""); + oputf("%s%*s", zUtf, aw-n, ""); } } @@ -979,7 +1716,7 @@ static int strlenChar(const char *z){ ** Otherwise return 0. */ static FILE * openChrSource(const char *zFile){ -#ifdef _WIN32 +#if defined(_WIN32) || defined(WIN32) struct _stat x = {0}; # define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0) /* On Windows, open first, then check the stream nature. This order @@ -1042,23 +1779,6 @@ static char *local_getline(char *zLine, FILE *in){ break; } } -#if defined(_WIN32) || defined(WIN32) - /* For interactive input on Windows systems, with -no-utf8, - ** translate the multi-byte characterset characters into UTF-8. - ** This is the translation that predates console UTF-8 input. */ - if( stdin_is_interactive && in==stdin && !console_utf8_in ){ - char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); - if( zTrans ){ - i64 nTrans = strlen(zTrans)+1; - if( nTrans>nLine ){ - zLine = realloc(zLine, nTrans); - shell_check_oom(zLine); - } - memcpy(zLine, zTrans, nTrans); - sqlite3_free(zTrans); - } - } -#endif /* defined(_WIN32) || defined(WIN32) */ return zLine; } @@ -1085,7 +1805,7 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ }else{ zPrompt = isContinuation ? CONTINUATION_PROMPT : mainPrompt; #if SHELL_USE_LOCAL_GETLINE - printf("%s", zPrompt); + sputz(stdout, zPrompt); fflush(stdout); do{ zResult = local_getline(zPrior, stdin); @@ -1332,7 +2052,7 @@ static void shellDtostr( char z[400]; if( n<1 ) n = 1; if( n>350 ) n = 350; - snprintf(z, sizeof(z)-1, "%#+.*e", n, r); + sqlite3_snprintf(sizeof(z), z, "%#+.*e", n, r); sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); } @@ -17618,7 +18338,7 @@ static const char *modeDescr[] = { static void shellLog(void *pArg, int iErrCode, const char *zMsg){ ShellState *p = (ShellState*)pArg; if( p->pLog==0 ) return; - utf8_printf(p->pLog, "(%d) %s\n", iErrCode, zMsg); + sputf(p->pLog, "(%d) %s\n", iErrCode, zMsg); fflush(p->pLog); } @@ -17633,9 +18353,9 @@ static void shellPutsFunc( int nVal, sqlite3_value **apVal ){ - ShellState *p = (ShellState*)sqlite3_user_data(pCtx); + /* Unused: (ShellState*)sqlite3_user_data(pCtx); */ (void)nVal; - utf8_printf(p->out, "%s\n", sqlite3_value_text(apVal[0])); + oputf("%s\n", sqlite3_value_text(apVal[0])); sqlite3_result_value(pCtx, apVal[0]); } @@ -17654,8 +18374,7 @@ static void failIfSafeMode( va_start(ap, zErrMsg); zMsg = sqlite3_vmprintf(zErrMsg, ap); va_end(ap); - raw_printf(stderr, "line %d: ", p->lineno); - utf8_printf(stderr, "%s\n", zMsg); + eputf("line %d: %s\n", p->lineno, zMsg); exit(1); } } @@ -17823,7 +18542,7 @@ static void outputModePop(ShellState *p){ /* ** Output the given string as a hex-encoded blob (eg. X'1234' ) */ -static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ +static void output_hex_blob(const void *pBlob, int nBlob){ int i; unsigned char *aBlob = (unsigned char*)pBlob; @@ -17840,7 +18559,7 @@ static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){ } zStr[i*2] = '\0'; - raw_printf(out,"X'%s'", zStr); + oputf("X'%s'", zStr); sqlite3_free(zStr); } @@ -17870,25 +18589,28 @@ static const char *unused_string( ** ** See also: output_quoted_escaped_string() */ -static void output_quoted_string(FILE *out, const char *z){ +static void output_quoted_string(const char *z){ int i; char c; - setBinaryMode(out, 1); +#ifndef SQLITE_SHELL_FIDDLE + FILE *pfO = setOutputStream(invalidFileStream); + setBinaryMode(pfO, 1); +#endif if( z==0 ) return; for(i=0; (c = z[i])!=0 && c!='\''; i++){} if( c==0 ){ - utf8_printf(out,"'%s'",z); + oputf("'%s'",z); }else{ - raw_printf(out, "'"); + oputz("'"); while( *z ){ for(i=0; (c = z[i])!=0 && c!='\''; i++){} if( c=='\'' ) i++; if( i ){ - utf8_printf(out, "%.*s", i, z); + oputf("%.*s", i, z); z += i; } if( c=='\'' ){ - raw_printf(out, "'"); + oputz("'"); continue; } if( c==0 ){ @@ -17896,9 +18618,13 @@ static void output_quoted_string(FILE *out, const char *z){ } z++; } - raw_printf(out, "'"); + oputz("'"); } - setTextMode(out, 1); +#ifndef SQLITE_SHELL_FIDDLE + setTextMode(pfO, 1); +#else + setTextMode(stdout, 1); +#endif } /* @@ -17910,13 +18636,16 @@ static void output_quoted_string(FILE *out, const char *z){ ** This is like output_quoted_string() but with the addition of the \r\n ** escape mechanism. */ -static void output_quoted_escaped_string(FILE *out, const char *z){ +static void output_quoted_escaped_string(const char *z){ int i; char c; - setBinaryMode(out, 1); +#ifndef SQLITE_SHELL_FIDDLE + FILE *pfO = setOutputStream(invalidFileStream); + setBinaryMode(pfO, 1); +#endif for(i=0; (c = z[i])!=0 && c!='\'' && c!='\n' && c!='\r'; i++){} if( c==0 ){ - utf8_printf(out,"'%s'",z); + oputf("'%s'",z); }else{ const char *zNL = 0; const char *zCR = 0; @@ -17928,23 +18657,23 @@ static void output_quoted_escaped_string(FILE *out, const char *z){ if( z[i]=='\r' ) nCR++; } if( nNL ){ - raw_printf(out, "replace("); + oputz("replace("); zNL = unused_string(z, "\\n", "\\012", zBuf1); } if( nCR ){ - raw_printf(out, "replace("); + oputz("replace("); zCR = unused_string(z, "\\r", "\\015", zBuf2); } - raw_printf(out, "'"); + oputz("'"); while( *z ){ for(i=0; (c = z[i])!=0 && c!='\n' && c!='\r' && c!='\''; i++){} if( c=='\'' ) i++; if( i ){ - utf8_printf(out, "%.*s", i, z); + oputf("%.*s", i, z); z += i; } if( c=='\'' ){ - raw_printf(out, "'"); + oputz("'"); continue; } if( c==0 ){ @@ -17952,93 +18681,139 @@ static void output_quoted_escaped_string(FILE *out, const char *z){ } z++; if( c=='\n' ){ - raw_printf(out, "%s", zNL); + oputz(zNL); continue; } - raw_printf(out, "%s", zCR); + oputz(zCR); } - raw_printf(out, "'"); + oputz("'"); if( nCR ){ - raw_printf(out, ",'%s',char(13))", zCR); + oputf(",'%s',char(13))", zCR); } if( nNL ){ - raw_printf(out, ",'%s',char(10))", zNL); + oputf(",'%s',char(10))", zNL); } } - setTextMode(out, 1); +#ifndef SQLITE_SHELL_FIDDLE + setTextMode(pfO, 1); +#else + setTextMode(stdout, 1); +#endif } +/* +** Find earliest of chars within s specified in zAny. +** With ns == ~0, is like strpbrk(s,zAny) and s must be 0-terminated. +*/ +static const char *anyOfInStr(const char *s, const char *zAny, size_t ns){ + const char *pcFirst = 0; + if( ns == ~(size_t)0 ) ns = strlen(s); + while(*zAny){ + const char *pc = (const char*)memchr(s, *zAny&0xff, ns); + if( pc ){ + pcFirst = pc; + ns = pcFirst - s; + } + ++zAny; + } + return pcFirst; +} /* ** Output the given string as a quoted according to C or TCL quoting rules. */ -static void output_c_string(FILE *out, const char *z){ - unsigned int c; - fputc('"', out); - while( (c = *(z++))!=0 ){ - if( c=='\\' ){ - fputc(c, out); - fputc(c, out); - }else if( c=='"' ){ - fputc('\\', out); - fputc('"', out); - }else if( c=='\t' ){ - fputc('\\', out); - fputc('t', out); - }else if( c=='\n' ){ - fputc('\\', out); - fputc('n', out); - }else if( c=='\r' ){ - fputc('\\', out); - fputc('r', out); - }else if( !isprint(c&0xff) ){ - raw_printf(out, "\\%03o", c&0xff); - }else{ - fputc(c, out); +static void output_c_string(const char *z){ + char c; + static const char *zq = "\""; + static long ctrlMask = ~0L; + static const char *zDQBSRO = "\"\\\x7f"; /* double-quote, backslash, rubout */ + char ace[3] = "\\?"; + char cbsSay; + oputz(zq); + while( *z!=0 ){ + const char *pcDQBSRO = anyOfInStr(z, zDQBSRO, ~(size_t)0); + const char *pcPast = zSkipValidUtf8(z, INT_MAX, ctrlMask); + const char *pcEnd = (pcDQBSRO && pcDQBSRO < pcPast)? pcDQBSRO : pcPast; + if( pcEnd > z ) oputb(z, (int)(pcEnd-z)); + if( (c = *pcEnd)==0 ) break; + ++pcEnd; + switch( c ){ + case '\\': case '"': + cbsSay = (char)c; + break; + case '\t': cbsSay = 't'; break; + case '\n': cbsSay = 'n'; break; + case '\r': cbsSay = 'r'; break; + case '\f': cbsSay = 'f'; break; + default: cbsSay = 0; break; } + if( cbsSay ){ + ace[1] = cbsSay; + oputz(ace); + }else if( !isprint(c&0xff) ){ + oputf("\\%03o", c&0xff); + }else{ + ace[1] = (char)c; + oputz(ace+1); + } + z = pcEnd; } - fputc('"', out); + oputz(zq); } /* ** Output the given string as a quoted according to JSON quoting rules. */ -static void output_json_string(FILE *out, const char *z, i64 n){ - unsigned int c; +static void output_json_string(const char *z, i64 n){ + char c; + static const char *zq = "\""; + static long ctrlMask = ~0L; + static const char *zDQBS = "\"\\"; + const char *pcLimit; + char ace[3] = "\\?"; + char cbsSay; + if( z==0 ) z = ""; - if( n<0 ) n = strlen(z); - fputc('"', out); - while( n-- ){ + pcLimit = z + ((n<0)? strlen(z) : (size_t)n); + oputz(zq); + while( z < pcLimit ){ + const char *pcDQBS = anyOfInStr(z, zDQBS, pcLimit-z); + const char *pcPast = zSkipValidUtf8(z, (int)(pcLimit-z), ctrlMask); + const char *pcEnd = (pcDQBS && pcDQBS < pcPast)? pcDQBS : pcPast; + if( pcEnd > z ){ + oputb(z, (int)(pcEnd-z)); + z = pcEnd; + } + if( z >= pcLimit ) break; c = *(z++); - if( c=='\\' || c=='"' ){ - fputc('\\', out); - fputc(c, out); + switch( c ){ + case '"': case '\\': + cbsSay = (char)c; + break; + case '\b': cbsSay = 'b'; break; + case '\f': cbsSay = 'f'; break; + case '\n': cbsSay = 'n'; break; + case '\r': cbsSay = 'r'; break; + case '\t': cbsSay = 't'; break; + default: cbsSay = 0; break; + } + if( cbsSay ){ + ace[1] = cbsSay; + oputz(ace); }else if( c<=0x1f ){ - fputc('\\', out); - if( c=='\b' ){ - fputc('b', out); - }else if( c=='\f' ){ - fputc('f', out); - }else if( c=='\n' ){ - fputc('n', out); - }else if( c=='\r' ){ - fputc('r', out); - }else if( c=='\t' ){ - fputc('t', out); - }else{ - raw_printf(out, "u%04x",c); - } + oputf("u%04x", c); }else{ - fputc(c, out); + ace[1] = (char)c; + oputz(ace+1); } } - fputc('"', out); + oputz(zq); } /* ** Output the given string with characters that are special to ** HTML escaped. */ -static void output_html_string(FILE *out, const char *z){ +static void output_html_string(const char *z){ int i; if( z==0 ) z = ""; while( *z ){ @@ -18050,18 +18825,18 @@ static void output_html_string(FILE *out, const char *z){ && z[i]!='\''; i++){} if( i>0 ){ - utf8_printf(out,"%.*s",i,z); + oputf("%.*s",i,z); } if( z[i]=='<' ){ - raw_printf(out,"<"); + oputz("<"); }else if( z[i]=='&' ){ - raw_printf(out,"&"); + oputz("&"); }else if( z[i]=='>' ){ - raw_printf(out,">"); + oputz(">"); }else if( z[i]=='\"' ){ - raw_printf(out,"""); + oputz("""); }else if( z[i]=='\'' ){ - raw_printf(out,"'"); + oputz("'"); }else{ break; } @@ -18099,9 +18874,8 @@ static const char needCsvQuote[] = { ** is only issued if bSep is true. */ static void output_csv(ShellState *p, const char *z, int bSep){ - FILE *out = p->out; if( z==0 ){ - utf8_printf(out,"%s",p->nullValue); + oputf("%s",p->nullValue); }else{ unsigned i; for(i=0; z[i]; i++){ @@ -18113,14 +18887,14 @@ static void output_csv(ShellState *p, const char *z, int bSep){ if( i==0 || strstr(z, p->colSeparator)!=0 ){ char *zQuoted = sqlite3_mprintf("\"%w\"", z); shell_check_oom(zQuoted); - utf8_printf(out, "%s", zQuoted); + oputz(zQuoted); sqlite3_free(zQuoted); }else{ - utf8_printf(out, "%s", z); + oputz(z); } } if( bSep ){ - utf8_printf(p->out, "%s", p->colSeparator); + oputz(p->colSeparator); } } @@ -18228,16 +19002,16 @@ static int shellAuth( az[1] = zA2; az[2] = zA3; az[3] = zA4; - utf8_printf(p->out, "authorizer: %s", azAction[op]); + oputf("authorizer: %s", azAction[op]); for(i=0; i<4; i++){ - raw_printf(p->out, " "); + oputz(" "); if( az[i] ){ - output_c_string(p->out, az[i]); + output_c_string(az[i]); }else{ - raw_printf(p->out, "NULL"); + oputz("NULL"); } } - raw_printf(p->out, "\n"); + oputz("\n"); if( p->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4); return SQLITE_OK; } @@ -18253,7 +19027,7 @@ static int shellAuth( ** sqlite3_complete() returns false, try to terminate the comment before ** printing the result. https://sqlite.org/forum/forumpost/d7be961c5c */ -static void printSchemaLine(FILE *out, const char *z, const char *zTail){ +static void printSchemaLine(const char *z, const char *zTail){ char *zToFree = 0; if( z==0 ) return; if( zTail==0 ) return; @@ -18275,16 +19049,16 @@ static void printSchemaLine(FILE *out, const char *z, const char *zTail){ } } if( sqlite3_strglob("CREATE TABLE ['\"]*", z)==0 ){ - utf8_printf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail); + oputf("CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail); }else{ - utf8_printf(out, "%s%s", z, zTail); + oputf("%s%s", z, zTail); } sqlite3_free(zToFree); } -static void printSchemaLineN(FILE *out, char *z, int n, const char *zTail){ +static void printSchemaLineN(char *z, int n, const char *zTail){ char c = z[n]; z[n] = 0; - printSchemaLine(out, z, zTail); + printSchemaLine(z, zTail); z[n] = c; } @@ -18312,7 +19086,7 @@ static void eqp_append(ShellState *p, int iEqpId, int p2, const char *zText){ if( zText==0 ) return; nText = strlen(zText); if( p->autoEQPtest ){ - utf8_printf(p->out, "%d,%d,%s\n", iEqpId, p2, zText); + oputf("%d,%d,%s\n", iEqpId, p2, zText); } pNew = sqlite3_malloc64( sizeof(*pNew) + nText ); shell_check_oom(pNew); @@ -18360,8 +19134,7 @@ static void eqp_render_level(ShellState *p, int iEqpId){ for(pRow = eqp_next_row(p, iEqpId, 0); pRow; pRow = pNext){ pNext = eqp_next_row(p, iEqpId, pRow); z = pRow->zText; - utf8_printf(p->out, "%s%s%s\n", p->sGraph.zPrefix, - pNext ? "|--" : "`--", z); + oputf("%s%s%s\n", p->sGraph.zPrefix, pNext ? "|--" : "`--", z); if( n<(i64)sizeof(p->sGraph.zPrefix)-7 ){ memcpy(&p->sGraph.zPrefix[n], pNext ? "| " : " ", 4); eqp_render_level(p, pRow->iEqpId); @@ -18381,13 +19154,13 @@ static void eqp_render(ShellState *p, i64 nCycle){ eqp_reset(p); return; } - utf8_printf(p->out, "%s\n", pRow->zText+3); + oputf("%s\n", pRow->zText+3); p->sGraph.pRow = pRow->pNext; sqlite3_free(pRow); }else if( nCycle>0 ){ - utf8_printf(p->out, "QUERY PLAN (cycles=%lld [100%%])\n", nCycle); + oputf("QUERY PLAN (cycles=%lld [100%%])\n", nCycle); }else{ - utf8_printf(p->out, "QUERY PLAN\n"); + oputz("QUERY PLAN\n"); } p->sGraph.zPrefix[0] = 0; eqp_render_level(p, 0); @@ -18403,13 +19176,13 @@ static int progress_handler(void *pClientData) { ShellState *p = (ShellState*)pClientData; p->nProgress++; if( p->nProgress>=p->mxProgress && p->mxProgress>0 ){ - raw_printf(p->out, "Progress limit reached (%u)\n", p->nProgress); + oputf("Progress limit reached (%u)\n", p->nProgress); if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0; if( p->flgProgress & SHELL_PROGRESS_ONCE ) p->mxProgress = 0; return 1; } if( (p->flgProgress & SHELL_PROGRESS_QUIET)==0 ){ - raw_printf(p->out, "Progress %u\n", p->nProgress); + oputf("Progress %u\n", p->nProgress); } return 0; } @@ -18418,14 +19191,14 @@ static int progress_handler(void *pClientData) { /* ** Print N dashes */ -static void print_dashes(FILE *out, int N){ +static void print_dashes(int N){ const char zDash[] = "--------------------------------------------------"; const int nDash = sizeof(zDash) - 1; while( N>nDash ){ - fputs(zDash, out); + oputz(zDash); N -= nDash; } - raw_printf(out, "%.*s", N, zDash); + oputf("%.*s", N, zDash); } /* @@ -18438,15 +19211,15 @@ static void print_row_separator( ){ int i; if( nArg>0 ){ - fputs(zSep, p->out); - print_dashes(p->out, p->actualWidth[0]+2); + oputz(zSep); + print_dashes(p->actualWidth[0]+2); for(i=1; iout); - print_dashes(p->out, p->actualWidth[i]+2); + oputz(zSep); + print_dashes(p->actualWidth[i]+2); } - fputs(zSep, p->out); + oputz(zSep); } - fputs("\n", p->out); + oputz("\n"); } /* @@ -18476,10 +19249,10 @@ static int shell_callback( int len = strlen30(azCol[i] ? azCol[i] : ""); if( len>w ) w = len; } - if( p->cnt++>0 ) utf8_printf(p->out, "%s", p->rowSeparator); + if( p->cnt++>0 ) oputz(p->rowSeparator); for(i=0; iout,"%*s = %s%s", w, azCol[i], - azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator); + oputf("%*s = %s%s", w, azCol[i], + azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator); } break; } @@ -18506,12 +19279,12 @@ static int shell_callback( /* If this is the first row seen, print out the headers */ if( p->cnt++==0 ){ for(i=0; iout, aWidth[i], azCol[ aMap[i] ]); - fputs(i==nArg-1 ? "\n" : " ", p->out); + utf8_width_print(aWidth[i], azCol[ aMap[i] ]); + oputz(i==nArg-1 ? "\n" : " "); } for(i=0; iout, aWidth[i]); - fputs(i==nArg-1 ? "\n" : " ", p->out); + print_dashes(aWidth[i]); + oputz(i==nArg-1 ? "\n" : " "); } } @@ -18529,17 +19302,17 @@ static int shell_callback( } if( i==iIndent && p->aiIndent && p->pStmt ){ if( p->iIndentnIndent ){ - utf8_printf(p->out, "%*.s", p->aiIndent[p->iIndent], ""); + oputf("%*.s", p->aiIndent[p->iIndent], ""); } p->iIndent++; } - utf8_width_print(p->out, w, zVal ? zVal : p->nullValue); - fputs(i==nArg-1 ? "\n" : zSep, p->out); + utf8_width_print(w, zVal ? zVal : p->nullValue); + oputz(i==nArg-1 ? "\n" : zSep); } break; } case MODE_Semi: { /* .schema and .fullschema output */ - printSchemaLine(p->out, azArg[0], ";\n"); + printSchemaLine(azArg[0], ";\n"); break; } case MODE_Pretty: { /* .schema and .fullschema with --indent */ @@ -18554,7 +19327,7 @@ static int shell_callback( if( sqlite3_strlike("CREATE VIEW%", azArg[0], 0)==0 || sqlite3_strlike("CREATE TRIG%", azArg[0], 0)==0 ){ - utf8_printf(p->out, "%s;\n", azArg[0]); + oputf("%s;\n", azArg[0]); break; } z = sqlite3_mprintf("%s", azArg[0]); @@ -18587,7 +19360,7 @@ static int shell_callback( }else if( c==')' ){ nParen--; if( nLine>0 && nParen==0 && j>0 ){ - printSchemaLineN(p->out, z, j, "\n"); + printSchemaLineN(z, j, "\n"); j = 0; } } @@ -18596,7 +19369,7 @@ static int shell_callback( && (c=='(' || c=='\n' || (c==',' && !wsToEol(z+i+1))) ){ if( c=='\n' ) j--; - printSchemaLineN(p->out, z, j, "\n "); + printSchemaLineN(z, j, "\n "); j = 0; nLine++; while( IsSpace(z[i+1]) ){ i++; } @@ -18604,64 +19377,59 @@ static int shell_callback( } z[j] = 0; } - printSchemaLine(p->out, z, ";\n"); + printSchemaLine(z, ";\n"); sqlite3_free(z); break; } case MODE_List: { if( p->cnt++==0 && p->showHeader ){ for(i=0; iout,"%s%s",azCol[i], - i==nArg-1 ? p->rowSeparator : p->colSeparator); + oputf("%s%s",azCol[i], i==nArg-1 ? p->rowSeparator : p->colSeparator); } } if( azArg==0 ) break; for(i=0; inullValue; - utf8_printf(p->out, "%s", z); - if( iout, "%s", p->colSeparator); - }else{ - utf8_printf(p->out, "%s", p->rowSeparator); - } + oputz(z); + oputz((icolSeparator : p->rowSeparator); } break; } case MODE_Html: { if( p->cnt++==0 && p->showHeader ){ - raw_printf(p->out,""); + oputz(""); for(i=0; iout,""); - output_html_string(p->out, azCol[i]); - raw_printf(p->out,"\n"); + oputz(""); + output_html_string(azCol[i]); + oputz("\n"); } - raw_printf(p->out,"\n"); + oputz("\n"); } if( azArg==0 ) break; - raw_printf(p->out,""); + oputz(""); for(i=0; iout,""); - output_html_string(p->out, azArg[i] ? azArg[i] : p->nullValue); - raw_printf(p->out,"\n"); + oputz(""); + output_html_string(azArg[i] ? azArg[i] : p->nullValue); + oputz("\n"); } - raw_printf(p->out,"\n"); + oputz("\n"); break; } case MODE_Tcl: { if( p->cnt++==0 && p->showHeader ){ for(i=0; iout,azCol[i] ? azCol[i] : ""); - if(iout, "%s", p->colSeparator); + output_c_string(azCol[i] ? azCol[i] : ""); + if(icolSeparator); } - utf8_printf(p->out, "%s", p->rowSeparator); + oputz(p->rowSeparator); } if( azArg==0 ) break; for(i=0; iout, azArg[i] ? azArg[i] : p->nullValue); - if(iout, "%s", p->colSeparator); + output_c_string(azArg[i] ? azArg[i] : p->nullValue); + if(icolSeparator); } - utf8_printf(p->out, "%s", p->rowSeparator); + oputz(p->rowSeparator); break; } case MODE_Csv: { @@ -18670,57 +19438,57 @@ static int shell_callback( for(i=0; iout, "%s", p->rowSeparator); + oputz(p->rowSeparator); } if( nArg>0 ){ for(i=0; iout, "%s", p->rowSeparator); + oputz(p->rowSeparator); } setTextMode(p->out, 1); break; } case MODE_Insert: { if( azArg==0 ) break; - utf8_printf(p->out,"INSERT INTO %s",p->zDestTable); + oputf("INSERT INTO %s",p->zDestTable); if( p->showHeader ){ - raw_printf(p->out,"("); + oputz("("); for(i=0; i0 ) raw_printf(p->out, ","); + if( i>0 ) oputz(","); if( quoteChar(azCol[i]) ){ char *z = sqlite3_mprintf("\"%w\"", azCol[i]); shell_check_oom(z); - utf8_printf(p->out, "%s", z); + oputz(z); sqlite3_free(z); }else{ - raw_printf(p->out, "%s", azCol[i]); + oputf("%s", azCol[i]); } } - raw_printf(p->out,")"); + oputz(")"); } p->cnt++; for(i=0; iout, i>0 ? "," : " VALUES("); + oputz(i>0 ? "," : " VALUES("); if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ - utf8_printf(p->out,"NULL"); + oputz("NULL"); }else if( aiType && aiType[i]==SQLITE_TEXT ){ if( ShellHasFlag(p, SHFLG_Newlines) ){ - output_quoted_string(p->out, azArg[i]); + output_quoted_string(azArg[i]); }else{ - output_quoted_escaped_string(p->out, azArg[i]); + output_quoted_escaped_string(azArg[i]); } }else if( aiType && aiType[i]==SQLITE_INTEGER ){ - utf8_printf(p->out,"%s", azArg[i]); + oputz(azArg[i]); }else if( aiType && aiType[i]==SQLITE_FLOAT ){ char z[50]; double r = sqlite3_column_double(p->pStmt, i); sqlite3_uint64 ur; memcpy(&ur,&r,sizeof(r)); if( ur==0x7ff0000000000000LL ){ - raw_printf(p->out, "9.0e+999"); + oputz("9.0e+999"); }else if( ur==0xfff0000000000000LL ){ - raw_printf(p->out, "-9.0e+999"); + oputz("-9.0e+999"); }else{ sqlite3_int64 ir = (sqlite3_int64)r; if( r==(double)ir ){ @@ -18728,21 +19496,21 @@ static int shell_callback( }else{ sqlite3_snprintf(50,z,"%!.20g", r); } - raw_printf(p->out, "%s", z); + oputz(z); } }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ const void *pBlob = sqlite3_column_blob(p->pStmt, i); int nBlob = sqlite3_column_bytes(p->pStmt, i); - output_hex_blob(p->out, pBlob, nBlob); + output_hex_blob(pBlob, nBlob); }else if( isNumber(azArg[i], 0) ){ - utf8_printf(p->out,"%s", azArg[i]); + oputz(azArg[i]); }else if( ShellHasFlag(p, SHFLG_Newlines) ){ - output_quoted_string(p->out, azArg[i]); + output_quoted_string(azArg[i]); }else{ - output_quoted_escaped_string(p->out, azArg[i]); + output_quoted_escaped_string(azArg[i]); } } - raw_printf(p->out,");\n"); + oputz(");\n"); break; } case MODE_Json: { @@ -18754,37 +19522,37 @@ static int shell_callback( } p->cnt++; for(i=0; iout, azCol[i], -1); - putc(':', p->out); + output_json_string(azCol[i], -1); + oputz(":"); if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ - fputs("null",p->out); + oputz("null"); }else if( aiType && aiType[i]==SQLITE_FLOAT ){ char z[50]; double r = sqlite3_column_double(p->pStmt, i); sqlite3_uint64 ur; memcpy(&ur,&r,sizeof(r)); if( ur==0x7ff0000000000000LL ){ - raw_printf(p->out, "9.0e+999"); + oputz("9.0e+999"); }else if( ur==0xfff0000000000000LL ){ - raw_printf(p->out, "-9.0e+999"); + oputz("-9.0e+999"); }else{ sqlite3_snprintf(50,z,"%!.20g", r); - raw_printf(p->out, "%s", z); + oputz(z); } }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ const void *pBlob = sqlite3_column_blob(p->pStmt, i); int nBlob = sqlite3_column_bytes(p->pStmt, i); - output_json_string(p->out, pBlob, nBlob); + output_json_string(pBlob, nBlob); }else if( aiType && aiType[i]==SQLITE_TEXT ){ - output_json_string(p->out, azArg[i], -1); + output_json_string(azArg[i], -1); }else{ - utf8_printf(p->out,"%s", azArg[i]); + oputz(azArg[i]); } if( iout); + oputz(","); } } - putc('}', p->out); + oputz("}"); break; } case MODE_Quote: { @@ -18792,7 +19560,7 @@ static int shell_callback( if( p->cnt==0 && p->showHeader ){ for(i=0; i0 ) fputs(p->colSeparator, p->out); - output_quoted_string(p->out, azCol[i]); + output_quoted_string(azCol[i]); } fputs(p->rowSeparator, p->out); } @@ -18800,24 +19568,24 @@ static int shell_callback( for(i=0; i0 ) fputs(p->colSeparator, p->out); if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){ - utf8_printf(p->out,"NULL"); + oputz("NULL"); }else if( aiType && aiType[i]==SQLITE_TEXT ){ - output_quoted_string(p->out, azArg[i]); + output_quoted_string(azArg[i]); }else if( aiType && aiType[i]==SQLITE_INTEGER ){ - utf8_printf(p->out,"%s", azArg[i]); + oputz(azArg[i]); }else if( aiType && aiType[i]==SQLITE_FLOAT ){ char z[50]; double r = sqlite3_column_double(p->pStmt, i); sqlite3_snprintf(50,z,"%!.20g", r); - raw_printf(p->out, "%s", z); + oputz(z); }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){ const void *pBlob = sqlite3_column_blob(p->pStmt, i); int nBlob = sqlite3_column_bytes(p->pStmt, i); - output_hex_blob(p->out, pBlob, nBlob); + output_hex_blob(pBlob, nBlob); }else if( isNumber(azArg[i], 0) ){ - utf8_printf(p->out,"%s", azArg[i]); + oputz(azArg[i]); }else{ - output_quoted_string(p->out, azArg[i]); + output_quoted_string(azArg[i]); } } fputs(p->rowSeparator, p->out); @@ -18826,17 +19594,17 @@ static int shell_callback( case MODE_Ascii: { if( p->cnt++==0 && p->showHeader ){ for(i=0; i0 ) utf8_printf(p->out, "%s", p->colSeparator); - utf8_printf(p->out,"%s",azCol[i] ? azCol[i] : ""); + if( i>0 ) oputz(p->colSeparator); + oputz(azCol[i] ? azCol[i] : ""); } - utf8_printf(p->out, "%s", p->rowSeparator); + oputz(p->rowSeparator); } if( azArg==0 ) break; for(i=0; i0 ) utf8_printf(p->out, "%s", p->colSeparator); - utf8_printf(p->out,"%s",azArg[i] ? azArg[i] : p->nullValue); + if( i>0 ) oputz(p->colSeparator); + oputz(azArg[i] ? azArg[i] : p->nullValue); } - utf8_printf(p->out, "%s", p->rowSeparator); + oputz(p->rowSeparator); break; } case MODE_EQP: { @@ -18915,7 +19683,7 @@ static void createSelftestTable(ShellState *p){ "DROP TABLE [_shell$self];" ,0,0,&zErrMsg); if( zErrMsg ){ - utf8_printf(stderr, "SELFTEST initialization failure: %s\n", zErrMsg); + eputf("SELFTEST initialization failure: %s\n", zErrMsg); sqlite3_free(zErrMsg); } sqlite3_exec(p->db, "RELEASE selftest_init",0,0,0); @@ -19018,8 +19786,8 @@ static int run_table_dump_query( rc = sqlite3_prepare_v2(p->db, zSelect, -1, &pSelect, 0); if( rc!=SQLITE_OK || !pSelect ){ char *zContext = shell_error_context(zSelect, p->db); - utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n%s", rc, - sqlite3_errmsg(p->db), zContext); + oputf("/**** ERROR: (%d) %s *****/\n%s", + rc, sqlite3_errmsg(p->db), zContext); sqlite3_free(zContext); if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; return rc; @@ -19028,23 +19796,22 @@ static int run_table_dump_query( nResult = sqlite3_column_count(pSelect); while( rc==SQLITE_ROW ){ z = (const char*)sqlite3_column_text(pSelect, 0); - utf8_printf(p->out, "%s", z); + oputf("%s", z); for(i=1; iout, ",%s", sqlite3_column_text(pSelect, i)); + oputf(",%s", sqlite3_column_text(pSelect, i)); } if( z==0 ) z = ""; while( z[0] && (z[0]!='-' || z[1]!='-') ) z++; if( z[0] ){ - raw_printf(p->out, "\n;\n"); + oputz("\n;\n"); }else{ - raw_printf(p->out, ";\n"); + oputz(";\n"); } rc = sqlite3_step(pSelect); } rc = sqlite3_finalize(pSelect); if( rc!=SQLITE_OK ){ - utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, - sqlite3_errmsg(p->db)); + oputf("/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; } return rc; @@ -19080,7 +19847,7 @@ static char *save_err_msg( /* ** Attempt to display I/O stats on Linux using /proc/PID/io */ -static void displayLinuxIoStats(FILE *out){ +static void displayLinuxIoStats(void){ FILE *in; char z[200]; sqlite3_snprintf(sizeof(z), z, "/proc/%d/io", getpid()); @@ -19103,7 +19870,7 @@ static void displayLinuxIoStats(FILE *out){ for(i=0; iout, "%-36s %s\n", zLabel, zLine); + oputf("%-36s %s\n", zLabel, zLine); } /* @@ -19148,30 +19914,28 @@ static int display_stats( ){ int iCur; int iHiwtr; - FILE *out; if( pArg==0 || pArg->out==0 ) return 0; - out = pArg->out; if( pArg->pStmt && pArg->statsOn==2 ){ int nCol, i, x; sqlite3_stmt *pStmt = pArg->pStmt; char z[100]; nCol = sqlite3_column_count(pStmt); - raw_printf(out, "%-36s %d\n", "Number of output columns:", nCol); + oputf("%-36s %d\n", "Number of output columns:", nCol); for(i=0; istatsOn==3 ){ if( pArg->pStmt ){ iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP,bReset); - raw_printf(pArg->out, "VM-steps: %d\n", iCur); + oputf("VM-steps: %d\n", iCur); } return 0; } - displayStatLine(pArg, "Memory Used:", + displayStatLine("Memory Used:", "%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset); - displayStatLine(pArg, "Number of Outstanding Allocations:", + displayStatLine("Number of Outstanding Allocations:", "%lld (max %lld)", SQLITE_STATUS_MALLOC_COUNT, bReset); if( pArg->shellFlgs & SHFLG_Pagecache ){ - displayStatLine(pArg, "Number of Pcache Pages Used:", + displayStatLine("Number of Pcache Pages Used:", "%lld (max %lld) pages", SQLITE_STATUS_PAGECACHE_USED, bReset); } - displayStatLine(pArg, "Number of Pcache Overflow Bytes:", + displayStatLine("Number of Pcache Overflow Bytes:", "%lld (max %lld) bytes", SQLITE_STATUS_PAGECACHE_OVERFLOW, bReset); - displayStatLine(pArg, "Largest Allocation:", + displayStatLine("Largest Allocation:", "%lld bytes", SQLITE_STATUS_MALLOC_SIZE, bReset); - displayStatLine(pArg, "Largest Pcache Allocation:", + displayStatLine("Largest Pcache Allocation:", "%lld bytes", SQLITE_STATUS_PAGECACHE_SIZE, bReset); #ifdef YYTRACKMAXSTACKDEPTH - displayStatLine(pArg, "Deepest Parser Stack:", + displayStatLine("Deepest Parser Stack:", "%lld (max %lld)", SQLITE_STATUS_PARSER_STACK, bReset); #endif @@ -19208,77 +19972,68 @@ static int display_stats( iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, - "Lookaside Slots Used: %d (max %d)\n", - iCur, iHiwtr); + oputf("Lookaside Slots Used: %d (max %d)\n", iCur, iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, "Successful lookaside attempts: %d\n", - iHiwtr); + oputf("Successful lookaside attempts: %d\n", iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, "Lookaside failures due to size: %d\n", - iHiwtr); + oputf("Lookaside failures due to size: %d\n", iHiwtr); sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, "Lookaside failures due to OOM: %d\n", - iHiwtr); + oputf("Lookaside failures due to OOM: %d\n", iHiwtr); } iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, "Pager Heap Usage: %d bytes\n", - iCur); + oputf("Pager Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1); - raw_printf(pArg->out, "Page cache hits: %d\n", iCur); + oputf("Page cache hits: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1); - raw_printf(pArg->out, "Page cache misses: %d\n", iCur); + oputf("Page cache misses: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1); - raw_printf(pArg->out, "Page cache writes: %d\n", iCur); + oputf("Page cache writes: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1); - raw_printf(pArg->out, "Page cache spills: %d\n", iCur); + oputf("Page cache spills: %d\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, "Schema Heap Usage: %d bytes\n", - iCur); + oputf("Schema Heap Usage: %d bytes\n", iCur); iHiwtr = iCur = -1; sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset); - raw_printf(pArg->out, "Statement Heap/Lookaside Usage: %d bytes\n", - iCur); + oputf("Statement Heap/Lookaside Usage: %d bytes\n", iCur); } if( pArg->pStmt ){ int iHit, iMiss; iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, bReset); - raw_printf(pArg->out, "Fullscan Steps: %d\n", iCur); + oputf("Fullscan Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset); - raw_printf(pArg->out, "Sort Operations: %d\n", iCur); + oputf("Sort Operations: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset); - raw_printf(pArg->out, "Autoindex Inserts: %d\n", iCur); + oputf("Autoindex Inserts: %d\n", iCur); iHit = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_HIT, bReset); iMiss = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_MISS, bReset); if( iHit || iMiss ){ - raw_printf(pArg->out, "Bloom filter bypass taken: %d/%d\n", - iHit, iHit+iMiss); + oputf("Bloom filter bypass taken: %d/%d\n", iHit, iHit+iMiss); } iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset); - raw_printf(pArg->out, "Virtual Machine Steps: %d\n", iCur); + oputf("Virtual Machine Steps: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset); - raw_printf(pArg->out, "Reprepare operations: %d\n", iCur); + oputf("Reprepare operations: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_RUN, bReset); - raw_printf(pArg->out, "Number of times run: %d\n", iCur); + oputf("Number of times run: %d\n", iCur); iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset); - raw_printf(pArg->out, "Memory used by prepared stmt: %d\n", iCur); + oputf("Memory used by prepared stmt: %d\n", iCur); } #ifdef __linux__ - displayLinuxIoStats(pArg->out); + displayLinuxIoStats(); #endif /* Do not remove this machine readable comment: extra-stats-output-here */ @@ -19513,7 +20268,7 @@ static void display_scanstats( UNUSED_PARAMETER(pArg); #else if( pArg->scanstatsOn==3 ){ - const char *zSql = + const char *zSql = " SELECT addr, opcode, p1, p2, p3, p4, p5, comment, nexec," " round(ncycle*100.0 / (sum(ncycle) OVER ()), 2)||'%' AS cycles" " FROM bytecode(?)"; @@ -19659,17 +20414,17 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ /* Draw horizontal line N characters long using unicode box ** characters */ -static void print_box_line(FILE *out, int N){ +static void print_box_line(int N){ const char zDash[] = BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24; const int nDash = sizeof(zDash) - 1; N *= 3; while( N>nDash ){ - utf8_printf(out, zDash); + oputz(zDash); N -= nDash; } - utf8_printf(out, "%.*s", N, zDash); + oputf("%.*s", N, zDash); } /* @@ -19684,15 +20439,15 @@ static void print_box_row_separator( ){ int i; if( nArg>0 ){ - utf8_printf(p->out, "%s", zSep1); - print_box_line(p->out, p->actualWidth[0]+2); + oputz(zSep1); + print_box_line(p->actualWidth[0]+2); for(i=1; iout, "%s", zSep2); - print_box_line(p->out, p->actualWidth[i]+2); + oputz(zSep2); + print_box_line(p->actualWidth[i]+2); } - utf8_printf(p->out, "%s", zSep3); + oputz(zSep3); } - fputs("\n", p->out); + oputz("\n"); } /* @@ -19955,11 +20710,11 @@ static void exec_prepared_stmt_columnar( for(i=0; iactualWidth[i]; if( p->colWidth[i]<0 ) w = -w; - utf8_width_print(p->out, w, azData[i]); + utf8_width_print(w, azData[i]); fputs(i==nColumn-1?"\n":" ", p->out); } for(i=0; iout, p->actualWidth[i]); + print_dashes(p->actualWidth[i]); fputs(i==nColumn-1?"\n":" ", p->out); } } @@ -19973,8 +20728,8 @@ static void exec_prepared_stmt_columnar( for(i=0; iactualWidth[i]; n = strlenChar(azData[i]); - utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); - fputs(i==nColumn-1?" |\n":" | ", p->out); + oputf("%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); + oputz(i==nColumn-1?" |\n":" | "); } print_row_separator(p, nColumn, "+"); break; @@ -19986,8 +20741,8 @@ static void exec_prepared_stmt_columnar( for(i=0; iactualWidth[i]; n = strlenChar(azData[i]); - utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); - fputs(i==nColumn-1?" |\n":" | ", p->out); + oputf("%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, ""); + oputz(i==nColumn-1?" |\n":" | "); } print_row_separator(p, nColumn, "|"); break; @@ -19996,13 +20751,13 @@ static void exec_prepared_stmt_columnar( colSep = " " BOX_13 " "; rowSep = " " BOX_13 "\n"; print_box_row_separator(p, nColumn, BOX_23, BOX_234, BOX_34); - utf8_printf(p->out, BOX_13 " "); + oputz(BOX_13 " "); for(i=0; iactualWidth[i]; n = strlenChar(azData[i]); - utf8_printf(p->out, "%*s%s%*s%s", - (w-n)/2, "", azData[i], (w-n+1)/2, "", - i==nColumn-1?" "BOX_13"\n":" "BOX_13" "); + oputf("%*s%s%*s%s", + (w-n)/2, "", azData[i], (w-n+1)/2, "", + i==nColumn-1?" "BOX_13"\n":" "BOX_13" "); } print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134); break; @@ -20010,28 +20765,28 @@ static void exec_prepared_stmt_columnar( } for(i=nColumn, j=0; icMode!=MODE_Column ){ - utf8_printf(p->out, "%s", p->cMode==MODE_Box?BOX_13" ":"| "); + oputz(p->cMode==MODE_Box?BOX_13" ":"| "); } z = azData[i]; if( z==0 ) z = p->nullValue; w = p->actualWidth[j]; if( p->colWidth[j]<0 ) w = -w; - utf8_width_print(p->out, w, z); + utf8_width_print(w, z); if( j==nColumn-1 ){ - utf8_printf(p->out, "%s", rowSep); + oputz(rowSep); if( bMultiLineRowExists && abRowDiv[i/nColumn-1] && i+1cMode==MODE_Table ){ print_row_separator(p, nColumn, "+"); }else if( p->cMode==MODE_Box ){ print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134); }else if( p->cMode==MODE_Column ){ - raw_printf(p->out, "\n"); + oputz("\n"); } } j = -1; if( seenInterrupt ) goto columnar_end; }else{ - utf8_printf(p->out, "%s", colSep); + oputz(colSep); } } if( p->cMode==MODE_Table ){ @@ -20041,7 +20796,7 @@ static void exec_prepared_stmt_columnar( } columnar_end: if( seenInterrupt ){ - utf8_printf(p->out, "Interrupt\n"); + oputz("Interrupt\n"); } nData = (nRow+1)*nColumn; for(i=0; iout; int bVerbose = pState->expert.bVerbose; rc = sqlite3_expert_analyze(p, pzErr); @@ -20190,8 +20944,8 @@ static int expertFinish( if( bVerbose ){ const char *zCand = sqlite3_expert_report(p,0,EXPERT_REPORT_CANDIDATES); - raw_printf(out, "-- Candidates -----------------------------\n"); - raw_printf(out, "%s\n", zCand); + oputz("-- Candidates -----------------------------\n"); + oputf("%s\n", zCand); } for(i=0; i=2 && 0==cli_strncmp(z, "-sample", n) ){ if( i==(nArg-1) ){ - raw_printf(stderr, "option requires an argument: %s\n", z); + eputf("option requires an argument: %s\n", z); rc = SQLITE_ERROR; }else{ iSample = (int)integerValue(azArg[++i]); if( iSample<0 || iSample>100 ){ - raw_printf(stderr, "value out of range: %s\n", azArg[i]); + eputf("value out of range: %s\n", azArg[i]); rc = SQLITE_ERROR; } } } else{ - raw_printf(stderr, "unknown option: %s\n", z); + eputf("unknown option: %s\n", z); rc = SQLITE_ERROR; } } @@ -20257,8 +21011,7 @@ static int expertDotCommand( if( rc==SQLITE_OK ){ pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr); if( pState->expert.pExpert==0 ){ - raw_printf(stderr, "sqlite3_expert_new: %s\n", - zErr ? zErr : "out of memory"); + eputf("sqlite3_expert_new: %s\n", zErr ? zErr : "out of memory"); rc = SQLITE_ERROR; }else{ sqlite3_expert_config( @@ -20585,9 +21338,9 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ noSys = (p->shellFlgs & SHFLG_DumpNoSys)!=0; if( cli_strcmp(zTable, "sqlite_sequence")==0 && !noSys ){ - if( !dataOnly ) raw_printf(p->out, "DELETE FROM sqlite_sequence;\n"); + if( !dataOnly ) oputz("DELETE FROM sqlite_sequence;\n"); }else if( sqlite3_strglob("sqlite_stat?", zTable)==0 && !noSys ){ - if( !dataOnly ) raw_printf(p->out, "ANALYZE sqlite_schema;\n"); + if( !dataOnly ) oputz("ANALYZE sqlite_schema;\n"); }else if( cli_strncmp(zTable, "sqlite_", 7)==0 ){ return 0; }else if( dataOnly ){ @@ -20595,7 +21348,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ }else if( cli_strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){ char *zIns; if( !p->writableSchema ){ - raw_printf(p->out, "PRAGMA writable_schema=ON;\n"); + oputz("PRAGMA writable_schema=ON;\n"); p->writableSchema = 1; } zIns = sqlite3_mprintf( @@ -20603,11 +21356,11 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ "VALUES('table','%q','%q',0,'%q');", zTable, zTable, zSql); shell_check_oom(zIns); - utf8_printf(p->out, "%s\n", zIns); + oputf("%s\n", zIns); sqlite3_free(zIns); return 0; }else{ - printSchemaLine(p->out, zSql, ";\n"); + printSchemaLine(zSql, ";\n"); } if( cli_strcmp(zType, "table")==0 ){ @@ -20665,7 +21418,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){ p->mode = p->cMode = MODE_Insert; rc = shell_exec(p, sSelect.z, 0); if( (rc&0xff)==SQLITE_CORRUPT ){ - raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n"); + oputz("/****** CORRUPTION ERROR *******/\n"); toggleSelectOrder(p->db); shell_exec(p, sSelect.z, 0); toggleSelectOrder(p->db); @@ -20696,9 +21449,9 @@ static int run_schema_dump_query( if( rc==SQLITE_CORRUPT ){ char *zQ2; int len = strlen30(zQuery); - raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n"); + oputz("/****** CORRUPTION ERROR *******/\n"); if( zErr ){ - utf8_printf(p->out, "/****** %s ******/\n", zErr); + oputf("/****** %s ******/\n", zErr); sqlite3_free(zErr); zErr = 0; } @@ -20707,7 +21460,7 @@ static int run_schema_dump_query( sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery); rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr); if( rc ){ - utf8_printf(p->out, "/****** ERROR: %s ******/\n", zErr); + oputf("/****** ERROR: %s ******/\n", zErr); }else{ rc = SQLITE_CORRUPT; } @@ -21063,10 +21816,10 @@ static int showHelp(FILE *out, const char *zPattern){ } if( ((hw^hh)&HH_Undoc)==0 ){ if( (hh&HH_Summary)!=0 ){ - utf8_printf(out, ".%s\n", azHelp[i]+1); + sputf(out, ".%s\n", azHelp[i]+1); ++n; }else if( (hw&HW_SummaryOnly)==0 ){ - utf8_printf(out, "%s\n", azHelp[i]); + sputf(out, "%s\n", azHelp[i]); } } } @@ -21076,7 +21829,7 @@ static int showHelp(FILE *out, const char *zPattern){ shell_check_oom(zPat); for(i=0; i65536 || (pgsz & (pgsz-1))!=0 ){ - utf8_printf(stderr, "invalid pagesize\n"); + eputz("invalid pagesize\n"); goto readHexDb_error; } for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){ @@ -21345,7 +22098,7 @@ readHexDb_error: p->lineno = nLine; } sqlite3_free(a); - utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine); + eputf("Error on line %d of --hexdb input\n", nLine); return 0; } #endif /* SQLITE_OMIT_DESERIALIZE */ @@ -21421,22 +22174,19 @@ static void open_db(ShellState *p, int openFlags){ } globalDb = p->db; if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ - utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n", - zDbFilename, sqlite3_errmsg(p->db)); + eputf("Error: unable to open database \"%s\": %s\n", + zDbFilename, sqlite3_errmsg(p->db)); if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){ exit(1); } sqlite3_close(p->db); sqlite3_open(":memory:", &p->db); if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){ - utf8_printf(stderr, - "Also: unable to open substitute in-memory database.\n" - ); + eputz("Also: unable to open substitute in-memory database.\n"); exit(1); }else{ - utf8_printf(stderr, - "Notice: using substitute in-memory database instead of \"%s\"\n", - zDbFilename); + eputf("Notice: using substitute in-memory database instead of \"%s\"\n", + zDbFilename); } } sqlite3_db_config(p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, (int)0, (int*)0); @@ -21543,7 +22293,7 @@ static void open_db(ShellState *p, int openFlags){ SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE); if( rc ){ - utf8_printf(stderr, "Error: sqlite3_deserialize() returns %d\n", rc); + eputf("Error: sqlite3_deserialize() returns %d\n", rc); } if( p->szMax>0 ){ sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax); @@ -21567,8 +22317,7 @@ static void open_db(ShellState *p, int openFlags){ void close_db(sqlite3 *db){ int rc = sqlite3_close(db); if( rc ){ - utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n", - rc, sqlite3_errmsg(db)); + eputf("Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db)); } } @@ -21729,8 +22478,7 @@ static int booleanValue(const char *zArg){ if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){ return 0; } - utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", - zArg); + eputf("ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", zArg); return 0; } @@ -21768,7 +22516,7 @@ static FILE *output_file_open(const char *zFile, int bTextMode){ }else{ f = fopen(zFile, bTextMode ? "w" : "wb"); if( f==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); + eputf("Error: cannot open \"%s\"\n", zFile); } } return f; @@ -21790,7 +22538,7 @@ static int sql_trace_callback( i64 nSql; if( p->traceOut==0 ) return 0; if( mType==SQLITE_TRACE_CLOSE ){ - utf8_printf(p->traceOut, "-- closing database connection\n"); + sputz(p->traceOut, "-- closing database connection\n"); return 0; } if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){ @@ -21821,12 +22569,12 @@ static int sql_trace_callback( switch( mType ){ case SQLITE_TRACE_ROW: case SQLITE_TRACE_STMT: { - utf8_printf(p->traceOut, "%.*s;\n", (int)nSql, zSql); + sputf(p->traceOut, "%.*s;\n", (int)nSql, zSql); break; } case SQLITE_TRACE_PROFILE: { sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0; - utf8_printf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec); + sputf(p->traceOut, "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec); break; } } @@ -21933,12 +22681,11 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){ break; } if( pc==cQuote && c!='\r' ){ - utf8_printf(stderr, "%s:%d: unescaped %c character\n", - p->zFile, p->nLine, cQuote); + eputf("%s:%d: unescaped %c character\n", p->zFile, p->nLine, cQuote); } if( c==EOF ){ - utf8_printf(stderr, "%s:%d: unterminated %c-quoted field\n", - p->zFile, startLine, cQuote); + eputf("%s:%d: unterminated %c-quoted field\n", + p->zFile, startLine, cQuote); p->cTerm = c; break; } @@ -22036,9 +22783,8 @@ static void tryToCloneData( shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ - utf8_printf(stderr, "Error %d: %s on [%s]\n", - sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), - zQuery); + eputf("Error %d: %s on [%s]\n", + sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery); goto end_data_xfer; } n = sqlite3_column_count(pQuery); @@ -22054,9 +22800,8 @@ static void tryToCloneData( memcpy(zInsert+i, ");", 3); rc = sqlite3_prepare_v2(newDb, zInsert, -1, &pInsert, 0); if( rc ){ - utf8_printf(stderr, "Error %d: %s on [%s]\n", - sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb), - zInsert); + eputf("Error %d: %s on [%s]\n", + sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb), zInsert); goto end_data_xfer; } for(k=0; k<2; k++){ @@ -22091,8 +22836,8 @@ static void tryToCloneData( } /* End for */ rc = sqlite3_step(pInsert); if( rc!=SQLITE_OK && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){ - utf8_printf(stderr, "Error %d: %s\n", sqlite3_extended_errcode(newDb), - sqlite3_errmsg(newDb)); + eputf("Error %d: %s\n", + sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb)); } sqlite3_reset(pInsert); cnt++; @@ -22109,7 +22854,7 @@ static void tryToCloneData( shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ - utf8_printf(stderr, "Warning: cannot step \"%s\" backwards", zTable); + eputf("Warning: cannot step \"%s\" backwards", zTable); break; } } /* End for(k=0...) */ @@ -22146,9 +22891,8 @@ static void tryToCloneSchema( shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ - utf8_printf(stderr, "Error: (%d) %s on [%s]\n", - sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), - zQuery); + eputf("Error: (%d) %s on [%s]\n", sqlite3_extended_errcode(p->db), + sqlite3_errmsg(p->db), zQuery); goto end_schema_xfer; } while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){ @@ -22156,10 +22900,10 @@ static void tryToCloneSchema( zSql = sqlite3_column_text(pQuery, 1); if( zName==0 || zSql==0 ) continue; if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ){ - printf("%s... ", zName); fflush(stdout); + sputf(stdout, "%s... ", zName); fflush(stdout); sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); if( zErrMsg ){ - utf8_printf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + eputf("Error: %s\nSQL: [%s]\n", zErrMsg, zSql); sqlite3_free(zErrMsg); zErrMsg = 0; } @@ -22167,7 +22911,7 @@ static void tryToCloneSchema( if( xForEach ){ xForEach(p, newDb, (const char*)zName); } - printf("done\n"); + sputz(stdout, "done\n"); } if( rc!=SQLITE_DONE ){ sqlite3_finalize(pQuery); @@ -22177,9 +22921,8 @@ static void tryToCloneSchema( shell_check_oom(zQuery); rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0); if( rc ){ - utf8_printf(stderr, "Error: (%d) %s on [%s]\n", - sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), - zQuery); + eputf("Error: (%d) %s on [%s]\n", + sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery); goto end_schema_xfer; } while( sqlite3_step(pQuery)==SQLITE_ROW ){ @@ -22187,17 +22930,17 @@ static void tryToCloneSchema( zSql = sqlite3_column_text(pQuery, 1); if( zName==0 || zSql==0 ) continue; if( sqlite3_stricmp((char*)zName, "sqlite_sequence")==0 ) continue; - printf("%s... ", zName); fflush(stdout); + sputf(stdout, "%s... ", zName); fflush(stdout); sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg); if( zErrMsg ){ - utf8_printf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql); + eputf("Error: %s\nSQL: [%s]\n", zErrMsg, zSql); sqlite3_free(zErrMsg); zErrMsg = 0; } if( xForEach ){ xForEach(p, newDb, (const char*)zName); } - printf("done\n"); + sputz(stdout, "done\n"); } } end_schema_xfer: @@ -22214,13 +22957,12 @@ static void tryToClone(ShellState *p, const char *zNewDb){ int rc; sqlite3 *newDb = 0; if( access(zNewDb,0)==0 ){ - utf8_printf(stderr, "File \"%s\" already exists.\n", zNewDb); + eputf("File \"%s\" already exists.\n", zNewDb); return; } rc = sqlite3_open(zNewDb, &newDb); if( rc ){ - utf8_printf(stderr, "Cannot create output database: %s\n", - sqlite3_errmsg(newDb)); + eputf("Cannot create output database: %s\n", sqlite3_errmsg(newDb)); }else{ sqlite3_exec(p->db, "PRAGMA writable_schema=ON;", 0, 0, 0); sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0); @@ -22232,6 +22974,18 @@ static void tryToClone(ShellState *p, const char *zNewDb){ close_db(newDb); } +#ifndef SQLITE_SHELL_FIDDLE +/* +** Change the output stream (file or pipe or console) to something else. +*/ +static void output_redir(ShellState *p, FILE *pfNew){ + if( p->out != stdout ) eputz("Output already redirected.\n"); + else{ + p->out = pfNew; + setOutputStream(pfNew); + } +} + /* ** Change the output file back to stdout. ** @@ -22259,7 +23013,7 @@ static void output_reset(ShellState *p){ char *zCmd; zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile); if( system(zCmd) ){ - utf8_printf(stderr, "Failed: [%s]\n", zCmd); + eputf("Failed: [%s]\n", zCmd); }else{ /* Give the start/open/xdg-open command some time to get ** going before we continue, and potential delete the @@ -22274,7 +23028,12 @@ static void output_reset(ShellState *p){ } p->outfile[0] = 0; p->out = stdout; + setOutputStream(stdout); } +#else +# define output_redir(SS,pfO) +# define output_reset(SS) +#endif /* ** Run an SQL command and return the single integer result. @@ -22345,7 +23104,7 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1", -1, &pStmt, 0); if( rc ){ - utf8_printf(stderr, "error: %s\n", sqlite3_errmsg(p->db)); + eputf("error: %s\n", sqlite3_errmsg(p->db)); sqlite3_finalize(pStmt); return 1; } @@ -22358,28 +23117,28 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ memcpy(aHdr, pb, 100); sqlite3_finalize(pStmt); }else{ - raw_printf(stderr, "unable to read database header\n"); + eputz("unable to read database header\n"); sqlite3_finalize(pStmt); return 1; } i = get2byteInt(aHdr+16); if( i==1 ) i = 65536; - utf8_printf(p->out, "%-20s %d\n", "database page size:", i); - utf8_printf(p->out, "%-20s %d\n", "write format:", aHdr[18]); - utf8_printf(p->out, "%-20s %d\n", "read format:", aHdr[19]); - utf8_printf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]); + oputf("%-20s %d\n", "database page size:", i); + oputf("%-20s %d\n", "write format:", aHdr[18]); + oputf("%-20s %d\n", "read format:", aHdr[19]); + oputf("%-20s %d\n", "reserved bytes:", aHdr[20]); for(i=0; iout, "%-20s %u", aField[i].zName, val); + oputf("%-20s %u", aField[i].zName, val); switch( ofst ){ case 56: { - if( val==1 ) raw_printf(p->out, " (utf8)"); - if( val==2 ) raw_printf(p->out, " (utf16le)"); - if( val==3 ) raw_printf(p->out, " (utf16be)"); + if( val==1 ) oputz(" (utf8)"); + if( val==2 ) oputz(" (utf16le)"); + if( val==3 ) oputz(" (utf16be)"); } } - raw_printf(p->out, "\n"); + oputz("\n"); } if( zDb==0 ){ zSchemaTab = sqlite3_mprintf("main.sqlite_schema"); @@ -22392,11 +23151,11 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab); int val = db_int(p->db, zSql); sqlite3_free(zSql); - utf8_printf(p->out, "%-20s %d\n", aQuery[i].zName, val); + oputf("%-20s %d\n", aQuery[i].zName, val); } sqlite3_free(zSchemaTab); sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion); - utf8_printf(p->out, "%-20s %u\n", "data version", iDataVersion); + oputf("%-20s %u\n", "data version", iDataVersion); return 0; } #endif /* SQLITE_SHELL_HAVE_RECOVER */ @@ -22406,7 +23165,7 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ */ static int shellDatabaseError(sqlite3 *db){ const char *zErr = sqlite3_errmsg(db); - utf8_printf(stderr, "Error: %s\n", zErr); + eputf("Error: %s\n", zErr); return 1; } @@ -22641,7 +23400,6 @@ static int lintFkeyIndexes( int nArg /* Number of entries in azArg[] */ ){ sqlite3 *db = pState->db; /* Database handle to query "main" db of */ - FILE *out = pState->out; /* Stream to write non-error output to */ int bVerbose = 0; /* If -verbose is present */ int bGroupByParent = 0; /* If -groupbyparent is present */ int i; /* To iterate through azArg[] */ @@ -22723,9 +23481,7 @@ static int lintFkeyIndexes( zIndent = " "; } else{ - raw_printf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n", - azArg[0], azArg[1] - ); + eputf("Usage: %s %s ?-verbose? ?-groupbyparent?\n", azArg[0], azArg[1]); return SQLITE_ERROR; } } @@ -22769,23 +23525,23 @@ static int lintFkeyIndexes( if( rc!=SQLITE_OK ) break; if( res<0 ){ - raw_printf(stderr, "Error: internal error"); + eputz("Error: internal error"); break; }else{ if( bGroupByParent && (bVerbose || res==0) && (zPrev==0 || sqlite3_stricmp(zParent, zPrev)) ){ - raw_printf(out, "-- Parent table %s\n", zParent); + oputf("-- Parent table %s\n", zParent); sqlite3_free(zPrev); zPrev = sqlite3_mprintf("%s", zParent); } if( res==0 ){ - raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget); + oputf("%s%s --> %s\n", zIndent, zCI, zTarget); }else if( bVerbose ){ - raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n", - zIndent, zFrom, zTarget + oputf("%s/* no extra indexes required for %s -> %s */\n", + zIndent, zFrom, zTarget ); } } @@ -22793,16 +23549,16 @@ static int lintFkeyIndexes( sqlite3_free(zPrev); if( rc!=SQLITE_OK ){ - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + eputf("%s\n", sqlite3_errmsg(db)); } rc2 = sqlite3_finalize(pSql); if( rc==SQLITE_OK && rc2!=SQLITE_OK ){ rc = rc2; - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + eputf("%s\n", sqlite3_errmsg(db)); } }else{ - raw_printf(stderr, "%s\n", sqlite3_errmsg(db)); + eputf("%s\n", sqlite3_errmsg(db)); } return rc; @@ -22822,9 +23578,9 @@ static int lintDotCommand( return lintFkeyIndexes(pState, azArg, nArg); usage: - raw_printf(stderr, "Usage %s sub-command ?switches...?\n", azArg[0]); - raw_printf(stderr, "Where sub-commands are:\n"); - raw_printf(stderr, " fkey-indexes\n"); + eputf("Usage %s sub-command ?switches...?\n", azArg[0]); + eputz("Where sub-commands are:\n"); + eputz(" fkey-indexes\n"); return SQLITE_ERROR; } @@ -22839,9 +23595,7 @@ static void shellPrepare( if( *pRc==SQLITE_OK ){ int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0); if( rc!=SQLITE_OK ){ - raw_printf(stderr, "sql error: %s (%d)\n", - sqlite3_errmsg(db), sqlite3_errcode(db) - ); + eputf("sql error: %s (%d)\n", sqlite3_errmsg(db), sqlite3_errcode(db)); *pRc = rc; } } @@ -22892,7 +23646,7 @@ void shellFinalize( int rc = sqlite3_finalize(pStmt); if( *pRc==SQLITE_OK ){ if( rc!=SQLITE_OK ){ - raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); + eputf("SQL error: %s\n", sqlite3_errmsg(db)); } *pRc = rc; } @@ -22913,7 +23667,7 @@ void shellReset( if( *pRc==SQLITE_OK ){ if( rc!=SQLITE_OK ){ sqlite3 *db = sqlite3_db_handle(pStmt); - raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); + eputf("SQL error: %s\n", sqlite3_errmsg(db)); } *pRc = rc; } @@ -22963,11 +23717,11 @@ static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){ va_start(ap, zFmt); z = sqlite3_vmprintf(zFmt, ap); va_end(ap); - utf8_printf(stderr, "Error: %s\n", z); + eputf("Error: %s\n", z); if( pAr->fromCmdLine ){ - utf8_printf(stderr, "Use \"-A\" for more help\n"); + eputz("Use \"-A\" for more help\n"); }else{ - utf8_printf(stderr, "Use \".archive --help\" for more help\n"); + eputz("Use \".archive --help\" for more help\n"); } sqlite3_free(z); return SQLITE_ERROR; @@ -23067,7 +23821,7 @@ static int arParseCommand( struct ArSwitch *pEnd = &aSwitch[nSwitch]; if( nArg<=1 ){ - utf8_printf(stderr, "Wrong number of arguments. Usage:\n"); + eputz("Wrong number of arguments. Usage:\n"); return arUsage(stderr); }else{ char *z = azArg[1]; @@ -23173,7 +23927,7 @@ static int arParseCommand( } } if( pAr->eCmd==0 ){ - utf8_printf(stderr, "Required argument missing. Usage:\n"); + eputz("Required argument missing. Usage:\n"); return arUsage(stderr); } return SQLITE_OK; @@ -23216,7 +23970,7 @@ static int arCheckEntries(ArCommand *pAr){ } shellReset(&rc, pTest); if( rc==SQLITE_OK && bOk==0 ){ - utf8_printf(stderr, "not found in archive: %s\n", z); + eputf("not found in archive: %s\n", z); rc = SQLITE_ERROR; } } @@ -23283,18 +24037,15 @@ static int arListCommand(ArCommand *pAr){ shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose], pAr->zSrcTable, zWhere); if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); + oputf("%s\n", sqlite3_sql(pSql)); }else{ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ if( pAr->bVerbose ){ - utf8_printf(pAr->p->out, "%s % 10d %s %s\n", - sqlite3_column_text(pSql, 0), - sqlite3_column_int(pSql, 1), - sqlite3_column_text(pSql, 2), - sqlite3_column_text(pSql, 3) - ); + oputf("%s % 10d %s %s\n", + sqlite3_column_text(pSql, 0), sqlite3_column_int(pSql, 1), + sqlite3_column_text(pSql, 2),sqlite3_column_text(pSql, 3)); }else{ - utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); + oputf("%s\n", sqlite3_column_text(pSql, 0)); } } } @@ -23303,7 +24054,6 @@ static int arListCommand(ArCommand *pAr){ return rc; } - /* ** Implementation of .ar "Remove" command. */ @@ -23322,7 +24072,7 @@ static int arRemoveCommand(ArCommand *pAr){ zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;", pAr->zSrcTable, zWhere); if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", zSql); + oputf("%s\n", zSql); }else{ char *zErr = 0; rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0); @@ -23335,7 +24085,7 @@ static int arRemoveCommand(ArCommand *pAr){ } } if( zErr ){ - utf8_printf(stdout, "ERROR: %s\n", zErr); + sputf(stdout, "ERROR: %s\n", zErr); /* stdout? */ sqlite3_free(zErr); } } @@ -23399,11 +24149,11 @@ static int arExtractCommand(ArCommand *pAr){ j = sqlite3_bind_parameter_index(pSql, "$dirOnly"); sqlite3_bind_int(pSql, j, i); if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql)); + oputf("%s\n", sqlite3_sql(pSql)); }else{ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ if( i==0 && pAr->bVerbose ){ - utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0)); + oputf("%s\n", sqlite3_column_text(pSql, 0)); } } } @@ -23423,13 +24173,13 @@ static int arExtractCommand(ArCommand *pAr){ static int arExecSql(ArCommand *pAr, const char *zSql){ int rc; if( pAr->bDryRun ){ - utf8_printf(pAr->p->out, "%s\n", zSql); + oputf("%s\n", zSql); rc = SQLITE_OK; }else{ char *zErr = 0; rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr); if( zErr ){ - utf8_printf(stdout, "ERROR: %s\n", zErr); + sputf(stdout, "ERROR: %s\n", zErr); sqlite3_free(zErr); } } @@ -23604,15 +24354,13 @@ static int arDotCommand( } cmd.db = 0; if( cmd.bDryRun ){ - utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile, - eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); + oputf("-- open database '%s'%s\n", cmd.zFile, + eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : ""); } rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags, eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0); if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "cannot open file: %s (%s)\n", - cmd.zFile, sqlite3_errmsg(cmd.db) - ); + eputf("cannot open file: %s (%s)\n", cmd.zFile, sqlite3_errmsg(cmd.db)); goto end_ar_command; } sqlite3_fileio_init(cmd.db, 0, 0); @@ -23625,7 +24373,7 @@ static int arDotCommand( if( cmd.eCmd!=AR_CMD_CREATE && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0) ){ - utf8_printf(stderr, "database does not contain an 'sqlar' table\n"); + eputz("database does not contain an 'sqlar' table\n"); rc = SQLITE_ERROR; goto end_ar_command; } @@ -23683,7 +24431,7 @@ end_ar_command: */ static int recoverSqlCb(void *pCtx, const char *zSql){ ShellState *pState = (ShellState*)pCtx; - utf8_printf(pState->out, "%s;\n", zSql); + sputf(pState->out, "%s;\n", zSql); return SQLITE_OK; } @@ -23726,7 +24474,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ bRowids = 0; } else{ - utf8_printf(stderr, "unexpected option: %s\n", azArg[i]); + eputf("unexpected option: %s\n", azArg[i]); showHelp(pState->out, azArg[0]); return 1; } @@ -23745,7 +24493,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ if( sqlite3_recover_errcode(p)!=SQLITE_OK ){ const char *zErr = sqlite3_recover_errmsg(p); int errCode = sqlite3_recover_errcode(p); - raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode); + eputf("sql error: %s (%d)\n", zErr, errCode); } rc = sqlite3_recover_finish(p); return rc; @@ -23770,7 +24518,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ #define rc_err_oom_die(rc) \ if( rc==SQLITE_NOMEM ) shell_check_oom(0); \ else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \ - fprintf(stderr,"E:%d\n",rc), assert(0) + eputf("E:%d\n",rc), assert(0) #else static void rc_err_oom_die(int rc){ if( rc==SQLITE_NOMEM ) shell_check_oom(0); @@ -23910,6 +24658,7 @@ FROM (\ sqlite3_exec(*pDb,"drop table if exists ColNames;" "drop view if exists RepeatedNames;",0,0,0); #endif +#undef SHELL_COLFIX_DB rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0); rc_err_oom_die(rc); } @@ -24023,7 +24772,7 @@ static int do_meta_command(char *zLine, ShellState *p){ #ifndef SQLITE_OMIT_AUTHORIZATION if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){ if( nArg!=2 ){ - raw_printf(stderr, "Usage: .auth ON|OFF\n"); + eputz("Usage: .auth ON|OFF\n"); rc = 1; goto meta_command_exit; } @@ -24070,7 +24819,7 @@ static int do_meta_command(char *zLine, ShellState *p){ bAsync = 1; }else { - utf8_printf(stderr, "unknown option: %s\n", azArg[j]); + eputf("unknown option: %s\n", azArg[j]); return 1; } }else if( zDestFile==0 ){ @@ -24079,19 +24828,19 @@ static int do_meta_command(char *zLine, ShellState *p){ zDb = zDestFile; zDestFile = azArg[j]; }else{ - raw_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n"); + eputz("Usage: .backup ?DB? ?OPTIONS? FILENAME\n"); return 1; } } if( zDestFile==0 ){ - raw_printf(stderr, "missing FILENAME argument on .backup\n"); + eputz("missing FILENAME argument on .backup\n"); return 1; } if( zDb==0 ) zDb = "main"; rc = sqlite3_open_v2(zDestFile, &pDest, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs); if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile); + eputf("Error: cannot open \"%s\"\n", zDestFile); close_db(pDest); return 1; } @@ -24102,7 +24851,7 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb); if( pBackup==0 ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); + eputf("Error: %s\n", sqlite3_errmsg(pDest)); close_db(pDest); return 1; } @@ -24111,7 +24860,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( rc==SQLITE_DONE ){ rc = 0; }else{ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); + eputf("Error: %s\n", sqlite3_errmsg(pDest)); rc = 1; } close_db(pDest); @@ -24122,7 +24871,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==2 ){ bail_on_error = booleanValue(azArg[1]); }else{ - raw_printf(stderr, "Usage: .bail on|off\n"); + eputz("Usage: .bail on|off\n"); rc = 1; } }else @@ -24136,9 +24885,8 @@ static int do_meta_command(char *zLine, ShellState *p){ setTextMode(p->out, 1); } }else{ - raw_printf(stderr, "The \".binary\" command is deprecated." - " Use \".crnl\" instead.\n"); - raw_printf(stderr, "Usage: .binary on|off\n"); + eputz("The \".binary\" command is deprecated. Use \".crnl\" instead.\n" + "Usage: .binary on|off\n"); rc = 1; } }else @@ -24162,11 +24910,11 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = chdir(azArg[1]); #endif if( rc ){ - utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]); + eputf("Cannot change to directory \"%s\"\n", azArg[1]); rc = 1; } }else{ - raw_printf(stderr, "Usage: .cd DIRECTORY\n"); + eputz("Usage: .cd DIRECTORY\n"); rc = 1; } }else @@ -24176,7 +24924,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==2 ){ setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); }else{ - raw_printf(stderr, "Usage: .changes on|off\n"); + eputz("Usage: .changes on|off\n"); rc = 1; } }else @@ -24190,17 +24938,16 @@ static int do_meta_command(char *zLine, ShellState *p){ char *zRes = 0; output_reset(p); if( nArg!=2 ){ - raw_printf(stderr, "Usage: .check GLOB-PATTERN\n"); + eputz("Usage: .check GLOB-PATTERN\n"); rc = 2; }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ rc = 2; }else if( testcase_glob(azArg[1],zRes)==0 ){ - utf8_printf(stderr, - "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", - p->zTestcase, azArg[1], zRes); + eputf("testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", + p->zTestcase, azArg[1], zRes); rc = 1; }else{ - utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase); + oputf("testcase-%s ok\n", p->zTestcase); p->nCheck++; } sqlite3_free(zRes); @@ -24213,7 +24960,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==2 ){ tryToClone(p, azArg[1]); }else{ - raw_printf(stderr, "Usage: .clone FILENAME\n"); + eputz("Usage: .clone FILENAME\n"); rc = 1; } }else @@ -24233,9 +24980,9 @@ static int do_meta_command(char *zLine, ShellState *p){ zFile = "(temporary-file)"; } if( p->pAuxDb == &p->aAuxDb[i] ){ - utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile); + sputf(stdout, "ACTIVE %d: %s\n", i, zFile); }else if( p->aAuxDb[i].db!=0 ){ - utf8_printf(stdout, " %d: %s\n", i, zFile); + sputf(stdout, " %d: %s\n", i, zFile); } } }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){ @@ -24252,7 +24999,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( i<0 || i>=ArraySize(p->aAuxDb) ){ /* No-op */ }else if( p->pAuxDb == &p->aAuxDb[i] ){ - raw_printf(stderr, "cannot close the active database connection\n"); + eputz("cannot close the active database connection\n"); rc = 1; }else if( p->aAuxDb[i].db ){ session_close_all(p, i); @@ -24260,7 +25007,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->aAuxDb[i].db = 0; } }else{ - raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n"); + eputz("Usage: .connection [close] [CONNECTION-NUMBER]\n"); rc = 1; } }else @@ -24274,9 +25021,9 @@ static int do_meta_command(char *zLine, ShellState *p){ } }else{ #if !defined(_WIN32) && !defined(WIN32) - raw_printf(stderr, "The \".crnl\" is a no-op on non-Windows machines.\n"); + eputz("The \".crnl\" is a no-op on non-Windows machines.\n"); #endif - raw_printf(stderr, "Usage: .crnl on|off\n"); + eputz("Usage: .crnl on|off\n"); rc = 1; } }else @@ -24289,7 +25036,7 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + eputf("Error: %s\n", sqlite3_errmsg(p->db)); rc = 1; }else{ while( sqlite3_step(pStmt)==SQLITE_ROW ){ @@ -24308,11 +25055,9 @@ static int do_meta_command(char *zLine, ShellState *p){ int eTxn = sqlite3_txn_state(p->db, azName[i*2]); int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]); const char *z = azName[i*2+1]; - utf8_printf(p->out, "%s: %s %s%s\n", - azName[i*2], - z && z[0] ? z : "\"\"", - bRdonly ? "r/o" : "r/w", - eTxn==SQLITE_TXN_NONE ? "" : + oputf("%s: %s %s%s\n", + azName[i*2], z && z[0] ? z : "\"\"", bRdonly ? "r/o" : "r/w", + eTxn==SQLITE_TXN_NONE ? "" : eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn"); free(azName[i*2]); free(azName[i*2+1]); @@ -24352,12 +25097,12 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); } sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v); - utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off"); + oputf("%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off"); if( nArg>1 ) break; } if( nArg>1 && ii==ArraySize(aDbConfig) ){ - utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]); - utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n"); + eputf("Error: unknown dbconfig \"%s\"\n", azArg[1]); + eputz("Enter \".dbconfig\" with no arguments for a list\n"); } }else @@ -24387,8 +25132,8 @@ static int do_meta_command(char *zLine, ShellState *p){ if( z[0]=='-' ) z++; if( cli_strcmp(z,"preserve-rowids")==0 ){ #ifdef SQLITE_OMIT_VIRTUALTABLE - raw_printf(stderr, "The --preserve-rowids option is not compatible" - " with SQLITE_OMIT_VIRTUALTABLE\n"); + eputz("The --preserve-rowids option is not compatible" + " with SQLITE_OMIT_VIRTUALTABLE\n"); rc = 1; sqlite3_free(zLike); goto meta_command_exit; @@ -24406,7 +25151,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ShellSetFlag(p, SHFLG_DumpNoSys); }else { - raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]); + eputf("Unknown option \"%s\" on \".dump\"\n", azArg[i]); rc = 1; sqlite3_free(zLike); goto meta_command_exit; @@ -24440,8 +25185,8 @@ static int do_meta_command(char *zLine, ShellState *p){ /* When playing back a "dump", the content might appear in an order ** which causes immediate foreign key constraints to be violated. ** So disable foreign-key constraint enforcement to prevent problems. */ - raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n"); - raw_printf(p->out, "BEGIN TRANSACTION;\n"); + oputz("PRAGMA foreign_keys=OFF;\n"); + oputz("BEGIN TRANSACTION;\n"); } p->writableSchema = 0; p->showHeader = 0; @@ -24472,13 +25217,13 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_free(zLike); if( p->writableSchema ){ - raw_printf(p->out, "PRAGMA writable_schema=OFF;\n"); + oputz("PRAGMA writable_schema=OFF;\n"); p->writableSchema = 0; } sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n"); + oputz(p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n"); } p->showHeader = savedShowHeader; p->shellFlgs = savedShellFlags; @@ -24488,7 +25233,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==2 ){ setOrClearFlag(p, SHFLG_Echo, azArg[1]); }else{ - raw_printf(stderr, "Usage: .echo on|off\n"); + eputz("Usage: .echo on|off\n"); rc = 1; } }else @@ -24519,7 +25264,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->autoEQP = (u8)booleanValue(azArg[1]); } }else{ - raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n"); + eputz("Usage: .eqp off|on|trace|trigger|full\n"); rc = 1; } }else @@ -24558,9 +25303,8 @@ static int do_meta_command(char *zLine, ShellState *p){ #ifndef SQLITE_OMIT_VIRTUALTABLE if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){ if( p->bSafeMode ){ - raw_printf(stderr, - "Cannot run experimental commands such as \"%s\" in safe mode\n", - azArg[0]); + eputf("Cannot run experimental commands such as \"%s\" in safe mode\n", + azArg[0]); rc = 1; }else{ open_db(p, 0); @@ -24616,10 +25360,9 @@ static int do_meta_command(char *zLine, ShellState *p){ /* --help lists all file-controls */ if( cli_strcmp(zCmd,"help")==0 ){ - utf8_printf(p->out, "Available file-controls:\n"); + oputz("Available file-controls:\n"); for(i=0; iout, " .filectrl %s %s\n", - aCtrl[i].zCtrlName, aCtrl[i].zUsage); + oputf(" .filectrl %s %s\n", aCtrl[i].zCtrlName, aCtrl[i].zUsage); } rc = 1; goto meta_command_exit; @@ -24634,16 +25377,16 @@ static int do_meta_command(char *zLine, ShellState *p){ filectrl = aCtrl[i].ctrlCode; iCtrl = i; }else{ - utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n" - "Use \".filectrl --help\" for help\n", zCmd); + eputf("Error: ambiguous file-control: \"%s\"\n" + "Use \".filectrl --help\" for help\n", zCmd); rc = 1; goto meta_command_exit; } } } if( filectrl<0 ){ - utf8_printf(stderr,"Error: unknown file-control: %s\n" - "Use \".filectrl --help\" for help\n", zCmd); + eputf("Error: unknown file-control: %s\n" + "Use \".filectrl --help\" for help\n", zCmd); }else{ switch(filectrl){ case SQLITE_FCNTL_SIZE_LIMIT: { @@ -24686,7 +25429,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg!=2 ) break; sqlite3_file_control(p->db, zSchema, filectrl, &z); if( z ){ - utf8_printf(p->out, "%s\n", z); + oputf("%s\n", z); sqlite3_free(z); } isOk = 2; @@ -24700,19 +25443,19 @@ static int do_meta_command(char *zLine, ShellState *p){ } x = -1; sqlite3_file_control(p->db, zSchema, filectrl, &x); - utf8_printf(p->out,"%d\n", x); + oputf("%d\n", x); isOk = 2; break; } } } if( isOk==0 && iCtrl>=0 ){ - utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); + oputf("Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); rc = 1; }else if( isOk==1 ){ char zBuf[100]; sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes); - raw_printf(p->out, "%s\n", zBuf); + oputf("%s\n", zBuf); } }else @@ -24727,7 +25470,7 @@ static int do_meta_command(char *zLine, ShellState *p){ nArg = 1; } if( nArg!=1 ){ - raw_printf(stderr, "Usage: .fullschema ?--indent?\n"); + eputz("Usage: .fullschema ?--indent?\n"); rc = 1; goto meta_command_exit; } @@ -24753,15 +25496,15 @@ static int do_meta_command(char *zLine, ShellState *p){ } } if( doStats==0 ){ - raw_printf(p->out, "/* No STAT tables available */\n"); + oputz("/* No STAT tables available */\n"); }else{ - raw_printf(p->out, "ANALYZE sqlite_schema;\n"); + oputz("ANALYZE sqlite_schema;\n"); data.cMode = data.mode = MODE_Insert; data.zDestTable = "sqlite_stat1"; shell_exec(&data, "SELECT * FROM sqlite_stat1", 0); data.zDestTable = "sqlite_stat4"; shell_exec(&data, "SELECT * FROM sqlite_stat4", 0); - raw_printf(p->out, "ANALYZE sqlite_schema;\n"); + oputz("ANALYZE sqlite_schema;\n"); } }else @@ -24770,7 +25513,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->showHeader = booleanValue(azArg[1]); p->shellFlgs |= SHFLG_HeaderSet; }else{ - raw_printf(stderr, "Usage: .headers on|off\n"); + eputz("Usage: .headers on|off\n"); rc = 1; } }else @@ -24779,7 +25522,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg>=2 ){ n = showHelp(p->out, azArg[1]); if( n==0 ){ - utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]); + oputf("Nothing matches '%s'\n", azArg[1]); } }else{ showHelp(p->out, 0); @@ -24823,7 +25566,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( zTable==0 ){ zTable = z; }else{ - utf8_printf(p->out, "ERROR: extra argument: \"%s\". Usage:\n", z); + oputf("ERROR: extra argument: \"%s\". Usage:\n", z); showHelp(p->out, "import"); goto meta_command_exit; } @@ -24844,14 +25587,14 @@ static int do_meta_command(char *zLine, ShellState *p){ xRead = csv_read_one_field; useOutputMode = 0; }else{ - utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", z); + oputf("ERROR: unknown option: \"%s\". Usage:\n", z); showHelp(p->out, "import"); goto meta_command_exit; } } if( zTable==0 ){ - utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n", - zFile==0 ? "FILE" : "TABLE"); + oputf("ERROR: missing %s argument. Usage:\n", + zFile==0 ? "FILE" : "TABLE"); showHelp(p->out, "import"); goto meta_command_exit; } @@ -24862,20 +25605,17 @@ static int do_meta_command(char *zLine, ShellState *p){ ** the column and row separator characters from the output mode. */ nSep = strlen30(p->colSeparator); if( nSep==0 ){ - raw_printf(stderr, - "Error: non-null column separator required for import\n"); + eputz("Error: non-null column separator required for import\n"); goto meta_command_exit; } if( nSep>1 ){ - raw_printf(stderr, - "Error: multi-character column separators not allowed" + eputz("Error: multi-character column separators not allowed" " for import\n"); goto meta_command_exit; } nSep = strlen30(p->rowSeparator); if( nSep==0 ){ - raw_printf(stderr, - "Error: non-null row separator required for import\n"); + eputz("Error: non-null row separator required for import\n"); goto meta_command_exit; } if( nSep==2 && p->mode==MODE_Csv @@ -24889,8 +25629,8 @@ static int do_meta_command(char *zLine, ShellState *p){ nSep = strlen30(p->rowSeparator); } if( nSep>1 ){ - raw_printf(stderr, "Error: multi-character row separators not allowed" - " for import\n"); + eputz("Error: multi-character row separators not allowed" + " for import\n"); goto meta_command_exit; } sCtx.cColSep = (u8)p->colSeparator[0]; @@ -24900,7 +25640,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sCtx.nLine = 1; if( sCtx.zFile[0]=='|' ){ #ifdef SQLITE_OMIT_POPEN - raw_printf(stderr, "Error: pipes are not supported in this OS\n"); + eputz("Error: pipes are not supported in this OS\n"); goto meta_command_exit; #else sCtx.in = popen(sCtx.zFile+1, "r"); @@ -24912,19 +25652,19 @@ static int do_meta_command(char *zLine, ShellState *p){ sCtx.xCloser = fclose; } if( sCtx.in==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile); + eputf("Error: cannot open \"%s\"\n", zFile); goto meta_command_exit; } if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){ char zSep[2]; zSep[1] = 0; zSep[0] = sCtx.cColSep; - utf8_printf(p->out, "Column separator "); - output_c_string(p->out, zSep); - utf8_printf(p->out, ", row separator "); + oputz("Column separator "); + output_c_string(zSep); + oputz(", row separator "); zSep[0] = sCtx.cRowSep; - output_c_string(p->out, zSep); - utf8_printf(p->out, "\n"); + output_c_string(zSep); + oputz("\n"); } sCtx.z = sqlite3_malloc64(120); if( sCtx.z==0 ){ @@ -24959,14 +25699,14 @@ static int do_meta_command(char *zLine, ShellState *p){ } zColDefs = zAutoColumn(0, &dbCols, &zRenames); if( zRenames!=0 ){ - utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr, - "Columns renamed during .import %s due to duplicates:\n" - "%s\n", sCtx.zFile, zRenames); + sputf((stdin_is_interactive && p->in==stdin)? p->out : stderr, + "Columns renamed during .import %s due to duplicates:\n" + "%s\n", sCtx.zFile, zRenames); sqlite3_free(zRenames); } assert(dbCols==0); if( zColDefs==0 ){ - utf8_printf(stderr,"%s: empty file\n", sCtx.zFile); + eputf("%s: empty file\n", sCtx.zFile); import_fail: sqlite3_free(zCreate); sqlite3_free(zSql); @@ -24977,11 +25717,11 @@ static int do_meta_command(char *zLine, ShellState *p){ } zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs); if( eVerbose>=1 ){ - utf8_printf(p->out, "%s\n", zCreate); + oputf("%s\n", zCreate); } rc = sqlite3_exec(p->db, zCreate, 0, 0, 0); if( rc ){ - utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); + eputf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db)); goto import_fail; } sqlite3_free(zCreate); @@ -24990,7 +25730,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } if( rc ){ if (pStmt) sqlite3_finalize(pStmt); - utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db)); + eputf("Error: %s\n", sqlite3_errmsg(p->db)); goto import_fail; } sqlite3_free(zSql); @@ -25012,11 +25752,11 @@ static int do_meta_command(char *zLine, ShellState *p){ zSql[j++] = ')'; zSql[j] = 0; if( eVerbose>=2 ){ - utf8_printf(p->out, "Insert using: %s\n", zSql); + oputf("Insert using: %s\n", zSql); } rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + eputf("Error: %s\n", sqlite3_errmsg(p->db)); if (pStmt) sqlite3_finalize(pStmt); goto import_fail; } @@ -25049,9 +25789,9 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); if( i=nCol ){ sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile, - startLine, sqlite3_errmsg(p->db)); + eputf("%s:%d: INSERT failed: %s\n", + sCtx.zFile, startLine, sqlite3_errmsg(p->db)); sCtx.nErr++; }else{ sCtx.nRow++; @@ -25082,9 +25821,8 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_finalize(pStmt); if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); if( eVerbose>0 ){ - utf8_printf(p->out, - "Added %d rows with %d errors using %d lines of input\n", - sCtx.nRow, sCtx.nErr, sCtx.nLine-1); + oputf("Added %d rows with %d errors using %d lines of input\n", + sCtx.nRow, sCtx.nErr, sCtx.nLine-1); } }else #endif /* !defined(SQLITE_SHELL_FIDDLE) */ @@ -25099,14 +25837,14 @@ static int do_meta_command(char *zLine, ShellState *p){ int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */ int i; if( !ShellHasFlag(p,SHFLG_TestingMode) ){ - utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n", - "imposter"); + eputf(".%s unavailable without --unsafe-testing\n", + "imposter"); rc = 1; goto meta_command_exit; } if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){ - utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n" - " .imposter off\n"); + eputz("Usage: .imposter INDEX IMPOSTER\n" + " .imposter off\n"); /* Also allowed, but not documented: ** ** .imposter TABLE IMPOSTER @@ -25165,7 +25903,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } sqlite3_finalize(pStmt); if( i==0 || tnum==0 ){ - utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]); + eputf("no such index: \"%s\"\n", azArg[1]); rc = 1; sqlite3_free(zCollist); goto meta_command_exit; @@ -25180,16 +25918,14 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = sqlite3_exec(p->db, zSql, 0, 0, 0); sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0); if( rc ){ - utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); + eputf("Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db)); }else{ - utf8_printf(stdout, "%s;\n", zSql); - raw_printf(stdout, - "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n", - azArg[1], isWO ? "table" : "index" - ); + sputf(stdout, "%s;\n", zSql); + sputf(stdout, "WARNING: writing to an imposter table will corrupt" + " the \"%s\" %s!\n", azArg[1], isWO ? "table" : "index"); } }else{ - raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); + eputf("SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc); rc = 1; } sqlite3_free(zSql); @@ -25209,7 +25945,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else{ iotrace = fopen(azArg[1], "w"); if( iotrace==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]); + eputf("Error: cannot open \"%s\"\n", azArg[1]); sqlite3IoTrace = 0; rc = 1; }else{ @@ -25241,11 +25977,11 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); if( nArg==1 ){ for(i=0; idb, aLimit[i].limitCode, -1)); + sputf(stdout, "%20s %d\n", aLimit[i].zLimitName, + sqlite3_limit(p->db, aLimit[i].limitCode, -1)); } }else if( nArg>3 ){ - raw_printf(stderr, "Usage: .limit NAME ?NEW-VALUE?\n"); + eputz("Usage: .limit NAME ?NEW-VALUE?\n"); rc = 1; goto meta_command_exit; }else{ @@ -25256,16 +25992,16 @@ static int do_meta_command(char *zLine, ShellState *p){ if( iLimit<0 ){ iLimit = i; }else{ - utf8_printf(stderr, "ambiguous limit: \"%s\"\n", azArg[1]); + eputf("ambiguous limit: \"%s\"\n", azArg[1]); rc = 1; goto meta_command_exit; } } } if( iLimit<0 ){ - utf8_printf(stderr, "unknown limit: \"%s\"\n" - "enter \".limits\" with no arguments for a list.\n", - azArg[1]); + eputf("unknown limit: \"%s\"\n" + "enter \".limits\" with no arguments for a list.\n", + azArg[1]); rc = 1; goto meta_command_exit; } @@ -25273,8 +26009,8 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_limit(p->db, aLimit[iLimit].limitCode, (int)integerValue(azArg[2])); } - printf("%20s %d\n", aLimit[iLimit].zLimitName, - sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); + sputf(stdout, "%20s %d\n", aLimit[iLimit].zLimitName, + sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1)); } }else @@ -25290,7 +26026,7 @@ static int do_meta_command(char *zLine, ShellState *p){ failIfSafeMode(p, "cannot run .load in safe mode"); if( nArg<2 || azArg[1][0]==0 ){ /* Must have a non-empty FILE. (Will not load self.) */ - raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n"); + eputz("Usage: .load FILE ?ENTRYPOINT?\n"); rc = 1; goto meta_command_exit; } @@ -25299,7 +26035,7 @@ static int do_meta_command(char *zLine, ShellState *p){ open_db(p, 0); rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg); if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "Error: %s\n", zErrMsg); + eputf("Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); rc = 1; } @@ -25308,7 +26044,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='l' && cli_strncmp(azArg[0], "log", n)==0 ){ if( nArg!=2 ){ - raw_printf(stderr, "Usage: .log FILENAME\n"); + eputz("Usage: .log FILENAME\n"); rc = 1; }else{ const char *zFile = azArg[1]; @@ -25316,8 +26052,8 @@ static int do_meta_command(char *zLine, ShellState *p){ && cli_strcmp(zFile,"on")!=0 && cli_strcmp(zFile,"off")!=0 ){ - raw_printf(stdout, "cannot set .log to anything other " - "than \"on\" or \"off\"\n"); + sputz(stdout, "cannot set .log to anything other" + " than \"on\" or \"off\"\n"); zFile = "off"; } output_file_close(p->pLog); @@ -25356,17 +26092,17 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( zTabname==0 ){ zTabname = z; }else if( z[0]=='-' ){ - utf8_printf(stderr, "unknown option: %s\n", z); - utf8_printf(stderr, "options:\n" - " --noquote\n" - " --quote\n" - " --wordwrap on/off\n" - " --wrap N\n" - " --ww\n"); + eputf("unknown option: %s\n", z); + eputz("options:\n" + " --noquote\n" + " --quote\n" + " --wordwrap on/off\n" + " --wrap N\n" + " --ww\n"); rc = 1; goto meta_command_exit; }else{ - utf8_printf(stderr, "extra argument: \"%s\"\n", z); + eputf("extra argument: \"%s\"\n", z); rc = 1; goto meta_command_exit; } @@ -25375,14 +26111,12 @@ static int do_meta_command(char *zLine, ShellState *p){ if( p->mode==MODE_Column || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) ){ - raw_printf - (p->out, - "current output mode: %s --wrap %d --wordwrap %s --%squote\n", - modeDescr[p->mode], p->cmOpts.iWrap, - p->cmOpts.bWordWrap ? "on" : "off", - p->cmOpts.bQuote ? "" : "no"); + oputf("current output mode: %s --wrap %d --wordwrap %s --%squote\n", + modeDescr[p->mode], p->cmOpts.iWrap, + p->cmOpts.bWordWrap ? "on" : "off", + p->cmOpts.bQuote ? "" : "no"); }else{ - raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]); + oputf("current output mode: %s\n", modeDescr[p->mode]); } zMode = modeDescr[p->mode]; } @@ -25441,9 +26175,9 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( cli_strncmp(zMode,"json",n2)==0 ){ p->mode = MODE_Json; }else{ - raw_printf(stderr, "Error: mode should be one of: " - "ascii box column csv html insert json line list markdown " - "qbox quote table tabs tcl\n"); + eputz("Error: mode should be one of: " + "ascii box column csv html insert json line list markdown " + "qbox quote table tabs tcl\n"); rc = 1; } p->cMode = p->mode; @@ -25452,11 +26186,11 @@ static int do_meta_command(char *zLine, ShellState *p){ #ifndef SQLITE_SHELL_FIDDLE if( c=='n' && cli_strcmp(azArg[0], "nonce")==0 ){ if( nArg!=2 ){ - raw_printf(stderr, "Usage: .nonce NONCE\n"); + eputz("Usage: .nonce NONCE\n"); rc = 1; }else if( p->zNonce==0 || cli_strcmp(azArg[1],p->zNonce)!=0 ){ - raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n", - p->lineno, azArg[1]); + eputf("line %d: incorrect nonce: \"%s\"\n", + p->lineno, azArg[1]); exit(1); }else{ p->bSafeMode = 0; @@ -25471,7 +26205,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_snprintf(sizeof(p->nullValue), p->nullValue, "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]); }else{ - raw_printf(stderr, "Usage: .nullvalue STRING\n"); + eputz("Usage: .nullvalue STRING\n"); rc = 1; } }else @@ -25510,11 +26244,11 @@ static int do_meta_command(char *zLine, ShellState *p){ }else #endif /* !SQLITE_SHELL_FIDDLE */ if( z[0]=='-' ){ - utf8_printf(stderr, "unknown option: %s\n", z); + eputf("unknown option: %s\n", z); rc = 1; goto meta_command_exit; }else if( zFN ){ - utf8_printf(stderr, "extra argument: \"%s\"\n", z); + eputf("extra argument: \"%s\"\n", z); rc = 1; goto meta_command_exit; }else{ @@ -25556,7 +26290,7 @@ static int do_meta_command(char *zLine, ShellState *p){ p->pAuxDb->zDbFilename = zNewFilename; open_db(p, OPEN_DB_KEEPALIVE); if( p->db==0 ){ - utf8_printf(stderr, "Error: cannot open '%s'\n", zNewFilename); + eputf("Error: cannot open '%s'\n", zNewFilename); sqlite3_free(zNewFilename); }else{ p->pAuxDb->zFreeOnClose = zNewFilename; @@ -25580,9 +26314,9 @@ static int do_meta_command(char *zLine, ShellState *p){ int i; int eMode = 0; int bOnce = 0; /* 0: .output, 1: .once, 2: .excel */ - unsigned char zBOM[4]; /* Byte-order mark to using if --bom is present */ + static const char *zBomUtf8 = "\xef\xbb\xbf"; + const char *zBom = 0; - zBOM[0] = 0; failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); if( c=='e' ){ eMode = 'x'; @@ -25595,17 +26329,13 @@ static int do_meta_command(char *zLine, ShellState *p){ if( z[0]=='-' ){ if( z[1]=='-' ) z++; if( cli_strcmp(z,"-bom")==0 ){ - zBOM[0] = 0xef; - zBOM[1] = 0xbb; - zBOM[2] = 0xbf; - zBOM[3] = 0; + zBom = zBomUtf8; }else if( c!='e' && cli_strcmp(z,"-x")==0 ){ eMode = 'x'; /* spreadsheet */ }else if( c!='e' && cli_strcmp(z,"-e")==0 ){ eMode = 'e'; /* text editor */ }else{ - utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", - azArg[i]); + oputf("ERROR: unknown option: \"%s\". Usage:\n", azArg[i]); showHelp(p->out, azArg[0]); rc = 1; goto meta_command_exit; @@ -25617,8 +26347,7 @@ static int do_meta_command(char *zLine, ShellState *p){ break; } }else{ - utf8_printf(p->out,"ERROR: extra parameter: \"%s\". Usage:\n", - azArg[i]); + oputf("ERROR: extra parameter: \"%s\". Usage:\n", azArg[i]); showHelp(p->out, azArg[0]); rc = 1; sqlite3_free(zFile); @@ -25657,30 +26386,30 @@ static int do_meta_command(char *zLine, ShellState *p){ shell_check_oom(zFile); if( zFile[0]=='|' ){ #ifdef SQLITE_OMIT_POPEN - raw_printf(stderr, "Error: pipes are not supported in this OS\n"); + eputz("Error: pipes are not supported in this OS\n"); rc = 1; - p->out = stdout; + output_redir(p, stdout); #else - p->out = popen(zFile + 1, "w"); - if( p->out==0 ){ - utf8_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1); - p->out = stdout; + FILE *pfPipe = popen(zFile + 1, "w"); + if( pfPipe==0 ){ + eputf("Error: cannot open pipe \"%s\"\n", zFile + 1); rc = 1; }else{ - if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out); + output_redir(p, pfPipe); + if( zBom ) oputz(zBom); sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); } #endif }else{ - p->out = output_file_open(zFile, bTxtMode); - if( p->out==0 ){ + FILE *pfFile = output_file_open(zFile, bTxtMode); + if( pfFile==0 ){ if( cli_strcmp(zFile,"off")!=0 ){ - utf8_printf(stderr,"Error: cannot write to \"%s\"\n", zFile); + eputf("Error: cannot write to \"%s\"\n", zFile); } - p->out = stdout; rc = 1; } else { - if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out); + output_redir(p, pfFile); + if( zBom ) oputz(zBom); sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile); } } @@ -25721,8 +26450,8 @@ static int do_meta_command(char *zLine, ShellState *p){ "SELECT key, quote(value) " "FROM temp.sqlite_parameters;", -1, &pStmt, 0); while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), - sqlite3_column_text(pStmt,1)); + oputf("%-*s %s\n", len, sqlite3_column_text(pStmt,0), + sqlite3_column_text(pStmt,1)); } sqlite3_finalize(pStmt); } @@ -25766,7 +26495,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); sqlite3_free(zSql); if( rx!=SQLITE_OK ){ - utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db)); + oputf("Error: %s\n", sqlite3_errmsg(p->db)); sqlite3_finalize(pStmt); pStmt = 0; rc = 1; @@ -25795,10 +26524,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='p' && n>=3 && cli_strncmp(azArg[0], "print", n)==0 ){ int i; for(i=1; i1 ) raw_printf(p->out, " "); - utf8_printf(p->out, "%s", azArg[i]); + if( i>1 ) oputz(" "); + oputz(azArg[i]); } - raw_printf(p->out, "\n"); + oputz("\n"); }else #ifndef SQLITE_OMIT_PROGRESS_CALLBACK @@ -25827,7 +26556,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } if( cli_strcmp(z,"limit")==0 ){ if( i+1>=nArg ){ - utf8_printf(stderr, "Error: missing argument on --limit\n"); + eputz("Error: missing argument on --limit\n"); rc = 1; goto meta_command_exit; }else{ @@ -25835,7 +26564,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } continue; } - utf8_printf(stderr, "Error: unknown option: \"%s\"\n", azArg[i]); + eputf("Error: unknown option: \"%s\"\n", azArg[i]); rc = 1; goto meta_command_exit; }else{ @@ -25868,19 +26597,19 @@ static int do_meta_command(char *zLine, ShellState *p){ int savedLineno = p->lineno; failIfSafeMode(p, "cannot run .read in safe mode"); if( nArg!=2 ){ - raw_printf(stderr, "Usage: .read FILE\n"); + eputz("Usage: .read FILE\n"); rc = 1; goto meta_command_exit; } if( azArg[1][0]=='|' ){ #ifdef SQLITE_OMIT_POPEN - raw_printf(stderr, "Error: pipes are not supported in this OS\n"); + eputz("Error: pipes are not supported in this OS\n"); rc = 1; p->out = stdout; #else p->in = popen(azArg[1]+1, "r"); if( p->in==0 ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]); + eputf("Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ rc = process_input(p); @@ -25888,7 +26617,7 @@ static int do_meta_command(char *zLine, ShellState *p){ } #endif }else if( (p->in = openChrSource(azArg[1]))==0 ){ - utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); + eputf("Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ rc = process_input(p); @@ -25915,20 +26644,20 @@ static int do_meta_command(char *zLine, ShellState *p){ zSrcFile = azArg[2]; zDb = azArg[1]; }else{ - raw_printf(stderr, "Usage: .restore ?DB? FILE\n"); + eputz("Usage: .restore ?DB? FILE\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_open(zSrcFile, &pSrc); if( rc!=SQLITE_OK ){ - utf8_printf(stderr, "Error: cannot open \"%s\"\n", zSrcFile); + eputf("Error: cannot open \"%s\"\n", zSrcFile); close_db(pSrc); return 1; } open_db(p, 0); pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main"); if( pBackup==0 ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + eputf("Error: %s\n", sqlite3_errmsg(p->db)); close_db(pSrc); return 1; } @@ -25943,10 +26672,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( rc==SQLITE_DONE ){ rc = 0; }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){ - raw_printf(stderr, "Error: source database is busy\n"); + eputz("Error: source database is busy\n"); rc = 1; }else{ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + eputf("Error: %s\n", sqlite3_errmsg(p->db)); rc = 1; } close_db(pSrc); @@ -25967,11 +26696,15 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_db_config( p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0 ); -#ifndef SQLITE_ENABLE_STMT_SCANSTATUS - raw_printf(stderr, "Warning: .scanstats not available in this build.\n"); +#if !defined(SQLITE_ENABLE_STMT_SCANSTATUS) + eputz("Warning: .scanstats not available in this build.\n"); +#elif !defined(SQLITE_ENABLE_BYTECODE_VTAB) + if( p->scanstatsOn==3 ){ + eputz("Warning: \".scanstats vm\" not available in this build.\n"); + } #endif }else{ - raw_printf(stderr, "Usage: .scanstats on|off|est\n"); + eputz("Usage: .scanstats on|off|est\n"); rc = 1; } }else @@ -26000,14 +26733,13 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( optionMatch(azArg[ii],"nosys") ){ bNoSystemTabs = 1; }else if( azArg[ii][0]=='-' ){ - utf8_printf(stderr, "Unknown option: \"%s\"\n", azArg[ii]); + eputf("Unknown option: \"%s\"\n", azArg[ii]); rc = 1; goto meta_command_exit; }else if( zName==0 ){ zName = azArg[ii]; }else{ - raw_printf(stderr, - "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n"); + eputz("Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n"); rc = 1; goto meta_command_exit; } @@ -26040,7 +26772,7 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list", -1, &pStmt, 0); if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + eputf("Error: %s\n", sqlite3_errmsg(p->db)); sqlite3_finalize(pStmt); rc = 1; goto meta_command_exit; @@ -26102,18 +26834,18 @@ static int do_meta_command(char *zLine, ShellState *p){ appendText(&sSelect, "sql IS NOT NULL" " ORDER BY snum, rowid", 0); if( bDebug ){ - utf8_printf(p->out, "SQL: %s;\n", sSelect.z); + oputf("SQL: %s;\n", sSelect.z); }else{ rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg); } freeText(&sSelect); } if( zErrMsg ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); + eputf("Error: %s\n", zErrMsg); sqlite3_free(zErrMsg); rc = 1; }else if( rc != SQLITE_OK ){ - raw_printf(stderr,"Error: querying schema information\n"); + eputz("Error: querying schema information\n"); rc = 1; }else{ rc = 0; @@ -26159,11 +26891,11 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nCmd!=2 ) goto session_syntax_error; if( pSession->p==0 ){ session_not_open: - raw_printf(stderr, "ERROR: No sessions are open\n"); + eputz("ERROR: No sessions are open\n"); }else{ rc = sqlite3session_attach(pSession->p, azCmd[1]); if( rc ){ - raw_printf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc); + eputf("ERROR: sqlite3session_attach() returns %d\n",rc); rc = 0; } } @@ -26182,8 +26914,8 @@ static int do_meta_command(char *zLine, ShellState *p){ if( pSession->p==0 ) goto session_not_open; out = fopen(azCmd[1], "wb"); if( out==0 ){ - utf8_printf(stderr, "ERROR: cannot open \"%s\" for writing\n", - azCmd[1]); + eputf("ERROR: cannot open \"%s\" for writing\n", + azCmd[1]); }else{ int szChng; void *pChng; @@ -26193,13 +26925,12 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = sqlite3session_patchset(pSession->p, &szChng, &pChng); } if( rc ){ - printf("Error: error code %d\n", rc); + sputf(stdout, "Error: error code %d\n", rc); rc = 0; } if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){ - raw_printf(stderr, "ERROR: Failed to write entire %d-byte output\n", - szChng); + eputf("ERROR: Failed to write entire %d-byte output\n", szChng); } sqlite3_free(pChng); fclose(out); @@ -26226,8 +26957,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); if( pAuxDb->nSession ){ ii = sqlite3session_enable(pSession->p, ii); - utf8_printf(p->out, "session %s enable flag = %d\n", - pSession->zName, ii); + oputf("session %s enable flag = %d\n", pSession->zName, ii); } }else @@ -26244,10 +26974,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_free(pSession->azFilter); nByte = sizeof(pSession->azFilter[0])*(nCmd-1); pSession->azFilter = sqlite3_malloc( nByte ); - if( pSession->azFilter==0 ){ - raw_printf(stderr, "Error: out or memory\n"); - exit(1); - } + shell_check_oom( pSession->azFilter ); for(ii=1; iiazFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]); shell_check_oom(x); @@ -26265,8 +26992,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ii = nCmd==1 ? -1 : booleanValue(azCmd[1]); if( pAuxDb->nSession ){ ii = sqlite3session_indirect(pSession->p, ii); - utf8_printf(p->out, "session %s indirect flag = %d\n", - pSession->zName, ii); + oputf("session %s indirect flag = %d\n", pSession->zName, ii); } }else @@ -26278,8 +27004,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nCmd!=1 ) goto session_syntax_error; if( pAuxDb->nSession ){ ii = sqlite3session_isempty(pSession->p); - utf8_printf(p->out, "session %s isempty flag = %d\n", - pSession->zName, ii); + oputf("session %s isempty flag = %d\n", pSession->zName, ii); } }else @@ -26288,7 +27013,7 @@ static int do_meta_command(char *zLine, ShellState *p){ */ if( cli_strcmp(azCmd[0],"list")==0 ){ for(i=0; inSession; i++){ - utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName); + oputf("%d %s\n", i, pAuxDb->aSession[i].zName); } }else @@ -26303,19 +27028,18 @@ static int do_meta_command(char *zLine, ShellState *p){ if( zName[0]==0 ) goto session_syntax_error; for(i=0; inSession; i++){ if( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){ - utf8_printf(stderr, "Session \"%s\" already exists\n", zName); + eputf("Session \"%s\" already exists\n", zName); goto meta_command_exit; } } if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){ - raw_printf(stderr, - "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); + eputf("Maximum of %d sessions\n", ArraySize(pAuxDb->aSession)); goto meta_command_exit; } pSession = &pAuxDb->aSession[pAuxDb->nSession]; rc = sqlite3session_create(p->db, azCmd[1], &pSession->p); if( rc ){ - raw_printf(stderr, "Cannot open session: error code=%d\n", rc); + eputf("Cannot open session: error code=%d\n", rc); rc = 0; goto meta_command_exit; } @@ -26339,7 +27063,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int i, v; for(i=1; iout, "%s: %d 0x%x\n", azArg[i], v, v); + oputf("%s: %d 0x%x\n", azArg[i], v, v); } } if( cli_strncmp(azArg[0]+9, "integer", n-9)==0 ){ @@ -26348,7 +27072,7 @@ static int do_meta_command(char *zLine, ShellState *p){ char zBuf[200]; v = integerValue(azArg[i]); sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v); - utf8_printf(p->out, "%s", zBuf); + oputz(zBuf); } } }else @@ -26375,9 +27099,8 @@ static int do_meta_command(char *zLine, ShellState *p){ bVerbose++; }else { - utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n", - azArg[i], azArg[0]); - raw_printf(stderr, "Should be one of: --init -v\n"); + eputf("Unknown option \"%s\" on \"%s\"\n", azArg[i], azArg[0]); + eputz("Should be one of: --init -v\n"); rc = 1; goto meta_command_exit; } @@ -26406,7 +27129,7 @@ static int do_meta_command(char *zLine, ShellState *p){ -1, &pStmt, 0); } if( rc ){ - raw_printf(stderr, "Error querying the selftest table\n"); + eputz("Error querying the selftest table\n"); rc = 1; sqlite3_finalize(pStmt); goto meta_command_exit; @@ -26422,10 +27145,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( zAns==0 ) continue; k = 0; if( bVerbose>0 ){ - printf("%d: %s %s\n", tno, zOp, zSql); + sputf(stdout, "%d: %s %s\n", tno, zOp, zSql); } if( cli_strcmp(zOp,"memo")==0 ){ - utf8_printf(p->out, "%s\n", zSql); + oputf("%s\n", zSql); }else if( cli_strcmp(zOp,"run")==0 ){ char *zErrMsg = 0; @@ -26434,23 +27157,22 @@ static int do_meta_command(char *zLine, ShellState *p){ rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg); nTest++; if( bVerbose ){ - utf8_printf(p->out, "Result: %s\n", str.z); + oputf("Result: %s\n", str.z); } if( rc || zErrMsg ){ nErr++; rc = 1; - utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg); + oputf("%d: error-code-%d: %s\n", tno, rc, zErrMsg); sqlite3_free(zErrMsg); }else if( cli_strcmp(zAns,str.z)!=0 ){ nErr++; rc = 1; - utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns); - utf8_printf(p->out, "%d: Got: [%s]\n", tno, str.z); + oputf("%d: Expected: [%s]\n", tno, zAns); + oputf("%d: Got: [%s]\n", tno, str.z); } - }else - { - utf8_printf(stderr, - "Unknown operation \"%s\" on selftest line %d\n", zOp, tno); + } + else{ + eputf("Unknown operation \"%s\" on selftest line %d\n", zOp, tno); rc = 1; break; } @@ -26458,12 +27180,12 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_finalize(pStmt); } /* End loop over k */ freeText(&str); - utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest); + oputf("%d errors out of %d tests\n", nErr, nTest); }else if( c=='s' && cli_strncmp(azArg[0], "separator", n)==0 ){ if( nArg<2 || nArg>3 ){ - raw_printf(stderr, "Usage: .separator COL ?ROW?\n"); + eputz("Usage: .separator COL ?ROW?\n"); rc = 1; } if( nArg>=2 ){ @@ -26506,14 +27228,13 @@ static int do_meta_command(char *zLine, ShellState *p){ bDebug = 1; }else { - utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n", - azArg[i], azArg[0]); + eputf("Unknown option \"%s\" on \"%s\"\n", azArg[i], azArg[0]); showHelp(p->out, azArg[0]); rc = 1; goto meta_command_exit; } }else if( zLike ){ - raw_printf(stderr, "Usage: .sha3sum ?OPTIONS? ?LIKE-PATTERN?\n"); + eputz("Usage: .sha3sum ?OPTIONS? ?LIKE-PATTERN?\n"); rc = 1; goto meta_command_exit; }else{ @@ -26585,7 +27306,7 @@ static int do_meta_command(char *zLine, ShellState *p){ freeText(&sQuery); freeText(&sSql); if( bDebug ){ - utf8_printf(p->out, "%s\n", zSql); + oputf("%s\n", zSql); }else{ shell_exec(p, zSql, 0); } @@ -26615,7 +27336,7 @@ static int do_meta_command(char *zLine, ShellState *p){ "' OR ') as query, tname from tabcols group by tname)" , zRevText); shell_check_oom(zRevText); - if( bDebug ) utf8_printf(p->out, "%s\n", zRevText); + if( bDebug ) oputf("%s\n", zRevText); lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0); if( lrc!=SQLITE_OK ){ /* assert(lrc==SQLITE_NOMEM); // might also be SQLITE_ERROR if the @@ -26628,7 +27349,7 @@ static int do_meta_command(char *zLine, ShellState *p){ const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0); sqlite3_stmt *pCheckStmt; lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0); - if( bDebug ) utf8_printf(p->out, "%s\n", zGenQuery); + if( bDebug ) oputf("%s\n", zGenQuery); if( lrc!=SQLITE_OK ){ rc = 1; }else{ @@ -26636,9 +27357,8 @@ static int do_meta_command(char *zLine, ShellState *p){ double countIrreversible = sqlite3_column_double(pCheckStmt, 0); if( countIrreversible>0 ){ int sz = (int)(countIrreversible + 0.5); - utf8_printf(stderr, - "Digest includes %d invalidly encoded text field%s.\n", - sz, (sz>1)? "s": ""); + eputf("Digest includes %d invalidly encoded text field%s.\n", + sz, (sz>1)? "s": ""); } } sqlite3_finalize(pCheckStmt); @@ -26646,7 +27366,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_finalize(pStmt); } } - if( rc ) utf8_printf(stderr, ".sha3sum failed.\n"); + if( rc ) eputz(".sha3sum failed.\n"); sqlite3_free(zRevText); } #endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */ @@ -26662,7 +27382,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int i, x; failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]); if( nArg<2 ){ - raw_printf(stderr, "Usage: .system COMMAND\n"); + eputz("Usage: .system COMMAND\n"); rc = 1; goto meta_command_exit; } @@ -26671,9 +27391,11 @@ static int do_meta_command(char *zLine, ShellState *p){ zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"", zCmd, azArg[i]); } + consoleRestore(); x = zCmd!=0 ? system(zCmd) : 1; + consoleRenewSetup(); sqlite3_free(zCmd); - if( x ) raw_printf(stderr, "System command returns %d\n", x); + if( x ) eputf("System command returns %d\n", x); }else #endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */ @@ -26682,52 +27404,51 @@ static int do_meta_command(char *zLine, ShellState *p){ const char *zOut; int i; if( nArg!=1 ){ - raw_printf(stderr, "Usage: .show\n"); + eputz("Usage: .show\n"); rc = 1; goto meta_command_exit; } - utf8_printf(p->out, "%12.12s: %s\n","echo", - azBool[ShellHasFlag(p, SHFLG_Echo)]); - utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]); - utf8_printf(p->out, "%12.12s: %s\n","explain", - p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off"); - utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]); + oputf("%12.12s: %s\n","echo", + azBool[ShellHasFlag(p, SHFLG_Echo)]); + oputf("%12.12s: %s\n","eqp", azBool[p->autoEQP&3]); + oputf("%12.12s: %s\n","explain", + p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off"); + oputf("%12.12s: %s\n","headers", azBool[p->showHeader!=0]); if( p->mode==MODE_Column || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) ){ - utf8_printf - (p->out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode", - modeDescr[p->mode], p->cmOpts.iWrap, - p->cmOpts.bWordWrap ? "on" : "off", - p->cmOpts.bQuote ? "" : "no"); + oputf("%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode", + modeDescr[p->mode], p->cmOpts.iWrap, + p->cmOpts.bWordWrap ? "on" : "off", + p->cmOpts.bQuote ? "" : "no"); }else{ - utf8_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]); + oputf("%12.12s: %s\n","mode", modeDescr[p->mode]); } - utf8_printf(p->out, "%12.12s: ", "nullvalue"); - output_c_string(p->out, p->nullValue); - raw_printf(p->out, "\n"); - utf8_printf(p->out,"%12.12s: %s\n","output", - strlen30(p->outfile) ? p->outfile : "stdout"); - utf8_printf(p->out,"%12.12s: ", "colseparator"); - output_c_string(p->out, p->colSeparator); - raw_printf(p->out, "\n"); - utf8_printf(p->out,"%12.12s: ", "rowseparator"); - output_c_string(p->out, p->rowSeparator); - raw_printf(p->out, "\n"); + oputf("%12.12s: ", "nullvalue"); + output_c_string(p->nullValue); + oputz("\n"); + oputf("%12.12s: %s\n","output", + strlen30(p->outfile) ? p->outfile : "stdout"); + oputf("%12.12s: ", "colseparator"); + output_c_string(p->colSeparator); + oputz("\n"); + oputf("%12.12s: ", "rowseparator"); + output_c_string(p->rowSeparator); + oputz("\n"); switch( p->statsOn ){ case 0: zOut = "off"; break; default: zOut = "on"; break; case 2: zOut = "stmt"; break; case 3: zOut = "vmstep"; break; } - utf8_printf(p->out, "%12.12s: %s\n","stats", zOut); - utf8_printf(p->out, "%12.12s: ", "width"); + oputf("%12.12s: %s\n","stats", zOut); + oputf("%12.12s: ", "width"); for (i=0;inWidth;i++) { - raw_printf(p->out, "%d ", p->colWidth[i]); + oputf("%d ", p->colWidth[i]); } - raw_printf(p->out, "\n"); - utf8_printf(p->out, "%12.12s: %s\n", "filename", - p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : ""); + oputz("\n"); + oputf("%12.12s: %s\n", "filename", + p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : ""); }else if( c=='s' && cli_strncmp(azArg[0], "stats", n)==0 ){ @@ -26742,7 +27463,7 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( nArg==1 ){ display_stats(p->db, p, 0); }else{ - raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n"); + eputz("Usage: .stats ?on|off|stmt|vmstep?\n"); rc = 1; } }else @@ -26768,7 +27489,7 @@ static int do_meta_command(char *zLine, ShellState *p){ /* It is an historical accident that the .indexes command shows an error ** when called with the wrong number of arguments whereas the .tables ** command does not. */ - raw_printf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n"); + eputz("Usage: .indexes ?LIKE-PATTERN?\n"); rc = 1; sqlite3_finalize(pStmt); goto meta_command_exit; @@ -26844,10 +27565,9 @@ static int do_meta_command(char *zLine, ShellState *p){ for(i=0; iout, "%s%-*s", zSp, maxlen, - azResult[j] ? azResult[j]:""); + oputf("%s%-*s", zSp, maxlen, azResult[j] ? azResult[j]:""); } - raw_printf(p->out, "\n"); + oputz("\n"); } } @@ -26861,7 +27581,7 @@ static int do_meta_command(char *zLine, ShellState *p){ output_reset(p); p->out = output_file_open("testcase-out.txt", 0); if( p->out==0 ){ - raw_printf(stderr, "Error: cannot open 'testcase-out.txt'\n"); + eputz("Error: cannot open 'testcase-out.txt'\n"); } if( nArg>=2 ){ sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]); @@ -26902,7 +27622,7 @@ static int do_meta_command(char *zLine, ShellState *p){ {"seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" }, {"sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" }, {"tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" }, - {"uselongdouble", SQLITE_TESTCTRL_USELONGDOUBLE,0,"?BOOLEAN|\"default\"?"}, + {"uselongdouble", SQLITE_TESTCTRL_USELONGDOUBLE,0,"?BOOLEAN|\"default\"?"}, }; int testctrl = -1; int iCtrl = -1; @@ -26922,11 +27642,11 @@ static int do_meta_command(char *zLine, ShellState *p){ /* --help lists all test-controls */ if( cli_strcmp(zCmd,"help")==0 ){ - utf8_printf(p->out, "Available test-controls:\n"); + oputz("Available test-controls:\n"); for(i=0; iout, " .testctrl %s %s\n", - aCtrl[i].zCtrlName, aCtrl[i].zUsage); + oputf(" .testctrl %s %s\n", + aCtrl[i].zCtrlName, aCtrl[i].zUsage); } rc = 1; goto meta_command_exit; @@ -26942,16 +27662,16 @@ static int do_meta_command(char *zLine, ShellState *p){ testctrl = aCtrl[i].ctrlCode; iCtrl = i; }else{ - utf8_printf(stderr, "Error: ambiguous test-control: \"%s\"\n" - "Use \".testctrl --help\" for help\n", zCmd); + eputf("Error: ambiguous test-control: \"%s\"\n" + "Use \".testctrl --help\" for help\n", zCmd); rc = 1; goto meta_command_exit; } } } if( testctrl<0 ){ - utf8_printf(stderr,"Error: unknown test-control: %s\n" - "Use \".testctrl --help\" for help\n", zCmd); + eputf("Error: unknown test-control: %s\n" + "Use \".testctrl --help\" for help\n", zCmd); }else{ switch(testctrl){ @@ -26991,7 +27711,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3 *db; if( ii==0 && cli_strcmp(azArg[2],"random")==0 ){ sqlite3_randomness(sizeof(ii),&ii); - printf("-- random seed: %d\n", ii); + sputf(stdout, "-- random seed: %d\n", ii); } if( nArg==3 ){ db = 0; @@ -27059,7 +27779,7 @@ static int do_meta_command(char *zLine, ShellState *p){ case SQLITE_TESTCTRL_SEEK_COUNT: { u64 x = 0; rc2 = sqlite3_test_control(testctrl, p->db, &x); - utf8_printf(p->out, "%llu\n", x); + oputf("%llu\n", x); isOk = 3; break; } @@ -27090,11 +27810,11 @@ static int do_meta_command(char *zLine, ShellState *p){ int val = 0; rc2 = sqlite3_test_control(testctrl, -id, &val); if( rc2!=SQLITE_OK ) break; - if( id>1 ) utf8_printf(p->out, " "); - utf8_printf(p->out, "%d: %d", id, val); + if( id>1 ) oputz(" "); + oputf("%d: %d", id, val); id++; } - if( id>1 ) utf8_printf(p->out, "\n"); + if( id>1 ) oputz("\n"); isOk = 3; } break; @@ -27110,12 +27830,12 @@ static int do_meta_command(char *zLine, ShellState *p){ } } if( isOk==0 && iCtrl>=0 ){ - utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); + oputf("Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage); rc = 1; }else if( isOk==1 ){ - raw_printf(p->out, "%d\n", rc2); + oputf("%d\n", rc2); }else if( isOk==2 ){ - raw_printf(p->out, "0x%08x\n", rc2); + oputf("0x%08x\n", rc2); } }else #endif /* !defined(SQLITE_UNTESTABLE) */ @@ -27129,11 +27849,11 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg==2 ){ enableTimer = booleanValue(azArg[1]); if( enableTimer && !HAS_TIMER ){ - raw_printf(stderr, "Error: timer not available on this system.\n"); + eputz("Error: timer not available on this system.\n"); enableTimer = 0; } }else{ - raw_printf(stderr, "Usage: .timer on|off\n"); + eputz("Usage: .timer on|off\n"); rc = 1; } }else @@ -27170,7 +27890,7 @@ static int do_meta_command(char *zLine, ShellState *p){ mType |= SQLITE_TRACE_CLOSE; } else { - raw_printf(stderr, "Unknown option \"%s\" on \".trace\"\n", z); + eputf("Unknown option \"%s\" on \".trace\"\n", z); rc = 1; goto meta_command_exit; } @@ -27194,7 +27914,7 @@ static int do_meta_command(char *zLine, ShellState *p){ int lenOpt; char *zOpt; if( nArg<2 ){ - raw_printf(stderr, "Usage: .unmodule [--allexcept] NAME ...\n"); + eputz("Usage: .unmodule [--allexcept] NAME ...\n"); rc = 1; goto meta_command_exit; } @@ -27216,60 +27936,60 @@ static int do_meta_command(char *zLine, ShellState *p){ #if SQLITE_USER_AUTHENTICATION if( c=='u' && cli_strncmp(azArg[0], "user", n)==0 ){ if( nArg<2 ){ - raw_printf(stderr, "Usage: .user SUBCOMMAND ...\n"); + eputz("Usage: .user SUBCOMMAND ...\n"); rc = 1; goto meta_command_exit; } open_db(p, 0); if( cli_strcmp(azArg[1],"login")==0 ){ if( nArg!=4 ){ - raw_printf(stderr, "Usage: .user login USER PASSWORD\n"); + eputz("Usage: .user login USER PASSWORD\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3], strlen30(azArg[3])); if( rc ){ - utf8_printf(stderr, "Authentication failed for user %s\n", azArg[2]); + eputf("Authentication failed for user %s\n", azArg[2]); rc = 1; } }else if( cli_strcmp(azArg[1],"add")==0 ){ if( nArg!=5 ){ - raw_printf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n"); + eputz("Usage: .user add USER PASSWORD ISADMIN\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]), booleanValue(azArg[4])); if( rc ){ - raw_printf(stderr, "User-Add failed: %d\n", rc); + eputf("User-Add failed: %d\n", rc); rc = 1; } }else if( cli_strcmp(azArg[1],"edit")==0 ){ if( nArg!=5 ){ - raw_printf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n"); + eputz("Usage: .user edit USER PASSWORD ISADMIN\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]), booleanValue(azArg[4])); if( rc ){ - raw_printf(stderr, "User-Edit failed: %d\n", rc); + eputf("User-Edit failed: %d\n", rc); rc = 1; } }else if( cli_strcmp(azArg[1],"delete")==0 ){ if( nArg!=3 ){ - raw_printf(stderr, "Usage: .user delete USER\n"); + eputz("Usage: .user delete USER\n"); rc = 1; goto meta_command_exit; } rc = sqlite3_user_delete(p->db, azArg[2]); if( rc ){ - raw_printf(stderr, "User-Delete failed: %d\n", rc); + eputf("User-Delete failed: %d\n", rc); rc = 1; } }else{ - raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n"); + eputz("Usage: .user login|add|edit|delete ...\n"); rc = 1; goto meta_command_exit; } @@ -27278,21 +27998,21 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){ char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit"; - utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/, - sqlite3_libversion(), sqlite3_sourceid()); + oputf("SQLite %s %s\n" /*extra-version-info*/, + sqlite3_libversion(), sqlite3_sourceid()); #if SQLITE_HAVE_ZLIB - utf8_printf(p->out, "zlib version %s\n", zlibVersion()); + oputf("zlib version %s\n", zlibVersion()); #endif #define CTIMEOPT_VAL_(opt) #opt #define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) #if defined(__clang__) && defined(__clang_major__) - utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "." - CTIMEOPT_VAL(__clang_minor__) "." - CTIMEOPT_VAL(__clang_patchlevel__) " (%s)\n", zPtrSz); + oputf("clang-" CTIMEOPT_VAL(__clang_major__) "." + CTIMEOPT_VAL(__clang_minor__) "." + CTIMEOPT_VAL(__clang_patchlevel__) " (%s)\n", zPtrSz); #elif defined(_MSC_VER) - utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz); + oputf("msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz); #elif defined(__GNUC__) && defined(__VERSION__) - utf8_printf(p->out, "gcc-" __VERSION__ " (%s)\n", zPtrSz); + oputf("gcc-" __VERSION__ " (%s)\n", zPtrSz); #endif }else @@ -27302,10 +28022,10 @@ static int do_meta_command(char *zLine, ShellState *p){ if( p->db ){ sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs); if( pVfs ){ - utf8_printf(p->out, "vfs.zName = \"%s\"\n", pVfs->zName); - raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); - raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); - raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); + oputf("vfs.zName = \"%s\"\n", pVfs->zName); + oputf("vfs.iVersion = %d\n", pVfs->iVersion); + oputf("vfs.szOsFile = %d\n", pVfs->szOsFile); + oputf("vfs.mxPathname = %d\n", pVfs->mxPathname); } } }else @@ -27317,13 +28037,13 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent); } for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){ - utf8_printf(p->out, "vfs.zName = \"%s\"%s\n", pVfs->zName, - pVfs==pCurrent ? " <--- CURRENT" : ""); - raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion); - raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile); - raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname); + oputf("vfs.zName = \"%s\"%s\n", pVfs->zName, + pVfs==pCurrent ? " <--- CURRENT" : ""); + oputf("vfs.iVersion = %d\n", pVfs->iVersion); + oputf("vfs.szOsFile = %d\n", pVfs->szOsFile); + oputf("vfs.mxPathname = %d\n", pVfs->mxPathname); if( pVfs->pNext ){ - raw_printf(p->out, "-----------------------------------\n"); + oputz("-----------------------------------\n"); } } }else @@ -27334,7 +28054,7 @@ static int do_meta_command(char *zLine, ShellState *p){ if( p->db ){ sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName); if( zVfsName ){ - utf8_printf(p->out, "%s\n", zVfsName); + oputf("%s\n", zVfsName); sqlite3_free(zVfsName); } } @@ -27358,8 +28078,8 @@ static int do_meta_command(char *zLine, ShellState *p){ }else { - utf8_printf(stderr, "Error: unknown command or invalid arguments: " - " \"%s\". Enter \".help\" for help\n", azArg[0]); + eputf("Error: unknown command or invalid arguments: " + " \"%s\". Enter \".help\" for help\n", azArg[0]); rc = 1; } @@ -27549,7 +28269,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ }else{ sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType); } - utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail); + eputf("%s %s\n", zPrefix, zErrorTail); sqlite3_free(zErrMsg); zErrMsg = 0; return 1; @@ -27558,13 +28278,13 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ sqlite3_snprintf(sizeof(zLineBuf), zLineBuf, "changes: %lld total_changes: %lld", sqlite3_changes64(p->db), sqlite3_total_changes64(p->db)); - raw_printf(p->out, "%s\n", zLineBuf); + oputf("%s\n", zLineBuf); } return 0; } static void echo_group_input(ShellState *p, const char *zDo){ - if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo); + if( ShellHasFlag(p, SHFLG_Echo) ) oputf("%s\n", zDo); } #ifdef SQLITE_SHELL_FIDDLE @@ -27622,8 +28342,8 @@ static int process_input(ShellState *p){ if( p->inputNesting==MAX_INPUT_NESTING ){ /* This will be more informative in a later version. */ - utf8_printf(stderr,"Input nesting limit (%d) reached at line %d." - " Check recursion.\n", MAX_INPUT_NESTING, p->lineno); + eputf("Input nesting limit (%d) reached at line %d." + " Check recursion.\n", MAX_INPUT_NESTING, p->lineno); return 1; } ++p->inputNesting; @@ -27634,7 +28354,7 @@ static int process_input(ShellState *p){ zLine = one_input_line(p->in, zLine, nSql>0); if( zLine==0 ){ /* End of input */ - if( p->in==0 && stdin_is_interactive ) printf("\n"); + if( p->in==0 && stdin_is_interactive ) oputz("\n"); break; } if( seenInterrupt ){ @@ -27844,8 +28564,8 @@ static void process_sqliterc( if( sqliterc == NULL ){ home_dir = find_home_dir(0); if( home_dir==0 ){ - raw_printf(stderr, "-- warning: cannot find home directory;" - " cannot read ~/.sqliterc\n"); + eputz("-- warning: cannot find home directory;" + " cannot read ~/.sqliterc\n"); return; } zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir); @@ -27855,12 +28575,12 @@ static void process_sqliterc( p->in = fopen(sqliterc,"rb"); if( p->in ){ if( stdin_is_interactive ){ - utf8_printf(stderr,"-- Loading resources from %s\n",sqliterc); + eputf("-- Loading resources from %s\n", sqliterc); } if( process_input(p) && bail_on_error ) exit(1); fclose(p->in); }else if( sqliterc_override!=0 ){ - utf8_printf(stderr,"cannot open: \"%s\"\n", sqliterc); + eputf("cannot open: \"%s\"\n", sqliterc); if( bail_on_error ) exit(1); } p->in = inSaved; @@ -27910,9 +28630,6 @@ static const char zOptions[] = " -multiplex enable the multiplexor VFS\n" #endif " -newline SEP set output row separator. Default: '\\n'\n" -#if SHELL_WIN_UTF8_OPT - " -no-utf8 do not try to set up UTF-8 output (for legacy)\n" -#endif " -nofollow refuse to open symbolic links to database files\n" " -nonce STRING set the safe-mode escape nonce\n" " -nullvalue TEXT set text string for NULL values. Default ''\n" @@ -27929,9 +28646,6 @@ static const char zOptions[] = " -table set output mode to 'table'\n" " -tabs set output mode to 'tabs'\n" " -unsafe-testing allow unsafe commands and modes for testing\n" -#if SHELL_WIN_UTF8_OPT && 0 /* Option is accepted, but is now the default. */ - " -utf8 setup interactive console code page for UTF-8\n" -#endif " -version show SQLite version\n" " -vfs NAME use NAME as the default VFS\n" #ifdef SQLITE_ENABLE_VFSTRACE @@ -27942,14 +28656,13 @@ static const char zOptions[] = #endif ; static void usage(int showDetail){ - utf8_printf(stderr, - "Usage: %s [OPTIONS] [FILENAME [SQL]]\n" - "FILENAME is the name of an SQLite database. A new database is created\n" - "if the file does not previously exist. Defaults to :memory:.\n", Argv0); + eputf("Usage: %s [OPTIONS] [FILENAME [SQL]]\n" + "FILENAME is the name of an SQLite database. A new database is created\n" + "if the file does not previously exist. Defaults to :memory:.\n", Argv0); if( showDetail ){ - utf8_printf(stderr, "OPTIONS include:\n%s", zOptions); + eputf("OPTIONS include:\n%s", zOptions); }else{ - raw_printf(stderr, "Use the -help option for additional information\n"); + eputz("Use the -help option for additional information\n"); } exit(1); } @@ -27960,8 +28673,8 @@ static void usage(int showDetail){ */ static void verify_uninitialized(void){ if( sqlite3_config(-1)==SQLITE_MISUSE ){ - utf8_printf(stdout, "WARNING: attempt to configure SQLite after" - " initialization.\n"); + sputz(stdout, "WARNING: attempt to configure SQLite after" + " initialization.\n"); } } @@ -27990,7 +28703,7 @@ static void main_init(ShellState *data) { /* ** Output text to the console in a font that attracts extra attention. */ -#ifdef _WIN32 +#if defined(_WIN32) || defined(WIN32) static void printBold(const char *zText){ #if !SQLITE_OS_WINRT HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); @@ -28000,14 +28713,14 @@ static void printBold(const char *zText){ FOREGROUND_RED|FOREGROUND_INTENSITY ); #endif - printf("%s", zText); + oputz(zText); #if !SQLITE_OS_WINRT SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes); #endif } #else static void printBold(const char *zText){ - printf("\033[1m%s\033[0m", zText); + oputf("\033[1m%s\033[0m", zText); } #endif @@ -28017,15 +28730,14 @@ static void printBold(const char *zText){ */ static char *cmdline_option_value(int argc, char **argv, int i){ if( i==argc ){ - utf8_printf(stderr, "%s: Error: missing argument to %s\n", - argv[0], argv[argc-1]); + eputf("%s: Error: missing argument to %s\n", argv[0], argv[argc-1]); exit(1); } return argv[i]; } static void sayAbnormalExit(void){ - if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n"); + if( seenInterrupt ) eputz("Program interrupted.\n"); } #ifndef SQLITE_SHELL_IS_UTF8 @@ -28055,6 +28767,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ # define data shellState #else ShellState data; + StreamsAreConsole consStreams = SAC_NoConsole; #endif const char *zInitFile = 0; int i; @@ -28076,12 +28789,10 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ stdout_is_console = 1; data.wasm.zDefaultDbName = "/fiddle.sqlite3"; #else - stdin_is_interactive = isatty(0); - stdout_is_console = isatty(1); -#endif -#if SHELL_WIN_UTF8_OPT - probe_console(); /* Check for console I/O and UTF-8 capability. */ - if( !mbcs_opted ) atexit(console_restore); + consStreams = consoleClassifySetup(stdin, stdout, stderr); + stdin_is_interactive = (consStreams & SAC_InConsole)!=0; + stdout_is_console = (consStreams & SAC_OutConsole)!=0; + atexit(consoleRestore); #endif atexit(sayAbnormalExit); #ifdef SQLITE_DEBUG @@ -28090,9 +28801,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ #if !defined(_WIN32_WCE) if( getenv("SQLITE_DEBUG_BREAK") ){ if( isatty(0) && isatty(2) ){ - fprintf(stderr, - "attach debugger to process %d and press any key to continue.\n", - GETPID()); + eputf("attach debugger to process %d and press any key to continue.\n", + GETPID()); fgetc(stdin); }else{ #if defined(_WIN32) || defined(WIN32) @@ -28112,14 +28822,14 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ signal(SIGINT, interrupt_handler); #elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE) if( !SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE) ){ - fprintf(stderr, "No ^C handler.\n"); + eputz("No ^C handler.\n"); } #endif #if USE_SYSTEM_SQLITE+0!=1 if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){ - utf8_printf(stderr, "SQLite header and source version mismatch\n%s\n%s\n", - sqlite3_sourceid(), SQLITE_SOURCE_ID); + eputf("SQLite header and source version mismatch\n%s\n%s\n", + sqlite3_sourceid(), SQLITE_SOURCE_ID); exit(1); } #endif @@ -28215,14 +28925,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ */ stdin_is_interactive = 0; }else if( cli_strcmp(z,"-utf8")==0 ){ -#if SHELL_WIN_UTF8_OPT - /* Option accepted, but is ignored except for this diagnostic. */ - if( mbcs_opted ) fprintf(stderr, "Cannot do UTF-8 at this console.\n"); -#endif /* SHELL_WIN_UTF8_OPT */ }else if( cli_strcmp(z,"-no-utf8")==0 ){ -#if SHELL_WIN_UTF8_OPT - mbcs_opted = 1; -#endif /* SHELL_WIN_UTF8_OPT */ }else if( cli_strcmp(z,"-heap")==0 ){ #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) const char *zSize; @@ -28357,26 +29060,17 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( pVfs ){ sqlite3_vfs_register(pVfs, 1); }else{ - utf8_printf(stderr, "no such VFS: \"%s\"\n", zVfs); + eputf("no such VFS: \"%s\"\n", zVfs); exit(1); } } -#if SHELL_WIN_UTF8_OPT - /* Get indicated Windows console setup done before running invocation commands. */ - if( in_console || out_console ){ - console_prepare_utf8(); - } - if( !in_console ){ - setBinaryMode(stdin, 0); - } -#endif if( data.pAuxDb->zDbFilename==0 ){ #ifndef SQLITE_OMIT_MEMORYDB data.pAuxDb->zDbFilename = ":memory:"; warnInmemoryDb = argc==1; #else - utf8_printf(stderr,"%s: Error: no database filename specified\n", Argv0); + eputf("%s: Error: no database filename specified\n", Argv0); return 1; #endif } @@ -28493,8 +29187,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-bail")==0 ){ /* No-op. The bail_on_error flag should already be set. */ }else if( cli_strcmp(z,"-version")==0 ){ - printf("%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), - 8*(int)sizeof(char*)); + oputf("%s %s (%d-bit)\n", sqlite3_libversion(), sqlite3_sourceid(), + 8*(int)sizeof(char*)); return 0; }else if( cli_strcmp(z,"-interactive")==0 ){ /* already handled */ @@ -28550,18 +29244,18 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ open_db(&data, 0); rc = shell_exec(&data, z, &zErrMsg); if( zErrMsg!=0 ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); + eputf("Error: %s\n", zErrMsg); if( bail_on_error ) return rc!=0 ? rc : 1; }else if( rc!=0 ){ - utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z); + eputf("Error: unable to process SQL \"%s\"\n", z); if( bail_on_error ) return rc; } } #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) }else if( cli_strncmp(z, "-A", 2)==0 ){ if( nCmd>0 ){ - utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands" - " with \"%s\"\n", z); + eputf("Error: cannot mix regular SQL or dot-commands" + " with \"%s\"\n", z); return 1; } open_db(&data, OPEN_DB_ZIPFILE); @@ -28579,8 +29273,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ }else if( cli_strcmp(z,"-unsafe-testing")==0 ){ /* Acted upon in first pass. */ }else{ - utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); - raw_printf(stderr,"Use -help for a list of options.\n"); + eputf("%s: Error: unknown option: %s\n", Argv0, z); + eputz("Use -help for a list of options.\n"); return 1; } data.cMode = data.mode; @@ -28604,9 +29298,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ rc = shell_exec(&data, azCmd[i], &zErrMsg); if( zErrMsg || rc ){ if( zErrMsg!=0 ){ - utf8_printf(stderr,"Error: %s\n", zErrMsg); + eputf("Error: %s\n", zErrMsg); }else{ - utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]); + eputf("Error: unable to process SQL: %s\n", azCmd[i]); } sqlite3_free(zErrMsg); free(azCmd); @@ -28620,27 +29314,21 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( stdin_is_interactive ){ char *zHome; char *zHistory; - const char *zCharset = ""; int nHistory; print_FTL_version(); -#if SHELL_WIN_UTF8_OPT - switch( console_utf8_in+2*console_utf8_out ){ - default: case 0: break; - case 1: zCharset = " (utf8 in)"; break; - case 2: zCharset = " (utf8 out)"; break; - case 3: zCharset = " (utf8 I/O)"; break; - } +#if CIO_WIN_WC_XLATE +# define SHELL_CIO_CHAR_SET (stdout_is_console? " (UTF-16 console I/O)" : "") +#else +# define SHELL_CIO_CHAR_SET "" #endif - printf( - "SQLite version %s %.19s%s\n" /*extra-version-info*/ - "Enter \".help\" for usage hints.\n", - sqlite3_libversion(), sqlite3_sourceid(), zCharset - ); + oputf("SQLite version %s %.19s%s\n" /*extra-version-info*/ + "Enter \".help\" for usage hints.\n", + sqlite3_libversion(), sqlite3_sourceid(), SHELL_CIO_CHAR_SET); if( warnInmemoryDb ){ - printf("Connected to a "); + oputz("Connected to a "); printBold("transient in-memory database"); - printf(".\nUse \".open FILENAME\" to reopen on a " - "persistent database.\n"); + oputz(".\nUse \".open FILENAME\" to reopen on a" + " persistent database.\n"); } zHistory = getenv("SQLITE_HISTORY"); if( zHistory ){ @@ -28700,8 +29388,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ memset(&data, 0, sizeof(data)); #ifdef SQLITE_DEBUG if( sqlite3_memory_used()>mem_main_enter ){ - utf8_printf(stderr, "Memory leaked: %u bytes\n", - (unsigned int)(sqlite3_memory_used()-mem_main_enter)); + eputf("Memory leaked: %u bytes\n", + (unsigned int)(sqlite3_memory_used()-mem_main_enter)); } #endif #endif /* !SQLITE_SHELL_FIDDLE */ diff --git a/src/database/sqlite3.c b/src/database/sqlite3.c index aacd507c..592320ff 100644 --- a/src/database/sqlite3.c +++ b/src/database/sqlite3.c @@ -1,6 +1,6 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.44.0. By combining all the individual C code files into this +** version 3.44.1. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -18,7 +18,7 @@ ** separate file. This file contains only code for the core SQLite library. ** ** The content in this amalgamation comes from Fossil check-in -** 17129ba1ff7f0daf37100ee82d507aef7827. +** d295f48e8f367b066b881780c98bdf980a1d. */ #define SQLITE_CORE 1 #define SQLITE_AMALGAMATION 1 @@ -459,9 +459,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.44.0" -#define SQLITE_VERSION_NUMBER 3044000 -#define SQLITE_SOURCE_ID "2023-11-01 11:23:50 17129ba1ff7f0daf37100ee82d507aef7827cf38de1866e2633096ae6ad81301" +#define SQLITE_VERSION "3.44.1" +#define SQLITE_VERSION_NUMBER 3044001 +#define SQLITE_SOURCE_ID "2023-11-22 14:18:12 d295f48e8f367b066b881780c98bdf980a1d550397d5ba0b0e49842c95b3e8b4" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -5886,13 +5886,27 @@ SQLITE_API int sqlite3_create_window_function( ** ** ** [[SQLITE_SUBTYPE]]
SQLITE_SUBTYPE
-** The SQLITE_SUBTYPE flag indicates to SQLite that a function may call +** The SQLITE_SUBTYPE flag indicates to SQLite that a function might call ** [sqlite3_value_subtype()] to inspect the sub-types of its arguments. -** Specifying this flag makes no difference for scalar or aggregate user -** functions. However, if it is not specified for a user-defined window -** function, then any sub-types belonging to arguments passed to the window -** function may be discarded before the window function is called (i.e. -** sqlite3_value_subtype() will always return 0). +** This flag instructs SQLite to omit some corner-case optimizations that +** might disrupt the operation of the [sqlite3_value_subtype()] function, +** causing it to return zero rather than the correct subtype(). +** SQL functions that invokes [sqlite3_value_subtype()] should have this +** property. If the SQLITE_SUBTYPE property is omitted, then the return +** value from [sqlite3_value_subtype()] might sometimes be zero even though +** a non-zero subtype was specified by the function argument expression. +** +** [[SQLITE_RESULT_SUBTYPE]]
SQLITE_RESULT_SUBTYPE
+** The SQLITE_RESULT_SUBTYPE flag indicates to SQLite that a function might call +** [sqlite3_result_subtype()] to cause a sub-type to be associated with its +** result. +** Every function that invokes [sqlite3_result_subtype()] should have this +** property. If it does not, then the call to [sqlite3_result_subtype()] +** might become a no-op if the function is used as term in an +** [expression index]. On the other hand, SQL functions that never invoke +** [sqlite3_result_subtype()] should avoid setting this property, as the +** purpose of this property is to disable certain optimizations that are +** incompatible with subtypes. **
** */ @@ -5900,6 +5914,7 @@ SQLITE_API int sqlite3_create_window_function( #define SQLITE_DIRECTONLY 0x000080000 #define SQLITE_SUBTYPE 0x000100000 #define SQLITE_INNOCUOUS 0x000200000 +#define SQLITE_RESULT_SUBTYPE 0x001000000 /* ** CAPI3REF: Deprecated Functions @@ -6096,6 +6111,12 @@ SQLITE_API int sqlite3_value_encoding(sqlite3_value*); ** information can be used to pass a limited amount of context from ** one SQL function to another. Use the [sqlite3_result_subtype()] ** routine to set the subtype for the return value of an SQL function. +** +** Every [application-defined SQL function] that invoke this interface +** should include the [SQLITE_SUBTYPE] property in the text +** encoding argument when the function is [sqlite3_create_function|registered]. +** If the [SQLITE_SUBTYPE] property is omitted, then sqlite3_value_subtype() +** might return zero instead of the upstream subtype in some corner cases. */ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); @@ -6226,14 +6247,22 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); **
  • ^(when sqlite3_set_auxdata() is invoked again on the same ** parameter)^, or **
  • ^(during the original sqlite3_set_auxdata() call when a memory -** allocation error occurs.)^ +** allocation error occurs.)^ +**
  • ^(during the original sqlite3_set_auxdata() call if the function +** is evaluated during query planning instead of during query execution, +** as sometimes happens with [SQLITE_ENABLE_STAT4].)^ ** -** Note the last bullet in particular. The destructor X in +** Note the last two bullets in particular. The destructor X in ** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the ** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata() ** should be called near the end of the function implementation and the ** function implementation should not make any use of P after -** sqlite3_set_auxdata() has been called. +** sqlite3_set_auxdata() has been called. Furthermore, a call to +** sqlite3_get_auxdata() that occurs immediately after a corresponding call +** to sqlite3_set_auxdata() might still return NULL if an out-of-memory +** condition occurred during the sqlite3_set_auxdata() call or if the +** function is being evaluated during query planning rather than during +** query execution. ** ** ^(In practice, auxiliary data is preserved between function calls for ** function parameters that are compile-time constants, including literal @@ -6507,6 +6536,20 @@ SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); ** higher order bits are discarded. ** The number of subtype bytes preserved by SQLite might increase ** in future releases of SQLite. +** +** Every [application-defined SQL function] that invokes this interface +** should include the [SQLITE_RESULT_SUBTYPE] property in its +** text encoding argument when the SQL function is +** [sqlite3_create_function|registered]. If the [SQLITE_RESULT_SUBTYPE] +** property is omitted from the function that invokes sqlite3_result_subtype(), +** then in some cases the sqlite3_result_subtype() might fail to set +** the result subtype. +** +** If SQLite is compiled with -DSQLITE_STRICT_SUBTYPE=1, then any +** SQL function that invokes the sqlite3_result_subtype() interface +** and that does not have the SQLITE_RESULT_SUBTYPE property will raise +** an error. Future versions of SQLite might enable -DSQLITE_STRICT_SUBTYPE=1 +** by default. */ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int); @@ -17811,14 +17854,15 @@ struct FuncDestructor { #define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a ** single query - might change over time */ #define SQLITE_FUNC_TEST 0x4000 /* Built-in testing functions */ -/* 0x8000 -- available for reuse */ +#define SQLITE_FUNC_RUNONLY 0x8000 /* Cannot be used by valueFromFunction */ #define SQLITE_FUNC_WINDOW 0x00010000 /* Built-in window-only function */ #define SQLITE_FUNC_INTERNAL 0x00040000 /* For use by NestedParse() only */ #define SQLITE_FUNC_DIRECT 0x00080000 /* Not for use in TRIGGERs or VIEWs */ -#define SQLITE_FUNC_SUBTYPE 0x00100000 /* Result likely to have sub-type */ +/* SQLITE_SUBTYPE 0x00100000 // Consumer of subtypes */ #define SQLITE_FUNC_UNSAFE 0x00200000 /* Function has side effects */ #define SQLITE_FUNC_INLINE 0x00400000 /* Functions implemented in-line */ #define SQLITE_FUNC_BUILTIN 0x00800000 /* This is a built-in function */ +/* SQLITE_RESULT_SUBTYPE 0x01000000 // Generator of subtypes */ #define SQLITE_FUNC_ANYORDER 0x08000000 /* count/min/max aggregate */ /* Identifier numbers for each in-line function */ @@ -17910,9 +17954,10 @@ struct FuncDestructor { #define MFUNCTION(zName, nArg, xPtr, xFunc) \ {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ xPtr, 0, xFunc, 0, 0, 0, #zName, {0} } -#define JFUNCTION(zName, nArg, iArg, xFunc) \ - {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|\ - SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ +#define JFUNCTION(zName, nArg, bUseCache, bWS, bRS, iArg, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|SQLITE_FUNC_CONSTANT|\ + SQLITE_UTF8|((bUseCache)*SQLITE_FUNC_RUNONLY)|\ + ((bRS)*SQLITE_SUBTYPE)|((bWS)*SQLITE_RESULT_SUBTYPE), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define INLINE_FUNC(zName, nArg, iArg, mFlags) \ {nArg, SQLITE_FUNC_BUILTIN|\ @@ -29453,7 +29498,7 @@ SQLITE_PRIVATE void sqlite3MemoryBarrier(void){ SQLITE_MEMORY_BARRIER; #elif defined(__GNUC__) __sync_synchronize(); -#elif MSVC_VERSION>=1300 +#elif MSVC_VERSION>=1400 _ReadWriteBarrier(); #elif defined(MemoryBarrier) MemoryBarrier(); @@ -61447,10 +61492,13 @@ act_like_temp_file: */ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char *zName){ Pager *pPager; + const char *p; while( zName[-1]!=0 || zName[-2]!=0 || zName[-3]!=0 || zName[-4]!=0 ){ zName--; } - pPager = *(Pager**)(zName - 4 - sizeof(Pager*)); + p = zName - 4 - sizeof(Pager*); + assert( EIGHT_BYTE_ALIGNMENT(p) ); + pPager = *(Pager**)p; return pPager->fd; } @@ -83411,7 +83459,7 @@ static int valueFromFunction( #endif assert( pFunc ); if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 - || (pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) + || (pFunc->funcFlags & (SQLITE_FUNC_NEEDCOLL|SQLITE_FUNC_RUNONLY))!=0 ){ return SQLITE_OK; } @@ -89952,6 +90000,18 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubt #ifdef SQLITE_ENABLE_API_ARMOR if( pCtx==0 ) return; #endif +#if defined(SQLITE_STRICT_SUBTYPE) && SQLITE_STRICT_SUBTYPE+0!=0 + if( pCtx->pFunc!=0 + && (pCtx->pFunc->funcFlags & SQLITE_RESULT_SUBTYPE)==0 + ){ + char zErr[200]; + sqlite3_snprintf(sizeof(zErr), zErr, + "misuse of sqlite3_result_subtype() by %s()", + pCtx->pFunc->zName); + sqlite3_result_error(pCtx, zErr, -1); + return; + } +#endif /* SQLITE_STRICT_SUBTYPE */ pOut = pCtx->pOut; assert( sqlite3_mutex_held(pOut->db->mutex) ); pOut->eSubtype = eSubtype & 0xff; @@ -100321,7 +100381,7 @@ case OP_VCheck: { /* out2 */ pTab = pOp->p4.pTab; assert( pTab!=0 ); assert( IsVirtual(pTab) ); - assert( pTab->u.vtab.p!=0 ); + if( pTab->u.vtab.p==0 ) break; pVtab = pTab->u.vtab.p->pVtab; assert( pVtab!=0 ); pModule = pVtab->pModule; @@ -113917,8 +113977,8 @@ SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList *pA, const ExprList *pB */ SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA,Expr *pB, int iTab){ return sqlite3ExprCompare(0, - sqlite3ExprSkipCollateAndLikely(pA), - sqlite3ExprSkipCollateAndLikely(pB), + sqlite3ExprSkipCollate(pA), + sqlite3ExprSkipCollate(pB), iTab); } @@ -147605,10 +147665,11 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ SrcList *pTabList; SrcItem *pFrom; - assert( p->selFlags & SF_Resolved ); if( p->selFlags & SF_HasTypeInfo ) return; p->selFlags |= SF_HasTypeInfo; pParse = pWalker->pParse; + testcase( (p->selFlags & SF_Resolved)==0 ); + assert( (p->selFlags & SF_Resolved) || IN_RENAME_OBJECT ); pTabList = p->pSrc; for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ Table *pTab = pFrom->pTab; @@ -148630,6 +148691,7 @@ SQLITE_PRIVATE int sqlite3Select( TREETRACE(0x1000,pParse,p, ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); + unsetJoinExpr(p->pWhere, pItem->iCursor, 0); } } if( pItem->fg.jointype & JT_LTORJ ){ @@ -148644,17 +148706,15 @@ SQLITE_PRIVATE int sqlite3Select( TREETRACE(0x1000,pParse,p, ("RIGHT-JOIN simplifies to JOIN on term %d\n",j)); pI2->fg.jointype &= ~(JT_RIGHT|JT_OUTER); + unsetJoinExpr(p->pWhere, pI2->iCursor, 1); } } } - for(j=pTabList->nSrc-1; j>=i; j--){ + for(j=pTabList->nSrc-1; j>=0; j--){ pTabList->a[j].fg.jointype &= ~JT_LTORJ; if( pTabList->a[j].fg.jointype & JT_RIGHT ) break; } } - assert( pItem->iCursor>=0 ); - unsetJoinExpr(p->pWhere, pItem->iCursor, - pTabList->a[0].fg.jointype & JT_LTORJ); } /* No further action if this term of the FROM clause is not a subquery */ @@ -166058,6 +166118,20 @@ static SQLITE_NOINLINE void whereAddIndexedExpr( continue; } if( sqlite3ExprIsConstant(pExpr) ) continue; + if( pExpr->op==TK_FUNCTION ){ + /* Functions that might set a subtype should not be replaced by the + ** value taken from an expression index since the index omits the + ** subtype. https://sqlite.org/forum/forumpost/68d284c86b082c3e */ + int n; + FuncDef *pDef; + sqlite3 *db = pParse->db; + assert( ExprUseXList(pExpr) ); + n = pExpr->x.pList ? pExpr->x.pList->nExpr : 0; + pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); + if( pDef==0 || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){ + continue; + } + } p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr)); if( p==0 ) break; p->pIENext = pParse->pIdxEpr; @@ -168240,7 +168314,7 @@ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){ assert( ExprUseXList(pWin->pOwner) ); assert( pWin->pWFunc!=0 ); pArgs = pWin->pOwner->x.pList; - if( pWin->pWFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){ + if( pWin->pWFunc->funcFlags & SQLITE_SUBTYPE ){ selectWindowRewriteEList(pParse, pMWin, pSrc, pArgs, pTab, &pSublist); pWin->iArgCol = (pSublist ? pSublist->nExpr : 0); pWin->bExprArgs = 1; @@ -179412,7 +179486,7 @@ SQLITE_PRIVATE int sqlite3CreateFunc( assert( SQLITE_FUNC_CONSTANT==SQLITE_DETERMINISTIC ); assert( SQLITE_FUNC_DIRECT==SQLITE_DIRECTONLY ); extraFlags = enc & (SQLITE_DETERMINISTIC|SQLITE_DIRECTONLY| - SQLITE_SUBTYPE|SQLITE_INNOCUOUS); + SQLITE_SUBTYPE|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE); enc &= (SQLITE_FUNC_ENCMASK|SQLITE_ANY); /* The SQLITE_INNOCUOUS flag is the same bit as SQLITE_FUNC_UNSAFE. But @@ -202993,13 +203067,19 @@ static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ zIn++; N -= 2; while( N>0 ){ - for(i=0; i0 ){ jsonAppendRawNZ(p, zIn, i); zIn += i; N -= i; if( N==0 ) break; } + if( zIn[0]=='"' ){ + jsonAppendRawNZ(p, "\\\"", 2); + zIn++; + N--; + continue; + } assert( zIn[0]=='\\' ); switch( (u8)zIn[1] ){ case '\'': @@ -203394,7 +203474,8 @@ static void jsonReturnJson( JsonParse *pParse, /* The complete JSON */ JsonNode *pNode, /* Node to return */ sqlite3_context *pCtx, /* Return value for this function */ - int bGenerateAlt /* Also store the rendered text in zAlt */ + int bGenerateAlt, /* Also store the rendered text in zAlt */ + int omitSubtype /* Do not call sqlite3_result_subtype() */ ){ JsonString s; if( pParse->oom ){ @@ -203409,7 +203490,7 @@ static void jsonReturnJson( pParse->nAlt = s.nUsed; } jsonResult(&s); - sqlite3_result_subtype(pCtx, JSON_SUBTYPE); + if( !omitSubtype ) sqlite3_result_subtype(pCtx, JSON_SUBTYPE); } } @@ -203450,7 +203531,8 @@ static u32 jsonHexToInt4(const char *z){ static void jsonReturn( JsonParse *pParse, /* Complete JSON parse tree */ JsonNode *pNode, /* Node to return */ - sqlite3_context *pCtx /* Return value for this function */ + sqlite3_context *pCtx, /* Return value for this function */ + int omitSubtype /* Do not call sqlite3_result_subtype() */ ){ switch( pNode->eType ){ default: { @@ -203596,7 +203678,7 @@ static void jsonReturn( } case JSON_ARRAY: case JSON_OBJECT: { - jsonReturnJson(pParse, pNode, pCtx, 0); + jsonReturnJson(pParse, pNode, pCtx, 0, omitSubtype); break; } } @@ -204948,7 +205030,7 @@ static void jsonParseFunc( printf("iSubst = %u\n", p->iSubst); printf("iHold = %u\n", p->iHold); jsonDebugPrintNodeEntries(p->aNode, p->nNode); - jsonReturnJson(p, p->aNode, ctx, 1); + jsonReturnJson(p, p->aNode, ctx, 1, 0); } /* @@ -205134,15 +205216,14 @@ static void jsonExtractFunc( } if( pNode ){ if( flags & JSON_JSON ){ - jsonReturnJson(p, pNode, ctx, 0); + jsonReturnJson(p, pNode, ctx, 0, 0); }else{ - jsonReturn(p, pNode, ctx); - sqlite3_result_subtype(ctx, 0); + jsonReturn(p, pNode, ctx, 1); } } }else{ pNode = jsonLookup(p, zPath, 0, ctx); - if( p->nErr==0 && pNode ) jsonReturn(p, pNode, ctx); + if( p->nErr==0 && pNode ) jsonReturn(p, pNode, ctx, 0); } }else{ /* Two or more PATH arguments results in a JSON array with each @@ -205268,7 +205349,7 @@ static void jsonPatchFunc( if( pResult && pX->oom==0 ){ jsonDebugPrintParse(pX); jsonDebugPrintNode(pResult); - jsonReturnJson(pX, pResult, ctx, 0); + jsonReturnJson(pX, pResult, ctx, 0, 0); }else{ sqlite3_result_error_nomem(ctx); } @@ -205347,7 +205428,7 @@ static void jsonRemoveFunc( } } if( (pParse->aNode[0].jnFlags & JNODE_REMOVE)==0 ){ - jsonReturnJson(pParse, pParse->aNode, ctx, 1); + jsonReturnJson(pParse, pParse->aNode, ctx, 1, 0); } remove_done: jsonDebugPrintParse(p); @@ -205476,7 +205557,7 @@ static void jsonReplaceFunc( jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); } } - jsonReturnJson(pParse, pParse->aNode, ctx, 1); + jsonReturnJson(pParse, pParse->aNode, ctx, 1, 0); replace_err: jsonDebugPrintParse(pParse); jsonParseFree(pParse); @@ -205530,7 +205611,7 @@ static void jsonSetFunc( } } jsonDebugPrintParse(pParse); - jsonReturnJson(pParse, pParse->aNode, ctx, 1); + jsonReturnJson(pParse, pParse->aNode, ctx, 1, 0); jsonSetDone: jsonParseFree(pParse); } @@ -206045,7 +206126,7 @@ static int jsonEachColumn( case JEACH_KEY: { if( p->i==0 ) break; if( p->eType==JSON_OBJECT ){ - jsonReturn(&p->sParse, pThis, ctx); + jsonReturn(&p->sParse, pThis, ctx, 0); }else if( p->eType==JSON_ARRAY ){ u32 iKey; if( p->bRecursive ){ @@ -206061,7 +206142,7 @@ static int jsonEachColumn( } case JEACH_VALUE: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; - jsonReturn(&p->sParse, pThis, ctx); + jsonReturn(&p->sParse, pThis, ctx, 0); break; } case JEACH_TYPE: { @@ -206072,7 +206153,7 @@ static int jsonEachColumn( case JEACH_ATOM: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; if( pThis->eType>=JSON_ARRAY ) break; - jsonReturn(&p->sParse, pThis, ctx); + jsonReturn(&p->sParse, pThis, ctx, 0); break; } case JEACH_ID: { @@ -206365,34 +206446,43 @@ static sqlite3_module jsonTreeModule = { SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void){ #ifndef SQLITE_OMIT_JSON static FuncDef aJsonFunc[] = { - JFUNCTION(json, 1, 0, jsonRemoveFunc), - JFUNCTION(json_array, -1, 0, jsonArrayFunc), - JFUNCTION(json_array_length, 1, 0, jsonArrayLengthFunc), - JFUNCTION(json_array_length, 2, 0, jsonArrayLengthFunc), - JFUNCTION(json_error_position,1, 0, jsonErrorFunc), - JFUNCTION(json_extract, -1, 0, jsonExtractFunc), - JFUNCTION(->, 2, JSON_JSON, jsonExtractFunc), - JFUNCTION(->>, 2, JSON_SQL, jsonExtractFunc), - JFUNCTION(json_insert, -1, 0, jsonSetFunc), - JFUNCTION(json_object, -1, 0, jsonObjectFunc), - JFUNCTION(json_patch, 2, 0, jsonPatchFunc), - JFUNCTION(json_quote, 1, 0, jsonQuoteFunc), - JFUNCTION(json_remove, -1, 0, jsonRemoveFunc), - JFUNCTION(json_replace, -1, 0, jsonReplaceFunc), - JFUNCTION(json_set, -1, JSON_ISSET, jsonSetFunc), - JFUNCTION(json_type, 1, 0, jsonTypeFunc), - JFUNCTION(json_type, 2, 0, jsonTypeFunc), - JFUNCTION(json_valid, 1, 0, jsonValidFunc), -#if SQLITE_DEBUG - JFUNCTION(json_parse, 1, 0, jsonParseFunc), - JFUNCTION(json_test1, 1, 0, jsonTest1Func), + /* calls sqlite3_result_subtype() */ + /* | */ + /* Uses cache ______ | __ calls sqlite3_value_subtype() */ + /* | | | */ + /* Num args _________ | | | ___ Flags */ + /* | | | | | */ + /* | | | | | */ + JFUNCTION(json, 1, 1, 1, 0, 0, jsonRemoveFunc), + JFUNCTION(json_array, -1, 0, 1, 1, 0, jsonArrayFunc), + JFUNCTION(json_array_length, 1, 1, 0, 0, 0, jsonArrayLengthFunc), + JFUNCTION(json_array_length, 2, 1, 0, 0, 0, jsonArrayLengthFunc), + JFUNCTION(json_error_position,1, 1, 0, 0, 0, jsonErrorFunc), + JFUNCTION(json_extract, -1, 1, 1, 0, 0, jsonExtractFunc), + JFUNCTION(->, 2, 1, 1, 0, JSON_JSON, jsonExtractFunc), + JFUNCTION(->>, 2, 1, 0, 0, JSON_SQL, jsonExtractFunc), + JFUNCTION(json_insert, -1, 1, 1, 1, 0, jsonSetFunc), + JFUNCTION(json_object, -1, 0, 1, 1, 0, jsonObjectFunc), + JFUNCTION(json_patch, 2, 1, 1, 0, 0, jsonPatchFunc), + JFUNCTION(json_quote, 1, 0, 1, 1, 0, jsonQuoteFunc), + JFUNCTION(json_remove, -1, 1, 1, 0, 0, jsonRemoveFunc), + JFUNCTION(json_replace, -1, 1, 1, 1, 0, jsonReplaceFunc), + JFUNCTION(json_set, -1, 1, 1, 1, JSON_ISSET, jsonSetFunc), + JFUNCTION(json_type, 1, 1, 0, 0, 0, jsonTypeFunc), + JFUNCTION(json_type, 2, 1, 0, 0, 0, jsonTypeFunc), + JFUNCTION(json_valid, 1, 1, 0, 0, 0, jsonValidFunc), +#ifdef SQLITE_DEBUG + JFUNCTION(json_parse, 1, 1, 1, 0, 0, jsonParseFunc), + JFUNCTION(json_test1, 1, 1, 0, 1, 0, jsonTest1Func), #endif WAGGREGATE(json_group_array, 1, 0, 0, jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse, - SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC), + SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8| + SQLITE_DETERMINISTIC), WAGGREGATE(json_group_object, 2, 0, 0, jsonObjectStep, jsonObjectFinal, jsonObjectValue, jsonGroupInverse, - SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC) + SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8| + SQLITE_DETERMINISTIC) }; sqlite3InsertBuiltinFuncs(aJsonFunc, ArraySize(aJsonFunc)); #endif @@ -236129,10 +236219,8 @@ static Fts5HashEntry *fts5HashEntryMerge( } /* -** Extract all tokens from hash table iHash and link them into a list -** in sorted order. The hash table is cleared before returning. It is -** the responsibility of the caller to free the elements of the returned -** list. +** Link all tokens from hash table iHash into a list in sorted order. The +** tokens are not removed from the hash table. */ static int fts5HashEntrySort( Fts5Hash *pHash, @@ -238998,6 +239086,14 @@ static void fts5SegIterHashInit( pLeaf->p = (u8*)pList; } } + + /* The call to sqlite3Fts5HashScanInit() causes the hash table to + ** fill the size field of all existing position lists. This means they + ** can no longer be appended to. Since the only scenario in which they + ** can be appended to is if the previous operation on this table was + ** a DELETE, by clearing the Fts5Index.bDelete flag we can avoid this + ** possibility altogether. */ + p->bDelete = 0; }else{ p->rc = sqlite3Fts5HashQuery(p->pHash, sizeof(Fts5Data), (const char*)pTerm, nTerm, (void**)&pLeaf, &nList @@ -240675,7 +240771,7 @@ static void fts5WriteAppendPoslistData( const u8 *a = aData; int n = nData; - assert( p->pConfig->pgsz>0 ); + assert( p->pConfig->pgsz>0 || p->rc!=SQLITE_OK ); while( p->rc==SQLITE_OK && (pPage->buf.n + pPage->pgidx.n + n)>=p->pConfig->pgsz ){ @@ -241935,8 +242031,9 @@ static int sqlite3Fts5IndexOptimize(Fts5Index *p){ assert( p->rc==SQLITE_OK ); fts5IndexFlush(p); - assert( p->nContentlessDelete==0 ); + assert( p->rc!=SQLITE_OK || p->nContentlessDelete==0 ); pStruct = fts5StructureRead(p); + assert( p->rc!=SQLITE_OK || pStruct!=0 ); fts5StructureInvalidate(p); if( pStruct ){ @@ -247513,7 +247610,7 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2023-11-01 11:23:50 17129ba1ff7f0daf37100ee82d507aef7827cf38de1866e2633096ae6ad81301", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2023-11-22 14:18:12 d295f48e8f367b066b881780c98bdf980a1d550397d5ba0b0e49842c95b3e8b4", -1, SQLITE_TRANSIENT); } /* diff --git a/src/database/sqlite3.h b/src/database/sqlite3.h index d4f1c810..fce162d6 100644 --- a/src/database/sqlite3.h +++ b/src/database/sqlite3.h @@ -146,9 +146,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.44.0" -#define SQLITE_VERSION_NUMBER 3044000 -#define SQLITE_SOURCE_ID "2023-11-01 11:23:50 17129ba1ff7f0daf37100ee82d507aef7827cf38de1866e2633096ae6ad81301" +#define SQLITE_VERSION "3.44.1" +#define SQLITE_VERSION_NUMBER 3044001 +#define SQLITE_SOURCE_ID "2023-11-22 14:18:12 d295f48e8f367b066b881780c98bdf980a1d550397d5ba0b0e49842c95b3e8b4" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -5573,13 +5573,27 @@ SQLITE_API int sqlite3_create_window_function( ** ** ** [[SQLITE_SUBTYPE]]
    SQLITE_SUBTYPE
    -** The SQLITE_SUBTYPE flag indicates to SQLite that a function may call +** The SQLITE_SUBTYPE flag indicates to SQLite that a function might call ** [sqlite3_value_subtype()] to inspect the sub-types of its arguments. -** Specifying this flag makes no difference for scalar or aggregate user -** functions. However, if it is not specified for a user-defined window -** function, then any sub-types belonging to arguments passed to the window -** function may be discarded before the window function is called (i.e. -** sqlite3_value_subtype() will always return 0). +** This flag instructs SQLite to omit some corner-case optimizations that +** might disrupt the operation of the [sqlite3_value_subtype()] function, +** causing it to return zero rather than the correct subtype(). +** SQL functions that invokes [sqlite3_value_subtype()] should have this +** property. If the SQLITE_SUBTYPE property is omitted, then the return +** value from [sqlite3_value_subtype()] might sometimes be zero even though +** a non-zero subtype was specified by the function argument expression. +** +** [[SQLITE_RESULT_SUBTYPE]]
    SQLITE_RESULT_SUBTYPE
    +** The SQLITE_RESULT_SUBTYPE flag indicates to SQLite that a function might call +** [sqlite3_result_subtype()] to cause a sub-type to be associated with its +** result. +** Every function that invokes [sqlite3_result_subtype()] should have this +** property. If it does not, then the call to [sqlite3_result_subtype()] +** might become a no-op if the function is used as term in an +** [expression index]. On the other hand, SQL functions that never invoke +** [sqlite3_result_subtype()] should avoid setting this property, as the +** purpose of this property is to disable certain optimizations that are +** incompatible with subtypes. **
    ** */ @@ -5587,6 +5601,7 @@ SQLITE_API int sqlite3_create_window_function( #define SQLITE_DIRECTONLY 0x000080000 #define SQLITE_SUBTYPE 0x000100000 #define SQLITE_INNOCUOUS 0x000200000 +#define SQLITE_RESULT_SUBTYPE 0x001000000 /* ** CAPI3REF: Deprecated Functions @@ -5783,6 +5798,12 @@ SQLITE_API int sqlite3_value_encoding(sqlite3_value*); ** information can be used to pass a limited amount of context from ** one SQL function to another. Use the [sqlite3_result_subtype()] ** routine to set the subtype for the return value of an SQL function. +** +** Every [application-defined SQL function] that invoke this interface +** should include the [SQLITE_SUBTYPE] property in the text +** encoding argument when the function is [sqlite3_create_function|registered]. +** If the [SQLITE_SUBTYPE] property is omitted, then sqlite3_value_subtype() +** might return zero instead of the upstream subtype in some corner cases. */ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); @@ -5913,14 +5934,22 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); **
  • ^(when sqlite3_set_auxdata() is invoked again on the same ** parameter)^, or **
  • ^(during the original sqlite3_set_auxdata() call when a memory -** allocation error occurs.)^ +** allocation error occurs.)^ +**
  • ^(during the original sqlite3_set_auxdata() call if the function +** is evaluated during query planning instead of during query execution, +** as sometimes happens with [SQLITE_ENABLE_STAT4].)^ ** -** Note the last bullet in particular. The destructor X in +** Note the last two bullets in particular. The destructor X in ** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the ** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata() ** should be called near the end of the function implementation and the ** function implementation should not make any use of P after -** sqlite3_set_auxdata() has been called. +** sqlite3_set_auxdata() has been called. Furthermore, a call to +** sqlite3_get_auxdata() that occurs immediately after a corresponding call +** to sqlite3_set_auxdata() might still return NULL if an out-of-memory +** condition occurred during the sqlite3_set_auxdata() call or if the +** function is being evaluated during query planning rather than during +** query execution. ** ** ^(In practice, auxiliary data is preserved between function calls for ** function parameters that are compile-time constants, including literal @@ -6194,6 +6223,20 @@ SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); ** higher order bits are discarded. ** The number of subtype bytes preserved by SQLite might increase ** in future releases of SQLite. +** +** Every [application-defined SQL function] that invokes this interface +** should include the [SQLITE_RESULT_SUBTYPE] property in its +** text encoding argument when the SQL function is +** [sqlite3_create_function|registered]. If the [SQLITE_RESULT_SUBTYPE] +** property is omitted from the function that invokes sqlite3_result_subtype(), +** then in some cases the sqlite3_result_subtype() might fail to set +** the result subtype. +** +** If SQLite is compiled with -DSQLITE_STRICT_SUBTYPE=1, then any +** SQL function that invokes the sqlite3_result_subtype() interface +** and that does not have the SQLITE_RESULT_SUBTYPE property will raise +** an error. Future versions of SQLite might enable -DSQLITE_STRICT_SUBTYPE=1 +** by default. */ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int); From ca617b69462d6b513b84881f62468a9294042e06 Mon Sep 17 00:00:00 2001 From: yubiuser Date: Wed, 22 Nov 2023 21:54:46 +0100 Subject: [PATCH 27/27] Address reviewer's comment Co-authored-by: DL6ER Signed-off-by: yubiuser --- src/config/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/config.c b/src/config/config.c index d8b23097..b8eac3d3 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -1453,7 +1453,7 @@ bool readFTLconf(struct config *conf, const bool rewrite) write_dnsmasq_config(conf, false, NULL); write_custom_list(); - return true; + return false; } bool getLogFilePath(void)