2016-05-02 08:28:56 +02:00
|
|
|
/* Simple program to search for BOLT references in C files and make sure
|
|
|
|
* they're accurate. */
|
2021-12-04 12:23:56 +01:00
|
|
|
#include "config.h"
|
2016-05-02 08:28:56 +02:00
|
|
|
#include <ccan/err/err.h>
|
|
|
|
#include <ccan/opt/opt.h>
|
|
|
|
#include <ccan/tal/grab_file/grab_file.h>
|
|
|
|
#include <ccan/tal/path/path.h>
|
|
|
|
#include <ccan/tal/str/str.h>
|
2018-04-25 12:55:34 +02:00
|
|
|
#include <common/utils.h>
|
2016-05-02 08:28:56 +02:00
|
|
|
#include <dirent.h>
|
|
|
|
|
|
|
|
static bool verbose = false;
|
|
|
|
|
2016-05-04 08:33:04 +02:00
|
|
|
struct bolt_file {
|
|
|
|
const char *prefix;
|
|
|
|
const char *contents;
|
|
|
|
};
|
|
|
|
|
2016-05-02 08:28:56 +02:00
|
|
|
/* Turn any whitespace into a single space. */
|
|
|
|
static char *canonicalize(char *str)
|
|
|
|
{
|
|
|
|
char *to = str, *from = str;
|
|
|
|
bool have_space = true;
|
|
|
|
|
|
|
|
while (*from) {
|
|
|
|
if (cisspace(*from)) {
|
|
|
|
if (!have_space)
|
|
|
|
*(to++) = ' ';
|
|
|
|
have_space = true;
|
|
|
|
} else {
|
|
|
|
*(to++) = *from;
|
|
|
|
have_space = false;
|
|
|
|
}
|
|
|
|
from++;
|
|
|
|
}
|
|
|
|
if (have_space && to != str)
|
|
|
|
to--;
|
|
|
|
*to = '\0';
|
|
|
|
tal_resize(&str, to + 1 - str);
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
2017-01-04 04:39:20 +01:00
|
|
|
static bool get_files(const char *dir, const char *subdir,
|
2016-05-04 08:33:04 +02:00
|
|
|
struct bolt_file **files)
|
2016-05-02 08:28:56 +02:00
|
|
|
{
|
2016-05-04 08:33:04 +02:00
|
|
|
char *path = path_join(NULL, dir, subdir);
|
|
|
|
DIR *d = opendir(path);
|
2016-05-02 08:28:56 +02:00
|
|
|
struct dirent *e;
|
2016-05-04 08:33:04 +02:00
|
|
|
|
2016-05-02 08:28:56 +02:00
|
|
|
if (!d)
|
2017-01-04 04:39:20 +01:00
|
|
|
return false;
|
2016-05-02 08:28:56 +02:00
|
|
|
|
|
|
|
while ((e = readdir(d)) != NULL) {
|
2016-05-04 08:33:04 +02:00
|
|
|
int preflen;
|
2019-01-15 04:51:27 +01:00
|
|
|
struct bolt_file bf;
|
2016-05-02 08:28:56 +02:00
|
|
|
|
|
|
|
/* Must end in .md */
|
|
|
|
if (!strends(e->d_name, ".md"))
|
|
|
|
continue;
|
|
|
|
|
2016-05-04 08:33:04 +02:00
|
|
|
/* Prefix is anything up to - */
|
|
|
|
preflen = strspn(e->d_name,
|
|
|
|
"0123456789"
|
|
|
|
"abcdefghijklmnopqrstuvwxyz"
|
|
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
|
|
|
if (!preflen)
|
|
|
|
continue;
|
|
|
|
if (preflen + strlen(".md") != strlen(e->d_name)
|
|
|
|
&& e->d_name[preflen] != '-')
|
|
|
|
continue;
|
2016-05-02 08:28:56 +02:00
|
|
|
|
|
|
|
if (verbose)
|
2016-05-04 08:33:04 +02:00
|
|
|
printf("Found bolt %.*s\n", preflen, e->d_name);
|
2016-05-02 08:28:56 +02:00
|
|
|
|
2019-01-15 04:51:27 +01:00
|
|
|
bf.prefix = tal_strndup(*files, e->d_name, preflen);
|
|
|
|
bf.contents
|
2016-05-04 08:33:04 +02:00
|
|
|
= canonicalize(grab_file(*files,
|
|
|
|
path_join(path, path,
|
|
|
|
e->d_name)));
|
2019-01-15 04:51:27 +01:00
|
|
|
tal_arr_expand(files, bf);
|
2016-05-02 08:28:56 +02:00
|
|
|
}
|
2023-01-12 04:51:38 +01:00
|
|
|
closedir(d);
|
2017-01-04 04:39:20 +01:00
|
|
|
return true;
|
2016-05-04 08:33:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct bolt_file *get_bolt_files(const char *dir)
|
|
|
|
{
|
|
|
|
struct bolt_file *bolts = tal_arr(NULL, struct bolt_file, 0);
|
|
|
|
|
2017-01-04 04:39:20 +01:00
|
|
|
if (!get_files(dir, ".", &bolts))
|
|
|
|
err(1, "Opening BOLT dir %s", dir);
|
|
|
|
/* This currently does not exist. */
|
2016-05-04 08:33:04 +02:00
|
|
|
get_files(dir, "early-drafts", &bolts);
|
2016-05-02 08:28:56 +02:00
|
|
|
return bolts;
|
|
|
|
}
|
|
|
|
|
2019-07-24 06:39:29 +02:00
|
|
|
static char *find_bolt_ref(const char *prefix, char **p, size_t *len)
|
2016-05-02 08:28:56 +02:00
|
|
|
{
|
|
|
|
for (;;) {
|
2016-05-04 08:33:04 +02:00
|
|
|
char *bolt, *end;
|
|
|
|
size_t preflen;
|
2016-05-02 08:28:56 +02:00
|
|
|
|
2020-08-31 03:13:25 +02:00
|
|
|
/* Quote is of form 'BOLT #X:' */
|
2019-09-10 04:18:27 +02:00
|
|
|
*p = strchr(*p, '*');
|
2016-05-04 08:33:04 +02:00
|
|
|
if (!*p)
|
2016-05-02 08:28:56 +02:00
|
|
|
return NULL;
|
2019-09-10 04:18:27 +02:00
|
|
|
*p += 1;
|
|
|
|
while (cisspace(**p))
|
|
|
|
(*p)++;
|
|
|
|
if (strncmp(*p, prefix, strlen(prefix)) != 0)
|
|
|
|
continue;
|
2019-07-24 06:39:29 +02:00
|
|
|
*p += strlen(prefix);
|
2016-05-04 08:33:04 +02:00
|
|
|
while (cisspace(**p))
|
|
|
|
(*p)++;
|
|
|
|
if (**p != '#')
|
2016-05-02 08:28:56 +02:00
|
|
|
continue;
|
2016-05-04 08:33:04 +02:00
|
|
|
(*p)++;
|
|
|
|
|
|
|
|
preflen = strcspn(*p, " :");
|
|
|
|
bolt = tal_strndup(NULL, *p, preflen);
|
|
|
|
|
|
|
|
(*p) += preflen;
|
|
|
|
while (cisspace(**p))
|
|
|
|
(*p)++;
|
|
|
|
if (**p != ':')
|
2016-05-02 08:28:56 +02:00
|
|
|
continue;
|
2016-05-04 08:33:04 +02:00
|
|
|
(*p)++;
|
2016-05-02 08:28:56 +02:00
|
|
|
|
2016-05-04 08:33:04 +02:00
|
|
|
end = strstr(*p, "*/");
|
2016-05-02 08:28:56 +02:00
|
|
|
if (!end)
|
2016-05-04 08:33:04 +02:00
|
|
|
*len = strlen(*p);
|
2016-05-02 08:28:56 +02:00
|
|
|
else
|
2016-05-04 08:33:04 +02:00
|
|
|
*len = end - *p;
|
|
|
|
return bolt;
|
2016-05-02 08:28:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-19 01:57:01 +02:00
|
|
|
/* Replace '*' at start of line with whitespace, canonicalize */
|
|
|
|
static char *de_prefix(char *str)
|
2016-05-02 08:28:56 +02:00
|
|
|
{
|
2024-06-19 01:57:01 +02:00
|
|
|
bool start_of_line = true;
|
2016-05-02 08:28:56 +02:00
|
|
|
size_t i;
|
2024-06-19 01:57:01 +02:00
|
|
|
|
|
|
|
for (i = 0; str[i]; i++) {
|
|
|
|
if (start_of_line && str[i] == '*') {
|
|
|
|
str[i] = ' ';
|
|
|
|
start_of_line = false;
|
2016-05-02 08:28:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-19 01:57:01 +02:00
|
|
|
/* Stay start of line until whitespace ends */
|
|
|
|
if (start_of_line)
|
|
|
|
start_of_line = cisspace(str[i]);
|
|
|
|
else
|
|
|
|
start_of_line = (str[i] == '\n');
|
|
|
|
}
|
2016-05-02 08:28:56 +02:00
|
|
|
|
2024-06-19 01:57:01 +02:00
|
|
|
return canonicalize(str);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Take a quote, split it on '...' (trim line prefixes) */
|
|
|
|
static char **split_pattern(const char *code, size_t len)
|
|
|
|
{
|
|
|
|
char **strings = tal_arr(NULL, char *, 0);
|
|
|
|
const char *sep;
|
|
|
|
|
|
|
|
while ((sep = strstr(code, "...")) != NULL) {
|
|
|
|
size_t matchlen = sep - code;
|
|
|
|
if (sep > code + len)
|
|
|
|
break;
|
|
|
|
tal_arr_expand(&strings,
|
|
|
|
de_prefix(tal_strndup(strings, code, matchlen)));
|
|
|
|
code += matchlen + strlen("...");
|
|
|
|
len -= matchlen + strlen("...");
|
2016-05-02 08:28:56 +02:00
|
|
|
}
|
2024-06-19 01:57:01 +02:00
|
|
|
|
|
|
|
tal_arr_expand(&strings, de_prefix(tal_strndup(strings, code, len)));
|
|
|
|
return strings;
|
2016-05-02 08:28:56 +02:00
|
|
|
}
|
|
|
|
|
2016-05-04 08:33:04 +02:00
|
|
|
/* Moves *pos to start of line. */
|
|
|
|
static unsigned linenum(const char *raw, const char **pos)
|
2016-05-02 08:28:56 +02:00
|
|
|
{
|
|
|
|
unsigned line = 0; /* Out-by-one below */
|
2016-05-04 08:33:04 +02:00
|
|
|
const char *l = raw, *point = *pos;
|
2016-05-02 08:28:56 +02:00
|
|
|
|
2016-05-04 08:33:04 +02:00
|
|
|
while (l < point) {
|
|
|
|
*pos = l;
|
2016-05-02 08:28:56 +02:00
|
|
|
l = strchr(l, '\n');
|
|
|
|
line++;
|
|
|
|
if (!l)
|
2016-05-04 08:33:04 +02:00
|
|
|
break;
|
|
|
|
l++;
|
2016-05-02 08:28:56 +02:00
|
|
|
}
|
2016-05-04 08:33:04 +02:00
|
|
|
return line;
|
|
|
|
}
|
2016-05-02 08:28:56 +02:00
|
|
|
|
2016-05-04 08:33:04 +02:00
|
|
|
static void fail_mismatch(const char *filename,
|
2024-06-19 01:57:01 +02:00
|
|
|
const char *raw,
|
|
|
|
const char *pos,
|
|
|
|
size_t len,
|
|
|
|
char **strings,
|
|
|
|
struct bolt_file *bolt)
|
2016-05-04 08:33:04 +02:00
|
|
|
{
|
|
|
|
unsigned line = linenum(raw, &pos);
|
2024-06-19 01:57:01 +02:00
|
|
|
/* If they all match, order must be wrong. */
|
|
|
|
const char *match = NULL;
|
|
|
|
int matchlen;
|
|
|
|
|
|
|
|
/* Figure out which substring didn't match, and how much to cut it */
|
|
|
|
for (size_t i = 0; i < tal_count(strings); i++) {
|
|
|
|
if (strstr(bolt->contents, strings[i]))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* OK, it doesn't match, truncate it until it does */
|
|
|
|
matchlen = strlen(strings[i]);
|
|
|
|
while (matchlen) {
|
|
|
|
match = memmem(bolt->contents, strlen(bolt->contents),
|
|
|
|
strings[i], matchlen);
|
|
|
|
if (match)
|
|
|
|
break;
|
|
|
|
matchlen--;
|
2016-05-02 08:28:56 +02:00
|
|
|
}
|
2024-06-19 01:57:01 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "%s:%u:", filename, line);
|
|
|
|
if (match) {
|
|
|
|
fprintf(stderr, "Closest match: %.*s...[%.20s]\n",
|
|
|
|
matchlen, match, match + matchlen);
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Parts match, but not in this order\n");
|
2016-05-04 08:33:04 +02:00
|
|
|
}
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2024-06-19 01:57:01 +02:00
|
|
|
static bool find_strings(const char *bolttext, char **strings, size_t nstrings)
|
|
|
|
{
|
|
|
|
const char *p = bolttext;
|
|
|
|
char *find;
|
|
|
|
|
|
|
|
if (nstrings == 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
while ((find = strstr(p, strings[0])) != NULL) {
|
|
|
|
if (find_strings(find + strlen(strings[0]), strings+1, nstrings-1))
|
|
|
|
return true;
|
|
|
|
p = find + 1;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-05-04 08:33:04 +02:00
|
|
|
static void fail_nobolt(const char *filename,
|
|
|
|
const char *raw, const char *pos,
|
|
|
|
const char *bolt_prefix)
|
|
|
|
{
|
|
|
|
unsigned line = linenum(raw, &pos);
|
|
|
|
|
|
|
|
fprintf(stderr, "%s:%u:unknown bolt %s\n",
|
|
|
|
filename, line, bolt_prefix);
|
2016-05-02 08:28:56 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
2016-11-11 00:02:04 +01:00
|
|
|
|
2016-05-04 08:33:04 +02:00
|
|
|
static struct bolt_file *find_bolt(const char *bolt_prefix,
|
|
|
|
struct bolt_file *bolts)
|
|
|
|
{
|
|
|
|
size_t i, n = tal_count(bolts);
|
2016-09-02 04:28:18 +02:00
|
|
|
int boltnum;
|
2017-08-22 07:04:03 +02:00
|
|
|
char *endp;
|
2016-05-04 08:33:04 +02:00
|
|
|
|
|
|
|
for (i = 0; i < n; i++)
|
|
|
|
if (streq(bolts[i].prefix, bolt_prefix))
|
|
|
|
return bolts+i;
|
|
|
|
|
|
|
|
/* Now search for numerical match. */
|
2017-08-22 07:04:03 +02:00
|
|
|
boltnum = strtol(bolt_prefix, &endp, 10);
|
|
|
|
if (endp != bolt_prefix && *endp == 0) {
|
2016-05-04 08:33:04 +02:00
|
|
|
for (i = 0; i < n; i++)
|
|
|
|
if (atoi(bolts[i].prefix) == boltnum)
|
|
|
|
return bolts+i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-05-02 08:28:56 +02:00
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
2018-04-25 12:55:34 +02:00
|
|
|
setup_locale();
|
|
|
|
|
2016-05-04 08:33:04 +02:00
|
|
|
struct bolt_file *bolts;
|
2016-05-02 08:28:56 +02:00
|
|
|
int i;
|
2019-07-24 06:39:29 +02:00
|
|
|
char *prefix = "BOLT";
|
2016-05-02 08:28:56 +02:00
|
|
|
|
|
|
|
err_set_progname(argv[0]);
|
|
|
|
|
|
|
|
opt_register_noarg("--help|-h", opt_usage_and_exit,
|
|
|
|
"<bolt-dir> <srcfile>...\n"
|
|
|
|
"A source checker for BOLT RFC references.",
|
|
|
|
"Print this message.");
|
|
|
|
opt_register_noarg("--verbose", opt_set_bool, &verbose,
|
|
|
|
"Print out files as we find them");
|
2019-07-24 06:39:29 +02:00
|
|
|
opt_register_arg("--prefix", opt_set_charp, opt_show_charp, &prefix,
|
|
|
|
"Only check these markers");
|
2016-05-02 08:28:56 +02:00
|
|
|
|
|
|
|
opt_parse(&argc, argv, opt_log_stderr_exit);
|
|
|
|
if (argc < 2)
|
|
|
|
opt_usage_exit_fail("Expected a bolt directory");
|
|
|
|
|
|
|
|
bolts = get_bolt_files(argv[1]);
|
|
|
|
|
|
|
|
for (i = 2; i < argc; i++) {
|
2016-05-04 08:33:04 +02:00
|
|
|
char *f = grab_file(NULL, argv[i]), *p, *bolt;
|
|
|
|
size_t len;
|
2016-05-02 08:28:56 +02:00
|
|
|
if (!f)
|
|
|
|
err(1, "Loading %s", argv[i]);
|
|
|
|
|
|
|
|
if (verbose)
|
|
|
|
printf("Checking %s...\n", argv[i]);
|
|
|
|
|
|
|
|
p = f;
|
2019-07-24 06:39:29 +02:00
|
|
|
while ((bolt = find_bolt_ref(prefix, &p, &len)) != NULL) {
|
2024-06-19 01:57:01 +02:00
|
|
|
char **strings = split_pattern(p, len);
|
2016-05-04 08:33:04 +02:00
|
|
|
struct bolt_file *b = find_bolt(bolt, bolts);
|
|
|
|
if (!b)
|
|
|
|
fail_nobolt(argv[i], f, p, bolt);
|
2024-06-19 01:57:01 +02:00
|
|
|
|
|
|
|
if (!find_strings(b->contents, strings, tal_count(strings)))
|
|
|
|
fail_mismatch(argv[i], f, p, len, strings, b);
|
2016-05-02 08:28:56 +02:00
|
|
|
|
|
|
|
if (verbose)
|
2016-05-04 08:33:04 +02:00
|
|
|
printf(" Found %.10s... in %s\n",
|
|
|
|
p, b->prefix);
|
2016-05-02 08:28:56 +02:00
|
|
|
p += len;
|
|
|
|
}
|
|
|
|
tal_free(f);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|