/*-
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright (c) 2026
 *  Project Tick. All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Keith Muller of the University of California, San Diego.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/xattr.h>

#include <ctype.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "pax.h"
#include "extern.h"

struct pax_xattr {
	char *name;
	unsigned char *value;
	size_t value_len;
	struct pax_xattr *next;
};

static int pax_xattr_add(ARCHD *, const char *, const void *, size_t);
static void pax_xattr_clear_list(PAX_XATTR *);
static char *url_encode_component(const char *);
static char *url_decode_component(const char *);
static char *base64_encode(const unsigned char *, size_t);
static unsigned char *base64_decode(const char *, size_t *);
static int append_pax_record(char **, size_t *, size_t *, const char *,
    const char *);
static int pax_xattr_is_selinux_label(const PAX_XATTR *);

void
pax_xattr_free(ARCHD *arcn)
{
	if (arcn == NULL)
		return;
	pax_xattr_clear_list(arcn->xattr_head);
	arcn->xattr_head = NULL;
}

void
pax_xattr_preserve_and_reset(ARCHD *arcn)
{
	PAX_XATTR *xattrs;

	if (arcn == NULL)
		return;

	xattrs = arcn->xattr_head;
	memset(arcn, 0, sizeof(*arcn));
	arcn->xattr_head = xattrs;
}

int
pax_xattr_has_any(const ARCHD *arcn)
{
	return (arcn != NULL) && (arcn->xattr_head != NULL);
}

int
pax_xattr_format_supported(const FSUB *frmt)
{
	return (frmt != NULL) && (strcmp(frmt->name, "ustar") == 0);
}

int
pax_xattr_has_non_selinux(const ARCHD *arcn)
{
	PAX_XATTR *entry;

	if (arcn == NULL)
		return(0);
	for (entry = arcn->xattr_head; entry != NULL; entry = entry->next) {
		if (!pax_xattr_is_selinux_label(entry))
			return(1);
	}
	return(0);
}

int
pax_xattr_drop_selinux(ARCHD *arcn)
{
	PAX_XATTR *entry;
	PAX_XATTR *next;
	PAX_XATTR *prev;
	int dropped;

	if (arcn == NULL)
		return(0);

	dropped = 0;
	prev = NULL;
	entry = arcn->xattr_head;
	while (entry != NULL) {
		next = entry->next;
		if (pax_xattr_is_selinux_label(entry)) {
			if (prev == NULL)
				arcn->xattr_head = next;
			else
				prev->next = next;
			free(entry->name);
			free(entry->value);
			free(entry);
			dropped = 1;
		} else
			prev = entry;
		entry = next;
	}
	return(dropped);
}

int
pax_xattr_read_path(ARCHD *arcn, const char *path)
{
	PAX_XATTR *entry;
	char *namebuf;
	char *name;
	char *end;
	ssize_t namesz;
	ssize_t valsz;
	unsigned char *value;

	pax_xattr_free(arcn);

	errno = 0;
	namesz = llistxattr(path, NULL, 0);
	if (namesz == 0)
		return(0);
	if (namesz < 0) {
		if ((errno == ENOTSUP) || (errno == EOPNOTSUPP) ||
		    (errno == ENODATA))
			return(0);
		syswarn(1, errno,
		    "Unable to inspect extended attributes on %s", path);
		return(-1);
	}

	if ((namebuf = malloc((size_t)namesz)) == NULL) {
		paxwarn(1,
		    "Unable to allocate memory to inspect extended attributes on %s",
		    path);
		return(-1);
	}

	errno = 0;
	namesz = llistxattr(path, namebuf, (size_t)namesz);
	if (namesz < 0) {
		syswarn(1, errno,
		    "Unable to inspect extended attributes on %s", path);
		free(namebuf);
		return(-1);
	}

	end = namebuf + namesz;
	for (name = namebuf; name < end; name += strlen(name) + 1) {
		errno = 0;
		valsz = lgetxattr(path, name, NULL, 0);
		if (valsz < 0) {
			syswarn(1, errno, "Unable to read extended attribute %s on %s",
			    name, path);
			free(namebuf);
			pax_xattr_free(arcn);
			return(-1);
		}
		value = NULL;
		if (valsz > 0) {
			if ((value = malloc((size_t)valsz)) == NULL) {
				paxwarn(1,
				    "Unable to allocate memory for extended attribute %s on %s",
				    name, path);
				free(namebuf);
				pax_xattr_free(arcn);
				return(-1);
			}
			errno = 0;
			if (lgetxattr(path, name, value, (size_t)valsz) != valsz) {
				syswarn(1, errno,
				    "Unable to read extended attribute %s on %s", name,
				    path);
				free(value);
				free(namebuf);
				pax_xattr_free(arcn);
				return(-1);
			}
		}
		if (pax_xattr_add(arcn, name, value, (size_t)valsz) < 0) {
			free(value);
			free(namebuf);
			pax_xattr_free(arcn);
			return(-1);
		}
		free(value);
	}

	free(namebuf);

	/*
	 * Reverse the push-front insertion order so pax records stay stable.
	 */
	entry = arcn->xattr_head;
	arcn->xattr_head = NULL;
	while (entry != NULL) {
		PAX_XATTR *next = entry->next;
		entry->next = arcn->xattr_head;
		arcn->xattr_head = entry;
		entry = next;
	}
	return(0);
}

