From cc9a471c32e106fa9ee045540613fefdc31c5cd2 Mon Sep 17 00:00:00 2001 From: Eric Fahlgren Date: Mon, 25 Nov 2024 09:48:52 -0800 Subject: [PATCH] rpc-sys: rework packagelist call for apk Parse the apk files to produce the packagelist. Reads both the 'installed' file to collect all package names and versions. If the 'all' parameter is false, then only the packages listed in the 'world' file are retained. Remaining issue: There is no ABI version information in any of the apk files, so the returned file names have not been cleaned. This is contrary to the opkg version, which cleaned the package names, and is a bug that needs to be fixed once the ABI version are available. Fixes: openwrt/openwrt#16907 Fixes: efahl/owut#22 Signed-off-by: Eric Fahlgren Link: https://github.com/openwrt/rpcd/pull/10 Signed-off-by: Robert Marko --- sys.c | 141 +++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 32 deletions(-) diff --git a/sys.c b/sys.c index da3508e..ec052d3 100644 --- a/sys.c +++ b/sys.c @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -166,9 +167,25 @@ rpc_cgi_password_set(struct ubus_context *ctx, struct ubus_object *obj, } static bool -is_field(const char *type, const char *line) +is_all_or_world(const char *pkg, const char **world) { - return strncmp(line, type, strlen(type)) == 0; + /* compares null-terminated pkg with non-null-terminated world[i] */ + /* e.g., "my_pkg\0" == "my_pkg==1.2.3\n" => true */ + + if (!world) return true; /* handles 'all' case */ + + const char *terminators = "\n@~<>="; /* man 5 apk-world */ + + size_t i, c; + const char *item; + for (i = 0; *world[i]; i++) { + item = world[i]; + for (c = 0; pkg[c] == item[c]; c++); + if (pkg[c] == '\0' && strchr(terminators, item[c])) + return true; + } + + return false; } static bool @@ -186,11 +203,24 @@ rpc_sys_packagelist(struct ubus_context *ctx, struct ubus_object *obj, struct blob_attr *msg) { struct blob_attr *tb[__RPC_PACKAGELIST_MAX]; - bool all = false, installed = false, auto_installed = false; + bool all = false; struct blob_buf buf = { 0 }; - char line[256], tmp[128], pkg[128], ver[128]; - char *pkg_abi; + char line[256], pkg[128], ver[128]; void *tbl; + struct stat statbuf; + const char **world = NULL; + char *world_mmap = NULL; + size_t world_mmap_size = 0; + + /* + * Status file fields, /usr/lib/opkg/status vs /lib/apk/db/installed + * opkg apk + * PACKAGE_ABIVERSION "ABIVersion" no equivalent - see BUG, below + * PACKAGE_AUTOINSTALLED "Auto-Installed" package listed in 'world', not a db field + * PACKAGE_NAME "Package" "P" + * PACKAGE_STATUS "Status" package listed in db, not a status value + * PACKAGE_VERSION "Version" "V" + */ blobmsg_parse(rpc_packagelist_policy, __RPC_PACKAGELIST_MAX, tb, blob_data(msg), blob_len(msg)); @@ -198,55 +228,102 @@ rpc_sys_packagelist(struct ubus_context *ctx, struct ubus_object *obj, if (tb[RPC_PACKAGELIST_ALL] && blobmsg_get_bool(tb[RPC_PACKAGELIST_ALL])) all = true; - FILE *f = fopen("/usr/lib/opkg/status", "r"); + FILE *f = fopen("/lib/apk/db/installed", "r"); if (!f) return UBUS_STATUS_NOT_FOUND; + if (!all) { + /* We return only those items appearing in 'world' file. */ + int world_fd = open("/etc/apk/world", O_RDONLY); + if (world_fd == -1) + return rpc_errno_status(); + + if (fstat(world_fd, &statbuf) == -1) { + close(world_fd); + return rpc_errno_status(); + } + + world_mmap_size = statbuf.st_size + 1; + if (world_mmap_size == 1) { + /* 'world' file is malformed: empty */ + close(world_fd); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + world_mmap = (char *)mmap(NULL, world_mmap_size, PROT_READ, MAP_PRIVATE, world_fd, 0); + close(world_fd); + if (world_mmap == MAP_FAILED) { + return rpc_errno_status(); + } + + if (world_mmap[world_mmap_size-2] != '\n') { + /* 'world' file is malformed: missing final newline */ + munmap(world_mmap, world_mmap_size); + return UBUS_STATUS_UNKNOWN_ERROR; + } + + /* resulting 'world' pointer map looks like this: + * nstrs = 2 == count of newlines in mmap + * mmap = "pkg1\npkg2=1.2\n\0" + * | | | + * world[0]-+ | | + * world[1]-------+ | + * world[2]-----------------+ + */ + + size_t istr, nstrs; + char *s; + for (nstrs = 0, s = world_mmap; s[nstrs]; s[nstrs] == '\n' ? nstrs++ : *s++); + + if (nstrs) { + /* extra one in world for NULL sentinel */ + world = (const char **)calloc(nstrs+1, sizeof(char *)); + world[0] = world_mmap; + for (istr = 1, s = world_mmap; *s; s++) { + if (*s == '\n') { + world[istr] = s + 1; + istr++; + } + } + } + } + blob_buf_init(&buf, 0); tbl = blobmsg_open_table(&buf, "packages"); pkg[0] = ver[0] = '\0'; while (fgets(line, sizeof(line), f)) { switch (line[0]) { - case 'A': - if (is_field("ABIVersion", line)) { - /* if there is ABIVersion, remove that suffix */ - if (sscanf(line, "ABIVersion: %127s", tmp) == 1 - && strlen(tmp) < strlen(pkg)) { - pkg_abi = pkg + (strlen(pkg) - strlen(tmp)); - if (strncmp(pkg_abi, tmp, strlen(tmp)) == 0) - pkg_abi[0] = '\0'; - } - } else if (is_field("Auto-Installed", line)) - if (sscanf(line, "Auto-Installed: %63s", tmp) == 1) - auto_installed = (strcmp(tmp, "yes") == 0); - break; case 'P': - if (is_field("Package", line)) - if (sscanf(line, "Package: %127s", pkg) != 1) - pkg[0] = '\0'; + if (sscanf(line, "P: %127s", pkg) != 1) + pkg[0] = '\0'; break; case 'V': - if (is_field("Version", line)) - if (sscanf(line, "Version: %127s", ver) != 1) - ver[0] = '\0'; - break; - case 'S': - if (is_field("Status", line)) - if (sscanf(line, "Status: install %63s installed", tmp) == 1) - installed = true; + if (sscanf(line, "V: %127s", ver) != 1) + ver[0] = '\0'; break; default: if (is_blank(line)) { - if (installed && (all || !auto_installed) && pkg[0] && ver[0]) + if (pkg[0] && ver[0] && is_all_or_world(pkg, world)) { + /* BUG: There's no ABI version info in any of + * the apk files, so some of the returned file + * names contain ABI-versioning. + * + * If you had that information, you'd apply it here. + */ blobmsg_add_string(&buf, pkg, ver); + } pkg[0] = ver[0] = '\0'; - installed = auto_installed = false; } break; } } + if (world) + free(world); + if (world_mmap) + munmap(world_mmap, world_mmap_size); + blobmsg_close_table(&buf, tbl); ubus_send_reply(ctx, req, buf.head); blob_buf_free(&buf); -- 2.30.2