archive.c 5.35 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
/* 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 */
};

50
#define GET_OCTAL(s) apk_blob_uint(APK_BLOB_PTR_LEN(s, sizeof(s)), 8)
51

52 53 54 55
struct apk_tar_entry_istream {
	struct apk_istream is;
	struct apk_istream *tar_is;
	size_t bytes_left;
56 57
	csum_ctx_t csum_ctx;
	csum_p csum;
58
};
59

60
static size_t tar_entry_read(void *stream, void *ptr, size_t size)
61
{
62 63 64 65 66 67
	struct apk_tar_entry_istream *teis =
		container_of(stream, struct apk_tar_entry_istream, is);

	if (size > teis->bytes_left)
		size = teis->bytes_left;
	size = teis->tar_is->read(teis->tar_is, ptr, size);
68
	if (size > 0) {
69
		teis->bytes_left -= size;
70 71 72 73
		csum_process(&teis->csum_ctx, ptr, size);
		if (teis->bytes_left == 0)
			csum_finish(&teis->csum_ctx, teis->csum);
	}
74
	return size;
75 76
}

77 78
int apk_parse_tar(struct apk_istream *is, apk_archive_entry_parser parser,
		  void *ctx)
79
{
80
	struct apk_file_info entry;
81 82 83
	struct apk_tar_entry_istream teis = {
		.is.read = tar_entry_read,
		.tar_is = is,
84
		.csum = entry.csum,
85
	};
86 87 88
	struct tar_header buf;
	unsigned long offset = 0;
	int end = 0, r;
89
	size_t toskip;
90 91

	memset(&entry, 0, sizeof(entry));
92
	while ((r = is->read(is, &buf, 512)) == 512) {
93 94
		offset += 512;
		if (buf.name[0] == '\0') {
95 96
			if (end) {
				r = 0;
97
				break;
98
			}
99 100 101 102
			end++;
			continue;
		}

103
		entry = (struct apk_file_info){
104 105 106
			.size  = GET_OCTAL(buf.size),
			.uid   = GET_OCTAL(buf.uid),
			.gid   = GET_OCTAL(buf.gid),
107
			.mode  = GET_OCTAL(buf.mode) & 07777,
108 109 110 111 112 113 114 115 116 117 118 119 120
			.mtime = GET_OCTAL(buf.mtime),
			.name  = entry.name,
			.uname = buf.uname,
			.gname = buf.gname,
			.device = makedev(GET_OCTAL(buf.devmajor),
					  GET_OCTAL(buf.devminor)),
		};

		switch (buf.typeflag) {
		case 'L':
			if (entry.name != NULL)
				free(entry.name);
			entry.name = malloc(entry.size+1);
121
			is->read(is, entry.name, entry.size);
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
			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;
		}

150
		teis.bytes_left = entry.size;
151 152 153 154 155
		if (entry.mode & S_IFMT) {
			if (entry.name == NULL)
				entry.name = strdup(buf.name);

			/* callback parser function */
156
			csum_init(&teis.csum_ctx);
157
			r = parser(ctx, &entry, &teis.is);
158 159 160 161 162 163 164
			if (r != 0)
				return r;

			free(entry.name);
			entry.name = NULL;
		}

165 166 167 168 169 170 171 172
		offset += entry.size - teis.bytes_left;
		toskip = teis.bytes_left;
		if ((offset + toskip) & 511)
			toskip += 512 - ((offset + toskip) & 511);
		offset += toskip;
		if (toskip != 0)
			is->read(is, NULL, toskip);
	}
173

174 175 176
	if (r != 0) {
		apk_error("Bad TAR header (r=%d)", r);
		return -1;
177 178 179 180 181
	}

	return 0;
}

182 183
int apk_parse_tar_gz(struct apk_bstream *bs, apk_archive_entry_parser parser,
		     void *ctx)
184
{
185
	return apk_parse_tar(apk_gunzip_bstream(bs), parser, ctx);
186 187
}

188
int apk_archive_entry_extract(const struct apk_file_info *ae,
189 190
			      struct apk_istream *is,
			      const char *fn)
191
{
192
	int r = -1, fd;
193 194 195 196 197 198 199 200 201

	if (fn == NULL)
		fn = ae->name;

	/* BIG HONKING FIXME */
	unlink(fn);

	switch (ae->mode & S_IFMT) {
	case S_IFDIR:
202
		r = mkdir(fn, ae->mode & 07777);
203 204 205 206 207
		if (r < 0 && errno == EEXIST)
			r = 0;
		break;
	case S_IFREG:
		if (ae->link_target == NULL) {
208 209 210
			fd = open(fn, O_WRONLY | O_CREAT, ae->mode & 07777);
			if (fd < 0) {
				r = -1;
211
				break;
212
			}
213
			if (apk_istream_splice(is, fd, ae->size) == ae->size)
214 215
				r = 0;
			close(fd);
216 217 218 219 220 221 222 223 224 225 226
		} 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:
227
		r = mknod(fn, ae->mode & 07777, ae->device);
228 229
		break;
	}
230 231 232 233 234
	if (r == 0) {
		if (!S_ISLNK(ae->mode))
			r = chown(fn, ae->uid, ae->gid);
		else
			r = lchown(fn, ae->uid, ae->gid);
235 236 237
		if (r < 0)
			apk_error("Failed to set ownership on %s: %s", fn, 
					strerror(errno));
238
	} else {
239
		apk_error("Failed to extract %s\n", ae->name);
240
	}
241 242
	return r;
}