summaryrefslogtreecommitdiffstats
path: root/src/deb
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:58:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:58:51 +0000
commitcbffab246997fb5a06211dfb706b54e5ae5bb59f (patch)
tree0573c5d96f58d74d76a49c0f2a70398e389a36d3 /src/deb
parentInitial commit. (diff)
downloaddpkg-cbffab246997fb5a06211dfb706b54e5ae5bb59f.tar.xz
dpkg-cbffab246997fb5a06211dfb706b54e5ae5bb59f.zip
Adding upstream version 1.21.22.upstream/1.21.22upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/deb')
-rw-r--r--src/deb/build.c730
-rw-r--r--src/deb/dpkg-deb.h87
-rw-r--r--src/deb/extract.c507
-rw-r--r--src/deb/info.c305
-rw-r--r--src/deb/main.c311
5 files changed, 1940 insertions, 0 deletions
diff --git a/src/deb/build.c b/src/deb/build.c
new file mode 100644
index 0000000..5c74ff3
--- /dev/null
+++ b/src/deb/build.c
@@ -0,0 +1,730 @@
+/*
+ * dpkg-deb - construction and deconstruction of *.deb archives
+ * build.c - building archives
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000,2001 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2007-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <string.h>
+#include <time.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/path.h>
+#include <dpkg/treewalk.h>
+#include <dpkg/varbuf.h>
+#include <dpkg/fdio.h>
+#include <dpkg/buffer.h>
+#include <dpkg/subproc.h>
+#include <dpkg/command.h>
+#include <dpkg/compress.h>
+#include <dpkg/ar.h>
+#include <dpkg/options.h>
+
+#include "dpkg-deb.h"
+
+static void
+control_treewalk_feed(const char *dir, int fd_out)
+{
+ struct treeroot *tree;
+ struct treenode *node;
+
+ tree = treewalk_open(dir, TREEWALK_NONE, NULL);
+ for (node = treewalk_node(tree); node; node = treewalk_next(tree)) {
+ char *nodename;
+
+ nodename = str_fmt("./%s", treenode_get_virtname(node));
+ if (fd_write(fd_out, nodename, strlen(nodename) + 1) < 0)
+ ohshite(_("failed to write filename to tar pipe (%s)"),
+ _("control member"));
+ free(nodename);
+ }
+ treewalk_close(tree);
+}
+
+/**
+ * Simple structure to store information about a file.
+ */
+struct file_info {
+ struct file_info *next;
+ char *fn;
+};
+
+static struct file_info *
+file_info_new(const char *filename)
+{
+ struct file_info *fi;
+
+ fi = m_malloc(sizeof(*fi));
+ fi->fn = m_strdup(filename);
+ fi->next = NULL;
+
+ return fi;
+}
+
+static void
+file_info_free(struct file_info *fi)
+{
+ free(fi->fn);
+ free(fi);
+}
+
+static struct file_info *
+file_info_find_name(struct file_info *list, const char *filename)
+{
+ struct file_info *node;
+
+ for (node = list; node; node = node->next)
+ if (strcmp(node->fn, filename) == 0)
+ return node;
+
+ return NULL;
+}
+
+/**
+ * Add a new file_info struct to a single linked list of file_info structs.
+ *
+ * We perform a slight optimization to work around a ‘feature’ in tar: tar
+ * always recurses into subdirectories if you list a subdirectory. So if an
+ * entry is added and the previous entry in the list is its subdirectory we
+ * remove the subdirectory.
+ *
+ * After a file_info struct is added to a list it may no longer be freed, we
+ * assume full responsibility for its memory.
+ */
+static void
+file_info_list_append(struct file_info **head, struct file_info **tail,
+ struct file_info *fi)
+{
+ if (*head == NULL)
+ *head = *tail = fi;
+ else
+ *tail = (*tail)->next =fi;
+}
+
+/**
+ * Free the memory for all entries in a list of file_info structs.
+ */
+static void
+file_info_list_free(struct file_info *fi)
+{
+ while (fi) {
+ struct file_info *fl;
+
+ fl=fi; fi=fi->next;
+ file_info_free(fl);
+ }
+}
+
+static void
+file_treewalk_feed(const char *dir, int fd_out)
+{
+ struct treeroot *tree;
+ struct treenode *node;
+ struct file_info *fi;
+ struct file_info *symlist = NULL;
+ struct file_info *symlist_end = NULL;
+
+ tree = treewalk_open(dir, TREEWALK_NONE, NULL);
+ for (node = treewalk_node(tree); node ; node = treewalk_next(tree)) {
+ const char *virtname = treenode_get_virtname(node);
+ char *nodename;
+
+ if (strncmp(virtname, BUILDCONTROLDIR, strlen(BUILDCONTROLDIR)) == 0)
+ continue;
+
+ nodename = str_fmt("./%s", virtname);
+
+ if (!nocheckflag && strchr(nodename, '\n'))
+ ohshit(_("newline not allowed in pathname '%s'"), nodename);
+
+ /* We need to reorder the files so we can make sure that symlinks
+ * will not appear before their target. */
+ if (S_ISLNK(treenode_get_mode(node))) {
+ fi = file_info_new(nodename);
+ file_info_list_append(&symlist, &symlist_end, fi);
+ } else {
+ if (fd_write(fd_out, nodename, strlen(nodename) + 1) < 0)
+ ohshite(_("failed to write filename to tar pipe (%s)"),
+ _("data member"));
+ }
+
+ free(nodename);
+ }
+ treewalk_close(tree);
+
+ for (fi = symlist; fi; fi = fi->next)
+ if (fd_write(fd_out, fi->fn, strlen(fi->fn) + 1) < 0)
+ ohshite(_("failed to write filename to tar pipe (%s)"), _("data member"));
+
+ file_info_list_free(symlist);
+}
+
+static const char *const maintainerscripts[] = {
+ PREINSTFILE,
+ POSTINSTFILE,
+ PRERMFILE,
+ POSTRMFILE,
+ MAINTSCRIPT_FILE_CONFIG,
+ NULL,
+};
+
+/**
+ * Check control directory and file permissions.
+ */
+static void
+check_file_perms(const char *ctrldir)
+{
+ struct varbuf path = VARBUF_INIT;
+ const char *const *mscriptp;
+ struct stat mscriptstab;
+
+ varbuf_printf(&path, "%s/", ctrldir);
+ if (lstat(path.buf, &mscriptstab))
+ ohshite(_("unable to stat control directory"));
+ if (!S_ISDIR(mscriptstab.st_mode))
+ ohshit(_("control directory is not a directory"));
+ if ((mscriptstab.st_mode & 07757) != 0755)
+ ohshit(_("control directory has bad permissions %03lo "
+ "(must be >=0755 and <=0775)"),
+ (unsigned long)(mscriptstab.st_mode & 07777));
+
+ for (mscriptp = maintainerscripts; *mscriptp; mscriptp++) {
+ varbuf_reset(&path);
+ varbuf_printf(&path, "%s/%s", ctrldir, *mscriptp);
+ if (!lstat(path.buf, &mscriptstab)) {
+ if (S_ISLNK(mscriptstab.st_mode))
+ continue;
+ if (!S_ISREG(mscriptstab.st_mode))
+ ohshit(_("maintainer script '%.50s' is not a plain file or symlink"),
+ *mscriptp);
+ if ((mscriptstab.st_mode & 07557) != 0555)
+ ohshit(_("maintainer script '%.50s' has bad permissions %03lo "
+ "(must be >=0555 and <=0775)"),
+ *mscriptp, (unsigned long)(mscriptstab.st_mode & 07777));
+ } else if (errno != ENOENT) {
+ ohshite(_("maintainer script '%.50s' is not stattable"), *mscriptp);
+ }
+ }
+
+ varbuf_destroy(&path);
+}
+
+/**
+ * Check if conffiles contains sane information.
+ */
+static void
+check_conffiles(const char *ctrldir, const char *rootdir)
+{
+ FILE *cf;
+ struct varbuf controlfile = VARBUF_INIT;
+ char conffilenamebuf[MAXCONFFILENAME + 1];
+ struct file_info *conffiles_head = NULL;
+ struct file_info *conffiles_tail = NULL;
+
+ varbuf_printf(&controlfile, "%s/%s", ctrldir, CONFFILESFILE);
+
+ cf = fopen(controlfile.buf, "r");
+ if (cf == NULL) {
+ if (errno == ENOENT) {
+ varbuf_destroy(&controlfile);
+ return;
+ }
+
+ ohshite(_("error opening conffiles file"));
+ }
+
+ while (fgets(conffilenamebuf, MAXCONFFILENAME + 1, cf)) {
+ struct stat controlstab;
+ char *conffilename = conffilenamebuf;
+ int n;
+ bool remove_on_upgrade = false;
+
+ n = strlen(conffilename);
+ if (!n)
+ ohshite(_("empty string from fgets reading conffiles"));
+
+ if (conffilename[n - 1] != '\n')
+ ohshit(_("conffile name '%s' is too long, or missing final newline"),
+ conffilename);
+
+ conffilename[--n] = '\0';
+
+ if (c_isspace(conffilename[0])) {
+ /* The conffiles lines cannot start with whitespace; by handling this
+ * case now, we simplify the remaining code. Move past the whitespace
+ * to give a better error. */
+ while (c_isspace(conffilename[0]))
+ conffilename++;
+ if (conffilename[0] == '\0')
+ ohshit(_("empty and whitespace-only lines are not allowed in "
+ "conffiles"));
+ ohshit(_("line with conffile filename '%s' has leading white spaces"),
+ conffilename);
+ }
+
+ if (conffilename[0] != '/') {
+ char *flag = conffilename;
+ char *flag_end = strchr(flag, ' ');
+
+ if (flag_end) {
+ conffilename = flag_end + 1;
+ n -= conffilename - flag;
+ }
+
+ /* If no flag separator is found, assume a missing leading slash. */
+ if (flag_end == NULL || (conffilename[0] && conffilename[0] != '/'))
+ ohshit(_("conffile name '%s' is not an absolute pathname"), conffilename);
+
+ flag_end[0] = '\0';
+
+ /* Otherwise assume a missing filename after the flag separator. */
+ if (conffilename[0] == '\0')
+ ohshit(_("conffile name missing after flag '%s'"), flag);
+
+ if (strcmp(flag, "remove-on-upgrade") == 0)
+ remove_on_upgrade = true;
+ else
+ ohshit(_("unknown flag '%s' for conffile '%s'"), flag, conffilename);
+ }
+
+ varbuf_reset(&controlfile);
+ varbuf_printf(&controlfile, "%s%s", rootdir, conffilename);
+ if (lstat(controlfile.buf, &controlstab)) {
+ if (errno == ENOENT) {
+ if ((n > 1) && c_isspace(conffilename[n - 1]))
+ warning(_("conffile filename '%s' contains trailing white spaces"),
+ conffilename);
+ if (!remove_on_upgrade)
+ ohshit(_("conffile '%.250s' does not appear in package"), conffilename);
+ } else
+ ohshite(_("conffile '%.250s' is not stattable"), conffilename);
+ } else if (remove_on_upgrade) {
+ ohshit(_("conffile '%s' is present but is requested to be removed"),
+ conffilename);
+ } else if (!S_ISREG(controlstab.st_mode)) {
+ warning(_("conffile '%s' is not a plain file"), conffilename);
+ }
+
+ if (file_info_find_name(conffiles_head, conffilename)) {
+ warning(_("conffile name '%s' is duplicated"), conffilename);
+ } else {
+ struct file_info *conffile;
+
+ conffile = file_info_new(conffilename);
+ file_info_list_append(&conffiles_head, &conffiles_tail, conffile);
+ }
+ }
+
+ file_info_list_free(conffiles_head);
+ varbuf_destroy(&controlfile);
+
+ if (ferror(cf))
+ ohshite(_("error reading conffiles file"));
+ fclose(cf);
+}
+
+/**
+ * Check the control file.
+ *
+ * @param ctrldir The directory from where to build the binary package.
+ * @return The pkginfo struct from the parsed control file.
+ */
+static struct pkginfo *
+check_control_file(const char *ctrldir)
+{
+ struct pkginfo *pkg;
+ char *controlfile;
+
+ controlfile = str_fmt("%s/%s", ctrldir, CONTROLFILE);
+ parsedb(controlfile, pdb_parse_binary, &pkg);
+
+ if (strspn(pkg->set->name, "abcdefghijklmnopqrstuvwxyz0123456789+-.") !=
+ strlen(pkg->set->name))
+ ohshit(_("package name has characters that aren't lowercase alphanums or '-+.'"));
+ if (pkg->available.arch->type == DPKG_ARCH_NONE ||
+ pkg->available.arch->type == DPKG_ARCH_EMPTY)
+ ohshit(_("package architecture is missing or empty"));
+ if (pkg->priority == PKG_PRIO_OTHER)
+ warning(_("'%s' contains user-defined Priority value '%s'"),
+ controlfile, pkg->otherpriority);
+
+ free(controlfile);
+
+ return pkg;
+}
+
+/**
+ * Perform some sanity checks on the to-be-built package control area.
+ *
+ * @param ctrldir The directory from where to build the binary package.
+ * @return The pkginfo struct from the parsed control file.
+ */
+static struct pkginfo *
+check_control_area(const char *ctrldir, const char *rootdir)
+{
+ struct pkginfo *pkg;
+ int warns;
+
+ /* Start by reading in the control file so we can check its contents. */
+ pkg = check_control_file(ctrldir);
+ check_file_perms(ctrldir);
+ check_conffiles(ctrldir, rootdir);
+
+ warns = warning_get_count();
+ if (warns)
+ warning(P_("ignoring %d warning about the control file(s)",
+ "ignoring %d warnings about the control file(s)", warns),
+ warns);
+
+ return pkg;
+}
+
+/**
+ * Generate the pathname for the destination binary package.
+ *
+ * If the pathname cannot be computed, because the destination is a directory,
+ * then NULL will be returned.
+ *
+ * @param dir The directory from where to build the binary package.
+ * @param dest The destination name, either a file or directory name.
+ * @return The pathname for the package being built.
+ */
+static char *
+gen_dest_pathname(const char *dir, const char *dest)
+{
+ if (dest) {
+ struct stat dest_stab;
+
+ if (stat(dest, &dest_stab)) {
+ if (errno != ENOENT)
+ ohshite(_("unable to check for existence of archive '%.250s'"), dest);
+ } else if (S_ISDIR(dest_stab.st_mode)) {
+ /* Need to compute the destination name from the package control file. */
+ return NULL;
+ }
+
+ return m_strdup(dest);
+ } else {
+ char *pathname;
+
+ pathname = m_malloc(strlen(dir) + sizeof(DEBEXT));
+ strcpy(pathname, dir);
+ path_trim_slash_slashdot(pathname);
+ strcat(pathname, DEBEXT);
+
+ return pathname;
+ }
+}
+
+/**
+ * Generate the pathname for the destination binary package from control file.
+ *
+ * @return The pathname for the package being built.
+ */
+static char *
+gen_dest_pathname_from_pkg(const char *dir, struct pkginfo *pkg)
+{
+ return str_fmt("%s/%s_%s_%s%s", dir, pkg->set->name,
+ versiondescribe(&pkg->available.version, vdew_never),
+ pkg->available.arch->name, DEBEXT);
+}
+
+typedef void filenames_feed_func(const char *dir, int fd_out);
+
+struct tar_pack_options {
+ intmax_t timestamp;
+ const char *mode;
+ bool root_owner_group;
+};
+
+/**
+ * Pack the contents of a directory into a tarball.
+ */
+static void
+tarball_pack(const char *dir, filenames_feed_func *tar_filenames_feeder,
+ struct tar_pack_options *options,
+ struct compress_params *tar_compress_params, int fd_out)
+{
+ int pipe_filenames[2], pipe_tarball[2];
+ pid_t pid_tar, pid_comp;
+
+ /* Fork off a tar. We will feed it a list of filenames on stdin later. */
+ m_pipe(pipe_filenames);
+ m_pipe(pipe_tarball);
+ pid_tar = subproc_fork();
+ if (pid_tar == 0) {
+ struct command cmd;
+ char mtime[50];
+
+ m_dup2(pipe_filenames[0], 0);
+ close(pipe_filenames[0]);
+ close(pipe_filenames[1]);
+ m_dup2(pipe_tarball[1], 1);
+ close(pipe_tarball[0]);
+ close(pipe_tarball[1]);
+
+ if (chdir(dir))
+ ohshite(_("failed to chdir to '%.255s'"), dir);
+
+ snprintf(mtime, sizeof(mtime), "@%jd", options->timestamp);
+
+ command_init(&cmd, TAR, "tar -cf");
+ command_add_args(&cmd, "tar", "-cf", "-", "--format=gnu",
+ "--mtime", mtime, "--clamp-mtime", NULL);
+ /* Mode might become a positional argument, pass it before -T. */
+ if (options->mode)
+ command_add_args(&cmd, "--mode", options->mode, NULL);
+ if (options->root_owner_group)
+ command_add_args(&cmd, "--owner", "root:0", "--group", "root:0", NULL);
+ command_add_args(&cmd, "--null", "--no-unquote", "--no-recursion",
+ "-T", "-", NULL);
+ command_exec(&cmd);
+ }
+ close(pipe_filenames[0]);
+ close(pipe_tarball[1]);
+
+ /* Of course we should not forget to compress the archive as well. */
+ pid_comp = subproc_fork();
+ if (pid_comp == 0) {
+ close(pipe_filenames[1]);
+ compress_filter(tar_compress_params, pipe_tarball[0], fd_out,
+ _("compressing tar member"));
+ exit(0);
+ }
+ close(pipe_tarball[0]);
+
+ /* All the pipes are set, now lets start feeding filenames to tar. */
+ tar_filenames_feeder(dir, pipe_filenames[1]);
+
+ /* All done, clean up wait for tar and <compress> to finish their job. */
+ close(pipe_filenames[1]);
+ subproc_reap(pid_comp, _("<compress> from tar -cf"), 0);
+ subproc_reap(pid_tar, "tar -cf", 0);
+}
+
+static intmax_t
+parse_timestamp(const char *value)
+{
+ intmax_t timestamp;
+ char *end;
+
+ errno = 0;
+ timestamp = strtoimax(value, &end, 10);
+ if (value == end || *end || errno != 0)
+ ohshite(_("unable to parse timestamp '%.255s'"), value);
+
+ return timestamp;
+}
+
+/**
+ * Overly complex function that builds a .deb file.
+ */
+int
+do_build(const char *const *argv)
+{
+ struct compress_params control_compress_params;
+ struct tar_pack_options tar_options;
+ struct dpkg_error err;
+ struct dpkg_ar *ar;
+ intmax_t timestamp;
+ const char *timestamp_str;
+ const char *dir, *dest;
+ char *ctrldir;
+ char *debar;
+ char *tfbuf;
+ int gzfd;
+
+ /* Decode our arguments. */
+ dir = *argv++;
+ if (!dir)
+ badusage(_("--%s needs a <directory> argument"), cipaction->olong);
+
+ dest = *argv++;
+ if (dest && *argv)
+ badusage(_("--%s takes at most two arguments"), cipaction->olong);
+
+ debar = gen_dest_pathname(dir, dest);
+ ctrldir = str_fmt("%s/%s", dir, BUILDCONTROLDIR);
+
+ /* Perform some sanity checks on the to-be-build package. */
+ if (nocheckflag) {
+ if (debar == NULL)
+ ohshit(_("target is directory - cannot skip control file check"));
+ warning(_("not checking contents of control area"));
+ info(_("building an unknown package in '%s'."), debar);
+ } else {
+ struct pkginfo *pkg;
+
+ pkg = check_control_area(ctrldir, dir);
+ if (debar == NULL)
+ debar = gen_dest_pathname_from_pkg(dest, pkg);
+ info(_("building package '%s' in '%s'."), pkg->set->name, debar);
+ }
+ m_output(stdout, _("<standard output>"));
+
+ timestamp_str = getenv("SOURCE_DATE_EPOCH");
+ if (timestamp_str)
+ timestamp = parse_timestamp(timestamp_str);
+ else
+ timestamp = time(NULL);
+
+ /* Now that we have verified everything it is time to actually
+ * build something. Let's start by making the ar-wrapper. */
+ ar = dpkg_ar_create(debar, 0644);
+
+ dpkg_ar_set_mtime(ar, timestamp);
+
+ unsetenv("TAR_OPTIONS");
+
+ /* Create a temporary file to store the control data in. Immediately
+ * unlink our temporary file so others can't mess with it. */
+ tfbuf = path_make_temp_template("dpkg-deb");
+ gzfd = mkstemp(tfbuf);
+ if (gzfd == -1)
+ ohshite(_("failed to make temporary file (%s)"), _("control member"));
+ /* Make sure it's gone, the fd will remain until we close it. */
+ if (unlink(tfbuf))
+ ohshit(_("failed to unlink temporary file (%s), %s"), _("control member"),
+ tfbuf);
+ free(tfbuf);
+
+ /* Select the compressor to use for our control archive. */
+ if (opt_uniform_compression) {
+ control_compress_params = compress_params;
+ } else {
+ control_compress_params.type = COMPRESSOR_TYPE_GZIP;
+ control_compress_params.strategy = COMPRESSOR_STRATEGY_NONE;
+ control_compress_params.level = -1;
+ if (!compressor_check_params(&control_compress_params, &err))
+ internerr("invalid control member compressor params: %s", err.str);
+ }
+
+ /* Fork a tar to package the control-section of the package. */
+ tar_options.mode = "u+rw,go=rX";
+ tar_options.timestamp = timestamp;
+ tar_options.root_owner_group = true;
+ tarball_pack(ctrldir, control_treewalk_feed, &tar_options,
+ &control_compress_params, gzfd);
+
+ free(ctrldir);
+
+ if (lseek(gzfd, 0, SEEK_SET))
+ ohshite(_("failed to rewind temporary file (%s)"), _("control member"));
+
+ /* We have our first file for the ar-archive. Write a header for it
+ * to the package and insert it. */
+ if (deb_format.major == 0) {
+ struct stat controlstab;
+ char versionbuf[40];
+
+ if (fstat(gzfd, &controlstab))
+ ohshite(_("failed to stat temporary file (%s)"), _("control member"));
+ sprintf(versionbuf, "%-8s\n%jd\n", OLDARCHIVEVERSION,
+ (intmax_t)controlstab.st_size);
+ if (fd_write(ar->fd, versionbuf, strlen(versionbuf)) < 0)
+ ohshite(_("error writing '%s'"), debar);
+ if (fd_fd_copy(gzfd, ar->fd, -1, &err) < 0)
+ ohshit(_("cannot copy '%s' into archive '%s': %s"), _("control member"),
+ ar->name, err.str);
+ } else if (deb_format.major == 2) {
+ const char deb_magic[] = ARCHIVEVERSION "\n";
+ char adminmember[16 + 1];
+
+ sprintf(adminmember, "%s%s", ADMINMEMBER,
+ compressor_get_extension(control_compress_params.type));
+
+ dpkg_ar_put_magic(ar);
+ dpkg_ar_member_put_mem(ar, DEBMAGIC, deb_magic, strlen(deb_magic));
+ dpkg_ar_member_put_file(ar, adminmember, gzfd, -1);
+ } else {
+ internerr("unknown deb format version %d.%d", deb_format.major, deb_format.minor);
+ }
+
+ close(gzfd);
+
+ /* Control is done, now we need to archive the data. */
+ if (deb_format.major == 0) {
+ /* In old format, the data member is just concatenated after the
+ * control member, so we do not need a temporary file and can use
+ * the compression file descriptor. */
+ gzfd = ar->fd;
+ } else if (deb_format.major == 2) {
+ /* Start by creating a new temporary file. Immediately unlink the
+ * temporary file so others can't mess with it. */
+ tfbuf = path_make_temp_template("dpkg-deb");
+ gzfd = mkstemp(tfbuf);
+ if (gzfd == -1)
+ ohshite(_("failed to make temporary file (%s)"), _("data member"));
+ /* Make sure it's gone, the fd will remain until we close it. */
+ if (unlink(tfbuf))
+ ohshit(_("failed to unlink temporary file (%s), %s"), _("data member"),
+ tfbuf);
+ free(tfbuf);
+ } else {
+ internerr("unknown deb format version %d.%d", deb_format.major, deb_format.minor);
+ }
+
+ /* Pack the directory into a tarball, feeding files from the callback. */
+ tar_options.mode = NULL;
+ tar_options.timestamp = timestamp;
+ tar_options.root_owner_group = opt_root_owner_group;
+ tarball_pack(dir, file_treewalk_feed, &tar_options, &compress_params, gzfd);
+
+ /* Okay, we have data.tar as well now, add it to the ar wrapper. */
+ if (deb_format.major == 2) {
+ char datamember[16 + 1];
+
+ sprintf(datamember, "%s%s", DATAMEMBER,
+ compressor_get_extension(compress_params.type));
+
+ if (lseek(gzfd, 0, SEEK_SET))
+ ohshite(_("failed to rewind temporary file (%s)"), _("data member"));
+
+ dpkg_ar_member_put_file(ar, datamember, gzfd, -1);
+
+ close(gzfd);
+ }
+ if (fsync(ar->fd))
+ ohshite(_("unable to sync file '%s'"), ar->name);
+
+ dpkg_ar_close(ar);
+
+ free(debar);
+
+ return 0;
+}
diff --git a/src/deb/dpkg-deb.h b/src/deb/dpkg-deb.h
new file mode 100644
index 0000000..a5e8d39
--- /dev/null
+++ b/src/deb/dpkg-deb.h
@@ -0,0 +1,87 @@
+/*
+ * dpkg-deb - construction and deconstruction of *.deb archives
+ * dpkg-deb.h - external definitions for this program
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-2012 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef DPKG_DEB_H
+#define DPKG_DEB_H
+
+#include <dpkg/deb-version.h>
+
+action_func do_build;
+action_func do_contents;
+action_func do_control;
+action_func do_showinfo;
+action_func do_info;
+action_func do_field;
+action_func do_extract;
+action_func do_vextract;
+action_func do_raw_extract;
+action_func do_ctrltarfile;
+action_func do_fsystarfile;
+
+extern int opt_verbose;
+extern int opt_root_owner_group;
+extern int opt_uniform_compression;
+extern int debugflag, nocheckflag;
+
+extern struct deb_version deb_format;
+
+enum dpkg_tar_options {
+ /** Output the tar file directly, without any processing. */
+ DPKG_TAR_PASSTHROUGH = 0,
+ /** List tar files. */
+ DPKG_TAR_LIST = DPKG_BIT(0),
+ /** Extract tar files. */
+ DPKG_TAR_EXTRACT = DPKG_BIT(1),
+ /** Preserve tar permissions on extract. */
+ DPKG_TAR_PERMS = DPKG_BIT(2),
+ /** Do not set tar mtime on extract. */
+ DPKG_TAR_NOMTIME = DPKG_BIT(3),
+ /** Guarantee extraction into a new directory, abort if it exists. */
+ DPKG_TAR_CREATE_DIR = DPKG_BIT(4),
+};
+
+void extracthalf(const char *debar, const char *dir,
+ enum dpkg_tar_options taroption, int admininfo);
+
+extern const char *showformat;
+extern struct compress_params compress_params;
+
+#define ARCHIVEVERSION "2.0"
+
+#define BUILDCONTROLDIR "DEBIAN"
+#define EXTRACTCONTROLDIR BUILDCONTROLDIR
+
+#define OLDARCHIVEVERSION "0.939000"
+
+#define OLDDEBDIR "DEBIAN"
+#define OLDOLDDEBDIR ".DEBIAN"
+
+#define DEBMAGIC "debian-binary"
+#define ADMINMEMBER "control.tar"
+#define DATAMEMBER "data.tar"
+
+#ifdef PATH_MAX
+# define INTERPRETER_MAX PATH_MAX
+#else
+# define INTERPRETER_MAX 1024
+#endif
+
+#endif /* DPKG_DEB_H */
diff --git a/src/deb/extract.c b/src/deb/extract.c
new file mode 100644
index 0000000..73612a4
--- /dev/null
+++ b/src/deb/extract.c
@@ -0,0 +1,507 @@
+/*
+ * dpkg-deb - construction and deconstruction of *.deb archives
+ * extract.c - extracting archives
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/fdio.h>
+#include <dpkg/buffer.h>
+#include <dpkg/subproc.h>
+#include <dpkg/command.h>
+#include <dpkg/compress.h>
+#include <dpkg/ar.h>
+#include <dpkg/deb-version.h>
+#include <dpkg/options.h>
+
+#include "dpkg-deb.h"
+
+static void
+movecontrolfiles(const char *dir, const char *thing)
+{
+ char *cmd;
+ pid_t pid;
+
+ cmd = str_fmt("mv %s/%s/* %s/ && rmdir %s/%s", dir, thing, dir, dir, thing);
+ pid = subproc_fork();
+ if (pid == 0) {
+ command_shell(cmd, _("shell command to move files"));
+ }
+ subproc_reap(pid, _("shell command to move files"), 0);
+ free(cmd);
+}
+
+static void DPKG_ATTR_NORET
+read_fail(int rc, const char *filename, const char *what)
+{
+ if (rc >= 0)
+ ohshit(_("unexpected end of file in %s in %.255s"),what,filename);
+ else
+ 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 r;
+ char *nl;
+
+ r = fd_read(fd, buf + line_size, n);
+ if (r <= 0)
+ return r;
+
+ nl = memchr(buf + line_size, '\n', r);
+ line_size += r;
+
+ 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)
+{
+ struct dpkg_error err;
+ const char *errstr;
+ struct dpkg_ar *ar;
+ char versionbuf[40];
+ struct deb_version version;
+ off_t ctrllennum, memberlen = 0;
+ ssize_t r;
+ int dummy;
+ pid_t c1=0,c2,c3;
+ int p1[2], p2[2];
+ int p2_out;
+ char nlc;
+ int adminmember = -1;
+ bool header_done;
+ struct compress_params decompress_params = {
+ .type = COMPRESSOR_TYPE_GZIP,
+ .threads_max = compress_params.threads_max,
+ };
+
+ ar = dpkg_ar_open(debar);
+
+ r = read_line(ar->fd, versionbuf, strlen(DPKG_AR_MAGIC), sizeof(versionbuf) - 1);
+ if (r <= 0)
+ read_fail(r, debar, _("archive magic version number"));
+
+ if (strcmp(versionbuf, DPKG_AR_MAGIC) == 0) {
+ ctrllennum= 0;
+ header_done = false;
+ for (;;) {
+ struct dpkg_ar_hdr arh;
+
+ r = fd_read(ar->fd, &arh, sizeof(arh));
+ if (r != sizeof(arh))
+ read_fail(r, debar, _("archive member header"));
+
+ if (dpkg_ar_member_is_illegal(&arh))
+ ohshit(_("file '%.250s' is corrupt - bad archive header magic"), debar);
+
+ dpkg_ar_normalize_name(&arh);
+
+ memberlen = dpkg_ar_member_get_size(ar, &arh);
+ if (!header_done) {
+ char *infobuf;
+
+ if (strncmp(arh.ar_name, DEBMAGIC, sizeof(arh.ar_name)) != 0)
+ ohshit(_("file '%.250s' is not a Debian binary archive (try dpkg-split?)"),
+ debar);
+ infobuf= m_malloc(memberlen+1);
+ r = fd_read(ar->fd, infobuf, memberlen + (memberlen & 1));
+ if (r != (memberlen + (memberlen & 1)))
+ read_fail(r, debar, _("archive information header member"));
+ infobuf[memberlen] = '\0';
+
+ if (strchr(infobuf, '\n') == NULL)
+ ohshit(_("archive has no newlines in header"));
+ errstr = deb_version_parse(&version, infobuf);
+ if (errstr)
+ ohshit(_("archive has invalid format version: %s"), errstr);
+ if (version.major != 2)
+ ohshit(_("archive is format version %d.%d; get a newer dpkg-deb"),
+ version.major, version.minor);
+
+ free(infobuf);
+
+ header_done = true;
+ } else if (arh.ar_name[0] == '_') {
+ /* Members with ‘_’ are noncritical, and if we don't understand
+ * them we skip them. */
+ if (fd_skip(ar->fd, memberlen + (memberlen & 1), &err) < 0)
+ ohshit(_("cannot skip archive member from '%s': %s"), ar->name, err.str);
+ } else {
+ if (strncmp(arh.ar_name, ADMINMEMBER, strlen(ADMINMEMBER)) == 0) {
+ const char *extension = arh.ar_name + strlen(ADMINMEMBER);
+
+ adminmember = 1;
+ decompress_params.type = compressor_find_by_extension(extension);
+ if (decompress_params.type != COMPRESSOR_TYPE_NONE &&
+ decompress_params.type != COMPRESSOR_TYPE_GZIP &&
+ decompress_params.type != COMPRESSOR_TYPE_ZSTD &&
+ decompress_params.type != COMPRESSOR_TYPE_XZ)
+ ohshit(_("archive '%s' uses unknown compression for member '%.*s', "
+ "giving up"),
+ debar, (int)sizeof(arh.ar_name), arh.ar_name);
+
+ if (ctrllennum != 0)
+ ohshit(_("archive '%.250s' contains two control members, giving up"),
+ debar);
+ ctrllennum = memberlen;
+ } else {
+ if (adminmember != 1)
+ ohshit(_("archive '%s' has premature member '%.*s' before '%s', "
+ "giving up"),
+ debar, (int)sizeof(arh.ar_name), arh.ar_name, ADMINMEMBER);
+
+ if (strncmp(arh.ar_name, DATAMEMBER, strlen(DATAMEMBER)) == 0) {
+ const char *extension = arh.ar_name + strlen(DATAMEMBER);
+
+ adminmember= 0;
+ decompress_params.type = compressor_find_by_extension(extension);
+ if (decompress_params.type == COMPRESSOR_TYPE_UNKNOWN)
+ ohshit(_("archive '%s' uses unknown compression for member '%.*s', "
+ "giving up"),
+ debar, (int)sizeof(arh.ar_name), arh.ar_name);
+ } else {
+ ohshit(_("archive '%s' has premature member '%.*s' before '%s', "
+ "giving up"),
+ debar, (int)sizeof(arh.ar_name), arh.ar_name, DATAMEMBER);
+ }
+ }
+ if (!adminmember != !admininfo) {
+ if (fd_skip(ar->fd, memberlen + (memberlen & 1), &err) < 0)
+ ohshit(_("cannot skip archive member from '%s': %s"), ar->name, err.str);
+ } else {
+ /* Yes! - found it. */
+ break;
+ }
+ }
+ }
+
+ if (admininfo >= 2) {
+ printf(_(" new Debian package, version %d.%d.\n"
+ " size %jd bytes: control archive=%jd bytes.\n"),
+ version.major, version.minor,
+ (intmax_t)ar->size, (intmax_t)ctrllennum);
+ m_output(stdout, _("<standard output>"));
+ }
+ } else if (strncmp(versionbuf, "0.93", 4) == 0) {
+ char ctrllenbuf[40];
+ int l;
+
+ l = strlen(versionbuf);
+
+ if (strchr(versionbuf, '\n') == NULL)
+ ohshit(_("archive has no newlines in header"));
+ errstr = deb_version_parse(&version, versionbuf);
+ if (errstr)
+ ohshit(_("archive has invalid format version: %s"), errstr);
+
+ r = read_line(ar->fd, ctrllenbuf, 1, sizeof(ctrllenbuf) - 1);
+ if (r <= 0)
+ read_fail(r, debar, _("archive control member size"));
+ if (sscanf(ctrllenbuf, "%jd%c%d", (intmax_t *)&ctrllennum, &nlc, &dummy) != 2 ||
+ nlc != '\n')
+ ohshit(_("archive has malformed control member size '%s'"), ctrllenbuf);
+
+ if (admininfo) {
+ memberlen = ctrllennum;
+ } else {
+ memberlen = ar->size - ctrllennum - strlen(ctrllenbuf) - l;
+ if (fd_skip(ar->fd, ctrllennum, &err) < 0)
+ ohshit(_("cannot skip archive control member from '%s': %s"), ar->name,
+ err.str);
+ }
+
+ if (admininfo >= 2) {
+ printf(_(" old Debian package, version %d.%d.\n"
+ " size %jd bytes: control archive=%jd, main archive=%jd.\n"),
+ version.major, version.minor,
+ (intmax_t)ar->size, (intmax_t)ctrllennum,
+ (intmax_t)(ar->size - ctrllennum - strlen(ctrllenbuf) - l));
+ m_output(stdout, _("<standard output>"));
+ }
+ } else {
+ if (strncmp(versionbuf, "!<arch>", 7) == 0) {
+ notice(_("file looks like it might be an archive which has been\n"
+ " corrupted by being downloaded in ASCII mode"));
+ }
+
+ ohshit(_("'%.255s' is not a Debian format archive"), debar);
+ }
+
+ m_pipe(p1);
+ c1 = subproc_fork();
+ if (!c1) {
+ close(p1[0]);
+ if (fd_fd_copy(ar->fd, p1[1], memberlen, &err) < 0)
+ ohshit(_("cannot copy archive member from '%s' to decompressor pipe: %s"),
+ ar->name, err.str);
+ if (close(p1[1]))
+ ohshite(_("cannot close decompressor pipe"));
+ exit(0);
+ }
+ close(p1[1]);
+
+ if (taroption) {
+ m_pipe(p2);
+ p2_out = p2[1];
+ } else {
+ p2_out = 1;
+ }
+
+ c2 = subproc_fork();
+ if (!c2) {
+ if (taroption)
+ close(p2[0]);
+ decompress_filter(&decompress_params, p1[0], p2_out,
+ _("decompressing archive '%s' (size=%jd) member '%s'"),
+ ar->name, (intmax_t)ar->size,
+ admininfo ? ADMINMEMBER : DATAMEMBER);
+ exit(0);
+ }
+ close(p1[0]);
+ dpkg_ar_close(ar);
+
+ if (taroption) {
+ close(p2[1]);
+
+ c3 = subproc_fork();
+ if (!c3) {
+ struct command cmd;
+
+ command_init(&cmd, TAR, "tar");
+ command_add_arg(&cmd, "tar");
+
+ if ((taroption & DPKG_TAR_LIST) && (taroption & DPKG_TAR_EXTRACT))
+ command_add_arg(&cmd, "-xv");
+ else if (taroption & DPKG_TAR_EXTRACT)
+ command_add_arg(&cmd, "-x");
+ else if (taroption & DPKG_TAR_LIST)
+ command_add_arg(&cmd, "-tv");
+ else
+ internerr("unknown or missing tar action '%d'", taroption);
+
+ if (taroption & DPKG_TAR_PERMS)
+ command_add_arg(&cmd, "-p");
+ if (taroption & DPKG_TAR_NOMTIME)
+ command_add_arg(&cmd, "-m");
+
+ command_add_arg(&cmd, "-f");
+ command_add_arg(&cmd, "-");
+ command_add_arg(&cmd, "--warning=no-timestamp");
+
+ m_dup2(p2[0],0);
+ close(p2[0]);
+
+ unsetenv("TAR_OPTIONS");
+
+ if (dir) {
+ if (mkdir(dir, 0777) != 0) {
+ if (errno != EEXIST)
+ ohshite(_("failed to create directory"));
+
+ if (taroption & DPKG_TAR_CREATE_DIR)
+ ohshite(_("unexpected pre-existing pathname %s"), dir);
+ }
+ if (chdir(dir) != 0)
+ ohshite(_("failed to chdir to directory"));
+ }
+
+ command_exec(&cmd);
+ }
+ close(p2[0]);
+ subproc_reap(c3, "tar", 0);
+ }
+
+ subproc_reap(c2, _("<decompress>"), SUBPROC_NOPIPE);
+ if (c1 != -1)
+ subproc_reap(c1, _("paste"), 0);
+ if (version.major == 0 && admininfo) {
+ /* Handle the version as a float to preserve the behaviour of old code,
+ * because even if the format is defined to be padded by 0's that might
+ * not have been always true for really ancient versions... */
+ while (version.minor && (version.minor % 10) == 0)
+ version.minor /= 10;
+
+ if (version.minor == 931)
+ movecontrolfiles(dir, OLDOLDDEBDIR);
+ else if (version.minor == 932 || version.minor == 933)
+ movecontrolfiles(dir, OLDDEBDIR);
+ }
+}
+
+int
+do_ctrltarfile(const char *const *argv)
+{
+ const char *debar;
+
+ debar = *argv++;
+ if (debar == NULL)
+ badusage(_("--%s needs a .deb filename argument"), cipaction->olong);
+ if (*argv)
+ badusage(_("--%s takes only one argument (.deb filename)"),
+ cipaction->olong);
+
+ extracthalf(debar, NULL, DPKG_TAR_PASSTHROUGH, 1);
+
+ return 0;
+}
+
+int
+do_fsystarfile(const char *const *argv)
+{
+ const char *debar;
+
+ debar = *argv++;
+ if (debar == NULL)
+ badusage(_("--%s needs a .deb filename argument"),cipaction->olong);
+ if (*argv)
+ badusage(_("--%s takes only one argument (.deb filename)"),cipaction->olong);
+ extracthalf(debar, NULL, DPKG_TAR_PASSTHROUGH, 0);
+
+ return 0;
+}
+
+int
+do_control(const char *const *argv)
+{
+ const char *debar, *dir;
+
+ debar = *argv++;
+ if (debar == NULL)
+ badusage(_("--%s needs a .deb filename argument"), cipaction->olong);
+
+ dir = *argv++;
+ if (dir == NULL)
+ dir = EXTRACTCONTROLDIR;
+ else if (*argv)
+ badusage(_("--%s takes at most two arguments (.deb and directory)"),
+ cipaction->olong);
+
+ extracthalf(debar, dir, DPKG_TAR_EXTRACT, 1);
+
+ return 0;
+}
+
+int
+do_extract(const char *const *argv)
+{
+ const char *debar, *dir;
+ enum dpkg_tar_options options = DPKG_TAR_EXTRACT | DPKG_TAR_PERMS;
+
+ if (opt_verbose)
+ options |= DPKG_TAR_LIST;
+
+ debar = *argv++;
+ if (debar == NULL)
+ badusage(_("--%s needs .deb filename and directory arguments"),
+ cipaction->olong);
+
+ dir = *argv++;
+ if (dir == NULL)
+ badusage(_("--%s needs a target directory.\n"
+ "Perhaps you should be using dpkg --install ?"),
+ cipaction->olong);
+ else if (*argv)
+ badusage(_("--%s takes at most two arguments (.deb and directory)"),
+ cipaction->olong);
+
+ extracthalf(debar, dir, options, 0);
+
+ return 0;
+}
+
+int
+do_vextract(const char *const *argv)
+{
+ /* XXX: Backward compatibility. */
+ opt_verbose = 1;
+ return do_extract(argv);
+}
+
+int
+do_raw_extract(const char *const *argv)
+{
+ enum dpkg_tar_options data_options;
+ const char *debar, *dir;
+ char *control_dir;
+
+ debar = *argv++;
+ if (debar == NULL)
+ badusage(_("--%s needs .deb filename and directory arguments"),
+ cipaction->olong);
+ else if (strcmp(debar, "-") == 0)
+ badusage(_("--%s does not support (yet) reading the .deb from standard input"),
+ cipaction->olong);
+
+ dir = *argv++;
+ if (dir == NULL)
+ badusage(_("--%s needs a target directory.\n"
+ "Perhaps you should be using dpkg --install ?"),
+ cipaction->olong);
+ else if (*argv)
+ badusage(_("--%s takes at most two arguments (.deb and directory)"),
+ cipaction->olong);
+
+ control_dir = str_fmt("%s/%s", dir, EXTRACTCONTROLDIR);
+
+ data_options = DPKG_TAR_EXTRACT | DPKG_TAR_PERMS;
+ if (opt_verbose)
+ data_options |= DPKG_TAR_LIST;
+
+ extracthalf(debar, dir, data_options, 0);
+ extracthalf(debar, control_dir, DPKG_TAR_EXTRACT | DPKG_TAR_CREATE_DIR, 1);
+
+ free(control_dir);
+
+ return 0;
+}
diff --git a/src/deb/info.c b/src/deb/info.c
new file mode 100644
index 0000000..794eeb0
--- /dev/null
+++ b/src/deb/info.c
@@ -0,0 +1,305 @@
+/*
+ * dpkg-deb - construction and deconstruction of *.deb archives
+ * info.c - providing information
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2001 Wichert Akkerman
+ * Copyright © 2007-2015 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/parsedump.h>
+#include <dpkg/pkg-format.h>
+#include <dpkg/buffer.h>
+#include <dpkg/path.h>
+#include <dpkg/options.h>
+
+#include "dpkg-deb.h"
+
+static void cu_info_prepare(int argc, void **argv) {
+ char *dir;
+
+ dir = argv[0];
+ path_remove_tree(dir);
+ free(dir);
+}
+
+static void info_prepare(const char *const **argvp,
+ const char **debarp,
+ const char **dirp,
+ int admininfo) {
+ char *dbuf;
+
+ *debarp= *(*argvp)++;
+ if (!*debarp) badusage(_("--%s needs a .deb filename argument"),cipaction->olong);
+
+ dbuf = mkdtemp(path_make_temp_template("dpkg-deb"));
+ if (!dbuf)
+ ohshite(_("unable to create temporary directory"));
+ *dirp = dbuf;
+
+ push_cleanup(cu_info_prepare, -1, 1, (void *)dbuf);
+ extracthalf(*debarp, dbuf, DPKG_TAR_EXTRACT | DPKG_TAR_NOMTIME, admininfo);
+}
+
+static int ilist_select(const struct dirent *de) {
+ return strcmp(de->d_name,".") && strcmp(de->d_name,"..");
+}
+
+static void
+info_spew(const char *debar, const char *dir, const char *const *argv)
+{
+ struct dpkg_error err;
+ const char *component;
+ struct varbuf controlfile = VARBUF_INIT;
+ int fd;
+ int re= 0;
+
+ while ((component = *argv++) != NULL) {
+ varbuf_reset(&controlfile);
+ varbuf_printf(&controlfile, "%s/%s", dir, component);
+
+ fd = open(controlfile.buf, O_RDONLY);
+ if (fd >= 0) {
+ if (fd_fd_copy(fd, 1, -1, &err) < 0)
+ ohshit(_("cannot extract control file '%s' from '%s': %s"),
+ controlfile.buf, debar, err.str);
+ close(fd);
+ } else if (errno == ENOENT) {
+ notice(_("'%.255s' contains no control component '%.255s'"),
+ debar, component);
+ re++;
+ } else {
+ ohshite(_("open component '%.255s' (in %.255s) failed in an unexpected way"),
+ component, dir);
+ }
+ }
+ varbuf_destroy(&controlfile);
+
+ if (re > 0)
+ ohshit(P_("%d requested control component is missing",
+ "%d requested control components are missing", re), re);
+}
+
+static void
+info_list(const char *debar, const char *dir)
+{
+ char interpreter[INTERPRETER_MAX+1], *p;
+ int il, lines;
+ struct varbuf controlfile = VARBUF_INIT;
+ struct dirent **cdlist, *cdep;
+ int cdn, n;
+ FILE *cc;
+ struct stat stab;
+ int c;
+
+ cdn = scandir(dir, &cdlist, &ilist_select, alphasort);
+ if (cdn == -1)
+ ohshite(_("cannot scan directory '%.255s'"), dir);
+
+ for (n = 0; n < cdn; n++) {
+ cdep = cdlist[n];
+
+ varbuf_reset(&controlfile);
+ varbuf_printf(&controlfile, "%s/%s", dir, cdep->d_name);
+
+ if (stat(controlfile.buf, &stab))
+ ohshite(_("cannot stat '%.255s' (in '%.255s')"), cdep->d_name, dir);
+ if (S_ISREG(stab.st_mode)) {
+ cc = fopen(controlfile.buf, "r");
+ if (!cc)
+ ohshite(_("cannot open '%.255s' (in '%.255s')"), cdep->d_name, dir);
+ lines = 0;
+ interpreter[0] = '\0';
+ if (getc(cc) == '#') {
+ if (getc(cc) == '!') {
+ while ((c= getc(cc))== ' ');
+ p=interpreter; *p++='#'; *p++='!'; il=2;
+ while (il < INTERPRETER_MAX && !c_isspace(c) && c != EOF) {
+ *p++= c; il++; c= getc(cc);
+ }
+ *p = '\0';
+ if (c=='\n') lines++;
+ }
+ }
+ while ((c= getc(cc))!= EOF) { if (c == '\n') lines++; }
+ if (ferror(cc))
+ ohshite(_("failed to read '%.255s' (in '%.255s')"), cdep->d_name, dir);
+ fclose(cc);
+ printf(_(" %7jd bytes, %5d lines %c %-20.127s %.127s\n"),
+ (intmax_t)stab.st_size, lines,
+ (S_IXUSR & stab.st_mode) ? '*' : ' ',
+ cdep->d_name, interpreter);
+ } else {
+ printf(_(" not a plain file %.255s\n"), cdep->d_name);
+ }
+ free(cdep);
+ }
+ free(cdlist);
+
+ varbuf_reset(&controlfile);
+ varbuf_printf(&controlfile, "%s/%s", dir, CONTROLFILE);
+ cc = fopen(controlfile.buf, "r");
+ if (!cc) {
+ if (errno != ENOENT)
+ ohshite(_("failed to read '%.255s' (in '%.255s')"), CONTROLFILE, dir);
+ warning(_("no 'control' file in control archive!"));
+ } else {
+ lines= 1;
+ while ((c= getc(cc))!= EOF) {
+ if (lines)
+ putc(' ', stdout);
+ putc(c, stdout);
+ lines= c=='\n';
+ }
+ if (!lines)
+ putc('\n', stdout);
+
+ if (ferror(cc))
+ ohshite(_("failed to read '%.255s' (in '%.255s')"), CONTROLFILE, dir);
+ fclose(cc);
+ }
+
+ m_output(stdout, _("<standard output>"));
+ varbuf_destroy(&controlfile);
+}
+
+static void
+info_field(const char *debar, const char *dir, const char *const *fields,
+ enum fwriteflags fieldflags)
+{
+ char *controlfile;
+ struct varbuf str = VARBUF_INIT;
+ struct pkginfo *pkg;
+ int i;
+
+ controlfile = str_fmt("%s/%s", dir, CONTROLFILE);
+ parsedb(controlfile, pdb_parse_binary | pdb_ignore_archives, &pkg);
+ free(controlfile);
+
+ for (i = 0; fields[i]; i++) {
+ const struct fieldinfo *field;
+ const struct arbitraryfield *arbfield;
+
+ varbuf_reset(&str);
+ field = find_field_info(fieldinfos, fields[i]);
+ if (field) {
+ field->wcall(&str, pkg, &pkg->available, fieldflags, field);
+ } else {
+ arbfield = find_arbfield_info(pkg->available.arbs, fields[i]);
+ if (arbfield)
+ varbuf_add_arbfield(&str, arbfield, fieldflags);
+ }
+ varbuf_end_str(&str);
+
+ if (fieldflags & fw_printheader)
+ printf("%s", str.buf);
+ else
+ printf("%s\n", str.buf);
+ }
+
+ m_output(stdout, _("<standard output>"));
+
+ varbuf_destroy(&str);
+}
+
+int
+do_showinfo(const char *const *argv)
+{
+ const char *debar, *dir;
+ char *controlfile;
+ struct dpkg_error err;
+ struct pkginfo *pkg;
+ struct pkg_format_node *fmt;
+
+ fmt = pkg_format_parse(showformat, &err);
+ if (!fmt)
+ ohshit(_("error in show format: %s"), err.str);
+
+ info_prepare(&argv, &debar, &dir, 1);
+
+ controlfile = str_fmt("%s/%s", dir, CONTROLFILE);
+ parsedb(controlfile, pdb_parse_binary | pdb_ignore_archives, &pkg);
+ pkg_format_show(fmt, pkg, &pkg->available);
+ pkg_format_free(fmt);
+ free(controlfile);
+
+ return 0;
+}
+
+int
+do_info(const char *const *argv)
+{
+ const char *debar, *dir;
+
+ if (*argv && argv[1]) {
+ info_prepare(&argv, &debar, &dir, 1);
+ info_spew(debar, dir, argv);
+ } else {
+ info_prepare(&argv, &debar, &dir, 2);
+ info_list(debar, dir);
+ }
+
+ return 0;
+}
+
+int
+do_field(const char *const *argv)
+{
+ const char *debar, *dir;
+
+ info_prepare(&argv, &debar, &dir, 1);
+ if (*argv) {
+ info_field(debar, dir, argv, argv[1] != NULL ? fw_printheader : 0);
+ } else {
+ static const char *const controlonly[] = { CONTROLFILE, NULL };
+ info_spew(debar, dir, controlonly);
+ }
+
+ return 0;
+}
+
+int
+do_contents(const char *const *argv)
+{
+ const char *debar = *argv++;
+
+ if (debar == NULL || *argv)
+ badusage(_("--%s takes exactly one argument"), cipaction->olong);
+ extracthalf(debar, NULL, DPKG_TAR_LIST, 0);
+
+ return 0;
+}
diff --git a/src/deb/main.c b/src/deb/main.c
new file mode 100644
index 0000000..4a16738
--- /dev/null
+++ b/src/deb/main.c
@@ -0,0 +1,311 @@
+/*
+ * dpkg-deb - construction and deconstruction of *.deb archives
+ * main.c - main program
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-2014 Guillem Jover <guillem@debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <limits.h>
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/compress.h>
+#include <dpkg/options.h>
+
+#include "dpkg-deb.h"
+
+const char *showformat = "${Package}\t${Version}\n";
+
+static int
+printversion(const char *const *argv)
+{
+ printf(_("Debian '%s' package archive backend version %s.\n"),
+ BACKEND, PACKAGE_RELEASE);
+ printf(_(
+"This is free software; see the GNU General Public License version 2 or\n"
+"later for copying conditions. There is NO warranty.\n"));
+
+ m_output(stdout, _("<standard output>"));
+
+ return 0;
+}
+
+static int
+usage(const char *const *argv)
+{
+ printf(_(
+"Usage: %s [<option>...] <command>\n"
+"\n"), BACKEND);
+
+ printf(_(
+"Commands:\n"
+" -b|--build <directory> [<deb>] Build an archive.\n"
+" -c|--contents <deb> List contents.\n"
+" -I|--info <deb> [<cfile>...] Show info to stdout.\n"
+" -W|--show <deb> Show information on package(s)\n"
+" -f|--field <deb> [<cfield>...] Show field(s) to stdout.\n"
+" -e|--control <deb> [<directory>] Extract control info.\n"
+" -x|--extract <deb> <directory> Extract files.\n"
+" -X|--vextract <deb> <directory> Extract & list files.\n"
+" -R|--raw-extract <deb> <directory>\n"
+" Extract control info and files.\n"
+" --ctrl-tarfile <deb> Output control tarfile.\n"
+" --fsys-tarfile <deb> Output filesystem tarfile.\n"
+"\n"));
+
+ printf(_(
+" -?, --help Show this help message.\n"
+" --version Show the version.\n"
+"\n"));
+
+ printf(_(
+"<deb> is the filename of a Debian format archive.\n"
+"<cfile> is the name of an administrative file component.\n"
+"<cfield> is the name of a field in the main 'control' file.\n"
+"\n"));
+
+ printf(_(
+"Options:\n"
+" -v, --verbose Enable verbose output.\n"
+" -D, --debug Enable debugging output.\n"
+" --showformat=<format> Use alternative format for --show.\n"
+" --deb-format=<format> Select archive format.\n"
+" Allowed values: 0.939000, 2.0 (default).\n"
+" --nocheck Suppress control file check (build bad\n"
+" packages).\n"
+" --root-owner-group Forces the owner and groups to root.\n"
+" --threads-max=<threads> Use at most <threads> with compressor.\n"
+" --[no-]uniform-compression Use the compression params on all members.\n"
+" -z# Set the compression level when building.\n"
+" -Z<type> Set the compression type used when building.\n"
+" Allowed types: gzip, xz, zstd, none.\n"
+" -S<strategy> Set the compression strategy when building.\n"
+" Allowed values: none; extreme (xz);\n"
+" filtered, huffman, rle, fixed (gzip).\n"
+"\n"));
+
+ printf(_(
+"Format syntax:\n"
+" A format is a string that will be output for each package. The format\n"
+" can include the standard escape sequences \\n (newline), \\r (carriage\n"
+" return) or \\\\ (plain backslash). Package information can be included\n"
+" by inserting variable references to package fields using the ${var[;width]}\n"
+" syntax. Fields will be right-aligned unless the width is negative in which\n"
+" case left alignment will be used.\n"));
+
+ printf(_(
+"\n"
+"Use 'dpkg' to install and remove packages from your system, or\n"
+"'apt' or 'aptitude' for user-friendly package management. Packages\n"
+"unpacked using 'dpkg-deb --extract' will be incorrectly installed !\n"));
+
+ m_output(stdout, _("<standard output>"));
+
+ return 0;
+}
+
+static const char printforhelp[] =
+ N_("Type dpkg-deb --help for help about manipulating *.deb files;\n"
+ "Type dpkg --help for help about installing and deinstalling packages.");
+
+int debugflag = 0;
+int nocheckflag = 0;
+int opt_verbose = 0;
+int opt_root_owner_group = 0;
+int opt_uniform_compression = 1;
+
+struct deb_version deb_format = DEB_VERSION(2, 0);
+
+static void
+set_deb_format(const struct cmdinfo *cip, const char *value)
+{
+ const char *err;
+
+ err = deb_version_parse(&deb_format, value);
+ if (err)
+ badusage(_("invalid deb format version: %s"), err);
+
+ if ((deb_format.major == 2 && deb_format.minor == 0) ||
+ (deb_format.major == 0 && deb_format.minor == 939000))
+ return;
+ else
+ badusage(_("unknown deb format version: %s"), value);
+}
+
+struct compress_params compress_params = {
+ .type = DPKG_DEB_DEFAULT_COMPRESSOR,
+ .strategy = COMPRESSOR_STRATEGY_NONE,
+ .level = -1,
+ .threads_max = -1,
+};
+
+static long
+parse_compress_level(const char *str)
+{
+ long value;
+ char *end;
+
+ errno = 0;
+ value = strtol(str, &end, 10);
+ if (str == end || *end != '\0' || errno != 0)
+ return 0;
+
+ return value;
+}
+
+static void
+set_compress_level(const struct cmdinfo *cip, const char *value)
+{
+ compress_params.level = dpkg_options_parse_arg_int(cip, value);
+}
+
+static void
+set_compress_strategy(const struct cmdinfo *cip, const char *value)
+{
+ compress_params.strategy = compressor_get_strategy(value);
+ if (compress_params.strategy == COMPRESSOR_STRATEGY_UNKNOWN)
+ badusage(_("unknown compression strategy '%s'!"), value);
+}
+
+static enum compressor_type
+parse_compress_type(const char *value)
+{
+ enum compressor_type type;
+
+ type = compressor_find_by_name(value);
+ if (type == COMPRESSOR_TYPE_UNKNOWN)
+ badusage(_("unknown compression type '%s'!"), value);
+ if (type == COMPRESSOR_TYPE_LZMA)
+ badusage(_("obsolete compression type '%s'; use xz instead"), value);
+ if (type == COMPRESSOR_TYPE_BZIP2)
+ badusage(_("obsolete compression type '%s'; use xz or gzip instead"), value);
+
+ return type;
+}
+
+static void
+set_compress_type(const struct cmdinfo *cip, const char *value)
+{
+ compress_params.type = parse_compress_type(value);
+}
+
+static long
+parse_threads_max(const char *str)
+{
+ long value;
+ char *end;
+
+ errno = 0;
+ value = strtol(str, &end, 10);
+ if (str == end || *end != '\0' || errno != 0)
+ return 0;
+
+ return value;
+}
+
+static void
+set_threads_max(const struct cmdinfo *cip, const char *value)
+{
+ compress_params.threads_max = dpkg_options_parse_arg_int(cip, value);
+}
+
+static const struct cmdinfo cmdinfos[]= {
+ ACTION("build", 'b', 0, do_build),
+ ACTION("contents", 'c', 0, do_contents),
+ ACTION("control", 'e', 0, do_control),
+ ACTION("info", 'I', 0, do_info),
+ ACTION("field", 'f', 0, do_field),
+ ACTION("extract", 'x', 0, do_extract),
+ ACTION("vextract", 'X', 0, do_vextract),
+ ACTION("raw-extract", 'R', 0, do_raw_extract),
+ ACTION("ctrl-tarfile", 0, 0, do_ctrltarfile),
+ ACTION("fsys-tarfile", 0, 0, do_fsystarfile),
+ ACTION("show", 'W', 0, do_showinfo),
+ ACTION("help", '?', 0, usage),
+ ACTION("version", 0, 0, printversion),
+
+ { "deb-format", 0, 1, NULL, NULL, set_deb_format },
+ { "debug", 'D', 0, &debugflag, NULL, NULL, 1 },
+ { "verbose", 'v', 0, &opt_verbose, NULL, NULL, 1 },
+ { "nocheck", 0, 0, &nocheckflag, NULL, NULL, 1 },
+ { "root-owner-group", 0, 0, &opt_root_owner_group, NULL, NULL, 1 },
+ { "threads-max", 0, 1, NULL, NULL, set_threads_max },
+ { "uniform-compression", 0, 0, &opt_uniform_compression, NULL, NULL, 1 },
+ { "no-uniform-compression", 0, 0, &opt_uniform_compression, NULL, NULL, 0 },
+ { NULL, 'z', 1, NULL, NULL, set_compress_level },
+ { NULL, 'Z', 1, NULL, NULL, set_compress_type },
+ { NULL, 'S', 1, NULL, NULL, set_compress_strategy },
+ { "showformat", 0, 1, NULL, &showformat, NULL },
+ { NULL, 0, 0, NULL, NULL, NULL }
+};
+
+int main(int argc, const char *const *argv) {
+ struct dpkg_error err;
+ char *env;
+ int ret;
+
+ dpkg_locales_init(PACKAGE);
+ dpkg_program_init(BACKEND);
+ /* XXX: Integrate this into options initialization/parsing. */
+ env = getenv("DPKG_DEB_THREADS_MAX");
+ if (str_is_set(env))
+ compress_params.threads_max = parse_threads_max(env);
+ env = getenv("DPKG_DEB_COMPRESSOR_TYPE");
+ if (str_is_set(env))
+ compress_params.type = parse_compress_type(env);
+ env = getenv("DPKG_DEB_COMPRESSOR_LEVEL");
+ if (str_is_set(env))
+ compress_params.level = parse_compress_level(env);
+ dpkg_options_parse(&argv, cmdinfos, printforhelp);
+
+ if (!cipaction) badusage(_("need an action option"));
+
+ if (!compressor_check_params(&compress_params, &err))
+ badusage(_("invalid compressor parameters: %s"), err.str);
+
+ if (opt_uniform_compression &&
+ (compress_params.type != COMPRESSOR_TYPE_NONE &&
+ compress_params.type != COMPRESSOR_TYPE_GZIP &&
+ compress_params.type != COMPRESSOR_TYPE_ZSTD &&
+ compress_params.type != COMPRESSOR_TYPE_XZ))
+ badusage(_("unsupported compression type '%s' with uniform compression"),
+ compressor_get_name(compress_params.type));
+
+ ret = cipaction->action(argv);
+
+ dpkg_program_done();
+ dpkg_locales_done();
+
+ return ret;
+}