From 962546f30ca035f20a56439e85692ef3a7f983e0 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 21 Feb 2023 15:53:56 +0100 Subject: [PATCH 01/21] lib/compat/humanize_number.c: switch to freebsd version --- include/compat.h | 20 +++-- lib/compat/humanize_number.c | 138 ++++++++++++++++++++++------------- 2 files changed, 101 insertions(+), 57 deletions(-) diff --git a/include/compat.h b/include/compat.h index cedd1be29..f33731ab0 100644 --- a/include/compat.h +++ b/include/compat.h @@ -28,13 +28,19 @@ int HIDDEN vasprintf(char **, const char *, va_list); #endif #ifndef HAVE_HUMANIZE_NUMBER -#define HN_DECIMAL 0x01 -#define HN_NOSPACE 0x02 -#define HN_B 0x04 -#define HN_DIVISOR_1000 0x08 -#define HN_GETSCALE 0x10 -#define HN_AUTOSCALE 0x20 -int HIDDEN humanize_number(char *, size_t, int64_t, const char *, int, int); +/* Values for humanize_number(3)'s flags parameter. */ +#define HN_DECIMAL 0x01 +#define HN_NOSPACE 0x02 +#define HN_B 0x04 +#define HN_DIVISOR_1000 0x08 +#define HN_IEC_PREFIXES 0x10 + +/* Values for humanize_number(3)'s scale parameter. */ +#define HN_GETSCALE 0x10 +#define HN_AUTOSCALE 0x20 + +int HIDDEN humanize_number(char *_buf, size_t _len, int64_t _number, + const char *_suffix, int _scale, int _flags); #endif #endif /* COMPAT_H */ diff --git a/lib/compat/humanize_number.c b/lib/compat/humanize_number.c index 446c94b83..c7c4bb336 100644 --- a/lib/compat/humanize_number.c +++ b/lib/compat/humanize_number.c @@ -1,7 +1,10 @@ /* $NetBSD: humanize_number.c,v 1.14 2008/04/28 20:22:59 martin Exp $ */ -/* +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 1997, 1998, 1999, 2002 The NetBSD Foundation, Inc. + * Copyright 2013 John-Mark Gurney * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -30,6 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include #include @@ -37,61 +41,85 @@ #include #include -#include "xbps_api_impl.h" #include "compat.h" + +static const int maxscale = 6; + + int HIDDEN -humanize_number(char *buf, size_t len, int64_t bytes, - const char *suffix, int scale, int flags) +humanize_number(char *buf, size_t len, int64_t quotient, + const char *suffix, int scale, int flags) { const char *prefixes, *sep; - int b, i, r, maxscale, s1, s2, sign; + int i, r, remainder, s1, s2, sign; + int divisordeccut; int64_t divisor, max; size_t baselen; - assert(buf != NULL); - assert(suffix != NULL); - assert(scale >= 0); + /* Since so many callers don't check -1, NUL terminate the buffer */ + if (len > 0) + buf[0] = '\0'; - if (flags & HN_DIVISOR_1000) { - /* SI for decimal multiplies */ - divisor = 1000; - if (flags & HN_B) - prefixes = "B\0k\0M\0G\0T\0P\0E"; - else - prefixes = "\0\0k\0M\0G\0T\0P\0E"; - } else { + /* validate args */ + if (buf == NULL || suffix == NULL) + return (-1); + if (scale < 0) + return (-1); + else if (scale > maxscale && + ((scale & ~(HN_AUTOSCALE|HN_GETSCALE)) != 0)) + return (-1); + if ((flags & HN_DIVISOR_1000) && (flags & HN_IEC_PREFIXES)) + return (-1); + + /* setup parameters */ + remainder = 0; + + if (flags & HN_IEC_PREFIXES) { + baselen = 2; /* - * binary multiplies - * XXX IEC 60027-2 recommends Ki, Mi, Gi... + * Use the prefixes for power of two recommended by + * the International Electrotechnical Commission + * (IEC) in IEC 80000-3 (i.e. Ki, Mi, Gi...). + * + * HN_IEC_PREFIXES implies a divisor of 1024 here + * (use of HN_DIVISOR_1000 would have triggered + * an assertion earlier). */ divisor = 1024; + divisordeccut = 973; /* ceil(.95 * 1024) */ if (flags & HN_B) - prefixes = "B\0K\0M\0G\0T\0P\0E"; + prefixes = "B\0\0Ki\0Mi\0Gi\0Ti\0Pi\0Ei"; else - prefixes = "\0\0K\0M\0G\0T\0P\0E"; + prefixes = "\0\0\0Ki\0Mi\0Gi\0Ti\0Pi\0Ei"; + } else { + baselen = 1; + if (flags & HN_DIVISOR_1000) { + divisor = 1000; + divisordeccut = 950; + if (flags & HN_B) + prefixes = "B\0\0k\0\0M\0\0G\0\0T\0\0P\0\0E"; + else + prefixes = "\0\0\0k\0\0M\0\0G\0\0T\0\0P\0\0E"; + } else { + divisor = 1024; + divisordeccut = 973; /* ceil(.95 * 1024) */ + if (flags & HN_B) + prefixes = "B\0\0K\0\0M\0\0G\0\0T\0\0P\0\0E"; + else + prefixes = "\0\0\0K\0\0M\0\0G\0\0T\0\0P\0\0E"; + } } -#define SCALE2PREFIX(scale) (&prefixes[(scale) << 1]) - maxscale = 7; +#define SCALE2PREFIX(scale) (&prefixes[(scale) * 3]) - if (scale >= maxscale && - (scale & (HN_AUTOSCALE | HN_GETSCALE)) == 0) - return (-1); - - if (buf == NULL || suffix == NULL) - return (-1); - - if (len > 0) - buf[0] = '\0'; - if (bytes < 0) { + if (quotient < 0) { sign = -1; - bytes *= -100; - baselen = 3; /* sign, digit, prefix */ + quotient = -quotient; + baselen += 2; /* sign, digit */ } else { sign = 1; - bytes *= 100; - baselen = 2; /* digit, prefix */ + baselen += 1; /* digit */ } if (flags & HN_NOSPACE) sep = ""; @@ -107,7 +135,7 @@ humanize_number(char *buf, size_t len, int64_t bytes, if (scale & (HN_AUTOSCALE | HN_GETSCALE)) { /* See if there is additional columns can be used. */ - for (max = 100, i = (int)(len - baselen); i-- > 0;) + for (max = 1, i = len - baselen; i-- > 0;) max *= 10; /* @@ -115,29 +143,39 @@ humanize_number(char *buf, size_t len, int64_t bytes, * If there will be an overflow by the rounding below, * divide once more. */ - for (i = 0; bytes >= max - 50 && i < maxscale; i++) - bytes /= divisor; + for (i = 0; + (quotient >= max || (quotient == max - 1 && + (remainder >= divisordeccut || remainder >= + divisor / 2))) && i < maxscale; i++) { + remainder = quotient % divisor; + quotient /= divisor; + } if (scale & HN_GETSCALE) return (i); - } else - for (i = 0; i < scale && i < maxscale; i++) - bytes /= divisor; + } else { + for (i = 0; i < scale && i < maxscale; i++) { + remainder = quotient % divisor; + quotient /= divisor; + } + } /* If a value <= 9.9 after rounding and ... */ - if (bytes < 995 && i > 0 && flags & HN_DECIMAL) { - /* baselen + \0 + .N */ - if (len < baselen + 1 + 2) - return (-1); - b = ((int)bytes + 5) / 10; - s1 = b / 10; - s2 = b % 10; + /* + * XXX - should we make sure there is enough space for the decimal + * place and if not, don't do HN_DECIMAL? + */ + if (((quotient == 9 && remainder < divisordeccut) || quotient < 9) && + i > 0 && flags & HN_DECIMAL) { + s1 = (int)quotient + ((remainder * 10 + divisor / 2) / + divisor / 10); + s2 = ((remainder * 10 + divisor / 2) / divisor) % 10; r = snprintf(buf, len, "%d%s%d%s%s%s", sign * s1, localeconv()->decimal_point, s2, sep, SCALE2PREFIX(i), suffix); } else r = snprintf(buf, len, "%" PRId64 "%s%s%s", - sign * ((bytes + 50) / 100), + sign * (quotient + (remainder + divisor / 2) / divisor), sep, SCALE2PREFIX(i), suffix); return (r); From 64234853f2b057befa15f3849f3242fb403a9f37 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Mon, 20 Feb 2023 23:53:18 +0100 Subject: [PATCH 02/21] lib: add xbps_fmt* functions for string formatting --- include/xbps.h.in | 192 ++++++++++++++++++ lib/Makefile | 2 +- lib/format.c | 504 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 697 insertions(+), 1 deletion(-) create mode 100644 lib/format.c diff --git a/include/xbps.h.in b/include/xbps.h.in index 45025d03d..6559e9423 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -2365,6 +2365,198 @@ xbps_plist_dictionary_from_file(const char *path); /**@}*/ +/** @addtogroup format */ +/**@{*/ + +/** + * @struct xbps_fmt xbps.h "xbps.h" + * @brief Structure of parsed format string. + */ +struct xbps_fmt; + +/** + * @struct xbps_fmt xbps.h "xbps.h" + * @brief Structure of parsed format specifier. + */ +struct xbps_fmt_spec { + /** + * @var conversion + * @brief Output conversion. + */ + char conversion; + /** + * @var fill + * @brief Padding character. + */ + char fill; + /** + * @var align + * @brief Alignment modifier. + * + * Possible values are: + * - `<`: left align. + * - `>`: right align. + * - `=`: place padding after the sign. + */ + char align; + /** + * @var sign + * @brief Sign modifier. + * + * Possible values are: + * - `-`: sign negative numbers. + * - `+`: sign both negative and positive numbers. + * - space: sign negative numbers and add space before positive numbers. + */ + char sign; + /** + * @var width + * @brief Minimum width. + */ + unsigned int width; + /** + * @var precision + * @brief Precision. + */ + unsigned int precision; + /** + * @var type + * @brief Type specifier usually to change the output format type. + * + * Can contain any character, xbps_fmt_number() uses the following: + * - `u`: Unsigned decimal. + * - `d`: Decimal. + * - `x`: Hex with lowercase letters. + * - `X`: hex with uppercase letters. + * - `h`: Human readable using humanize_number(3). + */ + char type; +}; + +/** + * @brief Format callback, called for each variable in the format string. + * + * A callback function should write data associated with \a var to \a fp and use + * \a w as alignment specifier. + * + * @param[in] fp The file to print to. + * @param[in] spec The format specifier. + * @param[in] var The format string variable name. + * @param[in] data Userdata passed to the xbps_fmt() function. + */ +typedef int (xbps_fmt_cb)(FILE *fp, const struct xbps_fmt_spec *spec, const char *var, void *data); + +/** + * @brief Parses the format string \a format. + * + * @param[in] format The format string. + * + * @return The parsed format structure, or NULL on error. + * The returned buffer must be freed with xbps_fmt_free(). + * @retval EINVAL Invalid format string. + * @retval ERANGE Invalid alignment specifier. + * @retval ENOMEM Memory allocation failure. + */ +struct xbps_fmt *xbps_fmt_parse(const char *format); + +/** + * @brief Releases memory associated with \a fmt. + * + * @param[in] fmt The format string. + */ +void xbps_fmt_free(struct xbps_fmt *fmt); + +/** + * @brief Print formatted text to \a fp. + * + * @param[in] fmt Format returned by struct xbps_fmt_parse(). + * @param[in] cb Callback function called for each variable in the format. + * @param[in] data Userdata passed to the callback \a cb. + * @param[in] fp File to print to. + * + * @return 0 on success or a negative errno. + * @retval 0 Success + */ +int xbps_fmt(const struct xbps_fmt *fmt, xbps_fmt_cb *cb, void *data, FILE *fp); + +/** + * @brief Print formatted dictionary values to \a fp. + * + * Prints formatted dictionary values as specified by the parsed \a fmt + * format string to \a fp. + * + * @param[in] fmt Format returned by struct xbps_fmt_parse(). + * @param[in] dict Dictionary to print values from. + * @param[in] fp File to print to. + * + * @return 0 on success or value returned by \a cb. + * @retval 0 Success + */ +int xbps_fmt_dictionary(const struct xbps_fmt *fmt, xbps_dictionary_t dict, FILE *fp); + +/** + * @brief Print formatted dictionary values to \a fp. + * + * Prints formatted dictionary values as specified by the format string + * \a format to \a fp. + * + * @param[in] format Format string. + * @param[in] dict Dictionary to print values from. + * @param[in] fp File to print to. + * + * @return 0 on success or value returned by \a cb. + * @retval 0 Success + */ +int xbps_fmts_dictionary(const char *format, xbps_dictionary_t dict, FILE *fp); + +/** + * @brief Print formatted dictionary to \a fp. + * + * Print the formatted dictionary according to the \a format format string + * to \a fp. + * + * @param[in] format Format string. + * @param[in] cb Callback function called for each variable in the format. + * @param[in] data Userdata passed to the callback \a cb. + * @param[in] fp File to print to. + * + * @return 0 on success. + * @retval 0 Success. + * @retval -EINVAL Invalid format string. + * @retval -ERANGE Invalid alignment specifier. + * @retval -ENOMEM Memory allocation failure. + */ +int xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp); + +/** + * @brief Print formatted number to \a fp. + * + * Prints the number \d to \a fp according to the specification \a spec. + * + * @param[in] spec Format specification. + * @param[in] num Number to print. + * @param[in] fp File to print to. + * + * @return Returns 0 on success. + */ +int xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t num, FILE *fp); + +/** + * @brief Print formatted string to \a fp. + * + * Prints the string \a str to \a fp according to the specification \a spec. + * + * @param[in] spec Format specification. + * @param[in] str String to print. + * @param[in] len Length of the string or 0. + * @param[in] fp File to print to. + * + * @return Returns 0 on success. + */ +int xbps_fmt_string(const struct xbps_fmt_spec *spec, const char *str, size_t len, FILE *fp); + +/**@}*/ + #ifdef __cplusplus } #endif diff --git a/lib/Makefile b/lib/Makefile index 0cf6ac84f..bd9838570 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -51,7 +51,7 @@ OBJS += plist_remove.o plist_fetch.o util.o util_path.o util_hash.o OBJS += repo.o repo_sync.o OBJS += rpool.o cb_util.o proplib_wrapper.o OBJS += package_alternatives.o -OBJS += conf.o log.o +OBJS += conf.o log.o format.o OBJS += $(EXTOBJS) $(COMPAT_OBJS) # unnecessary unless pkgdb format changes # OBJS += pkgdb_conversion.o diff --git a/lib/format.c b/lib/format.c new file mode 100644 index 000000000..80f8902cb --- /dev/null +++ b/lib/format.c @@ -0,0 +1,504 @@ +/*- + * Copyright (c) 2023 Duncan Overbruck . + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 +#include +#include +#include + +#include "xbps_api_impl.h" +#include "compat.h" + +/** + * @file lib/format.c + * @brief Format printing functions + * @defgroup format Format printing fuctions + * + * The format strings are similar to normal printf() format strings, + * but instead of character to specify types variable names are used. + * + */ + +struct strbuf { + size_t sz, len; + char *mem; +}; + +static int +strbuf_grow(struct strbuf *sb, size_t n) +{ + char *tmp; + size_t nsz; + if (sb->len+n+1 < sb->sz) + return 0; + nsz = 2*sb->sz + 16; + tmp = realloc(sb->mem, nsz); + if (!tmp) + return -errno; + sb->mem = tmp; + sb->sz = nsz; + return 0; +} + +static int +strbuf_putc(struct strbuf *sb, char c) +{ + int r = strbuf_grow(sb, 1); + if (r < 0) + return 0; + sb->mem[sb->len++] = c; + sb->mem[sb->len] = '\0'; + return 0; +} + +static int +strbuf_puts(struct strbuf *sb, const char *s, size_t n) +{ + int r = strbuf_grow(sb, n); + if (r < 0) + return 0; + memcpy(sb->mem+sb->len, s, n); + sb->len += n; + sb->mem[sb->len] = '\0'; + return 0; +} + +static void +strbuf_reset(struct strbuf *sb) +{ + sb->len = 0; + if (sb->mem) + sb->mem[0] = '\0'; +} + +static void +strbuf_release(struct strbuf *sb) +{ + free(sb->mem); + sb->mem = NULL; + sb->len = sb->sz = 0; +} + +struct common { + int type; +}; + +struct chunk { + struct common common; + char s[]; +}; + +struct var { + struct common common; + struct xbps_fmt_spec spec; + char s[]; +}; + +struct xbps_fmt { + union { + struct common *common; + struct chunk *chunk; + struct var *var; + }; +}; + +enum { + TTEXT = 1, + TVAR, +}; + +static int +nexttok(const char **pos, struct strbuf *buf) +{ + const char *p; + int r; + + strbuf_reset(buf); + + for (p = *pos; *p;) { + switch (*p) { + case '}': + if (p[1] != '}') + return -EINVAL; + r = strbuf_putc(buf, '}'); + if (r < 0) + return r; + p += 2; + break; + case '{': + if (p[1] == '{') { + r = strbuf_putc(buf, '{'); + if (r < 0) + return r; + p += 2; + continue; + } + *pos = p; + if (buf->len > 0) + return TTEXT; + return TVAR; + case '\\': + switch (*++p) { + case '\\': + r = strbuf_putc(buf, '\\'); + break; + case 'n': + r = strbuf_putc(buf, '\n'); + break; + case 't': + r = strbuf_putc(buf, '\t'); + break; + case '0': + r = strbuf_putc(buf, '\0'); + break; + default: + r = strbuf_putc(buf, '\\'); + if (r < 0) + break; + r = strbuf_putc(buf, *p); + } + if (r < 0) + return r; + p++; + break; + default: + r = strbuf_putc(buf, *p++); + if (r < 0) + return r; + } + } + if (buf->len > 0) { + *pos = p; + return TTEXT; + } + p++; + return 0; +} + +static int +parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec) +{ + const char *p = *pos; + const char *e; + int r; + bool fill = false; + + spec->conversion = '\0'; + spec->fill = ' '; + spec->align = '>'; + spec->sign = '-'; + spec->width = 0; + spec->precision = 0; + spec->type = '\0'; + + if (*p != '{') + return -EINVAL; + p++; + + e = strpbrk(p, "!:}"); + if (!e) + return -EINVAL; + + strbuf_reset(buf); + r = strbuf_puts(buf, p, e - p); + if (r < 0) + return r; + p = e; + + if (*p == '!') { + spec->conversion = *++p; + p++; + } + + if (*p == ':') { + p++; + if (*p && strchr("<>=", p[1])) { + fill = true; + spec->fill = *p; + spec->align = p[1]; + p += 2; + } else if (strchr("<>=", *p)) { + spec->align = *p; + p += 1; + } + if (strchr("+- ", *p)) { + spec->sign = *p; + p += 1; + } + if ((*p >= '0' && *p <= '9')) { + char *e1; + long v; + if (*p == '0') { + if (!fill) { + spec->fill = '0'; + spec->align = '='; + } + p++; + } + errno = 0; + v = strtoul(p, &e1, 10); + if (errno != 0) + return -errno; + if (v > INT_MAX) + return -ERANGE; + spec->width = v; + p = e1; + } + if (*p == '.') { + char *e1; + long v; + errno = 0; + v = strtoul(p+1, &e1, 10); + if (errno != 0) + return -errno; + if (v > 16) + return -ERANGE; + spec->precision = v; + p = e1; + } + if (*p != '}') + spec->type = *p++; + } + if (*p != '}') + return -EINVAL; + *pos = p+1; + return 0; +} + +struct xbps_fmt * +xbps_fmt_parse(const char *format) +{ + struct strbuf buf = {0}; + const char *pos = format; + struct xbps_fmt *fmt = NULL; + size_t n = 0; + int r = 1; + + for (;;) { + struct xbps_fmt_spec spec; + struct xbps_fmt *tmp; + r = nexttok(&pos, &buf); + if (r < 0) + goto err; + tmp = realloc(fmt, sizeof(*fmt)*(n + 1)); + if (!tmp) + goto err_errno; + fmt = tmp; + switch (r) { + case 0: + fmt[n].common = NULL; + goto out; + case TTEXT: + fmt[n].chunk = calloc(1, sizeof(struct chunk)+buf.len+1); + fmt[n].common->type = TTEXT; + if (!fmt[n].chunk) + goto err_errno; + memcpy(fmt[n].chunk->s, buf.mem, buf.len+1); + break; + case TVAR: + r = parse(&pos, &buf, &spec); + if (r < 0) + goto err; + fmt[n].var = calloc(1, sizeof(struct var)+buf.len+1); + if (!fmt[n].var) + goto err_errno; + fmt[n].common->type = TVAR; + fmt[n].var->spec = spec; + memcpy(fmt[n].var->s, buf.mem, buf.len+1); + break; + } + n++; + } +out: + strbuf_release(&buf); + return fmt; +err_errno: + r = -errno; +err: + free(fmt); + strbuf_release(&buf); + errno = -r; + return NULL; +} + +void +xbps_fmt_free(struct xbps_fmt *fmt) +{ + if (!fmt) + return; + for (struct xbps_fmt *f = fmt; f->common; f++) + switch (f->common->type) { + case TTEXT: free(f->chunk); break; + case TVAR: free(f->var); break; + } + free(fmt); +} + +int +xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp) +{ + struct strbuf buf = {0}; + const char *pos = format; + int r = 0; + + for (;;) { + struct xbps_fmt_spec spec; + r = nexttok(&pos, &buf); + if (r <= 0) + goto out; + switch (r) { + case TTEXT: + fprintf(fp, "%s", buf.mem); + break; + case TVAR: + r = parse(&pos, &buf, &spec); + if (r < 0) + goto out; + r = cb(fp, &spec, buf.mem, data); + if (r != 0) + goto out; + break; + } + } +out: + strbuf_release(&buf); + return r; +} + +int +xbps_fmt(const struct xbps_fmt *fmt, xbps_fmt_cb *cb, void *data, FILE *fp) +{ + int r; + for (const struct xbps_fmt *f = fmt; f->common; f++) { + switch (f->common->type) { + case TTEXT: + fprintf(fp, "%s", f->chunk->s); + break; + case TVAR: + r = cb(fp, &f->var->spec, f->var->s, data); + if (r != 0) + return r; + break; + } + } + return 0; +} + +struct fmt_dict_cb { + xbps_dictionary_t dict; +}; + +int +xbps_fmt_string(const struct xbps_fmt_spec *spec, const char *str, size_t len, FILE *fp) +{ + if (len == 0) + len = strlen(str); + if (spec->align == '>' && spec->width > (unsigned)len) { + for (unsigned i = 0; i < spec->width - len; i++) + fputc(spec->fill, fp); + } + fprintf(fp, "%.*s", (int)len, str); + if (spec->align == '<' && spec->width > (unsigned)len) { + for (unsigned i = 0; i < spec->width - len; i++) + fputc(spec->fill, fp); + } + return 0; +} + +int +xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) +{ + char buf[64]; + struct xbps_fmt_spec strspec = *spec; + const char *p = buf; + int len; + int scale; + + if (strspec.align == '=') + strspec.align = '>'; + + switch (spec->type) { + default: /* fallthrough */ + case 'd': + if (spec->sign == '+') + len = snprintf(buf, sizeof(buf), "%+" PRId64, d); + else + len = snprintf(buf, sizeof(buf), "%" PRId64, d); + if (spec->align == '=' && (buf[0] == '+' || buf[0] == '-')) { + len--, p++; + strspec.width -= 1; + fputc(buf[0], fp); + } + break; + case 'h': + len = spec->width < sizeof(buf) ? spec->width : sizeof(buf); + scale = humanize_number(buf, len, d, "B", HN_GETSCALE, HN_DECIMAL|HN_IEC_PREFIXES); + if (scale == -1) + return -EINVAL; + if (spec->precision && (unsigned int)scale < 6-spec->precision) + scale = 6-spec->precision; + len = humanize_number(buf, len, d, "B", scale, HN_DECIMAL|HN_IEC_PREFIXES); + if (scale == -1) + return -EINVAL; + break; + case 'o': len = snprintf(buf, sizeof(buf), "%" PRIo64, d); break; + case 'u': len = snprintf(buf, sizeof(buf), "%" PRIu64, d); break; + case 'x': len = snprintf(buf, sizeof(buf), "%" PRIx64, d); break; + case 'X': len = snprintf(buf, sizeof(buf), "%" PRIX64, d); break; + } + return xbps_fmt_string(&strspec, p, len, fp); +} + +static int +fmt_dict_cb(FILE *fp, const struct xbps_fmt_spec *spec, const char *var, void *data) +{ + struct fmt_dict_cb *ctx = data; + xbps_object_t val = xbps_dictionary_get(ctx->dict, var); + + switch (xbps_object_type(val)) { + case XBPS_TYPE_STRING: + return xbps_fmt_string(spec, xbps_string_cstring_nocopy(val), + xbps_string_size(val), fp); + case XBPS_TYPE_NUMBER: + return xbps_fmt_number(spec, xbps_number_integer_value(val), fp); + default: + break; + } + return 0; +} + +int +xbps_fmt_dictionary(const struct xbps_fmt *fmt, xbps_dictionary_t dict, FILE *fp) +{ + struct fmt_dict_cb ctx = {.dict = dict}; + return xbps_fmt(fmt, &fmt_dict_cb, &ctx, fp); +} + +int +xbps_fmts_dictionary(const char *format, xbps_dictionary_t dict, FILE *fp) +{ + struct fmt_dict_cb ctx = {.dict = dict}; + return xbps_fmts(format, &fmt_dict_cb, &ctx, fp); +} From 15a370b349a161389cb1428d582d4dd500f78eae Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Mon, 20 Feb 2023 23:54:08 +0100 Subject: [PATCH 03/21] bin/xbps-query: add -F/--format flag --- bin/xbps-query/defs.h | 8 +- bin/xbps-query/list.c | 273 ++++++++++++++++++++++-------------------- bin/xbps-query/main.c | 53 ++++++-- 3 files changed, 182 insertions(+), 152 deletions(-) diff --git a/bin/xbps-query/defs.h b/bin/xbps-query/defs.h index 2a1747253..0f4623114 100644 --- a/bin/xbps-query/defs.h +++ b/bin/xbps-query/defs.h @@ -52,14 +52,10 @@ int repo_show_pkg_namedesc(struct xbps_handle *, xbps_object_t, void *, int ownedby(struct xbps_handle *, const char *, bool, bool); /* From list.c */ -unsigned int find_longest_pkgver(struct xbps_handle *, xbps_object_t); - -int list_pkgs_in_dict(struct xbps_handle *, xbps_object_t, const char *, void *, bool *); int list_manual_pkgs(struct xbps_handle *, xbps_object_t, const char *, void *, bool *); -int list_hold_pkgs(struct xbps_handle *, xbps_object_t, const char *, void *, bool *); -int list_repolock_pkgs(struct xbps_handle *, xbps_object_t, const char *, void *, bool *); -int list_orphans(struct xbps_handle *); +int list_orphans(struct xbps_handle *, const char *); int list_pkgs_pkgdb(struct xbps_handle *); +int list_pkgdb(struct xbps_handle *, int (*filter)(xbps_object_t), const char *format); int repo_list(struct xbps_handle *); diff --git a/bin/xbps-query/list.c b/bin/xbps-query/list.c index c22ae00de..ff46dda28 100644 --- a/bin/xbps-query/list.c +++ b/bin/xbps-query/list.c @@ -24,29 +24,59 @@ */ #include -#include + +#include +#include +#include +#include #include +#include #include -#include #include -#include #include "defs.h" +#include "xbps.h" + +struct length_max_cb { + const char *key; + int max; +}; + +static int +length_max_cb(struct xbps_handle *xhp UNUSED, xbps_object_t obj, + const char *key UNUSED, void *arg, bool *loop_done UNUSED) +{ + struct length_max_cb *ctx = arg; + const char *s = NULL; + size_t len; + + if (!xbps_dictionary_get_cstring_nocopy(obj, ctx->key, &s)) + return -errno; + + len = strlen(s); + if (len > INT_MAX) + return -ERANGE; + if ((int)len > ctx->max) + ctx->max = len; + + return 0; +} struct list_pkgver_cb { - unsigned int pkgver_len; + unsigned int pkgver_align; unsigned int maxcols; - char *linebuf; + char *buf; + struct xbps_fmt *fmt; }; -int -list_pkgs_in_dict(struct xbps_handle *xhp UNUSED, +static int +list_pkgs_pkgdb_cb(struct xbps_handle *xhp UNUSED, xbps_object_t obj, const char *key UNUSED, void *arg, bool *loop_done UNUSED) { - struct list_pkgver_cb *lpc = arg; + struct list_pkgver_cb *ctx = arg; const char *pkgver = NULL, *short_desc = NULL, *state_str = NULL; unsigned int len; pkg_state_t state; @@ -58,104 +88,107 @@ list_pkgs_in_dict(struct xbps_handle *xhp UNUSED, xbps_pkg_state_dictionary(obj, &state); - if (state == XBPS_PKG_STATE_INSTALLED) - state_str = "ii"; - else if (state == XBPS_PKG_STATE_UNPACKED) - state_str = "uu"; - else if (state == XBPS_PKG_STATE_HALF_REMOVED) - state_str = "hr"; - else - state_str = "??"; - - if (lpc->linebuf == NULL) { - printf("%s %-*s %s\n", - state_str, - lpc->pkgver_len, pkgver, - short_desc); + switch (state) { + case XBPS_PKG_STATE_INSTALLED: state_str = "ii"; break; + case XBPS_PKG_STATE_UNPACKED: state_str = "uu"; break; + case XBPS_PKG_STATE_HALF_REMOVED: state_str = "hr"; break; + case XBPS_PKG_STATE_BROKEN: state_str = "br"; break; + case XBPS_PKG_STATE_NOT_INSTALLED: state_str = "??"; break; + } + + if (!ctx->buf) { + printf("%s %-*s %s\n", state_str, ctx->pkgver_align, pkgver, + short_desc); return 0; } - len = snprintf(lpc->linebuf, lpc->maxcols, "%s %-*s %s", - state_str, - lpc->pkgver_len, pkgver, - short_desc); /* add ellipsis if the line was truncated */ - if (len >= lpc->maxcols && lpc->maxcols > 4) { - for (unsigned int j = 0; j < 3; j++) - lpc->linebuf[lpc->maxcols-j-1] = '.'; - lpc->linebuf[lpc->maxcols] = '\0'; - } - puts(lpc->linebuf); + len = snprintf(ctx->buf, ctx->maxcols, "%s %-*s %s\n", state_str, + ctx->pkgver_align, pkgver, short_desc); + if (len >= ctx->maxcols && ctx->maxcols > sizeof("...")) + memcpy(ctx->buf + ctx->maxcols - sizeof("..."), "...", sizeof("...")); + fputs(ctx->buf, stdout); return 0; } int -list_manual_pkgs(struct xbps_handle *xhp UNUSED, - xbps_object_t obj, - const char *key UNUSED, - void *arg UNUSED, - bool *loop_done UNUSED) +list_pkgs_pkgdb(struct xbps_handle *xhp) { - const char *pkgver = NULL; - bool automatic = false; + struct length_max_cb longest = {.key = "pkgver"}; + struct list_pkgver_cb lpc = {0}; - xbps_dictionary_get_bool(obj, "automatic-install", &automatic); - if (automatic == false) { - xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); - puts(pkgver); + int r = xbps_pkgdb_foreach_cb_multi(xhp, length_max_cb, &longest); + if (r < 0) + return r; + + lpc.pkgver_align = longest.max; + lpc.maxcols = get_maxcols(); + if (lpc.maxcols > 0) { + lpc.buf = malloc(lpc.maxcols); + if (!lpc.buf) + return -errno; } - return 0; + return xbps_pkgdb_foreach_cb(xhp, list_pkgs_pkgdb_cb, &lpc); } -int -list_hold_pkgs(struct xbps_handle *xhp UNUSED, - xbps_object_t obj, - const char *key UNUSED, - void *arg UNUSED, - bool *loop_done UNUSED) -{ - const char *pkgver = NULL; +struct list_pkgdb_cb { + struct xbps_fmt *fmt; + int (*filter)(xbps_object_t obj); +}; - if (xbps_dictionary_get(obj, "hold")) { - xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); - puts(pkgver); +static int +list_pkgdb_cb(struct xbps_handle *xhp UNUSED, xbps_object_t obj, + const char *key UNUSED, void *arg, bool *loop_done UNUSED) +{ + struct list_pkgdb_cb *ctx = arg; + int r; + + if (ctx->filter) { + r = ctx->filter(obj); + if (r < 0) + return r; + if (r == 0) + return 0; } + r = xbps_fmt_dictionary(ctx->fmt, obj, stdout); + if (r < 0) + return r; return 0; } int -list_repolock_pkgs(struct xbps_handle *xhp UNUSED, - xbps_object_t obj, - const char *key UNUSED, - void *arg UNUSED, - bool *loop_done UNUSED) +list_pkgdb(struct xbps_handle *xhp, int (*filter)(xbps_object_t), const char *format) { - const char *pkgver = NULL; - - if (xbps_dictionary_get(obj, "repolock")) { - xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); - puts(pkgver); + struct list_pkgdb_cb ctx = {.filter = filter}; + int r; + + ctx.fmt = xbps_fmt_parse(format); + if (!ctx.fmt) { + r = -errno; + xbps_error_printf("failed to parse format: %s\n", strerror(-r)); + return r; } - - return 0; + r = xbps_pkgdb_foreach_cb(xhp, list_pkgdb_cb, &ctx); + xbps_fmt_free(ctx.fmt); + return r; } int -list_orphans(struct xbps_handle *xhp) +list_manual_pkgs(struct xbps_handle *xhp UNUSED, + xbps_object_t obj, + const char *key UNUSED, + void *arg UNUSED, + bool *loop_done UNUSED) { - xbps_array_t orphans; const char *pkgver = NULL; + bool automatic = false; - orphans = xbps_find_pkg_orphans(xhp, NULL); - if (orphans == NULL) - return EINVAL; - - for (unsigned int i = 0; i < xbps_array_count(orphans); i++) { - xbps_dictionary_get_cstring_nocopy(xbps_array_get(orphans, i), - "pkgver", &pkgver); + xbps_dictionary_get_bool(obj, "automatic-install", &automatic); + if (automatic == false) { + xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); puts(pkgver); } @@ -163,22 +196,43 @@ list_orphans(struct xbps_handle *xhp) } int -list_pkgs_pkgdb(struct xbps_handle *xhp) +list_orphans(struct xbps_handle *xhp, const char *format) { - struct list_pkgver_cb lpc; + xbps_array_t orphans; + struct xbps_fmt *fmt; + int r = 0; + + fmt = xbps_fmt_parse(format); + if (!fmt) { + r = -errno; + xbps_error_printf("failed to parse format: %s\n", strerror(-r)); + return r; + } - lpc.pkgver_len = find_longest_pkgver(xhp, NULL); - lpc.maxcols = get_maxcols(); - lpc.linebuf = NULL; - if (lpc.maxcols > 0) { - lpc.linebuf = malloc(lpc.maxcols); - if (lpc.linebuf == NULL) - exit(1); + orphans = xbps_find_pkg_orphans(xhp, NULL); + if (!orphans) { + r = -errno; + xbps_error_printf("failed to find orphans: %s\n", strerror(-r)); + goto err; } - return xbps_pkgdb_foreach_cb(xhp, list_pkgs_in_dict, &lpc); + for (unsigned int i = 0; i < xbps_array_count(orphans); i++) { + xbps_object_t obj = xbps_array_get(orphans, i); + if (!obj) + return -errno; + r = xbps_fmt_dictionary(fmt, obj, stdout); + if (r < 0) + goto err; + } +err: + xbps_fmt_free(fmt); + return r; } +#ifndef __UNCONST +#define __UNCONST(a) ((void *)(uintptr_t)(const void *)(a)) +#endif + static void repo_list_uri(struct xbps_repo *repo) { @@ -230,50 +284,3 @@ repo_list(struct xbps_handle *xhp) } return 0; } - -struct fflongest { - xbps_dictionary_t d; - unsigned int len; -}; - -static int -_find_longest_pkgver_cb(struct xbps_handle *xhp UNUSED, - xbps_object_t obj, - const char *key UNUSED, - void *arg, - bool *loop_done UNUSED) -{ - struct fflongest *ffl = arg; - const char *pkgver = NULL; - unsigned int len; - - xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver); - len = strlen(pkgver); - if (ffl->len == 0 || len > ffl->len) - ffl->len = len; - - return 0; -} - -unsigned int -find_longest_pkgver(struct xbps_handle *xhp, xbps_object_t o) -{ - struct fflongest ffl; - - ffl.d = o; - ffl.len = 0; - - if (xbps_object_type(o) == XBPS_TYPE_DICTIONARY) { - xbps_array_t array; - - array = xbps_dictionary_all_keys(o); - (void)xbps_array_foreach_cb_multi(xhp, array, o, - _find_longest_pkgver_cb, &ffl); - xbps_object_release(array); - } else { - (void)xbps_pkgdb_foreach_cb_multi(xhp, - _find_longest_pkgver_cb, &ffl); - } - - return ffl.len; -} diff --git a/bin/xbps-query/main.c b/bin/xbps-query/main.c index 44316c1ad..42afddb99 100644 --- a/bin/xbps-query/main.c +++ b/bin/xbps-query/main.c @@ -23,13 +23,15 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +#include +#include #include #include #include -#include -#include #include + #include "defs.h" static void __attribute__((noreturn)) @@ -41,6 +43,7 @@ usage(bool fail) " -C, --config Path to confdir (xbps.d)\n" " -c, --cachedir Path to cachedir\n" " -d, --debug Debug mode shown to stderr\n" + " -F, --format Format for list output\n" " -h, --help Show usage\n" " -i, --ignore-conf-repos Ignore repositories defined in xbps.d\n" " -M, --memory-sync Remote repository data is fetched and stored\n" @@ -74,10 +77,30 @@ usage(bool fail) exit(fail ? EXIT_FAILURE : EXIT_SUCCESS); } +static int +filter_hold(xbps_object_t obj) +{ + return xbps_dictionary_get(obj, "hold") != NULL; +} + +static int +filter_manual(xbps_object_t obj) +{ + bool automatic = false; + xbps_dictionary_get_bool(obj, "automatic-install", &automatic); + return !automatic; +} + +static int +filter_repolock(xbps_object_t obj) +{ + return xbps_dictionary_get(obj, "repolock") != NULL; +} + int main(int argc, char **argv) { - const char *shortopts = "C:c:df:hHiLlMmOo:p:Rr:s:S:VvX:x:"; + const char *shortopts = "C:c:dF:f:hHiLlMmOo:p:Rr:s:S:VvX:x:"; const struct option longopts[] = { { "config", required_argument, NULL, 'C' }, { "cachedir", required_argument, NULL, 'c' }, @@ -100,6 +123,7 @@ main(int argc, char **argv) { "version", no_argument, NULL, 'V' }, { "verbose", no_argument, NULL, 'v' }, { "files", required_argument, NULL, 'f' }, + { "format", required_argument, NULL, 'F' }, { "deps", required_argument, NULL, 'x' }, { "revdeps", required_argument, NULL, 'X' }, { "regex", no_argument, NULL, 0 }, @@ -108,13 +132,13 @@ main(int argc, char **argv) { NULL, 0, NULL, 0 }, }; struct xbps_handle xh; - const char *pkg, *rootdir, *cachedir, *confdir, *props, *catfile; + const char *pkg, *rootdir, *cachedir, *confdir, *props, *catfile, *format; int c, flags, rv; bool list_pkgs, list_repos, orphans, own, list_repolock; bool list_manual, list_hold, show_prop, show_files, show_deps, show_rdeps; bool show, pkg_search, regex, repo_mode, opmode, fulldeptree; - rootdir = cachedir = confdir = props = pkg = catfile = NULL; + rootdir = cachedir = confdir = props = pkg = catfile = format = NULL; flags = rv = c = 0; list_pkgs = list_repos = list_hold = orphans = pkg_search = own = false; list_manual = list_repolock = show_prop = show_files = false; @@ -138,6 +162,9 @@ main(int argc, char **argv) pkg = optarg; show_files = opmode = true; break; + case 'F': + format = optarg; + break; case 'H': list_hold = opmode = true; break; @@ -259,24 +286,24 @@ main(int argc, char **argv) rv = repo_list(&xh); } else if (list_hold) { - /* list on hold pkgs */ - rv = xbps_pkgdb_foreach_cb(&xh, list_hold_pkgs, NULL); + rv = list_pkgdb(&xh, filter_hold, format ? format : "{pkgver}\n") < 0; } else if (list_repolock) { - /* list repolocked packages */ - rv = xbps_pkgdb_foreach_cb(&xh, list_repolock_pkgs, NULL); + rv = list_pkgdb(&xh, filter_repolock, format ? format : "{pkgver}\n") < 0; } else if (list_manual) { - /* list manual pkgs */ - rv = xbps_pkgdb_foreach_cb(&xh, list_manual_pkgs, NULL); + rv = list_pkgdb(&xh, filter_manual, format ? format : "{pkgver}\n") < 0; } else if (list_pkgs) { /* list available pkgs */ - rv = list_pkgs_pkgdb(&xh); + if (format) + rv = list_pkgdb(&xh, NULL, format); + else + rv = list_pkgs_pkgdb(&xh); } else if (orphans) { /* list pkg orphans */ - rv = list_orphans(&xh); + rv = list_orphans(&xh, format ? format : "{pkgver}\n") < 0; } else if (own) { /* ownedby mode */ From 7125a817ce5d3425943c2a023cad4a8d70980f85 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 21 Feb 2023 15:56:27 +0100 Subject: [PATCH 04/21] tests: add tests for xbps_fmt* functions --- tests/xbps/libxbps/Kyuafile | 1 + tests/xbps/libxbps/Makefile | 1 + tests/xbps/libxbps/fmt/Kyuafile | 5 + tests/xbps/libxbps/fmt/Makefile | 8 ++ tests/xbps/libxbps/fmt/main.c | 180 ++++++++++++++++++++++++++++++++ 5 files changed, 195 insertions(+) create mode 100644 tests/xbps/libxbps/fmt/Kyuafile create mode 100644 tests/xbps/libxbps/fmt/Makefile create mode 100644 tests/xbps/libxbps/fmt/main.c diff --git a/tests/xbps/libxbps/Kyuafile b/tests/xbps/libxbps/Kyuafile index 86a009d9e..050e80733 100644 --- a/tests/xbps/libxbps/Kyuafile +++ b/tests/xbps/libxbps/Kyuafile @@ -12,3 +12,4 @@ include('config/Kyuafile') include('find_pkg_orphans/Kyuafile') include('pkgdb/Kyuafile') include('shell/Kyuafile') +include('fmt/Kyuafile') diff --git a/tests/xbps/libxbps/Makefile b/tests/xbps/libxbps/Makefile index ca361bc65..17bcd132e 100644 --- a/tests/xbps/libxbps/Makefile +++ b/tests/xbps/libxbps/Makefile @@ -12,5 +12,6 @@ SUBDIRS += find_pkg_orphans SUBDIRS += pkgdb SUBDIRS += config SUBDIRS += shell +SUBDIRS += fmt include ../../../mk/subdir.mk diff --git a/tests/xbps/libxbps/fmt/Kyuafile b/tests/xbps/libxbps/fmt/Kyuafile new file mode 100644 index 000000000..1a426bbdb --- /dev/null +++ b/tests/xbps/libxbps/fmt/Kyuafile @@ -0,0 +1,5 @@ +syntax("kyuafile", 1) + +test_suite("libxbps") + +atf_test_program{name="fmt_test"} diff --git a/tests/xbps/libxbps/fmt/Makefile b/tests/xbps/libxbps/fmt/Makefile new file mode 100644 index 000000000..c7749f64b --- /dev/null +++ b/tests/xbps/libxbps/fmt/Makefile @@ -0,0 +1,8 @@ +TOPDIR = ../../../.. +-include $(TOPDIR)/config.mk + +TESTSSUBDIR = xbps/libxbps/fmt +TEST = fmt_test +EXTRA_FILES = Kyuafile + +include $(TOPDIR)/mk/test.mk diff --git a/tests/xbps/libxbps/fmt/main.c b/tests/xbps/libxbps/fmt/main.c new file mode 100644 index 000000000..38b98c723 --- /dev/null +++ b/tests/xbps/libxbps/fmt/main.c @@ -0,0 +1,180 @@ +/*- + * Copyright (c) 2023 Duncan Overbruck . + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 "xbps.h" +#include "xbps/xbps_dictionary.h" +#include "xbps/xbps_object.h" +#include +#include +#include +#include +#include + +#include +#include + +ATF_TC(xbps_fmt_number); + +ATF_TC_HEAD(xbps_fmt_number, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_number"); +} + +ATF_TC_BODY(xbps_fmt_number, tc) +{ + char *buf = NULL; + size_t bufsz = 0; + FILE *fp; + struct test { + const char *expect; + int64_t d; + struct xbps_fmt_spec spec; + } tests[] = { + { "1", 1, {0}}, + {"-1", -1, {0}}, + {"-1", -1, {.sign = '+'}}, + {"+1", 1, {.sign = '+'}}, + + {"a", 0xA, {.type = 'x'}}, + {"A", 0xA, {.type = 'X'}}, + + {"644", 0644, {.type = 'o'}}, + + {"0010", 10, {.fill = '0', .align = '>', .width = 4}}, + {"1000", 10, {.fill = '0', .align = '<', .width = 4}}, + {"0010", 10, {.fill = '0', .align = '=', .width = 4}}, + {"-010", -10, {.fill = '0', .align = '=', .width = 4}}, + {"+010", 10, {.fill = '0', .align = '=', .sign = '+', .width = 4}}, + }; + ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); + + for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { + memset(buf, '\0', bufsz); + rewind(fp); + xbps_fmt_number(&tests[i].spec, tests[i].d, fp); + ATF_REQUIRE(fflush(fp) == 0); + ATF_CHECK_STREQ(buf, tests[i].expect); + } + ATF_REQUIRE(fclose(fp) == 0); + free(buf); +} + +ATF_TC(xbps_fmt_string); + +ATF_TC_HEAD(xbps_fmt_string, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_string"); +} + +ATF_TC_BODY(xbps_fmt_string, tc) +{ + char *buf = NULL; + size_t bufsz = 0; + FILE *fp; + struct test { + const char *expect; + const char *input; + size_t len; + struct xbps_fmt_spec spec; + } tests[] = { + { "1", "1", 0, {0}}, + { "2 ", "2", 0, {.fill = ' ', .align = '<', .width = 2}}, + { " 3", "3", 0, {.fill = ' ', .align = '>', .width = 2}}, + { "444", "444", 0, {.fill = ' ', .align = '>', .width = 2}}, + { "44", "444", 2, {.fill = ' ', .align = '>', .width = 2}}, + }; + ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); + + for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { + memset(buf, '\0', bufsz); + rewind(fp); + xbps_fmt_string(&tests[i].spec, tests[i].input, tests[i].len, fp); + ATF_REQUIRE(fflush(fp) == 0); + ATF_CHECK_STREQ(buf, tests[i].expect); + } + ATF_REQUIRE(fclose(fp) == 0); + free(buf); +} + +ATF_TC(xbps_fmt_dictionary); + +ATF_TC_HEAD(xbps_fmt_dictionary, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_dictionary"); +} + +ATF_TC_BODY(xbps_fmt_dictionary, tc) +{ + char *buf = NULL; + size_t bufsz = 0; + FILE *fp; + struct xbps_fmt *fmt; + xbps_dictionary_t dict; + ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); + ATF_REQUIRE(dict = xbps_dictionary_create()); + ATF_REQUIRE(xbps_dictionary_set_cstring_nocopy(dict, "string", "s")); + ATF_REQUIRE(xbps_dictionary_set_int64(dict, "number", 1)); + ATF_REQUIRE(fmt = xbps_fmt_parse(">{string} {number}<")); + ATF_REQUIRE(xbps_fmt_dictionary(fmt, dict, fp) == 0); + ATF_REQUIRE(fflush(fp) == 0); + ATF_CHECK_STREQ(buf, ">s 1<"); + ATF_REQUIRE(fclose(fp) == 0); + free(buf); + xbps_object_release(dict); +} + +ATF_TC(xbps_fmts_dictionary); + +ATF_TC_HEAD(xbps_fmts_dictionary, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_dictionary"); +} + +ATF_TC_BODY(xbps_fmts_dictionary, tc) +{ + char *buf = NULL; + size_t bufsz = 0; + FILE *fp; + xbps_dictionary_t dict; + ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); + ATF_REQUIRE(dict = xbps_dictionary_create()); + ATF_REQUIRE(xbps_dictionary_set_cstring_nocopy(dict, "string", "s")); + ATF_REQUIRE(xbps_dictionary_set_int64(dict, "number", 1)); + ATF_REQUIRE(xbps_fmts_dictionary(">{string} {number}<", dict, fp) == 0); + ATF_REQUIRE(fflush(fp) == 0); + ATF_CHECK_STREQ(buf, ">s 1<"); + ATF_REQUIRE(fclose(fp) == 0); + free(buf); + xbps_object_release(dict); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, xbps_fmt_number); + ATF_TP_ADD_TC(tp, xbps_fmt_string); + ATF_TP_ADD_TC(tp, xbps_fmt_dictionary); + ATF_TP_ADD_TC(tp, xbps_fmts_dictionary); + return atf_no_error(); +} From 851b1de49e5be592493267c4f9668952fb930b8a Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 21 Feb 2023 17:42:36 +0100 Subject: [PATCH 05/21] lib/format.c: new humanize format !humanize[ ][.][width][minscale[maxscale]][i] --- include/xbps.h.in | 3 +- lib/format.c | 181 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 142 insertions(+), 42 deletions(-) diff --git a/include/xbps.h.in b/include/xbps.h.in index 6559e9423..d0f0627b8 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -2380,10 +2380,11 @@ struct xbps_fmt; */ struct xbps_fmt_spec { /** + * @private * @var conversion * @brief Output conversion. */ - char conversion; + struct conversion *conversion; /** * @var fill * @brief Padding character. diff --git a/lib/format.c b/lib/format.c index 80f8902cb..c17cf28f2 100644 --- a/lib/format.c +++ b/lib/format.c @@ -197,15 +197,44 @@ nexttok(const char **pos, struct strbuf *buf) return 0; } +struct conversion { + enum { HUMANIZE = 1, STRMODE } type; + union { + struct humanize { + unsigned width : 8; + unsigned minscale : 8; + unsigned maxscale : 8; + bool decimal : 1; + int flags; + } humanize; + }; +}; + static int -parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec) +parse_u(const char **pos, unsigned int *u) +{ + char *e = NULL; + long v; + errno = 0; + v = strtoul(*pos, &e, 10); + if (errno != 0) + return -errno; + if (v > UINT_MAX) + return -ERANGE; + *u = v; + *pos = e; + return 0; +} + +static int +parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct conversion *conversion) { const char *p = *pos; const char *e; int r; bool fill = false; - spec->conversion = '\0'; + spec->conversion = NULL; spec->fill = ' '; spec->align = '>'; spec->sign = '-'; @@ -228,8 +257,53 @@ parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec) p = e; if (*p == '!') { - spec->conversion = *++p; - p++; + if (strncmp(p+1, "humanize", sizeof("humanize") - 1) == 0) { + /* humanize[ ][.][i][width][minscale[maxscale]] */ + const char *scale = "BKMGTPE"; + const char *p1; + p += sizeof("humanize"); + conversion->type = HUMANIZE; + if (*p != ':' && *p != '}') { + conversion->humanize.flags = HN_NOSPACE; + if (*p == ' ') { + conversion->humanize.flags &= ~HN_NOSPACE; + p++; + } + if (*p == '.') { + conversion->humanize.flags |= HN_DECIMAL; + p++; + } + if ((*p >= '0' && *p <= '9')) { + unsigned width = 0; + r = parse_u(&p, &width); + if (r < 0) + return r; + conversion->humanize.width = width <= 12 ? width : 12; + } + if ((p1 = strchr(scale, *p))) { + conversion->humanize.minscale = p1-scale+1; + p++; + if ((p1 = strchr(scale, *p))) { + conversion->humanize.maxscale = p1-scale+1; + p++; + } + } + if (*p == 'i') { + conversion->humanize.flags |= HN_IEC_PREFIXES; + p++; + } + } else { + /* default: !humanize .8Ki:8 */ + conversion->humanize.width = 8; + conversion->humanize.minscale = 2; + conversion->humanize.flags = HN_DECIMAL|HN_IEC_PREFIXES; + } + } else if (strncmp(p+1, "strmode", sizeof("strmode") - 1) == 0) { + p += sizeof("strmode"); + conversion->type = STRMODE; + } else { + return -EINVAL; + } } if (*p == ':') { @@ -248,8 +322,6 @@ parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec) p += 1; } if ((*p >= '0' && *p <= '9')) { - char *e1; - long v; if (*p == '0') { if (!fill) { spec->fill = '0'; @@ -257,26 +329,15 @@ parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec) } p++; } - errno = 0; - v = strtoul(p, &e1, 10); - if (errno != 0) - return -errno; - if (v > INT_MAX) - return -ERANGE; - spec->width = v; - p = e1; + r = parse_u(&p, &spec->width); + if (r < 0) + return r; } if (*p == '.') { - char *e1; - long v; - errno = 0; - v = strtoul(p+1, &e1, 10); - if (errno != 0) - return -errno; - if (v > 16) - return -ERANGE; - spec->precision = v; - p = e1; + p++; + r = parse_u(&p, &spec->precision); + if (r < 0) + return r; } if (*p != '}') spec->type = *p++; @@ -297,7 +358,8 @@ xbps_fmt_parse(const char *format) int r = 1; for (;;) { - struct xbps_fmt_spec spec; + struct xbps_fmt_spec spec = {0}; + struct conversion conversion = {0}; struct xbps_fmt *tmp; r = nexttok(&pos, &buf); if (r < 0) @@ -318,7 +380,7 @@ xbps_fmt_parse(const char *format) memcpy(fmt[n].chunk->s, buf.mem, buf.len+1); break; case TVAR: - r = parse(&pos, &buf, &spec); + r = parse(&pos, &buf, &spec, &conversion); if (r < 0) goto err; fmt[n].var = calloc(1, sizeof(struct var)+buf.len+1); @@ -327,6 +389,12 @@ xbps_fmt_parse(const char *format) fmt[n].common->type = TVAR; fmt[n].var->spec = spec; memcpy(fmt[n].var->s, buf.mem, buf.len+1); + if (conversion.type) { + fmt[n].var->spec.conversion = calloc(1, sizeof(struct conversion)); + if (!fmt[n].var->spec.conversion) + goto err_errno; + *fmt[n].var->spec.conversion = conversion; + } break; } n++; @@ -351,7 +419,10 @@ xbps_fmt_free(struct xbps_fmt *fmt) for (struct xbps_fmt *f = fmt; f->common; f++) switch (f->common->type) { case TTEXT: free(f->chunk); break; - case TVAR: free(f->var); break; + case TVAR: + free(f->var->spec.conversion); + free(f->var); + break; } free(fmt); } @@ -364,7 +435,8 @@ xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp) int r = 0; for (;;) { - struct xbps_fmt_spec spec; + struct xbps_fmt_spec spec = {0}; + struct conversion conversion = {0}; r = nexttok(&pos, &buf); if (r <= 0) goto out; @@ -373,7 +445,7 @@ xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp) fprintf(fp, "%s", buf.mem); break; case TVAR: - r = parse(&pos, &buf, &spec); + r = parse(&pos, &buf, &spec, &conversion); if (r < 0) goto out; r = cb(fp, &spec, buf.mem, data); @@ -427,6 +499,38 @@ xbps_fmt_string(const struct xbps_fmt_spec *spec, const char *str, size_t len, F return 0; } +static int +humanize(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) +{ + char buf[64]; + const struct humanize *h = &spec->conversion->humanize; + int scale = 0; + int width = h->width ? h->width : 8; + int len; + + if (h->minscale) { + scale = humanize_number(buf, width, d, "B", HN_GETSCALE, h->flags); + if (scale == -1) + return -EINVAL; + if (scale < h->minscale - 1) + scale = h->minscale - 1; + if (h->maxscale && scale > h->maxscale - 1) + scale = h->maxscale - 1; + } else if (scale == 0) { + scale = HN_AUTOSCALE; + } + len = humanize_number(buf, width, d, "B", scale, h->flags); + if (len == -1) + return -EINVAL; + return xbps_fmt_string(spec, buf, len, fp); +} + +static int +tostrmode(const struct xbps_fmt_spec *spec UNUSED, int64_t d UNUSED, FILE *fp UNUSED) +{ + return -ENOTSUP; +} + int xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) { @@ -434,7 +538,13 @@ xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) struct xbps_fmt_spec strspec = *spec; const char *p = buf; int len; - int scale; + + if (spec->conversion) { + switch (spec->conversion->type) { + case HUMANIZE: return humanize(spec, d, fp); + case STRMODE: return tostrmode(spec, d, fp); + } + } if (strspec.align == '=') strspec.align = '>'; @@ -452,17 +562,6 @@ xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) fputc(buf[0], fp); } break; - case 'h': - len = spec->width < sizeof(buf) ? spec->width : sizeof(buf); - scale = humanize_number(buf, len, d, "B", HN_GETSCALE, HN_DECIMAL|HN_IEC_PREFIXES); - if (scale == -1) - return -EINVAL; - if (spec->precision && (unsigned int)scale < 6-spec->precision) - scale = 6-spec->precision; - len = humanize_number(buf, len, d, "B", scale, HN_DECIMAL|HN_IEC_PREFIXES); - if (scale == -1) - return -EINVAL; - break; case 'o': len = snprintf(buf, sizeof(buf), "%" PRIo64, d); break; case 'u': len = snprintf(buf, sizeof(buf), "%" PRIu64, d); break; case 'x': len = snprintf(buf, sizeof(buf), "%" PRIx64, d); break; From 0ec41f52ede477a869ccbfbcc16671478cbdc2af Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 21 Feb 2023 18:46:11 +0100 Subject: [PATCH 06/21] bin/xbps-query: document format flag --- bin/xbps-query/xbps-query.1 | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index b4c364180..ed475db5e 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -88,6 +88,49 @@ If the first character is not '/' then it's a relative path of .Ar rootdir . .It Fl d, Fl -debug Enables extra debugging shown to stderr. +.It Fl F, Fl -format Ar format +Format for list output. +.Bd -literal + ::= "{" variable ["!" conversion] [":" format] "}" + ::= [a-zA-Z90-9_-] + + ::= humanize | strmode + +-- Convert inode status information into a symbolic string + ::= "strmode" + +-- Format a number into a human readable form, the default is:`humanize .8Ki`: + ::= "humanize" [space] [decimal] [width] [scale] [i] + ::= " " -- Put a space between number and the suffix. + ::= "." -- If the final result is less than 10, display + it using one digit. + ::= [0-9]+ -- Width of the output. + ::= multiplier -- Minimum scale multiplier and optionally + [multiplier] -- Maxium scale multiplier. + ::= "B" + | "K" -- kilo + | "M" -- mega + | "G" -- giga + | "T" -- tera + | "P" -- peta + | "E" -- exa + ::= "i" -- Divide number with 1000 instead of 1024. + + ::= [[fill] align] [sign] [width] ["." precision] [type] + ::= -- The character to use when aligning the output. + ::= "<" -- Left align. + | ">" -- Right align. + | "=" -- Left align with zero paddings after the sign. + ::= "+" -- Add sign to positive and negative numbers. + | "-" -- Add sign to negative numbers. + ::= [0-9]+ -- The alignment width. + ::= [0-9]+ -- Percision for numbers. + ::= "d" -- Decimal number. + | "o" -- Octal number. + | "u" -- Unsigned number. + | "x" -- Hexadecimal with lowercase letters. + | "X" -- Hexadecimal with uppercase letters. +.Ed .It Fl h, Fl -help Show the help message. .It Fl i, Fl -ignore-conf-repos From cbcc350cd939c861e5805a3781b5f54fd615c830 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Fri, 10 Mar 2023 15:58:55 +0100 Subject: [PATCH 07/21] lib/format.c: split/cleanup code --- lib/format.c | 240 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 150 insertions(+), 90 deletions(-) diff --git a/lib/format.c b/lib/format.c index c17cf28f2..00a213b37 100644 --- a/lib/format.c +++ b/lib/format.c @@ -227,13 +227,76 @@ parse_u(const char **pos, unsigned int *u) } static int -parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct conversion *conversion) +parse_humanize(const char **pos, struct humanize *humanize) { + const char *scale = "BKMGTPE"; const char *p = *pos; - const char *e; - int r; + const char *p1; + + /* default: !humanize .8Ki:8 */ + humanize->width = 8; + humanize->minscale = 2; + humanize->flags = HN_DECIMAL|HN_IEC_PREFIXES; + humanize->flags = HN_NOSPACE; + + /* humanize[ ][.][i][width][minscale[maxscale]] */ + + if (*p == ' ') { + humanize->flags &= ~HN_NOSPACE; + p++; + } + if (*p == '.') { + humanize->flags |= HN_DECIMAL; + p++; + } + if ((*p >= '0' && *p <= '9')) { + unsigned width = 0; + int r = parse_u(&p, &width); + if (r < 0) + return r; + humanize->width = width <= 12 ? width : 12; + } + if ((p1 = strchr(scale, *p))) { + humanize->minscale = p1-scale+1; + p++; + if ((p1 = strchr(scale, *p))) { + humanize->maxscale = p1-scale+1; + p++; + } + } + if (*p == 'i') { + humanize->flags |= HN_IEC_PREFIXES; + p++; + } + *pos = p; + return 0; +} + +static int +parse_conversion(const char **pos, struct conversion *conversion) +{ + if (**pos != '!') + return 0; + if (strncmp(*pos + 1, "strmode", sizeof("strmode") - 1) == 0) { + *pos += sizeof("strmode"); + conversion->type = STRMODE; + return 0; + } else if (strncmp(*pos + 1, "humanize", sizeof("humanize") - 1) == 0) { + conversion->type = HUMANIZE; + *pos += sizeof("humanize"); + return parse_humanize(pos, &conversion->humanize); + } + return -EINVAL; +} + +static int +parse_spec(const char **pos, struct xbps_fmt_spec *spec) +{ bool fill = false; + const char *p = *pos; + int r; + /* defaults */ spec->conversion = NULL; spec->fill = ' '; spec->align = '>'; @@ -242,12 +305,85 @@ parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct c spec->precision = 0; spec->type = '\0'; + /* format_spec ::= [[fill]align][sign][zero][width][.precision][type] */ + + if (*p != ':') + return 0; + p++; + + /* fill ::= . */ + if (*p && strchr("<>=", p[1])) { + fill = true; + spec->fill = *p; + spec->align = p[1]; + p += 2; + } + + /* align ::= [<>=] */ + if (strchr("<>=", *p)) { + spec->align = *p; + p += 1; + } + + /* sign ::= [+-] */ + if (strchr("+- ", *p)) { + spec->sign = *p; + p += 1; + } + + /* zero ::= [0] */ + if (*p == '0') { + if (!fill) { + spec->fill = '0'; + spec->align = '='; + } + p++; + } + + /* width ::= [[0-9]+] */ + if ((*p >= '0' && *p <= '9')) { + r = parse_u(&p, &spec->width); + if (r < 0) + return r; + } + + /* precision ::= ['.' [0-9]+] */ + if (*p == '.') { + p++; + r = parse_u(&p, &spec->precision); + if (r < 0) + return r; + } + + /* type ::= [[a-zA-Z]] */ + if ((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z')) + spec->type = *p++; + + *pos = p; + return 0; +} + +static int +parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct conversion *conversion) +{ + const char *p = *pos; + const char *e; + int r; + if (*p != '{') return -EINVAL; p++; - e = strpbrk(p, "!:}"); - if (!e) + /* var ::= '{' name [conversion][format_spec] '}' */ + + /* name ::= [a-zA-Z0-9_-]+ */ + for (e = p; (*e >= 'a' && *e <= 'z') || + (*e >= 'A' && *e <= 'Z') || + (*e >= '0' && *e <= '0') || + (*e == '_' || *e == '-'); e++) + ; + if (e == p) return -EINVAL; strbuf_reset(buf); @@ -256,92 +392,16 @@ parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct c return r; p = e; - if (*p == '!') { - if (strncmp(p+1, "humanize", sizeof("humanize") - 1) == 0) { - /* humanize[ ][.][i][width][minscale[maxscale]] */ - const char *scale = "BKMGTPE"; - const char *p1; - p += sizeof("humanize"); - conversion->type = HUMANIZE; - if (*p != ':' && *p != '}') { - conversion->humanize.flags = HN_NOSPACE; - if (*p == ' ') { - conversion->humanize.flags &= ~HN_NOSPACE; - p++; - } - if (*p == '.') { - conversion->humanize.flags |= HN_DECIMAL; - p++; - } - if ((*p >= '0' && *p <= '9')) { - unsigned width = 0; - r = parse_u(&p, &width); - if (r < 0) - return r; - conversion->humanize.width = width <= 12 ? width : 12; - } - if ((p1 = strchr(scale, *p))) { - conversion->humanize.minscale = p1-scale+1; - p++; - if ((p1 = strchr(scale, *p))) { - conversion->humanize.maxscale = p1-scale+1; - p++; - } - } - if (*p == 'i') { - conversion->humanize.flags |= HN_IEC_PREFIXES; - p++; - } - } else { - /* default: !humanize .8Ki:8 */ - conversion->humanize.width = 8; - conversion->humanize.minscale = 2; - conversion->humanize.flags = HN_DECIMAL|HN_IEC_PREFIXES; - } - } else if (strncmp(p+1, "strmode", sizeof("strmode") - 1) == 0) { - p += sizeof("strmode"); - conversion->type = STRMODE; - } else { - return -EINVAL; - } - } + /* conversion ::= ['!' ...] */ + r = parse_conversion(&p, conversion); + if (r < 0) + return r; + + /* format_spec ::= [':' ...] */ + r = parse_spec(&p, spec); + if (r < 0) + return r; - if (*p == ':') { - p++; - if (*p && strchr("<>=", p[1])) { - fill = true; - spec->fill = *p; - spec->align = p[1]; - p += 2; - } else if (strchr("<>=", *p)) { - spec->align = *p; - p += 1; - } - if (strchr("+- ", *p)) { - spec->sign = *p; - p += 1; - } - if ((*p >= '0' && *p <= '9')) { - if (*p == '0') { - if (!fill) { - spec->fill = '0'; - spec->align = '='; - } - p++; - } - r = parse_u(&p, &spec->width); - if (r < 0) - return r; - } - if (*p == '.') { - p++; - r = parse_u(&p, &spec->precision); - if (r < 0) - return r; - } - if (*p != '}') - spec->type = *p++; - } if (*p != '}') return -EINVAL; *pos = p+1; From aefb164afea7127d501a9e05fb771a4b2a47d977 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Fri, 10 Mar 2023 16:39:25 +0100 Subject: [PATCH 08/21] lib/format.c: change escaped [{}] to \\[{}] --- lib/format.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/format.c b/lib/format.c index 00a213b37..bae80a42c 100644 --- a/lib/format.c +++ b/lib/format.c @@ -140,21 +140,8 @@ nexttok(const char **pos, struct strbuf *buf) for (p = *pos; *p;) { switch (*p) { case '}': - if (p[1] != '}') - return -EINVAL; - r = strbuf_putc(buf, '}'); - if (r < 0) - return r; - p += 2; - break; + return -EINVAL; case '{': - if (p[1] == '{') { - r = strbuf_putc(buf, '{'); - if (r < 0) - return r; - p += 2; - continue; - } *pos = p; if (buf->len > 0) return TTEXT; @@ -173,6 +160,12 @@ nexttok(const char **pos, struct strbuf *buf) case '0': r = strbuf_putc(buf, '\0'); break; + case '{': + r = strbuf_putc(buf, '{'); + break; + case '}': + r = strbuf_putc(buf, '}'); + break; default: r = strbuf_putc(buf, '\\'); if (r < 0) From 28fe8de74bb4ddff0caf3829f4eeaaf2a545f190 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Fri, 10 Mar 2023 16:46:34 +0100 Subject: [PATCH 09/21] lib/format.c: add some more escape sequences --- lib/format.c | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/lib/format.c b/lib/format.c index bae80a42c..0db817ae5 100644 --- a/lib/format.c +++ b/lib/format.c @@ -148,24 +148,16 @@ nexttok(const char **pos, struct strbuf *buf) return TVAR; case '\\': switch (*++p) { - case '\\': - r = strbuf_putc(buf, '\\'); - break; - case 'n': - r = strbuf_putc(buf, '\n'); - break; - case 't': - r = strbuf_putc(buf, '\t'); - break; - case '0': - r = strbuf_putc(buf, '\0'); - break; - case '{': - r = strbuf_putc(buf, '{'); - break; - case '}': - r = strbuf_putc(buf, '}'); - break; + case '\\': r = strbuf_putc(buf, '\\'); break; + case 'a': r = strbuf_putc(buf, '\a'); break; + case 'b': r = strbuf_putc(buf, '\b'); break; + case 'f': r = strbuf_putc(buf, '\f'); break; + case 'n': r = strbuf_putc(buf, '\n'); break; + case 'r': r = strbuf_putc(buf, '\r'); break; + case 't': r = strbuf_putc(buf, '\t'); break; + case '0': r = strbuf_putc(buf, '\0'); break; + case '{': r = strbuf_putc(buf, '{'); break; + case '}': r = strbuf_putc(buf, '}'); break; default: r = strbuf_putc(buf, '\\'); if (r < 0) From aab32fee67c998ecb3a427eb9dfc0a4967257243 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Fri, 10 Mar 2023 16:53:33 +0100 Subject: [PATCH 10/21] bin/xbps-query: update/clean format documentation --- bin/xbps-query/xbps-query.1 | 110 ++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 42 deletions(-) diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index ed475db5e..cfc196e2b 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -89,48 +89,10 @@ If the first character is not '/' then it's a relative path of .It Fl d, Fl -debug Enables extra debugging shown to stderr. .It Fl F, Fl -format Ar format -Format for list output. -.Bd -literal - ::= "{" variable ["!" conversion] [":" format] "}" - ::= [a-zA-Z90-9_-] - - ::= humanize | strmode - --- Convert inode status information into a symbolic string - ::= "strmode" - --- Format a number into a human readable form, the default is:`humanize .8Ki`: - ::= "humanize" [space] [decimal] [width] [scale] [i] - ::= " " -- Put a space between number and the suffix. - ::= "." -- If the final result is less than 10, display - it using one digit. - ::= [0-9]+ -- Width of the output. - ::= multiplier -- Minimum scale multiplier and optionally - [multiplier] -- Maxium scale multiplier. - ::= "B" - | "K" -- kilo - | "M" -- mega - | "G" -- giga - | "T" -- tera - | "P" -- peta - | "E" -- exa - ::= "i" -- Divide number with 1000 instead of 1024. - - ::= [[fill] align] [sign] [width] ["." precision] [type] - ::= -- The character to use when aligning the output. - ::= "<" -- Left align. - | ">" -- Right align. - | "=" -- Left align with zero paddings after the sign. - ::= "+" -- Add sign to positive and negative numbers. - | "-" -- Add sign to negative numbers. - ::= [0-9]+ -- The alignment width. - ::= [0-9]+ -- Percision for numbers. - ::= "d" -- Decimal number. - | "o" -- Octal number. - | "u" -- Unsigned number. - | "x" -- Hexadecimal with lowercase letters. - | "X" -- Hexadecimal with uppercase letters. -.Ed +Format string for output formatting. +See +.Sx FORMAT STRINGS +for syntax. .It Fl h, Fl -help Show the help message. .It Fl i, Fl -ignore-conf-repos @@ -320,6 +282,70 @@ expression wins. This expects an absolute path. This mode only works with repositories. .El +.Sh FORMAT STRINGS +Variables are package properties if not otherwise documented. +See +.Sx PROPERTIES +section for a list of available properties. +.Pp +As example a format string like: +.Bd -offset indent -literal +{pkgname:<30} {installed_size!humanize :>10}\\n +.Ed +.Pp +Would produce a list formatted like: +.Bd -offset indent -literal +libxbps 304 KB +xbps 484 KB +.Ed +.Pp +Format strings are parsed by the following EBNF: +.Bd -literal + ::= (text | escape | substitution)* + ::= [^\\{}]+ -- literal text chunk + ::= "\\" [abfnrtv0] -- POSIX-like espace sequence + | "\\{" | "\\}" -- escaped "{" and "}" + + ::= "{" variable ["!" conversion] [":" format] "}" + ::= [a-zA-Z0-9_-] + + ::= humanize | strmode + +-- Convert inode status information into a symbolic string + ::= "strmode" + +-- Format a number into a human readable form, the default is:`humanize .8Ki`: + ::= "humanize" [space] [decimal] [width] [scale] [i] + ::= " " -- Put a space between number and the suffix. + ::= "." -- If the final result is less than 10, display + it using one digit. + ::= [0-9]+ -- Width of the output. + ::= multiplier -- Minimum scale multiplier and optionally + [multiplier] -- Maxium scale multiplier. + ::= "B" + | "K" -- kilo + | "M" -- mega + | "G" -- giga + | "T" -- tera + | "P" -- peta + | "E" -- exa + ::= "i" -- Divide number with 1000 instead of 1024. + + ::= [[fill] align] [sign] [width] ["." precision] [type] + ::= -- The character to use when aligning the output. + ::= "<" -- Left align. + | ">" -- Right align. + | "=" -- Left align with zero paddings after the sign. + ::= "+" -- Add sign to positive and negative numbers. + | "-" -- Add sign to negative numbers. + ::= [0-9]+ -- The alignment width. + ::= [0-9]+ -- Percision for numbers. + ::= "d" -- Decimal number. + | "o" -- Octal number. + | "u" -- Unsigned number. + | "x" -- Hexadecimal with lowercase letters. + | "X" -- Hexadecimal with uppercase letters. +.Ed .Sh PROPERTIES This is the list of a packages properties. Note that not all properties are available for all packages. From ea189d19d8bfb55b8a889842fe3288e5bc799256 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Fri, 10 Mar 2023 17:18:16 +0100 Subject: [PATCH 11/21] bin/xbps-query: fix humanize "i" SI prefixs documentation --- bin/xbps-query/xbps-query.1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index cfc196e2b..9aa6a8cc1 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -322,14 +322,14 @@ Format strings are parsed by the following EBNF: ::= [0-9]+ -- Width of the output. ::= multiplier -- Minimum scale multiplier and optionally [multiplier] -- Maxium scale multiplier. - ::= "B" + ::= "B" -- byte | "K" -- kilo | "M" -- mega | "G" -- giga | "T" -- tera | "P" -- peta | "E" -- exa - ::= "i" -- Divide number with 1000 instead of 1024. + ::= "i" -- Use IEE/IEC (and now also SI) power of two prefixes. ::= [[fill] align] [sign] [width] ["." precision] [type] ::= -- The character to use when aligning the output. From 754fae20af7b68fefbe90539613bcf03ed013a54 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 14 Mar 2023 17:02:30 +0100 Subject: [PATCH 12/21] lib/format.c: refactor a bit --- include/xbps.h.in | 51 +++++-- lib/format.c | 263 +++++++++++++++++----------------- tests/xbps/libxbps/fmt/main.c | 34 ++--- 3 files changed, 189 insertions(+), 159 deletions(-) diff --git a/include/xbps.h.in b/include/xbps.h.in index d0f0627b8..059673fc3 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -2372,19 +2372,35 @@ xbps_plist_dictionary_from_file(const char *path); * @struct xbps_fmt xbps.h "xbps.h" * @brief Structure of parsed format string. */ -struct xbps_fmt; +struct xbps_fmt { + /** + * @private + * @var prefix + * @brief Prefix of the format chunk. + */ + char *prefix; + /** + * @var var + * @brief Variable name. + */ + char *var; + /** + * @var conv + * @brief Format conversion. + */ + struct xbps_fmt_conv *conv; + /** + * @var spec + * @brief Format specification. + */ + struct xbps_fmt_spec *spec; +}; /** * @struct xbps_fmt xbps.h "xbps.h" * @brief Structure of parsed format specifier. */ struct xbps_fmt_spec { - /** - * @private - * @var conversion - * @brief Output conversion. - */ - struct conversion *conversion; /** * @var fill * @brief Padding character. @@ -2445,7 +2461,7 @@ struct xbps_fmt_spec { * @param[in] var The format string variable name. * @param[in] data Userdata passed to the xbps_fmt() function. */ -typedef int (xbps_fmt_cb)(FILE *fp, const struct xbps_fmt_spec *spec, const char *var, void *data); +typedef int (xbps_fmt_cb)(FILE *fp, const struct xbps_fmt *fmt, void *data); /** * @brief Parses the format string \a format. @@ -2532,7 +2548,7 @@ int xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp); /** * @brief Print formatted number to \a fp. * - * Prints the number \d to \a fp according to the specification \a spec. + * Prints the number \a num to \a fp according to the specification \a spec. * * @param[in] spec Format specification. * @param[in] num Number to print. @@ -2540,7 +2556,7 @@ int xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp); * * @return Returns 0 on success. */ -int xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t num, FILE *fp); +int xbps_fmt_print_number(const struct xbps_fmt *fmt, int64_t num, FILE *fp); /** * @brief Print formatted string to \a fp. @@ -2554,7 +2570,20 @@ int xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t num, FILE *fp); * * @return Returns 0 on success. */ -int xbps_fmt_string(const struct xbps_fmt_spec *spec, const char *str, size_t len, FILE *fp); +int xbps_fmt_print_string(const struct xbps_fmt *fmt, const char *str, size_t len, FILE *fp); + +/** + * @brief Print formatted ::xbps_object_t to \a fp. + * + * Prints the ::xbps_object_t \a obj to \a fp according to the specification \a spec. + * + * @param[in] spec Format specification. + * @param[in] obj The object to print. + * @param[in] fp File to print to. + * + * @return Returns 0 on success. + */ +int xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *fp); /**@}*/ diff --git a/lib/format.c b/lib/format.c index 0db817ae5..dc34be670 100644 --- a/lib/format.c +++ b/lib/format.c @@ -101,35 +101,12 @@ strbuf_release(struct strbuf *sb) sb->len = sb->sz = 0; } -struct common { - int type; -}; - -struct chunk { - struct common common; - char s[]; -}; - -struct var { - struct common common; - struct xbps_fmt_spec spec; - char s[]; -}; - -struct xbps_fmt { - union { - struct common *common; - struct chunk *chunk; - struct var *var; - }; -}; - -enum { +enum tok { TTEXT = 1, TVAR, }; -static int +static enum tok nexttok(const char **pos, struct strbuf *buf) { const char *p; @@ -182,7 +159,7 @@ nexttok(const char **pos, struct strbuf *buf) return 0; } -struct conversion { +struct xbps_fmt_conv { enum { HUMANIZE = 1, STRMODE } type; union { struct humanize { @@ -258,31 +235,54 @@ parse_humanize(const char **pos, struct humanize *humanize) } static int -parse_conversion(const char **pos, struct conversion *conversion) +parse_conversion(const char **pos, struct xbps_fmt *fmt, struct xbps_fmt_conv *conv_storage) { - if (**pos != '!') + if (**pos != '!') { + fmt->conv = NULL; return 0; + } + fmt->conv = conv_storage; + if (!conv_storage) + fmt->conv = calloc(1, sizeof(*fmt->conv)); + if (!fmt->conv) + return -errno; if (strncmp(*pos + 1, "strmode", sizeof("strmode") - 1) == 0) { *pos += sizeof("strmode"); - conversion->type = STRMODE; + fmt->conv->type = STRMODE; return 0; } else if (strncmp(*pos + 1, "humanize", sizeof("humanize") - 1) == 0) { - conversion->type = HUMANIZE; + fmt->conv->type = HUMANIZE; *pos += sizeof("humanize"); - return parse_humanize(pos, &conversion->humanize); + return parse_humanize(pos, &fmt->conv->humanize); } return -EINVAL; } static int -parse_spec(const char **pos, struct xbps_fmt_spec *spec) +parse_spec(const char **pos, struct xbps_fmt *fmt, struct xbps_fmt_spec *spec_storage) { bool fill = false; + struct xbps_fmt_spec *spec; const char *p = *pos; int r; + /* format_spec ::= [[fill]align][sign][zero][width][.precision][type] */ + + if (*p != ':') { + fmt->spec = NULL; + return 0; + } + p++; + + if (!spec_storage) { + spec = fmt->spec = calloc(1, sizeof(*fmt->spec)); + if (!fmt->spec) + return -errno; + } else { + spec = fmt->spec = spec_storage; + } + /* defaults */ - spec->conversion = NULL; spec->fill = ' '; spec->align = '>'; spec->sign = '-'; @@ -290,12 +290,6 @@ parse_spec(const char **pos, struct xbps_fmt_spec *spec) spec->precision = 0; spec->type = '\0'; - /* format_spec ::= [[fill]align][sign][zero][width][.precision][type] */ - - if (*p != ':') - return 0; - p++; - /* fill ::= . */ if (*p && strchr("<>=", p[1])) { fill = true; @@ -350,7 +344,10 @@ parse_spec(const char **pos, struct xbps_fmt_spec *spec) } static int -parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct conversion *conversion) +parse(const char **pos, struct xbps_fmt *fmt, + struct strbuf *buf, + struct xbps_fmt_conv *conv_storage, + struct xbps_fmt_spec *spec_storage) { const char *p = *pos; const char *e; @@ -371,19 +368,25 @@ parse(const char **pos, struct strbuf *buf, struct xbps_fmt_spec *spec, struct c if (e == p) return -EINVAL; - strbuf_reset(buf); - r = strbuf_puts(buf, p, e - p); - if (r < 0) - return r; + if (buf) { + strbuf_reset(buf); + r = strbuf_puts(buf, p, e - p); + if (r < 0) + return r; + } else { + fmt->var = strndup(p, e - p); + if (!fmt->var) + return -errno; + } p = e; /* conversion ::= ['!' ...] */ - r = parse_conversion(&p, conversion); + r = parse_conversion(&p, fmt, conv_storage); if (r < 0) return r; /* format_spec ::= [':' ...] */ - r = parse_spec(&p, spec); + r = parse_spec(&p, fmt, spec_storage); if (r < 0) return r; @@ -403,45 +406,31 @@ xbps_fmt_parse(const char *format) int r = 1; for (;;) { - struct xbps_fmt_spec spec = {0}; - struct conversion conversion = {0}; struct xbps_fmt *tmp; - r = nexttok(&pos, &buf); - if (r < 0) - goto err; + enum tok t; + + t = nexttok(&pos, &buf); + tmp = realloc(fmt, sizeof(*fmt)*(n + 1)); if (!tmp) goto err_errno; fmt = tmp; - switch (r) { - case 0: - fmt[n].common = NULL; + memset(&fmt[n], '\0', sizeof(struct xbps_fmt)); + + if (t == 0) goto out; - case TTEXT: - fmt[n].chunk = calloc(1, sizeof(struct chunk)+buf.len+1); - fmt[n].common->type = TTEXT; - if (!fmt[n].chunk) + if (t == TTEXT) { + fmt[n].prefix = strndup(buf.mem, buf.len); + if (!fmt[n].prefix) goto err_errno; - memcpy(fmt[n].chunk->s, buf.mem, buf.len+1); - break; - case TVAR: - r = parse(&pos, &buf, &spec, &conversion); + t = nexttok(&pos, &buf); + } + if (t == TVAR) { + r = parse(&pos, &fmt[n], NULL, NULL, NULL); if (r < 0) goto err; - fmt[n].var = calloc(1, sizeof(struct var)+buf.len+1); - if (!fmt[n].var) - goto err_errno; - fmt[n].common->type = TVAR; - fmt[n].var->spec = spec; - memcpy(fmt[n].var->s, buf.mem, buf.len+1); - if (conversion.type) { - fmt[n].var->spec.conversion = calloc(1, sizeof(struct conversion)); - if (!fmt[n].var->spec.conversion) - goto err_errno; - *fmt[n].var->spec.conversion = conversion; - } - break; } + fprintf(stderr, "fmt: prefix='%s' var='%s'\n", fmt[n].prefix, fmt[n].var); n++; } out: @@ -461,14 +450,12 @@ xbps_fmt_free(struct xbps_fmt *fmt) { if (!fmt) return; - for (struct xbps_fmt *f = fmt; f->common; f++) - switch (f->common->type) { - case TTEXT: free(f->chunk); break; - case TVAR: - free(f->var->spec.conversion); - free(f->var); - break; - } + for (struct xbps_fmt *f = fmt; f->prefix || f->var; f++) { + free(f->prefix); + free(f->var); + free(f->spec); + free(f->conv); + } free(fmt); } @@ -480,23 +467,25 @@ xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp) int r = 0; for (;;) { - struct xbps_fmt_spec spec = {0}; - struct conversion conversion = {0}; - r = nexttok(&pos, &buf); - if (r <= 0) + enum tok t; + + t = nexttok(&pos, &buf); + if (t == 0) goto out; - switch (r) { - case TTEXT: + if (t == TTEXT) { fprintf(fp, "%s", buf.mem); - break; - case TVAR: - r = parse(&pos, &buf, &spec, &conversion); + t = nexttok(&pos, &buf); + } + if (t == TVAR) { + struct xbps_fmt_spec spec = {0}; + struct xbps_fmt_conv conv = {0}; + struct xbps_fmt fmt = { .var = buf.mem }; + r = parse(&pos, &fmt, &buf, &conv, &spec); if (r < 0) goto out; - r = cb(fp, &spec, buf.mem, data); + r = cb(fp, &fmt, data); if (r != 0) goto out; - break; } } out: @@ -508,16 +497,13 @@ int xbps_fmt(const struct xbps_fmt *fmt, xbps_fmt_cb *cb, void *data, FILE *fp) { int r; - for (const struct xbps_fmt *f = fmt; f->common; f++) { - switch (f->common->type) { - case TTEXT: - fprintf(fp, "%s", f->chunk->s); - break; - case TVAR: - r = cb(fp, &f->var->spec, f->var->s, data); + for (const struct xbps_fmt *f = fmt; f->prefix || f->var; f++) { + if (f->prefix) + fprintf(fp, "%s", f->prefix); + if (f->var) { + r = cb(fp, f, data); if (r != 0) return r; - break; } } return 0; @@ -528,16 +514,17 @@ struct fmt_dict_cb { }; int -xbps_fmt_string(const struct xbps_fmt_spec *spec, const char *str, size_t len, FILE *fp) +xbps_fmt_print_string(const struct xbps_fmt *fmt, const char *str, size_t len, FILE *fp) { + const struct xbps_fmt_spec *spec = fmt->spec; if (len == 0) len = strlen(str); - if (spec->align == '>' && spec->width > (unsigned)len) { + if (spec && spec->align == '>' && spec->width > (unsigned)len) { for (unsigned i = 0; i < spec->width - len; i++) fputc(spec->fill, fp); } fprintf(fp, "%.*s", (int)len, str); - if (spec->align == '<' && spec->width > (unsigned)len) { + if (spec && spec->align == '<' && spec->width > (unsigned)len) { for (unsigned i = 0; i < spec->width - len; i++) fputc(spec->fill, fp); } @@ -545,10 +532,9 @@ xbps_fmt_string(const struct xbps_fmt_spec *spec, const char *str, size_t len, F } static int -humanize(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) +humanize(const struct humanize *h, const struct xbps_fmt *fmt, int64_t d, FILE *fp) { char buf[64]; - const struct humanize *h = &spec->conversion->humanize; int scale = 0; int width = h->width ? h->width : 8; int len; @@ -567,41 +553,45 @@ humanize(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) len = humanize_number(buf, width, d, "B", scale, h->flags); if (len == -1) return -EINVAL; - return xbps_fmt_string(spec, buf, len, fp); + return xbps_fmt_print_string(fmt, buf, len, fp); } static int -tostrmode(const struct xbps_fmt_spec *spec UNUSED, int64_t d UNUSED, FILE *fp UNUSED) +tostrmode(const struct xbps_fmt *fmt UNUSED, int64_t d UNUSED, FILE *fp UNUSED) { return -ENOTSUP; } int -xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) +xbps_fmt_print_number(const struct xbps_fmt *fmt, int64_t d, FILE *fp) { char buf[64]; - struct xbps_fmt_spec strspec = *spec; + struct xbps_fmt_spec strspec = {0}; + struct xbps_fmt strfmt = { .spec = &strspec }; + struct xbps_fmt_spec *spec = fmt->spec; const char *p = buf; int len; - if (spec->conversion) { - switch (spec->conversion->type) { - case HUMANIZE: return humanize(spec, d, fp); - case STRMODE: return tostrmode(spec, d, fp); + if (fmt->conv) { + switch (fmt->conv->type) { + case HUMANIZE: return humanize(&fmt->conv->humanize, fmt, d, fp); + case STRMODE: return tostrmode(fmt, d, fp); } } + if (spec) { + strspec = *spec; + if (spec->align == '=') + strspec.align = '>'; + } - if (strspec.align == '=') - strspec.align = '>'; - - switch (spec->type) { + switch (spec ? spec->type : '\0') { default: /* fallthrough */ case 'd': - if (spec->sign == '+') + if (spec && spec->sign == '+') len = snprintf(buf, sizeof(buf), "%+" PRId64, d); else len = snprintf(buf, sizeof(buf), "%" PRId64, d); - if (spec->align == '=' && (buf[0] == '+' || buf[0] == '-')) { + if (spec && spec->align == '=' && (buf[0] == '+' || buf[0] == '-')) { len--, p++; strspec.width -= 1; fputc(buf[0], fp); @@ -612,27 +602,36 @@ xbps_fmt_number(const struct xbps_fmt_spec *spec, int64_t d, FILE *fp) case 'x': len = snprintf(buf, sizeof(buf), "%" PRIx64, d); break; case 'X': len = snprintf(buf, sizeof(buf), "%" PRIX64, d); break; } - return xbps_fmt_string(&strspec, p, len, fp); + return xbps_fmt_print_string(&strfmt, p, len, fp); } -static int -fmt_dict_cb(FILE *fp, const struct xbps_fmt_spec *spec, const char *var, void *data) +int +xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *fp) { - struct fmt_dict_cb *ctx = data; - xbps_object_t val = xbps_dictionary_get(ctx->dict, var); - - switch (xbps_object_type(val)) { - case XBPS_TYPE_STRING: - return xbps_fmt_string(spec, xbps_string_cstring_nocopy(val), - xbps_string_size(val), fp); + switch (xbps_object_type(obj)) { + case XBPS_TYPE_BOOL: + return xbps_fmt_print_string(fmt, xbps_bool_true(obj) ? "true" : "false", 0, fp); case XBPS_TYPE_NUMBER: - return xbps_fmt_number(spec, xbps_number_integer_value(val), fp); + return xbps_fmt_print_number(fmt, xbps_number_integer_value(obj), fp); + case XBPS_TYPE_STRING: + return xbps_fmt_print_string(fmt, xbps_string_cstring_nocopy(obj), + xbps_string_size(obj), fp); + case XBPS_TYPE_UNKNOWN: + return xbps_fmt_print_string(fmt, "(null)", 0, fp); default: break; } return 0; } +static int +fmt_dict_cb(FILE *fp, const struct xbps_fmt *fmt, void *data) +{ + struct fmt_dict_cb *ctx = data; + xbps_object_t obj = xbps_dictionary_get(ctx->dict, fmt->var); + return xbps_fmt_print_object(fmt, obj, fp); +} + int xbps_fmt_dictionary(const struct xbps_fmt *fmt, xbps_dictionary_t dict, FILE *fp) { diff --git a/tests/xbps/libxbps/fmt/main.c b/tests/xbps/libxbps/fmt/main.c index 38b98c723..381f2e911 100644 --- a/tests/xbps/libxbps/fmt/main.c +++ b/tests/xbps/libxbps/fmt/main.c @@ -35,14 +35,14 @@ #include #include -ATF_TC(xbps_fmt_number); +ATF_TC(xbps_fmt_print_number); -ATF_TC_HEAD(xbps_fmt_number, tc) +ATF_TC_HEAD(xbps_fmt_print_number, tc) { - atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_number"); + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_print_number"); } -ATF_TC_BODY(xbps_fmt_number, tc) +ATF_TC_BODY(xbps_fmt_print_number, tc) { char *buf = NULL; size_t bufsz = 0; @@ -71,9 +71,10 @@ ATF_TC_BODY(xbps_fmt_number, tc) ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { + struct xbps_fmt fmt = { .spec = &tests[i].spec }; memset(buf, '\0', bufsz); rewind(fp); - xbps_fmt_number(&tests[i].spec, tests[i].d, fp); + xbps_fmt_print_number(&fmt, tests[i].d, fp); ATF_REQUIRE(fflush(fp) == 0); ATF_CHECK_STREQ(buf, tests[i].expect); } @@ -81,14 +82,14 @@ ATF_TC_BODY(xbps_fmt_number, tc) free(buf); } -ATF_TC(xbps_fmt_string); +ATF_TC(xbps_fmt_print_string); -ATF_TC_HEAD(xbps_fmt_string, tc) +ATF_TC_HEAD(xbps_fmt_print_string, tc) { - atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_string"); + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_print_string"); } -ATF_TC_BODY(xbps_fmt_string, tc) +ATF_TC_BODY(xbps_fmt_print_string, tc) { char *buf = NULL; size_t bufsz = 0; @@ -108,9 +109,10 @@ ATF_TC_BODY(xbps_fmt_string, tc) ATF_REQUIRE(fp = open_memstream(&buf, &bufsz)); for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { + struct xbps_fmt fmt = { .spec = &tests[i].spec }; memset(buf, '\0', bufsz); rewind(fp); - xbps_fmt_string(&tests[i].spec, tests[i].input, tests[i].len, fp); + xbps_fmt_print_string(&fmt, tests[i].input, tests[i].len, fp); ATF_REQUIRE(fflush(fp) == 0); ATF_CHECK_STREQ(buf, tests[i].expect); } @@ -136,10 +138,10 @@ ATF_TC_BODY(xbps_fmt_dictionary, tc) ATF_REQUIRE(dict = xbps_dictionary_create()); ATF_REQUIRE(xbps_dictionary_set_cstring_nocopy(dict, "string", "s")); ATF_REQUIRE(xbps_dictionary_set_int64(dict, "number", 1)); - ATF_REQUIRE(fmt = xbps_fmt_parse(">{string} {number}<")); + ATF_REQUIRE(fmt = xbps_fmt_parse(">{string} {number} {number!humanize}<")); ATF_REQUIRE(xbps_fmt_dictionary(fmt, dict, fp) == 0); ATF_REQUIRE(fflush(fp) == 0); - ATF_CHECK_STREQ(buf, ">s 1<"); + ATF_CHECK_STREQ(buf, ">s 1 0KB<"); ATF_REQUIRE(fclose(fp) == 0); free(buf); xbps_object_release(dict); @@ -162,9 +164,9 @@ ATF_TC_BODY(xbps_fmts_dictionary, tc) ATF_REQUIRE(dict = xbps_dictionary_create()); ATF_REQUIRE(xbps_dictionary_set_cstring_nocopy(dict, "string", "s")); ATF_REQUIRE(xbps_dictionary_set_int64(dict, "number", 1)); - ATF_REQUIRE(xbps_fmts_dictionary(">{string} {number}<", dict, fp) == 0); + ATF_REQUIRE(xbps_fmts_dictionary(">{string} {number} {number!humanize}<", dict, fp) == 0); ATF_REQUIRE(fflush(fp) == 0); - ATF_CHECK_STREQ(buf, ">s 1<"); + ATF_CHECK_STREQ(buf, ">s 1 0KB<"); ATF_REQUIRE(fclose(fp) == 0); free(buf); xbps_object_release(dict); @@ -172,8 +174,8 @@ ATF_TC_BODY(xbps_fmts_dictionary, tc) ATF_TP_ADD_TCS(tp) { - ATF_TP_ADD_TC(tp, xbps_fmt_number); - ATF_TP_ADD_TC(tp, xbps_fmt_string); + ATF_TP_ADD_TC(tp, xbps_fmt_print_number); + ATF_TP_ADD_TC(tp, xbps_fmt_print_string); ATF_TP_ADD_TC(tp, xbps_fmt_dictionary); ATF_TP_ADD_TC(tp, xbps_fmts_dictionary); return atf_no_error(); From 6d46066ec390f74757a957c7a3d3bb865681f35b Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 14 Mar 2023 18:17:47 +0100 Subject: [PATCH 13/21] lib/format.c: add optional default to format string variables --- bin/xbps-query/xbps-query.1 | 15 +-- include/xbps.h.in | 26 +++++- lib/format.c | 167 ++++++++++++++++++++++++++++++---- tests/xbps/libxbps/fmt/main.c | 4 +- 4 files changed, 184 insertions(+), 28 deletions(-) diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index 9aa6a8cc1..3db83d686 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -301,14 +301,17 @@ xbps 484 KB .Pp Format strings are parsed by the following EBNF: .Bd -literal - ::= (text | escape | substitution)* - ::= [^\\{}]+ -- literal text chunk - ::= "\\" [abfnrtv0] -- POSIX-like espace sequence - | "\\{" | "\\}" -- escaped "{" and "}" + ::= (prefix | "\\" (escape|[{}]) | substitution)* + ::= [^\\{}]+ -- Literal text chunk. + ::= [abfnrtv0] -- POSIX-like espace character. - ::= "{" variable ["!" conversion] [":" format] "}" + ::= "{" variable ["?" default] ["!" conversion] [":" format] "}" ::= [a-zA-Z0-9_-] + ::= ([-]?[0-9]+) -- default number. + | "true" | "false" -- default boolean. + | ('"' (("\\" (escape|'"')) | [^"])* '"') -- default string. + ::= humanize | strmode -- Convert inode status information into a symbolic string @@ -329,7 +332,7 @@ Format strings are parsed by the following EBNF: | "T" -- tera | "P" -- peta | "E" -- exa - ::= "i" -- Use IEE/IEC (and now also SI) power of two prefixes. + ::= "i" -- Use IEEE/IEC (and now also SI) power of two prefixes. ::= [[fill] align] [sign] [width] ["." precision] [type] ::= -- The character to use when aligning the output. diff --git a/include/xbps.h.in b/include/xbps.h.in index 059673fc3..42d28138d 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -2370,7 +2370,7 @@ xbps_plist_dictionary_from_file(const char *path); /** * @struct xbps_fmt xbps.h "xbps.h" - * @brief Structure of parsed format string. + * @brief Structure of parsed format string variable. */ struct xbps_fmt { /** @@ -2384,6 +2384,11 @@ struct xbps_fmt { * @brief Variable name. */ char *var; + /** + * @var def + * @brief Default value. + */ + struct xbps_fmt_def *def; /** * @var conv * @brief Format conversion. @@ -2397,7 +2402,24 @@ struct xbps_fmt { }; /** - * @struct xbps_fmt xbps.h "xbps.h" + * @struct xbps_fmt_def xbps.h "xbps.h" + * @brief Structure of parsed format specifier. + */ +struct xbps_fmt_def { + enum { + XBPS_FMT_DEF_STR = 1, + XBPS_FMT_DEF_NUM, + XBPS_FMT_DEF_BOOL, + } type; + union { + char *str; + int64_t num; + bool boolean; + } val; +}; + +/** + * @struct xbps_fmt_spec xbps.h "xbps.h" * @brief Structure of parsed format specifier. */ struct xbps_fmt_spec { diff --git a/lib/format.c b/lib/format.c index dc34be670..4cea7e0a4 100644 --- a/lib/format.c +++ b/lib/format.c @@ -159,19 +159,6 @@ nexttok(const char **pos, struct strbuf *buf) return 0; } -struct xbps_fmt_conv { - enum { HUMANIZE = 1, STRMODE } type; - union { - struct humanize { - unsigned width : 8; - unsigned minscale : 8; - unsigned maxscale : 8; - bool decimal : 1; - int flags; - } humanize; - }; -}; - static int parse_u(const char **pos, unsigned int *u) { @@ -188,6 +175,129 @@ parse_u(const char **pos, unsigned int *u) return 0; } +static int +parse_d(const char **pos, int64_t *d) +{ + char *e = NULL; + long v; + errno = 0; + v = strtol(*pos, &e, 10); + if (errno != 0) + return -errno; + if (v > UINT_MAX) + return -ERANGE; + *d = v; + *pos = e; + return 0; +} + +static int +parse_default(const char **pos, struct xbps_fmt *fmt, struct strbuf *buf, + struct xbps_fmt_def *def_storage) +{ + struct strbuf buf2 = {0}; + struct xbps_fmt_def *def; + const char *p = *pos; + char *str = NULL; + int r; + + if (*p++ != '?') + return 0; + if (!def_storage) { + fmt->def = def = calloc(1, sizeof(*def)); + if (!def) + return -errno; + } else { + fmt->def = def = def_storage; + } + + if ((*p >= '0' && *p <= '9') || *p == '-') { + r = parse_d(&p, &def->val.num); + if (r < 0) + return r; + def->type = XBPS_FMT_DEF_NUM; + *pos = p; + return 0; + } else if (strncmp(p, "true", sizeof("true") - 1) == 0) { + *pos = p + sizeof("true") - 1; + def->type = XBPS_FMT_DEF_BOOL; + def->val.boolean = true; + return 0; + } else if (strncmp(p, "false", sizeof("false") - 1) == 0) { + *pos = p + sizeof("false") - 1; + def->type = XBPS_FMT_DEF_BOOL; + def->val.boolean = false; + return 0; + } + + if (*p++ != '"') + return -EINVAL; + + if (!buf) { + buf = &buf2; + } else { + r = strbuf_putc(buf, '\0'); + if (r < 0) + return r; + str = buf->mem + buf->len; + } + for (; *p && *p != '"'; p++) { + switch (*p) { + case '\\': + switch (*++p) { + case '\\': r = strbuf_putc(buf, '\\'); break; + case 'a': r = strbuf_putc(buf, '\a'); break; + case 'b': r = strbuf_putc(buf, '\b'); break; + case 'f': r = strbuf_putc(buf, '\f'); break; + case 'n': r = strbuf_putc(buf, '\n'); break; + case 'r': r = strbuf_putc(buf, '\r'); break; + case 't': r = strbuf_putc(buf, '\t'); break; + case '0': r = strbuf_putc(buf, '\0'); break; + case '"': r = strbuf_putc(buf, '"'); break; + default: r = -EINVAL; + } + break; + default: + r = strbuf_putc(buf, *p); + } + if (r < 0) + goto err; + } + if (*p++ != '"') { + r = -EINVAL; + goto err; + } + *pos = p; + def->type = XBPS_FMT_DEF_STR; + if (buf == &buf2) { + def->val.str = strdup(buf2.mem); + if (!def->val.str) { + r = -errno; + goto err; + } + strbuf_release(&buf2); + } else { + def->val.str = str; + } + return 0; +err: + strbuf_release(&buf2); + return r; +} + +struct xbps_fmt_conv { + enum { HUMANIZE = 1, STRMODE } type; + union { + struct humanize { + unsigned width : 8; + unsigned minscale : 8; + unsigned maxscale : 8; + bool decimal : 1; + int flags; + } humanize; + }; +}; + static int parse_humanize(const char **pos, struct humanize *humanize) { @@ -346,6 +456,7 @@ parse_spec(const char **pos, struct xbps_fmt *fmt, struct xbps_fmt_spec *spec_st static int parse(const char **pos, struct xbps_fmt *fmt, struct strbuf *buf, + struct xbps_fmt_def *def_storage, struct xbps_fmt_conv *conv_storage, struct xbps_fmt_spec *spec_storage) { @@ -357,7 +468,7 @@ parse(const char **pos, struct xbps_fmt *fmt, return -EINVAL; p++; - /* var ::= '{' name [conversion][format_spec] '}' */ + /* var ::= '{' name [default][conversion][format_spec] '}' */ /* name ::= [a-zA-Z0-9_-]+ */ for (e = p; (*e >= 'a' && *e <= 'z') || @@ -380,6 +491,11 @@ parse(const char **pos, struct xbps_fmt *fmt, } p = e; + /* default ::= ['?' ...] */ + r = parse_default(&p, fmt, buf, def_storage); + if (r < 0) + return r; + /* conversion ::= ['!' ...] */ r = parse_conversion(&p, fmt, conv_storage); if (r < 0) @@ -426,7 +542,7 @@ xbps_fmt_parse(const char *format) t = nexttok(&pos, &buf); } if (t == TVAR) { - r = parse(&pos, &fmt[n], NULL, NULL, NULL); + r = parse(&pos, &fmt[n], NULL, NULL, NULL, NULL); if (r < 0) goto err; } @@ -453,6 +569,9 @@ xbps_fmt_free(struct xbps_fmt *fmt) for (struct xbps_fmt *f = fmt; f->prefix || f->var; f++) { free(f->prefix); free(f->var); + if (f->def && f->def->type == XBPS_FMT_DEF_STR) + free(f->def->val.str); + free(f->def); free(f->spec); free(f->conv); } @@ -477,10 +596,11 @@ xbps_fmts(const char *format, xbps_fmt_cb *cb, void *data, FILE *fp) t = nexttok(&pos, &buf); } if (t == TVAR) { - struct xbps_fmt_spec spec = {0}; + struct xbps_fmt_def def = {0}; struct xbps_fmt_conv conv = {0}; + struct xbps_fmt_spec spec = {0}; struct xbps_fmt fmt = { .var = buf.mem }; - r = parse(&pos, &fmt, &buf, &conv, &spec); + r = parse(&pos, &fmt, &buf, &def, &conv, &spec); if (r < 0) goto out; r = cb(fp, &fmt, data); @@ -617,7 +737,18 @@ xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *fp) return xbps_fmt_print_string(fmt, xbps_string_cstring_nocopy(obj), xbps_string_size(obj), fp); case XBPS_TYPE_UNKNOWN: - return xbps_fmt_print_string(fmt, "(null)", 0, fp); + if (fmt->def) { + struct xbps_fmt_def *def = fmt->def; + switch (fmt->def->type) { + case XBPS_FMT_DEF_BOOL: + return xbps_fmt_print_string(fmt, def->val.boolean ? + "true" : "false", 0, fp); + case XBPS_FMT_DEF_STR: + return xbps_fmt_print_string(fmt, def->val.str, 0, fp); + case XBPS_FMT_DEF_NUM: + return xbps_fmt_print_number(fmt, def->val.num, fp); + } + } default: break; } diff --git a/tests/xbps/libxbps/fmt/main.c b/tests/xbps/libxbps/fmt/main.c index 381f2e911..40298a98e 100644 --- a/tests/xbps/libxbps/fmt/main.c +++ b/tests/xbps/libxbps/fmt/main.c @@ -138,10 +138,10 @@ ATF_TC_BODY(xbps_fmt_dictionary, tc) ATF_REQUIRE(dict = xbps_dictionary_create()); ATF_REQUIRE(xbps_dictionary_set_cstring_nocopy(dict, "string", "s")); ATF_REQUIRE(xbps_dictionary_set_int64(dict, "number", 1)); - ATF_REQUIRE(fmt = xbps_fmt_parse(">{string} {number} {number!humanize}<")); + ATF_REQUIRE(fmt = xbps_fmt_parse(">{string} {number} {number!humanize} {foo?\"bar\"} {n?1000!humanize}<")); ATF_REQUIRE(xbps_fmt_dictionary(fmt, dict, fp) == 0); ATF_REQUIRE(fflush(fp) == 0); - ATF_CHECK_STREQ(buf, ">s 1 0KB<"); + ATF_CHECK_STREQ(buf, ">s 1 0KB bar 1KB<"); ATF_REQUIRE(fclose(fp) == 0); free(buf); xbps_object_release(dict); From 02f7bcd7e02ab06d21492bb4f0e3dac1a135182f Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 14 Mar 2023 18:49:27 +0100 Subject: [PATCH 14/21] lib/format.c: simplify escape characters --- lib/format.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/lib/format.c b/lib/format.c index 4cea7e0a4..462fa28c3 100644 --- a/lib/format.c +++ b/lib/format.c @@ -133,17 +133,12 @@ nexttok(const char **pos, struct strbuf *buf) case 'r': r = strbuf_putc(buf, '\r'); break; case 't': r = strbuf_putc(buf, '\t'); break; case '0': r = strbuf_putc(buf, '\0'); break; - case '{': r = strbuf_putc(buf, '{'); break; - case '}': r = strbuf_putc(buf, '}'); break; - default: - r = strbuf_putc(buf, '\\'); - if (r < 0) - break; - r = strbuf_putc(buf, *p); + default: r = *p ? strbuf_putc(buf, *p) : 0; } if (r < 0) return r; - p++; + if (*p) + p++; break; default: r = strbuf_putc(buf, *p++); @@ -241,9 +236,8 @@ parse_default(const char **pos, struct xbps_fmt *fmt, struct strbuf *buf, return r; str = buf->mem + buf->len; } - for (; *p && *p != '"'; p++) { - switch (*p) { - case '\\': + for (; *p && *p != '"';) { + if (*p == '\\') { switch (*++p) { case '\\': r = strbuf_putc(buf, '\\'); break; case 'a': r = strbuf_putc(buf, '\a'); break; @@ -253,15 +247,15 @@ parse_default(const char **pos, struct xbps_fmt *fmt, struct strbuf *buf, case 'r': r = strbuf_putc(buf, '\r'); break; case 't': r = strbuf_putc(buf, '\t'); break; case '0': r = strbuf_putc(buf, '\0'); break; - case '"': r = strbuf_putc(buf, '"'); break; - default: r = -EINVAL; + default: r = *p ? strbuf_putc(buf, *p) : 0; } - break; - default: + } else { r = strbuf_putc(buf, *p); } if (r < 0) goto err; + if (*p) + p++; } if (*p++ != '"') { r = -EINVAL; From bc43563ad7bde173662a7998f5159215b9bab114 Mon Sep 17 00:00:00 2001 From: classabbyamp Date: Tue, 14 Mar 2023 22:12:14 -0400 Subject: [PATCH 15/21] lib/format: add \e escape, fix typos in format spec, remove dbg print --- bin/xbps-query/xbps-query.1 | 10 +++++----- lib/format.c | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index 3db83d686..16fcfb6a0 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -303,13 +303,13 @@ Format strings are parsed by the following EBNF: .Bd -literal ::= (prefix | "\\" (escape|[{}]) | substitution)* ::= [^\\{}]+ -- Literal text chunk. - ::= [abfnrtv0] -- POSIX-like espace character. + ::= [abefnrtv0] -- POSIX-like escape character. ::= "{" variable ["?" default] ["!" conversion] [":" format] "}" ::= [a-zA-Z0-9_-] - ::= ([-]?[0-9]+) -- default number. - | "true" | "false" -- default boolean. + ::= ([-]?[0-9]+) -- default number. + | "true" | "false" -- default boolean. | ('"' (("\\" (escape|'"')) | [^"])* '"') -- default string. ::= humanize | strmode @@ -324,7 +324,7 @@ Format strings are parsed by the following EBNF: it using one digit. ::= [0-9]+ -- Width of the output. ::= multiplier -- Minimum scale multiplier and optionally - [multiplier] -- Maxium scale multiplier. + [multiplier] -- Maximum scale multiplier. ::= "B" -- byte | "K" -- kilo | "M" -- mega @@ -342,7 +342,7 @@ Format strings are parsed by the following EBNF: ::= "+" -- Add sign to positive and negative numbers. | "-" -- Add sign to negative numbers. ::= [0-9]+ -- The alignment width. - ::= [0-9]+ -- Percision for numbers. + ::= [0-9]+ -- Precision for numbers. ::= "d" -- Decimal number. | "o" -- Octal number. | "u" -- Unsigned number. diff --git a/lib/format.c b/lib/format.c index 462fa28c3..5a885e138 100644 --- a/lib/format.c +++ b/lib/format.c @@ -128,6 +128,7 @@ nexttok(const char **pos, struct strbuf *buf) case '\\': r = strbuf_putc(buf, '\\'); break; case 'a': r = strbuf_putc(buf, '\a'); break; case 'b': r = strbuf_putc(buf, '\b'); break; + case 'e': r = strbuf_putc(buf, '\e'); break; case 'f': r = strbuf_putc(buf, '\f'); break; case 'n': r = strbuf_putc(buf, '\n'); break; case 'r': r = strbuf_putc(buf, '\r'); break; @@ -540,7 +541,6 @@ xbps_fmt_parse(const char *format) if (r < 0) goto err; } - fprintf(stderr, "fmt: prefix='%s' var='%s'\n", fmt[n].prefix, fmt[n].var); n++; } out: From e95dbf8283343a25bb16e0f381c9f517ce60530e Mon Sep 17 00:00:00 2001 From: classabbyamp Date: Mon, 20 Feb 2023 17:58:22 -0500 Subject: [PATCH 16/21] lib/compat: add strmode() and xbps_strmode() for printing file permissions and type --- configure | 22 +++++++++++++ include/compat.h | 4 +++ include/xbps.h.in | 8 +++++ lib/compat/strmode.c | 76 ++++++++++++++++++++++++++++++++++++++++++++ lib/util.c | 8 +++++ 5 files changed, 118 insertions(+) create mode 100644 lib/compat/strmode.c diff --git a/configure b/configure index 303c90a10..0c10dbf1c 100755 --- a/configure +++ b/configure @@ -549,6 +549,28 @@ else fi rm -f _$func.c _$func +# +# Check for strmode(). +func=strmode +printf "Checking for $func() ... " +cat < _$func.c +#include + +int main(void) { + const char dest[] = ""; + strmode(0104644, dest); + return 0; +} +EOF +if $XCC _$func.c -o _$func 2>/dev/null; then + echo yes. + echo "CPPFLAGS+= -DHAVE_STRMODE" >>$CONFIG_MK +else + echo no. + echo "COMPAT_OBJS+= compat/strmode.o" >>$CONFIG_MK +fi +rm -f _$func.c _$func + # # Check for rbtree_ininit(). # diff --git a/include/compat.h b/include/compat.h index f33731ab0..4035a9b79 100644 --- a/include/compat.h +++ b/include/compat.h @@ -43,4 +43,8 @@ int HIDDEN humanize_number(char *_buf, size_t _len, int64_t _number, const char *_suffix, int _scale, int _flags); #endif +#ifndef HAVE_STRMODE +void HIDDEN strmode(mode_t, char *); +#endif + #endif /* COMPAT_H */ diff --git a/include/xbps.h.in b/include/xbps.h.in index 42d28138d..e3565b8ac 100644 --- a/include/xbps.h.in +++ b/include/xbps.h.in @@ -2213,6 +2213,14 @@ size_t xbps_strlcat(char *dst, const char *src, size_t dstsize); */ size_t xbps_strlcpy(char *dst, const char *src, size_t dstsize); +/** + * Convert a \a mode from stat to a string in \a buf. + * + * @param[in] mode Mode to convert. + * @param[out] buf Buffer to store the resulting string. + */ +void xbps_strmode(mode_t mode, char *buf); + /** * Tests if pkgver is reverted by pkg * diff --git a/lib/compat/strmode.c b/lib/compat/strmode.c new file mode 100644 index 000000000..1708379a2 --- /dev/null +++ b/lib/compat/strmode.c @@ -0,0 +1,76 @@ +/*- + * Copyright (c) 2023 classabbyamp. + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 +#include + +#include "compat.h" + +void HIDDEN +strmode(mode_t mode, char *buf) +{ + switch (mode & S_IFMT) { + /* many of these are not currently packageable, but why not keep them for future compat? */ + case S_IFSOCK: *buf++ = 's'; break; + case S_IFLNK: *buf++ = 'l'; break; + case S_IFREG: *buf++ = '-'; break; + case S_IFBLK: *buf++ = 'b'; break; + case S_IFDIR: *buf++ = 'd'; break; + case S_IFCHR: *buf++ = 'c'; break; + case S_IFIFO: *buf++ = 'p'; break; +#ifdef S_IFWHT + case S_IFWHT: *buf++ = 'w'; break; +#endif + default: *buf++ = '?'; break; + } + *buf++ = (mode & S_IRUSR) ? 'r' : '-'; + *buf++ = (mode & S_IWUSR) ? 'w' : '-'; + switch (mode & (S_IXUSR | S_ISUID)) { + case (S_IXUSR | S_ISUID): *buf++ = 's'; break; + case S_ISUID: *buf++ = 'S'; break; + case S_IXUSR: *buf++ = 'x'; break; + default: *buf++ = '-'; break; + } + + *buf++ = (mode & S_IRGRP) ? 'r' : '-'; + *buf++ = (mode & S_IWGRP) ? 'w' : '-'; + switch (mode & (S_IXGRP | S_ISGID)) { + case S_IXGRP | S_ISGID: *buf++ = 's'; break; + case S_ISUID: *buf++ = 'S'; break; + case S_IXGRP: *buf++ = 'x'; break; + default: *buf++ = '-'; break; + } + + *buf++ = (mode & S_IROTH) ? 'r' : '-'; + *buf++ = (mode & S_IWOTH) ? 'w' : '-'; + switch (mode & (S_IXOTH | S_ISVTX)) { + case S_IXOTH | S_ISVTX: *buf++ = 't'; break; + case S_ISVTX: *buf++ = 'T'; break; + case S_IXOTH: *buf++ = 'x'; break; + default: *buf++ = '-'; break; + } + + *buf = '\0'; +} diff --git a/lib/util.c b/lib/util.c index c2209cbc6..7b621e90b 100644 --- a/lib/util.c +++ b/lib/util.c @@ -532,6 +532,14 @@ xbps_strlcpy(char *dest, const char *src, size_t siz) return strlcpy(dest, src, siz); } +void +xbps_strmode(mode_t mode, char *buf) +{ + assert(buf); + + return strmode(mode, buf); +} + /* * Check if pkg is explicitly marked to replace a specific installed version. */ From 7bc4884a6b2c1381ec547af44aef9720705e21e9 Mon Sep 17 00:00:00 2001 From: classabbyamp Date: Tue, 14 Mar 2023 03:49:48 -0400 Subject: [PATCH 17/21] lib/format.c: implement tostrmode() --- lib/format.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/format.c b/lib/format.c index 5a885e138..7fe1859eb 100644 --- a/lib/format.c +++ b/lib/format.c @@ -671,9 +671,13 @@ humanize(const struct humanize *h, const struct xbps_fmt *fmt, int64_t d, FILE * } static int -tostrmode(const struct xbps_fmt *fmt UNUSED, int64_t d UNUSED, FILE *fp UNUSED) +tostrmode(const struct xbps_fmt *fmt, int64_t d, FILE *fp) { - return -ENOTSUP; + char buf[64] = ""; + int len; + xbps_strmode(d, buf); + len = strlen(buf); + return xbps_fmt_print_string(fmt, buf, len, fp); } int From 75fdf6fb5d50de490603a66e4596390ea32c4714 Mon Sep 17 00:00:00 2001 From: classabbyamp Date: Sun, 19 Feb 2023 21:39:54 -0500 Subject: [PATCH 18/21] bin/xbps-create: record file mode, owner, and group also add a couple missing things to the xbps_create completions --- bin/xbps-create/main.c | 90 +++++++++++++++++++++++++++++++++-- bin/xbps-create/xbps-create.1 | 16 +++++++ data/_xbps | 4 +- 3 files changed, 105 insertions(+), 5 deletions(-) diff --git a/bin/xbps-create/main.c b/bin/xbps-create/main.c index a9a902ba0..45d322389 100644 --- a/bin/xbps-create/main.c +++ b/bin/xbps-create/main.c @@ -22,6 +22,9 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +#define _DEFAULT_SOURCE + #include #include #include @@ -41,6 +44,7 @@ #include #include #include +#include #include #include "queue.h" @@ -66,6 +70,7 @@ struct xentry { char *file, *target; char sha256[XBPS_SHA256_SIZE]; ino_t inode; + mode_t mode; }; static TAILQ_HEAD(xentry_head, xentry) xentry_list = @@ -109,6 +114,9 @@ usage(bool fail) " 'vi:/usr/bin/vi:/usr/bin/vim foo:/usr/bin/foo:/usr/bin/blah'\n" " --build-options A string with the used build options\n" " --compression Compression format: none, gzip, bzip2, lz4, xz, zstd (default)\n" + " --chown Set the ownership of files and directories.\n" + " This expects a blank-separated list of ::,\n" + " where '' is an fnmatch(3) pattern.\n" " --shlib-provides List of provided shared libraries (blank separated list,\n" " e.g 'libfoo.so.1 libblah.so.2')\n" " --shlib-requires List of required shared libraries (blank separated list,\n" @@ -245,7 +253,6 @@ process_one_alternative(const char *altgrname, const char *val) } } - static void process_dict_of_arrays(const char *key UNUSED, const char *val) { @@ -281,6 +288,71 @@ process_dict_of_arrays(const char *key UNUSED, const char *val) free(args); } +static bool +process_chown_pattern(const char *key, const char *fpat, const char *user, const char *group) +{ + xbps_object_iterator_t iter; + xbps_object_t obj; + const char *fname; + bool match = false; + + if ((iter = xbps_array_iter_from_dict(pkg_filesd, key)) != NULL) { + while ((obj = xbps_object_iterator_next(iter))) { + xbps_dictionary_get_cstring_nocopy(obj, "file", &fname); + if (fnmatch(fpat, fname, 0) == 0) { + match = true; + if (user != NULL) + xbps_dictionary_set_cstring(obj, "user", user); + if (group != NULL) + xbps_dictionary_set_cstring(obj, "group", group); + } + } + } + return match; +} + +static void +process_chown_patterns(const char *val) +{ + char *raw, *itm, *fpat, *user, *group; + + if (val == NULL) + return; + + raw = strdup(val); + + while ((itm = strsep(&raw, " "))) { + fpat = strsep(&itm, ":"); + if (fpat == NULL || strlen(fpat) == 0) { + xbps_warn_printf("%s: skipping chown pattern `:%s': empty pattern\n", _PROGNAME, itm); + continue; + } + + user = strsep(&itm, ":"); + if (strlen(user) == 0 || strcmp(user, "root") == 0) + user = NULL; + group = strsep(&itm, ":"); + if (strlen(group) == 0 || strcmp(group, "root") == 0) + group = NULL; + + if (itm != NULL) + xbps_warn_printf("%s: chown pattern contains extra data: %s\n", _PROGNAME, itm); + + if (user == NULL && group == NULL) { + xbps_warn_printf("%s: skipping chown pattern `%s': user and group empty or root\n", _PROGNAME, fpat); + continue; + } + + if (!(process_chown_pattern("dirs", fpat, user, group) || + process_chown_pattern("files", fpat, user, group) || + process_chown_pattern("links", fpat, user, group))) { + xbps_warn_printf("%s: chown pattern %s matched nothing\n", _PROGNAME, fpat); + } + } + + free(raw); +} + static void process_file(const char *file, const char *key) { @@ -396,6 +468,10 @@ ftw_cb(const char *fpath, const struct stat *sb, const struct dirent *dir UNUSED goto out; } + /* symlinks don't have a mode on linux */ + if (!S_ISLNK(sb->st_mode)) + xe->mode = sb->st_mode; + if (S_ISLNK(sb->st_mode)) { char buf[PATH_MAX]; ssize_t len; @@ -530,7 +606,6 @@ ftw_cb(const char *fpath, const struct stat *sb, const struct dirent *dir UNUSED xbps_dictionary_set_uint64(fileinfo, "inode", sb->st_ino); xe->inode = sb->st_ino; xe->size = (uint64_t)sb->st_size; - } else if (S_ISDIR(sb->st_mode)) { /* directory */ xbps_dictionary_set_cstring_nocopy(fileinfo, "type", "dirs"); @@ -650,6 +725,8 @@ process_xentry(enum entry_type type, const char *mutable_files) xbps_dictionary_set_cstring(d, "sha256", xe->sha256); if (xe->size) xbps_dictionary_set_uint64(d, "size", xe->size); + if (xe->mode) + xbps_dictionary_set_uint32(d, "mode", xe->mode); xbps_array_add(a, d); xbps_object_release(d); @@ -832,6 +909,7 @@ main(int argc, char **argv) { "build-options", required_argument, NULL, '2' }, { "built-with", required_argument, NULL, 'B' }, { "changelog", required_argument, NULL, 'c'}, + { "chown", required_argument, NULL, '6'}, { "compression", required_argument, NULL, '3' }, { "config-files", required_argument, NULL, 'F' }, { "conflicts", required_argument, NULL, 'C' }, @@ -865,7 +943,7 @@ main(int argc, char **argv) const char *provides, *pkgver, *replaces, *reverts, *desc, *ldesc; const char *arch, *config_files, *mutable_files, *version, *changelog; const char *buildopts, *shlib_provides, *shlib_requires, *alternatives; - const char *compression, *tags = NULL, *srcrevs = NULL, *sourcepkg = NULL; + const char *compression, *tags = NULL, *srcrevs = NULL, *sourcepkg = NULL, *chownlst; char pkgname[XBPS_NAME_SIZE], *binpkg, *tname, *p, cwd[PATH_MAX-1]; bool quiet = false, preserve = false; int c, pkg_fd; @@ -874,7 +952,7 @@ main(int argc, char **argv) arch = conflicts = deps = homepage = license = maint = compression = NULL; provides = pkgver = replaces = reverts = desc = ldesc = bwith = NULL; buildopts = config_files = mutable_files = shlib_provides = NULL; - alternatives = shlib_requires = changelog = NULL; + alternatives = shlib_requires = changelog = chownlst = NULL; while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { if (optarg && strcmp(optarg, "") == 0) @@ -965,6 +1043,9 @@ main(int argc, char **argv) case '5': sourcepkg = optarg; break; + case '6': + chownlst = optarg; + break; case '?': default: usage(true); @@ -1081,6 +1162,7 @@ main(int argc, char **argv) die("xbps_dictionary_create"); process_destdir(mutable_files); + process_chown_patterns(chownlst); /* Back to original cwd after file tree walk processing */ if (chdir(p) == -1) diff --git a/bin/xbps-create/xbps-create.1 b/bin/xbps-create/xbps-create.1 index a94b87f33..ae195747b 100644 --- a/bin/xbps-create/xbps-create.1 +++ b/bin/xbps-create/xbps-create.1 @@ -81,6 +81,22 @@ is a relative path, the symlink will be created relative to .Em target . .It Fl -build-options Ar string A string containing the build options used in package. +.It Fl -chown Ar list +Set the ownership of package files and directories. +This expects a whitespace-separated list of +.Ar :: , +where +.Ar +is an +.Xr fnmatch 3 +pattern. +If +.Ar +or +.Ar +are empty, root is assumed. +Example: +.Ar '/usr/lib/foo/*:foo:foo /usr/bin/foo::foo' .It Fl -compression Ar none | gzip | bzip2 | xz | lz4 | zstd Set the binary package compression format. If unset, defaults to .Ar zstd . diff --git a/data/_xbps b/data/_xbps index 315875717..d513197d7 100644 --- a/data/_xbps +++ b/data/_xbps @@ -96,8 +96,10 @@ _xbps_create() { {-s,--desc}'[Short description]:short description: ' \ {-t,--tags}'[A list of tags/categories]:tags: ' \ {-V,--version}'[Prints XBPS release version]' \ + --alternatives'[List of provided alternatives]:list of provided alternatives: ' \ --build-options'[A string with the used build options]:used build options: ' \ - --compression'[Compression format]:compression format:(gzip bzip2 xz)' \ + --chown'[List of files to chown]:list of files to chown: ' \ + --compression'[Compression format]:compression format:(none gzip bzip2 xz lz4 zstd)' \ --shlib-provides'[List of provided shared libraries]:provided shared libraries: ' \ --shlib-requires'[List of required shared libraries]:required shared libraries: ' } From 5a40d219299960a6aac4ceebef5e2b6797e4bce4 Mon Sep 17 00:00:00 2001 From: classabbyamp Date: Sun, 19 Feb 2023 21:40:47 -0500 Subject: [PATCH 19/21] bin/xbps-pkgdb: check file and symlink owners & perms --- LICENSE.3RDPARTY | 26 +++++ bin/xbps-pkgdb/Makefile | 1 + bin/xbps-pkgdb/check_files.c | 52 +++++++++ bin/xbps-pkgdb/check_pkg_files.c | 122 ++++++++++++++++----- bin/xbps-pkgdb/check_pkg_symlinks.c | 53 +++++++-- bin/xbps-pkgdb/defs.h | 7 ++ include/idtree.h | 17 +++ lib/Makefile | 2 +- lib/external/idtree.c | 162 ++++++++++++++++++++++++++++ 9 files changed, 410 insertions(+), 32 deletions(-) create mode 100644 bin/xbps-pkgdb/check_files.c create mode 100644 include/idtree.h create mode 100644 lib/external/idtree.c diff --git a/LICENSE.3RDPARTY b/LICENSE.3RDPARTY index 4c3358308..cc25e3f0d 100644 --- a/LICENSE.3RDPARTY +++ b/LICENSE.3RDPARTY @@ -249,3 +249,29 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +[lib/external/idtree.c] +/* + * Copyright (C) 2015-2020 Leah Neukirchen + * Parts of code derived from musl libc, which is + * Copyright (C) 2005-2014 Rich Felker, et al. + * + * 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/bin/xbps-pkgdb/Makefile b/bin/xbps-pkgdb/Makefile index a278b0636..e99a22f0f 100644 --- a/bin/xbps-pkgdb/Makefile +++ b/bin/xbps-pkgdb/Makefile @@ -5,5 +5,6 @@ BIN = xbps-pkgdb OBJS = main.o check.o check_pkg_files.o OBJS += check_pkg_alternatives.o check_pkg_rundeps.o OBJS += check_pkg_symlinks.o check_pkg_unneeded.o +OBJS += check_files.o include $(TOPDIR)/mk/prog.mk diff --git a/bin/xbps-pkgdb/check_files.c b/bin/xbps-pkgdb/check_files.c new file mode 100644 index 000000000..7c3af51f2 --- /dev/null +++ b/bin/xbps-pkgdb/check_files.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#include "defs.h" + +int +file_mode_check(const char *file, const mode_t mode) { + struct stat sb; + + assert(file != NULL); + assert(mode); + + if (lstat(file, &sb) == -1) + return -errno; + + if (sb.st_mode != mode) + return ERANGE; + + return 0; +} + +int +file_user_check(struct idtree * idt, const char *file, const char *user) { + struct stat sb; + char *act_user; + + assert(file != NULL); + assert(user != NULL); + + if (lstat(file, &sb) == -1) + return -errno; + + act_user = idtree_username(idt, sb.st_uid); + return strcmp(user, act_user) == 0 ? 0 : ERANGE; +} + +int +file_group_check(struct idtree * idt, const char *file, const char *grp) { + struct stat sb; + char *act_grp; + + assert(file != NULL); + assert(grp != NULL); + + if (lstat(file, &sb) == -1) + return -errno; + + act_grp = idtree_groupname(idt, sb.st_gid); + return strcmp(grp, act_grp) == 0 ? 0 : ERANGE; +} diff --git a/bin/xbps-pkgdb/check_pkg_files.c b/bin/xbps-pkgdb/check_pkg_files.c index e15e51c54..fe87efc5a 100644 --- a/bin/xbps-pkgdb/check_pkg_files.c +++ b/bin/xbps-pkgdb/check_pkg_files.c @@ -43,7 +43,11 @@ * o Check the hash for all installed files, except * configuration files (which is expected if they are modified). * - * o Compares stored file modification time. + * o Check the mode for all installed files, except configuration files. + * + * o Check the user for all installed files, except configuration files. + * + * o Check the group for all installed files, except configuration files. * * Return 0 if test ran successfully, 1 otherwise and -1 on error. */ @@ -55,56 +59,120 @@ check_pkg_files(struct xbps_handle *xhp, const char *pkgname, void *arg) xbps_object_t obj; xbps_object_iterator_t iter; xbps_dictionary_t pkg_filesd = arg; - const char *file = NULL, *sha256 = NULL; + const char *file = NULL, *sha256 = NULL, *user = NULL, *group = NULL; char *path; - bool mutable, test_broken = false; + bool mutable, test_broken = false, noexist = false; int rv = 0, errors = 0; + mode_t mode = 0; + struct idtree *idt = NULL; array = xbps_dictionary_get(pkg_filesd, "files"); if (array != NULL && xbps_array_count(array) > 0) { iter = xbps_array_iter_from_dict(pkg_filesd, "files"); - if (iter == NULL) - return -1; + if (iter == NULL) { + errors++; + goto out; + } while ((obj = xbps_object_iterator_next(iter))) { + noexist = mutable = false; + xbps_dictionary_get_cstring_nocopy(obj, "file", &file); /* skip noextract files */ if (xhp->noextract && xbps_patterns_match(xhp->noextract, file)) continue; path = xbps_xasprintf("%s/%s", xhp->rootdir, file); - xbps_dictionary_get_cstring_nocopy(obj, - "sha256", &sha256); + + xbps_dictionary_get_bool(obj, "mutable", &mutable); + + /* check sha256 */ + xbps_dictionary_get_cstring_nocopy(obj, "sha256", &sha256); rv = xbps_file_sha256_check(path, sha256); switch (rv) { case 0: - free(path); break; case ENOENT: - xbps_error_printf("%s: unexistent file %s.\n", - pkgname, file); - free(path); - test_broken = true; + xbps_error_printf("%s: unexistent file %s.\n", pkgname, file); + test_broken = noexist = true; break; case ERANGE: - mutable = false; - xbps_dictionary_get_bool(obj, - "mutable", &mutable); if (!mutable) { - xbps_error_printf("%s: hash mismatch " - "for %s.\n", pkgname, file); + xbps_error_printf("%s: hash mismatch for %s.\n", pkgname, file); test_broken = true; } - free(path); break; default: - xbps_error_printf( - "%s: can't check `%s' (%s)\n", - pkgname, file, strerror(rv)); + xbps_error_printf("%s: can't check `%s' (%s)\n", pkgname, file, strerror(rv)); + break; + } + + if (noexist) { free(path); + continue; + } + + /* check mode */ + mode = 0; + if (xbps_dictionary_get_uint32(obj, "mode", &mode)) { + rv = file_mode_check(path, mode); + switch (rv) { + case 0: + break; + case ERANGE: + if (!mutable) { + xbps_error_printf("%s: mode mismatch for %s.\n", pkgname, file); + test_broken = true; + } + break; + default: + xbps_error_printf("%s: can't check `%s' (%s)\n", pkgname, file, strerror(-rv)); + break; + } + } + + /* check user */ + user = NULL; + xbps_dictionary_get_cstring_nocopy(obj, "user", &user); + if (user == NULL) + user = "root"; + rv = file_user_check(idt, path, user); + switch (rv) { + case 0: + break; + case ERANGE: + if (!mutable) { + xbps_error_printf("%s: user mismatch for %s.\n", pkgname, file); + test_broken = true; + } + break; + default: + xbps_error_printf("%s: can't check `%s' (%s)\n", pkgname, file, strerror(-rv)); + break; + } + + /* check group */ + group = NULL; + xbps_dictionary_get_cstring_nocopy(obj, "group", &group); + if (group == NULL) + group = "root"; + rv = file_group_check(idt, path, group); + switch (rv) { + case 0: + break; + case ERANGE: + if (!mutable) { + xbps_error_printf("%s: group mismatch for %s.\n", pkgname, file); + test_broken = true; + } + break; + default: + xbps_error_printf("%s: can't check `%s' (%s)\n", pkgname, file, strerror(-rv)); break; } - } - xbps_object_iterator_release(iter); + + free(path); + } + xbps_object_iterator_release(iter); } if (test_broken) { xbps_error_printf("%s: files check FAILED.\n", pkgname); @@ -118,8 +186,10 @@ check_pkg_files(struct xbps_handle *xhp, const char *pkgname, void *arg) array = xbps_dictionary_get(pkg_filesd, "conf_files"); if (array != NULL && xbps_array_count(array) > 0) { iter = xbps_array_iter_from_dict(pkg_filesd, "conf_files"); - if (iter == NULL) - return -1; + if (iter == NULL) { + errors++; + goto out; + } while ((obj = xbps_object_iterator_next(iter))) { xbps_dictionary_get_cstring_nocopy(obj, "file", &file); @@ -148,5 +218,7 @@ check_pkg_files(struct xbps_handle *xhp, const char *pkgname, void *arg) errors++; } +out: + idtree_free(idt); return errors ? -1 : 0; } diff --git a/bin/xbps-pkgdb/check_pkg_symlinks.c b/bin/xbps-pkgdb/check_pkg_symlinks.c index 851e67774..ded0fdee4 100644 --- a/bin/xbps-pkgdb/check_pkg_symlinks.c +++ b/bin/xbps-pkgdb/check_pkg_symlinks.c @@ -41,9 +41,11 @@ * The following task is accomplished in this file: * * o Check for target file in symlinks, so that we can check that - * they have not been modified. + * they have not been modified or broken. * - * returns 0 if test ran successfully, 1 otherwise and -1 on error. + * o Check for symlink ownership. + * + * returns 0 if test ran successfully and -1 on error. */ int @@ -52,14 +54,16 @@ check_pkg_symlinks(struct xbps_handle *xhp, const char *pkgname, void *arg) xbps_array_t array; xbps_object_t obj; xbps_dictionary_t filesd = arg; + bool test_broken = false; int rv = 0; + struct idtree *idt = NULL; array = xbps_dictionary_get(filesd, "links"); if (array == NULL) return 0; for (unsigned int i = 0; i < xbps_array_count(array); i++) { - const char *file = NULL, *tgt = NULL; + const char *file = NULL, *tgt = NULL, *user = NULL, *group = NULL; char path[PATH_MAX], *lnk = NULL; obj = xbps_array_get(array, i); @@ -83,16 +87,53 @@ check_pkg_symlinks(struct xbps_handle *xhp, const char *pkgname, void *arg) snprintf(path, sizeof(path), "%s/%s", xhp->rootdir, file); if ((lnk = xbps_symlink_target(xhp, path, tgt)) == NULL) { xbps_error_printf("%s: broken symlink %s (target: %s)\n", pkgname, file, tgt); - rv = -1; + test_broken = true; continue; } if (strcmp(lnk, tgt)) { xbps_warn_printf("%s: modified symlink %s " "points to %s (shall be %s)\n", pkgname, file, lnk, tgt); - rv = -1; + test_broken = true; + } + + user = NULL; + xbps_dictionary_get_cstring_nocopy(obj, "user", &user); + if (user == NULL) + user = "root"; + rv = file_user_check(idt, path, user); + switch (rv) { + case 0: + break; + case ERANGE: + xbps_error_printf("%s: user mismatch for %s.\n", pkgname, file); + test_broken = true; + break; + default: + xbps_error_printf("%s: can't check `%s' (%s)\n", pkgname, file, strerror(-rv)); + break; } + + group = NULL; + xbps_dictionary_get_cstring_nocopy(obj, "group", &group); + if (group == NULL) + group = "root"; + rv = file_group_check(idt, path, group); + switch (rv) { + case 0: + break; + case ERANGE: + xbps_error_printf("%s: group mismatch for %s.\n", pkgname, file); + test_broken = true; + break; + default: + xbps_error_printf("%s: can't check `%s' (%s)\n", pkgname, file, strerror(-rv)); + break; + } + free(lnk); } - return rv; + + idtree_free(idt); + return test_broken ? -1 : 0; } diff --git a/bin/xbps-pkgdb/defs.h b/bin/xbps-pkgdb/defs.h index f9663f41d..a9718ac59 100644 --- a/bin/xbps-pkgdb/defs.h +++ b/bin/xbps-pkgdb/defs.h @@ -29,6 +29,8 @@ #include #include +#include "idtree.h" + /* from check.c */ int check_pkg_integrity(struct xbps_handle *, xbps_dictionary_t, const char *); int check_pkg_integrity_all(struct xbps_handle *); @@ -45,4 +47,9 @@ CHECK_PKG_DECL(alternatives); /* from convert.c */ void convert_pkgdb_format(struct xbps_handle *); +/* from check_files.c */ +int file_mode_check(const char *, const mode_t); +int file_user_check(struct idtree *, const char *, const char *); +int file_group_check(struct idtree *, const char *, const char *); + #endif /* !_XBPS_PKGDB_DEFS_H_ */ diff --git a/include/idtree.h b/include/idtree.h new file mode 100644 index 000000000..f8b5e16d1 --- /dev/null +++ b/include/idtree.h @@ -0,0 +1,17 @@ +#ifndef IDTREE_H +#define IDTREE_H + +#include + +struct idtree { + long id; + char *name; + struct idtree *left, *right; + int level; +}; + +char * idtree_username(struct idtree *, uid_t); +char * idtree_groupname(struct idtree *, gid_t); +void idtree_free(struct idtree *); + +#endif /* IDTREE_H */ diff --git a/lib/Makefile b/lib/Makefile index bd9838570..68dbba076 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -32,7 +32,7 @@ LIBFETCH_INCS = fetch/common.h LIBFETCH_GEN = fetch/ftperr.h fetch/httperr.h # External code used by libxbps -EXTOBJS = external/dewey.o external/fexec.o external/mkpath.o +EXTOBJS = external/dewey.o external/fexec.o external/mkpath.o external/idtree.o # libxbps OBJS = package_configure.o package_config_files.o package_orphans.o diff --git a/lib/external/idtree.c b/lib/external/idtree.c new file mode 100644 index 000000000..89bc8f74f --- /dev/null +++ b/lib/external/idtree.c @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2015-2020 Leah Neukirchen + * Parts of code derived from musl libc, which is + * Copyright (C) 2005-2014 Rich Felker, et al. + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include "idtree.h" + +/* AA-tree implementation, adapted from https://github.com/ccxvii/minilibs */ + +static struct idtree idtree_sentinel = { 0, 0, &idtree_sentinel, &idtree_sentinel, 0 }; + +static struct idtree * +idtree_make(long id, char *name) +{ + struct idtree *node = malloc(sizeof (struct idtree)); + node->id = id; + node->name = name; + node->left = node->right = &idtree_sentinel; + node->level = 1; + return node; +} + +static char * +idtree_lookup(struct idtree *node, long id) +{ + if (node) { + while (node != &idtree_sentinel) { + if (id == node->id) + return node->name; + else if (id < node->id) + node = node->left; + else + node = node->right; + } + } + + return 0; +} + +static struct idtree * +idtree_skew(struct idtree *node) +{ + if (node->left->level == node->level) { + struct idtree *save = node; + node = node->left; + save->left = node->right; + node->right = save; + } + return node; +} + +static struct idtree * +idtree_split(struct idtree *node) +{ + if (node->right->right->level == node->level) { + struct idtree *save = node; + node = node->right; + save->right = node->left; + node->left = save; + node->level++; + } + return node; +} + +static struct idtree * +idtree_insert(struct idtree *node, long id, char *name) +{ + if (node && node != &idtree_sentinel) { + if (id == node->id) + return node; + else if (id < node->id) + node->left = idtree_insert(node->left, id, name); + else + node->right = idtree_insert(node->right, id, name); + node = idtree_skew(node); + node = idtree_split(node); + return node; + } + return idtree_make(id, name); +} +/**/ + +static char * +strid(long id) +{ + static char buf[32]; + snprintf(buf, sizeof buf, "%ld", id); + return buf; +} + +char * +idtree_groupname(struct idtree *groups, gid_t gid) +{ + char *name = idtree_lookup(groups, gid); + struct group *g; + + if (name) + return name; + + g = getgrgid(gid); + if (g) { + name = strdup(g->gr_name); + groups = idtree_insert(groups, gid, name); + return name; + } + + return strid(gid); +} + +char * +idtree_username(struct idtree *users, uid_t uid) +{ + char *name = idtree_lookup(users, uid); + struct passwd *p; + + if (name) + return name; + + p = getpwuid(uid); + if (p) { + name = strdup(p->pw_name); + users = idtree_insert(users, uid, name); + return name; + } + + return strid(uid); +} + +void +idtree_free(struct idtree *node) { + if (node && node != &idtree_sentinel) { + idtree_free(node->left); + idtree_free(node->right); + free(node); + } +} From 4bc0224883d3e5c766c4fe29034bbdff9dba9aa9 Mon Sep 17 00:00:00 2001 From: classabbyamp Date: Sun, 19 Feb 2023 21:42:06 -0500 Subject: [PATCH 20/21] bin/xbps-query: add --long for -f to show file perms, owner, size --- bin/xbps-query/defs.h | 6 ++-- bin/xbps-query/main.c | 19 +++++++--- bin/xbps-query/show-info-files.c | 61 +++++++++++++++++++++++++------- bin/xbps-query/xbps-query.1 | 41 +++++++++++++++++++-- data/_xbps | 1 + 5 files changed, 104 insertions(+), 24 deletions(-) diff --git a/bin/xbps-query/defs.h b/bin/xbps-query/defs.h index 0f4623114..5034689ee 100644 --- a/bin/xbps-query/defs.h +++ b/bin/xbps-query/defs.h @@ -39,9 +39,9 @@ void show_pkg_info(xbps_dictionary_t); void show_pkg_info_one(xbps_dictionary_t, const char *); int show_pkg_info_from_metadir(struct xbps_handle *, const char *, const char *); -int show_pkg_files(xbps_dictionary_t); -int show_pkg_files_from_metadir(struct xbps_handle *, const char *); -int repo_show_pkg_files(struct xbps_handle *, const char *); +int show_pkg_files(xbps_dictionary_t, const char *); +int show_pkg_files_from_metadir(struct xbps_handle *, const char *, const char *); +int repo_show_pkg_files(struct xbps_handle *, const char *, const char *); int cat_file(struct xbps_handle *, const char *, const char *); int repo_cat_file(struct xbps_handle *, const char *, const char *); int repo_show_pkg_info(struct xbps_handle *, const char *, const char *); diff --git a/bin/xbps-query/main.c b/bin/xbps-query/main.c index 42afddb99..77cdd802d 100644 --- a/bin/xbps-query/main.c +++ b/bin/xbps-query/main.c @@ -56,6 +56,7 @@ usage(bool fail) " specified multiple times\n" " --regex Use Extended Regular Expressions to match\n" " --fulldeptree Full dependency tree for -x/--deps\n" + " --long Show permissions, ownership, and size for -f/--files\n" " -r, --rootdir Full path to rootdir\n" " -V, --version Show XBPS version\n" " -v, --verbose Verbose messages\n" @@ -124,6 +125,7 @@ main(int argc, char **argv) { "verbose", no_argument, NULL, 'v' }, { "files", required_argument, NULL, 'f' }, { "format", required_argument, NULL, 'F' }, + { "long", no_argument, NULL, 4 }, { "deps", required_argument, NULL, 'x' }, { "revdeps", required_argument, NULL, 'X' }, { "regex", no_argument, NULL, 0 }, @@ -136,14 +138,14 @@ main(int argc, char **argv) int c, flags, rv; bool list_pkgs, list_repos, orphans, own, list_repolock; bool list_manual, list_hold, show_prop, show_files, show_deps, show_rdeps; - bool show, pkg_search, regex, repo_mode, opmode, fulldeptree; + bool show, pkg_search, regex, repo_mode, opmode, fulldeptree, long_listing; rootdir = cachedir = confdir = props = pkg = catfile = format = NULL; flags = rv = c = 0; list_pkgs = list_repos = list_hold = orphans = pkg_search = own = false; list_manual = list_repolock = show_prop = show_files = false; regex = show = show_deps = show_rdeps = fulldeptree = false; - repo_mode = opmode = false; + repo_mode = opmode = long_listing = false; memset(&xh, 0, sizeof(xh)); @@ -240,6 +242,9 @@ main(int argc, char **argv) case 3: list_repolock = opmode = true; break; + case 4: + long_listing = true; + break; case '?': default: usage(true); @@ -328,11 +333,15 @@ main(int argc, char **argv) } else if (show_files) { /* show-files mode */ + const char *fmt = format ? format : + (long_listing ? + "{mode?0!strmode} {user?\"root\":<8} {group?\"root\":<8} " + "{size?0!humanize .8Bi:>8} {file-target}\n" + : "{file-target}\n"); if (repo_mode) - rv = repo_show_pkg_files(&xh, pkg); + rv = repo_show_pkg_files(&xh, pkg, fmt); else - rv = show_pkg_files_from_metadir(&xh, pkg); - + rv = show_pkg_files_from_metadir(&xh, pkg, fmt); } else if (show_deps) { /* show-deps mode */ rv = show_pkg_deps(&xh, pkg, repo_mode, fulldeptree); diff --git a/bin/xbps-query/show-info-files.c b/bin/xbps-query/show-info-files.c index d9fa51748..920997348 100644 --- a/bin/xbps-query/show-info-files.c +++ b/bin/xbps-query/show-info-files.c @@ -186,13 +186,50 @@ show_pkg_info(xbps_dictionary_t dict) xbps_object_release(all_keys); } +struct file_print_cb { + xbps_dictionary_t dict; + bool islnk; +}; + +static int +file_print_cb(FILE *fp, const struct xbps_fmt *fmt, void *data) +{ + struct file_print_cb *ctx = data; + xbps_object_t obj; + if (ctx->islnk && strcmp(fmt->var, "mode") == 0) { + /* symbolic links don't store mode in the metadata, so it would normally display as + * unknown (?---------). be a bit more like ls -l and print 'l---------' without + * having to include this data in the plist */ + return xbps_fmt_print_number(fmt, 0120000, fp); + } else if (strcmp(fmt->var, "file-target") == 0) { + const char *buf, *target; + int len; + xbps_dictionary_get_cstring_nocopy(ctx->dict, "file", &buf); + if (xbps_dictionary_get_cstring_nocopy(ctx->dict, "target", &target)) { + buf = xbps_xasprintf("%s -> %s", buf, target); + } + len = strlen(buf); + return xbps_fmt_print_string(fmt, buf, len, fp); + } + obj = xbps_dictionary_get(ctx->dict, fmt->var); + return xbps_fmt_print_object(fmt, obj, fp); +} + int -show_pkg_files(xbps_dictionary_t filesd) +show_pkg_files(xbps_dictionary_t filesd, const char *fmts) { xbps_array_t array, allkeys; xbps_object_t obj; xbps_dictionary_keysym_t ksym; - const char *keyname = NULL, *file = NULL; + struct file_print_cb ctx = {0}; + const char *keyname = NULL; + + struct xbps_fmt *fmt = xbps_fmt_parse(fmts); + if (!fmt) { + int rv = -errno; + xbps_error_printf("failed to parse format: %s\n", strerror(-rv)); + return rv; + } if (xbps_object_type(filesd) != XBPS_TYPE_DICTIONARY) return EINVAL; @@ -206,6 +243,8 @@ show_pkg_files(xbps_dictionary_t filesd) (strcmp(keyname, "links"))))) continue; + ctx.islnk = strcmp(keyname, "links") == 0; + array = xbps_dictionary_get(filesd, keyname); if (array == NULL || xbps_array_count(array) == 0) continue; @@ -214,17 +253,13 @@ show_pkg_files(xbps_dictionary_t filesd) obj = xbps_array_get(array, x); if (xbps_object_type(obj) != XBPS_TYPE_DICTIONARY) continue; - xbps_dictionary_get_cstring_nocopy(obj, "file", &file); - printf("%s", file); - if (xbps_dictionary_get_cstring_nocopy(obj, - "target", &file)) - printf(" -> %s", file); - printf("\n"); + ctx.dict = obj; + xbps_fmt(fmt, &file_print_cb, &ctx, stdout); } } xbps_object_release(allkeys); - + xbps_fmt_free(fmt); return 0; } @@ -248,7 +283,7 @@ show_pkg_info_from_metadir(struct xbps_handle *xhp, } int -show_pkg_files_from_metadir(struct xbps_handle *xhp, const char *pkg) +show_pkg_files_from_metadir(struct xbps_handle *xhp, const char *pkg, const char *fmts) { xbps_dictionary_t d; int rv = 0; @@ -257,7 +292,7 @@ show_pkg_files_from_metadir(struct xbps_handle *xhp, const char *pkg) if (d == NULL) return ENOENT; - rv = show_pkg_files(d); + rv = show_pkg_files(d, fmts); return rv; } @@ -324,7 +359,7 @@ repo_cat_file(struct xbps_handle *xhp, const char *pkg, const char *file) } int -repo_show_pkg_files(struct xbps_handle *xhp, const char *pkg) +repo_show_pkg_files(struct xbps_handle *xhp, const char *pkg, const char *fmts) { xbps_dictionary_t pkgd; int rv; @@ -337,7 +372,7 @@ repo_show_pkg_files(struct xbps_handle *xhp, const char *pkg) return errno; } - rv = show_pkg_files(pkgd); + rv = show_pkg_files(pkgd, fmts); xbps_object_release(pkgd); return rv; } diff --git a/bin/xbps-query/xbps-query.1 b/bin/xbps-query/xbps-query.1 index 16fcfb6a0..23fa9f3e9 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -134,6 +134,12 @@ modes. Prints a full dependency tree in the .Sy show dependencies mode. +.It Fl -long +Prints permissions, ownership, and filesize in the +.Sy files +mode. +Equivalent to +.Fl -format Ar '{mode?0!strmode} {user?"root":<8} {group?"root":<8} {size?0!humanize .8Bi:>8} {file-target}\(rsn' . .It Fl r, Fl -rootdir Ar dir Specifies a full path for the target root directory. .It Fl v, Fl -verbose @@ -284,9 +290,11 @@ This mode only works with repositories. .El .Sh FORMAT STRINGS Variables are package properties if not otherwise documented. -See +See the .Sx PROPERTIES -section for a list of available properties. +and +.Sx FILE PROPERTIES +sections for a list of available properties. .Pp As example a format string like: .Bd -offset indent -literal @@ -350,7 +358,7 @@ Format strings are parsed by the following EBNF: | "X" -- Hexadecimal with uppercase letters. .Ed .Sh PROPERTIES -This is the list of a packages properties. +This is the list of a package's properties. Note that not all properties are available for all packages. .Pp .Bl -tag -compact -width 17m @@ -425,6 +433,33 @@ installation state of the package. .It Ic tags list of categories the package is associated with. .El +.Sh FILE PROPERTIES +This is the list of a package's files' properties. +Note that not all properties are available for all files in all packages. +.Pp +.Bl -tag -compact -width 17m +.It Ic file +absolute path of the file. +.It Ic file-target +alias for +.Ar file -> target +if target exists, otherwise +.Ar file . +.It Ic group +group that owns the file (root if unspecified). +.It Ic mode +file type and permissions as an integer. +See also: +.Xr inode 7 . +.It Ic mtime +modification time of the file (deprecated). +.It Ic sha256 +sha256sum of the file. +.It Ic target +target of the file, if it is a symlink. +.It Ic user +user that owns the file (root if unspecified). +.El .Sh ENVIRONMENT .Bl -tag -width XBPS_TARGET_ARCH .It Sy XBPS_ARCH diff --git a/data/_xbps b/data/_xbps index d513197d7..dcef94058 100644 --- a/data/_xbps +++ b/data/_xbps @@ -156,6 +156,7 @@ _xbps_query() { {-p,--property=-}'[Show properties]:property:($_xbps_properties)' \ --regex'[Use Extended Regular Expressions to match]' \ --fulldeptree'[Full dependency tree for -x/--deps]' \ + --long'[Show permissions, ownership, and size for -f/--files]' \ {-R,--repository}'[Enable repository mode]' \ '*'--repository=-'[Add repository to the top of the list]:repository url:_files -/' \ - '(mode)' \ From 8f483c67e4255900438efa619fb65acda22551cc Mon Sep 17 00:00:00 2001 From: classabbyamp Date: Thu, 16 Mar 2023 03:23:06 -0400 Subject: [PATCH 21/21] tests/xbps/xbps-query: add tests for [--long] -f --- tests/xbps/xbps-query/list_test.sh | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/xbps/xbps-query/list_test.sh b/tests/xbps/xbps-query/list_test.sh index fd11ff134..f1bf531e2 100644 --- a/tests/xbps/xbps-query/list_test.sh +++ b/tests/xbps/xbps-query/list_test.sh @@ -26,6 +26,43 @@ list_repos_body() { atf_check_equal "$output" " -1 https://localhost/wtf (RSA maybe-signed)" } +list_files_head() { + atf_set "descr" "xbps-query(1) [--long] -f" +} + +list_files_body() { + mkdir -p some_repo pkg/bin + cd pkg/bin + touch suidfile + chmod 4755 suidfile + touch suidfile_no_x + chmod 4644 suidfile_no_x + touch sgidfile + chmod 2755 sgidfile + touch sgidfile_no_x + chmod 2644 sgidfile_no_x + touch some_exe + chmod 0750 some_exe + touch some_file + chmod 0644 some_file + touch owned_by_nobody + touch owned_by_foo + echo "AAAAAAAAAAAAAAAAAAAAAAAAAAAA" > file_with_size + ln -s foo symlink + cd ../../some_repo + xbps-create -A noarch -n foo-1.0_1 -s "foo pkg" \ + --chown "/bin/owned_by_nobody:nobody:root /bin/owned_by_foo:foo:foo" ../pkg + atf_check_equal $? 0 + xbps-rindex -d -a $PWD/*.xbps + atf_check_equal $? 0 + cd .. + output="$(xbps-query -C empty.conf -i --repository=some_repo -f foo | tr -d '\n')" + atf_check_equal "$output" "/bin/file_with_size/bin/owned_by_foo/bin/owned_by_nobody/bin/sgidfile/bin/sgidfile_no_x/bin/some_exe/bin/some_file/bin/suidfile/bin/suidfile_no_x/bin/symlink -> /bin/foo" + output="$(xbps-query -C empty.conf -i --repository=some_repo --long -f foo | tr -d '\n')" + atf_check_equal "$output" "-rw-r--r-- root root 29 B /bin/file_with_size-rw-r--r-- foo foo 0 B /bin/owned_by_foo-rw-r--r-- nobody root 0 B /bin/owned_by_nobody-rwxr-sr-x root root 0 B /bin/sgidfile-rw-r--r-- root root 0 B /bin/sgidfile_no_x-rwxr-x--- root root 0 B /bin/some_exe-rw-r--r-- root root 0 B /bin/some_file-rwsr-xr-x root root 0 B /bin/suidfile-rwSr--r-- root root 0 B /bin/suidfile_no_xl--------- root root 0 B /bin/symlink -> /bin/foo" +} + atf_init_test_cases() { atf_add_test_case list_repos + atf_add_test_case list_files }