pull-dkr.c revision b5efdb8af40ea759a1ea584c1bc44ecc81dd00ce
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2014 Lennart Poettering
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "sd-daemon.h"
#include "alloc-util.h"
#include "aufs-util.h"
#include "btrfs-util.h"
#include "curl-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "hostname-util.h"
#include "import-common.h"
#include "import-util.h"
#include "json.h"
#include "mkdir.h"
#include "path-util.h"
#include "process-util.h"
#include "pull-common.h"
#include "pull-dkr.h"
#include "pull-job.h"
#include "rm-rf.h"
#include "string-util.h"
#include "strv.h"
#include "utf8.h"
#include "web-util.h"
typedef enum DkrProgress {
} DkrProgress;
struct DkrPull {
char *index_protocol;
char *index_address;
char *index_url;
char *image_root;
char *name;
char *reference;
char *id;
char *response_digest;
char *response_token;
char **response_registries;
char **ancestry;
unsigned n_ancestry;
unsigned current_ancestry;
void *userdata;
char *local;
bool force_local;
bool grow_machine_directory;
char *temp_path;
char *final_path;
};
#define PROTOCOL_PREFIX "https://"
#define LAYERS_MAX 127
static void dkr_pull_job_on_finished(PullJob *j);
if (!i)
return NULL;
if (i->tar_pid > 1) {
}
pull_job_unref(i->images_job);
pull_job_unref(i->tags_job);
pull_job_unref(i->json_job);
pull_job_unref(i->layer_job);
curl_glue_unref(i->glue);
sd_event_unref(i->event);
if (i->temp_path) {
}
free(i->response_token);
free(i->final_path);
free(i->index_address);
free(i->index_protocol);
free(i->image_root);
free(i);
return NULL;
}
int dkr_pull_new(
const char *index_url,
const char *image_root,
void *userdata) {
char *e;
int r;
if (!http_url_is_valid(index_url))
return -EINVAL;
if (!i)
return -ENOMEM;
i->on_finished = on_finished;
if (!i->image_root)
return -ENOMEM;
if (!i->index_url)
return -ENOMEM;
if (e)
*e = 0;
if (event)
else {
r = sd_event_default(&i->event);
if (r < 0)
return r;
}
if (r < 0)
return r;
*ret = i;
i = NULL;
return 0;
}
unsigned percent;
assert(i);
switch (p) {
case DKR_SEARCHING:
percent = 0;
if (i->images_job)
break;
case DKR_RESOLVING:
percent = 5;
if (i->tags_job)
break;
case DKR_METADATA:
percent = 10;
if (i->ancestry_job)
if (i->json_job)
break;
case DKR_DOWNLOADING:
percent = 20;
if (i->layer_job)
break;
case DKR_COPYING:
percent = 95;
break;
default:
assert_not_reached("Unknown progress state");
}
}
union json_value v = {};
void *json_state = NULL;
const char *p;
int t;
if (size <= 0)
return -EBADMSG;
return -EBADMSG;
if (!buf)
return -ENOMEM;
p = buf;
if (t < 0)
return t;
if (t != JSON_STRING)
return -EBADMSG;
if (t < 0)
return t;
if (t != JSON_END)
return -EBADMSG;
if (!dkr_id_is_valid(id))
return -EBADMSG;
return 0;
}
void *json_state = NULL;
const char *p;
enum {
} state = STATE_BEGIN;
_cleanup_strv_free_ char **l = NULL;
if (size <= 0)
return -EBADMSG;
return -EBADMSG;
if (!buf)
return -ENOMEM;
p = buf;
for (;;) {
_cleanup_free_ char *str;
union json_value v = {};
int t;
if (t < 0)
return t;
switch (state) {
case STATE_BEGIN:
if (t == JSON_ARRAY_OPEN)
state = STATE_ITEM;
else
return -EBADMSG;
break;
case STATE_ITEM:
if (t == JSON_STRING) {
if (!dkr_id_is_valid(str))
return -EBADMSG;
if (n+1 > LAYERS_MAX)
return -EFBIG;
return -ENOMEM;
l[n++] = str;
l[n] = NULL;
state = STATE_COMMA;
} else if (t == JSON_ARRAY_CLOSE)
else
return -EBADMSG;
break;
case STATE_COMMA:
if (t == JSON_COMMA)
state = STATE_ITEM;
else if (t == JSON_ARRAY_CLOSE)
else
return -EBADMSG;
break;
case STATE_END:
if (t == JSON_END) {
if (strv_isempty(l))
return -EBADMSG;
if (!strv_is_uniq(l))
return -EBADMSG;
l = strv_reverse(l);
*ret = l;
l = NULL;
return 0;
} else
return -EBADMSG;
}
}
}
static const char *dkr_pull_current_layer(DkrPull *i) {
assert(i);
if (strv_isempty(i->ancestry))
return NULL;
return i->ancestry[i->current_ancestry];
}
static const char *dkr_pull_current_base_layer(DkrPull *i) {
assert(i);
if (strv_isempty(i->ancestry))
return NULL;
if (i->current_ancestry <= 0)
return NULL;
}
const char *t;
assert(i);
assert(j);
if (i->response_token)
else
t = HEADER_TOKEN " true";
if (!j->request_header)
return -ENOMEM;
return 0;
}
const char *t = NULL;
assert(i);
assert(j);
if (i->response_token)
else
return -EINVAL;
if (!j->request_header)
return -ENOMEM;
return 0;
}
static bool dkr_pull_is_done(DkrPull *i) {
assert(i);
assert(i->images_job);
return false;
return false;
return false;
return false;
return false;
if (dkr_pull_current_layer(i))
return false;
return true;
}
int r;
_cleanup_free_ char *p = NULL;
assert(i);
if (!i->local)
return 0;
if (!i->final_path) {
if (!i->final_path)
return -ENOMEM;
}
if (version == DKR_PULL_V2) {
p = dirname_malloc(i->image_root);
if (!p)
return -ENOMEM;
}
if (r < 0)
return r;
if (version == DKR_PULL_V2) {
char **k;
STRV_FOREACH(k, i->ancestry) {
_cleanup_free_ char *d;
if (!d)
return -ENOMEM;
r = btrfs_subvol_remove(d, BTRFS_REMOVE_QUOTA);
if (r < 0)
return r;
}
r = rmdir(i->image_root);
if (r < 0)
return r;
}
return 0;
}
static int dkr_pull_job_on_open_disk(PullJob *j) {
const char *base;
DkrPull *i;
int r;
assert(j);
i = j->userdata;
assert(i->final_path);
if (r < 0)
return log_oom();
if (base) {
const char *base_path;
r = btrfs_subvol_snapshot(base_path, i->temp_path, BTRFS_SNAPSHOT_FALLBACK_COPY|BTRFS_SNAPSHOT_QUOTA);
} else
r = btrfs_subvol_make(i->temp_path);
if (r < 0)
(void) import_assign_pool_quota_and_warn(i->temp_path);
if (j->disk_fd < 0)
return j->disk_fd;
return 0;
}
static void dkr_pull_job_on_progress(PullJob *j) {
DkrPull *i;
assert(j);
i = j->userdata;
i,
j == i->images_job ? DKR_SEARCHING :
j == i->tags_job ? DKR_RESOLVING :
}
static void dkr_pull_job_on_finished_v2(PullJob *j);
static int dkr_pull_pull_layer_v2(DkrPull *i) {
int r;
assert(i);
assert(!i->final_path);
for (;;) {
layer = dkr_pull_current_layer(i);
if (!layer)
return 0; /* no more layers */
if (!path)
return log_oom();
break;
}
i->current_ancestry++;
}
i->final_path = path;
if (r < 0)
return log_error_errno(r, "Failed to allocate layer job: %m");
r = dkr_pull_add_bearer_token(i, i->layer_job);
if (r < 0)
return log_oom();
r = pull_job_begin(i->layer_job);
if (r < 0)
return log_error_errno(r, "Failed to start layer job: %m");
return 0;
}
static int dkr_pull_pull_layer(DkrPull *i) {
int r;
assert(i);
assert(!i->final_path);
for (;;) {
layer = dkr_pull_current_layer(i);
if (!layer)
return 0; /* no more layers */
if (!path)
return log_oom();
break;
}
i->current_ancestry++;
}
i->final_path = path;
if (r < 0)
return log_error_errno(r, "Failed to allocate layer job: %m");
r = dkr_pull_add_token(i, i->layer_job);
if (r < 0)
return log_oom();
r = pull_job_begin(i->layer_job);
if (r < 0)
return log_error_errno(r, "Failed to start layer job: %m");
return 0;
}
DkrPull *i;
int r;
assert(j);
i = j->userdata;
if (r < 0)
return log_oom();
if (r > 0) {
free(i->response_token);
i->response_token = token;
return 0;
}
if (r < 0)
return log_oom();
if (r > 0) {
free(i->response_digest);
i->response_digest = digest;
return 0;
}
if (r < 0)
return log_oom();
if (r > 0) {
char **l, **k;
if (!l)
return log_oom();
STRV_FOREACH(k, l) {
if (!hostname_is_valid(*k, false)) {
log_error("Registry hostname is not valid.");
strv_free(l);
return -EBADMSG;
}
}
i->response_registries = l;
}
return 0;
}
static void dkr_pull_job_on_finished_v2(PullJob *j) {
DkrPull *i;
int r;
assert(j);
i = j->userdata;
if (j->error != 0) {
if (j == i->images_job)
else if (j == i->ancestry_job)
else if (j == i->json_job)
else
r = j->error;
goto finish;
}
if (i->images_job == j) {
const char *url;
assert(!i->ancestry_job);
if (strv_isempty(i->response_registries)) {
r = -EBADMSG;
log_error("Didn't get registry information.");
goto finish;
}
if (r < 0) {
log_error_errno(r, "Failed to allocate tags job: %m");
goto finish;
}
r = pull_job_begin(i->tags_job);
if (r < 0) {
log_error_errno(r, "Failed to start tags job: %m");
goto finish;
}
} else if (i->tags_job == j) {
const char *url;
_cleanup_free_ char *buf;
JsonVariant *e = NULL;
assert(!i->ancestry_job);
if (!buf) {
r = -ENOMEM;
log_oom();
goto finish;
}
if (r < 0) {
goto finish;
}
if (!e || e->type != JSON_VARIANT_STRING) {
r = -EBADMSG;
log_error("Invalid JSON format for Bearer token");
goto finish;
}
if (r < 0) {
log_oom();
goto finish;
}
url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v2/", i->name, "/manifests/", i->reference);
if (r < 0) {
log_error_errno(r, "Failed to allocate ancestry job: %m");
goto finish;
}
r = dkr_pull_add_bearer_token(i, i->ancestry_job);
if (r < 0)
goto finish;
r = pull_job_begin(i->ancestry_job);
if (r < 0) {
log_error_errno(r, "Failed to start ancestry job: %m");
goto finish;
}
} else if (i->ancestry_job == j) {
JsonVariant *e = NULL;
if (r < 0) {
log_error("Invalid JSON Manifest");
goto finish;
}
r = -EBADMSG;
goto finish;
}
for (unsigned z = 0; z < e->size; z++) {
const char *layer;
if (f->type != JSON_VARIANT_OBJECT) {
r = -EBADMSG;
goto finish;
}
g = json_variant_value(f, "blobSum");
layer = json_variant_string(g);
if (!dkr_digest_is_valid(layer)) {
r = -EBADMSG;
goto finish;
}
r = -ENOMEM;
log_oom();
goto finish;
}
r = -ENOMEM;
log_oom();
goto finish;
}
size += 1;
}
if (!e || e->type != JSON_VARIANT_ARRAY) {
r = -EBADMSG;
goto finish;
}
e = json_variant_element(e, 0);
e = json_variant_value(e, "v1Compatibility");
if (r < 0) {
log_error("Invalid v1Compatibility JSON");
goto finish;
}
i->current_ancestry = 0;
if (!i->id) {
r = -ENOMEM;
log_oom();
goto finish;
}
if (!path) {
r = -ENOMEM;
log_oom();
goto finish;
}
free(i->image_root);
i->image_root = path;
log_info("Required layers:\n");
STRV_FOREACH(k, i->ancestry)
log_info("\t%s", *k);
r = dkr_pull_pull_layer_v2(i);
if (r < 0)
goto finish;
} else if (i->layer_job == j) {
assert(i->final_path);
if (i->tar_pid > 0) {
i->tar_pid = 0;
if (r < 0)
goto finish;
}
r = aufs_resolve(i->temp_path);
if (r < 0) {
log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
goto finish;
}
r = btrfs_subvol_set_read_only(i->temp_path, true);
if (r < 0) {
log_error_errno(r, "Failed to mark snapshot read-only: %m");
goto finish;
}
goto finish;
}
free(i->final_path);
i->final_path = NULL;
i->current_ancestry ++;
r = dkr_pull_pull_layer_v2(i);
if (r < 0)
goto finish;
} else if (i->json_job != j)
assert_not_reached("Got finished event for unknown curl object");
if (!dkr_pull_is_done(i))
return;
r = dkr_pull_make_local_copy(i, DKR_PULL_V2);
if (r < 0)
goto finish;
r = 0;
if (i->on_finished)
i->on_finished(i, r, i->userdata);
else
sd_event_exit(i->event, r);
}
static void dkr_pull_job_on_finished(PullJob *j) {
DkrPull *i;
int r;
assert(j);
i = j->userdata;
if (j->error != 0) {
if (j == i->images_job)
else if (j == i->tags_job)
else if (j == i->ancestry_job)
else if (j == i->json_job)
else
r = j->error;
goto finish;
}
if (i->images_job == j) {
const char *url;
assert(!i->ancestry_job);
if (strv_isempty(i->response_registries)) {
r = -EBADMSG;
log_error("Didn't get registry information.");
goto finish;
}
url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/repositories/", i->name, "/tags/", i->reference);
if (r < 0) {
log_error_errno(r, "Failed to allocate tags job: %m");
goto finish;
}
r = dkr_pull_add_token(i, i->tags_job);
if (r < 0) {
log_oom();
goto finish;
}
r = pull_job_begin(i->tags_job);
if (r < 0) {
log_error_errno(r, "Failed to start tags job: %m");
goto finish;
}
} else if (i->tags_job == j) {
const char *url;
assert(!i->ancestry_job);
if (r < 0) {
log_error_errno(r, "Failed to parse JSON id.");
goto finish;
}
if (r < 0) {
log_error_errno(r, "Failed to allocate ancestry job: %m");
goto finish;
}
r = dkr_pull_add_token(i, i->ancestry_job);
if (r < 0) {
log_oom();
goto finish;
}
if (r < 0) {
log_error_errno(r, "Failed to allocate json job: %m");
goto finish;
}
r = dkr_pull_add_token(i, i->json_job);
if (r < 0) {
log_oom();
goto finish;
}
r = pull_job_begin(i->ancestry_job);
if (r < 0) {
log_error_errno(r, "Failed to start ancestry job: %m");
goto finish;
}
r = pull_job_begin(i->json_job);
if (r < 0) {
log_error_errno(r, "Failed to start json job: %m");
goto finish;
}
} else if (i->ancestry_job == j) {
unsigned n;
if (r < 0) {
log_error_errno(r, "Failed to parse JSON id.");
goto finish;
}
n = strv_length(ancestry);
log_error("Ancestry doesn't end in main layer.");
r = -EBADMSG;
goto finish;
}
log_info("Ancestor lookup succeeded, requires layers:\n");
STRV_FOREACH(k, ancestry)
log_info("\t%s", *k);
i->n_ancestry = n;
i->current_ancestry = 0;
r = dkr_pull_pull_layer(i);
if (r < 0)
goto finish;
} else if (i->layer_job == j) {
assert(i->final_path);
if (i->tar_pid > 0) {
i->tar_pid = 0;
if (r < 0)
goto finish;
}
r = aufs_resolve(i->temp_path);
if (r < 0) {
log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
goto finish;
}
r = btrfs_subvol_set_read_only(i->temp_path, true);
if (r < 0) {
log_error_errno(r, "Failed to mark snapshot read-only: %m");
goto finish;
}
goto finish;
}
i->current_ancestry ++;
r = dkr_pull_pull_layer(i);
if (r < 0)
goto finish;
} else if (i->json_job != j)
assert_not_reached("Got finished event for unknown curl object");
if (!dkr_pull_is_done(i))
return;
r = dkr_pull_make_local_copy(i, DKR_PULL_V1);
if (r < 0)
goto finish;
r = 0;
if (i->on_finished)
i->on_finished(i, r, i->userdata);
else
sd_event_exit(i->event, r);
}
if (!sep)
return -EINVAL;
if (!dot)
return -EINVAL;
dot--;
if (!p)
return log_oom();
dot--;
if (!a)
return log_oom();
*address = a;
*protocol = p;
a = p = NULL;
return 0;
}
int dkr_pull_start(DkrPull *i, const char *name, const char *reference, const char *local, bool force_local, DkrPullVersion version) {
const char *url;
int r;
assert(i);
if (!dkr_name_is_valid(name))
return -EINVAL;
return -EINVAL;
return -EINVAL;
if (i->images_job)
return -EBUSY;
if (!reference)
reference = "latest";
free(i->index_protocol);
free(i->index_address);
if (r < 0)
return r;
if (r < 0)
return r;
i->force_local = force_local;
if (r < 0)
return r;
if (r < 0)
return r;
if (r < 0)
return r;
r = dkr_pull_add_token(i, i->images_job);
if (r < 0)
return r;
if (version == DKR_PULL_V1)
else
return pull_job_begin(i->images_job);
}