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

Reply via email to