From a833b714c8669740387ad2eb875fe7867be407b7 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Mon, 29 Jan 2024 16:09:03 +0800 Subject: [PATCH 01/10] lib/tomlc99: Import tomlc99 TOML parser Import a copy of the tomlc99 parser, somewhat temporarily, from upstream commit 5221b3d3. We import the toml.[ch] sources, the LICENSE, and add a short readme for the upstream reference. Signed-off-by: Jeremy Kerr --- lib/tomlc99/LICENSE | 22 + lib/tomlc99/README | 6 + lib/tomlc99/toml.c | 2392 +++++++++++++++++++++++++++++++++++++++++++ lib/tomlc99/toml.h | 175 ++++ 4 files changed, 2595 insertions(+) create mode 100644 lib/tomlc99/LICENSE create mode 100644 lib/tomlc99/README create mode 100644 lib/tomlc99/toml.c create mode 100644 lib/tomlc99/toml.h diff --git a/lib/tomlc99/LICENSE b/lib/tomlc99/LICENSE new file mode 100644 index 0000000..bb09e49 --- /dev/null +++ b/lib/tomlc99/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) CK Tan +https://github.com/cktan/tomlc99 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/tomlc99/README b/lib/tomlc99/README new file mode 100644 index 0000000..9507f0c --- /dev/null +++ b/lib/tomlc99/README @@ -0,0 +1,6 @@ +This directory includes an export of the tomlc99 library, hosted at +https://github.com/cktan/tomlc99, at commit 5221b3d3. We only use the toml.c +and toml.h files from that repository. + +tomlc99 is released under the MIT license - see LICENSE in this directory for +full license text. diff --git a/lib/tomlc99/toml.c b/lib/tomlc99/toml.c new file mode 100644 index 0000000..e7b878e --- /dev/null +++ b/lib/tomlc99/toml.c @@ -0,0 +1,2392 @@ +/* + + MIT License + + Copyright (c) CK Tan + https://github.com/cktan/tomlc99 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +*/ +#define _POSIX_C_SOURCE 200809L +#include "toml.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static void *(*ppmalloc)(size_t) = malloc; +static void (*ppfree)(void *) = free; + +void toml_set_memutil(void *(*xxmalloc)(size_t), void (*xxfree)(void *)) { + if (xxmalloc) + ppmalloc = xxmalloc; + if (xxfree) + ppfree = xxfree; +} + +#define ALIGN8(sz) (((sz) + 7) & ~7) +#define MALLOC(a) ppmalloc(a) +#define FREE(a) ppfree(a) + +#define malloc(x) error - forbidden - use MALLOC instead +#define free(x) error - forbidden - use FREE instead +#define calloc(x, y) error - forbidden - use CALLOC instead + +static void *CALLOC(size_t nmemb, size_t sz) { + int nb = ALIGN8(sz) * nmemb; + void *p = MALLOC(nb); + if (p) { + memset(p, 0, nb); + } + return p; +} + +// some old platforms define strdup macro -- drop it. +#undef strdup +#define strdup(x) error - forbidden - use STRDUP instead + +static char *STRDUP(const char *s) { + int len = strlen(s); + char *p = MALLOC(len + 1); + if (p) { + memcpy(p, s, len); + p[len] = 0; + } + return p; +} + +// some old platforms define strndup macro -- drop it. +#undef strndup +#define strndup(x) error - forbiden - use STRNDUP instead + +static char *STRNDUP(const char *s, size_t n) { + size_t len = strnlen(s, n); + char *p = MALLOC(len + 1); + if (p) { + memcpy(p, s, len); + p[len] = 0; + } + return p; +} + +/** + * Convert a char in utf8 into UCS, and store it in *ret. + * Return #bytes consumed or -1 on failure. + */ +int toml_utf8_to_ucs(const char *orig, int len, int64_t *ret) { + const unsigned char *buf = (const unsigned char *)orig; + unsigned i = *buf++; + int64_t v; + + /* 0x00000000 - 0x0000007F: + 0xxxxxxx + */ + if (0 == (i >> 7)) { + if (len < 1) + return -1; + v = i; + return *ret = v, 1; + } + /* 0x00000080 - 0x000007FF: + 110xxxxx 10xxxxxx + */ + if (0x6 == (i >> 5)) { + if (len < 2) + return -1; + v = i & 0x1f; + for (int j = 0; j < 1; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + + /* 0x00000800 - 0x0000FFFF: + 1110xxxx 10xxxxxx 10xxxxxx + */ + if (0xE == (i >> 4)) { + if (len < 3) + return -1; + v = i & 0x0F; + for (int j = 0; j < 2; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + + /* 0x00010000 - 0x001FFFFF: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x1E == (i >> 3)) { + if (len < 4) + return -1; + v = i & 0x07; + for (int j = 0; j < 3; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + + /* 0x00200000 - 0x03FFFFFF: + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x3E == (i >> 2)) { + if (len < 5) + return -1; + v = i & 0x03; + for (int j = 0; j < 4; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + + /* 0x04000000 - 0x7FFFFFFF: + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x7e == (i >> 1)) { + if (len < 6) + return -1; + v = i & 0x01; + for (int j = 0; j < 5; j++) { + i = *buf++; + if (0x2 != (i >> 6)) + return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char *)buf - orig; + } + return -1; +} + +/** + * Convert a UCS char to utf8 code, and return it in buf. + * Return #bytes used in buf to encode the char, or + * -1 on error. + */ +int toml_ucs_to_utf8(int64_t code, char buf[6]) { + /* http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16 + */ + /* The UCS code values 0xd800–0xdfff (UTF-16 surrogates) as well + * as 0xfffe and 0xffff (UCS noncharacters) should not appear in + * conforming UTF-8 streams. + */ + if (0xd800 <= code && code <= 0xdfff) + return -1; + if (0xfffe <= code && code <= 0xffff) + return -1; + + /* 0x00000000 - 0x0000007F: + 0xxxxxxx + */ + if (code < 0) + return -1; + if (code <= 0x7F) { + buf[0] = (unsigned char)code; + return 1; + } + + /* 0x00000080 - 0x000007FF: + 110xxxxx 10xxxxxx + */ + if (code <= 0x000007FF) { + buf[0] = (unsigned char)(0xc0 | (code >> 6)); + buf[1] = (unsigned char)(0x80 | (code & 0x3f)); + return 2; + } + + /* 0x00000800 - 0x0000FFFF: + 1110xxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x0000FFFF) { + buf[0] = (unsigned char)(0xe0 | (code >> 12)); + buf[1] = (unsigned char)(0x80 | ((code >> 6) & 0x3f)); + buf[2] = (unsigned char)(0x80 | (code & 0x3f)); + return 3; + } + + /* 0x00010000 - 0x001FFFFF: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x001FFFFF) { + buf[0] = (unsigned char)(0xf0 | (code >> 18)); + buf[1] = (unsigned char)(0x80 | ((code >> 12) & 0x3f)); + buf[2] = (unsigned char)(0x80 | ((code >> 6) & 0x3f)); + buf[3] = (unsigned char)(0x80 | (code & 0x3f)); + return 4; + } + + /* 0x00200000 - 0x03FFFFFF: + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x03FFFFFF) { + buf[0] = (unsigned char)(0xf8 | (code >> 24)); + buf[1] = (unsigned char)(0x80 | ((code >> 18) & 0x3f)); + buf[2] = (unsigned char)(0x80 | ((code >> 12) & 0x3f)); + buf[3] = (unsigned char)(0x80 | ((code >> 6) & 0x3f)); + buf[4] = (unsigned char)(0x80 | (code & 0x3f)); + return 5; + } + + /* 0x04000000 - 0x7FFFFFFF: + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x7FFFFFFF) { + buf[0] = (unsigned char)(0xfc | (code >> 30)); + buf[1] = (unsigned char)(0x80 | ((code >> 24) & 0x3f)); + buf[2] = (unsigned char)(0x80 | ((code >> 18) & 0x3f)); + buf[3] = (unsigned char)(0x80 | ((code >> 12) & 0x3f)); + buf[4] = (unsigned char)(0x80 | ((code >> 6) & 0x3f)); + buf[5] = (unsigned char)(0x80 | (code & 0x3f)); + return 6; + } + + return -1; +} + +/* + * TOML has 3 data structures: value, array, table. + * Each of them can have identification key. + */ +typedef struct toml_keyval_t toml_keyval_t; +struct toml_keyval_t { + const char *key; /* key to this value */ + const char *val; /* the raw value */ +}; + +typedef struct toml_arritem_t toml_arritem_t; +struct toml_arritem_t { + int valtype; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, + 'D'ate, 'T'imestamp */ + char *val; + toml_array_t *arr; + toml_table_t *tab; +}; + +struct toml_array_t { + const char *key; /* key to this array */ + int kind; /* element kind: 'v'alue, 'a'rray, or 't'able, 'm'ixed */ + int type; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, + 'D'ate, 'T'imestamp, 'm'ixed */ + + int nitem; /* number of elements */ + toml_arritem_t *item; +}; + +struct toml_table_t { + const char *key; /* key to this table */ + bool implicit; /* table was created implicitly */ + bool readonly; /* no more modification allowed */ + + /* key-values in the table */ + int nkval; + toml_keyval_t **kval; + + /* arrays in the table */ + int narr; + toml_array_t **arr; + + /* tables in the table */ + int ntab; + toml_table_t **tab; +}; + +static inline void xfree(const void *x) { + if (x) + FREE((void *)(intptr_t)x); +} + +enum tokentype_t { + INVALID, + DOT, + COMMA, + EQUAL, + LBRACE, + RBRACE, + NEWLINE, + LBRACKET, + RBRACKET, + STRING, +}; +typedef enum tokentype_t tokentype_t; + +typedef struct token_t token_t; +struct token_t { + tokentype_t tok; + int lineno; + char *ptr; /* points into context->start */ + int len; + int eof; +}; + +typedef struct context_t context_t; +struct context_t { + char *start; + char *stop; + char *errbuf; + int errbufsz; + + token_t tok; + toml_table_t *root; + toml_table_t *curtab; + + struct { + int top; + char *key[10]; + token_t tok[10]; + } tpath; +}; + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define FLINE __FILE__ ":" TOSTRING(__LINE__) + +static int next_token(context_t *ctx, int dotisspecial); + +/* + Error reporting. Call when an error is detected. Always return -1. +*/ +static int e_outofmemory(context_t *ctx, const char *fline) { + snprintf(ctx->errbuf, ctx->errbufsz, "ERROR: out of memory (%s)", fline); + return -1; +} + +static int e_internal(context_t *ctx, const char *fline) { + snprintf(ctx->errbuf, ctx->errbufsz, "internal error (%s)", fline); + return -1; +} + +static int e_syntax(context_t *ctx, int lineno, const char *msg) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); + return -1; +} + +static int e_badkey(context_t *ctx, int lineno) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: bad key", lineno); + return -1; +} + +static int e_keyexists(context_t *ctx, int lineno) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: key exists", lineno); + return -1; +} + +static int e_forbid(context_t *ctx, int lineno, const char *msg) { + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); + return -1; +} + +static void *expand(void *p, int sz, int newsz) { + void *s = MALLOC(newsz); + if (!s) + return 0; + + if (p) { + memcpy(s, p, sz); + FREE(p); + } + return s; +} + +static void **expand_ptrarr(void **p, int n) { + void **s = MALLOC((n + 1) * sizeof(void *)); + if (!s) + return 0; + + s[n] = 0; + if (p) { + memcpy(s, p, n * sizeof(void *)); + FREE(p); + } + return s; +} + +static toml_arritem_t *expand_arritem(toml_arritem_t *p, int n) { + toml_arritem_t *pp = expand(p, n * sizeof(*p), (n + 1) * sizeof(*p)); + if (!pp) + return 0; + + memset(&pp[n], 0, sizeof(pp[n])); + return pp; +} + +static char *norm_lit_str(const char *src, int srclen, int multiline, + char *errbuf, int errbufsz) { + char *dst = 0; /* will write to dst[] and return it */ + int max = 0; /* max size of dst[] */ + int off = 0; /* cur offset in dst[] */ + const char *sp = src; + const char *sq = src + srclen; + int ch; + + /* scan forward on src */ + for (;;) { + if (off >= max - 10) { /* have some slack for misc stuff */ + int newmax = max + 50; + char *x = expand(dst, max, newmax); + if (!x) { + xfree(dst); + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + dst = x; + max = newmax; + } + + /* finished? */ + if (sp >= sq) + break; + + ch = *sp++; + /* control characters other than tab is not allowed */ + if ((0 <= ch && ch <= 0x08) || (0x0a <= ch && ch <= 0x1f) || (ch == 0x7f)) { + if (!(multiline && (ch == '\r' || ch == '\n'))) { + xfree(dst); + snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); + return 0; + } + } + + // a plain copy suffice + dst[off++] = ch; + } + + dst[off++] = 0; + return dst; +} + +/* + * Convert src to raw unescaped utf-8 string. + * Returns NULL if error with errmsg in errbuf. + */ +static char *norm_basic_str(const char *src, int srclen, int multiline, + char *errbuf, int errbufsz) { + char *dst = 0; /* will write to dst[] and return it */ + int max = 0; /* max size of dst[] */ + int off = 0; /* cur offset in dst[] */ + const char *sp = src; + const char *sq = src + srclen; + int ch; + + /* scan forward on src */ + for (;;) { + if (off >= max - 10) { /* have some slack for misc stuff */ + int newmax = max + 50; + char *x = expand(dst, max, newmax); + if (!x) { + xfree(dst); + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + dst = x; + max = newmax; + } + + /* finished? */ + if (sp >= sq) + break; + + ch = *sp++; + if (ch != '\\') { + /* these chars must be escaped: U+0000 to U+0008, U+000A to U+001F, U+007F + */ + if ((0 <= ch && ch <= 0x08) || (0x0a <= ch && ch <= 0x1f) || + (ch == 0x7f)) { + if (!(multiline && (ch == '\r' || ch == '\n'))) { + xfree(dst); + snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); + return 0; + } + } + + // a plain copy suffice + dst[off++] = ch; + continue; + } + + /* ch was backslash. we expect the escape char. */ + if (sp >= sq) { + snprintf(errbuf, errbufsz, "last backslash is invalid"); + xfree(dst); + return 0; + } + + /* for multi-line, we want to kill line-ending-backslash ... */ + if (multiline) { + + // if there is only whitespace after the backslash ... + if (sp[strspn(sp, " \t\r")] == '\n') { + /* skip all the following whitespaces */ + sp += strspn(sp, " \t\r\n"); + continue; + } + } + + /* get the escaped char */ + ch = *sp++; + switch (ch) { + case 'u': + case 'U': { + int64_t ucs = 0; + int nhex = (ch == 'u' ? 4 : 8); + for (int i = 0; i < nhex; i++) { + if (sp >= sq) { + snprintf(errbuf, errbufsz, "\\%c expects %d hex chars", ch, nhex); + xfree(dst); + return 0; + } + ch = *sp++; + int v = ('0' <= ch && ch <= '9') + ? ch - '0' + : (('A' <= ch && ch <= 'F') ? ch - 'A' + 10 : -1); + if (-1 == v) { + snprintf(errbuf, errbufsz, "invalid hex chars for \\u or \\U"); + xfree(dst); + return 0; + } + ucs = ucs * 16 + v; + } + int n = toml_ucs_to_utf8(ucs, &dst[off]); + if (-1 == n) { + snprintf(errbuf, errbufsz, "illegal ucs code in \\u or \\U"); + xfree(dst); + return 0; + } + off += n; + } + continue; + + case 'b': + ch = '\b'; + break; + case 't': + ch = '\t'; + break; + case 'n': + ch = '\n'; + break; + case 'f': + ch = '\f'; + break; + case 'r': + ch = '\r'; + break; + case '"': + ch = '"'; + break; + case '\\': + ch = '\\'; + break; + default: + snprintf(errbuf, errbufsz, "illegal escape char \\%c", ch); + xfree(dst); + return 0; + } + + dst[off++] = ch; + } + + // Cap with NUL and return it. + dst[off++] = 0; + return dst; +} + +/* Normalize a key. Convert all special chars to raw unescaped utf-8 chars. */ +static char *normalize_key(context_t *ctx, token_t strtok) { + const char *sp = strtok.ptr; + const char *sq = strtok.ptr + strtok.len; + int lineno = strtok.lineno; + char *ret; + int ch = *sp; + char ebuf[80]; + + /* handle quoted string */ + if (ch == '\'' || ch == '\"') { + /* if ''' or """, take 3 chars off front and back. Else, take 1 char off. */ + int multiline = 0; + if (sp[1] == ch && sp[2] == ch) { + sp += 3, sq -= 3; + multiline = 1; + } else + sp++, sq--; + + if (ch == '\'') { + /* for single quote, take it verbatim. */ + if (!(ret = STRNDUP(sp, sq - sp))) { + e_outofmemory(ctx, FLINE); + return 0; + } + } else { + /* for double quote, we need to normalize */ + ret = norm_basic_str(sp, sq - sp, multiline, ebuf, sizeof(ebuf)); + if (!ret) { + e_syntax(ctx, lineno, ebuf); + return 0; + } + } + + /* newlines are not allowed in keys */ + if (strchr(ret, '\n')) { + xfree(ret); + e_badkey(ctx, lineno); + return 0; + } + return ret; + } + + /* for bare-key allow only this regex: [A-Za-z0-9_-]+ */ + const char *xp; + for (xp = sp; xp != sq; xp++) { + int k = *xp; + if (isalnum(k)) + continue; + if (k == '_' || k == '-') + continue; + e_badkey(ctx, lineno); + return 0; + } + + /* dup and return it */ + if (!(ret = STRNDUP(sp, sq - sp))) { + e_outofmemory(ctx, FLINE); + return 0; + } + return ret; +} + +/* + * Look up key in tab. Return 0 if not found, or + * 'v'alue, 'a'rray or 't'able depending on the element. + */ +static int check_key(toml_table_t *tab, const char *key, + toml_keyval_t **ret_val, toml_array_t **ret_arr, + toml_table_t **ret_tab) { + int i; + void *dummy; + + if (!ret_tab) + ret_tab = (toml_table_t **)&dummy; + if (!ret_arr) + ret_arr = (toml_array_t **)&dummy; + if (!ret_val) + ret_val = (toml_keyval_t **)&dummy; + + *ret_tab = 0; + *ret_arr = 0; + *ret_val = 0; + + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) { + *ret_val = tab->kval[i]; + return 'v'; + } + } + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) { + *ret_arr = tab->arr[i]; + return 'a'; + } + } + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) { + *ret_tab = tab->tab[i]; + return 't'; + } + } + return 0; +} + +static int key_kind(toml_table_t *tab, const char *key) { + return check_key(tab, key, 0, 0, 0); +} + +/* Create a keyval in the table. + */ +static toml_keyval_t *create_keyval_in_table(context_t *ctx, toml_table_t *tab, + token_t keytok) { + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char *newkey = normalize_key(ctx, keytok); + if (!newkey) + return 0; + + /* if key exists: error out. */ + toml_keyval_t *dest = 0; + if (key_kind(tab, newkey)) { + xfree(newkey); + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* make a new entry */ + int n = tab->nkval; + toml_keyval_t **base; + if (0 == (base = (toml_keyval_t **)expand_ptrarr((void **)tab->kval, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->kval = base; + + if (0 == (base[n] = (toml_keyval_t *)CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + dest = tab->kval[tab->nkval++]; + + /* save the key in the new value struct */ + dest->key = newkey; + return dest; +} + +/* Create a table in the table. + */ +static toml_table_t *create_keytable_in_table(context_t *ctx, toml_table_t *tab, + token_t keytok) { + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char *newkey = normalize_key(ctx, keytok); + if (!newkey) + return 0; + + /* if key exists: error out */ + toml_table_t *dest = 0; + if (check_key(tab, newkey, 0, 0, &dest)) { + xfree(newkey); /* don't need this anymore */ + + /* special case: if table exists, but was created implicitly ... */ + if (dest && dest->implicit) { + /* we make it explicit now, and simply return it. */ + dest->implicit = false; + return dest; + } + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* create a new table entry */ + int n = tab->ntab; + toml_table_t **base; + if (0 == (base = (toml_table_t **)expand_ptrarr((void **)tab->tab, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->tab = base; + + if (0 == (base[n] = (toml_table_t *)CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + dest = tab->tab[tab->ntab++]; + + /* save the key in the new table struct */ + dest->key = newkey; + return dest; +} + +/* Create an array in the table. + */ +static toml_array_t *create_keyarray_in_table(context_t *ctx, toml_table_t *tab, + token_t keytok, char kind) { + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char *newkey = normalize_key(ctx, keytok); + if (!newkey) + return 0; + + /* if key exists: error out */ + if (key_kind(tab, newkey)) { + xfree(newkey); /* don't need this anymore */ + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* make a new array entry */ + int n = tab->narr; + toml_array_t **base; + if (0 == (base = (toml_array_t **)expand_ptrarr((void **)tab->arr, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->arr = base; + + if (0 == (base[n] = (toml_array_t *)CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + toml_array_t *dest = tab->arr[tab->narr++]; + + /* save the key in the new array struct */ + dest->key = newkey; + dest->kind = kind; + return dest; +} + +static toml_arritem_t *create_value_in_array(context_t *ctx, + toml_array_t *parent) { + const int n = parent->nitem; + toml_arritem_t *base = expand_arritem(parent->item, n); + if (!base) { + e_outofmemory(ctx, FLINE); + return 0; + } + parent->item = base; + parent->nitem++; + return &parent->item[n]; +} + +/* Create an array in an array + */ +static toml_array_t *create_array_in_array(context_t *ctx, + toml_array_t *parent) { + const int n = parent->nitem; + toml_arritem_t *base = expand_arritem(parent->item, n); + if (!base) { + e_outofmemory(ctx, FLINE); + return 0; + } + toml_array_t *ret = (toml_array_t *)CALLOC(1, sizeof(toml_array_t)); + if (!ret) { + e_outofmemory(ctx, FLINE); + return 0; + } + base[n].arr = ret; + parent->item = base; + parent->nitem++; + return ret; +} + +/* Create a table in an array + */ +static toml_table_t *create_table_in_array(context_t *ctx, + toml_array_t *parent) { + int n = parent->nitem; + toml_arritem_t *base = expand_arritem(parent->item, n); + if (!base) { + e_outofmemory(ctx, FLINE); + return 0; + } + toml_table_t *ret = (toml_table_t *)CALLOC(1, sizeof(toml_table_t)); + if (!ret) { + e_outofmemory(ctx, FLINE); + return 0; + } + base[n].tab = ret; + parent->item = base; + parent->nitem++; + return ret; +} + +static int skip_newlines(context_t *ctx, int isdotspecial) { + while (ctx->tok.tok == NEWLINE) { + if (next_token(ctx, isdotspecial)) + return -1; + if (ctx->tok.eof) + break; + } + return 0; +} + +static int parse_keyval(context_t *ctx, toml_table_t *tab); + +static inline int eat_token(context_t *ctx, tokentype_t typ, int isdotspecial, + const char *fline) { + if (ctx->tok.tok != typ) + return e_internal(ctx, fline); + + if (next_token(ctx, isdotspecial)) + return -1; + + return 0; +} + +/* We are at '{ ... }'. + * Parse the table. + */ +static int parse_inline_table(context_t *ctx, toml_table_t *tab) { + if (eat_token(ctx, LBRACE, 1, FLINE)) + return -1; + + for (;;) { + if (ctx->tok.tok == NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, + "newline not allowed in inline table"); + + /* until } */ + if (ctx->tok.tok == RBRACE) + break; + + if (ctx->tok.tok != STRING) + return e_syntax(ctx, ctx->tok.lineno, "expect a string"); + + if (parse_keyval(ctx, tab)) + return -1; + + if (ctx->tok.tok == NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, + "newline not allowed in inline table"); + + /* on comma, continue to scan for next keyval */ + if (ctx->tok.tok == COMMA) { + if (eat_token(ctx, COMMA, 1, FLINE)) + return -1; + continue; + } + break; + } + + if (eat_token(ctx, RBRACE, 1, FLINE)) + return -1; + + tab->readonly = 1; + + return 0; +} + +static int valtype(const char *val) { + toml_timestamp_t ts; + if (*val == '\'' || *val == '"') + return 's'; + if (0 == toml_rtob(val, 0)) + return 'b'; + if (0 == toml_rtoi(val, 0)) + return 'i'; + if (0 == toml_rtod(val, 0)) + return 'd'; + if (0 == toml_rtots(val, &ts)) { + if (ts.year && ts.hour) + return 'T'; /* timestamp */ + if (ts.year) + return 'D'; /* date */ + return 't'; /* time */ + } + return 'u'; /* unknown */ +} + +/* We are at '[...]' */ +static int parse_array(context_t *ctx, toml_array_t *arr) { + if (eat_token(ctx, LBRACKET, 0, FLINE)) + return -1; + + for (;;) { + if (skip_newlines(ctx, 0)) + return -1; + + /* until ] */ + if (ctx->tok.tok == RBRACKET) + break; + + switch (ctx->tok.tok) { + case STRING: { + /* set array kind if this will be the first entry */ + if (arr->kind == 0) + arr->kind = 'v'; + else if (arr->kind != 'v') + arr->kind = 'm'; + + char *val = ctx->tok.ptr; + int vlen = ctx->tok.len; + + /* make a new value in array */ + toml_arritem_t *newval = create_value_in_array(ctx, arr); + if (!newval) + return e_outofmemory(ctx, FLINE); + + if (!(newval->val = STRNDUP(val, vlen))) + return e_outofmemory(ctx, FLINE); + + newval->valtype = valtype(newval->val); + + /* set array type if this is the first entry */ + if (arr->nitem == 1) + arr->type = newval->valtype; + else if (arr->type != newval->valtype) + arr->type = 'm'; /* mixed */ + + if (eat_token(ctx, STRING, 0, FLINE)) + return -1; + break; + } + + case LBRACKET: { /* [ [array], [array] ... ] */ + /* set the array kind if this will be the first entry */ + if (arr->kind == 0) + arr->kind = 'a'; + else if (arr->kind != 'a') + arr->kind = 'm'; + + toml_array_t *subarr = create_array_in_array(ctx, arr); + if (!subarr) + return -1; + if (parse_array(ctx, subarr)) + return -1; + break; + } + + case LBRACE: { /* [ {table}, {table} ... ] */ + /* set the array kind if this will be the first entry */ + if (arr->kind == 0) + arr->kind = 't'; + else if (arr->kind != 't') + arr->kind = 'm'; + + toml_table_t *subtab = create_table_in_array(ctx, arr); + if (!subtab) + return -1; + if (parse_inline_table(ctx, subtab)) + return -1; + break; + } + + default: + return e_syntax(ctx, ctx->tok.lineno, "syntax error"); + } + + if (skip_newlines(ctx, 0)) + return -1; + + /* on comma, continue to scan for next element */ + if (ctx->tok.tok == COMMA) { + if (eat_token(ctx, COMMA, 0, FLINE)) + return -1; + continue; + } + break; + } + + if (eat_token(ctx, RBRACKET, 1, FLINE)) + return -1; + return 0; +} + +/* handle lines like these: + key = "value" + key = [ array ] + key = { table } +*/ +static int parse_keyval(context_t *ctx, toml_table_t *tab) { + if (tab->readonly) { + return e_forbid(ctx, ctx->tok.lineno, + "cannot insert new entry into existing table"); + } + + token_t key = ctx->tok; + if (eat_token(ctx, STRING, 1, FLINE)) + return -1; + + if (ctx->tok.tok == DOT) { + /* handle inline dotted key. + e.g. + physical.color = "orange" + physical.shape = "round" + */ + toml_table_t *subtab = 0; + { + char *subtabstr = normalize_key(ctx, key); + if (!subtabstr) + return -1; + + subtab = toml_table_in(tab, subtabstr); + xfree(subtabstr); + } + if (!subtab) { + subtab = create_keytable_in_table(ctx, tab, key); + if (!subtab) + return -1; + } + if (next_token(ctx, 1)) + return -1; + if (parse_keyval(ctx, subtab)) + return -1; + return 0; + } + + if (ctx->tok.tok != EQUAL) { + return e_syntax(ctx, ctx->tok.lineno, "missing ="); + } + + if (next_token(ctx, 0)) + return -1; + + switch (ctx->tok.tok) { + case STRING: { /* key = "value" */ + toml_keyval_t *keyval = create_keyval_in_table(ctx, tab, key); + if (!keyval) + return -1; + token_t val = ctx->tok; + + assert(keyval->val == 0); + if (!(keyval->val = STRNDUP(val.ptr, val.len))) + return e_outofmemory(ctx, FLINE); + + if (next_token(ctx, 1)) + return -1; + + return 0; + } + + case LBRACKET: { /* key = [ array ] */ + toml_array_t *arr = create_keyarray_in_table(ctx, tab, key, 0); + if (!arr) + return -1; + if (parse_array(ctx, arr)) + return -1; + return 0; + } + + case LBRACE: { /* key = { table } */ + toml_table_t *nxttab = create_keytable_in_table(ctx, tab, key); + if (!nxttab) + return -1; + if (parse_inline_table(ctx, nxttab)) + return -1; + return 0; + } + + default: + return e_syntax(ctx, ctx->tok.lineno, "syntax error"); + } + return 0; +} + +typedef struct tabpath_t tabpath_t; +struct tabpath_t { + int cnt; + token_t key[10]; +}; + +/* at [x.y.z] or [[x.y.z]] + * Scan forward and fill tabpath until it enters ] or ]] + * There will be at least one entry on return. + */ +static int fill_tabpath(context_t *ctx) { + int lineno = ctx->tok.lineno; + int i; + + /* clear tpath */ + for (i = 0; i < ctx->tpath.top; i++) { + char **p = &ctx->tpath.key[i]; + xfree(*p); + *p = 0; + } + ctx->tpath.top = 0; + + for (;;) { + if (ctx->tpath.top >= 10) + return e_syntax(ctx, lineno, + "table path is too deep; max allowed is 10."); + + if (ctx->tok.tok != STRING) + return e_syntax(ctx, lineno, "invalid or missing key"); + + char *key = normalize_key(ctx, ctx->tok); + if (!key) + return -1; + ctx->tpath.tok[ctx->tpath.top] = ctx->tok; + ctx->tpath.key[ctx->tpath.top] = key; + ctx->tpath.top++; + + if (next_token(ctx, 1)) + return -1; + + if (ctx->tok.tok == RBRACKET) + break; + + if (ctx->tok.tok != DOT) + return e_syntax(ctx, lineno, "invalid key"); + + if (next_token(ctx, 1)) + return -1; + } + + if (ctx->tpath.top <= 0) + return e_syntax(ctx, lineno, "empty table selector"); + + return 0; +} + +/* Walk tabpath from the root, and create new tables on the way. + * Sets ctx->curtab to the final table. + */ +static int walk_tabpath(context_t *ctx) { + /* start from root */ + toml_table_t *curtab = ctx->root; + + for (int i = 0; i < ctx->tpath.top; i++) { + const char *key = ctx->tpath.key[i]; + + toml_keyval_t *nextval = 0; + toml_array_t *nextarr = 0; + toml_table_t *nexttab = 0; + switch (check_key(curtab, key, &nextval, &nextarr, &nexttab)) { + case 't': + /* found a table. nexttab is where we will go next. */ + break; + + case 'a': + /* found an array. nexttab is the last table in the array. */ + if (nextarr->kind != 't') + return e_internal(ctx, FLINE); + + if (nextarr->nitem == 0) + return e_internal(ctx, FLINE); + + nexttab = nextarr->item[nextarr->nitem - 1].tab; + break; + + case 'v': + return e_keyexists(ctx, ctx->tpath.tok[i].lineno); + + default: { /* Not found. Let's create an implicit table. */ + int n = curtab->ntab; + toml_table_t **base = + (toml_table_t **)expand_ptrarr((void **)curtab->tab, n); + if (0 == base) + return e_outofmemory(ctx, FLINE); + + curtab->tab = base; + + if (0 == (base[n] = (toml_table_t *)CALLOC(1, sizeof(*base[n])))) + return e_outofmemory(ctx, FLINE); + + if (0 == (base[n]->key = STRDUP(key))) + return e_outofmemory(ctx, FLINE); + + nexttab = curtab->tab[curtab->ntab++]; + + /* tabs created by walk_tabpath are considered implicit */ + nexttab->implicit = true; + } break; + } + + /* switch to next tab */ + curtab = nexttab; + } + + /* save it */ + ctx->curtab = curtab; + + return 0; +} + +/* handle lines like [x.y.z] or [[x.y.z]] */ +static int parse_select(context_t *ctx) { + assert(ctx->tok.tok == LBRACKET); + + /* true if [[ */ + int llb = (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == '['); + /* need to detect '[[' on our own because next_token() will skip whitespace, + and '[ [' would be taken as '[[', which is wrong. */ + + /* eat [ or [[ */ + if (eat_token(ctx, LBRACKET, 1, FLINE)) + return -1; + if (llb) { + assert(ctx->tok.tok == LBRACKET); + if (eat_token(ctx, LBRACKET, 1, FLINE)) + return -1; + } + + if (fill_tabpath(ctx)) + return -1; + + /* For [x.y.z] or [[x.y.z]], remove z from tpath. + */ + token_t z = ctx->tpath.tok[ctx->tpath.top - 1]; + xfree(ctx->tpath.key[ctx->tpath.top - 1]); + ctx->tpath.top--; + + /* set up ctx->curtab */ + if (walk_tabpath(ctx)) + return -1; + + if (!llb) { + /* [x.y.z] -> create z = {} in x.y */ + toml_table_t *curtab = create_keytable_in_table(ctx, ctx->curtab, z); + if (!curtab) + return -1; + ctx->curtab = curtab; + } else { + /* [[x.y.z]] -> create z = [] in x.y */ + toml_array_t *arr = 0; + { + char *zstr = normalize_key(ctx, z); + if (!zstr) + return -1; + arr = toml_array_in(ctx->curtab, zstr); + xfree(zstr); + } + if (!arr) { + arr = create_keyarray_in_table(ctx, ctx->curtab, z, 't'); + if (!arr) + return -1; + } + if (arr->kind != 't') + return e_syntax(ctx, z.lineno, "array mismatch"); + + /* add to z[] */ + toml_table_t *dest; + { + toml_table_t *t = create_table_in_array(ctx, arr); + if (!t) + return -1; + + if (0 == (t->key = STRDUP("__anon__"))) + return e_outofmemory(ctx, FLINE); + + dest = t; + } + + ctx->curtab = dest; + } + + if (ctx->tok.tok != RBRACKET) { + return e_syntax(ctx, ctx->tok.lineno, "expects ]"); + } + if (llb) { + if (!(ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == ']')) { + return e_syntax(ctx, ctx->tok.lineno, "expects ]]"); + } + if (eat_token(ctx, RBRACKET, 1, FLINE)) + return -1; + } + + if (eat_token(ctx, RBRACKET, 1, FLINE)) + return -1; + + if (ctx->tok.tok != NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, "extra chars after ] or ]]"); + + return 0; +} + +toml_table_t *toml_parse(char *conf, char *errbuf, int errbufsz) { + context_t ctx; + + // clear errbuf + if (errbufsz <= 0) + errbufsz = 0; + if (errbufsz > 0) + errbuf[0] = 0; + + // init context + memset(&ctx, 0, sizeof(ctx)); + ctx.start = conf; + ctx.stop = ctx.start + strlen(conf); + ctx.errbuf = errbuf; + ctx.errbufsz = errbufsz; + + // start with an artificial newline of length 0 + ctx.tok.tok = NEWLINE; + ctx.tok.lineno = 1; + ctx.tok.ptr = conf; + ctx.tok.len = 0; + + // make a root table + if (0 == (ctx.root = CALLOC(1, sizeof(*ctx.root)))) { + e_outofmemory(&ctx, FLINE); + // Do not goto fail, root table not set up yet + return 0; + } + + // set root as default table + ctx.curtab = ctx.root; + + /* Scan forward until EOF */ + for (token_t tok = ctx.tok; !tok.eof; tok = ctx.tok) { + switch (tok.tok) { + + case NEWLINE: + if (next_token(&ctx, 1)) + goto fail; + break; + + case STRING: + if (parse_keyval(&ctx, ctx.curtab)) + goto fail; + + if (ctx.tok.tok != NEWLINE) { + e_syntax(&ctx, ctx.tok.lineno, "extra chars after value"); + goto fail; + } + + if (eat_token(&ctx, NEWLINE, 1, FLINE)) + goto fail; + break; + + case LBRACKET: /* [ x.y.z ] or [[ x.y.z ]] */ + if (parse_select(&ctx)) + goto fail; + break; + + default: + e_syntax(&ctx, tok.lineno, "syntax error"); + goto fail; + } + } + + /* success */ + for (int i = 0; i < ctx.tpath.top; i++) + xfree(ctx.tpath.key[i]); + return ctx.root; + +fail: + // Something bad has happened. Free resources and return error. + for (int i = 0; i < ctx.tpath.top; i++) + xfree(ctx.tpath.key[i]); + toml_free(ctx.root); + return 0; +} + +toml_table_t *toml_parse_file(FILE *fp, char *errbuf, int errbufsz) { + int bufsz = 0; + char *buf = 0; + int off = 0; + + /* read from fp into buf */ + while (!feof(fp)) { + + if (off == bufsz) { + int xsz = bufsz + 1000; + char *x = expand(buf, bufsz, xsz); + if (!x) { + snprintf(errbuf, errbufsz, "out of memory"); + xfree(buf); + return 0; + } + buf = x; + bufsz = xsz; + } + + errno = 0; + int n = fread(buf + off, 1, bufsz - off, fp); + if (ferror(fp)) { + snprintf(errbuf, errbufsz, "%s", + errno ? strerror(errno) : "Error reading file"); + xfree(buf); + return 0; + } + off += n; + } + + /* tag on a NUL to cap the string */ + if (off == bufsz) { + int xsz = bufsz + 1; + char *x = expand(buf, bufsz, xsz); + if (!x) { + snprintf(errbuf, errbufsz, "out of memory"); + xfree(buf); + return 0; + } + buf = x; + bufsz = xsz; + } + buf[off] = 0; + + /* parse it, cleanup and finish */ + toml_table_t *ret = toml_parse(buf, errbuf, errbufsz); + xfree(buf); + return ret; +} + +static void xfree_kval(toml_keyval_t *p) { + if (!p) + return; + xfree(p->key); + xfree(p->val); + xfree(p); +} + +static void xfree_tab(toml_table_t *p); + +static void xfree_arr(toml_array_t *p) { + if (!p) + return; + + xfree(p->key); + const int n = p->nitem; + for (int i = 0; i < n; i++) { + toml_arritem_t *a = &p->item[i]; + if (a->val) + xfree(a->val); + else if (a->arr) + xfree_arr(a->arr); + else if (a->tab) + xfree_tab(a->tab); + } + xfree(p->item); + xfree(p); +} + +static void xfree_tab(toml_table_t *p) { + int i; + + if (!p) + return; + + xfree(p->key); + + for (i = 0; i < p->nkval; i++) + xfree_kval(p->kval[i]); + xfree(p->kval); + + for (i = 0; i < p->narr; i++) + xfree_arr(p->arr[i]); + xfree(p->arr); + + for (i = 0; i < p->ntab; i++) + xfree_tab(p->tab[i]); + xfree(p->tab); + + xfree(p); +} + +void toml_free(toml_table_t *tab) { xfree_tab(tab); } + +static void set_token(context_t *ctx, tokentype_t tok, int lineno, char *ptr, + int len) { + token_t t; + t.tok = tok; + t.lineno = lineno; + t.ptr = ptr; + t.len = len; + t.eof = 0; + ctx->tok = t; +} + +static void set_eof(context_t *ctx, int lineno) { + set_token(ctx, NEWLINE, lineno, ctx->stop, 0); + ctx->tok.eof = 1; +} + +/* Scan p for n digits compositing entirely of [0-9] */ +static int scan_digits(const char *p, int n) { + int ret = 0; + for (; n > 0 && isdigit(*p); n--, p++) { + ret = 10 * ret + (*p - '0'); + } + return n ? -1 : ret; +} + +static int scan_date(const char *p, int *YY, int *MM, int *DD) { + int year, month, day; + year = scan_digits(p, 4); + month = (year >= 0 && p[4] == '-') ? scan_digits(p + 5, 2) : -1; + day = (month >= 0 && p[7] == '-') ? scan_digits(p + 8, 2) : -1; + if (YY) + *YY = year; + if (MM) + *MM = month; + if (DD) + *DD = day; + return (year >= 0 && month >= 0 && day >= 0) ? 0 : -1; +} + +static int scan_time(const char *p, int *hh, int *mm, int *ss) { + int hour, minute, second; + hour = scan_digits(p, 2); + minute = (hour >= 0 && p[2] == ':') ? scan_digits(p + 3, 2) : -1; + second = (minute >= 0 && p[5] == ':') ? scan_digits(p + 6, 2) : -1; + if (hh) + *hh = hour; + if (mm) + *mm = minute; + if (ss) + *ss = second; + return (hour >= 0 && minute >= 0 && second >= 0) ? 0 : -1; +} + +static int scan_string(context_t *ctx, char *p, int lineno, int dotisspecial) { + char *orig = p; + if (0 == strncmp(p, "'''", 3)) { + char *q = p + 3; + + while (1) { + q = strstr(q, "'''"); + if (0 == q) { + return e_syntax(ctx, lineno, "unterminated triple-s-quote"); + } + while (q[3] == '\'') + q++; + break; + } + + set_token(ctx, STRING, lineno, orig, q + 3 - orig); + return 0; + } + + if (0 == strncmp(p, "\"\"\"", 3)) { + char *q = p + 3; + + while (1) { + q = strstr(q, "\"\"\""); + if (0 == q) { + return e_syntax(ctx, lineno, "unterminated triple-d-quote"); + } + if (q[-1] == '\\') { + q++; + continue; + } + while (q[3] == '\"') + q++; + break; + } + + // the string is [p+3, q-1] + + int hexreq = 0; /* #hex required */ + int escape = 0; + for (p += 3; p < q; p++) { + if (escape) { + escape = 0; + if (strchr("btnfr\"\\", *p)) + continue; + if (*p == 'u') { + hexreq = 4; + continue; + } + if (*p == 'U') { + hexreq = 8; + continue; + } + if (p[strspn(p, " \t\r")] == '\n') + continue; /* allow for line ending backslash */ + return e_syntax(ctx, lineno, "bad escape char"); + } + if (hexreq) { + hexreq--; + if (strchr("0123456789ABCDEF", *p)) + continue; + return e_syntax(ctx, lineno, "expect hex char"); + } + if (*p == '\\') { + escape = 1; + continue; + } + } + if (escape) + return e_syntax(ctx, lineno, "expect an escape char"); + if (hexreq) + return e_syntax(ctx, lineno, "expected more hex char"); + + set_token(ctx, STRING, lineno, orig, q + 3 - orig); + return 0; + } + + if ('\'' == *p) { + for (p++; *p && *p != '\n' && *p != '\''; p++) + ; + if (*p != '\'') { + return e_syntax(ctx, lineno, "unterminated s-quote"); + } + + set_token(ctx, STRING, lineno, orig, p + 1 - orig); + return 0; + } + + if ('\"' == *p) { + int hexreq = 0; /* #hex required */ + int escape = 0; + for (p++; *p; p++) { + if (escape) { + escape = 0; + if (strchr("btnfr\"\\", *p)) + continue; + if (*p == 'u') { + hexreq = 4; + continue; + } + if (*p == 'U') { + hexreq = 8; + continue; + } + return e_syntax(ctx, lineno, "bad escape char"); + } + if (hexreq) { + hexreq--; + if (strchr("0123456789ABCDEF", *p)) + continue; + return e_syntax(ctx, lineno, "expect hex char"); + } + if (*p == '\\') { + escape = 1; + continue; + } + if (*p == '\'') { + if (p[1] == '\'' && p[2] == '\'') { + return e_syntax(ctx, lineno, "triple-s-quote inside string lit"); + } + continue; + } + if (*p == '\n') + break; + if (*p == '"') + break; + } + if (*p != '"') { + return e_syntax(ctx, lineno, "unterminated quote"); + } + + set_token(ctx, STRING, lineno, orig, p + 1 - orig); + return 0; + } + + /* check for timestamp without quotes */ + if (0 == scan_date(p, 0, 0, 0) || 0 == scan_time(p, 0, 0, 0)) { + // forward thru the timestamp + p += strspn(p, "0123456789.:+-Tt Zz"); + // squeeze out any spaces at end of string + for (; p[-1] == ' '; p--) + ; + // tokenize + set_token(ctx, STRING, lineno, orig, p - orig); + return 0; + } + + /* literals */ + for (; *p && *p != '\n'; p++) { + int ch = *p; + if (ch == '.' && dotisspecial) + break; + if ('A' <= ch && ch <= 'Z') + continue; + if ('a' <= ch && ch <= 'z') + continue; + if (strchr("0123456789+-_.", ch)) + continue; + break; + } + + set_token(ctx, STRING, lineno, orig, p - orig); + return 0; +} + +static int next_token(context_t *ctx, int dotisspecial) { + int lineno = ctx->tok.lineno; + char *p = ctx->tok.ptr; + int i; + + /* eat this tok */ + for (i = 0; i < ctx->tok.len; i++) { + if (*p++ == '\n') + lineno++; + } + + /* make next tok */ + while (p < ctx->stop) { + /* skip comment. stop just before the \n. */ + if (*p == '#') { + for (p++; p < ctx->stop && *p != '\n'; p++) + ; + continue; + } + + if (dotisspecial && *p == '.') { + set_token(ctx, DOT, lineno, p, 1); + return 0; + } + + switch (*p) { + case ',': + set_token(ctx, COMMA, lineno, p, 1); + return 0; + case '=': + set_token(ctx, EQUAL, lineno, p, 1); + return 0; + case '{': + set_token(ctx, LBRACE, lineno, p, 1); + return 0; + case '}': + set_token(ctx, RBRACE, lineno, p, 1); + return 0; + case '[': + set_token(ctx, LBRACKET, lineno, p, 1); + return 0; + case ']': + set_token(ctx, RBRACKET, lineno, p, 1); + return 0; + case '\n': + set_token(ctx, NEWLINE, lineno, p, 1); + return 0; + case '\r': + case ' ': + case '\t': + /* ignore white spaces */ + p++; + continue; + } + + return scan_string(ctx, p, lineno, dotisspecial); + } + + set_eof(ctx, lineno); + return 0; +} + +const char *toml_key_in(const toml_table_t *tab, int keyidx) { + if (keyidx < tab->nkval) + return tab->kval[keyidx]->key; + + keyidx -= tab->nkval; + if (keyidx < tab->narr) + return tab->arr[keyidx]->key; + + keyidx -= tab->narr; + if (keyidx < tab->ntab) + return tab->tab[keyidx]->key; + + return 0; +} + +int toml_key_exists(const toml_table_t *tab, const char *key) { + int i; + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) + return 1; + } + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) + return 1; + } + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) + return 1; + } + return 0; +} + +toml_raw_t toml_raw_in(const toml_table_t *tab, const char *key) { + int i; + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) + return tab->kval[i]->val; + } + return 0; +} + +toml_array_t *toml_array_in(const toml_table_t *tab, const char *key) { + int i; + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) + return tab->arr[i]; + } + return 0; +} + +toml_table_t *toml_table_in(const toml_table_t *tab, const char *key) { + int i; + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) + return tab->tab[i]; + } + return 0; +} + +toml_raw_t toml_raw_at(const toml_array_t *arr, int idx) { + return (0 <= idx && idx < arr->nitem) ? arr->item[idx].val : 0; +} + +char toml_array_kind(const toml_array_t *arr) { return arr->kind; } + +char toml_array_type(const toml_array_t *arr) { + if (arr->kind != 'v') + return 0; + + if (arr->nitem == 0) + return 0; + + return arr->type; +} + +int toml_array_nelem(const toml_array_t *arr) { return arr->nitem; } + +const char *toml_array_key(const toml_array_t *arr) { + return arr ? arr->key : (const char *)NULL; +} + +int toml_table_nkval(const toml_table_t *tab) { return tab->nkval; } + +int toml_table_narr(const toml_table_t *tab) { return tab->narr; } + +int toml_table_ntab(const toml_table_t *tab) { return tab->ntab; } + +const char *toml_table_key(const toml_table_t *tab) { + return tab ? tab->key : (const char *)NULL; +} + +toml_array_t *toml_array_at(const toml_array_t *arr, int idx) { + return (0 <= idx && idx < arr->nitem) ? arr->item[idx].arr : 0; +} + +toml_table_t *toml_table_at(const toml_array_t *arr, int idx) { + return (0 <= idx && idx < arr->nitem) ? arr->item[idx].tab : 0; +} + +static int parse_millisec(const char *p, const char **endp); + +int toml_rtots(toml_raw_t src_, toml_timestamp_t *ret) { + if (!src_) + return -1; + + const char *p = src_; + int must_parse_time = 0; + + memset(ret, 0, sizeof(*ret)); + + int *year = &ret->__buffer.year; + int *month = &ret->__buffer.month; + int *day = &ret->__buffer.day; + int *hour = &ret->__buffer.hour; + int *minute = &ret->__buffer.minute; + int *second = &ret->__buffer.second; + int *millisec = &ret->__buffer.millisec; + + /* parse date YYYY-MM-DD */ + if (0 == scan_date(p, year, month, day)) { + ret->year = year; + ret->month = month; + ret->day = day; + + p += 10; + if (*p) { + // parse the T or space separator + if (*p != 'T' && *p != 't' && *p != ' ') + return -1; + must_parse_time = 1; + p++; + } + } + + /* parse time HH:MM:SS */ + if (0 == scan_time(p, hour, minute, second)) { + ret->hour = hour; + ret->minute = minute; + ret->second = second; + + /* optionally, parse millisec */ + p += 8; + if (*p == '.') { + p++; /* skip '.' */ + const char *qq; + *millisec = parse_millisec(p, &qq); + ret->millisec = millisec; + p = qq; + } + + if (*p) { + /* parse and copy Z */ + char *z = ret->__buffer.z; + ret->z = z; + if (*p == 'Z' || *p == 'z') { + *z++ = 'Z'; + p++; + *z = 0; + + } else if (*p == '+' || *p == '-') { + *z++ = *p++; + + if (!(isdigit(p[0]) && isdigit(p[1]))) + return -1; + *z++ = *p++; + *z++ = *p++; + + if (*p == ':') { + *z++ = *p++; + + if (!(isdigit(p[0]) && isdigit(p[1]))) + return -1; + *z++ = *p++; + *z++ = *p++; + } + + *z = 0; + } + } + } + if (*p != 0) + return -1; + + if (must_parse_time && !ret->hour) + return -1; + + return 0; +} + +/* Raw to boolean */ +int toml_rtob(toml_raw_t src, int *ret_) { + if (!src) + return -1; + int dummy; + int *ret = ret_ ? ret_ : &dummy; + + if (0 == strcmp(src, "true")) { + *ret = 1; + return 0; + } + if (0 == strcmp(src, "false")) { + *ret = 0; + return 0; + } + return -1; +} + +/* Raw to integer */ +int toml_rtoi(toml_raw_t src, int64_t *ret_) { + if (!src) + return -1; + + char buf[100]; + char *p = buf; + char *q = p + sizeof(buf); + const char *s = src; + int base = 0; + int64_t dummy; + int64_t *ret = ret_ ? ret_ : &dummy; + + /* allow +/- */ + if (s[0] == '+' || s[0] == '-') + *p++ = *s++; + + /* disallow +_100 */ + if (s[0] == '_') + return -1; + + /* if 0* ... */ + if ('0' == s[0]) { + switch (s[1]) { + case 'x': + base = 16; + s += 2; + break; + case 'o': + base = 8; + s += 2; + break; + case 'b': + base = 2; + s += 2; + break; + case '\0': + return *ret = 0, 0; + default: + /* ensure no other digits after it */ + if (s[1]) + return -1; + } + } + + /* just strip underscores and pass to strtoll */ + while (*s && p < q) { + int ch = *s++; + if (ch == '_') { + // disallow '__' + if (s[0] == '_') + return -1; + // numbers cannot end with '_' + if (s[0] == '\0') + return -1; + continue; /* skip _ */ + } + *p++ = ch; + } + + // if not at end-of-string or we ran out of buffer ... + if (*s || p == q) + return -1; + + /* cap with NUL */ + *p = 0; + + /* Run strtoll on buf to get the integer */ + char *endp; + errno = 0; + *ret = strtoll(buf, &endp, base); + return (errno || *endp) ? -1 : 0; +} + +int toml_rtod_ex(toml_raw_t src, double *ret_, char *buf, int buflen) { + if (!src) + return -1; + + char *p = buf; + char *q = p + buflen; + const char *s = src; + double dummy; + double *ret = ret_ ? ret_ : &dummy; + + /* allow +/- */ + if (s[0] == '+' || s[0] == '-') + *p++ = *s++; + + /* disallow +_1.00 */ + if (s[0] == '_') + return -1; + + /* decimal point, if used, must be surrounded by at least one digit on each + * side */ + { + char *dot = strchr(s, '.'); + if (dot) { + if (dot == s || !isdigit(dot[-1]) || !isdigit(dot[1])) + return -1; + } + } + + /* zero must be followed by . or 'e', or NUL */ + if (s[0] == '0' && s[1] && !strchr("eE.", s[1])) + return -1; + + /* just strip underscores and pass to strtod */ + while (*s && p < q) { + int ch = *s++; + if (ch == '_') { + // disallow '__' + if (s[0] == '_') + return -1; + // disallow last char '_' + if (s[0] == 0) + return -1; + continue; /* skip _ */ + } + *p++ = ch; + } + if (*s || p == q) + return -1; /* reached end of string or buffer is full? */ + + /* cap with NUL */ + *p = 0; + + /* Run strtod on buf to get the value */ + char *endp; + errno = 0; + *ret = strtod(buf, &endp); + return (errno || *endp) ? -1 : 0; +} + +int toml_rtod(toml_raw_t src, double *ret_) { + char buf[100]; + return toml_rtod_ex(src, ret_, buf, sizeof(buf)); +} + +int toml_rtos(toml_raw_t src, char **ret) { + int multiline = 0; + const char *sp; + const char *sq; + + *ret = 0; + if (!src) + return -1; + + // for strings, first char must be a s-quote or d-quote + int qchar = src[0]; + int srclen = strlen(src); + if (!(qchar == '\'' || qchar == '"')) { + return -1; + } + + // triple quotes? + if (qchar == src[1] && qchar == src[2]) { + multiline = 1; // triple-quote implies multiline + sp = src + 3; // first char after quote + sq = src + srclen - 3; // first char of ending quote + + if (!(sp <= sq && sq[0] == qchar && sq[1] == qchar && sq[2] == qchar)) { + // last 3 chars in src must be qchar + return -1; + } + + /* skip new line immediate after qchar */ + if (sp[0] == '\n') + sp++; + else if (sp[0] == '\r' && sp[1] == '\n') + sp += 2; + + } else { + sp = src + 1; // first char after quote + sq = src + srclen - 1; // ending quote + if (!(sp <= sq && *sq == qchar)) { + /* last char in src must be qchar */ + return -1; + } + } + + // at this point: + // sp points to first valid char after quote. + // sq points to one char beyond last valid char. + // string len is (sq - sp). + if (qchar == '\'') { + *ret = norm_lit_str(sp, sq - sp, multiline, 0, 0); + } else { + *ret = norm_basic_str(sp, sq - sp, multiline, 0, 0); + } + + return *ret ? 0 : -1; +} + +toml_datum_t toml_string_at(const toml_array_t *arr, int idx) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtos(toml_raw_at(arr, idx), &ret.u.s)); + return ret; +} + +toml_datum_t toml_bool_at(const toml_array_t *arr, int idx) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtob(toml_raw_at(arr, idx), &ret.u.b)); + return ret; +} + +toml_datum_t toml_int_at(const toml_array_t *arr, int idx) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtoi(toml_raw_at(arr, idx), &ret.u.i)); + return ret; +} + +toml_datum_t toml_double_at(const toml_array_t *arr, int idx) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtod(toml_raw_at(arr, idx), &ret.u.d)); + return ret; +} + +toml_datum_t toml_timestamp_at(const toml_array_t *arr, int idx) { + toml_timestamp_t ts; + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtots(toml_raw_at(arr, idx), &ts)); + if (ret.ok) { + ret.ok = !!(ret.u.ts = MALLOC(sizeof(*ret.u.ts))); + if (ret.ok) { + *ret.u.ts = ts; + if (ret.u.ts->year) + ret.u.ts->year = &ret.u.ts->__buffer.year; + if (ret.u.ts->month) + ret.u.ts->month = &ret.u.ts->__buffer.month; + if (ret.u.ts->day) + ret.u.ts->day = &ret.u.ts->__buffer.day; + if (ret.u.ts->hour) + ret.u.ts->hour = &ret.u.ts->__buffer.hour; + if (ret.u.ts->minute) + ret.u.ts->minute = &ret.u.ts->__buffer.minute; + if (ret.u.ts->second) + ret.u.ts->second = &ret.u.ts->__buffer.second; + if (ret.u.ts->millisec) + ret.u.ts->millisec = &ret.u.ts->__buffer.millisec; + if (ret.u.ts->z) + ret.u.ts->z = ret.u.ts->__buffer.z; + } + } + return ret; +} + +toml_datum_t toml_string_in(const toml_table_t *arr, const char *key) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_in(arr, key); + if (raw) { + ret.ok = (0 == toml_rtos(raw, &ret.u.s)); + } + return ret; +} + +toml_datum_t toml_bool_in(const toml_table_t *arr, const char *key) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtob(toml_raw_in(arr, key), &ret.u.b)); + return ret; +} + +toml_datum_t toml_int_in(const toml_table_t *arr, const char *key) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtoi(toml_raw_in(arr, key), &ret.u.i)); + return ret; +} + +toml_datum_t toml_double_in(const toml_table_t *arr, const char *key) { + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtod(toml_raw_in(arr, key), &ret.u.d)); + return ret; +} + +toml_datum_t toml_timestamp_in(const toml_table_t *arr, const char *key) { + toml_timestamp_t ts; + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtots(toml_raw_in(arr, key), &ts)); + if (ret.ok) { + ret.ok = !!(ret.u.ts = MALLOC(sizeof(*ret.u.ts))); + if (ret.ok) { + *ret.u.ts = ts; + if (ret.u.ts->year) + ret.u.ts->year = &ret.u.ts->__buffer.year; + if (ret.u.ts->month) + ret.u.ts->month = &ret.u.ts->__buffer.month; + if (ret.u.ts->day) + ret.u.ts->day = &ret.u.ts->__buffer.day; + if (ret.u.ts->hour) + ret.u.ts->hour = &ret.u.ts->__buffer.hour; + if (ret.u.ts->minute) + ret.u.ts->minute = &ret.u.ts->__buffer.minute; + if (ret.u.ts->second) + ret.u.ts->second = &ret.u.ts->__buffer.second; + if (ret.u.ts->millisec) + ret.u.ts->millisec = &ret.u.ts->__buffer.millisec; + if (ret.u.ts->z) + ret.u.ts->z = ret.u.ts->__buffer.z; + } + } + return ret; +} + +static int parse_millisec(const char *p, const char **endp) { + int ret = 0; + int unit = 100; /* unit in millisec */ + for (; '0' <= *p && *p <= '9'; p++, unit /= 10) { + ret += (*p - '0') * unit; + } + *endp = p; + return ret; +} diff --git a/lib/tomlc99/toml.h b/lib/tomlc99/toml.h new file mode 100644 index 0000000..c6aabd0 --- /dev/null +++ b/lib/tomlc99/toml.h @@ -0,0 +1,175 @@ +/* + MIT License + + Copyright (c) CK Tan + https://github.com/cktan/tomlc99 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef TOML_H +#define TOML_H + +#ifdef _MSC_VER +#pragma warning(disable : 4996) +#endif + +#include +#include + +#ifdef __cplusplus +#define TOML_EXTERN extern "C" +#else +#define TOML_EXTERN extern +#endif + +typedef struct toml_timestamp_t toml_timestamp_t; +typedef struct toml_table_t toml_table_t; +typedef struct toml_array_t toml_array_t; +typedef struct toml_datum_t toml_datum_t; + +/* Parse a file. Return a table on success, or 0 otherwise. + * Caller must toml_free(the-return-value) after use. + */ +TOML_EXTERN toml_table_t *toml_parse_file(FILE *fp, char *errbuf, int errbufsz); + +/* Parse a string containing the full config. + * Return a table on success, or 0 otherwise. + * Caller must toml_free(the-return-value) after use. + */ +TOML_EXTERN toml_table_t *toml_parse(char *conf, /* NUL terminated, please. */ + char *errbuf, int errbufsz); + +/* Free the table returned by toml_parse() or toml_parse_file(). Once + * this function is called, any handles accessed through this tab + * directly or indirectly are no longer valid. + */ +TOML_EXTERN void toml_free(toml_table_t *tab); + +/* Timestamp types. The year, month, day, hour, minute, second, z + * fields may be NULL if they are not relevant. e.g. In a DATE + * type, the hour, minute, second and z fields will be NULLs. + */ +struct toml_timestamp_t { + struct { /* internal. do not use. */ + int year, month, day; + int hour, minute, second, millisec; + char z[10]; + } __buffer; + int *year, *month, *day; + int *hour, *minute, *second, *millisec; + char *z; +}; + +/*----------------------------------------------------------------- + * Enhanced access methods + */ +struct toml_datum_t { + int ok; + union { + toml_timestamp_t *ts; /* ts must be freed after use */ + char *s; /* string value. s must be freed after use */ + int b; /* bool value */ + int64_t i; /* int value */ + double d; /* double value */ + } u; +}; + +/* on arrays: */ +/* ... retrieve size of array. */ +TOML_EXTERN int toml_array_nelem(const toml_array_t *arr); +/* ... retrieve values using index. */ +TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t *arr, int idx); +/* ... retrieve array or table using index. */ +TOML_EXTERN toml_array_t *toml_array_at(const toml_array_t *arr, int idx); +TOML_EXTERN toml_table_t *toml_table_at(const toml_array_t *arr, int idx); + +/* on tables: */ +/* ... retrieve the key in table at keyidx. Return 0 if out of range. */ +TOML_EXTERN const char *toml_key_in(const toml_table_t *tab, int keyidx); +/* ... returns 1 if key exists in tab, 0 otherwise */ +TOML_EXTERN int toml_key_exists(const toml_table_t *tab, const char *key); +/* ... retrieve values using key. */ +TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t *arr, + const char *key); +TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t *arr, const char *key); +TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t *arr, const char *key); +TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t *arr, + const char *key); +TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t *arr, + const char *key); +/* .. retrieve array or table using key. */ +TOML_EXTERN toml_array_t *toml_array_in(const toml_table_t *tab, + const char *key); +TOML_EXTERN toml_table_t *toml_table_in(const toml_table_t *tab, + const char *key); + +/*----------------------------------------------------------------- + * lesser used + */ +/* Return the array kind: 't'able, 'a'rray, 'v'alue, 'm'ixed */ +TOML_EXTERN char toml_array_kind(const toml_array_t *arr); + +/* For array kind 'v'alue, return the type of values + i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp, 'm'ixed + 0 if unknown +*/ +TOML_EXTERN char toml_array_type(const toml_array_t *arr); + +/* Return the key of an array */ +TOML_EXTERN const char *toml_array_key(const toml_array_t *arr); + +/* Return the number of key-values in a table */ +TOML_EXTERN int toml_table_nkval(const toml_table_t *tab); + +/* Return the number of arrays in a table */ +TOML_EXTERN int toml_table_narr(const toml_table_t *tab); + +/* Return the number of sub-tables in a table */ +TOML_EXTERN int toml_table_ntab(const toml_table_t *tab); + +/* Return the key of a table*/ +TOML_EXTERN const char *toml_table_key(const toml_table_t *tab); + +/*-------------------------------------------------------------- + * misc + */ +TOML_EXTERN int toml_utf8_to_ucs(const char *orig, int len, int64_t *ret); +TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]); +TOML_EXTERN void toml_set_memutil(void *(*xxmalloc)(size_t), + void (*xxfree)(void *)); + +/*-------------------------------------------------------------- + * deprecated + */ +/* A raw value, must be processed by toml_rto* before using. */ +typedef const char *toml_raw_t; +TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t *tab, const char *key); +TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t *arr, int idx); +TOML_EXTERN int toml_rtos(toml_raw_t s, char **ret); +TOML_EXTERN int toml_rtob(toml_raw_t s, int *ret); +TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t *ret); +TOML_EXTERN int toml_rtod(toml_raw_t s, double *ret); +TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double *ret, char *buf, int buflen); +TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t *ret); + +#endif /* TOML_H */ From 41dd75de9b6e4858951926a594422367762bcf02 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Tue, 23 Jan 2024 16:58:06 +0800 Subject: [PATCH 02/10] mctpd: Add toml-based configuration interface This change adds a basic configuration interface, using a simple toml syntax. This allows configuration of the existing mctpd parameters: bus-owner mode, the mctp timeout, and the UUID. We ship a small example configuration under conf/. Signed-off-by: Jeremy Kerr --- CHANGELOG.md | 1 + README.md | 3 ++ conf/mctpd.conf | 10 ++++ meson.build | 9 +++- src/mctpd.c | 124 ++++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 136 insertions(+), 11 deletions(-) create mode 100644 conf/mctpd.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index 211d94f..cb7fa71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 2. mctpd: Allow recovery of devices reporting a nil UUID for development 3. mctpd: Allow configuring .Connectivity as writable for development 4. mctpd: Add AssignEndpointStatic for static EID allocations +5. mctpd: Add a configuration file facility ### Changed diff --git a/README.md b/README.md index 59abfd0..1ec4f8d 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,9 @@ configured (ie., interfaces are enabled, and local addresses have been assigned). There are two sample systemd unit files under the conf/ directory, to coordinate the local setup and the supervision of the mctpd process. +`mctpd` can read some basic configuration from a file, if the `-c FILE` argument +is given. An example configuration is in [`conf/mctpd.conf`](conf/mctpd.conf). + The `mctpd` daemon will expose a dbus interface, claiming the bus name `xyz.openbmc_project.MCTP` and object path `/xyz/openbmc_project/mctp`. This provides a few functions for configuring remote endpoints: diff --git a/conf/mctpd.conf b/conf/mctpd.conf new file mode 100644 index 0000000..82b5546 --- /dev/null +++ b/conf/mctpd.conf @@ -0,0 +1,10 @@ +# Mode: either bus-owner or endpoint +mode = "bus-owner" + +# MCTP protocol configuration. Used for both endpoint and bus-owner modes. +[mctp] +message_timeout_ms = 30 + +# Specify a UUID: not generally required - mctpd will query the system UUID +# where available. +# uuid = "21f0f554-7f7c-4211-9ca1-6d0f000ea9e7" diff --git a/meson.build b/meson.build index 0914e66..872825a 100644 --- a/meson.build +++ b/meson.build @@ -38,6 +38,11 @@ util_sources = ['src/mctp-util.c'] netlink_sources = ['src/mctp-netlink.c'] ops_sources = ['src/mctp-ops.c'] +toml_dep = declare_dependency( + sources: ['lib/tomlc99/toml.c'], + include_directories: include_directories('lib/tomlc99'), +) + executable('mctp', sources: ['src/mctp.c'] + netlink_sources + util_sources + ops_sources, install: true, @@ -56,7 +61,7 @@ if libsystemd.found() sources: [ 'src/mctpd.c', ] + netlink_sources + util_sources + ops_sources, - dependencies: libsystemd, + dependencies: [libsystemd, toml_dep], install: true, install_dir: get_option('sbindir'), ) @@ -67,7 +72,7 @@ if libsystemd.found() 'tests/mctp-ops-test.c', ] + netlink_sources + util_sources, include_directories: include_directories('src'), - dependencies: libsystemd, + dependencies: [libsystemd, toml_dep], ) endif diff --git a/src/mctpd.c b/src/mctpd.c index db6f4f2..38ccd8a 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -29,6 +29,8 @@ #include #include +#include "toml.h" + #include "mctp.h" #include "mctp-util.h" #include "mctp-netlink.h" @@ -136,6 +138,10 @@ typedef struct peer peer; struct ctx { sd_event *event; sd_bus *bus; + + // Configuration + const char *config_filename; + // Main instance for link/address state and listening for updates mctp_nl *nl; @@ -3534,9 +3540,10 @@ static int setup_testing(ctx *ctx) { static void print_usage(ctx *ctx) { - fprintf(stderr, "mctpd [-v] [-N]\n"); + fprintf(stderr, "mctpd [-v] [-N] [-c FILE]\n"); fprintf(stderr, " -v verbose\n"); fprintf(stderr, " -N testing mode. Not safe for production\n"); + fprintf(stderr, " -c FILE read config from FILE\n"); } static int parse_args(ctx *ctx, int argc, char **argv) @@ -3545,12 +3552,13 @@ static int parse_args(ctx *ctx, int argc, char **argv) { .name = "help", .has_arg = no_argument, .val = 'h' }, { .name = "verbose", .has_arg = no_argument, .val = 'v' }, { .name = "testing", .has_arg = no_argument, .val = 'N' }, + { .name = "config", .has_arg = required_argument, .val = 'c' }, { 0 }, }; int c; for (;;) { - c = getopt_long(argc, argv, "+hvN", options, NULL); + c = getopt_long(argc, argv, "+hvNc:", options, NULL); if (c == -1) break; @@ -3561,6 +3569,9 @@ static int parse_args(ctx *ctx, int argc, char **argv) case 'v': ctx->verbose = true; break; + case 'c': + ctx->config_filename = strdup(optarg); + break; case 'h': default: print_usage(ctx); @@ -3570,6 +3581,20 @@ static int parse_args(ctx *ctx, int argc, char **argv) return 0; } +static int parse_config_mode(ctx *ctx, const char *mode) +{ + if (!strcmp(mode, "bus-owner")) { + ctx->bus_owner = true; + } else if (!strcmp(mode, "endpoint")) { + ctx->bus_owner = false; + } else { + warnx("invalid value '%s' for mode configuration", mode); + return -1; + } + + return 0; +} + static int fill_uuid(ctx *ctx) { int rc; @@ -3594,16 +3619,91 @@ static int fill_uuid(ctx *ctx) return rc; } -static int setup_config(ctx *ctx) +static int parse_config_mctp(ctx *ctx, toml_table_t *mctp_tab) +{ + toml_datum_t val; + int rc; + + val = toml_int_in(mctp_tab, "message_timeout_ms"); + if (val.ok) { + int64_t i = val.u.i; + if (i <= 0 || i > 100 * 1000) { + warnx("invalid message_timeout_ms value"); + return -1; + } + ctx->mctp_timeout = i * 1000; + } + + val = toml_string_in(mctp_tab, "uuid"); + if (val.ok) { + rc = sd_id128_from_string(val.u.s, (void *)&ctx->uuid); + free(val.u.s); + if (rc) { + warnx("invalid UUID value"); + return rc; + } + } else { + rc = fill_uuid(ctx); + if (rc) + return rc; + } + + return 0; +} + +static int parse_config(ctx *ctx) { + toml_table_t *conf_root, *mctp_tab; + char errbuf[256] = { 0 }; + toml_datum_t val; + FILE *fp; int rc; - // TODO: this will go in a config file or arguments. + + if (!ctx->config_filename) + return 0; + + rc = -1; + fp = fopen(ctx->config_filename, "r"); + if (!fp) { + warn("can't open configuration file %s", ctx->config_filename); + return -1; + } + + conf_root = toml_parse_file(fp, errbuf, sizeof(errbuf)); + if (!conf_root) { + warnx("can't parse configuration file %s: %s", + ctx->config_filename, errbuf); + goto out_close; + } + + val = toml_string_in(conf_root, "mode"); + if (val.ok) { + rc = parse_config_mode(ctx, val.u.s); + free(val.u.s); + if (rc) + goto out_free; + } + + mctp_tab = toml_table_in(conf_root, "mctp"); + if (mctp_tab) { + rc = parse_config_mctp(ctx, mctp_tab); + if (rc) + goto out_free; + } + + rc = 0; + +out_free: + toml_free(conf_root); +out_close: + fclose(fp); + return rc; +} + +static void setup_config_defaults(ctx *ctx) +{ ctx->mctp_timeout = 250000; // 250ms ctx->bus_owner = true; - rc = fill_uuid(ctx); - if (rc < 0) - return rc; - return 0; } int main(int argc, char **argv) @@ -3613,7 +3713,7 @@ int main(int argc, char **argv) setlinebuf(stdout); - setup_config(ctx); + setup_config_defaults(ctx); mctp_ops_init(); rc = parse_args(ctx, argc, argv); @@ -3621,6 +3721,12 @@ int main(int argc, char **argv) return rc; } + rc = parse_config(ctx); + if (rc) { + err(EXIT_FAILURE, "Can't load configuration file %s", + ctx->config_filename); + } + ctx->nl = mctp_nl_new(false); if (!ctx->nl) { warnx("Failed creating netlink object"); From 947205f0b8e364c1b00abdbdf0966ef3cd21ba85 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Tue, 23 Jan 2024 17:46:00 +0800 Subject: [PATCH 03/10] mctpd: attempt a default configuration from /etc/mctpd.conf Rather than requiring a -c FILE argument, allow a default configuration file of /etc/mctpd.conf. We don't error if this is absent, to allow running with the existing defaults. Signed-off-by: Jeremy Kerr --- CHANGELOG.md | 2 +- README.md | 5 +++-- meson.build | 5 +++++ src/mctpd.c | 26 +++++++++++++++++--------- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb7fa71..4f0a8f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 2. mctpd: Allow recovery of devices reporting a nil UUID for development 3. mctpd: Allow configuring .Connectivity as writable for development 4. mctpd: Add AssignEndpointStatic for static EID allocations -5. mctpd: Add a configuration file facility +5. mctpd: Add a configuration file facility, defaulting to /etc/mctpd.conf. ### Changed diff --git a/README.md b/README.md index 1ec4f8d..0dc2fc7 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,9 @@ configured (ie., interfaces are enabled, and local addresses have been assigned). There are two sample systemd unit files under the conf/ directory, to coordinate the local setup and the supervision of the mctpd process. -`mctpd` can read some basic configuration from a file, if the `-c FILE` argument -is given. An example configuration is in [`conf/mctpd.conf`](conf/mctpd.conf). +`mctpd` can read some basic configuration from a file, by default +`/etc/mctpd.conf`, but other files can be specified with the `-c FILE` argument. +An example configuration is in [`conf/mctpd.conf`](conf/mctpd.conf). The `mctpd` daemon will expose a dbus interface, claiming the bus name `xyz.openbmc_project.MCTP` and object path `/xyz/openbmc_project/mctp`. This diff --git a/meson.build b/meson.build index 872825a..55533d4 100644 --- a/meson.build +++ b/meson.build @@ -29,6 +29,11 @@ conf.set10('MCTPD_WRITABLE_CONNECTIVITY', get_option('unsafe-writable-connectivity'), description: 'Allow writes to the Connectivity member of the au.com.CodeConstruct.MCTP.Endpoint interface on endpoint objects') +conf.set_quoted('MCTPD_CONF_FILE_DEFAULT', + join_paths(get_option('prefix'), get_option('sysconfdir'), 'mctpd.conf'), + description: 'Default configuration file path', +) + config_h = configure_file( output: 'config.h', configuration: conf, diff --git a/src/mctpd.c b/src/mctpd.c index 38ccd8a..1c64607 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -51,6 +51,8 @@ // an arbitrary constant for use with sd_id128_get_machine_app_specific() static const char* mctpd_appid = "67369c05-4b97-4b7e-be72-65cfd8639f10"; +static const char *conf_file_default = MCTPD_CONF_FILE_DEFAULT; + static mctp_eid_t eid_alloc_min = 0x08; static mctp_eid_t eid_alloc_max = 0xfe; @@ -3654,25 +3656,32 @@ static int parse_config_mctp(ctx *ctx, toml_table_t *mctp_tab) static int parse_config(ctx *ctx) { toml_table_t *conf_root, *mctp_tab; + bool conf_file_specified; char errbuf[256] = { 0 }; + const char *filename; toml_datum_t val; FILE *fp; int rc; - if (!ctx->config_filename) - return 0; + conf_file_specified = !!ctx->config_filename; + filename = ctx->config_filename ?: conf_file_default; rc = -1; - fp = fopen(ctx->config_filename, "r"); + fp = fopen(filename, "r"); if (!fp) { - warn("can't open configuration file %s", ctx->config_filename); - return -1; + /* only fatal if a configuration file was specifed by args */ + rc = 0; + if (conf_file_specified) { + warn("can't open configuration file %s", filename); + rc = -1; + } + return rc; } conf_root = toml_parse_file(fp, errbuf, sizeof(errbuf)); if (!conf_root) { - warnx("can't parse configuration file %s: %s", - ctx->config_filename, errbuf); + warnx("can't parse configuration file %s: %s", filename, + errbuf); goto out_close; } @@ -3723,8 +3732,7 @@ int main(int argc, char **argv) rc = parse_config(ctx); if (rc) { - err(EXIT_FAILURE, "Can't load configuration file %s", - ctx->config_filename); + err(EXIT_FAILURE, "Can't read configuration"); } ctx->nl = mctp_nl_new(false); From e01d74da723bf10f1832181ad54aa0b515f23e51 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 6 Jun 2024 13:16:49 +0800 Subject: [PATCH 04/10] mctp-util: move ARRAY_SIZE, min() and max() to utils header We have these repeated, move to a common location. Signed-off-by: Jeremy Kerr --- src/mctp-netlink.c | 5 ----- src/mctp-util.h | 5 +++++ src/mctp.c | 5 ----- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/mctp-netlink.c b/src/mctp-netlink.c index 56f620d..34fb668 100644 --- a/src/mctp-netlink.c +++ b/src/mctp-netlink.c @@ -47,11 +47,6 @@ struct mctp_nl { bool quiet_eexist; }; -#define max(a, b) ((a) > (b) ? (a) : (b)) -#define min(a, b) ((a) < (b) ? (a) : (b)) - -#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) - static int fill_local_addrs(mctp_nl *nl); static int fill_linkmap(mctp_nl *nl); static void sort_linkmap(mctp_nl *nl); diff --git a/src/mctp-util.h b/src/mctp-util.h index 3089299..c56aade 100644 --- a/src/mctp-util.h +++ b/src/mctp-util.h @@ -1,5 +1,10 @@ #include +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define min(a, b) ((a) < (b) ? (a) : (b)) + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) + void mctp_hexdump(const void *b, int len, const char *indent); void print_hex_addr(const uint8_t *data, size_t len); int write_hex_addr(const uint8_t *data, size_t len, char* dest, size_t dest_len); diff --git a/src/mctp.c b/src/mctp.c index c136e21..0edd02b 100644 --- a/src/mctp.c +++ b/src/mctp.c @@ -35,11 +35,6 @@ #include "mctp-netlink.h" #include "mctp-ops.h" -#define max(a, b) ((a) > (b) ? (a) : (b)) -#define min(a, b) ((a) < (b) ? (a) : (b)) - -#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) - struct ctx { mctp_nl *nl; bool verbose; From 2830d8afc542fdbf46a4592125ff0bb1fc5430e2 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 6 Jun 2024 13:18:52 +0800 Subject: [PATCH 05/10] mctpd: conf: use a set of role descriptions for parsing the mode config values Rather than hard-coding the mode types in the config parsing, move these to an array of role descriptions, and parse from that. This will be used in an upcoming change where we parse roles from dbus properties too. Signed-off-by: Jeremy Kerr --- src/mctpd.c | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/mctpd.c b/src/mctpd.c index 1c64607..d3b8fc2 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -82,6 +82,31 @@ struct ctx; // all local peers have the same phys static const dest_phys local_phys = { .ifindex = 0 }; +enum endpoint_role { + ENDPOINT_ROLE_UNKNOWN, + ENDPOINT_ROLE_BUS_OWNER, + ENDPOINT_ROLE_ENDPOINT, +}; + +struct role { + enum endpoint_role role; + const char *conf_val; +}; + +static const struct role roles[] = { + [ENDPOINT_ROLE_UNKNOWN] = { + .role = ENDPOINT_ROLE_UNKNOWN, + }, + [ENDPOINT_ROLE_BUS_OWNER] = { + .role = ENDPOINT_ROLE_BUS_OWNER, + .conf_val = "bus-owner", + }, + [ENDPOINT_ROLE_ENDPOINT] = { + .role = ENDPOINT_ROLE_ENDPOINT, + .conf_val = "endpoint", + }, +}; + struct peer { int net; mctp_eid_t eid; @@ -3585,16 +3610,20 @@ static int parse_args(ctx *ctx, int argc, char **argv) static int parse_config_mode(ctx *ctx, const char *mode) { - if (!strcmp(mode, "bus-owner")) { - ctx->bus_owner = true; - } else if (!strcmp(mode, "endpoint")) { - ctx->bus_owner = false; - } else { - warnx("invalid value '%s' for mode configuration", mode); - return -1; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(roles); i++) { + const struct role *role = &roles[i]; + + if (!role->conf_val || strcmp(role->conf_val, mode)) + continue; + + ctx->bus_owner = role->role == ENDPOINT_ROLE_BUS_OWNER; + return 0; } - return 0; + warnx("invalid value '%s' for mode configuration", mode); + return -1; } static int fill_uuid(ctx *ctx) From f26db98f17eac3962c15fbb0da25d6fa970aa7c9 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Tue, 6 Feb 2024 14:11:05 +0800 Subject: [PATCH 06/10] mctpd: dbus interface rework This change implements the dbus interface rework specified in https://github.com/CodeConstruct/mctp/issues/40. Essentially: this uses more standard bus, object and path names, and moves away from the xyz.openbmc_project namespace, and use au.com.codeconstruct (all lowercase) there instead, as we're not specificially an OpenBMC project. We also put collections of things (networks and endpoints) under a specifically-named object path, so we can introduce new collections alongside (interfaces) without compatibility issues This means: - the bus owner name is now au.com.codeconstruct.MCTP1 - interfaces are namespaced and versioned: - au.com.codeconstruct.MCTP.Endpoint1 - au.com.codeconstruct.MCTP.BusOwner1 - the top-level entrypoint path is versioned, as `/au/com/codeconstruct/mctp1` - the endpoint object tree is structured as `/au/com/codeconstruct/mctp1/networks//endpoints/` Signed-off-by: Jeremy Kerr --- CHANGELOG.md | 4 ++ README.md | 10 ++--- conf/mctpd-dbus.conf | 6 +-- conf/mctpd.service | 2 +- docs/endpoint-recovery.md | 60 +++++++++++++++-------------- docs/mctpd.md | 81 +++++++++++++++++++++++---------------- meson.build | 2 +- meson_options.txt | 2 +- src/mctpd.c | 32 +++++++++------- tests/conftest.py | 2 +- tests/mctp_test_utils.py | 10 ++--- tests/test_mctpd.py | 8 ++-- 12 files changed, 122 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f0a8f5..07fdc1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 3. The `tests` option has changed type from `feature` to `boolean`. Tests are enabled by default. +4. The dbus interface has undergone a major rework, using standard prefixes + and version interface, bus owner and entry-point object names. See + docs/mctpd.md for full details on the new interface. + ### Fixed 1. mctpd: EID assignments now work in the case where a new endpoint has a diff --git a/README.md b/README.md index 0dc2fc7..033ed7c 100644 --- a/README.md +++ b/README.md @@ -73,12 +73,12 @@ coordinate the local setup and the supervision of the mctpd process. An example configuration is in [`conf/mctpd.conf`](conf/mctpd.conf). The `mctpd` daemon will expose a dbus interface, claiming the bus name -`xyz.openbmc_project.MCTP` and object path `/xyz/openbmc_project/mctp`. This +`au.com.codeconstruct.MCTP1` and object path `/au/com/codeconstruct/mctp1`. This provides a few functions for configuring remote endpoints: - # busctl introspect xyz.openbmc_project.MCTP /xyz/openbmc_project/mctp + # busctl introspect au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1/ NAME TYPE SIGNATURE RESULT/VALUE FLAGS - au.com.CodeConstruct.MCTP interface - - - + au.com.codeconstruct.MCTP interface - - - .AssignEndpoint method say yisb - .AssignEndpointStatic method sayy yisb - .LearnEndpoint method say yisb - @@ -88,7 +88,7 @@ Results of mctpd enumeration are also represented as dbus objects, using the OpenBMC-specified MCTP endpoint format. Each endpoint appears on the bus at the object path: - /xyz/openbmc_project/mctp// + /au/com/codeconstruct/mctp/networks//endpoints/ where `mctpd` exposes three dbus interfaces for each: @@ -103,7 +103,7 @@ where `mctpd` exposes three dbus interfaces for each: This interface is defined by the Common.UUID phosphor-dbus specification. - - `au.com.CodeConstruct.MCTP.EndPoint`: Additional control methods for the + - `au.com.codeconstruct.MCTP1.Endpoint1`: Additional control methods for the endpoint - for example, `Remove` Testing diff --git a/conf/mctpd-dbus.conf b/conf/mctpd-dbus.conf index e35eaec..e97bfe0 100644 --- a/conf/mctpd-dbus.conf +++ b/conf/mctpd-dbus.conf @@ -3,8 +3,8 @@ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> - - - + + + diff --git a/conf/mctpd.service b/conf/mctpd.service index 5f1eb6c..62410c1 100644 --- a/conf/mctpd.service +++ b/conf/mctpd.service @@ -6,7 +6,7 @@ After=mctp-local.target [Service] Type=dbus -BusName=xyz.openbmc_project.MCTP +BusName=au.com.codeconstruct.MCTP1 ExecStart=/usr/sbin/mctpd [Install] diff --git a/docs/endpoint-recovery.md b/docs/endpoint-recovery.md index 63180c1..d67ab4a 100644 --- a/docs/endpoint-recovery.md +++ b/docs/endpoint-recovery.md @@ -144,20 +144,21 @@ issues. This may include applications in addition to the process which requested `mctpd` currently structures its D-Bus objects as follows: ``` -root@cc-nvme-mi:~# busctl tree xyz.openbmc_project.MCTP -`-/xyz - `-/xyz/openbmc_project - `-/xyz/openbmc_project/mctp - `-/xyz/openbmc_project/mctp/1 - |-/xyz/openbmc_project/mctp/1/12 - |-/xyz/openbmc_project/mctp/1/8 - `-/xyz/openbmc_project/mctp/1/9 +root@cc-nvme-mi:~# busctl tree au.com.codeconstruct.MCTP1 +└─/au + └─/au/com + └─/au/com/codeconstruct + └─/au/com/codeconstruct/mctp1 + ├─/au/com/codeconstruct/mctp1/networks/1 + │ └─/au/com/codeconstruct/mctp1/networks/1/endpoints/12 + ├─/au/com/codeconstruct/mctp1/networks/1/endpoints/8 + └─/au/com/codeconstruct/mctp1/networks/1/endpoints/9 ``` ``` -root@cc-nvme-mi:~# busctl introspect xyz.openbmc_project.MCTP /xyz/openbmc_project/mctp +root@cc-nvme-mi:~# busctl introspect au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 NAME TYPE SIGNATURE RESULT/VALUE FLAGS -au.com.CodeConstruct.MCTP interface - - - +au.com.codeconstruct.Endpoint1 interface - - - .AssignEndpoint method say yisb - .LearnEndpoint method say yisb - .SetupEndpoint method say yisb - @@ -178,7 +179,7 @@ org.freedesktop.DBus.Properties interface - - - ``` ``` -root@cc-nvme-mi:~# busctl introspect xyz.openbmc_project.MCTP /xyz/openbmc_project/mctp/1 +root@cc-nvme-mi:~# busctl introspect au.com.codeconstruct.MCTP1 /au/com/codecontrust/mctp1/networks/1 NAME TYPE SIGNATURE RESULT/VALUE FLAGS org.freedesktop.DBus.Introspectable interface - - - .Introspect method - s - @@ -193,9 +194,9 @@ org.freedesktop.DBus.Properties interface - - - ``` ``` -root@cc-nvme-mi:~# busctl introspect xyz.openbmc_project.MCTP /xyz/openbmc_project/mctp/1/9 +root@cc-nvme-mi:~# busctl introspect au.com.codeconstruct.MCTP1 /au/com/codecontrust/mctp1/networks/1/endpoints/9 NAME TYPE SIGNATURE RESULT/VALUE FLAGS -au.com.CodeConstruct.MCTP.Endpoint interface - - - +au.com.codeconstruct.MCTP.Endpoint1 interface - - - .Remove method - - - .SetMTU method u - - org.freedesktop.DBus.Introspectable interface - - - @@ -217,16 +218,16 @@ xyz.openbmc_project.MCTP.Endpoint interface - - ``` The problem we have deals with specific endpoints. To satisfy the third scenario -the object `/xyz/openbmc_project/mctp/1/9` would be removed. +the object `/au/com/codeconstruct/mctp1/networks/1/endpoints/9` would be removed. For scenarios 1 and 2 we need to add capabilities on the endpoint object. The -`au.com.CodeConstruct.MCTP.Endpoint` already exposes endpoint control APIs, and +`au.com.codeconstruct.MCTP.Endpoint` already exposes endpoint control APIs, and given we're not removing capabilities we can change it. ## Proposed Design The approach is to add a `.Recover` method and a `.Connectivity` property to the -`au.com.CodeConstruct.MCTP.Endpoint` interface. `.Recover` takes no arguments, +`au.com.codeconstruct.MCTP.Endpoint1` interface. `.Recover` takes no arguments, produces no result, and returns immediately. `.Connectivity` takes one of two values: @@ -346,11 +347,11 @@ The general strategy for tracking endpoint lifecycles is [as follows] data-structures ``` - type='signal',sender='xyz.openbmc_project.MCTP',path_namespace='/xyz/openbmc_project/mctp' + type='signal',sender='xyz.openbmc_project.MCTP',path_namespace='/au/com/codeconstruct/mctp1' ``` 2. Issue a call to `GetManagedObjects` `org.freedesktop.DBus.ObjectManager` on - `/xyz/openbmc_project/mctp` for initial population of local data-structures + `/au/com/codeconstruct/mctp1` for initial population of local data-structures 3. Issue @@ -360,7 +361,8 @@ The general strategy for tracking endpoint lifecycles is [as follows] registered as an MCTP endpoint. 2. The FRU data is decoded and `SetupEndpoint` on the - `au.com.CodeConstruct.MCTP` interface of the `/xyz/openbmc_project/mctp` + `au.com.codeconstruct.MCTP.BusOwner1` interface of the + `/au/com/codeconstruct/mctp1` object hosted by `mctpd` is invoked. 3. The device was not previously configured and has no programmed static EID. @@ -372,8 +374,8 @@ The general strategy for tracking endpoint lifecycles is [as follows] interfaces): ``` - "/xyz/openbmc_project/mctp/1/9": { - "au.com.CodeConstruct.MCTP.Endpoint": { }, + "/au.com.codeconstruct/mctp1/networks/1/endpoints/9": { + "au.com.codeconstruct.MCTP.Endpoint1": { }, "xyz.openbmc_project.Common.UUID": { "UUID": "..." }, @@ -394,16 +396,18 @@ The general strategy for tracking endpoint lifecycles is [as follows] 7. MCTP endpoint 9 stops responding to NVMe-MI commands from `nvmesensor`. 8. `nvmesensor` calls the `Recover` method on the - `au.com.CodeConstruct.MCTP.Endpoint` of the `/xyz/openbmc_project/mctp/1/9` + `au.com.codeconstruct.MCTP.Endpoint1` of the + `/au/com/codeconstruct/mctp1/networks/1/endpoints/9` D-Bus object hosted by `mctpd`. This may occur via e.g. failure to regularly poll the drive CTEMP. 9. `mctpd` [emits a `PropertiesChanged` signal][dbus-spec-standard-interfaces-properties] - from `/xyz/openbmc_project/mctp/1/9` with the following contents: + from `/au/com/codeconstruct/mctp/networks/1/enpoints/9` with the following + contents: ``` [ - "au.com.CodeConstruct.MCTP.Endpoint", + "au.com.codeconstruct.MCTP.Endpoint1", { "Connectivity": "Degraded" }, { } ] @@ -417,12 +421,12 @@ The general strategy for tracking endpoint lifecycles is [as follows] 12. The device fails to respond to all (retried) queries issued inside `Treclaim`. -13. `mctpd` removes `/xyz/openbmc_project/mctp/1/9` from D-Bus, emitting an - `InterfacesRemoved` signal with the following content +13. `mctpd` removes `/au/com/codeconstruct/mctp1/networks/1/endpoints/9` from + D-Bus, emitting an `InterfacesRemoved` signal with the following content ``` - "/xyz/openbmc_project/mctp/1/9": [ - "au.com.CodeConstruct.MCTP.Endpoint", + "/au/com/codeconstruct/mctp1/networks/1/endpoints/9": [ + "au.com.codeconstruct.MCTP.Endpoint1", "xyz.openbmc_project.MCTP.Endpoint", ... ] diff --git a/docs/mctpd.md b/docs/mctpd.md index 0ff827b..2e1f967 100644 --- a/docs/mctpd.md +++ b/docs/mctpd.md @@ -2,20 +2,28 @@ ## D-Bus -`mctpd` provides a D-Bus path of `/xyz/openbmc_project/mctp`. For each known MCTP endpoint, `mctpd` -will populate an object `/xyz/openbmc_project/mctp//`. The objects have interface -`xyz.openbmc_project.MCTP.Endpoint`, as per -[OpenBMC documentation](https://github.com/openbmc/phosphor-dbus-interfaces/tree/master/yaml/xyz/openbmc_project/MCTP). +`mctpd` provides a D-Bus service named `au.com.codeconstruct.MCTP1`, and a base +object path of `/au/com/codeconstruct/mctp1`. For each known MCTP endpoint, +`mctpd` will populate an object at +`/au/com/codeconstruct/mctp1/networks//endpoints/`. The objects have +interface `xyz.openbmc_project.MCTP.Endpoint`, as per [OpenBMC +documentation](https://github.com/openbmc/phosphor-dbus-interfaces/tree/master/yaml/xyz/openbmc_project/MCTP). -As well as the standard interfaces, `mctpd` provides methods to add and configure MCTP endpoints. -These are provided by the `au.com.CodeConstruct.MCTP` D-Bus interface. +As well as those standard interfaces, `mctpd` provides methods to add and +configure MCTP endpoints. These are provided by the `au.com.codeconstruct.MCTP1` +D-Bus interface. -### `.SetupEndpoint` +## Bus-owner methods: `au.com.codeconstruct.MCTP.BusOwner1` interface -This method is the normal method used to add a MCTP endpoint. -The endpoint is identified by MCTP network interface, and physical address. -`mctpd` will query for the endpoint's current EID, and assign an EID to the endpoint if needed. -`mctpd` will add local MCTP routes and neighbour table entries for endpoints as they are added. +This interface exposes bus-owner level functions. + +### `.SetupEndpoint`: `say` → `yisb` + +This method is the normal method used to add a MCTP endpoint. The endpoint is +identified by MCTP network interface, and physical address. `mctpd` will query +for the endpoint's current EID, and assign an EID to the endpoint if needed. +`mctpd` will add local MCTP routes and neighbour table entries for endpoints as +they are added. `SetupEndpoint ` @@ -29,23 +37,27 @@ new (bool) - true if a new EID was assigned `` is an interface such as `mctpi2c6`. -`` depends on the transport type - for i2c it is a 1 byte client address (7-bit, the same as other Linux tools like `i2cdetect`). +`` depends on the transport type - for i2c it is a 1 byte client address +(7-bit, the same as other Linux tools like `i2cdetect`). An example: ```shell -busctl call xyz.openbmc_project.MCTP /xyz/openbmc_project/mctp \ - au.com.CodeConstruct.MCTP SetupEndpoint say mctpi2c6 1 0x1d +busctl call au.com.codeconstruct.MCTP1 \ + /au/com/codeconstruct/mctp1 \ + au.com.codeconstruct.MCTP1 \ + SetupEndpoint say mctpi2c6 1 0x1d ``` `1` is the length of the hwaddr array. -### `.AssignEndpoint` +### `.AssignEndpoint`: `say` → `yisb` -Similar to SetupEndpoint, but will always assign an EID rather than querying for existing ones. -Will return `new = false` when an endpoint is already known to `mctpd`. +Similar to SetupEndpoint, but will always assign an EID rather than querying for +existing ones. Will return `new = false` when an endpoint is already known to +`mctpd`. -### `.AssignEndpointStatic` +### `.AssignEndpointStatic`: `sayy` → `yisb` Similar to AssignEndpoint, but takes an additional EID argument: @@ -59,35 +71,36 @@ This call will fail if the endpoint already has an EID, and that EID is different from `static-EID`, or if `static-EID` is already assigned to another endpoint. -### `.LearnEndpoint` +### `.LearnEndpoint`: `say` → `yisb` -Like SetupEndpoint but will not assign EIDs, will only query endpoints for a current EID. -The `new` return value is set to `false` for an already known endpoint, or `true` when an -endpoint's EID is newly discovered. +Like SetupEndpoint but will not assign EIDs, will only query endpoints for a +current EID. The `new` return value is set to `false` for an already known +endpoint, or `true` when an endpoint's EID is newly discovered. -## Endpoint Methods +## Endpoint methods: the `au.com.codeconstruct.MCTP.Endpoint1` interface -Each endpoint object has methods to configure it, with `au.com.CodeConstruct.MCTP.Endpoint` -interface on each endpoint. +Each endpoint object has methods to configure it, through the +`au.com.codeconstruct.MCTP.Endpoint1` interface on each endpoint. -## `.SetMTU` +## `.SetMTU`: `u` -Sets the MTU (maximum transmission unit) on the route for that endpoint. This must be within -the MTU range allowed for the network device. For i2c that is [68, 254]. +Sets the MTU (maximum transmission unit) on the route for that endpoint. This +must be within the MTU range allowed for the network device. For i2c that is +[68, 254]. -If a route-specific MTU has not been set (or set to 0), Linux will use the per-interface -MTU, configurable with `mctp link set mtu `. +If a route-specific MTU has not been set (or set to 0), Linux will use the +per-interface MTU, configurable with `mctp link set mtu `. An example, setting MTU of 80: ```shell -busctl call xyz.openbmc_project.MCTP /xyz/openbmc_project/mctp/1/11 \ - au.com.CodeConstruct.MCTP.Endpoint SetMTU u 80 +busctl call au.com.codeconstruct.MCTP1 \ + /au/com/codeconstruct/mctp1/networks/1/endpoints/11 \ + au.com.codeconstruct.MCTP.Endpoint1 \ + SetMTU u 80 ``` ## `.Remove` Removes the MCTP endpoint from `mctpd`, and deletes routes and neighbour entries. - - diff --git a/meson.build b/meson.build index 55533d4..592c76d 100644 --- a/meson.build +++ b/meson.build @@ -27,7 +27,7 @@ conf.set10('MCTPD_RECOVER_NIL_UUID', ) conf.set10('MCTPD_WRITABLE_CONNECTIVITY', get_option('unsafe-writable-connectivity'), - description: 'Allow writes to the Connectivity member of the au.com.CodeConstruct.MCTP.Endpoint interface on endpoint objects') + description: 'Allow writes to the Connectivity member of the au.com.codeconstruct.MCTP.Endpoint1 interface on endpoint objects') conf.set_quoted('MCTPD_CONF_FILE_DEFAULT', join_paths(get_option('prefix'), get_option('sysconfdir'), 'mctpd.conf'), diff --git a/meson_options.txt b/meson_options.txt index b634a5d..b8323f5 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -2,5 +2,5 @@ option('unsafe-recover-nil-uuid', type: 'boolean', value: false) option('unsafe-writable-connectivity', type: 'boolean', value: false, - description: 'Allow writes to the Connectivity member of the au.com.CodeConstruct.MCTP.Endpoint interface on endpoint objects') + description: 'Allow writes to the Connectivity member of the au.com.codeconstruct.MCTP.Endpoint1 interface on endpoint objects') option('tests', type: 'boolean', value: true) diff --git a/src/mctpd.c b/src/mctpd.c index d3b8fc2..b120788 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -40,11 +40,11 @@ #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) -#define MCTP_DBUS_PATH "/xyz/openbmc_project/mctp" -#define CC_MCTP_DBUS_IFACE "au.com.CodeConstruct.MCTP" -#define CC_MCTP_DBUS_IFACE_ENDPOINT "au.com.CodeConstruct.MCTP.Endpoint" -#define CC_MCTP_DBUS_IFACE_TESTING "au.com.CodeConstruct.MCTPTesting" -#define MCTP_DBUS_IFACE "xyz.openbmc_project.MCTP" +#define MCTP_DBUS_PATH "/au/com/codeconstruct/mctp1" +#define CC_MCTP_DBUS_IFACE_BUSOWNER "au.com.codeconstruct.MCTP.BusOwner1" +#define CC_MCTP_DBUS_IFACE_ENDPOINT "au.com.codeconstruct.MCTP.Endpoint1" +#define CC_MCTP_DBUS_IFACE_TESTING "au.com.codeconstruct.MCTPTesting" +#define MCTP_DBUS_NAME "au.com.codeconstruct.MCTP1" #define MCTP_DBUS_IFACE_ENDPOINT "xyz.openbmc_project.MCTP.Endpoint" #define OPENBMC_IFACE_COMMON_UUID "xyz.openbmc_project.Common.UUID" @@ -397,8 +397,9 @@ static int peer_from_path(ctx *ctx, const char* path, peer **ret_peer) int rc; *ret_peer = NULL; - rc = sd_bus_path_decode_many(path, MCTP_DBUS_PATH "/%/%", - &netstr, &eidstr); + rc = sd_bus_path_decode_many(path, + MCTP_DBUS_PATH "/networks/%/endpoints/%", + &netstr, &eidstr); if (rc == 0) return -ENOENT; if (rc < 0) @@ -434,7 +435,7 @@ static int path_from_peer(const peer *peer, char ** ret_path) { return -ENOMEM; /* can't use sd_bus_path_encode_many() since it escapes leading digits */ - snprintf(buf, l, "%s/%d/%d", MCTP_DBUS_PATH, + snprintf(buf, l, "%s/networks/%d/endpoints/%d", MCTP_DBUS_PATH, peer->net, peer->eid); *ret_path = buf; return 0; @@ -2912,7 +2913,7 @@ static char* net_path(int net) } /* can't use sd_bus_path_encode_many() since it escapes leading digits */ - snprintf(buf, l, "%s/%d", MCTP_DBUS_PATH, net); + snprintf(buf, l, "%s/networks/%d", MCTP_DBUS_PATH, net); return buf; } @@ -3105,7 +3106,7 @@ static int setup_bus(ctx *ctx) mix non-fallback and fallback vtables on MCTP_DBUS_PATH */ rc = sd_bus_add_fallback_vtable(ctx->bus, NULL, MCTP_DBUS_PATH, - CC_MCTP_DBUS_IFACE, + CC_MCTP_DBUS_IFACE_BUSOWNER, bus_mctpd_vtable, bus_mctpd_find, ctx); @@ -3167,14 +3168,17 @@ static int setup_bus(ctx *ctx) } -int request_dbus(ctx *ctx) { +int request_dbus(ctx *ctx) +{ int rc; - rc = sd_bus_request_name(ctx->bus, MCTP_DBUS_IFACE, 0); + rc = sd_bus_request_name(ctx->bus, MCTP_DBUS_NAME, 0); if (rc < 0) { - warnx("Failed requesting name %s", MCTP_DBUS_IFACE); + warnx("Failed requesting dbus name %s", MCTP_DBUS_NAME); + return rc; } - return rc; + + return 0; } diff --git a/tests/conftest.py b/tests/conftest.py index ae05099..86988ee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -812,7 +812,7 @@ async def mctpd_proc(self, nursery, task_status = trio.TASK_STATUS_IGNORED): s = trio.Semaphore(initial_value = 0) def name_owner_changed(name, new_owner, old_owner): - if name == 'xyz.openbmc_project.MCTP': + if name == 'au.com.codeconstruct.MCTP1': s.release() await interface.on_name_owner_changed(name_owner_changed) diff --git a/tests/mctp_test_utils.py b/tests/mctp_test_utils.py index ae48066..1b40bdc 100644 --- a/tests/mctp_test_utils.py +++ b/tests/mctp_test_utils.py @@ -1,14 +1,14 @@ async def mctpd_mctp_obj(dbus): obj = await dbus.get_proxy_object( - 'xyz.openbmc_project.MCTP', - '/xyz/openbmc_project/mctp' + 'au.com.codeconstruct.MCTP1', + '/au/com/codeconstruct/mctp1' ) - return await obj.get_interface('au.com.CodeConstruct.MCTP') + return await obj.get_interface('au.com.codeconstruct.MCTP.BusOwner1') async def mctpd_mctp_endpoint_obj(dbus, path): obj = await dbus.get_proxy_object( - 'xyz.openbmc_project.MCTP', + 'au.com.codeconstruct.MCTP1', path, ) - return await obj.get_interface('au.com.CodeConstruct.MCTP.Endpoint') + return await obj.get_interface('au.com.codeconstruct.MCTP.Endpoint1') diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index 052e0a4..2ca90fa 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -11,10 +11,10 @@ # - C: Connection # - P: Path # - I: Interface -MCTPD_C = 'xyz.openbmc_project.MCTP' -MCTPD_MCTP_P = '/xyz/openbmc_project/mctp' -MCTPD_MCTP_I = 'au.com.CodeConstruct.MCTP' -MCTPD_ENDPOINT_I = 'au.com.CodeConstruct.MCTP.Endpoint' +MCTPD_C = 'au.com.codeconstruct.MCTP1' +MCTPD_MCTP_P = '/au/com/codeconstruct/mctp1' +MCTPD_MCTP_I = 'au.com.codeconstruct.MCTP.BusOwner1' +MCTPD_ENDPOINT_I = 'au.com.codeconstruct.MCTP.Endpoint1' DBUS_OBJECT_MANAGER_I = 'org.freedesktop.DBus.ObjectManager' DBUS_PROPERTIES_I = 'org.freedesktop.DBus.Properties' From 9ed35ed31664d6c3f5985a3b4523f02ddd9b140c Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 30 May 2024 14:10:09 +0800 Subject: [PATCH 07/10] mctpd: use .DRAFT suffix on BusOwner1 interface We'll be modifying this for the new Role-based interfaces which exist on the link/interface objects, so use a temporary name for now. Signed-off-by: Jeremy Kerr --- docs/endpoint-recovery.md | 4 ++-- docs/mctpd.md | 2 +- src/mctpd.c | 2 +- tests/mctp_test_utils.py | 2 +- tests/test_mctpd.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/endpoint-recovery.md b/docs/endpoint-recovery.md index d67ab4a..d5bd109 100644 --- a/docs/endpoint-recovery.md +++ b/docs/endpoint-recovery.md @@ -361,7 +361,7 @@ The general strategy for tracking endpoint lifecycles is [as follows] registered as an MCTP endpoint. 2. The FRU data is decoded and `SetupEndpoint` on the - `au.com.codeconstruct.MCTP.BusOwner1` interface of the + `au.com.codeconstruct.MCTP.BusOwner1.DRAFT` interface of the `/au/com/codeconstruct/mctp1` object hosted by `mctpd` is invoked. @@ -402,7 +402,7 @@ The general strategy for tracking endpoint lifecycles is [as follows] poll the drive CTEMP. 9. `mctpd` [emits a `PropertiesChanged` signal][dbus-spec-standard-interfaces-properties] - from `/au/com/codeconstruct/mctp/networks/1/enpoints/9` with the following + from `/au/com/codeconstruct/mctp/networks/1/endpoints/9` with the following contents: ``` diff --git a/docs/mctpd.md b/docs/mctpd.md index 2e1f967..3cc65da 100644 --- a/docs/mctpd.md +++ b/docs/mctpd.md @@ -13,7 +13,7 @@ As well as those standard interfaces, `mctpd` provides methods to add and configure MCTP endpoints. These are provided by the `au.com.codeconstruct.MCTP1` D-Bus interface. -## Bus-owner methods: `au.com.codeconstruct.MCTP.BusOwner1` interface +## Bus-owner methods: `au.com.codeconstruct.MCTP.BusOwner1.DRAFT` interface This interface exposes bus-owner level functions. diff --git a/src/mctpd.c b/src/mctpd.c index b120788..867fdb7 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -41,7 +41,7 @@ #define min(a, b) ((a) < (b) ? (a) : (b)) #define MCTP_DBUS_PATH "/au/com/codeconstruct/mctp1" -#define CC_MCTP_DBUS_IFACE_BUSOWNER "au.com.codeconstruct.MCTP.BusOwner1" +#define CC_MCTP_DBUS_IFACE_BUSOWNER "au.com.codeconstruct.MCTP.BusOwner1.DRAFT" #define CC_MCTP_DBUS_IFACE_ENDPOINT "au.com.codeconstruct.MCTP.Endpoint1" #define CC_MCTP_DBUS_IFACE_TESTING "au.com.codeconstruct.MCTPTesting" #define MCTP_DBUS_NAME "au.com.codeconstruct.MCTP1" diff --git a/tests/mctp_test_utils.py b/tests/mctp_test_utils.py index 1b40bdc..ffd31cc 100644 --- a/tests/mctp_test_utils.py +++ b/tests/mctp_test_utils.py @@ -4,7 +4,7 @@ async def mctpd_mctp_obj(dbus): 'au.com.codeconstruct.MCTP1', '/au/com/codeconstruct/mctp1' ) - return await obj.get_interface('au.com.codeconstruct.MCTP.BusOwner1') + return await obj.get_interface('au.com.codeconstruct.MCTP.BusOwner1.DRAFT') async def mctpd_mctp_endpoint_obj(dbus, path): obj = await dbus.get_proxy_object( diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index 2ca90fa..8c2e550 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -13,7 +13,7 @@ # - I: Interface MCTPD_C = 'au.com.codeconstruct.MCTP1' MCTPD_MCTP_P = '/au/com/codeconstruct/mctp1' -MCTPD_MCTP_I = 'au.com.codeconstruct.MCTP.BusOwner1' +MCTPD_MCTP_I = 'au.com.codeconstruct.MCTP.BusOwner1.DRAFT' MCTPD_ENDPOINT_I = 'au.com.codeconstruct.MCTP.Endpoint1' DBUS_OBJECT_MANAGER_I = 'org.freedesktop.DBus.ObjectManager' DBUS_PROPERTIES_I = 'org.freedesktop.DBus.Properties' From 29936ec4089980ac7cf6c6fd3df4964c36633056 Mon Sep 17 00:00:00 2001 From: Thu Nguyen Date: Thu, 6 Jun 2024 23:43:09 +0000 Subject: [PATCH 08/10] mctpd: enumerate /networks and /endpoints object paths Enumerate the parent D-Bus object path `.../mctp1/networks` for the network paths `.../mctp1/networks/` and `.../mctp1/networks//endpoints` for the endpoints path `.../mctp1/networks//endpoints/`. Tested: Check the MCTP D-Bus interface: ``` busctl tree au.com.codeconstruct.MCTP1 `- /au `- /au/com `- /au/com/codeconstruct `- /au/com/codeconstruct/mctp1 `- /au/com/codeconstruct/mctp1/networks `- /au/com/codeconstruct/mctp1/networks/1 `- /au/com/codeconstruct/mctp1/networks/1/endpoints `- /au/com/codeconstruct/mctp1/networks/1/endpoints/8 ``` Signed-off-by: Thu Nguyen --- src/mctpd.c | 77 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/src/mctpd.c b/src/mctpd.c index 867fdb7..2be12d3 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -41,6 +41,7 @@ #define min(a, b) ((a) < (b) ? (a) : (b)) #define MCTP_DBUS_PATH "/au/com/codeconstruct/mctp1" +#define MCTP_DBUS_PATH_NETWORKS "/au/com/codeconstruct/mctp1/networks" #define CC_MCTP_DBUS_IFACE_BUSOWNER "au.com.codeconstruct.MCTP.BusOwner1.DRAFT" #define CC_MCTP_DBUS_IFACE_ENDPOINT "au.com.codeconstruct.MCTP.Endpoint1" #define CC_MCTP_DBUS_IFACE_TESTING "au.com.codeconstruct.MCTPTesting" @@ -2901,6 +2902,20 @@ static int bus_endpoint_find_uuid(sd_bus *bus, const char *path, return 0; } +static char* root_endpoints_path(int net) +{ + size_t l; + char *buf = NULL; + + l = strlen(MCTP_DBUS_PATH) + 30; + buf = malloc(l); + if (!buf) { + return NULL; + } + snprintf(buf, l, "%s/networks/%d/endpoints", MCTP_DBUS_PATH, net); + return buf; +} + static char* net_path(int net) { size_t l; @@ -2998,15 +3013,28 @@ static int mctpd_dbus_enumerate(sd_bus *bus, const char* path, // NULL terminator num_nodes = 1; - // .../mctp object + // .../mctp1 object + num_nodes++; + // .../mctp1/networks object num_nodes++; + // .../mctp1/networks/ + for (i = 0; i < ctx->num_nets; i++) { + num_nodes++; + for (size_t t = 0; t < 256; t++) { + if (ctx->nets[i].peeridx[t] != -1) { + // .../mctp1/networks//endpoints object + num_nodes++; + break; + } + } + } + + // .../mctp1/networks//endpoints/ object for (i = 0; i < ctx->size_peers; i++) if (ctx->peers[i].published) num_nodes++; - num_nodes += ctx->num_nets; - nodes = malloc(sizeof(*nodes) * num_nodes); if (!nodes) { rc = -ENOMEM; @@ -3014,6 +3042,7 @@ static int mctpd_dbus_enumerate(sd_bus *bus, const char* path, } j = 0; + // .../mctp1 nodes[j] = strdup(MCTP_DBUS_PATH); if (!nodes[j]) { rc = -ENOMEM; @@ -3021,6 +3050,38 @@ static int mctpd_dbus_enumerate(sd_bus *bus, const char* path, } j++; + // .../mctp1/networks + nodes[j] = strdup(MCTP_DBUS_PATH_NETWORKS); + if (!nodes[j]) { + rc = -ENOMEM; + goto out; + } + j++; + + for (i = 0; i < ctx->num_nets; i++) { + // .../mctp1/networks/ + nodes[j] = net_path(ctx->nets[i].net); + if (nodes[j] == NULL) { + rc = -ENOMEM; + goto out; + } + j++; + + for (size_t t = 0; t < 256; t++) { + if (ctx->nets[i].peeridx[t] == -1) { + continue; + } + // .../mctp1/networks//endpoints object + nodes[j] = root_endpoints_path(ctx->nets[i].net); + if (nodes[j] == NULL) { + rc = -ENOMEM; + goto out; + } + j++; + break; + } + } + // Peers for (i = 0; i < ctx->size_peers; i++) { peer *peer = &ctx->peers[i]; @@ -3034,16 +3095,6 @@ static int mctpd_dbus_enumerate(sd_bus *bus, const char* path, j++; } - // Nets - for (i = 0; i < ctx->num_nets; i++) { - nodes[j] = net_path(ctx->nets[i].net); - if (nodes[j] == NULL) { - rc = -ENOMEM; - goto out; - } - j++; - } - // NULL terminator nodes[j] = NULL; j++; From 6e9c757ede3b54268188d5d7e57ed68557d8f60d Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Mon, 10 Jun 2024 11:58:34 +0800 Subject: [PATCH 09/10] mctp-netlink: Add a facility to store external data on links For upcoming mctpd changes, we will need a facility for storing dbus-specific information on each link. This change adds a void * userdata pointer to the links, and accessors for callers to get/set this pointer. For convenience, we also pass this pointer on change events, allowing for a quick way to release/update the userdata on link delete/change. WIP: untested. Signed-off-by: Jeremy Kerr --- src/mctp-netlink.c | 27 +++++++++++++++++++++++++-- src/mctp-netlink.h | 16 ++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/mctp-netlink.c b/src/mctp-netlink.c index 34fb668..8a7f878 100644 --- a/src/mctp-netlink.c +++ b/src/mctp-netlink.c @@ -28,6 +28,8 @@ struct linkmap_entry { mctp_eid_t *local_eids; size_t num_local; + + void *userdata; }; struct mctp_nl { @@ -242,7 +244,7 @@ static void fill_eid_changes(const struct linkmap_entry *oe, } static void fill_link_changes(const struct linkmap_entry *old, size_t old_count, - const struct linkmap_entry *new, size_t new_count, + struct linkmap_entry *new, size_t new_count, mctp_nl_change **changes, size_t *num_changes) { size_t siz = 0; @@ -250,7 +252,7 @@ static void fill_link_changes(const struct linkmap_entry *old, size_t old_count, // iterate and match old/new interface lists for (size_t o = 0, n = 0; o < old_count || n < new_count; ) { const struct linkmap_entry *oe = &old[o]; - const struct linkmap_entry *ne = &new[n]; + struct linkmap_entry *ne = &new[n]; mctp_nl_change *ch = NULL; if (o >= old_count) @@ -261,6 +263,7 @@ static void fill_link_changes(const struct linkmap_entry *old, size_t old_count, if (oe && ne && oe->ifindex == ne->ifindex) { // Same link. + ne->userdata = oe->userdata; if (oe->net == ne->net) { // Same net. Check for eid changes. fill_eid_changes(oe, @@ -280,6 +283,7 @@ static void fill_link_changes(const struct linkmap_entry *old, size_t old_count, ch->op = MCTP_NL_CHANGE_NET; ch->ifindex = ne->ifindex; ch->old_net = oe->net; + ch->link_userdata = oe->userdata; } if (oe->up != ne->up) { @@ -287,6 +291,7 @@ static void fill_link_changes(const struct linkmap_entry *old, size_t old_count, ch->op = MCTP_NL_CHANGE_UP; ch->ifindex = ne->ifindex; ch->old_up = oe->up; + ch->link_userdata = oe->userdata; } o++; n++; @@ -309,6 +314,7 @@ static void fill_link_changes(const struct linkmap_entry *old, size_t old_count, ch->op = MCTP_NL_DEL_LINK; ch->ifindex = oe->ifindex; ch->old_net = oe->net; + ch->link_userdata = oe->userdata; o++; } } @@ -929,6 +935,23 @@ int mctp_nl_net_byindex(const mctp_nl *nl, int index) return 0; } +int mctp_nl_set_link_userdata(mctp_nl *nl, int ifindex, void *userdata) +{ + struct linkmap_entry *entry = entry_byindex(nl, ifindex); + if (!entry) + return -1; + + entry->userdata = userdata; + return 0; +} + +void *mctp_nl_get_link_userdata(const mctp_nl *nl, int ifindex) +{ + struct linkmap_entry *entry = entry_byindex(nl, ifindex); + + return entry ? entry->userdata : NULL; +} + bool mctp_nl_up_byindex(const mctp_nl *nl, int index) { struct linkmap_entry *entry = entry_byindex(nl, index); diff --git a/src/mctp-netlink.h b/src/mctp-netlink.h index 31ee6b6..e434245 100644 --- a/src/mctp-netlink.h +++ b/src/mctp-netlink.h @@ -31,6 +31,10 @@ struct mctp_nl_change { // Filled for CHANGE_UP bool old_up; + + // If userdata is present on the link, it is passed here. Populated for + // link change events (DEL_LINK, CHANGE_NET, CHANGE_UP). + void *link_userdata; }; typedef struct mctp_nl_change mctp_nl_change; @@ -69,6 +73,18 @@ int *mctp_nl_net_list(const mctp_nl *nl, size_t *ret_num_nets); /* Returns an allocated list of ifindex, caller to free */ int *mctp_nl_if_list(const mctp_nl *nl, size_t *ret_num_if); +/* Get/set userdata for a link. The userdata is attached to a link + * with index @ifindex. Userdata will also be populated into + * struct mctp_nl_change->userdata, and would typically be freed on + * MCTP_NL_DEL_LINK events + * + * Returns non-zero if the link does not exist. + */ +int mctp_nl_set_link_userdata(mctp_nl *nl, int ifindex, void *userdata); + +/* Returns NULL if the link does not exist */ +void *mctp_nl_get_link_userdata(const mctp_nl *nl, int ifindex); + /* MCTP route helper */ int mctp_nl_route_add(struct mctp_nl *nl, uint8_t eid, const char* ifname, uint32_t mtu); From 1315ccccf12184dd0904175422b40da0be37fbcd Mon Sep 17 00:00:00 2001 From: Thu Nguyen Date: Thu, 16 May 2024 01:05:39 +0000 Subject: [PATCH 10/10] mctpd: add MCTP Interface D-Bus object 1. Create the MCTP interfaces D-Bus objects for the existing MCTP links at `/xyz/openbmc_project/mctp1/interfaces/`. When the MCTP links is removed from/added to the system, the D-Bus object will also be removed/added. 2. Create the au.com.CodeConstruct.MCTP.Link1 D-Bus interface for MCTP interface D-Bus objects. The D-Bus interface includes the `Role` property which reports BMC roles in the MCTP link. The possible value of `Role` are `BusOwner`, `Endpoint` and `Unknown`. 3. Because the BMC `Role` in the MCTP interface is fixed. The `Role` property is changeable value but it can only be changed when the current configured value is `Unknown`. Ex: ``` busctl tree au.com.codeconstruct.MCTP1 `- /au `- /au/com `- /au/com/codeconstruct `- /au/com/codeconstruct/mctp1 |- /au/com/codeconstruct/mctp1/interfaces | |- /au/com/codeconstruct/mctp1/interfaces/lo | `- /au/com/codeconstruct/mctp1/interfaces/mctpi2c3 `- /au/com/codeconstruct/mctp1/networks `- /au/com/codeconstruct/mctp1/networks/1 `- /au/com/codeconstruct/mctp1/networks/1/endpoints `- /au/com/codeconstruct/mctp1/networks/1/endpoints/8 busctl introspect au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1/interfaces/mctpi2c3 NAME TYPE SIGNATURE RESULT/VALUE FLAGS au.com.CodeConstruct.MCTP.Link1 interface - - - .Role property s "Endpoint" emits-change writable org.freedesktop.DBus.Introspectable interface - - - .Introspect method - s - org.freedesktop.DBus.Peer interface - - - .GetMachineId method - s - .Ping method - - - org.freedesktop.DBus.Properties interface - - - .Get method ss v - .GetAll method s a{sv} - .Set method ssv - - .PropertiesChanged signal sa{sv}as - - busctl set-property au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1/interfaces/mctpi2c3 au.com.CodeConstruct.MCTP.Link1 Role s BusOwner busctl get-property au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1/interfaces/mctpi2c3 au.com.CodeConstruct.MCTP.Link1 Role s "BusOwner" ``` Signed-off-by: Thu Nguyen --- CHANGELOG.md | 1 + conf/mctpd.conf | 2 +- docs/mctpd.md | 14 ++ src/mctp-netlink.c | 13 ++ src/mctp-netlink.h | 2 + src/mctpd.c | 426 ++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 451 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07fdc1e..5e6f5ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 3. mctpd: Allow configuring .Connectivity as writable for development 4. mctpd: Add AssignEndpointStatic for static EID allocations 5. mctpd: Add a configuration file facility, defaulting to /etc/mctpd.conf. +6. mctpd: Add mctp/interfaces/ D-Bus object ### Changed diff --git a/conf/mctpd.conf b/conf/mctpd.conf index 82b5546..89ba406 100644 --- a/conf/mctpd.conf +++ b/conf/mctpd.conf @@ -1,4 +1,4 @@ -# Mode: either bus-owner or endpoint +# Mode: either bus-owner or endpoint or unknown mode = "bus-owner" # MCTP protocol configuration. Used for both endpoint and bus-owner modes. diff --git a/docs/mctpd.md b/docs/mctpd.md index 3cc65da..7d0078c 100644 --- a/docs/mctpd.md +++ b/docs/mctpd.md @@ -104,3 +104,17 @@ busctl call au.com.codeconstruct.MCTP1 \ Removes the MCTP endpoint from `mctpd`, and deletes routes and neighbour entries. +## D-Bus /au/com/codeconstruct/mctp1/interfaces/ + +`mctpd` provides a D-Bus path of `/au/com/codeconstruct/mctp1/interfaces`. +For each known MCTP interfaces, `mctpd` will populate an D-Bus object +`/au/com/codeconstruct/mctp1/interfaces/`. The D-Bus objects have +interface `au.com.CodeConstruct.MCTP.Link1`. +The D-Bus interface includes the `Role` property which reports BMC roles +in the link. The possible value of `Role` are `BusOwner`, `Endpoint` and +`Unknown`. The `Role` property is a changeable value but it can only be +changed when the current configured value is `Unknown` because the BMC +`Role` in the MCTP link is specific depend on the system. +The D-Bus `/au/com/codeconstruct/mctp1/interfaces/` objects also +includes an au.com.codeconstruct.MCTP.BusOwner1 which exposes bus-owner +level functions. diff --git a/src/mctp-netlink.c b/src/mctp-netlink.c index 8a7f878..dfbe042 100644 --- a/src/mctp-netlink.c +++ b/src/mctp-netlink.c @@ -952,6 +952,19 @@ void *mctp_nl_get_link_userdata(const mctp_nl *nl, int ifindex) return entry ? entry->userdata : NULL; } +void *mctp_nl_get_link_userdata_byname(const mctp_nl *nl, const char *ifname) +{ + size_t i; + + for (i = 0; i < nl->linkmap_count; i++) { + struct linkmap_entry *entry = &nl->linkmap[i]; + if (!strcmp(entry->ifname, ifname)) + return entry->userdata; + } + + return NULL; +} + bool mctp_nl_up_byindex(const mctp_nl *nl, int index) { struct linkmap_entry *entry = entry_byindex(nl, index); diff --git a/src/mctp-netlink.h b/src/mctp-netlink.h index e434245..52847c9 100644 --- a/src/mctp-netlink.h +++ b/src/mctp-netlink.h @@ -84,6 +84,8 @@ int mctp_nl_set_link_userdata(mctp_nl *nl, int ifindex, void *userdata); /* Returns NULL if the link does not exist */ void *mctp_nl_get_link_userdata(const mctp_nl *nl, int ifindex); +/* Returns NULL if the link does not exist */ +void *mctp_nl_get_link_userdata_byname(const mctp_nl *nl, const char *ifname); /* MCTP route helper */ int mctp_nl_route_add(struct mctp_nl *nl, uint8_t eid, const char* ifname, diff --git a/src/mctpd.c b/src/mctpd.c index 2be12d3..be4119c 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -42,12 +42,14 @@ #define MCTP_DBUS_PATH "/au/com/codeconstruct/mctp1" #define MCTP_DBUS_PATH_NETWORKS "/au/com/codeconstruct/mctp1/networks" +#define MCTP_DBUS_PATH_LINKS "/au/com/codeconstruct/mctp1/interfaces" #define CC_MCTP_DBUS_IFACE_BUSOWNER "au.com.codeconstruct.MCTP.BusOwner1.DRAFT" #define CC_MCTP_DBUS_IFACE_ENDPOINT "au.com.codeconstruct.MCTP.Endpoint1" #define CC_MCTP_DBUS_IFACE_TESTING "au.com.codeconstruct.MCTPTesting" #define MCTP_DBUS_NAME "au.com.codeconstruct.MCTP1" #define MCTP_DBUS_IFACE_ENDPOINT "xyz.openbmc_project.MCTP.Endpoint" #define OPENBMC_IFACE_COMMON_UUID "xyz.openbmc_project.Common.UUID" +#define CC_MCTP_DBUS_IFACE_LINK "au.com.CodeConstruct.MCTP.Link1" // an arbitrary constant for use with sd_id128_get_machine_app_specific() static const char* mctpd_appid = "67369c05-4b97-4b7e-be72-65cfd8639f10"; @@ -89,22 +91,33 @@ enum endpoint_role { ENDPOINT_ROLE_ENDPOINT, }; +struct link_userdata { + bool published; + enum endpoint_role role; +}; +typedef struct link_userdata link_userdata; + struct role { enum endpoint_role role; const char *conf_val; + const char *dbus_val; }; static const struct role roles[] = { [ENDPOINT_ROLE_UNKNOWN] = { .role = ENDPOINT_ROLE_UNKNOWN, + .conf_val = "unknown", + .dbus_val = "Unknown", }, [ENDPOINT_ROLE_BUS_OWNER] = { .role = ENDPOINT_ROLE_BUS_OWNER, .conf_val = "bus-owner", + .dbus_val = "BusOwner", }, [ENDPOINT_ROLE_ENDPOINT] = { .role = ENDPOINT_ROLE_ENDPOINT, .conf_val = "endpoint", + .dbus_val = "Endpoint", }, }; @@ -176,8 +189,8 @@ struct ctx { // Second instance for sending mctp socket requests. State is unused. mctp_nl *nl_query; - // Whether we are running as the bus owner - bool bus_owner; + // Default BMC role in All of MCTP medium interface + enum endpoint_role default_role; // An allocated array of peers, changes address (reallocated) during runtime peer *peers; @@ -199,6 +212,8 @@ typedef struct ctx ctx; static int emit_endpoint_added(const peer *peer); static int emit_endpoint_removed(const peer *peer); +static int emit_interface_added(ctx *ctx, int ifindex); +static int emit_interface_removed(ctx *ctx, int ifindex); static int emit_net_added(ctx *ctx, int net); static int emit_net_removed(ctx *ctx, int net); static int query_peer_properties(peer *peer); @@ -215,6 +230,7 @@ static int change_net_interface(ctx *ctx, int ifindex, int old_net); static int add_local_eid(ctx *ctx, int net, int eid); static int del_local_eid(ctx *ctx, int net, int eid); static int add_net(ctx *ctx, int net); +static int add_interface(ctx *ctx, int ifindex); mctp_eid_t local_addr(const ctx *ctx, int ifindex) { mctp_eid_t *eids, ret = 0; @@ -442,6 +458,19 @@ static int path_from_peer(const peer *peer, char ** ret_path) { return 0; } +static int get_role(const char *mode, struct role *role) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(roles); i++) { + if (roles[i].dbus_val && (strcmp(roles[i].dbus_val, mode) == 0)) { + memcpy(role, &roles[i], sizeof(struct role)); + return 0; + } + } + + return -1; +} /* Returns the message from a socket. ret_buf is allocated, should be freed by the caller */ @@ -625,7 +654,7 @@ static int handle_control_get_endpoint_id(ctx *ctx, resp->ctrl_hdr.rq_dgram_inst = RQDI_RESP; resp->eid = local_addr(ctx, addr->smctp_ifindex); - if (ctx->bus_owner) + if (ctx->default_role == ENDPOINT_ROLE_BUS_OWNER) SET_ENDPOINT_TYPE(resp->eid_type, MCTP_BUS_OWNER_BRIDGE); // 10b = 2 = static EID supported, matches currently assigned. SET_ENDPOINT_ID_TYPE(resp->eid_type, 2); @@ -895,6 +924,7 @@ static int cb_listen_monitor(sd_event_source *s, int sd, uint32_t revents, case MCTP_NL_DEL_LINK: { + free(c->link_userdata); // Local addresses have already been deleted with DEL_EID rc = del_interface(ctx, c->ifindex); any_error |= (rc < 0); @@ -2733,6 +2763,53 @@ static const sd_bus_vtable testing_vtable[] = { SD_BUS_VTABLE_END }; +static bool is_endpoint_path(const char *path) +{ + char *netstr = NULL, *eidstr = NULL; + uint32_t tmp, net; + int rc; + + rc = sd_bus_path_decode_many(path, + MCTP_DBUS_PATH "/networks/%/endpoints/%", + &netstr, &eidstr); + + if (rc == 0) + return false; + if (rc < 0) + return false; + + dfree(netstr); + dfree(eidstr); + + if (parse_uint32(eidstr, &tmp) < 0 || tmp > 0xff) + return false; + + if (parse_uint32(netstr, &net) < 0) + return false; + + return true; +} + +static bool is_interfaces_path(const char *path) +{ + char *intfName = NULL; + int rc; + + rc = sd_bus_path_decode_many(path, + MCTP_DBUS_PATH "/interfaces/%", + &intfName); + + if (rc == 0) + return false; + + if (rc < 0) + return false; + + dfree(intfName); + + return true; +} + static int bus_endpoint_get_prop(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *berr) @@ -2740,6 +2817,10 @@ static int bus_endpoint_get_prop(sd_bus *bus, peer *peer = userdata; int rc; + if (!is_endpoint_path(path)) { + return -ENOENT; + } + if (strcmp(property, "NetworkId") == 0) { rc = sd_bus_message_append(reply, "u", peer->net); } else if (strcmp(property, "EID") == 0) { @@ -2760,6 +2841,116 @@ static int bus_endpoint_get_prop(sd_bus *bus, return rc; } +static int bus_link_get_prop(sd_bus *bus, + const char *path, const char *interface, const char *property, + sd_bus_message *reply, void *userdata, sd_bus_error *berr) +{ + ctx *ctx = userdata; + char *tmpstr = NULL; + char *link_name = NULL; + link_userdata *lmUserData = NULL; + int rc = 0; + + if (!is_interfaces_path(path)) { + sd_bus_error_setf(berr, SD_BUS_ERROR_INVALID_ARGS, + "Invalid Object Path"); + goto out; + } + + rc = sd_bus_path_decode_many(path, MCTP_DBUS_PATH "/%/%", &tmpstr, + &link_name); + if (rc <= 0) { + sd_bus_error_setf(berr, SD_BUS_ERROR_INVALID_ARGS, + "Invalid Object Path"); + goto out; + } + + lmUserData = mctp_nl_get_link_userdata_byname(ctx->nl, link_name); + if (!lmUserData) { + rc = -ENOENT; + goto out; + } + + if (lmUserData->published && strcmp(property, "Role") == 0) { + rc = sd_bus_message_append(reply, "s", roles[lmUserData->role].dbus_val); + } else { + sd_bus_error_setf(berr, SD_BUS_ERROR_INVALID_ARGS, + "Unknown property."); + rc = -ENOENT; + } + +out: + set_berr(ctx, rc, berr); + + return rc; +} + +static int bus_link_set_prop(sd_bus *bus, + const char *path, const char *interface, const char *property, + sd_bus_message *value, void *userdata, sd_bus_error *berr) +{ + ctx *ctx = userdata; + const char *state; + char *tmpstr = NULL; + char *link_name = NULL; + link_userdata *lmUserData; + int rc; + struct role role; + + if (!is_interfaces_path(path)) { + sd_bus_error_setf(berr, SD_BUS_ERROR_INVALID_ARGS, + "Invalid Object Path"); + goto out; + } + + rc = sd_bus_path_decode_many(path, MCTP_DBUS_PATH "/%/%", &tmpstr, + &link_name); + if (rc <= 0) { + sd_bus_error_setf(berr, SD_BUS_ERROR_INVALID_ARGS, + "Invalid Object Path"); + goto out; + } + + lmUserData = mctp_nl_get_link_userdata_byname(ctx->nl, link_name); + if (!lmUserData) { + rc = -ENOENT; + goto out; + } + + if (strcmp(property, "Role") != 0) { + printf("Unknown property '%s' for %s iface %s\n", property, path, interface); + rc = -ENOENT; + goto out; + } + + if (lmUserData->role != ENDPOINT_ROLE_UNKNOWN) { + sd_bus_error_setf(berr, SD_BUS_ERROR_INVALID_ARGS, + "Role is already set."); + rc = -ENOENT; + goto out; + } + + rc = sd_bus_message_read(value, "s", &state); + if (rc < 0) { + sd_bus_error_setf(berr, SD_BUS_ERROR_INVALID_ARGS, + "Unknown Role. Only Support BusOwner/EndPoint."); + goto out; + } + + rc = get_role(state, &role); + if (rc < 0) { + printf("Invalid property value '%s' for property '%s' from interface '%s' on object '%s'\n", + state, property, interface, path); + rc = -EINVAL; + goto out; + } + lmUserData->role = role.role; + +out: + set_berr(ctx, rc, berr); + return rc; +} + __attribute__((unused)) static int bus_endpoint_set_prop(sd_bus *bus, const char *path, const char *interface, @@ -2773,6 +2964,11 @@ static int bus_endpoint_set_prop(sd_bus *bus, const char *path, ctx *ctx = peer->ctx; int rc; + if (!is_endpoint_path(path)) { + rc = -ENOENT; + goto out; + } + if (strcmp(property, "Connectivity") == 0) { bool previously = peer->degraded; rc = sd_bus_message_read(value, "s", &connectivity); @@ -2833,6 +3029,17 @@ static const sd_bus_vtable bus_endpoint_uuid_vtable[] = { SD_BUS_VTABLE_END }; +static const sd_bus_vtable bus_endpoint_link_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_WRITABLE_PROPERTY("Role", + "s", + bus_link_get_prop, + bus_link_set_prop, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_VTABLE_END +}; + static const sd_bus_vtable bus_endpoint_cc_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_METHOD_WITH_ARGS("SetMTU", @@ -2874,6 +3081,9 @@ static int bus_endpoint_find(sd_bus *bus, const char *path, ctx *ctx = userdata; peer *peer = NULL; int rc; + if (!is_endpoint_path(path)) { + return 0; + } rc = peer_from_path(ctx, path, &peer); if (rc >= 0 && peer->published) { @@ -2891,6 +3101,9 @@ static int bus_endpoint_find_uuid(sd_bus *bus, const char *path, ctx *ctx = userdata; peer *peer = NULL; int rc; + if (!is_endpoint_path(path)) { + return 0; + } rc = peer_from_path(ctx, path, &peer); if (rc >= 0 && peer->published) { @@ -2916,6 +3129,42 @@ static char* root_endpoints_path(int net) return buf; } +/* au.com.CodeConstruct.MCTP.Link1 interface */ +static int bus_mctp_link_find(sd_bus *bus, const char *path, + const char *interface, void *userdata, void **ret_found, + sd_bus_error *ret_error) +{ + ctx *ctx = userdata; + char *tmpstr = NULL; + char *link_name = NULL; + link_userdata *lmUserData = NULL; + int rc = 0; + + if (!is_interfaces_path(path)) { + return 0; + } + + rc = sd_bus_path_decode_many(path, MCTP_DBUS_PATH "/%/%", &tmpstr, + &link_name); + if (rc == 0) + return -ENOENT; + if (rc < 0) + return rc; + + lmUserData = mctp_nl_get_link_userdata_byname(ctx->nl, link_name); + if (!lmUserData) { + warnx("Linkmap entry of link %s is not existing\n", link_name); + return -ENOMEM; + } + + if (lmUserData->published) { + *ret_found = ctx; + return 1; + } + + return 0; +} + static char* net_path(int net) { size_t l; @@ -2932,6 +3181,21 @@ static char* net_path(int net) return buf; } +static char* interface_path(const char* link_name) +{ + size_t l; + char *buf = NULL; + + l = strlen(MCTP_DBUS_PATH) + 30; + buf = malloc(l); + if (!buf) { + return NULL; + } + + snprintf(buf, l, "%s/interfaces/%s", MCTP_DBUS_PATH, link_name); + return buf; +} + static int emit_endpoint_added(const peer *peer) { char *path = NULL; int rc; @@ -2977,6 +3241,29 @@ static int emit_net_added(ctx *ctx, int net) { return rc; } +static int emit_interface_added(ctx *ctx, int ifindex) { + const char* ifname = NULL; + char *path = NULL; + int rc; + + ifname = mctp_nl_if_byindex(ctx->nl, ifindex); + if (!ifname) { + warnx("BUG %s: no interface for ifindex %d", __func__, ifindex); + return -EPROTO; + } + + path = interface_path(ifname); + if (path == NULL) { + warnx("%s: out of memory", __func__); + return -ENOMEM; + } + rc = sd_bus_emit_object_added(ctx->bus, dfree(path)); + if (rc < 0) + warnx("%s: error emitting, %s", __func__, strerror(-rc)); + + return rc; +} + static int emit_net_removed(ctx *ctx, int net) { char *path = NULL; int rc; @@ -2992,6 +3279,31 @@ static int emit_net_removed(ctx *ctx, int net) { return rc; } +static int emit_interface_removed(ctx *ctx, int ifindex) { + const char* ifname = NULL; + char *path = NULL; + int rc; + + ifname = mctp_nl_if_byindex(ctx->nl_query, ifindex); + if (!ifname) { + warnx("BUG %s: no interface for ifindex %d", __func__, ifindex); + return -EPROTO; + } + + path = interface_path(ifname); + if (path == NULL) { + warnx("%s: out of memory", __func__); + return -ENOMEM; + } + rc = sd_bus_emit_object_removed(ctx->bus, dfree(path)); + if (rc < 0) { + errno = -rc; + warn("%s: error emitting", __func__); + } + + return rc; +} + static int bus_mctpd_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **ret_found, sd_bus_error *ret_error) @@ -3007,9 +3319,16 @@ static int mctpd_dbus_enumerate(sd_bus *bus, const char* path, void *data, char ***out, sd_bus_error *err) { ctx *ctx = data; + link_userdata* lmUserData = NULL; size_t num_nodes, i, j; char **nodes = NULL; + const char* ifname = NULL; int rc; + size_t num_ifs; + int *ifs; + + /* Set up local addresses */ + ifs = mctp_nl_if_list(ctx->nl, &num_ifs); // NULL terminator num_nodes = 1; @@ -3035,6 +3354,17 @@ static int mctpd_dbus_enumerate(sd_bus *bus, const char* path, if (ctx->peers[i].published) num_nodes++; + // .../mctp1/interfaces object + num_nodes++; + + // .../mctp1/interface/ + for (size_t i = 0; i < num_ifs; i++) { + lmUserData = mctp_nl_get_link_userdata(ctx->nl, ifs[i]); + if (lmUserData && lmUserData->published) { + num_nodes++; + } + } + nodes = malloc(sizeof(*nodes) * num_nodes); if (!nodes) { rc = -ENOMEM; @@ -3095,6 +3425,31 @@ static int mctpd_dbus_enumerate(sd_bus *bus, const char* path, j++; } + // .../mctp1/interfaces object + nodes[j] = strdup(MCTP_DBUS_PATH_LINKS); + if (!nodes[j]) { + rc = -ENOMEM; + goto out; + } + j++; + + for (size_t i = 0; i < num_ifs; i++) { + lmUserData = mctp_nl_get_link_userdata(ctx->nl, ifs[i]); + if (!lmUserData || !lmUserData->published) + continue; + ifname = mctp_nl_if_byindex(ctx->nl, ifs[i]); + if (!ifname) { + continue; + } + nodes[j] = interface_path(ifname); + if (nodes[j] == NULL) { + rc = -ENOMEM; + goto out; + } + j++; + } + + free(ifs); // NULL terminator nodes[j] = NULL; j++; @@ -3200,6 +3555,17 @@ static int setup_bus(ctx *ctx) goto out; } + rc = sd_bus_add_fallback_vtable(ctx->bus, NULL, + MCTP_DBUS_PATH, + CC_MCTP_DBUS_IFACE_LINK, + bus_endpoint_link_vtable, + bus_mctp_link_find, + ctx); + if (rc < 0) { + warnx("Failed adding link D-Bus interface: %s", strerror(-rc)); + goto out; + } + rc = sd_bus_add_object_manager(ctx->bus, NULL, MCTP_DBUS_PATH); if (rc < 0) { warnx("Adding object manager failed: %s", strerror(-rc)); @@ -3315,7 +3681,11 @@ static int del_interface(ctx *ctx, int old_ifindex) remove_peer(p); } } + + if (emit_interface_removed(ctx, old_ifindex) < 0) + warnx("Failed to remove D-Bus interface of ifindex %d", old_ifindex); prune_old_nets(ctx); + return 0; } @@ -3452,6 +3822,7 @@ static int add_local_eid(ctx *ctx, int net, int eid) static int add_interface_local(ctx *ctx, int ifindex) { mctp_eid_t *eids = NULL; + link_userdata *lmUserData = NULL; size_t num; int net; int rc; @@ -3477,11 +3848,19 @@ static int add_interface_local(ctx *ctx, int ifindex) if (rc < 0) return rc; } - eids = mctp_nl_addrs_byindex(ctx->nl, ifindex, &num); for (size_t j = 0; j < num; j++) { add_local_eid(ctx, net, eids[j]); } + + // Add new link if required + lmUserData = mctp_nl_get_link_userdata(ctx->nl, ifindex); + if (!lmUserData || !lmUserData->published) { + rc = add_interface(ctx, ifindex); + if (rc < 0) + return rc; + } + free(eids); return 0; } @@ -3512,6 +3891,41 @@ static int add_net(ctx *ctx, int net) return 0; } +static int add_interface(ctx *ctx, int ifindex) +{ + link_userdata *lmUserData = malloc(sizeof(link_userdata)); + int rc; + + int net = mctp_nl_net_byindex(ctx->nl, ifindex); + if (net <= 0) { + warnx("Can't find link index %d\n", ifindex); + return -ENOMEM; + } + + /* Use the `mode` setting in conf/mctp.conf */ + lmUserData->role = ctx->default_role; + lmUserData->published = true; + + rc = mctp_nl_set_link_userdata(ctx->nl, ifindex, lmUserData); + if (rc < 0) { + warnx("Failed to set UserData for link index %d", ifindex); + return -ENOMEM; + } + + rc = emit_interface_added(ctx, ifindex); + if (rc < 0) { + lmUserData->published = false; + rc = mctp_nl_set_link_userdata(ctx->nl, ifindex, lmUserData); + if (rc < 0) { + warnx("Failed to set UserData for link index %d\n", ifindex); + return -ENOMEM; + } + return -ENOMEM; + } + + return rc; +} + static int setup_nets(ctx *ctx) { size_t num_ifs; @@ -3673,7 +4087,7 @@ static int parse_config_mode(ctx *ctx, const char *mode) if (!role->conf_val || strcmp(role->conf_val, mode)) continue; - ctx->bus_owner = role->role == ENDPOINT_ROLE_BUS_OWNER; + ctx->default_role = role->role; return 0; } @@ -3796,7 +4210,7 @@ static int parse_config(ctx *ctx) static void setup_config_defaults(ctx *ctx) { ctx->mctp_timeout = 250000; // 250ms - ctx->bus_owner = true; + ctx->default_role = ENDPOINT_ROLE_BUS_OWNER; } int main(int argc, char **argv)