The `OPTION_INTEGER` option type accepts a signed integer. The type of
the underlying integer is a simple `int`, which restricts the range of
values accepted by such options. But there is a catch: because the
caller provides a pointer to the value via the `.value` field, which is
a simple void pointer. This has two consequences:
- There is no check whether the passed value is sufficiently long to
store the entire range of `int`. This can lead to integer wraparound
in the best case and out-of-bounds writes in the worst case.
- Even when a caller knows that they want to store a value larger than
`INT_MAX` they don't have a way to do so.
In practice this doesn't tend to be a huge issue because users typically
don't end up passing huge values to most commands. But the parsing logic
is demonstrably broken, and it is too easy to get the calling convention
wrong.
Improve the situation by introducing a new `precision` field into the
structure. This field gets assigned automatically by `OPT_INTEGER_F()`
and tracks the size of the passed value. Like this it becomes possible
for the caller to pass arbitrarily-sized integers and the underlying
logic knows to handle it correctly by doing range checks. Furthermore,
convert the code to use `strtoimax()` intstead of `strtol()` so that we
can also parse values larger than `LONG_MAX`.
Note that we do not yet assert signedness of the passed variable, which
is another source of bugs. This will be handled in a subsequent commit.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
92 lines
2.4 KiB
C
92 lines
2.4 KiB
C
#define USE_THE_REPOSITORY_VARIABLE
|
|
#include "builtin.h"
|
|
#include "config.h"
|
|
#include "fmt-merge-msg.h"
|
|
#include "gettext.h"
|
|
#include "parse-options.h"
|
|
|
|
static const char * const fmt_merge_msg_usage[] = {
|
|
N_("git fmt-merge-msg [-m <message>] [--log[=<n>] | --no-log] [--file <file>]"),
|
|
NULL
|
|
};
|
|
|
|
int cmd_fmt_merge_msg(int argc,
|
|
const char **argv,
|
|
const char *prefix,
|
|
struct repository *repo UNUSED)
|
|
{
|
|
char *inpath = NULL;
|
|
const char *message = NULL;
|
|
char *into_name = NULL;
|
|
int shortlog_len = -1;
|
|
struct option options[] = {
|
|
{
|
|
.type = OPTION_INTEGER,
|
|
.long_name = "log",
|
|
.value = &shortlog_len,
|
|
.precision = sizeof(shortlog_len),
|
|
.argh = N_("n"),
|
|
.help = N_("populate log with at most <n> entries from shortlog"),
|
|
.flags = PARSE_OPT_OPTARG,
|
|
.defval = DEFAULT_MERGE_LOG_LEN,
|
|
},
|
|
{
|
|
.type = OPTION_INTEGER,
|
|
.long_name = "summary",
|
|
.value = &shortlog_len,
|
|
.precision = sizeof(shortlog_len),
|
|
.argh = N_("n"),
|
|
.help = N_("alias for --log (deprecated)"),
|
|
.flags = PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN,
|
|
.defval = DEFAULT_MERGE_LOG_LEN,
|
|
},
|
|
OPT_STRING('m', "message", &message, N_("text"),
|
|
N_("use <text> as start of message")),
|
|
OPT_STRING(0, "into-name", &into_name, N_("name"),
|
|
N_("use <name> instead of the real target branch")),
|
|
OPT_FILENAME('F', "file", &inpath, N_("file to read from")),
|
|
OPT_END()
|
|
};
|
|
|
|
FILE *in = stdin;
|
|
struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
|
|
int ret;
|
|
struct fmt_merge_msg_opts opts;
|
|
|
|
git_config(fmt_merge_msg_config, NULL);
|
|
argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,
|
|
0);
|
|
if (argc > 0)
|
|
usage_with_options(fmt_merge_msg_usage, options);
|
|
if (shortlog_len < 0)
|
|
shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
|
|
|
|
if (inpath && strcmp(inpath, "-")) {
|
|
in = fopen(inpath, "r");
|
|
if (!in)
|
|
die_errno("cannot open '%s'", inpath);
|
|
}
|
|
|
|
if (strbuf_read(&input, fileno(in), 0) < 0)
|
|
die_errno("could not read input file");
|
|
|
|
if (message)
|
|
strbuf_addstr(&output, message);
|
|
|
|
memset(&opts, 0, sizeof(opts));
|
|
opts.add_title = !message;
|
|
opts.credit_people = 1;
|
|
opts.shortlog_len = shortlog_len;
|
|
opts.into_name = into_name;
|
|
|
|
ret = fmt_merge_msg(&input, &output, &opts);
|
|
if (ret)
|
|
return ret;
|
|
write_in_full(STDOUT_FILENO, output.buf, output.len);
|
|
|
|
strbuf_release(&input);
|
|
strbuf_release(&output);
|
|
free(inpath);
|
|
return 0;
|
|
}
|