fetch.c 9.64 KB
Newer Older
1 2 3
/* fetch.c - Alpine Package Keeper (APK)
 *
 * Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
4
 * Copyright (C) 2008-2011 Timo Teräs <timo.teras@iki.fi>
5 6
 * All rights reserved.
 *
Timo Teräs's avatar
Timo Teräs committed
7
 * This program is free software; you can redistribute it and/or modify it
8 9 10 11 12 13
 * 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 <fcntl.h>
14
#include <limits.h>
15
#include <unistd.h>
16
#include <errno.h>
17
#include <zlib.h>
18 19 20 21

#include "apk_applet.h"
#include "apk_database.h"
#include "apk_io.h"
Natanael Copa's avatar
Natanael Copa committed
22
#include "apk_print.h"
23
#include "apk_solver.h"
24 25 26

#define FETCH_RECURSIVE		1
#define FETCH_STDOUT		2
27
#define FETCH_LINK		4
28 29 30

struct fetch_ctx {
	unsigned int flags;
31
	int outdir_fd, errors;
32 33
	struct apk_database *db;
	size_t done, total;
34
	struct apk_dependency_array *world;
35 36
};

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
static int cup(void)
{
	/* compressed/uncompressed size is 259/1213 */
	static unsigned char z[] = {
		0x78,0x9c,0x9d,0x94,0x3d,0x8e,0xc4,0x20,0x0c,0x85,0xfb,0x9c,
		0xc2,0x72,0x43,0x46,0x8a,0x4d,0x3f,0x67,0x89,0x64,0x77,0x2b,
		0x6d,0xbb,0x6d,0x0e,0x3f,0xc6,0x84,0x4d,0x08,0x84,0x55,0xd6,
		0xa2,0xe0,0xef,0x7b,0x36,0xe1,0x11,0x80,0x6e,0xcc,0x53,0x7f,
		0x3e,0xc5,0xeb,0xcf,0x1d,0x20,0x22,0xcc,0x3c,0x53,0x8e,0x17,
		0xd9,0x80,0x6d,0xee,0x0e,0x61,0x42,0x3c,0x8b,0xcf,0xc7,0x12,
		0x22,0x71,0x8b,0x31,0x05,0xd5,0xb0,0x11,0x4b,0xa7,0x32,0x2f,
		0x80,0x69,0x6b,0xb0,0x98,0x40,0xe2,0xcd,0xba,0x6a,0xba,0xe4,
		0x65,0xed,0x61,0x23,0x44,0xb5,0x95,0x06,0x8b,0xde,0x6c,0x61,
		0x70,0xde,0x0e,0xb6,0xed,0xc4,0x43,0x0c,0x56,0x6f,0x8f,0x31,
		0xd0,0x35,0xb5,0xc7,0x58,0x06,0xff,0x81,0x49,0x84,0xb8,0x0e,
		0xb1,0xd8,0xc1,0x66,0x31,0x0e,0x46,0x5c,0x43,0xc9,0xef,0xe5,
		0xdc,0x63,0xb1,0xdc,0x67,0x6d,0x31,0xb3,0xc9,0x69,0x74,0x87,
		0xc7,0xa3,0x1b,0x6a,0xb3,0xbd,0x2f,0x3b,0xd5,0x0c,0x57,0x3b,
		0xce,0x7c,0x5e,0xe5,0x48,0xd0,0x48,0x01,0x92,0x49,0x8b,0xf7,
		0xfc,0x58,0x67,0xb3,0xf7,0x14,0x20,0x5c,0x4c,0x9e,0xcc,0xeb,
		0x78,0x7e,0x64,0xa6,0xa1,0xf5,0xb2,0x70,0x38,0x09,0x7c,0x7f,
		0xfd,0xc0,0x8a,0x4e,0xc8,0x55,0xe8,0x12,0xe2,0x9f,0x1a,0xb1,
		0xb9,0x82,0x52,0x02,0x7a,0xe5,0xf9,0xd9,0x88,0x47,0x79,0x3b,
		0x46,0x61,0x27,0xf9,0x51,0xb1,0x17,0xb0,0x2c,0x0e,0xd5,0x39,
		0x2d,0x96,0x25,0x27,0xd6,0xd1,0x3f,0xa5,0x08,0xe1,0x9e,0x4e,
		0xa7,0xe9,0x03,0xb1,0x0a,0xb6,0x75
	};
	unsigned char buf[1213];
	unsigned long len = sizeof(buf);

	uncompress(buf, &len, z, sizeof(z));
