/** * PROJECT: XTchain * LICENSE: See COPYING.md in the top level directory * FILE: tools/xtchain.h * DESCRIPTION: Common header for XTchain tools * DEVELOPERS: Martin Storsjo * Rafal Kupiec * Aiken Harris */ #ifndef _WIN32 #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #include #define XTCHAIN_PROCESS_ID _getpid #define PATH_SEP '\\' #else #define XTCHAIN_PROCESS_ID getpid #define PATH_SEP '/' #endif #define SECTOR_SIZE 512 #define _T(x) x #define YAML_MAX_BINARY (64u * 1024u * 1024u) #define YAML_MAX_TEXT (512u * 1024u * 1024u) typedef struct MBR_PARTITION { uint8_t BootFlag; // 0x80 = bootable, 0x00 = non-boot uint8_t StartCHS [3]; // CHS address uint8_t Type; // Partition type uint8_t EndCHS[3]; // CHS address uint32_t StartLBA; // Start sector uint32_t Size; // Sectors count } MBR_PARTITION, *PMBR_PARTITION; typedef struct _RESERVED_SECTOR_INFO { int SectorNumber; const char* Description; } RESERVED_SECTOR_INFO, *PRESERVED_SECTOR_INFO; typedef struct _YAML_BUFFER { unsigned char *data; size_t len, cap; } YAML_BUFFER; typedef enum _YAML_NODE_TYPE { YAML_SCALAR, YAML_MAP, YAML_SEQUENCE, YAML_BINARY } YAML_NODE_TYPE; typedef struct _YAML_NODE YAML_NODE; struct _YAML_NODE { YAML_NODE_TYPE type; char *text; int IsInteger; unsigned char *bytes; size_t length; char **keys; YAML_NODE **values; size_t count, cap, *slots, slots_cap; }; typedef struct YAML_ALLOCATION { void *p; struct YAML_ALLOCATION *next; } YAML_ALLOCATION; typedef struct YAML_LINE { char *s; size_t n, indent, number; } YAML_LINE; typedef struct YAML_PARSER { YAML_LINE *lines; size_t count, at, col; } YAML_PARSER; typedef struct YAML_INLINE { const char *s; size_t n, at, line; } YAML_INLINE; static const char *YamlProgramName = "xtchain"; static const char *(*YamlCharacterName)(uint32_t Codepoint) = NULL; static YAML_ALLOCATION *YamlAllocations; static void (*YamlValidationError)(void); static inline void YamlWriterEmit(FILE *f, YAML_NODE *n, unsigned depth); static inline YAML_NODE * YamlParserBlock(YAML_PARSER *p, size_t indent, unsigned depth); static inline char * _tcsrchrs(const char *str, char char1, char char2) { char *ptr1 = strrchr(str, char1); char *ptr2 = strrchr(str, char2); if(!ptr1) { return ptr2; } if(!ptr2) { return ptr1; } if(ptr1 < ptr2) { return ptr2; } return ptr1; } static inline void split_argv(const char *argv0, char **dir_ptr, char **basename_ptr, char **target_ptr, char **exe_ptr) { const char *sep = _tcsrchrs(argv0, '/', '\\'); const char *basename_ptr_const = argv0; char *dir = strdup(_T("")); if(sep) { dir = strdup(argv0); dir[sep + 1 - argv0] = '\0'; basename_ptr_const = sep + 1; } char *basename = strdup(basename_ptr_const); char *period = strchr(basename, '.'); if(period) { *period = '\0'; } char *target = strdup(basename); char *dash = strrchr(target, '-'); char *exe = basename; if(dash) { *dash = '\0'; exe = dash + 1; } else { target = NULL; } if(dir_ptr) { *dir_ptr = dir; } if(basename_ptr) { *basename_ptr = basename; } if(target_ptr) { *target_ptr = target; } if(exe_ptr) { *exe_ptr = exe; } } /* Release only the allocations made after a validation checkpoint. */ static inline void YamlReleaseAllocations(YAML_ALLOCATION *checkpoint) { while(YamlAllocations != checkpoint) { YAML_ALLOCATION *a = YamlAllocations; YamlAllocations = a->next; free(a->p); free(a); } } /* A per-command arena ensures cleanup on every validation failure as well. */ static inline void YamlCleanup(void) { YamlReleaseAllocations(NULL); } static inline void YamlError(const char *format, ...) { if(YamlValidationError) { YamlValidationError(); } va_list ap; va_start(ap, format); fprintf(stderr, "%s: error: ", YamlProgramName); vfprintf(stderr, format, ap); fputc('\n', stderr); va_end(ap); exit(1); } static inline void * YamlAllocateMemory(size_t n) { YAML_ALLOCATION *a = malloc(sizeof(*a)); void *p = calloc(n ? n : 1, 1); if(!a || !p) { free(a); free(p); /* Resource exhaustion is fatal, even during format detection. */ YamlValidationError = NULL; YamlError("out of memory"); } a->p = p; a->next = YamlAllocations; YamlAllocations = a; return p; } static inline char * YamlCopyString(const char *s) { size_t n = strlen(s) + 1; char *p = YamlAllocateMemory(n); memcpy(p, s, n); return p; } static inline char * YamlFormatString(const char *format, ...) { if(!format) { YamlError("null format string"); } va_list ap, aq; va_start(ap, format); va_copy(aq, ap); int n = vsnprintf(NULL, 0, format, aq); va_end(aq); if(n < 0) { YamlError("formatting failed"); } char *p = YamlAllocateMemory((size_t)n + 1); vsnprintf(p, (size_t)n + 1, format, ap); va_end(ap); return p; } static inline void YamlAppendBuffer(YAML_BUFFER *b, const void *data, size_t n) { if(n > YAML_MAX_BINARY || b->len > YAML_MAX_BINARY - n) { YamlError("binary exceeds 64 MiB"); } if(b->len + n > b->cap) { size_t cap = b->cap ? b->cap : 1024; while(cap < b->len + n) { cap *= 2; } unsigned char *p = YamlAllocateMemory(cap); if(b->len) { memcpy(p, b->data, b->len); } b->data = p; b->cap = cap; } if(n) { memcpy(b->data + b->len, data, n); } b->len += n; } static inline void WriteUInt16(YAML_BUFFER *b, uint32_t v) { if(v > 65535) { YamlError("value does not fit 16 bits"); } unsigned char p[2] = {(unsigned char)v, (unsigned char)(v >> 8)}; YamlAppendBuffer(b, p, 2); } static inline void WriteUInt32(YAML_BUFFER *b, uint32_t v) { unsigned char p[4] = { (unsigned char)v, (unsigned char)(v >> 8), (unsigned char)(v >> 16), (unsigned char)(v >> 24)}; YamlAppendBuffer(b, p, 4); } static inline uint32_t ReadUInt16(const unsigned char *p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8); } static inline uint32_t ReadUInt32(const unsigned char *p) { return ReadUInt16(p) | (ReadUInt16(p + 2) << 16); } static inline YAML_NODE * YamlCreateNode(YAML_NODE_TYPE type) { YAML_NODE *n = YamlAllocateMemory(sizeof(*n)); n->type = type; return n; } static inline YAML_NODE * YamlCreateString(const char *s) { YAML_NODE *n = YamlCreateNode(YAML_SCALAR); n->text = YamlCopyString(s); return n; } static inline YAML_NODE * YamlCreateInteger(uint32_t v) { YAML_NODE *n = YamlCreateString(YamlFormatString("%u", v)); n->IsInteger = 1; return n; } static inline YAML_NODE * YamlCreateHexadecimal(uint32_t v, unsigned width) { return YamlCreateString(YamlFormatString("0x%0*X", (int)width, v)); } static inline YAML_NODE * YamlCreateCodepoint(uint32_t v) { return YamlCreateString(YamlFormatString("U+%04X", v)); } static inline YAML_NODE * YamlCreateBinary(const void *data, size_t n) { YAML_NODE *v = YamlCreateNode(YAML_BINARY); v->bytes = YamlAllocateMemory(n); if(n) { memcpy(v->bytes, data, n); } v->length = n; return v; } static inline size_t YamlWriterHash(const char *s) { size_t h = 2166136261u; for(; *s; s++) { h = (h ^ (unsigned char)*s) * 16777619u; } return h; } static inline void YamlWriterReserve(YAML_NODE *n) { if(n->count == n->cap) { size_t cap = n->cap ? n->cap * 2 : 8; if(cap > 2097152) { YamlError("too many YAML entries"); } YAML_NODE **v = YamlAllocateMemory(cap * sizeof(*v)); char **k = YamlAllocateMemory(cap * sizeof(*k)); if(n->count) { memcpy(v, n->values, n->count * sizeof(*v)); memcpy(k, n->keys, n->count * sizeof(*k)); } n->values = v; n->keys = k; n->cap = cap; } } static inline void YamlAddField(YAML_NODE *n, const char *key, YAML_NODE *v) { if(!n || n->type != YAML_MAP) { YamlError("expected mapping"); } if(!n->slots_cap || (n->count + 1) * 2 >= n->slots_cap) { size_t cap = n->slots_cap ? n->slots_cap * 2 : 16; size_t *slots = YamlAllocateMemory(cap * sizeof(*slots)); for(size_t i = 0; i < n->count; i++) { size_t s = YamlWriterHash(n->keys[i]) & (cap - 1); while(slots[s]) { s = (s + 1) & (cap - 1); } slots[s] = i + 1; } n->slots = slots; n->slots_cap = cap; } size_t s = YamlWriterHash(key) & (n->slots_cap - 1); while(n->slots[s]) { if(!strcmp(n->keys[n->slots[s] - 1], key)) { YamlError("duplicate YAML key: %s", key); } s = (s + 1) & (n->slots_cap - 1); } YamlWriterReserve(n); n->keys[n->count] = YamlCopyString(key); n->values[n->count] = v; n->slots[s] = ++n->count; } static inline void YamlAppendItem(YAML_NODE *n, YAML_NODE *v) { if(!n || n->type != YAML_SEQUENCE) { YamlError("expected sequence"); } YamlWriterReserve(n); n->values[n->count++] = v; } static inline YAML_NODE * YamlFindField(YAML_NODE *n, const char *key) { if(!n || n->type != YAML_MAP) { YamlError("expected mapping for %s", key); } if(!n->slots_cap) { return NULL; } size_t s = YamlWriterHash(key) & (n->slots_cap - 1); while(n->slots[s]) { if(!strcmp(n->keys[n->slots[s] - 1], key)) { return n->values[n->slots[s] - 1]; } s = (s + 1) & (n->slots_cap - 1); } return NULL; } static inline YAML_NODE * YamlGetField(YAML_NODE *n, const char *key) { YAML_NODE *v = YamlFindField(n, key); if(!v) { YamlError("missing field: %s", key); } return v; } static inline int YamlWriterInList(const char *list, const char *key) { size_t n = strlen(key); while(*list) { size_t len = strcspn(list, " "); if(n == len && !memcmp(list, key, n)) { return 1; } list += len; while(*list == ' ') { list++; } } return 0; } static inline void YamlValidateFields(YAML_NODE *n, const char *required, const char *allowed) { if(!n || n->type != YAML_MAP) { YamlError("expected mapping"); } for(size_t i = 0; i < n->count; i++) { if(!YamlWriterInList(required, n->keys[i]) && !YamlWriterInList(allowed, n->keys[i])) { YamlError("unexpected field: %s", n->keys[i]); } } char *r = YamlCopyString(required), *p = r; while(*p) { char *end = strchr(p, ' '); if(end) { *end = 0; } (void)YamlGetField(n, p); if(!end) { break; } p = end + 1; } } const char * YamlGetScalar(YAML_NODE *n) { if(!n || n->type != YAML_SCALAR) { YamlError("expected scalar"); } return n->text; } static inline uint32_t YamlParseNumber(const char *s, unsigned base, uint32_t max) { uint64_t YamlWriterValue = 0; if(!*s) { YamlError("empty number"); } for(const unsigned char *p = (const unsigned char *)s; *p; p++) { unsigned digit = *p >= '0' && *p <= '9' ? *p - '0' : *p >= 'A' && *p <= 'F' ? *p - 'A' + 10 : *p >= 'a' && *p <= 'f' ? *p - 'a' + 10 : 99; if(digit >= base || YamlWriterValue > (UINT32_MAX - digit) / base) { YamlError("invalid number: %s", s); } YamlWriterValue = YamlWriterValue * base + digit; } if(YamlWriterValue > max) { YamlError("number out of range: %s", s); } return (uint32_t)YamlWriterValue; } static inline uint32_t YamlGetUnsigned(YAML_NODE *n, uint32_t max) { return YamlParseNumber(YamlGetScalar(n), 10, max); } static inline uint32_t YamlGetHexadecimal(YAML_NODE *n, uint32_t max) { const char *s = YamlGetScalar(n); if(strncmp(s, "0x", 2) || strlen(s) > 10) { YamlError("expected 0x... encoded value"); } return YamlParseNumber(s + 2, 16, max); } static inline uint32_t YamlGetCodepoint(YAML_NODE *n) { const char *s = YamlGetScalar(n); if(strlen(s) != 6 || strncmp(s, "U+", 2)) { YamlError("expected U+XXXX"); } return YamlParseNumber(s + 2, 16, 65535); } static inline int YamlWriterBase64Digit(unsigned char c) { if(c >= 'A' && c <= 'Z') { return c - 'A'; } if(c >= 'a' && c <= 'z') { return c - 'a' + 26; } if(c >= '0' && c <= '9') { return c - '0' + 52; } return c == '+' ? 62 : c == '/' ? 63 : c == '=' ? 64 : -1; } static inline YAML_NODE * YamlReadBinary(const unsigned char *s, size_t n) { YAML_BUFFER b = {0}; unsigned q[4], used = 0; int ended = 0; for(size_t i = 0; i < n; i++) { if(isspace(s[i])) { continue; } int d = YamlWriterBase64Digit(s[i]); if(d < 0 || ended) { YamlError("invalid Base64"); } q[used++] = (unsigned)d; if(used == 4) { if(q[0] > 63 || q[1] > 63 || (q[2] == 64 && q[3] != 64)) { YamlError("invalid Base64 padding"); } unsigned char bytes[3] = {(unsigned char)((q[0] << 2) | (q[1] >> 4)), (unsigned char)((q[1] << 4) | (q[2] >> 2)), (unsigned char)((q[2] << 6) | q[3])}; size_t count = q[2] == 64 ? 1 : q[3] == 64 ? 2 : 3; if((count == 1 && (q[1] & 15)) || (count == 2 && (q[2] & 3))) { YamlError("noncanonical Base64 padding bits"); } YamlAppendBuffer(&b, bytes, count); ended = count != 3; used = 0; } } if(used) { YamlError("incomplete Base64 group"); } return YamlCreateBinary(b.data, b.len); } static inline void YamlWriterIndent(FILE *f, unsigned n) { while(n--) { fputc(' ', f); } } static inline void YamlWriterQuoted(FILE *f, const char *s) { int escaped = 0; for(const unsigned char *p = (const unsigned char *)s; *p; p++) { if(*p < 0x20 || *p == 0x7f || *p >= 0x80) { escaped = 1; } } if(escaped) { fputc('"', f); for(const unsigned char *p = (const unsigned char *)s; *p; p++) { if(*p == '"' || *p == '\\') { fputc('\\', f); fputc(*p, f); } else if(*p < 0x20 || *p == 0x7f) { fprintf(f, "\\u%04X", *p); } else if(*p >= 0x80) { uint32_t c = *p; unsigned extra; if(c < 0xe0) { c &= 31; extra = 1; } else if(c < 0xf0) { c &= 15; extra = 2; } else { c &= 7; extra = 3; } while(extra--) { p++; c = (c << 6) | (*p & 63); } fprintf(f, c <= 65535 ? "\\u%04X" : "\\U%08X", c); } else { fputc(*p, f); } } fputc('"', f); } else { fputc('\'', f); for(; *s; s++) { fputc(*s, f); if(*s == '\'') { fputc('\'', f); } } fputc('\'', f); } } static inline int YamlWriterPlain(const char *s) { if(!*s) { return 0; } const char *reserved[] = {"null", "true", "false", "yes", "no", "on", "off", "y", "n"}; for(size_t i = 0; i < sizeof(reserved) / sizeof(reserved[0]); i++) { size_t j = 0; while(s[j] && reserved[i][j] && tolower((unsigned char)s[j]) == reserved[i][j]) { j++; } if(!s[j] && !reserved[i][j]) { return 0; } } for(const unsigned char *p = (const unsigned char *)s; *p; p++) { if(!isalnum(*p) && *p != '_' && *p != '-' && *p != '+') { return 0; } } return strncmp(s, "0x", 2) != 0; } static inline void YamlWriterOutputScalar(FILE *f, YAML_NODE *n) { if(n->IsInteger || (YamlWriterPlain(n->text) && !isdigit((unsigned char)n->text[0]) && n->text[0] != '-' && n->text[0] != '+')) { fputs(n->text, f); } else { YamlWriterQuoted(f, n->text); } } static inline int YamlWriterCodepoint(const char *s) { if(!s || strlen(s) != 6 || strncmp(s, "U+", 2)) { return -1; } for(unsigned i = 2; i < 6; i++) { if(!isxdigit((unsigned char)s[i])) { return -1; } } return (int)strtoul(s + 2, NULL, 16); } static inline void YamlWriterComment(FILE *f, const char *key, YAML_NODE *v) { int c = v->type == YAML_SCALAR ? YamlWriterCodepoint(v->text) : -1; if(c < 0) { c = YamlWriterCodepoint(key); } if(c >= 0 && YamlCharacterName) { fprintf(f, " # %s", YamlCharacterName((uint32_t)c)); } } static inline void YamlWriterValue(FILE *f, YAML_NODE *v, unsigned depth, const char *key) { if(v->type == YAML_SCALAR) { fputc(' ', f); YamlWriterOutputScalar(f, v); YamlWriterComment(f, key, v); fputc('\n', f); } else if(v->type == YAML_BINARY) { static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; if(!v->length) { fputs(" !!binary ''\n", f); return; } fputs(" !!binary |\n", f); unsigned column = 0; for(size_t i = 0; i < v->length; i += 3) { uint32_t bits = (uint32_t)v->bytes[i] << 16; if(i + 1 < v->length) { bits |= (uint32_t)v->bytes[i + 1] << 8; } if(i + 2 < v->length) { bits |= v->bytes[i + 2]; } char chars[4] = {alphabet[bits >> 18], alphabet[(bits >> 12) & 63], i + 1 < v->length ? alphabet[(bits >> 6) & 63] : '=', i + 2 < v->length ? alphabet[bits & 63] : '='}; if(!column) { YamlWriterIndent(f, depth); } fwrite(chars, 1, 4, f); column += 4; if(column == 76) { fputc('\n', f); column = 0; } } if(column) { fputc('\n', f); } } else if(!v->count) { fputs(v->type == YAML_MAP ? " {}\n" : " []\n", f); } else { fputc('\n', f); YamlWriterEmit(f, v, depth); } } static inline void YamlWriterEmit(FILE *f, YAML_NODE *n, unsigned depth) { for(size_t i = 0; i < n->count; i++) { YamlWriterIndent(f, depth); if(n->type == YAML_MAP) { if(YamlWriterPlain(n->keys[i])) { fputs(n->keys[i], f); } else { YamlWriterQuoted(f, n->keys[i]); } fputc(':', f); } else { fputc('-', f); } YamlWriterValue(f, n->values[i], depth + 2, n->type == YAML_MAP ? n->keys[i] : NULL); } } static inline void YamlWriteDocument(FILE *f, YAML_NODE *root, const char *Generator) { if(Generator) { fprintf(f, "# Generated by %s. Do not edit manually.\n", Generator); } YamlWriterEmit(f, root, 0); if(ferror(f)) { YamlError("cannot write YAML output"); } } /* Bounded YAML parser: block and flow collections, quoted scalars and binary values */ /* Bounded YAML subset with explicit scalar handling and no implicit type resolver */ static inline void YamlParserError(size_t line, const char *why) { YamlError("YAML line %zu: %s", line, why); } static inline int YamlParserSpace(char c) { return c == ' ' || c == '\t'; } static inline void YamlParserWs(YAML_INLINE *p) { while(p->at < p->n && YamlParserSpace(p->s[p->at])) { p->at++; } } static inline void YamlParserDepthCheck(unsigned depth) { if(depth > 32) { YamlError("YAML nesting exceeds 32 levels"); } } static inline char * YamlParserSlice(const char *s, size_t n) { char *v = YamlAllocateMemory(n + 1); if(n) { memcpy(v, s, n); } return v; } static inline void YamlParserUtf8(YAML_BUFFER *b, uint32_t c, size_t line) { unsigned char v[4]; size_t n; if(!c || c > 0x10ffff || (c >= 0xd800 && c <= 0xdfff)) { YamlParserError(line, "invalid Unicode escape"); } if(c < 0x80) { v[0] = (unsigned char)c; n = 1; } else if(c < 0x800) { v[0] = 0xc0 | (c >> 6); v[1] = 0x80 | (c & 63); n = 2; } else if(c < 0x10000) { v[0] = 0xe0 | (c >> 12); v[1] = 0x80 | ((c >> 6) & 63); v[2] = 0x80 | (c & 63); n = 3; } else { v[0] = 0xf0 | (c >> 18); v[1] = 0x80 | ((c >> 12) & 63); v[2] = 0x80 | ((c >> 6) & 63); v[3] = 0x80 | (c & 63); n = 4; } YamlAppendBuffer(b, v, n); } static inline char * YamlParserQuoted(YAML_INLINE *p) { char quote = p->s[p->at++]; YAML_BUFFER b = {0}; while(p->at < p->n) { unsigned char c = (unsigned char)p->s[p->at++]; if(c == (unsigned char)quote) { if(quote == '\'' && p->at < p->n && p->s[p->at] == '\'') { p->at++; YamlAppendBuffer(&b, &c, 1); continue; } return YamlParserSlice((const char *)b.data, b.len); } if(quote == '"' && c == '\\') { if(p->at == p->n) { YamlParserError(p->line, "unfinished quoted escape"); } c = (unsigned char)p->s[p->at++]; uint32_t cp = c; switch(c) { case 'a': cp = 7; break; case 'b': cp = 8; break; case 't': cp = 9; break; case 'n': cp = 10; break; case 'v': cp = 11; break; case 'f': cp = 12; break; case 'r': cp = 13; break; case 'e': cp = 27; break; case 'N': cp = 0x85; break; case '_': cp = 0xa0; break; case 'L': cp = 0x2028; break; case 'P': cp = 0x2029; break; case ' ': case '"': case '/': case '\\': break; case 'x': case 'u': case 'U': { unsigned digits = c == 'x' ? 2 : c == 'u' ? 4 : 8; cp = 0; if(p->n - p->at < digits) { YamlParserError(p->line, "short Unicode escape"); } while(digits--) { unsigned char h = (unsigned char)p->s[p->at++]; unsigned d = h >= '0' && h <= '9' ? h - '0' : h >= 'a' && h <= 'f' ? h - 'a' + 10 : h >= 'A' && h <= 'F' ? h - 'A' + 10 : 99; if(d > 15) { YamlParserError(p->line, "invalid Unicode escape"); } cp = cp * 16 + d; } break; } default: YamlParserError(p->line, "unsupported quoted escape"); } YamlParserUtf8(&b, cp, p->line); } else { YamlAppendBuffer(&b, &c, 1); } } YamlParserError(p->line, "unterminated quote; multiline strings are not supported"); return NULL; } static inline char * YamlParserTextValue(YAML_INLINE *p, int key, int flow) { YamlParserWs(p); if(p->at == p->n) { YamlParserError(p->line, "missing scalar"); } char c = p->s[p->at]; if(c == '\'' || c == '"') { return YamlParserQuoted(p); } if(strchr("!&*|>%@`{}[],?#", c) || ((c == '-' || c == ':') && (p->at + 1 == p->n || YamlParserSpace(p->s[p->at + 1])))) { YamlParserError(p->line, "unsupported scalar, tag, anchor or alias"); } size_t start = p->at; while(p->at < p->n) { c = p->s[p->at]; if(c == '#' && (p->at == start || YamlParserSpace(p->s[p->at - 1]))) { break; } if(flow && strchr(",[]{}", c)) { break; } if(c == ':' && (key || p->at + 1 == p->n || YamlParserSpace(p->s[p->at + 1]) || (flow && strchr(",[]{}", p->s[p->at + 1])))) { break; } p->at++; } size_t end = p->at; while(end > start && YamlParserSpace(p->s[end - 1])) { end--; } if(end == start) { YamlParserError(p->line, "missing scalar"); } return YamlParserSlice(p->s + start, end - start); } static inline YAML_NODE * YamlParserInlineValue(YAML_INLINE *p, unsigned depth, int flow) { YamlParserDepthCheck(depth); YamlParserWs(p); if(p->at == p->n) { YamlParserError(p->line, "missing value"); } char open = p->s[p->at]; if(open == '[' || open == '{') { YAML_NODE *n = YamlCreateNode(open == '[' ? YAML_SEQUENCE : YAML_MAP); char close = open == '[' ? ']' : '}'; p->at++; YamlParserWs(p); if(p->at < p->n && p->s[p->at] == close) { p->at++; return n; } for(;;) { if(n->type == YAML_MAP) { YamlParserWs(p); int quoted_key = p->at < p->n && (p->s[p->at] == '\'' || p->s[p->at] == '"'); char *key = YamlParserTextValue(p, 1, 1); YamlParserWs(p); if(p->at == p->n || p->s[p->at++] != ':') { YamlParserError(p->line, "expected ':' in flow mapping"); } if(!quoted_key && p->at < p->n && !YamlParserSpace(p->s[p->at]) && !strchr("[{", p->s[p->at])) { YamlParserError(p->line, "plain flow mapping keys require separation after ':'"); } if(!strcmp(key, "<<")) { YamlParserError(p->line, "YAML merge keys are not supported"); } YamlAddField(n, key, YamlParserInlineValue(p, depth + 1, 1)); } else { YamlAppendItem(n, YamlParserInlineValue(p, depth + 1, 1)); } YamlParserWs(p); if(p->at == p->n) { YamlParserError(p->line, "unterminated flow collection; multiline flow is not supported"); } char c = p->s[p->at++]; if(c == close) { return n; } if(c != ',') { YamlParserError(p->line, "expected comma or closing delimiter"); } YamlParserWs(p); if(p->at < p->n && p->s[p->at] == close) { p->at++; return n; } } } if(p->n - p->at >= 8 && !memcmp(p->s + p->at, "!!binary", 8) && (p->at + 8 == p->n || YamlParserSpace(p->s[p->at + 8]))) { p->at += 8; char *s = YamlParserTextValue(p, 0, flow); return YamlReadBinary((const unsigned char *)s, strlen(s)); } return YamlCreateString(YamlParserTextValue(p, 0, flow)); } static inline void YamlParserFinish(YAML_INLINE *p) { size_t before = p->at; YamlParserWs(p); if(p->at == p->n) { return; } if(p->s[p->at] == '#' && p->at > before) { return; } /* Plain scalars consume whitespace before stopping at a comment. */ if(p->s[p->at] == '#' && p->at && YamlParserSpace(p->s[p->at - 1])) { return; } YamlParserError(p->line, "unexpected trailing content"); } static inline void YamlParserSkip(YAML_PARSER *p) { while(p->at < p->count) { YAML_LINE *l = &p->lines[p->at]; if(l->indent != l->n && l->s[l->indent] != '#') { break; } p->at++; } if(p->at < p->count) { p->col = p->lines[p->at].indent; } } static inline void YamlParserNext(YAML_PARSER *p) { p->at++; YamlParserSkip(p); } static inline int YamlParserDash(const YAML_LINE *l, size_t col) { return col < l->n && l->s[col] == '-' && (col + 1 == l->n || YamlParserSpace(l->s[col + 1])); } static inline int YamlParserEmpty(const YAML_INLINE *p) { return p->at == p->n || p->s[p->at] == '#'; } static inline YAML_NODE * YamlParserBlockValue(YAML_PARSER *p, YAML_INLINE *v, size_t parent, unsigned depth) { YamlParserDepthCheck(depth); YamlParserWs(v); if(YamlParserEmpty(v)) { YamlParserNext(p); if(p->at == p->count || p->col < parent || (p->col == parent && !YamlParserDash(&p->lines[p->at], p->col))) { YamlParserError(v->line, "missing value; implicit null is not supported"); } return YamlParserBlock(p, p->col, depth); } if(v->n - v->at >= 8 && !memcmp(v->s + v->at, "!!binary", 8) && (v->at + 8 == v->n || YamlParserSpace(v->s[v->at + 8]))) { YAML_INLINE tail = *v; tail.at += 8; YamlParserWs(&tail); if(tail.at < tail.n && tail.s[tail.at] == '|') { tail.at++; if(tail.at < tail.n && (tail.s[tail.at] == '-' || tail.s[tail.at] == '+')) { tail.at++; } YamlParserFinish(&tail); YAML_BUFFER b = {0}; size_t content_indent = 0; p->at++; while(p->at < p->count) { YAML_LINE *l = &p->lines[p->at]; if(l->indent == l->n) { p->at++; continue; } if(l->indent <= parent) { break; } if(!content_indent) { content_indent = l->indent; } if(l->indent != content_indent) { YamlParserError(l->number, "inconsistent binary block indentation"); } YamlAppendBuffer(&b, l->s + l->indent, l->n - l->indent); p->at++; } YamlParserSkip(p); return YamlReadBinary(b.data, b.len); } } YAML_NODE *n = YamlParserInlineValue(v, depth, 0); YamlParserFinish(v); YamlParserNext(p); return n; } static inline YAML_NODE * YamlParserBlock(YAML_PARSER *p, size_t indent, unsigned depth) { YamlParserDepthCheck(depth); YAML_LINE *l = &p->lines[p->at]; int seq = YamlParserDash(l, p->col); YAML_NODE *n = YamlCreateNode(seq ? YAML_SEQUENCE : YAML_MAP); while(p->at < p->count && p->col == indent) { l = &p->lines[p->at]; if(!indent && ((!strcmp(l->s, "---")) || (!strcmp(l->s, "...")))) { break; } if(YamlParserDash(l, p->col) != seq) { break; } YAML_INLINE v = {l->s, l->n, p->col, l->number}; if(seq) { v.at++; YamlParserWs(&v); /* Compact sequence/map entries, as emitted by PyYAML. */ int compact = YamlParserDash(l, v.at); if(!compact && !YamlParserEmpty(&v) && !strchr("[{!&*", v.s[v.at])) { YAML_INLINE probe = v; (void)YamlParserTextValue(&probe, 1, 0); YamlParserWs(&probe); if(probe.at < probe.n && probe.s[probe.at] == ':' && (probe.at + 1 == probe.n || YamlParserSpace(probe.s[probe.at + 1]))) { compact = 1; } } if(compact) { p->col = v.at; YamlAppendItem(n, YamlParserBlock(p, v.at, depth + 1)); } else { if(YamlParserEmpty(&v)) { YamlParserNext(p); if(p->at == p->count || p->col <= indent) { YamlParserError(v.line, "missing sequence item"); } YamlAppendItem(n, YamlParserBlock(p, p->col, depth + 1)); } else { YamlAppendItem(n, YamlParserBlockValue(p, &v, indent, depth + 1)); } } } else { char *key = YamlParserTextValue(&v, 1, 0); YamlParserWs(&v); if(v.at == v.n || v.s[v.at++] != ':' || (v.at < v.n && !YamlParserSpace(v.s[v.at]))) { YamlParserError(v.line, "expected block mapping 'key: value'"); } if(!strcmp(key, "<<")) { YamlParserError(v.line, "YAML merge keys are not supported"); } YamlAddField(n, key, YamlParserBlockValue(p, &v, indent, depth + 1)); } } if(p->at < p->count && p->col > indent) { YamlParserError(p->lines[p->at].number, "unexpected indentation or multiline scalar"); } return n; } static inline void YamlParserValidateUtf8(const unsigned char *s, size_t len) { for(size_t i = 0; i < len;) { uint32_t c = s[i++], minimum = 0; unsigned extra = 0; if(c >= 0xc2 && c <= 0xdf) { c &= 31; extra = 1; minimum = 0x80; } else if(c >= 0xe0 && c <= 0xef) { c &= 15; extra = 2; minimum = 0x800; } else if(c >= 0xf0 && c <= 0xf4) { c &= 7; extra = 3; minimum = 0x10000; } else if(c >= 0x80) { YamlError("invalid UTF-8 in YAML input"); } if(extra > len - i) { YamlError("truncated UTF-8 in YAML input"); } while(extra--) { unsigned b = s[i++]; if((b & 0xc0) != 0x80) { YamlError("invalid UTF-8 in YAML input"); } c = (c << 6) | (b & 63); } if(c < minimum || c > 0x10ffff || (c >= 0xd800 && c <= 0xdfff)) { YamlError("invalid UTF-8 codepoint"); } if((c < 32 && c != 9 && c != 10 && c != 13) || (c >= 0x7f && c <= 0x9f && c != 0x85) || c == 0xfffe || c == 0xffff) { YamlError("non-printable character in YAML input"); } if(c == 0x85 || c == 0x2028 || c == 0x2029 || c == 0xfeff) { YamlError("unsupported YAML line break or embedded BOM"); } } } static inline YAML_NODE * YamlReadDocument(const unsigned char *data, size_t len) { if(len > YAML_MAX_TEXT) { YamlError("YAML input exceeds 512 MiB"); } YamlParserValidateUtf8(data, len); size_t count = 1; for(size_t i = 0; i < len; i++) { if(data[i] == '\n') { count++; } } if(count > 2097152) { YamlError("too many YAML lines"); } YAML_PARSER p = {YamlAllocateMemory(count * sizeof(YAML_LINE)), 0, 0, 0}; char *storage = YamlParserSlice((const char *)data, len); size_t start = 0; for(size_t i = 0; i <= len; i++) { if(i < len && storage[i] != '\n') { continue; } size_t end = i; if(end > start && storage[end - 1] == '\r') { end--; } while(end > start && YamlParserSpace(storage[end - 1])) { end--; } storage[end] = 0; YAML_LINE *l = &p.lines[p.count++]; l->s = storage + start; l->n = end - start; l->number = p.count; while(l->indent < l->n && l->s[l->indent] == ' ') { l->indent++; } if(l->indent < l->n && l->s[l->indent] == '\t') { YamlParserError(l->number, "tabs in indentation are not supported"); } if(memchr(l->s, '\r', l->n)) { YamlParserError(l->number, "use LF or CRLF line endings"); } start = i + 1; } YamlParserSkip(&p); if(p.at < p.count && !p.col && !strcmp(p.lines[p.at].s, "---")) { YamlParserNext(&p); } if(p.at == p.count) { YamlError("empty YAML document"); } if(p.col) { YamlParserError(p.lines[p.at].number, "root must start at column one"); } YAML_NODE *root; if(p.lines[p.at].s[0] == '{' || p.lines[p.at].s[0] == '[') { YAML_INLINE v = {p.lines[p.at].s, p.lines[p.at].n, 0, p.lines[p.at].number}; root = YamlParserInlineValue(&v, 0, 0); YamlParserFinish(&v); YamlParserNext(&p); } else { root = YamlParserBlock(&p, 0, 0); } if(p.at < p.count && !p.col && !strcmp(p.lines[p.at].s, "...")) { YamlParserNext(&p); } if(p.at != p.count) { YamlParserError(p.lines[p.at].number, "unexpected content or multiple YAML documents"); } return root; }