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-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/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/bin/xbps-query/defs.h b/bin/xbps-query/defs.h index 2a1747253..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 *); @@ -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..77cdd802d 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" @@ -53,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" @@ -74,10 +78,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 +124,8 @@ 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' }, + { "long", no_argument, NULL, 4 }, { "deps", required_argument, NULL, 'x' }, { "revdeps", required_argument, NULL, 'X' }, { "regex", no_argument, NULL, 0 }, @@ -108,18 +134,18 @@ 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; + bool show, pkg_search, regex, repo_mode, opmode, fulldeptree, long_listing; - 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; regex = show = show_deps = show_rdeps = fulldeptree = false; - repo_mode = opmode = false; + repo_mode = opmode = long_listing = false; memset(&xh, 0, sizeof(xh)); @@ -138,6 +164,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; @@ -213,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); @@ -259,24 +291,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 */ @@ -301,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 b4c364180..23fa9f3e9 100644 --- a/bin/xbps-query/xbps-query.1 +++ b/bin/xbps-query/xbps-query.1 @@ -88,6 +88,11 @@ 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 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 @@ -129,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 @@ -277,8 +288,77 @@ 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 the +.Sx PROPERTIES +and +.Sx FILE PROPERTIES +sections 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 + ::= (prefix | "\\" (escape|[{}]) | substitution)* + ::= [^\\{}]+ -- Literal text chunk. + ::= [abefnrtv0] -- POSIX-like escape character. + + ::= "{" 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 + ::= "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] -- Maximum scale multiplier. + ::= "B" -- byte + | "K" -- kilo + | "M" -- mega + | "G" -- giga + | "T" -- tera + | "P" -- peta + | "E" -- exa + ::= "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. + ::= "<" -- 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]+ -- Precision 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. +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 @@ -353,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/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/data/_xbps b/data/_xbps index 315875717..dcef94058 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: ' } @@ -154,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)' \ diff --git a/include/compat.h b/include/compat.h index cedd1be29..4035a9b79 100644 --- a/include/compat.h +++ b/include/compat.h @@ -28,13 +28,23 @@ 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 + +#ifndef HAVE_STRMODE +void HIDDEN strmode(mode_t, char *); #endif #endif /* COMPAT_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/include/xbps.h.in b/include/xbps.h.in index 45025d03d..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 * @@ -2365,6 +2373,250 @@ xbps_plist_dictionary_from_file(const char *path); /**@}*/ +/** @addtogroup format */ +/**@{*/ + +/** + * @struct xbps_fmt xbps.h "xbps.h" + * @brief Structure of parsed format string variable. + */ +struct xbps_fmt { + /** + * @private + * @var prefix + * @brief Prefix of the format chunk. + */ + char *prefix; + /** + * @var var + * @brief Variable name. + */ + char *var; + /** + * @var def + * @brief Default value. + */ + struct xbps_fmt_def *def; + /** + * @var conv + * @brief Format conversion. + */ + struct xbps_fmt_conv *conv; + /** + * @var spec + * @brief Format specification. + */ + struct xbps_fmt_spec *spec; +}; + +/** + * @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 { + /** + * @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 *fmt, 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 \a num 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_print_number(const struct xbps_fmt *fmt, 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_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); + +/**@}*/ + #ifdef __cplusplus } #endif diff --git a/lib/Makefile b/lib/Makefile index 0cf6ac84f..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 @@ -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/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); 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/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); + } +} diff --git a/lib/format.c b/lib/format.c new file mode 100644 index 000000000..7fe1859eb --- /dev/null +++ b/lib/format.c @@ -0,0 +1,776 @@ +/*- + * 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; +} + +enum tok { + TTEXT = 1, + TVAR, +}; + +static enum tok +nexttok(const char **pos, struct strbuf *buf) +{ + const char *p; + int r; + + strbuf_reset(buf); + + for (p = *pos; *p;) { + switch (*p) { + case '}': + return -EINVAL; + case '{': + *pos = p; + if (buf->len > 0) + return TTEXT; + return TVAR; + 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 '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; + case 't': r = strbuf_putc(buf, '\t'); break; + case '0': r = strbuf_putc(buf, '\0'); break; + default: r = *p ? strbuf_putc(buf, *p) : 0; + } + if (r < 0) + return r; + if (*p) + 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_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_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 != '"';) { + if (*p == '\\') { + 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; + default: r = *p ? strbuf_putc(buf, *p) : 0; + } + } else { + r = strbuf_putc(buf, *p); + } + if (r < 0) + goto err; + if (*p) + p++; + } + 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) +{ + const char *scale = "BKMGTPE"; + const char *p = *pos; + 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 xbps_fmt *fmt, struct xbps_fmt_conv *conv_storage) +{ + 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"); + fmt->conv->type = STRMODE; + return 0; + } else if (strncmp(*pos + 1, "humanize", sizeof("humanize") - 1) == 0) { + fmt->conv->type = HUMANIZE; + *pos += sizeof("humanize"); + return parse_humanize(pos, &fmt->conv->humanize); + } + return -EINVAL; +} + +static int +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->fill = ' '; + spec->align = '>'; + spec->sign = '-'; + spec->width = 0; + spec->precision = 0; + spec->type = '\0'; + + /* 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 xbps_fmt *fmt, + struct strbuf *buf, + struct xbps_fmt_def *def_storage, + struct xbps_fmt_conv *conv_storage, + struct xbps_fmt_spec *spec_storage) +{ + const char *p = *pos; + const char *e; + int r; + + if (*p != '{') + return -EINVAL; + p++; + + /* var ::= '{' name [default][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; + + 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; + + /* 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) + return r; + + /* format_spec ::= [':' ...] */ + r = parse_spec(&p, fmt, spec_storage); + if (r < 0) + return r; + + 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 *tmp; + enum tok t; + + t = nexttok(&pos, &buf); + + tmp = realloc(fmt, sizeof(*fmt)*(n + 1)); + if (!tmp) + goto err_errno; + fmt = tmp; + memset(&fmt[n], '\0', sizeof(struct xbps_fmt)); + + if (t == 0) + goto out; + if (t == TTEXT) { + fmt[n].prefix = strndup(buf.mem, buf.len); + if (!fmt[n].prefix) + goto err_errno; + t = nexttok(&pos, &buf); + } + if (t == TVAR) { + r = parse(&pos, &fmt[n], NULL, NULL, NULL, NULL); + if (r < 0) + goto err; + } + 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->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); + } + 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 (;;) { + enum tok t; + + t = nexttok(&pos, &buf); + if (t == 0) + goto out; + if (t == TTEXT) { + fprintf(fp, "%s", buf.mem); + t = nexttok(&pos, &buf); + } + if (t == TVAR) { + 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, &def, &conv, &spec); + if (r < 0) + goto out; + r = cb(fp, &fmt, data); + if (r != 0) + goto out; + } + } +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->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; + } + } + return 0; +} + +struct fmt_dict_cb { + xbps_dictionary_t dict; +}; + +int +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 && 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 && spec->align == '<' && spec->width > (unsigned)len) { + for (unsigned i = 0; i < spec->width - len; i++) + fputc(spec->fill, fp); + } + return 0; +} + +static int +humanize(const struct humanize *h, const struct xbps_fmt *fmt, int64_t d, FILE *fp) +{ + char buf[64]; + 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_print_string(fmt, buf, len, fp); +} + +static int +tostrmode(const struct xbps_fmt *fmt, int64_t d, FILE *fp) +{ + char buf[64] = ""; + int len; + xbps_strmode(d, buf); + len = strlen(buf); + return xbps_fmt_print_string(fmt, buf, len, fp); +} + +int +xbps_fmt_print_number(const struct xbps_fmt *fmt, int64_t d, FILE *fp) +{ + char buf[64]; + 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 (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 = '>'; + } + + switch (spec ? spec->type : '\0') { + default: /* fallthrough */ + case 'd': + if (spec && spec->sign == '+') + len = snprintf(buf, sizeof(buf), "%+" PRId64, d); + else + len = snprintf(buf, sizeof(buf), "%" PRId64, d); + if (spec && spec->align == '=' && (buf[0] == '+' || buf[0] == '-')) { + len--, p++; + strspec.width -= 1; + fputc(buf[0], fp); + } + 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_print_string(&strfmt, p, len, fp); +} + +int +xbps_fmt_print_object(const struct xbps_fmt *fmt, xbps_object_t obj, FILE *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_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: + 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; + } + 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) +{ + 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); +} 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. */ 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..40298a98e --- /dev/null +++ b/tests/xbps/libxbps/fmt/main.c @@ -0,0 +1,182 @@ +/*- + * 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_print_number); + +ATF_TC_HEAD(xbps_fmt_print_number, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_print_number"); +} + +ATF_TC_BODY(xbps_fmt_print_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++) { + struct xbps_fmt fmt = { .spec = &tests[i].spec }; + memset(buf, '\0', bufsz); + rewind(fp); + xbps_fmt_print_number(&fmt, 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_print_string); + +ATF_TC_HEAD(xbps_fmt_print_string, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test xbps_fmt_print_string"); +} + +ATF_TC_BODY(xbps_fmt_print_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++) { + struct xbps_fmt fmt = { .spec = &tests[i].spec }; + memset(buf, '\0', bufsz); + rewind(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); + } + 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} {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 bar 1KB<"); + 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} {number!humanize}<", dict, fp) == 0); + ATF_REQUIRE(fflush(fp) == 0); + ATF_CHECK_STREQ(buf, ">s 1 0KB<"); + ATF_REQUIRE(fclose(fp) == 0); + free(buf); + xbps_object_release(dict); +} + +ATF_TP_ADD_TCS(tp) +{ + 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(); +} 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 }