Package: dpkg Version: 1.21.22 Severity: wishlist Dear Maintainer,
I'm working on the dpkg codebase, and I've found that when extracting a .deb with an uncompressed data.tar, we can install it even without calling dpk-deb as subprocess. Find a patch attached. Regards, -- Package-specific info: This system uses merged-usr-via-aliased-dirs, going behind dpkg's back, breaking its core assumptions. This can cause silent file overwrites and disappearances, and its general tools misbehavior. See <https://wiki.debian.org/Teams/Dpkg/FAQ#broken-usrmerge>. -- System Information: Debian Release: 12.7 APT prefers stable-security APT policy: (500, 'stable-security'), (500, 'stable') Architecture: amd64 (x86_64) Kernel: Linux 6.11.0-saturno (SMP w/8 CPU threads) Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8), LANGUAGE not set Shell: /bin/sh linked to /usr/bin/dash Init: systemd (via /run/systemd/system) Versions of packages dpkg depends on: ii libbz2-1.0 1.0.8-5+b1 ii libc6 2.36-9+deb12u8 ii liblzma5 5.4.1-0.2 ii libmd0 1.0.4-2 ii libselinux1 3.4-1+b6 ii libzstd1 1.5.4+dfsg2-5 ii tar 1.34+dfsg-1.2+deb12u1 ii zlib1g 1:1.2.13.dfsg-1 dpkg recommends no packages. Versions of packages dpkg suggests: ii apt 2.6.1 pn debsig-verify <none> -- no debconf information
From 608ecaee805e7b5305e3b527c5109652908c4d5b Mon Sep 17 00:00:00 2001 From: Matteo Croce <teknora...@meta.com> Date: Tue, 22 Oct 2024 02:05:32 +0200 Subject: [PATCH] dpkg: don't spawn a subprocess if not needed If the archive is uncompressed, avoid execing `deb-src`, just open the file and seek to the data start. read_line() is just moved from src/deb/extract.c to lib/dpkg/ar.c. dpkg_ar_member_get_offset() is a new function which returns the offset of data.tar inside the deb archive. If dpkg_ar_member_get_offset() returns a valid offset, and the archive is not compressed, just open the archive, skip to the data.tar start and pass that file descriptor to tar_extractor(), without spawning a subprocess and a pipe. --- lib/dpkg/ar.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++ lib/dpkg/ar.h | 3 ++ src/deb/extract.c | 29 ------------------- src/main/unpack.c | 46 +++++++++++++++++++---------- 4 files changed, 107 insertions(+), 45 deletions(-) diff --git a/lib/dpkg/ar.c b/lib/dpkg/ar.c index 6eea5ad13..8388ed90b 100644 --- a/lib/dpkg/ar.c +++ b/lib/dpkg/ar.c @@ -232,3 +232,77 @@ dpkg_ar_member_put_file(struct dpkg_ar *ar, if (fd_write(ar->fd, "\n", 1) < 0) ohshite(_("unable to write file '%s'"), ar->name); } + +ssize_t read_line(int fd, char *buf, size_t min_size, size_t max_size) +{ + ssize_t line_size = 0; + size_t n = min_size; + + while (line_size < (ssize_t) max_size) { + ssize_t nread; + char *nl; + + nread = fd_read(fd, buf + line_size, n); + if (nread <= 0) + return nread; + + nl = memchr(buf + line_size, '\n', nread); + line_size += nread; + + if (nl != NULL) { + nl[1] = '\0'; + return line_size; + } + + n = 1; + } + + buf[line_size] = '\0'; + return line_size; +} + +off_t dpkg_ar_member_get_offset(const char *debar) +{ + struct dpkg_ar ar = { .name = debar }; + char versionbuf[sizeof(DPKG_AR_MAGIC)]; + off_t memberlen; + ssize_t rc; + off_t offset = -1; + + ar.fd = open(ar.name, O_RDONLY); + if (ar.fd < 0) + ohshite(_("failed to open archive '%.255s'"), debar); + + rc = read_line(ar.fd, versionbuf, sizeof(DPKG_AR_MAGIC) - 1, + sizeof(versionbuf)); + if (rc <= 0) + ohshite(_("read failed")); + if (strcmp(versionbuf, DPKG_AR_MAGIC)) + ohshite(_("archive magic version number")); + + for (;;) { + struct dpkg_ar_hdr arh; + + rc = read(ar.fd, &arh, sizeof(arh)); + if (rc != sizeof(arh)) + break; + + if (dpkg_ar_member_is_illegal(&arh)) + ohshite(_("file '%.250s' is corrupt - bad archive header magic"), + debar); + + dpkg_ar_normalize_name(&arh); + if (strcmp(arh.ar_name, "data.tar") == 0) { + offset = lseek(ar.fd, 0, SEEK_CUR); + break; + } + + memberlen = dpkg_ar_member_get_size(&ar, &arh); + if (memberlen % 2) + memberlen++; + lseek(ar.fd, memberlen, SEEK_CUR); + } + + close(ar.fd); + return offset; +} diff --git a/lib/dpkg/ar.h b/lib/dpkg/ar.h index 8a7aff977..3af7fbe89 100644 --- a/lib/dpkg/ar.h +++ b/lib/dpkg/ar.h @@ -76,6 +76,8 @@ struct dpkg_ar_member { gid_t gid; }; +ssize_t read_line(int fd, char *buf, size_t min_size, size_t max_size); + struct dpkg_ar * dpkg_ar_fdopen(const char *filename, int fd); struct dpkg_ar *dpkg_ar_open(const char *filename); @@ -95,6 +97,7 @@ void dpkg_ar_member_put_mem(struct dpkg_ar *ar, const char *name, const void *data, size_t size); off_t dpkg_ar_member_get_size(struct dpkg_ar *ar, struct dpkg_ar_hdr *arh); +off_t dpkg_ar_member_get_offset(const char *debar); /** @} */ DPKG_END_DECLS diff --git a/src/deb/extract.c b/src/deb/extract.c index 08b281564..b76e0160a 100644 --- a/src/deb/extract.c +++ b/src/deb/extract.c @@ -74,35 +74,6 @@ read_fail(int rc, const char *filename, const char *what) ohshite(_("error reading %s from file %.255s"), what, filename); } -static ssize_t -read_line(int fd, char *buf, size_t min_size, size_t max_size) -{ - ssize_t line_size = 0; - size_t n = min_size; - - while (line_size < (ssize_t)max_size) { - ssize_t nread; - char *nl; - - nread = fd_read(fd, buf + line_size, n); - if (nread <= 0) - return nread; - - nl = memchr(buf + line_size, '\n', nread); - line_size += nread; - - if (nl != NULL) { - nl[1] = '\0'; - return line_size; - } - - n = 1; - } - - buf[line_size] = '\0'; - return line_size; -} - void extracthalf(const char *debar, const char *dir, enum dpkg_tar_options taroption, int admininfo) diff --git a/src/main/unpack.c b/src/main/unpack.c index e57a6e35e..9ae5959e7 100644 --- a/src/main/unpack.c +++ b/src/main/unpack.c @@ -55,6 +55,7 @@ #include <dpkg/db-ctrl.h> #include <dpkg/db-fsys.h> #include <dpkg/triglib.h> +#include <dpkg/ar.h> #include "file-match.h" #include "main.h" @@ -1223,6 +1224,7 @@ void process_archive(const char *filename) { const char *pfilename; struct fsys_namenode_queue newconffiles, newfiles_queue; struct stat stab; + off_t data_tar_offset; cleanup_pkg_failed= cleanup_conflictor_failed= 0; @@ -1536,24 +1538,33 @@ void process_archive(const char *filename) { * files get replaced ‘as we go’. */ - m_pipe(p1); - push_cleanup(cu_closepipe, ehflag_bombout, 1, (void *)&p1[0]); - pid = subproc_fork(); - if (pid == 0) { - m_dup2(p1[1],1); close(p1[0]); close(p1[1]); - execlp(BACKEND, BACKEND, "--fsys-tarfile", filename, NULL); - ohshite(_("unable to execute %s (%s)"), - _("package filesystem archive extraction"), BACKEND); + data_tar_offset = dpkg_ar_member_get_offset(filename); + + if (data_tar_offset < 0) { + m_pipe(p1); + push_cleanup(cu_closepipe, ehflag_bombout, 1, (void *)&p1[0]); + pid = subproc_fork(); + if (pid == 0) { + m_dup2(p1[1],1); close(p1[0]); close(p1[1]); + execlp(BACKEND, BACKEND, "--fsys-tarfile", filename, NULL); + ohshite(_("unable to execute %s (%s)"), + _("package filesystem archive extraction"), BACKEND); + } + close(p1[1]); + p1[1] = -1; } - close(p1[1]); - p1[1] = -1; newfiles_queue.head = NULL; newfiles_queue.tail = &newfiles_queue.head; tc.newfiles_queue = &newfiles_queue; push_cleanup(cu_fileslist, ~0, 0); tc.pkg= pkg; - tc.backendpipe= p1[0]; + if (data_tar_offset < 0) { + tc.backendpipe= p1[0]; + } else { + tc.backendpipe= open(filename, O_RDONLY); + lseek(tc.backendpipe, data_tar_offset, SEEK_SET); + } tc.pkgset_getting_in_sync = pkgset_getting_in_sync(pkg); /* Setup the tar archive. */ @@ -1565,11 +1576,14 @@ void process_archive(const char *filename) { if (rc) dpkg_error_print(&tar.err, _("corrupted filesystem tarfile in package archive")); - if (fd_skip(p1[0], -1, &err) < 0) - ohshit(_("cannot zap possible trailing zeros from dpkg-deb: %s"), err.str); - close(p1[0]); - p1[0] = -1; - subproc_reap(pid, BACKEND " --fsys-tarfile", SUBPROC_NOPIPE); + + if (data_tar_offset < 0) { + if (fd_skip(p1[0], -1, &err) < 0) + ohshit(_("cannot zap possible trailing zeros from dpkg-deb: %s"), err.str); + close(p1[0]); + p1[0] = -1; + subproc_reap(pid, BACKEND " --fsys-tarfile", SUBPROC_NOPIPE); + } tar_deferred_extract(newfiles_queue.head, pkg); -- 2.39.5