| /* |
| * dpkg-deb - construction and deconstruction of *.deb archives |
| * build.c - building archives |
| * |
| * Copyright © 1994,1995 Ian Jackson <ian@chiark.greenend.org.uk> |
| * Copyright © 2000,2001 Wichert Akkerman <wakkerma@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 <http://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 <ctype.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/dpkg-db.h> |
| #include <dpkg/path.h> |
| #include <dpkg/varbuf.h> |
| #include <dpkg/fdio.h> |
| #include <dpkg/buffer.h> |
| #include <dpkg/subproc.h> |
| #include <dpkg/compress.h> |
| #include <dpkg/ar.h> |
| #include <dpkg/options.h> |
| |
| #include "dpkg-deb.h" |
| |
| /** |
| * Simple structure to store information about a file. |
| */ |
| struct file_info { |
| struct file_info *next; |
| struct stat st; |
| 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; |
| } |
| |
| /** |
| * Read a filename from the file descriptor and create a file_info struct. |
| * |
| * @return A file_info struct or NULL if there is nothing to read. |
| */ |
| static struct file_info * |
| file_info_get(const char *root, int fd) |
| { |
| static struct varbuf fn = VARBUF_INIT; |
| struct file_info *fi; |
| size_t root_len; |
| |
| varbuf_reset(&fn); |
| root_len = varbuf_printf(&fn, "%s/", root); |
| |
| while (1) { |
| int res; |
| |
| varbuf_grow(&fn, 1); |
| res = fd_read(fd, (fn.buf + fn.used), 1); |
| if (res < 0) |
| return NULL; |
| if (res == 0) /* EOF -> parent died. */ |
| return NULL; |
| if (fn.buf[fn.used] == '\0') |
| break; |
| |
| varbuf_trunc(&fn, fn.used + 1); |
| if (fn.used >= MAXFILENAME) |
| ohshit(_("file name '%.50s...' is too long"), fn.buf + root_len); |
| } |
| |
| fi = file_info_new(fn.buf + root_len); |
| if (lstat(fn.buf, &(fi->st)) != 0) |
| ohshite(_("unable to stat file name '%.250s'"), fn.buf); |
| |
| return fi; |
| } |
| |
| /** |
| * 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 const char *const maintainerscripts[] = { |
| PREINSTFILE, |
| POSTINSTFILE, |
| PRERMFILE, |
| POSTRMFILE, |
| NULL, |
| }; |
| |
| /** |
| * Check control directory and file permissions. |
| */ |
| static void |
| check_file_perms(const char *dir) |
| { |
| struct varbuf path = VARBUF_INIT; |
| const char *const *mscriptp; |
| struct stat mscriptstab; |
| |
| varbuf_printf(&path, "%s/%s/", dir, BUILDCONTROLDIR); |
| 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/%s", dir, BUILDCONTROLDIR, *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 *dir) |
| { |
| FILE *cf; |
| struct varbuf controlfile = VARBUF_INIT; |
| char conffilename[MAXCONFFILENAME + 1]; |
| struct file_info *conffiles_head = NULL; |
| struct file_info *conffiles_tail = NULL; |
| |
| varbuf_printf(&controlfile, "%s/%s/%s", dir, BUILDCONTROLDIR, CONFFILESFILE); |
| |
| cf = fopen(controlfile.buf, "r"); |
| if (cf == NULL) { |
| if (errno == ENOENT) |
| return; |
| |
| ohshite(_("error opening conffiles file")); |
| } |
| |
| while (fgets(conffilename, MAXCONFFILENAME + 1, cf)) { |
| struct stat controlstab; |
| int n; |
| |
| n = strlen(conffilename); |
| if (!n) |
| ohshite(_("empty string from fgets reading conffiles")); |
| |
| if (conffilename[n - 1] != '\n') { |
| int c; |
| |
| warning(_("conffile name '%.50s...' is too long, or missing final newline"), |
| conffilename); |
| while ((c = getc(cf)) != EOF && c != '\n'); |
| |
| continue; |
| } |
| |
| conffilename[n - 1] = '\0'; |
| varbuf_reset(&controlfile); |
| varbuf_printf(&controlfile, "%s/%s", dir, conffilename); |
| if (lstat(controlfile.buf, &controlstab)) { |
| if (errno == ENOENT) { |
| if ((n > 1) && isspace(conffilename[n - 2])) |
| warning(_("conffile filename '%s' contains trailing white spaces"), |
| conffilename); |
| ohshit(_("conffile `%.250s' does not appear in package"), conffilename); |
| } else |
| ohshite(_("conffile `%.250s' is not stattable"), 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); |
| } |
| |
| static const char *arbitrary_fields[] = { |
| "Built-Using", |
| "Package-Type", |
| "Subarchitecture", |
| "Kernel-Version", |
| "Installer-Menu-Item", |
| "Homepage", |
| "Tag", |
| NULL |
| }; |
| |
| static const char private_prefix[] = "Private-"; |
| |
| static bool |
| known_arbitrary_field(const struct arbitraryfield *field) |
| { |
| const char **known; |
| |
| /* Always accept fields starting with a private field prefix. */ |
| if (strncasecmp(field->name, private_prefix, strlen(private_prefix)) == 0) |
| return true; |
| |
| for (known = arbitrary_fields; *known; known++) |
| if (strcasecmp(field->name, *known) == 0) |
| return true; |
| |
| return false; |
| } |
| |
| /** |
| * Perform some sanity checks on the to-be-built package. |
| * |
| * @return The pkginfo struct from the parsed control file. |
| */ |
| static struct pkginfo * |
| check_new_pkg(const char *dir) |
| { |
| struct pkginfo *pkg; |
| struct arbitraryfield *field; |
| char *controlfile; |
| int warns; |
| |
| /* Start by reading in the control file so we can check its contents. */ |
| m_asprintf(&controlfile, "%s/%s/%s", dir, BUILDCONTROLDIR, CONTROLFILE); |
| parsedb(controlfile, pdb_recordavailable | pdb_rejectstatus, &pkg); |
| |
| if (strspn(pkg->name, "abcdefghijklmnopqrstuvwxyz0123456789+-.") != |
| strlen(pkg->name)) |
| ohshit(_("package name has characters that aren't lowercase alphanums or `-+.'")); |
| if (pkg->priority == pri_other) |
| warning(_("'%s' contains user-defined Priority value '%s'"), |
| controlfile, pkg->otherpriority); |
| for (field = pkg->available.arbs; field; field = field->next) { |
| if (known_arbitrary_field(field)) |
| continue; |
| |
| warning(_("'%s' contains user-defined field '%s'"), controlfile, |
| field->name); |
| } |
| |
| free(controlfile); |
| |
| check_file_perms(dir); |
| check_conffiles(dir); |
| |
| warns = warning_get_count(); |
| if (warns) |
| warning(P_("ignoring %d warning about the control file(s)\n", |
| "ignoring %d warnings about the control file(s)\n", warns), |
| warns); |
| |
| return pkg; |
| } |
| |
| /** |
| * Generate the pathname for the to-be-built package. |
| * |
| * @return The pathname for the package being built. |
| */ |
| static char * |
| pkg_get_pathname(const char *dir, struct pkginfo *pkg) |
| { |
| char *path; |
| const char *versionstring, *arch_sep; |
| |
| versionstring = versiondescribe(&pkg->available.version, vdew_never); |
| arch_sep = pkg->available.arch[0] == '\0' ? "" : "_"; |
| m_asprintf(&path, "%s/%s_%s%s%s%s", dir, pkg->name, versionstring, |
| arch_sep, pkg->available.arch, DEBEXT); |
| |
| return path; |
| } |
| |
| /** |
| * Overly complex function that builds a .deb file. |
| */ |
| int |
| do_build(const char *const *argv) |
| { |
| const char *debar, *dir; |
| bool subdir; |
| char *tfbuf; |
| int arfd; |
| int p1[2], p2[2], p3[2], gzfd; |
| pid_t c1,c2,c3; |
| struct file_info *fi; |
| struct file_info *symlist = NULL; |
| struct file_info *symlist_end = NULL; |
| |
| /* Decode our arguments. */ |
| dir = *argv++; |
| if (!dir) |
| badusage(_("--%s needs a <directory> argument"), cipaction->olong); |
| subdir = false; |
| debar = *argv++; |
| if (debar != NULL) { |
| struct stat debarstab; |
| |
| if (*argv) badusage(_("--build takes at most two arguments")); |
| |
| if (stat(debar, &debarstab)) { |
| if (errno != ENOENT) |
| ohshite(_("unable to check for existence of archive `%.250s'"), debar); |
| } else if (S_ISDIR(debarstab.st_mode)) { |
| subdir = true; |
| } |
| } else { |
| char *m; |
| |
| m= m_malloc(strlen(dir) + sizeof(DEBEXT)); |
| strcpy(m, dir); |
| path_trim_slash_slashdot(m); |
| strcat(m, DEBEXT); |
| debar= m; |
| } |
| |
| /* Perform some sanity checks on the to-be-build package. */ |
| if (nocheckflag) { |
| if (subdir) |
| ohshit(_("target is directory - cannot skip control file check")); |
| warning(_("not checking contents of control area.")); |
| printf(_("dpkg-deb: building an unknown package in '%s'.\n"), debar); |
| } else { |
| struct pkginfo *pkg; |
| |
| pkg = check_new_pkg(dir); |
| if (subdir) |
| debar = pkg_get_pathname(debar, pkg); |
| printf(_("dpkg-deb: building package `%s' in `%s'.\n"), pkg->name, debar); |
| } |
| m_output(stdout, _("<standard output>")); |
| |
| /* Now that we have verified everything its time to actually |
| * build something. Let's start by making the ar-wrapper. */ |
| arfd = creat(debar, 0644); |
| if (arfd < 0) |
| ohshite(_("unable to create `%.255s'"), debar); |
| /* Fork a tar to package the control-section of the package. */ |
| unsetenv("TAR_OPTIONS"); |
| m_pipe(p1); |
| c1 = subproc_fork(); |
| if (!c1) { |
| m_dup2(p1[1],1); close(p1[0]); close(p1[1]); |
| if (chdir(dir)) |
| ohshite(_("failed to chdir to `%.255s'"), dir); |
| if (chdir(BUILDCONTROLDIR)) |
| ohshite(_("failed to chdir to `%.255s'"), ".../DEBIAN"); |
| execlp(TAR, "tar", "-cf", "-", "--format=gnu", ".", NULL); |
| ohshite(_("unable to execute %s (%s)"), "tar -cf", TAR); |
| } |
| close(p1[1]); |
| /* 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); |
| |
| /* And run gzip to compress our control archive. */ |
| c2 = subproc_fork(); |
| if (!c2) { |
| compress_filter(&compressor_gzip, p1[0], gzfd, 9, _("control member")); |
| exit(0); |
| } |
| close(p1[0]); |
| subproc_wait_check(c2, "gzip -9c", 0); |
| subproc_wait_check(c1, "tar -cf", 0); |
| |
| 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 (oldformatflag) { |
| 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(arfd, versionbuf, strlen(versionbuf)) < 0) |
| ohshite(_("error writing `%s'"), debar); |
| fd_fd_copy(gzfd, arfd, -1, _("control member")); |
| } else { |
| const char deb_magic[] = ARCHIVEVERSION "\n"; |
| |
| dpkg_ar_put_magic(debar, arfd); |
| dpkg_ar_member_put_mem(debar, arfd, DEBMAGIC, deb_magic, strlen(deb_magic)); |
| dpkg_ar_member_put_file(debar, arfd, ADMINMEMBER, gzfd, -1); |
| } |
| close(gzfd); |
| |
| /* Control is done, now we need to archive the data. */ |
| if (oldformatflag) { |
| /* 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 = arfd; |
| } else { |
| /* 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); |
| } |
| /* Fork off a tar. We will feed it a list of filenames on stdin later. */ |
| m_pipe(p1); |
| m_pipe(p2); |
| c1 = subproc_fork(); |
| if (!c1) { |
| m_dup2(p1[0],0); close(p1[0]); close(p1[1]); |
| m_dup2(p2[1],1); close(p2[0]); close(p2[1]); |
| if (chdir(dir)) |
| ohshite(_("failed to chdir to `%.255s'"), dir); |
| execlp(TAR, "tar", "-cf", "-", "--format=gnu", "--null", "-T", "-", "--no-recursion", NULL); |
| ohshite(_("unable to execute %s (%s)"), "tar -cf", TAR); |
| } |
| close(p1[0]); |
| close(p2[1]); |
| /* Of course we should not forget to compress the archive as well. */ |
| c2 = subproc_fork(); |
| if (!c2) { |
| close(p1[1]); |
| compress_filter(compressor, p2[0], gzfd, compress_level, _("data member")); |
| exit(0); |
| } |
| close(p2[0]); |
| |
| /* All the pipes are set, now lets run find, and start feeding |
| * filenames to tar. */ |
| m_pipe(p3); |
| c3 = subproc_fork(); |
| if (!c3) { |
| m_dup2(p3[1],1); close(p3[0]); close(p3[1]); |
| if (chdir(dir)) |
| ohshite(_("failed to chdir to `%.255s'"), dir); |
| execlp(FIND, "find", ".", "-path", "./" BUILDCONTROLDIR, "-prune", "-o", |
| "-print0", NULL); |
| ohshite(_("unable to execute %s (%s)"), "find", FIND); |
| } |
| close(p3[1]); |
| /* We need to reorder the files so we can make sure that symlinks |
| * will not appear before their target. */ |
| while ((fi = file_info_get(dir, p3[0])) != NULL) |
| if (S_ISLNK(fi->st.st_mode)) |
| file_info_list_append(&symlist, &symlist_end, fi); |
| else { |
| if (fd_write(p1[1], fi->fn, strlen(fi->fn) + 1) < 0) |
| ohshite(_("failed to write filename to tar pipe (%s)"), |
| _("data member")); |
| file_info_free(fi); |
| } |
| close(p3[0]); |
| subproc_wait_check(c3, "find", 0); |
| |
| for (fi= symlist;fi;fi= fi->next) |
| if (fd_write(p1[1], fi->fn, strlen(fi->fn) + 1) < 0) |
| ohshite(_("failed to write filename to tar pipe (%s)"), _("data member")); |
| /* All done, clean up wait for tar and gzip to finish their job. */ |
| close(p1[1]); |
| file_info_list_free(symlist); |
| subproc_wait_check(c2, _("<compress> from tar -cf"), 0); |
| subproc_wait_check(c1, "tar -cf", 0); |
| /* Okay, we have data.tar as well now, add it to the ar wrapper. */ |
| if (!oldformatflag) { |
| char datamember[16 + 1]; |
| |
| sprintf(datamember, "%s%s", DATAMEMBER, compressor->extension); |
| |
| if (lseek(gzfd, 0, SEEK_SET)) |
| ohshite(_("failed to rewind temporary file (%s)"), _("data member")); |
| |
| dpkg_ar_member_put_file(debar, arfd, datamember, gzfd, -1); |
| } |
| if (fsync(arfd)) |
| ohshite(_("unable to sync file '%s'"), debar); |
| if (close(arfd)) |
| ohshite(_("unable to close file '%s'"), debar); |
| |
| return 0; |
| } |