diff options
Diffstat (limited to '')
-rw-r--r-- | src/deb/build.c | 729 | ||||
-rw-r--r-- | src/deb/dpkg-deb.h | 89 | ||||
-rw-r--r-- | src/deb/extract.c | 508 | ||||
-rw-r--r-- | src/deb/info.c | 341 | ||||
-rw-r--r-- | src/deb/main.c | 325 |
5 files changed, 1992 insertions, 0 deletions
diff --git a/src/deb/build.c b/src/deb/build.c new file mode 100644 index 0000000..597872b --- /dev/null +++ b/src/deb/build.c @@ -0,0 +1,729 @@ +/* + * 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 (!opt_nocheck && 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 (opt_nocheck) { + 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 = compress_params_deb0; + + 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..eafb5b7 --- /dev/null +++ b/src/deb/dpkg-deb.h @@ -0,0 +1,89 @@ +/* + * 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 opt_debug; +extern int opt_nocheck; +extern const char *opt_showformat; + +extern struct deb_version deb_format; + +enum DPKG_ATTR_ENUM_FLAGS 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 struct compress_params compress_params_deb0; +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..8b78a7e --- /dev/null +++ b/src/deb/extract.c @@ -0,0 +1,508 @@ +/* + * 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 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) +{ + 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 rc; + int dummy; + pid_t c1=0,c2,c3; + int p1[2], p2[2]; + int p2_out; + char nlc; + struct compress_params decompress_params = { + .type = COMPRESSOR_TYPE_GZIP, + .threads_max = compress_params.threads_max, + }; + + ar = dpkg_ar_open(debar); + + rc = read_line(ar->fd, versionbuf, strlen(DPKG_AR_MAGIC), sizeof(versionbuf) - 1); + if (rc <= 0) + read_fail(rc, debar, _("archive magic version number")); + + if (strcmp(versionbuf, DPKG_AR_MAGIC) == 0) { + int adminmember = -1; + bool header_done = false; + + ctrllennum= 0; + for (;;) { + struct dpkg_ar_hdr arh; + + rc = fd_read(ar->fd, &arh, sizeof(arh)); + if (rc != sizeof(arh)) + read_fail(rc, 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); + rc = fd_read(ar->fd, infobuf, memberlen + (memberlen & 1)); + if (rc != (memberlen + (memberlen & 1))) + read_fail(rc, 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); + + rc = read_line(ar->fd, ctrllenbuf, 1, sizeof(ctrllenbuf) - 1); + if (rc <= 0) + read_fail(rc, 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); + dpkg_ar_close(ar); + 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 behavior 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..6598663 --- /dev/null +++ b/src/deb/info.c @@ -0,0 +1,341 @@ +/* + * 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 re= 0; + + while ((component = *argv++) != NULL) { + int fd; + + 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 char * +info_interpreter(FILE *cc, int *lines) +{ + char interpreter[INTERPRETER_MAX + 1]; + int c; + + *lines = 0; + interpreter[0] = '\0'; + if (getc(cc) == '#' && getc(cc) == '!') { + char *p; + int il; + + 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)++; + } + + return m_strdup(interpreter); +} + +static void +info_list(const char *debar, const char *dir) +{ + struct varbuf controlfile = VARBUF_INIT; + struct dirent **cdlist; + int cdn, n; + FILE *cc; + + cdn = scandir(dir, &cdlist, &ilist_select, alphasort); + if (cdn == -1) + ohshite(_("cannot scan directory '%.255s'"), dir); + + for (n = 0; n < cdn; n++) { + struct dirent *cdep; + struct stat stab; + + 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)) { + int exec_mark = (S_IXUSR & stab.st_mode) ? '*' : ' '; + char *interpreter; + int lines; + + cc = fopen(controlfile.buf, "r"); + if (!cc) + ohshite(_("cannot open '%.255s' (in '%.255s')"), cdep->d_name, dir); + + interpreter = info_interpreter(cc, &lines); + + if (ferror(cc)) + ohshite(_("failed to read '%.255s' (in '%.255s')"), cdep->d_name, dir); + fclose(cc); + if (str_is_set(interpreter)) + printf(_(" %7jd bytes, %5d lines %c %-20.127s %.127s\n"), + (intmax_t)stab.st_size, lines, exec_mark, cdep->d_name, + interpreter); + else + printf(_(" %7jd bytes, %5d lines %c %.127s\n"), + (intmax_t)stab.st_size, lines, exec_mark, cdep->d_name); + + free(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 { + int lines, c; + + 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; + + varbuf_reset(&str); + field = find_field_info(fieldinfos, fields[i]); + if (field) { + field->wcall(&str, pkg, &pkg->available, fieldflags, field); + } else { + const struct arbitraryfield *arbfield; + + 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(opt_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..0217278 --- /dev/null +++ b/src/deb/main.c @@ -0,0 +1,325 @@ +/* + * 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 *opt_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 opt_debug = 0; +int opt_nocheck = 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_deb0 = { + .type = COMPRESSOR_TYPE_GZIP, + .strategy = COMPRESSOR_STRATEGY_NONE, + .level = -1, + .threads_max = -1, +}; + +struct compress_params compress_params = { + .type = 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, &opt_debug, NULL, NULL, 1 }, + { "verbose", 'v', 0, &opt_verbose, NULL, NULL, 1 }, + { "nocheck", 0, 0, &opt_nocheck, 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, &opt_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 && deb_format.major == 0) + badusage(_("unsupported deb format '%d.%d' with non-uniform compression"), + deb_format.major, deb_format.minor); + + if (deb_format.major == 0) + compress_params = compress_params_deb0; + + 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; +} |