tool: use our own stderr variable

Earlier this year we changed our own stderr variable to use the standard
name `stderr` (to avoid bugs where someone is using `stderr` instead of
the curl-tool specific variable). This solution needed to override the
standard `stderr` symbol via the preprocessor. This in turn didn't play
well with unity builds and caused curl tool to crash or stay silent due
to an uninitialized stderr. This was a hard to find issue, fixed by
manually breaking out one file from the unity sources.

To avoid two these two tricks, this patch implements a different
solution: Restore using our own local variable for our stderr output and
leave `stderr` as-is. To avoid using `stderr` by mistake, add a
`checksrc` rule (based on logic we already used in lib for `strerror`)
that detects any `stderr` use in `src` and points to using our own
variable instead: `tool_stderr`.

Follow-up to 06133d3e9b
Follow-up to 2f17a9b654

Closes #11958
This commit is contained in:
Viktor Szakats 2023-09-28 10:50:07 +00:00
parent b5bb84cbef
commit e5bb88b8f8
No known key found for this signature in database
GPG Key ID: B5ABD165E2AEF201
19 changed files with 58 additions and 53 deletions

View File

@ -41,7 +41,7 @@ Copyright: Daniel Stenberg, <daniel@haxx.se>, et al.
License: curl License: curl
# checksrc control files # checksrc control files
Files: lib/.checksrc docs/examples/.checksrc tests/libtest/.checksrc Files: lib/.checksrc src/.checksrc docs/examples/.checksrc tests/libtest/.checksrc
Copyright: Daniel Stenberg, <daniel@haxx.se>, et al. Copyright: Daniel Stenberg, <daniel@haxx.se>, et al.
License: curl License: curl

View File

@ -137,6 +137,8 @@ Currently these are the extended warnings which can be enabled:
- `STRERROR`: use of banned function strerror() - `STRERROR`: use of banned function strerror()
- `STDERR`: use of banned variable `stderr`
## Ignore certain warnings ## Ignore certain warnings
Due to the nature of the source code and the flaws of the `checksrc` tool, Due to the nature of the source code and the flaws of the `checksrc` tool,

View File