int
pax_xattr_apply_path(ARCHD *arcn, const char *path)
{
	PAX_XATTR *entry;
	unsigned char *current;
	ssize_t current_len;
	int rv = 0;

	if ((arcn == NULL) || (arcn->xattr_head == NULL))
		return(0);

	for (entry = arcn->xattr_head; entry != NULL; entry = entry->next) {
		current = NULL;
		errno = 0;
		current_len = lgetxattr(path, entry->name, NULL, 0);
		if (current_len >= 0) {
			if ((size_t)current_len == entry->value_len) {
				if (current_len > 0) {
					if ((current = malloc((size_t)current_len)) == NULL) {
						paxwarn(1,
						    "Unable to allocate memory while restoring extended attribute %s on %s",
						    entry->name, path);
						return(-1);
					}
					if (lgetxattr(path, entry->name, current,
					    (size_t)current_len) == current_len &&
					    memcmp(current, entry->value,
					    entry->value_len) == 0) {
						free(current);
						continue;
					}
					free(current);
				} else {
					continue;
				}
			}
		} else if ((errno == ENOTSUP) || (errno == EOPNOTSUPP) ||
		    (errno == EPERM) || (errno == EACCES)) {
			continue;
		}

		if (lsetxattr(path, entry->name, entry->value, entry->value_len, 0) < 0) {
			if ((errno == ENOTSUP) || (errno == EOPNOTSUPP) ||
			    (errno == EPERM) || (errno == EACCES))
				continue;
			syswarn(1, errno,
			    "Unable to restore extended attribute %s on %s",
			    entry->name, path);
			rv = -1;
		}
	}
	return(rv);
}

char *
pax_xattr_to_pax_records(const ARCHD *arcn, size_t *outlen)
{
	PAX_XATTR *entry;
	char *records;
	char *encoded_key;
	char *encoded_value;
	char *fullkey;
	const char *dot;
	const char *namespace;
	const char *key;
	size_t records_len;
	size_t records_cap;
	size_t nslen;
	size_t fullkey_len;
	size_t i;

	records = NULL;
	records_len = 0;
	records_cap = 0;

	for (entry = arcn->xattr_head; entry != NULL; entry = entry->next) {
		encoded_key = NULL;
		encoded_value = NULL;
		fullkey = NULL;

		dot = strchr(entry->name, '.');
		if (dot == NULL) {
			namespace = "user";
			nslen = 4;
			key = entry->name;
		} else {
			namespace = entry->name;
			nslen = (size_t)(dot - entry->name);
			key = dot + 1;
		}

		if (nslen == 0 || key[0] == '\0') {
			free(records);
			return(NULL);
		}

		if (dot != NULL) {
			if (strchr(dot + 1, '.') != NULL) {
				free(records);
				return(NULL);
			}
		}

		for (i = 0; i < nslen; i++) {
			if (!isalnum((unsigned char)namespace[i]) && namespace[i] != '_') {
				free(records);
				return(NULL);
			}
		}

		if ((encoded_key = url_encode_component(key)) == NULL ||
		    (encoded_value = base64_encode(entry->value,
		    entry->value_len)) == NULL) {
			free(encoded_key);
			free(encoded_value);
			free(records);
			return(NULL);
		}

		fullkey_len = sizeof("LIBARCHIVE.xattr.") - 1 + nslen + 1 +
		    strlen(encoded_key);
		if ((fullkey = malloc(fullkey_len + 1)) == NULL) {
			free(encoded_key);
			free(encoded_value);
			free(records);
			return(NULL);
		}
		(void)snprintf(fullkey, fullkey_len + 1,
		    "LIBARCHIVE.xattr.%.*s.%s", (int)nslen, namespace, encoded_key);

		free(encoded_key);
		if (append_pax_record(&records, &records_len, &records_cap, fullkey,
		    encoded_value) < 0) {
			free(fullkey);
			free(encoded_value);
			free(records);
			return(NULL);
		}
		free(fullkey);
		free(encoded_value);
	}

	if (outlen != NULL)
		*outlen = records_len;
	return(records);
}

