summaryrefslogblamecommitdiff
path: root/src/archive.c
blob: e5092dc6b4b5a3ef967c99a5b270d5ca553bd568 (plain) (tree)




























































































































































































































































































































































                                                                                    
/* 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;
}