Coverage Report

Created: 2025-12-31 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/usage.c
Line
Count
Source
1
/*
2
 * GIT - The information manager from hell
3
 *
4
 * Copyright (C) Linus Torvalds, 2005
5
 */
6
7
#include "git-compat-util.h"
8
#include "gettext.h"
9
#include "trace2.h"
10
#include "strbuf.h"
11
12
static void vfreportf(FILE *f, const char *prefix, const char *err, va_list params)
13
531
{
14
531
  char msg[4096];
15
531
  char *p, *pend = msg + sizeof(msg);
16
531
  size_t prefix_len = strlen(prefix);
17
18
531
  if (sizeof(msg) <= prefix_len) {
19
0
    fprintf(stderr, "BUG!!! too long a prefix '%s'\n", prefix);
20
0
    abort();
21
0
  }
22
531
  memcpy(msg, prefix, prefix_len);
23
531
  p = msg + prefix_len;
24
531
  if (vsnprintf(p, pend - p, err, params) < 0) {
25
0
    fprintf(stderr, _("error: unable to format message: %s\n"),
26
0
      err);
27
0
    *p = '\0'; /* vsnprintf() failed, clip at prefix */
28
0
  }
29
30
24.5k
  for (; p != pend - 1 && *p; p++) {
31
24.0k
    if (iscntrl(*p) && *p != '\t' && *p != '\n')
32
0
      *p = '?';
33
24.0k
  }
34
35
531
  *(p++) = '\n'; /* we no longer need a NUL */
36
531
  fflush(f);
37
531
  write_in_full(fileno(f), msg, p - msg);
38
531
}
39
40
static void vreportf(const char *prefix, const char *err, va_list params)
41
531
{
42
531
  vfreportf(stderr, prefix, err, params);
43
531
}
44
45
static NORETURN void usage_builtin(const char *err, va_list params)
46
0
{
47
0
  vreportf(_("usage: "), err, params);
48
49
  /*
50
   * When we detect a usage error *before* the command dispatch in
51
   * cmd_main(), we don't know what verb to report.  Force it to this
52
   * to facilitate post-processing.
53
   */
54
0
  trace2_cmd_name("_usage_");
55
56
  /*
57
   * Currently, the (err, params) are usually just the static usage
58
   * string which isn't very useful here.  Usually, the call site
59
   * manually calls fprintf(stderr,...) with the actual detailed
60
   * syntax error before calling usage().
61
   *
62
   * TODO It would be nice to update the call sites to pass both
63
   * the static usage string and the detailed error message.
64
   */
65
66
0
  exit(129);
67
0
}
68
69
static void die_message_builtin(const char *err, va_list params)
70
0
{
71
0
  if (!err)
72
0
    return;
73
0
  trace2_cmd_error_va(err, params);
74
0
  vreportf(_("fatal: "), err, params);
75
0
}
76
77
/*
78
 * We call trace2_cmd_error_va() in the below functions first and
79
 * expect it to va_copy 'params' before using it (because an 'ap' can
80
 * only be walked once).
81
 */
82
static NORETURN void die_builtin(const char *err, va_list params)
83
0
{
84
0
  report_fn die_message_fn = get_die_message_routine();
85
86
0
  die_message_fn(err, params);
87
0
  exit(128);
88
0
}
89
90
static void error_builtin(const char *err, va_list params)
91
521
{
92
521
  trace2_cmd_error_va(err, params);
93
94
521
  vreportf(_("error: "), err, params);
95
521
}
96
97
static void warn_builtin(const char *warn, va_list params)
98
10
{
99
10
  trace2_cmd_error_va(warn, params);
100
101
10
  vreportf(_("warning: "), warn, params);
102
10
}
103
104
static int die_is_recursing_builtin(void)
105
0
{
106
0
  static int dying;
107
  /*
108
   * Just an arbitrary number X where "a < x < b" where "a" is
109
   * "maximum number of pthreads we'll ever plausibly spawn" and
110
   * "b" is "something less than Inf", since the point is to
111
   * prevent infinite recursion.
112
   */
113
0
  static const int recursion_limit = 1024;
114
115
0
  dying++;
116
0
  if (dying > recursion_limit) {
117
0
    return 1;
118
0
  } else if (dying == 2) {
119
0
    warning("die() called many times. Recursion error or racy threaded death!");
120
0
    return 0;
121
0
  } else {
122
0
    return 0;
123
0
  }
124
0
}
125
126
/* If we are in a dlopen()ed .so write to a global variable would segfault
127
 * (ugh), so keep things static. */