int
pax_xattr_from_pax_records(ARCHD *arcn, const char *records, size_t records_len)
{
	const char *line;
	const char *line_end;
	const char *spc;
	const char *eq;
	const char *payload;
	char *key;
	char *value;
	char *full_name;
	char *decoded_key;
	unsigned char *decoded_value;
	size_t line_len;
	size_t value_len;
	size_t key_len;
	unsigned long long declen;
	char *endp;
	size_t full_name_len;
	size_t count = 0;
	size_t total_decoded_size = 0;

	pax_xattr_free(arcn);

	if (records_len > PAX_XHDR_MAX_SIZE || records_len == 0)
		return(-1);

	line = records;
	while ((size_t)(line - records) < records_len) {
		spc = memchr(line, ' ', records_len - (size_t)(line - records));
		if (spc == NULL)
			goto fail;
		
		errno = 0;
		declen = strtoull(line, &endp, 10);
		if ((errno != 0) || (endp != spc) || (declen < 5) ||
		    (declen > (unsigned long long)PAX_XHDR_MAX_SIZE))
			goto fail;
		
		line_len = (size_t)declen;
		if (line_len > records_len - (size_t)(line - records))
			goto fail;
		
		line_end = line + line_len;
		if ((line_end[-1] != '\n'))
			goto fail;

		if (memchr(line, '\0', line_len) != NULL)
			goto fail;

		payload = spc + 1;
		eq = memchr(payload, '=', (size_t)(line_end - payload));
		if ((eq == NULL))
			goto fail;
		
		key_len = (size_t)(eq - payload);
		if (key_len == 0 || key_len > PAX_XHDR_MAX_SIZE)
			goto fail;
		
		key = malloc(key_len + 1);
		if (key == NULL)
			goto fail;
		memcpy(key, payload, key_len);
		key[key_len] = '\0';
		
		value_len = (size_t)((line_end - 1) - (eq + 1));
		if (value_len > PAX_XHDR_MAX_SIZE) {
			free(key);
			goto fail;
		}

		value = malloc(value_len + 1);
		if (value == NULL) {
			free(key);
			goto fail;
		}
		memcpy(value, eq + 1, value_len);
		value[value_len] = '\0';

		if (strncmp(key, "LIBARCHIVE.xattr.", 17) == 0) {
			char *ns = key + 17;
			char *nsdot = strchr(ns, '.');
			
			if (nsdot == NULL || nsdot == ns || nsdot[1] == '\0') {
				free(key);
				free(value);
				goto fail;
			}

			if (strchr(nsdot + 1, '.') != NULL) {
				free(key);
				free(value);
				goto fail;
			}

			int valid_ns = 1;
			for (char *c = ns; c < nsdot; c++) {
				if (!isalnum((unsigned char)*c) && *c != '_') {
					valid_ns = 0;
					break;
				}
			}

			if (!valid_ns) {
				free(key);
				free(value);
				goto fail;
			}

			*nsdot = '\0';
			decoded_key = url_decode_component(nsdot + 1);
			decoded_value = base64_decode(value, &value_len);
			
			if ((decoded_key == NULL) || (decoded_value == NULL)) {
				free(decoded_key);
				free(decoded_value);
				free(key);
				free(value);
				goto fail;
			}

			if (value_len > PAX_XATTR_MAX_VALUE) {
				free(decoded_key);
				free(decoded_value);
				free(key);
				free(value);
				goto fail;
			}

			if (total_decoded_size > PAX_XATTR_MAX_TOTAL_RECORDS - value_len) {
				free(decoded_key);
				free(decoded_value);
				free(key);
				free(value);
				goto fail;
			}
			total_decoded_size += value_len;

			if (++count > PAX_XATTR_MAX_COUNT) {
				free(decoded_key);
				free(decoded_value);
				free(key);
				free(value);
				goto fail;
			}

			full_name_len = strlen(ns) + 1 + strlen(decoded_key);
			if (full_name_len > PAX_XHDR_MAX_SIZE ||
			    (full_name = malloc(full_name_len + 1)) == NULL) {
				free(decoded_key);
				free(decoded_value);
				free(key);
				free(value);
				goto fail;
			}
			(void)snprintf(full_name, full_name_len + 1, "%s.%s",
			    ns, decoded_key);
			free(decoded_key);
			
			if (pax_xattr_add(arcn, full_name, decoded_value,
			    value_len) < 0) {
				free(full_name);
				free(decoded_value);
				free(key);
				free(value);
				goto fail;
			}
			free(full_name);
			free(decoded_value);
		}

		free(key);
		free(value);
		line = line_end;
	}

	/*
	 * Reverse the push-front insertion order to match archive order.
	 */
	{
		PAX_XATTR *entry = arcn->xattr_head;
		arcn->xattr_head = NULL;
		while (entry != NULL) {
			PAX_XATTR *next = entry->next;
			entry->next = arcn->xattr_head;
			arcn->xattr_head = entry;
			entry = next;
		}
	}
	return(0);

fail:
	pax_xattr_free(arcn);
	return(-1);
}

