On Thu, Sep 05, 2024 at 15:49:37 +0200, Ján Tomko wrote:
> While the parsing is still done by 1K buffers, the results
> are no longer filtered during the parsing, but the whole JSON
> has to live in memory at once, which was also the case before
> the NSS plugin dropped its dependency on libvirt_util.
>
> Also, the new parser might be more forgiving of missing elements.
>
> Signed-off-by: Ján Tomko <[email protected]>
> ---
> tools/nss/libvirt_nss_leases.c | 370 +++++++++++----------------------
> 1 file changed, 119 insertions(+), 251 deletions(-)
[...]
> @@ -156,190 +128,120 @@ appendAddr(const char *name __attribute__((unused)),
> }
Subsequent hunks have the deletions trimmed to just show the new code.
>
>
> +/**
> + * findLeaseInJSON
> + *
> + * @jobj: the json object containing the leases
> + * @name: the requested hostname (optional if a MAC address is present)
> + * @macs: the array of MAC addresses we're matching (optional if we have a
> hostname)
> + * @nmacs: the size of the MAC array
> + * @af: the requested address family
> + * @now: current time (to eliminate expired leases)
> + * @addrs: the returned matching addresses
> + * @naddrs: size of the returned array
> + * @found: whether a match was found
> + *
> + * Returns 0 even if nothing was found
> + * -1 on error
> + */
> static int
> +findLeaseInJSON(json_object *jobj,
> + const char *name,
> + char **macs,
> + size_t nmacs,
> + int af,
> + time_t now,
> + leaseAddress **addrs,
> + size_t *naddrs,
> + bool *found)
> {
> size_t i;
> + int len;
>
> + if (!json_object_is_type(jobj, json_type_array)) {
> + ERROR("parsed JSON does not contain the leases array");
> + return -1;
> }
>
> + len = json_object_array_length(jobj);
> + for (i = 0; i < len; i++) {
> + json_object *lease = NULL;
> + json_object *expiry = NULL;
> + json_object *ipobj = NULL;
> + unsigned long long expiryTime;
> + const char *ipaddr;
> +
> + lease = json_object_array_get_idx(jobj, i);
> +
> + if (macs) {
> + const char *macAddr;
> + bool match = false;
> + json_object *val;
> + size_t j;
> +
> + val = json_object_object_get(lease, "mac-address");
> + if (!val)
> + continue;
> +
> + macAddr = json_object_get_string(val);
> + if (!macAddr)
> + continue;
> +
> + for (j = 0; j < nmacs; j++) {
> + if (!strcmp(macs[j], macAddr)) {
strcmp(..) != 0
> + match = true;
> + break;
> + }
> + }
> + if (!match)
> + continue;
> + } else {
> + const char *leaseName;
> + json_object *val;
> +
> + val = json_object_object_get(lease, "hostname");
> + if (!val)
> + continue;
> +
> + leaseName = json_object_get_string(val);
> + if (!leaseName)
> + continue;
> +
> + if (strcasecmp(leaseName, name) != 0)
> + continue;
> + }
>
> + expiry = json_object_object_get(lease, "expiry-time");
> + if (!expiry) {
> + ERROR("Missing expiry time for %s", name);
> + return -1;
> + }
>
> + expiryTime = json_object_get_uint64(expiry);
> + if (expiryTime > 0 && expiryTime < now) {
> + DEBUG("Skipping expired lease for %s", name);
> + continue;
> + }
>
> + ipobj = json_object_object_get(lease, "ip-address");
> + if (!ipobj) {
> + DEBUG("Missing IP address for %s", name);
> + continue;
> + }
> + ipaddr = json_object_get_string(ipobj);
>
> + DEBUG("Found record for %s", name);
> + *found = true;
>
> + if (appendAddr(name,
> + addrs, naddrs,
> + ipaddr,
> + expiryTime,
> + af) < 0)
> + return -1;
> }
>
> + return 0;
> }
>
>
> @@ -356,30 +258,10 @@ findLeases(const char *file,
> {
> int fd = -1;
> int ret = -1;
> + json_object *jobj = NULL;
> + json_tokener *tok = NULL;
> + enum json_tokener_error jerr;
> + int jsonflags = JSON_TOKENER_STRICT | JSON_TOKENER_VALIDATE_UTF8;
> char line[1024];
> ssize_t nreadTotal = 0;
> int rv;
> @@ -389,51 +271,37 @@ findLeases(const char *file,
> goto cleanup;
> }
>
> + tok = json_tokener_new();
> + json_tokener_set_flags(tok, jsonflags);
>
> + do {
> + rv = read(fd, line, sizeof(line) - 1);
-1 should not be needed here
> if (rv < 0)
> goto cleanup;
> if (rv == 0)
> break;
So if you get an incomplete JSON document of exactly sizeof(len) bytes,
this will go through the first loop adn thend break out ...
> nreadTotal += rv;
>
> + jobj = json_tokener_parse_ex(tok, line, rv);
> + jerr = json_tokener_get_error(tok);
> + } while (jerr == json_tokener_continue);
>
> + if (nreadTotal > 0 && jerr != json_tokener_success) {
... this condition will match as we read something and 'jerr' is still
'json_tokener_continue'
> + ERROR("Cannot parse %s: %s", file, json_tokener_error_desc(jerr));
So this error will read:
Cannot parse /path/to/file: continue
Thus you also need to specialcase also json_tokener_continue case to
state that the file is incomplete.
> goto cleanup;
> }
>
> + ret = findLeaseInJSON(jobj, name, macs, nmacs, af, now,
> + addrs, naddrs, found);
>
> cleanup:
> + json_object_put(jobj);
> + json_tokener_free(tok);
> if (ret != 0) {
> free(*addrs);
> *addrs = NULL;
> *naddrs = 0;
> }
> if (fd != -1)
> close(fd);
> return ret;
With the corner case fixed:
Reviewed-by: Peter Krempa <[email protected]>