archive.c 7.59 KB
Newer Older
1 2 3 4 5 6
/* 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.
 *
7
 * This program is free software; you can redistribute it and/or modify it
8 9 10 11 12 13 14 15 16
 * 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>
17
#include <utime.h>
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
#include <malloc.h>
#include <string.h>
#include <unistd.h>
#include <sysexits.h>
#include <sys/wait.h>

#include "apk_defines.h"
#include "apk_archive.h"

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

Timo Teräs's avatar
Timo Teräs committed
47 48 49
#define GET_OCTAL(s)	get_octal(s, sizeof(s))
#define PUT_OCTAL(s,v)	put_octal(s, sizeof(s), v)

50 51 52
static int get_octal(char *s, size_t l)
{
	apk_blob_t b = APK_BLOB_PTR_LEN(s, l);
53
	return apk_blob_pull_uint(&b, 8);
54
}
55

Timo Teräs's avatar
Timo Teräs committed
56 57 58 59 60 61 62 63 64 65 66 67 68
static void put_octal(char *s, size_t l, size_t value)
{
	char *ptr = &s[l - 1];

	*(ptr--) = '\0';
	while (value != 0 && ptr >= s) {
		*(ptr--) = '0' + (value % 8);
		value /= 8;
	}
	while (ptr >= s)
		*(ptr--) = '0';
}

69 70 71 72
struct apk_tar_entry_istream {
	struct apk_istream is;
	struct apk_istream *tar_is;
	size_t bytes_left;
Timo Teräs's avatar
Timo Teräs committed
73 74
	EVP_MD_CTX mdctx;
	struct apk_checksum *csum;
75
};
76

77
static size_t tar_entry_read(void *stream, void *ptr, size_t size)
78
{
79 80 81 82 83 84
	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);
85
	if (size > 0) {
86
		teis->bytes_left -= size;
Timo Teräs's avatar
Timo Teräs committed
87 88 89 90 91
		EVP_DigestUpdate(&teis->mdctx, ptr, size);
		if (teis->bytes_left == 0) {
			teis->csum->type = EVP_MD_CTX_size(&teis->mdctx);
			EVP_DigestFinal_ex(&teis->mdctx, teis->csum->data, NULL);
		}
92
	}
93
	return size;
94 95
}

96 97
int apk_parse_tar(struct apk_istream *is, apk_archive_entry_parser parser,
		  void *ctx)
98
{
99
	struct apk_file_info entry;
100 101 102
	struct apk_tar_entry_istream teis = {
		.is.read = tar_entry_read,
		.tar_is = is,
Timo Teräs's avatar
Timo Teräs committed
103
		.csum = &entry.csum,
104
	};
105 106 107
	struct tar_header buf;
	unsigned long offset = 0;
	int end = 0, r;
108
	size_t toskip;
109

Timo Teräs's avatar
Timo Teräs committed
110
	EVP_MD_CTX_init(&teis.mdctx);
111
	memset(&entry, 0, sizeof(entry));
112
	while ((r = is->read(is, &buf, 512)) == 512) {
113 114
		offset += 512;
		if (buf.name[0] == '\0') {
115 116
			if (end) {
				r = 0;
117
				//break;
118
			}
119 120 121 122
			end++;
			continue;
		}

123
		entry = (struct apk_file_info){
124 125 126
			.size  = GET_OCTAL(buf.size),
			.uid   = GET_OCTAL(buf.uid),
			.gid   = GET_OCTAL(buf.gid),
127
			.mode  = GET_OCTAL(buf.mode) & 07777,
128 129 130 131 132 133 134 135 136 137 138 139 140
			.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);
141
			is->read(is, entry.name, entry.size);
Timo Teräs's avatar
Timo Teräs committed
142
			entry.name[entry.size] = 0;
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
			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;
Timo Teräs's avatar
Timo Teräs committed
161
		case '4': /* block device */
162 163 164 165 166 167 168 169 170
			entry.mode |= S_IFBLK;
			break;
		case '5': /* directory */
			entry.mode |= S_IFDIR;
			break;
		default:
			break;
		}

171
		teis.bytes_left = entry.size;
172 173 174 175 176
		if (entry.mode & S_IFMT) {
			if (entry.name == NULL)
				entry.name = strdup(buf.name);

			/* callback parser function */
Timo Teräs's avatar
Timo Teräs committed
177
			EVP_DigestInit_ex(&teis.mdctx, apk_default_checksum(), NULL);
178
			r = parser(ctx, &entry, &teis.is);
Timo Teräs's avatar
Timo Teräs committed
179
			free(entry.name);
180
			if (r != 0)
Timo Teräs's avatar
Timo Teräs committed
181
				goto err;
182 183 184 185

			entry.name = NULL;
		}