static int
pax_xattr_add(ARCHD *arcn, const char *name, const void *value, size_t value_len)
{
	PAX_XATTR *entry;

	if ((entry = calloc(1, sizeof(*entry))) == NULL)
		return(-1);
	if ((entry->name = strdup(name)) == NULL) {
		free(entry);
		return(-1);
	}
	if (value_len > 0) {
		if ((entry->value = malloc(value_len)) == NULL) {
			free(entry->name);
			free(entry);
			return(-1);
		}
		memcpy(entry->value, value, value_len);
	}
	entry->value_len = value_len;
	entry->next = arcn->xattr_head;
	arcn->xattr_head = entry;
	return(0);
}

static int
pax_xattr_is_selinux_label(const PAX_XATTR *entry)
{
	return (entry != NULL) && (strcmp(entry->name, "security.selinux") == 0);
}

static void
pax_xattr_clear_list(PAX_XATTR *head)
{
	PAX_XATTR *next;

	while (head != NULL) {
		next = head->next;
		free(head->name);
		free(head->value);
		free(head);
		head = next;
	}
}

static char *
url_encode_component(const char *src)
{
	char *dst;
	char *out;
	size_t len;
	size_t i;

	len = 0;
	for (i = 0; src[i] != '\0'; ++i) {
		if (isalnum((unsigned char)src[i]) || (src[i] == '.') ||
		    (src[i] == '_') || (src[i] == '-') || (src[i] == '~'))
			++len;
		else
			len += 3;
	}
	if ((dst = malloc(len + 1)) == NULL)
		return(NULL);
	out = dst;
	for (i = 0; src[i] != '\0'; ++i) {
		if (isalnum((unsigned char)src[i]) || (src[i] == '.') ||
		    (src[i] == '_') || (src[i] == '-') || (src[i] == '~'))
			*out++ = src[i];
		else {
			(void)sprintf(out, "%%%02X", (unsigned char)src[i]);
			out += 3;
		}
	}
	*out = '\0';
	return(dst);
}

static char *
url_decode_component(const char *src)
{
	char *dst;
	char *out;
	int hi;
	int lo;

	if ((dst = malloc(strlen(src) + 1)) == NULL)
		return(NULL);
	out = dst;
	while (*src != '\0') {
		if (*src == '%') {
			if (isxdigit((unsigned char)src[1]) &&
			    isxdigit((unsigned char)src[2])) {
				hi = isdigit((unsigned char)src[1]) ? src[1] - '0' :
				    (tolower((unsigned char)src[1]) - 'a' + 10);
				lo = isdigit((unsigned char)src[2]) ? src[2] - '0' :
				    (tolower((unsigned char)src[2]) - 'a' + 10);
				*out++ = (char)((hi << 4) | lo);
				src += 3;
			} else {
				free(dst);
				return(NULL);
			}
			continue;
		}
		*out++ = *src++;
	}
	*out = '\0';
	return(dst);
}

static char *
base64_encode(const unsigned char *src, size_t srclen)
{
	static const char table[] =
	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	char *dst;
	size_t i;
	size_t j;
	size_t dstlen;
	uint32_t chunk;

	dstlen = ((srclen + 2) / 3) * 4;
	if (srclen > SIZE_MAX - 2 || (dst = malloc(dstlen + 1)) == NULL)
		return(NULL);

	for (i = 0, j = 0; i < srclen; i += 3) {
		chunk = (uint32_t)src[i] << 16;
		if ((i + 1) < srclen)
			chunk |= (uint32_t)src[i + 1] << 8;
		if ((i + 2) < srclen)
			chunk |= src[i + 2];
		dst[j++] = table[(chunk >> 18) & 0x3f];
		dst[j++] = table[(chunk >> 12) & 0x3f];
		dst[j++] = ((i + 1) < srclen) ? table[(chunk >> 6) & 0x3f] : '=';
		dst[j++] = ((i + 2) < srclen) ? table[chunk & 0x3f] : '=';
	}
	dst[j] = '\0';
	return(dst);
}