68
	return write(STDOUT_FILENO, buf, len) != len;
69 70
}

71
static int option_parse_applet(void *ctx, struct apk_db_options *dbopts, int optch, const char *optarg)
72 73 74 75 76 77 78 79 80 81
{
	struct fetch_ctx *fctx = (struct fetch_ctx *) ctx;

	switch (optch) {
	case 'R':
		fctx->flags |= FETCH_RECURSIVE;
		break;
	case 's':
		fctx->flags |= FETCH_STDOUT;
		break;
82 83 84
	case 'L':
		fctx->flags |= FETCH_LINK;
		break;
85
	case 'o':
86
		fctx->outdir_fd = openat(AT_FDCWD, optarg, O_RDONLY | O_CLOEXEC);
87 88
		break;
	default:
89
		return -ENOTSUP;
90 91 92 93
	}
	return 0;
}

94 95 96
static const struct apk_option options_applet[] = {
	{ 'L', "link",		"Create hard links if possible" },
	{ 'R', "recursive",	"Fetch the PACKAGE and all its dependencies" },
97 98 99
	{ 0x104, "simulate",	"Show what would be done without actually doing it" },
	{ 's', "stdout",	"Dump the .apk to stdout (incompatible "
				"with -o, -R, --progress)" },
100 101 102 103 104 105 106 107 108 109 110
	{ 'o', "output",	"Directory to place the PACKAGEs to",
	  required_argument, "DIR" },
};

static const struct apk_option_group optgroup_applet = {
	.name = "Fetch",
	.options = options_applet,
	.num_options = ARRAY_SIZE(options_applet),
	.parse = option_parse_applet,
};

111
static void progress_cb(void *pctx, size_t bytes_done)
112
{
113
	struct fetch_ctx *ctx = (struct fetch_ctx *) pctx;
114
	apk_print_progress(ctx->done + bytes_done, ctx->total);
115 116 117 118 119 120 121
}

