summaryrefslogtreecommitdiffstats
path: root/src/archive.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/archive.c')
-rw-r--r--src/archive.c349
1 files changed, 349 insertions, 0 deletions
diff --git a/src/archive.c b/src/archive.c
new file mode 100644
index 0000000..e5092dc
--- /dev/null
+++ b/src/archive.c
@@ -0,0 +1,349 @@
+/* archive.c - Alpine Package Keeper (APK)
+ *
+ * Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
+ * Copyright (C) 2008 Timo Teräs <timo.teras@iki.fi>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation. See http://www.gnu.org/ for details.
+ */
+
+#include <stdio.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <malloc.h>
+#include <string.h>
+#include <unistd.h>
+#include <sysexits.h>
+#include <sys/wait.h>
+
+#include "apk_defines.h"
+#include "apk_archive.h"
+
+#ifndef GUNZIP_BINARY
+#define GUNZIP_BINARY "/bin/gunzip"
+#endif
+
+struct tar_header {
+ /* ustar header, Posix 1003.1 */
+ char name[100]; /* 0-99 */
+ char mode[8]; /* 100-107 */
+ char uid[8]; /* 108-115 */
+ char gid[8]; /* 116-123 */
+ char size[12]; /* 124-135 */
+ char mtime[12]; /* 136-147 */
+ char chksum[8]; /* 148-155 */
+ char typeflag; /* 156-156 */
+ char linkname[100]; /* 157-256 */
+ char magic[8]; /* 257-264 */
+ char uname[32]; /* 265-296 */
+ char gname[32]; /* 297-328 */
+ char devmajor[8]; /* 329-336 */
+ char devminor[8]; /* 337-344 */
+ char prefix[155]; /* 345-499 */
+ char padding[12]; /* 500-512 */
+};
+
+static int get_dev_null(void)
+{
+ static int fd_null = 0;
+
+ if (fd_null == 0) {
+ fd_null = open("/dev/null", O_WRONLY);
+ if (fd_null < 0)
+ err(EX_OSFILE, "/dev/null");
+ }
+ return fd_null;
+
+}
+
+pid_t apk_open_gz(int *fd)
+{
+ int pipe_fd[2];
+ pid_t child_pid;
+
+ if (pipe(pipe_fd) < 0)
+ err(EX_OSERR, "pipe");
+
+ child_pid = fork();
+ if (child_pid < 0)
+ err(EX_OSERR, "fork");
+
+ if (child_pid == 0) {
+ close(pipe_fd[0]);
+ dup2(pipe_fd[1], STDOUT_FILENO);
+ dup2(*fd, STDIN_FILENO);
+ dup2(get_dev_null(), STDERR_FILENO);
+ close(pipe_fd[1]);
+ execl(GUNZIP_BINARY, "gunzip", "-c", NULL);
+ err(EX_UNAVAILABLE, GUNZIP_BINARY);
+ }
+
+ close(pipe_fd[1]);
+ *fd = pipe_fd[0];
+
+ return child_pid;
+}
+
+#define GET_OCTAL(s) apk_blob_uint(APK_BLOB_PTR_LEN(s, sizeof(s)), 8)
+
+static int do_splice(int from_fd, int to_fd, int len)
+{
+ int i = 0, r;
+
+ while (i != len) {
+ r = splice(from_fd, NULL, to_fd, NULL, len - i, SPLICE_F_MOVE);
+ if (r == -1)
+ return i;
+ i += r;
+ }
+
+ return i;
+}
+
+int apk_parse_tar(int fd, apk_archive_entry_parser parser, void *ctx)
+{
+ struct apk_archive_entry entry;
+ struct tar_header buf;
+ unsigned long offset = 0;
+ int end = 0, r;
+
+ memset(&entry, 0, sizeof(entry));
+ while (read(fd, &buf, 512) == 512) {
+ offset += 512;
+ if (buf.name[0] == '\0') {
+ if (end)
+ break;
+ end++;
+ continue;
+ }
+
+ entry = (struct apk_archive_entry){
+ .size = GET_OCTAL(buf.size),
+ .uid = GET_OCTAL(buf.uid),
+ .gid = GET_OCTAL(buf.gid),
+ .mode = GET_OCTAL(buf.mode) & 0777,
+ .mtime = GET_OCTAL(buf.mtime),
+ .name = entry.name,
+ .uname = buf.uname,
+ .gname = buf.gname,
+ .device = makedev(GET_OCTAL(buf.devmajor),
+ GET_OCTAL(buf.devminor)),
+ .read_fd = fd,
+ };
+
+ switch (buf.typeflag) {
+ case 'L':
+ if (entry.name != NULL)
+ free(entry.name);
+ entry.name = malloc(entry.size+1);
+ read(fd, entry.name, entry.size);
+ offset += entry.size;
+ entry.size = 0;
+ break;
+ case '0':
+ case '7': /* regular file */
+ entry.mode |= S_IFREG;
+ break;
+ case '1': /* hard link */
+ entry.mode |= S_IFREG;
+ entry.link_target = buf.linkname;
+ break;
+ case '2': /* symbolic link */
+ entry.mode |= S_IFLNK;
+ entry.link_target = buf.linkname;
+ break;
+ case '3': /* char device */
+ entry.mode |= S_IFCHR;
+ break;
+ case '4': /* block devicek */
+ entry.mode |= S_IFBLK;
+ break;
+ case '5': /* directory */
+ entry.mode |= S_IFDIR;
+ break;
+ default:
+ break;
+ }
+
+ if (entry.mode & S_IFMT) {
+ if (entry.name == NULL)
+ entry.name = strdup(buf.name);
+
+ /* callback parser function */
+ offset += entry.size;
+ r = parser(&entry, ctx);
+ if (r != 0)
+ return r;
+ offset -= entry.size;
+
+ free(entry.name);
+ entry.name = NULL;
+ }
+
+ if (entry.size)
+ offset += do_splice(fd, get_dev_null(), entry.size);
+
+ /* align to next 512 block */
+ if (offset & 511)
+ offset += do_splice(fd, get_dev_null(),
+ 512 - (offset & 511));
+ }
+
+ return 0;
+}
+
+int apk_parse_tar_gz(int fd, apk_archive_entry_parser parser, void *ctx)
+{
+ pid_t pid;
+ int r, status;
+
+ pid = apk_open_gz(&fd);
+ if (pid < 0)
+ return pid;
+
+ r = apk_parse_tar(fd, parser, ctx);
+ close(fd);
+ waitpid(pid, &status, 0);
+
+ return r;
+}
+
+apk_blob_t apk_archive_entry_read(struct apk_archive_entry *ae)
+{
+ char *str;
+ int pos = 0;
+ ssize_t r;
+
+ str = malloc(ae->size + 1);
+ pos = 0;
+ while (ae->size) {
+ r = read(ae->read_fd, &str[pos], ae->size);
+ if (r < 0) {
+ free(str);
+ return APK_BLOB_NULL;
+ }
+ pos += r;
+ ae->size -= r;
+ }
+ str[pos] = 0;
+
+ return APK_BLOB_PTR_LEN(str, pos+1);
+}
+
+int apk_archive_entry_extract(struct apk_archive_entry *ae, const char *fn)
+{
+ int r = -1;
+
+ if (fn == NULL)
+ fn = ae->name;
+
+ /* BIG HONKING FIXME */
+ unlink(fn);
+
+ switch (ae->mode & S_IFMT) {
+ case S_IFDIR:
+ r = mkdir(fn, ae->mode & 0777);
+ if (r < 0 && errno == EEXIST)
+ r = 0;
+ break;
+ case S_IFREG:
+ if (ae->link_target == NULL) {
+ r = open(fn, O_WRONLY | O_CREAT, ae->mode & 0777);
+ if (r < 0)
+ break;
+ ae->size -= do_splice(ae->read_fd, r, ae->size);
+ close(r);
+ r = ae->size ? -1 : 0;
+ } else {
+ r = link(ae->link_target, fn);
+ }
+ break;
+ case S_IFLNK:
+ r = symlink(ae->link_target, fn);
+ break;
+ case S_IFSOCK:
+ case S_IFBLK:
+ case S_IFCHR:
+ case S_IFIFO:
+ r = mknod(fn, ae->mode, ae->device);
+ break;
+ }
+ if (r != 0)
+ apk_error("Failed to extract %s\n", ae->name);
+ return r;
+}
+
+struct checksum_and_tee {
+ int in_fd, tee_fd;
+ void *ptr;
+};
+
+static void *__apk_checksum_and_tee(void *arg)
+{
+ struct checksum_and_tee *args = (struct checksum_and_tee *) arg;
+ char buf[2*1024];
+ int r, w, wt;
+ __off64_t offset;
+ csum_ctx_t ctx;
+ int dosplice = 1;
+
+ offset = lseek(args->in_fd, 0, SEEK_CUR);
+ csum_init(&ctx);
+ do {
+ r = read(args->in_fd, buf, sizeof(buf));
+ if (r <= 0)
+ break;
+
+ wt = 0;
+ do {
+ if (dosplice) {
+ w = splice(args->in_fd, &offset, args->tee_fd, NULL,
+ r - wt, SPLICE_F_MOVE);
+ if (w < 0) {
+ dosplice = 0;
+ continue;
+ }
+ } else {
+ w = write(args->tee_fd, &buf[wt], r - wt);
+ if (w < 0)
+ break;
+ }
+ wt += w;
+ } while (wt != r);
+
+ csum_process(&ctx, buf, r);
+ } while (r == sizeof(buf));
+
+ csum_finish(&ctx, args->ptr);
+ close(args->tee_fd);
+ close(args->in_fd);
+ free(args);
+
+ return NULL;
+}
+
+pthread_t apk_checksum_and_tee(int *fd, void *ptr)
+{
+ struct checksum_and_tee *args;
+ int fds[2];
+ pthread_t tid;
+
+ if (pipe(fds) < 0)
+ return -1;
+
+ fcntl(fds[0], F_SETFD, FD_CLOEXEC);
+ fcntl(fds[1], F_SETFD, FD_CLOEXEC);
+
+ args = malloc(sizeof(*args));
+ *args = (struct checksum_and_tee){ *fd, fds[1], ptr };
+ if (pthread_create(&tid, NULL, __apk_checksum_and_tee, args) < 0)
+ return -1;
+
+ *fd = fds[0];
+ return tid;
+}
+