static unsigned char *
base64_decode(const char *src, size_t *outlen)
{
	static const signed char lookup[256] = {
		-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
		-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,
		-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
		-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
		-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
		-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
		-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
		-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
	};
	unsigned char *dst;
	uint32_t chunk;
	size_t len, i, j;
	int b64[4], pad;

	len = strlen(src);
	if (len == 0) {
		if ((dst = malloc(1)) == NULL)
			return(NULL);
		dst[0] = '\0';
		*outlen = 0;
		return(dst);
	}

	if ((len % 4) != 0)
		return(NULL);
	if ((dst = malloc((len / 4) * 3 + 1)) == NULL)
		return(NULL);

	for (i = 0, j = 0; i < len; i += 4) {
		pad = 0;
		for (int k = 0; k < 4; k++) {
			int v = lookup[(unsigned char)src[i+k]];
			if (v == -2) { /* '=' */
				if (k < 2 || (k == 2 && src[i+3] != '=') || i + 4 < len) {
					free(dst);
					return(NULL);
				}
				pad++;
				b64[k] = 0;
			} else if (v == -1) {
				free(dst);
				return(NULL);
			} else {
				if (pad > 0) {
					free(dst);
					return(NULL);
				}
				b64[k] = v;
			}
		}
		chunk = (uint32_t)b64[0] << 18 | (uint32_t)b64[1] << 12 |
		        (uint32_t)b64[2] << 6  | (uint32_t)b64[3];
		dst[j++] = (chunk >> 16) & 0xff;
		if (pad < 2) dst[j++] = (chunk >> 8) & 0xff;
		if (pad < 1) dst[j++] = chunk & 0xff;
	}
	*outlen = j;
	return(dst);
}

static int
append_pax_record(char **buf, size_t *buflen, size_t *bufcap, const char *key,
    const char *value)
{
	char *out;
	char *record;
	size_t payload_len;
	size_t len;
	size_t digits;
	size_t need;
	size_t newcap;
	int written;
	size_t key_len;
	size_t val_len;

	key_len = strlen(key);
	val_len = strlen(value);

	if (key_len > PAX_XATTR_MAX_VALUE || val_len > PAX_XATTR_MAX_VALUE)
		return(-1);

	if (key_len > SIZE_MAX - val_len - 2)
		return(-1);

	payload_len = key_len + 1 + val_len + 1;

	if (payload_len > PAX_XATTR_MAX_TOTAL_RECORDS)
		return(-1);

	digits = 1;
	for (;;) {
		if (payload_len > SIZE_MAX - digits - 1)
			return(-1);
		len = digits + 1 + payload_len;
		if (len < 10)
			need = 1;
		else
			need = 0;
		for (size_t n = len; n > 0; n /= 10)
			++need;
		if (need == digits)
			break;
		digits = need;
	}

	if (len > PAX_XATTR_MAX_TOTAL_RECORDS)
		return(-1);

	if ((record = malloc(len + 1)) == NULL)
		return(-1);
	written = snprintf(record, len + 1, "%zu %s=%s\n", len, key, value);
	if ((written < 0) || ((size_t)written != len)) {
		free(record);
		return(-1);
	}

	if (*buflen + len > *bufcap) {
		if (len > SIZE_MAX - *buflen) {
			free(record);
			return(-1);
		}
		newcap = (*bufcap == 0) ? 128 : *bufcap;
		while (newcap < (*buflen + len)) {
			if (newcap > SIZE_MAX / 2) {
				newcap = *buflen + len;
				break;
			}
			newcap *= 2;
		}
		if (newcap > PAX_XATTR_MAX_TOTAL_RECORDS)
			newcap = PAX_XATTR_MAX_TOTAL_RECORDS;
		if (newcap < (*buflen + len)) {
			free(record);
			return(-1);
		}
		out = realloc(*buf, newcap);
		if (out == NULL) {
			free(record);
			return(-1);
		}
		*buf = out;
		*bufcap = newcap;
	}

	memcpy(*buf + *buflen, record, len);
	*buflen += len;
	free(record);
	return(0);
}
