| git.druid.rocks | index | druid520 | mp | src/ | conf.c |
src/conf.c
#define _POSIX_C_SOURCE 200809L
#define _NETBSD_SOURCE 1
#include <string.h>
#include "mpx.h"
long
parseconf(const char* buf, long n, struct kv* kv, long max)
{
long i = 0;
long nkv = 0;
long ki;
long vi;
while(i < n && nkv < max)
{
ki = 0;
while(i < n && buf[i] != '=' && buf[i] != '\n' && ki < MAXKEY - 1)
{
kv[nkv].key[ki] = buf[i];
ki = ki + 1;
i = i + 1;
}
kv[nkv].key[ki] = '\0';
while(i < n && buf[i] != '\n' && buf[i] != '"')
{
i = i + 1;
}
if(i < n && buf[i] == '"')
{
i = i + 1;
vi = 0;
while(i < n && buf[i] != '"' && buf[i] != '\n' && vi < MAXVAL - 1)
{
kv[nkv].val[vi] = buf[i];
vi = vi + 1;
i = i + 1;
}
kv[nkv].val[vi] = '\0';
if(kv[nkv].key[0] != '\0')
{
nkv = nkv + 1;
}
}
while(i < n && buf[i] != '\n')
{
i = i + 1;
}
if(i < n)
{
i = i + 1;
}
}
return nkv;
}
/* one level of "IMPORT=<path1> <path2> ..." expansion for mpx.conf (a
* global-scope, unindented directive only -- not recursive: an imported
* file's own IMPORT= line, if any, is inert). scans buf for such lines
* and, into out, places buf's own content FIRST, then each named file's
* raw content after it, later-listed files before earlier-listed ones
* -- extractsection/kvget's first-match-wins semantics mean whichever
* occurrence of a key is scanned first wins, so this ordering makes
* buf's own content always win over anything it imports, and among the
* imports themselves gives a later IMPORT= entry precedence over an
* earlier one (mirrors mp.conf's own OVERLAYS= "later wins" rule). a
* named file that can't be opened is
* silently skipped (an optional import, same tolerance mpx.conf's own
* possible absence already gets) -- but one that opens and is simply
* too big to fit is NOT silently truncated into a garbled partial read;
* it's skipped just as if it were missing, since a truncated import
* would otherwise silently drop whatever keys fall past the cut, with
* no way for the caller to tell truncation from "this file just wasn't
* imported". returns the combined length written to out, or 0 if there
* were no IMPORT= lines at all (the caller then just uses buf as-is). */
#define MAXIMPORTS 16
long
expand_imports(const char* buf, long n, char* out, long outmax)
{
char imports[MAXIMPORTS][MAXPATH];
long nimports = 0;
long i = 0;
size_t outi;
long k;
while(i < n && nimports < MAXIMPORTS)
{
long linestart = i;
long lineend = i;
while(lineend < n && buf[lineend] != '\n')
{
lineend = lineend + 1;
}
if(linestart < lineend && buf[linestart] != ' ' && buf[linestart] != '\t'
&& lineend - linestart > 7 && strncmp(buf + linestart, "IMPORT=", 7) == 0)
{
long j = linestart + 7;
while(j < lineend && nimports < MAXIMPORTS)
{
long tokstart;
long toklen;
while(j < lineend && buf[j] == ' ')
{
j = j + 1;
}
tokstart = j;
while(j < lineend && buf[j] != ' ')
{
j = j + 1;
}
toklen = j - tokstart;
if(toklen > 0 && toklen < MAXPATH)
{
memcpy(imports[nimports], buf + tokstart, (size_t)toklen);
imports[nimports][toklen] = '\0';
nimports = nimports + 1;
}
}
}
i = lineend + 1;
}
if(nimports == 0)
{
return 0;
}
/* buf's own content goes FIRST, not last: extractsection/kvget's
* first-match-wins picks whichever occurrence of a key it scans
* first, so buf must be scanned before anything it imports in order
* to actually win over it, matching this function's own documented
* "buf's own content always wins" contract. */
outi = 0;
if(n < outmax)
{
memcpy(out, buf, (size_t)n);
outi = (size_t)n;
if(outi + 1 < (size_t)outmax)
{
out[outi] = '\n';
outi = outi + 1;
}
}
for(k = 0; k < nimports; k = k + 1)
{
long idx = nimports - 1 - k;
long subn = readfile(imports[idx], out + outi, outmax - (long)outi - 1);
if(subn < 0)
{
continue;
}
outi = outi + (size_t)subn;
if(outi + 1 < (size_t)outmax)
{
out[outi] = '\n';
outi = outi + 1;
}
}
return (long)outi;
}
const char*
kvget(struct kv* kv, long nkv, const char* key)
{
long i = 0;
while(i < nkv)
{
if(strcmp(kv[i].key, key) == 0)
{
return kv[i].val;
}
i = i + 1;
}
return NULL;
}
/* filters a raw mpx.conf-format buffer down to a plain key="value" buffer
* parseconf() can consume: every unindented key="value" line (global,
* unchanged), plus -- if pkgname is non-NULL -- every indented key="value"
* line belonging to pkgname's "<name>:" section, de-indented and placed
* first so parseconf/kvget (which returns the first match) prefers a
* per-package override over the same global key. mirrors mp.conf's
* per-package sections: an unindented "<name>:" line (name may itself
* contain ':', e.g. a slotted "gcc:12:", so the section delimiter is the
* last colon on the line, not the first) opens/switches the current
* section; an unindented key="value" line closes any open section.
*
* pkgname's own section wins, but falls back to pkgname's BARE name's
* section (pkgname with everything from the last ':' on stripped) before
* falling back to the plain global lines -- mirrors
* mplib::util::pconf_lookup on the perl side: a pkg_slot_use-composed
* pkgname (e.g. "combodep:foo") doesn't exist yet at the moment a
* per-package mpx.conf section is written, so the bare name is the only
* one that could plausibly have been used. a no-op when pkgname is
* already bare (no ':' in it).
*
* returns the number of bytes written to out. */
long
extractsection(const char* buf, long n, const char* pkgname, char* out, long outmax)
{
long i;
size_t outi;
long pass;
char cursec[MAXKEY];
char barename[MAXKEY];
const char* basename;
long lastcolon;
long k;
basename = NULL;
if(pkgname != NULL)
{
lastcolon = -1;
for(k = 0; pkgname[k] != '\0'; k = k + 1)
{
if(pkgname[k] == ':')
{
lastcolon = k;
}
}
if(lastcolon > 0 && lastcolon < MAXKEY)
{
memcpy(barename, pkgname, (size_t)lastcolon);
barename[lastcolon] = '\0';
basename = barename;
}
}
outi = 0;
for(pass = 0; pass < 3; pass = pass + 1)
{
/* pass 0: pkgname's own indented section (skipped if pkgname is
* NULL). pass 1: pkgname's bare name's section (skipped if
* pkgname is already bare). pass 2: unindented global lines,
* appended after both. */
const char* matchname = (pass == 0) ? pkgname : (pass == 1) ? basename : NULL;
if(pass != 2 && matchname == NULL)
{
continue;
}
cursec[0] = '\0';
i = 0;
while(i < n)
{
long linestart = i;
long lineend = i;
int indented;
while(lineend < n && buf[lineend] != '\n')
{
lineend = lineend + 1;
}
indented = (linestart < lineend && (buf[linestart] == ' ' || buf[linestart] == '\t')) ? 1 : 0;
if(!indented)
{
/* look for a "<name>:" header: last non-whitespace char
* on the line is ':', and there's at least one char
* before it once trailing whitespace is trimmed. */
long lasttext = -1;
{
long j;
for(j = linestart; j < lineend; j = j + 1)
{
if(buf[j] != ' ' && buf[j] != '\t' && buf[j] != '\r')
{
lasttext = j;
}
}
}
if(lasttext >= linestart && buf[lasttext] == ':')
{
long namelen = lasttext - linestart;
if(namelen > 0 && namelen < MAXKEY)
{
memcpy(cursec, buf + linestart, (size_t)namelen);
cursec[namelen] = '\0';
}
else
{
cursec[0] = '\0';
}
i = lineend + 1;
continue;
}
/* not a header: unindented key=value line closes any
* section, and (pass 2 only) gets copied to out. */
cursec[0] = '\0';
if(pass == 2 && lineend > linestart)
{
long cpy = lineend - linestart;
if(outi + (size_t)cpy + 1 < (size_t)outmax)
{
memcpy(out + outi, buf + linestart, (size_t)cpy);
outi = outi + (size_t)cpy;
out[outi] = '\n';
outi = outi + 1;
}
}
}
else if(pass != 2 && cursec[0] != '\0' && strcmp(cursec, matchname) == 0)
{
long j = linestart;
while(j < lineend && (buf[j] == ' ' || buf[j] == '\t'))
{
j = j + 1;
}
if(j < lineend)
{
long cpy = lineend - j;
if(outi + (size_t)cpy + 1 < (size_t)outmax)
{
memcpy(out + outi, buf + j, (size_t)cpy);
outi = outi + (size_t)cpy;
out[outi] = '\n';
outi = outi + 1;
}
}
}
i = lineend + 1;
}
}
return (long)outi;
}