import-dck.c revision 72648326ea6d3e68cdb0b5890df737047d031a41
/*-*- 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 "hashmap.h"
#include "set.h"
#include "json.h"
#include "strv.h"
#include "curl-util.h"
#include "import-dck.h"
#include "btrfs-util.h"
#include "aufs-util.h"
/* TODO:
- convert json bits
- man page
- fall back to btrfs loop pool device
*/
typedef struct DckImportJob DckImportJob;
typedef struct DckImportName DckImportName;
typedef enum DckImportJobType {
struct DckImportJob {
bool done;
char *url;
struct curl_slist *request_header;
void *payload;
char *response_token;
char **response_registries;
char *temp_path;
char *final_path;
};
struct DckImportName {
char *name;
char *tag;
char *id;
char *local;
char **ancestry;
unsigned current_ancestry;
bool force_local;
};
struct DckImport {
void *userdata;
};
#define PROTOCOL_PREFIX "https://"
#define LAYERS_MAX 2048
static int dck_import_name_add_job(DckImportName *name, DckImportJobType type, const char *url, DckImportJob **ret);
if (!job)
return NULL;
if (job->tar_stream)
}
return NULL;
}
if (!name)
return NULL;
if (name->job_images)
if (name->job_ancestry)
return NULL;
}
if (import->on_finished)
else
}
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 (!dck_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 (!dck_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;
}
}
}
return NULL;
}
return NULL;
if (name->current_ancestry <= 0)
return NULL;
}
if (!name->job_images)
return NULL;
return NULL;
return NULL;
}
if (!name->job_images)
return NULL;
return NULL;
}
int r;
return;
return;
return;
return;
return;
const char *p, *q;
if (name->force_local) {
(void) btrfs_subvol_remove(p);
(void) rm_rf(p, false, true, false);
}
r = btrfs_subvol_snapshot(q, p, false, false);
if (r < 0) {
log_error_errno(r, "Failed to snapshot final image: %m");
return;
}
log_info("Created new image %s.", p);
}
}
bool gzip;
/* A stream to run tar on? */
return 0;
if (job->tar_stream)
return 0;
/* Maybe fork off tar, if we have enough to figure out that
* something is gzip compressed or not */
return 0;
/* Detect gzip signature */
int null_fd;
}
if (pipefd[0] != STDIN_FILENO)
safe_close(pipefd[0]);
if (null_fd < 0) {
}
}
if (null_fd != STDOUT_FILENO)
}
if (!job->tar_stream)
job->payload_size = 0;
return 0;
}
char **rg;
int r;
}
for (;;) {
if (!layer) {
return 0;
}
if (!path)
return log_oom();
break;
}
name->current_ancestry++;
}
if (r < 0) {
log_error_errno(r, "Failed to issue HTTP request: %m");
return r;
}
if (r == 0) /* Already downloading this one? */
return 0;
if (r < 0)
return log_oom();
if (base) {
const char *base_path;
} else
r = btrfs_subvol_make(temp);
if (r < 0)
return 0;
}
int r;
const char *url;
char **rg;
if (strv_isempty(rg)) {
log_error("Didn't get registry information.");
r = -EBADMSG;
goto fail;
}
if (r < 0) {
log_error_errno(r, "Failed to issue HTTP request: %m");
goto fail;
}
const char *url;
if (r < 0) {
log_error_errno(r, "Failed to parse JSON id.");
goto fail;
}
if (r < 0) {
log_error_errno(r, "Failed to issue HTTP request: %m");
goto fail;
}
if (r < 0) {
log_error_errno(r, "Failed to issue HTTP request: %m");
goto fail;
}
unsigned n;
if (r < 0) {
log_error_errno(r, "Failed to parse JSON id.");
goto fail;
}
n = strv_length(ancestry);
log_error("Ancestry doesn't end in main layer.");
r = -EBADMSG;
goto fail;
}
log_info("Ancestor lookup succeeded, requires layers:\n");
STRV_FOREACH(i, ancestry)
log_info("\t%s", *i);
name->current_ancestry = 0;
if (r < 0)
goto fail;
name->current_ancestry ++;
if (r < 0)
goto fail;
} else
assert_not_reached("Got finished event for unknown curl object");
return;
fail:
}
DckImportName *n;
long status;
Iterator i;
int r;
return;
if (!job)
return;
r = -EIO;
goto fail;
}
r = -EIO;
goto fail;
} else if (status >= 300) {
r = -EIO;
goto fail;
} else if (status < 200) {
r = -EIO;
goto fail;
}
case DCK_IMPORT_JOB_LAYER: {
if (!job->tar_stream) {
log_error("Downloaded layer too short.");
r = -EIO;
goto fail;
}
if (r < 0) {
log_error_errno(r, "Failed to wait for tar process: %m");
goto fail;
}
log_error_errno(r, "tar failed abnormally.");
r = -EIO;
goto fail;
}
if (r < 0) {
log_error_errno(r, "Couldn't resolve aufs whiteouts: %m");
goto fail;
}
if (r < 0) {
log_error_errno(r, "Failed to mark snapshot read-only: %m");
goto fail;
}
log_error_errno(r, "Failed to rename snapshot: %m");
goto fail;
}
break;
}
default:
;
}
return;
fail:
}
static size_t dck_import_job_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
DckImportJob *j = userdata;
char *p;
int r;
assert(j);
if (j->tar_stream) {
size_t l;
if (l != nmemb) {
r = -errno;
goto fail;
}
return l;
}
r = -EFBIG;
goto fail;
}
if (!p) {
r = -ENOMEM;
goto fail;
}
j->payload_size += sz;
j->payload = p;
r = dck_import_job_run_tar(j);
if (r < 0)
goto fail;
return sz;
fail:
dck_import_finish(j->import, r);
return 0;
}
static size_t dck_import_job_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
DckImportJob *j = userdata;
char *token;
int r;
assert(j);
if (r < 0) {
log_oom();
goto fail;
}
if (r > 0) {
free(j->response_token);
j->response_token = token;
}
if (r < 0) {
log_oom();
goto fail;
}
if (r > 0) {
char **l, **i;
if (!l) {
r = log_oom();
goto fail;
}
STRV_FOREACH(i, l) {
if (!hostname_is_valid(*i)) {
log_error("Registry hostname is not valid.");
strv_free(l);
r = -EBADMSG;
goto fail;
}
}
j->response_registries = l;
}
return sz;
fail:
dck_import_finish(j->import, r);
return 0;
}
static int dck_import_name_add_job(DckImportName *name, DckImportJobType type, const char *url, DckImportJob **ret) {
DckImportJob *f = NULL;
const char *t, *token;
int r;
if (f) {
return -EINVAL;
if (r < 0)
return r;
return 0;
}
if (r < 0)
return r;
if (!j)
return -ENOMEM;
if (!j->url)
return -ENOMEM;
if (r < 0)
return r;
if (r < 0)
return r;
if (token)
else
t = HEADER_TOKEN " true";
if (!j->request_header)
return -ENOMEM;
return -EIO;
return -EIO;
return -EIO;
return -EIO;
return -EIO;
if (r < 0)
return r;
if (r < 0)
return r;
if (r < 0) {
return r;
}
*ret = j;
j = NULL;
return 1;
}
const char *url;
}
int dck_import_new(DckImport **import, sd_event *event, dck_import_on_finished on_finished, void *userdata) {
int r;
if (!i)
return -ENOMEM;
i->on_finished = on_finished;
if (event)
else {
r = sd_event_default(&i->event);
if (r < 0)
return r;
}
if (r < 0)
return r;
*import = i;
i = NULL;
return 0;
}
DckImportName *n;
DckImportJob *j;
if (!import)
return NULL;
return NULL;
}
DckImportName *n;
if (!n)
return 0;
return 1;
}
int dck_import_pull(DckImport *import, const char *name, const char *tag, const char *local, bool force_local) {
int r;
return -EEXIST;
if (r < 0)
return r;
if (!n)
return -ENOMEM;
if (!n->name)
return -ENOMEM;
if (!n->tag)
return -ENOMEM;
if (local) {
if (!n->local)
return -ENOMEM;
n->force_local = force_local;
}
if (r < 0)
return r;
r = dck_import_name_begin(n);
if (r < 0) {
n = NULL;
return r;
}
n = NULL;
return 0;
}
bool dck_name_is_valid(const char *name) {
const char *slash, *p;
return false;
if (!slash)
return false;
return false;
if (!filename_is_valid(p))
return false;
return true;
}
bool dck_id_is_valid(const char *id) {
if (!filename_is_valid(id))
return false;
return false;
return true;
}