128
static NORETURN_PTR report_fn usage_routine = usage_builtin;
129
static NORETURN_PTR report_fn die_routine = die_builtin;
130
static report_fn die_message_routine = die_message_builtin;
131
static report_fn error_routine = error_builtin;
132
static report_fn warn_routine = warn_builtin;
133
static int (*die_is_recursing)(void) = die_is_recursing_builtin;
134
135
void set_die_routine(NORETURN_PTR report_fn routine)
136
0
{
137
0
  die_routine = routine;
138
0
}
139
140
report_fn get_die_message_routine(void)
141
0
{
142
0
  return die_message_routine;
143
0
}
144
145
void set_error_routine(report_fn routine)
146
0
{
147
0
  error_routine = routine;
148
0
}
149
150
report_fn get_error_routine(void)
151
0
{
152
0
  return error_routine;
153
0
}
154
155
void set_warn_routine(report_fn routine)
156
0
{
157
0
  warn_routine = routine;
158
0
}
159
160
report_fn get_warn_routine(void)
161
0
{
162
0
  return warn_routine;
163
0
}
164
165
void set_die_is_recursing_routine(int (*routine)(void))
166
0
{
167
0
  die_is_recursing = routine;
168
0
}
169
170
void NORETURN usagef(const char *err, ...)
171
0
{
172
0
  va_list params;
173
174
0
  va_start(params, err);
175
0
  usage_routine(err, params);
176
0
  va_end(params);
177
0
}
178
179
void NORETURN usage(const char *err)
180
0
{
181
0
  usagef("%s", err);
182
0
}
183
184
static void show_usage_if_asked_helper(const char *err, ...)
185
0
{
186
0
  va_list params;
187
188
0
  va_start(params, err);
189
0
  vfreportf(stdout, _("usage: "), err, params);
190
0
  va_end(params);
191
0
  exit(129);
192
0
}
193
194
void show_usage_if_asked(int ac, const char **av, const char *err)
195
0
{
196
0
  if (ac == 2 && (!strcmp(av[1], "-h") ||
197
0
      !strcmp(av[1], "--help-all")))
198
0
    show_usage_if_asked_helper(err);
199
0
}
200
201
void NORETURN die(const char *err, ...)
202
0
{
203
0
  va_list params;
204
205
0
  if (die_is_recursing()) {
206
0
    fputs("fatal: recursion detected in die handler\n", stderr);
207
0
    exit(128);
208
0
  }
209
210
0
  va_start(params, err);
211
0
  die_routine(err, params);
212
0
  va_end(params);
213
0
}
214
215
static const char *fmt_with_err(char *buf, int n, const char *fmt)
216
0
{
217
0
  char str_error[256], *err;
218
0
  size_t i, j;
219
220
0
  err = strerror(errno);
221
0
  for (i = j = 0; err[i] && j < sizeof(str_error) - 1; ) {
222
0
    if ((str_error[j++] = err[i++]) != '%')
223
0
      continue;
224
0
    if (j < sizeof(str_error) - 1) {
225
0
      str_error[j++] = '%';
226
0
    } else {
227
      /* No room to double the '%', so we overwrite it with
228
       * '\0' below */
229
0
      j--;
230
0
      break;
231
0
    }
232
0
  }
233
0
  str_error[j] = 0;
234
  /* Truncation is acceptable here */
235
0
  snprintf(buf, n, "%s: %s", fmt, str_error);
236
0
  return buf;
237
0
}
238
239
void NORETURN die_errno(const char *fmt, ...)
240
0
{
241
0
  char buf[1024];
242
0
  va_list params;
243
244
0
  if (die_is_recursing()) {
245
0
    fputs("fatal: recursion detected in die_errno handler\n",
246
0
      stderr);
247
0
    exit(128);
248
0
  }
249
250
0
  va_start(params, fmt);
251
0
  die_routine(fmt_with_err(buf, sizeof(buf), fmt), params);
252
0
  va_end(params);
253
0
}
254
255
#undef die_message
256
int die_message(const char *err, ...)
257
0
{
258
0
  va_list params;
259
260
0
  va_start(params, err);
261
0
  die_message_routine(err, params);
262
0
  va_end(params);
263
0
  return 128;
264
0
}
265
266
#undef die_message_errno
267
int die_message_errno(const char *fmt, ...)
268
0
{
269
0
  char buf[1024];
270
0
  va_list params;
271
272
0
  va_start(params, fmt);
273
0
  die_message_routine(fmt_with_err(buf, sizeof(buf), fmt), params);
274
0
  va_end(params);
275
0
  return 128;
276
0
}
277
278
#undef error_errno
279
int error_errno(const char *fmt, ...)
280
0
{
281
0
  char buf[1024];
282
0
  va_list params;
283
284
0
  va_start(params, fmt);
285
0
  error_routine(fmt_with_err(buf, sizeof(buf), fmt), params);
286
0
  va_end(params);
287
0
  return -1;
288
0
}
289
290
#undef error
291
int error(const char *err, ...)
292
521
{
293
521
  va_list params;
294
295
521
  va_start(params, err);
296
521
  error_routine(err, params);
297
521
  va_end(params);
298
521
  return -1;
299
521
}
300
301
void warning_errno(const char *warn, ...)
302
0
{
303
0
  char buf[1024];
304
0
  va_list params;
305
306
0
  va_start(params, warn);
307
0
  warn_routine(fmt_with_err(buf, sizeof(buf), warn), params);
308
0
  va_end(params);
309
0
}
310
311
void warning(const char *warn, ...)
312
10
{
313
10
  va_list params;
314
315
10
  va_start(params, warn);
316
10
  warn_routine(warn, params);
317
10
  va_end(params);
318
10
}
319
320
/* Only set this, ever, from t/helper/, when verifying that bugs are caught. */
321
int BUG_exit_code;
322
323
static void BUG_vfl_common(const char *file, int line, const char *fmt,
324
         va_list params)