186 187 188 189 190 191 192 193
		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);
	}
Timo Teräs's avatar
Timo Teräs committed
194
	EVP_MD_CTX_cleanup(&teis.mdctx);
195

196 197 198
	if (r != 0) {
		apk_error("Bad TAR header (r=%d)", r);
		return -1;
199 200
	}

Timo Teräs's avatar
Timo Teräs committed
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
	return 0;

err:
	EVP_MD_CTX_cleanup(&teis.mdctx);
	return r;
}

int apk_write_tar_entry(struct apk_ostream *os, const struct apk_file_info *ae, char *data)
{
	static char padding[512];
	struct tar_header buf;
	int pad;

	memset(&buf, 0, sizeof(buf));
	if (ae != NULL) {
		const unsigned char *src;
	        int chksum, i;

		if (S_ISREG(ae->mode))
			buf.typeflag = '0';
		else
			return -1;

		strncpy(buf.name, ae->name, sizeof(buf.name));
		strncpy(buf.uname, ae->uname, sizeof(buf.uname));
		strncpy(buf.gname, ae->gname, sizeof(buf.gname));

		PUT_OCTAL(buf.size, ae->size);
		PUT_OCTAL(buf.uid, ae->uid);
		PUT_OCTAL(buf.gid, ae->gid);
		PUT_OCTAL(buf.mode, ae->mode & 07777);
		PUT_OCTAL(buf.mtime, ae->mtime);

		/* Checksum */
		strcpy(buf.magic, "ustar  ");
		memset(buf.chksum, ' ', sizeof(buf.chksum));
		src = (const unsigned char *) &buf;
		for (i = chksum = 0; i < sizeof(buf); i++)
			chksum += src[i];
	        put_octal(buf.chksum, sizeof(buf.chksum)-1, chksum);
	}

	if (os->write(os, &buf, sizeof(buf)) != sizeof(buf))
		return -1;

	if (data != NULL) {
		if (os->write(os, data, ae->size) != ae->size)
			return -1;
		pad = 512 - (ae->size & 511);
		if (pad != 512 &&
		    os->write(os, padding, pad) != pad)
			return -1;
	}

255 256 257
	return 0;
}

258
int apk_archive_entry_extract(const struct apk_file_info *ae,
259
			      struct apk_istream *is,
260 261
			      const char *fn, apk_progress_cb cb,
			      void *cb_ctx)
262
{
263
	struct utimbuf utb;
264
	int r = -1, fd;
265 266 267 268 269 270 271 272 273

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

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

	switch (ae->mode & S_IFMT) {
	case S_IFDIR:
274
		r = mkdir(fn, ae->mode & 07777);
275 276 277 278 279
		if (r < 0 && errno == EEXIST)
			r = 0;
		break;
	case S_IFREG:
		if (ae->link_target == NULL) {
280 281 282
			fd = open(fn, O_WRONLY | O_CREAT, ae->mode & 07777);
			if (fd < 0) {
				r = -1;
283
				break;
284
			}
285 286
			if (apk_istream_splice(is, fd, ae->size, cb, cb_ctx)
			    == ae->size)
287 288
				r = 0;
			close(fd);
289 290 291 292 293 294 295 296 297 298 299
		} 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:
300
		r = mknod(fn, ae->mode & 07777, ae->device);
301 302
		break;
	}
303 304 305 306 307
	if (r == 0) {
		if (!S_ISLNK(ae->mode))
			r = chown(fn, ae->uid, ae->gid);
		else
			r = lchown(fn, ae->uid, ae->gid);
308 309 310 311 312 313
		if (r < 0) {
			apk_error("Failed to set ownership on %s: %s",
				  fn, strerror(errno));
			return -errno;
		}

314
		/* chown resets suid bit so we need set it again */
315
		if (ae->mode & 07000) {
316
			r = chmod(fn, ae->mode & 07777);
317 318 319 320 321 322 323 324
			if (r < 0) {
				apk_error("Failed to set file permissions "
					  "on %s: %s",
					  fn, strerror(errno));
				return -errno;
			}
		}

325 326 327 328 329 330 331 332 333
		if (!S_ISLNK(ae->mode)) {
			/* preserve modification time */
			utb.actime = utb.modtime = ae->mtime;
			r = utime(fn, &utb);
			if (r < 0) {
				apk_error("Failed to preserve modification time on %s: %s",
					fn, strerror(errno));
				return -errno;
			}
334
		}
335
	} else {
336 337
		apk_error("Failed to extract %s: %s", ae->name, strerror(errno));
		return -errno;
338
	}
339
	return 0;
340
}