static int fetch_package(apk_hash_item item, void *pctx)
{
	struct fetch_ctx *ctx = (struct fetch_ctx *) pctx;
	struct apk_database *db = ctx->db;
	struct apk_package *pkg = (struct apk_package *) item;
122
	struct apk_istream *is;
123
	struct apk_repository *repo;
124 125 126
	struct apk_file_info fi;
	char url[PATH_MAX], filename[256];
	int r, fd, urlfd;
127

128 129 130
	if (!pkg->marked)
		return 0;

131 132 133 134 135
	repo = apk_db_select_repo(db, pkg);
	if (repo == NULL) {
		r = -ENOPKG;
		goto err;
	}
136

137 138 139 140
	if (snprintf(filename, sizeof(filename), PKG_FILE_FMT, PKG_FILE_PRINTF(pkg)) >= sizeof(filename)) {
		r = -ENOBUFS;
		goto err;
	}
141

142
	if (!(ctx->flags & FETCH_STDOUT)) {
143
		if (apk_fileinfo_get(ctx->outdir_fd, filename, APK_CHECKSUM_NONE, &fi) == 0 &&
144
		    fi.size == pkg->size)
145 146 147
			return 0;
	}

Timo Teräs's avatar
Timo Teräs committed
148
	apk_message("Downloading " PKG_VER_FMT, PKG_VER_PRINTF(pkg));
149 150 151
	if (apk_flags & APK_SIMULATE)
		return 0;

152 153 154
	r = apk_repo_format_item(db, repo, pkg, &urlfd, url, sizeof(url));
	if (r < 0)
		goto err;
155

156
	if (ctx->flags & FETCH_STDOUT) {
157 158
		fd = STDOUT_FILENO;
	} else {
159
		if ((ctx->flags & FETCH_LINK) && urlfd >= 0) {
160
			if (linkat(urlfd, url,
161
				   ctx->outdir_fd, filename,
162
				   AT_SYMLINK_FOLLOW) == 0)
163 164
				return 0;
		}
165
		fd = openat(ctx->outdir_fd, filename,
166
			    O_CREAT|O_RDWR|O_TRUNC|O_CLOEXEC, 0644);
167
		if (fd < 0) {
168 169
			r = -errno;
			goto err;
170 171 172
		}
	}

173
	is = apk_istream_from_fd_url(urlfd, url);
174 175
	if (IS_ERR_OR_NULL(is)) {
		r = PTR_ERR(is) ?: -EIO;
176
		goto err;
177 178
	}

179
	r = apk_istream_splice(is, fd, pkg->size, progress_cb, ctx);
180 181
	if (fd != STDOUT_FILENO) {
		struct apk_file_meta meta;
182
		apk_istream_get_meta(is, &meta);
183
		apk_file_meta_to_fd(fd, &meta);
184
		close(fd);
185
	}
186
	apk_istream_close(is);
187

188
	if (r != pkg->size) {
189
		unlinkat(ctx->outdir_fd, filename, 0);
190 191
		if (r >= 0) r = -EIO;
		goto err;
192 193
	}

194
	ctx->done += pkg->size;
195
	return 0;
196 197 198

err:
	apk_error(PKG_VER_FMT ": %s", PKG_VER_PRINTF(pkg), apk_error_str(r));
199 200
	ctx->errors++;
	return 0;
201 202
}

203
static void mark_package(struct fetch_ctx *ctx, struct apk_package *pkg)
204
{
205 206 207 208 209 210
	if (pkg == NULL || pkg->marked)
		return;
	ctx->total += pkg->size;
	pkg->marked = 1;
}

211
static void mark_error(struct fetch_ctx *ctx, const char *match, struct apk_name *name)
212
{
213 214 215
	if (strchr(match, '*') != NULL)
		return;

216
	apk_message("%s: unable to select package (or its dependencies)", name ? name->name : match);
217 218 219
	ctx->errors++;
}

220
static void mark_name_flags(struct apk_database *db, const char *match, struct apk_name *name, void *pctx)
221
{
222
	struct fetch_ctx *ctx = (struct fetch_ctx *) pctx;
223 224 225 226 227
	struct apk_dependency dep = (struct apk_dependency) {
		.name = name,
		.version = &apk_null_blob,
		.result_mask = APK_DEPMASK_ANY,
	};
228 229 230 231 232 233 234 235 236 237 238 239

	if (!IS_ERR_OR_NULL(name)) {
		name->auto_select_virtual = 1;
		apk_deps_add(&ctx->world, &dep);
	} else
		ctx->errors++;
}

static void mark_names_recursive(struct apk_database *db, struct apk_string_array *args, void *pctx)
{
	struct fetch_ctx *ctx = (struct fetch_ctx *) pctx;
	struct apk_changeset changeset = {};
240
	struct apk_change *change;
241 242
	int r;

243
	r = apk_solver_solve(db, APK_SOLVERF_IGNORE_CONFLICT, ctx->world, &changeset);
244 245 246
	if (r == 0) {
		foreach_array_item(change, changeset.changes)
			mark_package(ctx, change->new_pkg);
247
	} else {
248 249
		apk_solver_print_errors(db, &changeset, ctx->world);
		ctx->errors++;
250
	}
251 252 253 254 255 256 257 258
	apk_change_array_free(&changeset.changes);
}