325
0
{
326
0
  char prefix[256];
327
328
  /* truncation via snprintf is OK here */
329
0
  snprintf(prefix, sizeof(prefix), "BUG: %s:%d: ", file, line);
330
331
0
  vreportf(prefix, fmt, params);
332
0
}
333
334
static NORETURN void BUG_vfl(const char *file, int line, const char *fmt, va_list params)
335
0
{
336
0
  va_list params_copy;
337
0
  static int in_bug;
338
339
0
  va_copy(params_copy, params);
340
0
  BUG_vfl_common(file, line, fmt, params);
341
342
0
  if (in_bug)
343
0
    abort();
344
0
  in_bug = 1;
345
346
0
  trace2_cmd_error_va(fmt, params_copy);
347
348
0
  if (BUG_exit_code)
349
0
    exit(BUG_exit_code);
350
0
  abort();
351
0
}
352
353
NORETURN void BUG_fl(const char *file, int line, const char *fmt, ...)
354
0
{
355
0
  va_list ap;
356
357
0
  bug_called_must_BUG = 0;
358
359
0
  va_start(ap, fmt);
360
0
  BUG_vfl(file, line, fmt, ap);
361
0
  va_end(ap);
362
0
}
363
364
int bug_called_must_BUG;
365
void bug_fl(const char *file, int line, const char *fmt, ...)
366
0
{
367
0
  va_list ap;
368
369
0
  bug_called_must_BUG = 1;
370
371
0
  va_start(ap, fmt);
372
0
  BUG_vfl_common(file, line, fmt, ap);
373
0
  va_end(ap);
374
375
0
  va_start(ap, fmt);
376
0
  trace2_cmd_error_va(fmt, ap);
377
0
  va_end(ap);
378
0
}
379
380
381
NORETURN void you_still_use_that(const char *command_name, const char *hint)
382
0
{
383
0
  struct strbuf percent_encoded = STRBUF_INIT;
384
0
  strbuf_add_percentencode(&percent_encoded,
385
0
         command_name,
386
0
         STRBUF_ENCODE_SLASH);
387
388
0
  fprintf(stderr,
389
0
    _("'%s' is nominated for removal.\n"), command_name);
390
391
0
  if (hint)
392
0
    fputs(hint, stderr);
393
394
  fprintf(stderr,
395
0
    _("If you still use this command, here's what you can do:\n"
396
0
      "\n"
397
0
      "- read https://git-scm.com/docs/BreakingChanges.html\n"
398
0
      "- check if anyone has discussed this on the mailing\n"
399
0
      "  list and if they came up with something that can\n"
400
0
      "  help you: https://lore.kernel.org/git/?q=%s\n"
401
0
      "- send an email to <git@vger.kernel.org> to let us\n"
402
0
      "  know that you still use this command and were unable\n"
403
0
      "  to determine a suitable replacement\n"
404
0
      "\n"),
405
0
    percent_encoded.buf);
406
0
  strbuf_release(&percent_encoded);
407
0
  die(_("refusing to run without --i-still-use-this"));
408
0
}