@ -50,6 +50,7 @@ my @ignore_line;
my %warnings_extended = ( my %warnings_extended = (
'COPYRIGHTYEAR' => 'copyright year incorrect', 'COPYRIGHTYEAR' => 'copyright year incorrect',
'STRERROR', => 'strerror() detected', 'STRERROR', => 'strerror() detected',
'STDERR', => 'stderr detected',
); );
my %warnings = ( my %warnings = (
@ -729,6 +730,18 @@ sub scanfile {
} }
} }
} }
if($warnings{"STDERR"}) {
# scan for use of banned stderr. This is not a BANNEDFUNC to
# allow for individual enable/disable of this warning.
if($l =~ /^([^\"-]*\W)(stderr)[^\"_]/x) {
if($1 !~ /^ *\#/) {
# skip preprocessor lines
checkwarn("STDERR",
$line, length($1), $file, $ol,
"use of $2 is banned (use tool_stderr instead)");
}
}
}
# scan for use of snprintf for curl-internals reasons # scan for use of snprintf for curl-internals reasons
if($l =~ /^(.*\W)(v?snprintf)\s*\(/x) { if($l =~ /^(.*\W)(v?snprintf)\s*\(/x) {
checkwarn("SNPRINTF", checkwarn("SNPRINTF",

1
src/.checksrc Normal file
View File

@ -0,0 +1 @@
enable STDERR

View File

@ -67,11 +67,6 @@ if(BUILD_STATIC_CURL)
set(CURLX_CFILES ${CURLTOOL_LIBCURL_CFILES}) set(CURLX_CFILES ${CURLTOOL_LIBCURL_CFILES})
endif() endif()
# Compile this source separately to avoid having `stderr` overridden in this
# file. Necessary for successfully initializing stderr in unity builds.
# (Not necessary for the libcurltool target.)
set_source_files_properties(tool_stderr.c PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON)
add_executable( add_executable(
${EXE_NAME} ${EXE_NAME}
${CURL_CFILES} ${CURLX_CFILES} ${CURL_HFILES} ${CURL_CFILES} ${CURLX_CFILES} ${CURL_HFILES}

View File

@ -95,7 +95,7 @@ int tool_debug_cb(CURL *handle, curl_infotype type,
{ {
struct OperationConfig *operation = userdata; struct OperationConfig *operation = userdata;
struct GlobalConfig *config = operation->global; struct GlobalConfig *config = operation->global;
FILE *output = stderr; FILE *output = tool_stderr;
const char *text; const char *text;
struct timeval tv; struct timeval tv;
char timebuf[20]; char timebuf[20];
@ -137,7 +137,7 @@ int tool_debug_cb(CURL *handle, curl_infotype type,
config->trace_stream = stdout; config->trace_stream = stdout;
else if(!strcmp("%", config->trace_dump)) else if(!strcmp("%", config->trace_dump))
/* Ok, this is somewhat hackish but we do it undocumented for now */ /* Ok, this is somewhat hackish but we do it undocumented for now */
config->trace_stream = stderr; config->trace_stream = tool_stderr;
else { else {
config->trace_stream = fopen(config->trace_dump, FOPEN_WRITETEXT); config->trace_stream = fopen(config->trace_dump, FOPEN_WRITETEXT);
config->trace_fopened = TRUE; config->trace_fopened = TRUE;
@ -195,7 +195,8 @@ int tool_debug_cb(CURL *handle, curl_infotype type,
to stderr or stdout, we don't display the alert about the data not to stderr or stdout, we don't display the alert about the data not
being shown as the data _is_ shown then just not via this being shown as the data _is_ shown then just not via this
function */ function */
if(!config->isatty || ((output != stderr) && (output != stdout))) { if(!config->isatty ||
((output != tool_stderr) && (output != stdout))) {
if(!newl) if(!newl)
log_line_start(output, timebuf, idsbuf, type); log_line_start(output, timebuf, idsbuf, type);
fprintf(output, "[%zu bytes data]\n", size); fprintf(output, "[%zu bytes data]\n", size);

View File

@ -274,7 +274,7 @@ void progressbarinit(struct ProgressData *bar,
else if(bar->width > MAX_BARLENGTH) else if(bar->width > MAX_BARLENGTH)
bar->width = MAX_BARLENGTH; bar->width = MAX_BARLENGTH;
bar->out = stderr; bar->out = tool_stderr;
bar->tick = 150; bar->tick = 150;
bar->barmove = 1; bar->barmove = 1;
} }

View File

@ -2777,9 +2777,9 @@ ParameterError parse_args(struct GlobalConfig *global, int argc,
const char *reason = param2text(result); const char *reason = param2text(result);
if(orig_opt && strcmp(":", orig_opt)) if(orig_opt && strcmp(":", orig_opt))
helpf(stderr, "option %s: %s", orig_opt, reason); helpf(tool_stderr, "option %s: %s", orig_opt, reason);
else else
helpf(stderr, "%s", reason); helpf(tool_stderr, "%s", reason);
} }
curlx_unicodefree(orig_opt); curlx_unicodefree(orig_opt);

View File

@ -99,7 +99,7 @@ char *getpass_r(const char *prompt, char *buffer, size_t buflen)
char *getpass_r(const char *prompt, char *buffer, size_t buflen) char *getpass_r(const char *prompt, char *buffer, size_t buflen)
{ {
size_t i; size_t i;
fputs(prompt, stderr); fputs(prompt, tool_stderr);
for(i = 0; i < buflen; i++) { for(i = 0; i < buflen; i++) {
buffer[i] = (char)getch(); buffer[i] = (char)getch();
@ -114,7 +114,7 @@ char *getpass_r(const char *prompt, char *buffer, size_t buflen)
i = i - (i >= 1 ? 2 : 1); i = i - (i >= 1 ? 2 : 1);
} }
/* since echo is disabled, print a newline */ /* since echo is disabled, print a newline */
fputs("\n", stderr); fputs("\n", tool_stderr);
/* if user didn't hit ENTER, terminate buffer */ /* if user didn't hit ENTER, terminate buffer */
if(i == buflen) if(i == buflen)
buffer[buflen-1] = '\0'; buffer[buflen-1] = '\0';
@ -184,7 +184,7 @@ char *getpass_r(const char *prompt, /* prompt to display */
disabled = ttyecho(FALSE, fd); /* disable terminal echo */ disabled = ttyecho(FALSE, fd); /* disable terminal echo */
fputs(prompt, stderr); fputs(prompt, tool_stderr);
nread = read(fd, password, buflen); nread = read(fd, password, buflen);
if(nread > 0) if(nread > 0)
password[--nread] = '\0'; /* null-terminate where enter is stored */ password[--nread] = '\0'; /* null-terminate where enter is stored */
@ -193,7 +193,7 @@ char *getpass_r(const char *prompt, /* prompt to display */
if(disabled) { if(disabled) {
/* if echo actually was disabled, add a newline */ /* if echo actually was disabled, add a newline */
fputs("\n", stderr); fputs("\n", tool_stderr);
(void)ttyecho(TRUE, fd); /* enable echo */ (void)ttyecho(TRUE, fd); /* enable echo */
} }

View File

@ -164,7 +164,7 @@ void tool_version_info(void)
{ {
const char *const *builtin; const char *const *builtin;
if(is_debug()) if(is_debug())
fprintf(stderr, "WARNING: this libcurl is Debug-enabled, " fprintf(tool_stderr, "WARNING: this libcurl is Debug-enabled, "
"do not use in production\n\n"); "do not use in production\n\n");
printf(CURL_ID "%s\n", curl_version()); printf(CURL_ID "%s\n", curl_version());

View File

@ -42,7 +42,7 @@
#endif #endif
#ifndef STDERR_FILENO #ifndef STDERR_FILENO
# define STDERR_FILENO fileno(stderr) # define STDERR_FILENO fileno(tool_stderr)
#endif #endif
#endif /* HEADER_CURL_TOOL_MAIN_H */ #endif /* HEADER_CURL_TOOL_MAIN_H */

View File

@ -55,7 +55,7 @@ static void voutf(struct GlobalConfig *config,
ptr = print_buffer; ptr = print_buffer;
while(len > 0) { while(len > 0) {
fputs(prefix, stderr); fputs(prefix, tool_stderr);
if(len > width) { if(len > width) {
size_t cut = width-1; size_t cut = width-1;
@ -68,14 +68,14 @@ static void voutf(struct GlobalConfig *config,
max text width then! */ max text width then! */
cut = width-1; cut = width-1;
(void)fwrite(ptr, cut + 1, 1, stderr); (void)fwrite(ptr, cut + 1, 1, tool_stderr);
fputs("\n", stderr); fputs("\n", tool_stderr);
ptr += cut + 1; /* skip the space too */ ptr += cut + 1; /* skip the space too */
len -= cut + 1; len -= cut + 1;
} }
else { else {
fputs(ptr, stderr); fputs(ptr, tool_stderr);
fputs("\n", stderr); fputs("\n", tool_stderr);
len = 0; len = 0;
} }
} }

View File

@ -304,7 +304,7 @@ static CURLcode pre_transfer(struct GlobalConfig *global,
if((per->infd == -1) || fstat(per->infd, &fileinfo)) if((per->infd == -1) || fstat(per->infd, &fileinfo))
#endif #endif
{ {
helpf(stderr, "Can't open '%s'", per->uploadfile); helpf(tool_stderr, "Can't open '%s'", per->uploadfile);
if(per->infd != -1) { if(per->infd != -1) {
close(per->infd); close(per->infd);
per->infd = STDIN_FILENO; per->infd = STDIN_FILENO;
@ -415,10 +415,10 @@ static CURLcode post_per_transfer(struct GlobalConfig *global,
if(!config->synthetic_error && result && if(!config->synthetic_error && result &&
(!global->silent || global->showerror)) { (!global->silent || global->showerror)) {
const char *msg = per->errorbuffer; const char *msg = per->errorbuffer;
fprintf(stderr, "curl: (%d) %s\n", result, fprintf(tool_stderr, "curl: (%d) %s\n", result,
(msg && msg[0]) ? msg : curl_easy_strerror(result)); (msg && msg[0]) ? msg : curl_easy_strerror(result));
if(result == CURLE_PEER_FAILED_VERIFICATION) if(result == CURLE_PEER_FAILED_VERIFICATION)
fputs(CURL_CA_CERT_ERRORMSG, stderr); fputs(CURL_CA_CERT_ERRORMSG, tool_stderr);
} }
else if(config->failwithbody) { else if(config->failwithbody) {
/* if HTTP response >= 400, return error */ /* if HTTP response >= 400, return error */
@ -426,7 +426,7 @@ static CURLcode post_per_transfer(struct GlobalConfig *global,
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
if(code >= 400) { if(code >= 400) {
if(!global->silent || global->showerror) if(!global->silent || global->showerror)
fprintf(stderr, fprintf(tool_stderr,
"curl: (%d) The requested URL returned error: %ld\n", "curl: (%d) The requested URL returned error: %ld\n",
CURLE_HTTP_RETURNED_ERROR, code); CURLE_HTTP_RETURNED_ERROR, code);
result = CURLE_HTTP_RETURNED_ERROR; result = CURLE_HTTP_RETURNED_ERROR;
@ -864,19 +864,19 @@ clean:
switch(result) { switch(result) {
case CURLE_URL_MALFORMAT: case CURLE_URL_MALFORMAT:
helpf(stderr, "malformed URL. Visit https://curl.se/" helpf(tool_stderr, "malformed URL. Visit https://curl.se/"
"docs/ipfs.html#Gateway-file-and-" "docs/ipfs.html#Gateway-file-and-"
"environment-variable for more " "environment-variable for more "
"information"); "information");
break; break;
case CURLE_FILE_COULDNT_READ_FILE: case CURLE_FILE_COULDNT_READ_FILE:
helpf(stderr, "IPFS automatic gateway detection " helpf(tool_stderr, "IPFS automatic gateway detection "
"failure. Visit https://curl.se/docs/" "failure. Visit https://curl.se/docs/"
"ipfs.html#Malformed-gateway-URL for " "ipfs.html#Malformed-gateway-URL for "
"more information"); "more information");
break; break;
case CURLE_BAD_FUNCTION_ARGUMENT: case CURLE_BAD_FUNCTION_ARGUMENT:
helpf(stderr, "--ipfs-gateway argument results in " helpf(tool_stderr, "--ipfs-gateway argument results in "
"malformed URL. Visit https://curl.se/" "malformed URL. Visit https://curl.se/"
"docs/ipfs.html#Malformed-gateway-URL " "docs/ipfs.html#Malformed-gateway-URL "
"for more information"); "for more information");
@ -1020,7 +1020,7 @@ static CURLcode single_transfer(struct GlobalConfig *global,
/* Unless explicitly shut off */ /* Unless explicitly shut off */
result = glob_url(&inglob, infiles, &state->infilenum, result = glob_url(&inglob, infiles, &state->infilenum,
(!global->silent || global->showerror)? (!global->silent || global->showerror)?
stderr:NULL); tool_stderr:NULL);
if(result) if(result)
break; break;
config->state.inglob = inglob; config->state.inglob = inglob;
@ -1056,7 +1056,7 @@ static CURLcode single_transfer(struct GlobalConfig *global,
expressions and return total number of URLs in pattern set */ expressions and return total number of URLs in pattern set */
result = glob_url(&state->urls, urlnode->url, &state->urlnum, result = glob_url(&state->urls, urlnode->url, &state->urlnum,
(!global->silent || global->showerror)? (!global->silent || global->showerror)?
stderr:NULL); tool_stderr:NULL);
if(result) if(result)
break; break;
urlnum = state->urlnum; urlnum = state->urlnum;
@ -2074,7 +2074,7 @@ static CURLcode single_transfer(struct GlobalConfig *global,
my_setopt(curl, CURLOPT_TIMEVALUE_LARGE, config->condtime); my_setopt(curl, CURLOPT_TIMEVALUE_LARGE, config->condtime);
my_setopt_str(curl, CURLOPT_CUSTOMREQUEST, config->customrequest); my_setopt_str(curl, CURLOPT_CUSTOMREQUEST, config->customrequest);
customrequest_helper(config, config->httpreq, config->customrequest); customrequest_helper(config, config->httpreq, config->customrequest);
my_setopt(curl, CURLOPT_STDERR, stderr); my_setopt(curl, CURLOPT_STDERR, tool_stderr);
/* three new ones in libcurl 7.3: */ /* three new ones in libcurl 7.3: */
my_setopt_str(curl, CURLOPT_INTERFACE, config->iface); my_setopt_str(curl, CURLOPT_INTERFACE, config->iface);
@ -2745,7 +2745,7 @@ static CURLcode transfer_per_config(struct GlobalConfig *global,
/* Check we have a url */ /* Check we have a url */
if(!config->url_list || !config->url_list->url) { if(!config->url_list || !config->url_list->url) {
helpf(stderr, "(%d) no URL specified", CURLE_FAILED_INIT); helpf(tool_stderr, "(%d) no URL specified", CURLE_FAILED_INIT);
return CURLE_FAILED_INIT; return CURLE_FAILED_INIT;
} }
@ -2922,7 +2922,7 @@ CURLcode operate(struct GlobalConfig *global, int argc, argv_item_t argv[])
/* If we had no arguments then make sure a url was specified in .curlrc */ /* If we had no arguments then make sure a url was specified in .curlrc */
if((argc < 2) && (!global->first->url_list)) { if((argc < 2) && (!global->first->url_list)) {
helpf(stderr, NULL); helpf(tool_stderr, NULL);
result = CURLE_FAILED_INIT; result = CURLE_FAILED_INIT;
} }
} }

View File

@ -168,7 +168,7 @@ int parseconfig(const char *filename, struct GlobalConfig *global)
*line++ = '\0'; /* null-terminate, we have a local copy of the data */ *line++ = '\0'; /* null-terminate, we have a local copy of the data */
#ifdef DEBUG_CONFIG #ifdef DEBUG_CONFIG
fprintf(stderr, "GOT: %s\n", option); fprintf(tool_stderr, "GOT: %s\n", option);
#endif #endif
/* pass spaces and separator(s) */ /* pass spaces and separator(s) */
@ -221,7 +221,7 @@ int parseconfig(const char *filename, struct GlobalConfig *global)
} }
#ifdef DEBUG_CONFIG #ifdef DEBUG_CONFIG
fprintf(stderr, "PARAM: \"%s\"\n",(param ? param : "(null)")); fprintf(tool_stderr, "PARAM: \"%s\"\n",(param ? param : "(null)"));
#endif #endif
res = getparameter(option, param, NULL, &usedarg, global, operation); res = getparameter(option, param, NULL, &usedarg, global, operation);
operation = global->last; operation = global->last;

View File

@ -173,7 +173,7 @@ bool progress_meter(struct GlobalConfig *global,
header = TRUE; header = TRUE;
fputs("DL% UL% Dled Uled Xfers Live " fputs("DL% UL% Dled Uled Xfers Live "
"Total Current Left Speed\n", "Total Current Left Speed\n",
stderr); tool_stderr);
} }
if(final || (diff > 500)) { if(final || (diff > 500)) {
char time_left[10]; char time_left[10];
@ -275,7 +275,7 @@ bool progress_meter(struct GlobalConfig *global,
} }
time2str(time_spent, spent); time2str(time_spent, spent);
fprintf(stderr, fprintf(tool_stderr,
"\r" "\r"
"%-3s " /* percent downloaded */ "%-3s " /* percent downloaded */
"%-3s " /* percent uploaded */ "%-3s " /* percent uploaded */

View File

@ -39,13 +39,6 @@
extern FILE *tool_stderr; extern FILE *tool_stderr;
#if !defined(CURL_DO_NOT_OVERRIDE_STDERR) && !defined(UNITTESTS)
#ifdef stderr
#undef stderr
#endif
#define stderr tool_stderr
#endif
/* /*
* curl tool certainly uses libcurl's external interface. * curl tool certainly uses libcurl's external interface.
*/ */

View File

@ -22,20 +22,17 @@
* *
***************************************************************************/ ***************************************************************************/
/* In this file, stdio.h's stderr macro is not overridden. */
#define CURL_DO_NOT_OVERRIDE_STDERR
#include "tool_setup.h" #include "tool_setup.h"
#include "tool_stderr.h" #include "tool_stderr.h"
#include "tool_msgs.h" #include "tool_msgs.h"
#include "memdebug.h" /* keep this as LAST include */ #include "memdebug.h" /* keep this as LAST include */
/* In other tool files stderr is defined as tool_stderr by tool_setup.h */
FILE *tool_stderr; FILE *tool_stderr;
void tool_init_stderr(void) void tool_init_stderr(void)
{ {
/* !checksrc! disable STDERR 1 */
tool_stderr = stderr; tool_stderr = stderr;
} }
@ -62,11 +59,13 @@ void tool_set_stderr_file(struct GlobalConfig *global, char *filename)
/* freopen the actual stderr (stdio.h stderr) instead of tool_stderr since /* freopen the actual stderr (stdio.h stderr) instead of tool_stderr since
the latter may be set to stdout. */ the latter may be set to stdout. */
/* !checksrc! disable STDERR 1 */
fp = freopen(filename, FOPEN_WRITETEXT, stderr); fp = freopen(filename, FOPEN_WRITETEXT, stderr);
if(!fp) { if(!fp) {
/* stderr may have been closed by freopen. there is nothing to be done. */ /* stderr may have been closed by freopen. there is nothing to be done. */
DEBUGASSERT(0); DEBUGASSERT(0);
return; return;
} }
/* !checksrc! disable STDERR 1 */
tool_stderr = stderr; tool_stderr = stderr;
} }

View File

@ -668,7 +668,7 @@ CURLcode glob_match_url(char **result, char *filename, struct URLGlob *glob)
appendlen = strlen(numbuf); appendlen = strlen(numbuf);
break; break;
default: default:
fprintf(stderr, "internal error: invalid pattern type (%d)\n", fprintf(tool_stderr, "internal error: invalid pattern type (%d)\n",
(int)pat->type); (int)pat->type);
curlx_dyn_free(&dyn); curlx_dyn_free(&dyn);
return CURLE_FAILED_INIT; return CURLE_FAILED_INIT;

View File

@ -566,7 +566,7 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per,
if(fclose_stream) if(fclose_stream)
fclose(stream); fclose(stream);
fclose_stream = FALSE; fclose_stream = FALSE;
stream = stderr; stream = tool_stderr;
break; break;
case VAR_JSON: case VAR_JSON:
ourWriteOutJSON(stream, variables, per, per_result); ourWriteOutJSON(stream, variables, per, per_result);
@ -583,7 +583,8 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per,
} }
} }
if(!match) { if(!match) {
fprintf(stderr, "curl: unknown --write-out variable: '%.*s'\n", fprintf(tool_stderr,
"curl: unknown --write-out variable: '%.*s'\n",
(int)vlen, ptr); (int)vlen, ptr);
} }
ptr = end + 1; /* pass the end */ ptr = end + 1; /* pass the end */