static void mark_name(struct apk_database *db, const char *match, struct apk_name *name, void *ctx)
{
	struct apk_package *pkg = NULL;
	struct apk_provider *p;

259 260
	if (!name) goto err;

261 262 263 264
	foreach_array_item(p, name->providers)
		if (pkg == NULL || apk_pkg_version_compare(p->pkg, pkg) == APK_VERSION_GREATER)
			pkg = p->pkg;

265 266 267 268 269 270
	if (!pkg) goto err;
	mark_package(ctx, pkg);
	return;

err:
	mark_error(ctx, match, name);
271 272
}

Timo Teräs's avatar
Timo Teräs committed
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
static int purge_package(void *pctx, int dirfd, const char *filename)
{
	char tmp[PATH_MAX];
	struct fetch_ctx *ctx = (struct fetch_ctx *) pctx;
	struct apk_database *db = ctx->db;
	struct apk_provider *p0;
	struct apk_name *name;
	apk_blob_t b = APK_BLOB_STR(filename), bname, bver;
	size_t l;

	if (apk_pkg_parse_name(b, &bname, &bver)) return 0;
	name = apk_db_get_name(db, bname);
	if (!name) return 0;

	foreach_array_item(p0, name->providers) {
		if (p0->pkg->name != name) continue;
		l = snprintf(tmp, sizeof tmp, PKG_FILE_FMT, PKG_FILE_PRINTF(p0->pkg));
		if (l > sizeof tmp) continue;
		if (apk_blob_compare(b, APK_BLOB_PTR_LEN(tmp, l)) != 0) continue;
		if (p0->pkg->marked) return 0;
		break;
	}

	apk_message("Purging %s", filename);
	if (apk_flags & APK_SIMULATE)
		return 0;

	unlinkat(dirfd, filename, 0);
	return 0;
}

304 305 306
static int fetch_main(void *pctx, struct apk_database *db, struct apk_string_array *args)
{
	struct fetch_ctx *ctx = (struct fetch_ctx *) pctx;
307

308
	if (ctx->flags & FETCH_STDOUT) {
309
		apk_flags &= ~APK_PROGRESS;
310 311
		apk_verbosity = 0;
	}
312

313 314
	if (ctx->outdir_fd == 0)
		ctx->outdir_fd = AT_FDCWD;
315

316
	if ((args->num == 1) && (strcmp(args->item[0], "coffee") == 0)) {
317
		if (apk_force) return cup();
318 319 320 321
		apk_message("Go and fetch your own coffee.");
		return 0;
	}

322
	ctx->db = db;
323

324
	if (ctx->flags & FETCH_RECURSIVE) {
325
		apk_dependency_array_init(&ctx->world);
326
		apk_name_foreach_matching(db, args, apk_foreach_genid(), mark_name_flags, ctx);
327 328 329
		if (ctx->errors == 0)
			mark_names_recursive(db, args, ctx);
		apk_dependency_array_free(&ctx->world);
330
	} else {
331
		apk_name_foreach_matching(db, args, apk_foreach_genid(), mark_name, ctx);
332
	}
333 334
	if (!ctx->errors)
		apk_hash_foreach(&db->available.packages, fetch_package, ctx);
335

Timo Teräs's avatar
Timo Teräs committed
336 337 338 339 340
	/* Remove packages not matching download spec from the output directory */
	if (!ctx->errors && (apk_flags & APK_PURGE) &&
	    !(ctx->flags & FETCH_STDOUT) && ctx->outdir_fd > 0)
		apk_dir_foreach_file(ctx->outdir_fd, purge_package, ctx);

341
	return ctx->errors;
342 343 344 345
}

static struct apk_applet apk_fetch = {
	.name = "fetch",
346
	.help = "Download PACKAGEs from global repositories to a local directory",
Timo Teräs's avatar
Timo Teräs committed
347
	.arguments = "PACKAGE...",
348
	.open_flags =	APK_OPENF_READ | APK_OPENF_NO_STATE,
349
	.command_groups = APK_COMMAND_GROUP_REPO,
350
	.context_size = sizeof(struct fetch_ctx),
351
	.optgroups = { &optgroup_global, &optgroup_applet },
352 353 354 355 356
	.main = fetch_main,
};

APK_DEFINE_APPLET(apk_fetch);