From: Chengyu Zhu <[email protected]> Avoid buffering downloaded OCI blobs in memory. Instead, write the received data directly to the target file descriptor within the curl write callback.
Signed-off-by: Chengyu Zhu <[email protected]> --- lib/remotes/oci.c | 198 +++++++++++++--------------------------------- 1 file changed, 55 insertions(+), 143 deletions(-) diff --git a/lib/remotes/oci.c b/lib/remotes/oci.c index 8b253a3..a3efd77 100644 --- a/lib/remotes/oci.c +++ b/lib/remotes/oci.c @@ -64,10 +64,8 @@ struct ocierofs_response { long http_code; }; -struct ocierofs_stream { - const char *digest; - int blobfd; -}; +static int ocierofs_download_blob_range(struct ocierofs_ctx *ctx, const char *digest, + off_t offset, size_t length, int fd); static inline const char *ocierofs_get_api_registry(const char *registry) { @@ -125,26 +123,42 @@ static size_t ocierofs_write_callback(void *contents, size_t size, return realsize; } -static size_t ocierofs_layer_write_callback(void *contents, size_t size, - size_t nmemb, void *userp) +struct ocierofs_write_ctx { + int fd; + off_t offset; + CURL *curl; + bool range_req; +}; + +static size_t ocierofs_fd_write_callback(void *contents, size_t size, + size_t nmemb, void *userp) { - struct ocierofs_stream *stream = userp; size_t realsize = size * nmemb; + struct ocierofs_write_ctx *wctx = userp; const char *buf = contents; size_t written = 0; + long http_code = 0; - if (stream->blobfd < 0) - return 0; + if (wctx->curl) { + curl_easy_getinfo(wctx->curl, CURLINFO_RESPONSE_CODE, &http_code); + if (wctx->range_req && http_code == 200) { + erofs_err("server returned 200 OK for Range request, aborting"); + return 0; + } + wctx->curl = NULL; + } while (written < realsize) { - ssize_t n = write(stream->blobfd, buf + written, realsize - written); + ssize_t n = pwrite(wctx->fd, buf + written, realsize - written, wctx->offset); if (n < 0) { - erofs_err("failed to write layer data for layer %s", - stream->digest); + if (errno == EINTR) + continue; + erofs_err("failed to write cache data: %s", strerror(errno)); return 0; } written += n; + wctx->offset += n; } return realsize; } @@ -1157,89 +1171,33 @@ static int ocierofs_init(struct ocierofs_ctx *ctx, const struct ocierofs_config return 0; } -static int ocierofs_download_blob_to_fd(struct ocierofs_ctx *ctx, - const char *digest, - const char *auth_header, - int outfd) -{ - struct ocierofs_request req = {}; - struct ocierofs_stream stream = {}; - const char *api_registry; - long http_code; - int ret; - - stream = (struct ocierofs_stream) { - .digest = digest, - .blobfd = outfd, - }; - - api_registry = ocierofs_get_api_registry(ctx->registry); - if (asprintf(&req.url, "https://%s/v2/%s/blobs/%s", - api_registry, ctx->repository, digest) == -1) - return -ENOMEM; - - if (auth_header && strstr(auth_header, "Bearer")) - req.headers = curl_slist_append(req.headers, auth_header); - - curl_easy_reset(ctx->curl); - - ret = ocierofs_curl_setup_common_options(ctx->curl); - if (ret) - goto out; - - ret = ocierofs_curl_setup_rq(ctx->curl, req.url, OCIEROFS_HTTP_GET, - req.headers, - ocierofs_layer_write_callback, - &stream, NULL, NULL); - if (ret) - goto out; - - ret = ocierofs_curl_perform(ctx->curl, &http_code); - if (ret) - goto out; - - if (http_code < 200 || http_code >= 300) { - erofs_err("HTTP request failed with code %ld", http_code); - ret = -EIO; - goto out; - } - ret = 0; -out: - ocierofs_request_cleanup(&req); - return ret; -} - static int ocierofs_extract_layer(struct ocierofs_ctx *ctx, const char *digest, const char *auth_header) { - struct ocierofs_stream stream = {}; - int ret; + int fd, ret; - stream = (struct ocierofs_stream) { - .digest = digest, - .blobfd = erofs_tmpfile(), - }; - if (stream.blobfd < 0) { + fd = erofs_tmpfile(); + if (fd < 0) { erofs_err("failed to create temporary file for %s", digest); return -errno; } - ret = ocierofs_download_blob_to_fd(ctx, digest, auth_header, stream.blobfd); + ret = ocierofs_download_blob_range(ctx, digest, 0, 0, fd); if (ret) goto out; - if (lseek(stream.blobfd, 0, SEEK_SET) < 0) { + if (lseek(fd, 0, SEEK_SET) < 0) { erofs_err("failed to seek to beginning of temp file: %s", strerror(errno)); ret = -errno; goto out; } - return stream.blobfd; + return fd; out: - if (stream.blobfd >= 0) - close(stream.blobfd); + if (fd >= 0) + close(fd); return ret; } @@ -1334,33 +1292,32 @@ out: return ret; } -static int ocierofs_download_blob_range(struct ocierofs_ctx *ctx, off_t offset, size_t length, - void **out_buf, size_t *out_size) +static int ocierofs_download_blob_range(struct ocierofs_ctx *ctx, const char *digest, + off_t offset, size_t length, int fd) { struct ocierofs_request req = {}; - struct ocierofs_response resp = {}; + struct ocierofs_write_ctx wctx = { + .fd = fd, + .offset = offset, + .curl = ctx->curl, + .range_req = (length != 0), + }; const char *api_registry; char rangehdr[64]; long http_code = 0; int ret, index; - const char *digest; u64 blob_size; - size_t available; - size_t copy_size; - index = ocierofs_find_layer_by_digest(ctx, ctx->blob_digest); + index = ocierofs_find_layer_by_digest(ctx, digest); if (index < 0) return -ENOENT; - digest = ctx->blob_digest; blob_size = ctx->layers[index]->size; if (offset < 0) return -EINVAL; - if (offset >= blob_size) { - *out_size = 0; + if (offset >= blob_size) return 0; - } if (length && offset + length > blob_size) length = (size_t)(blob_size - offset); @@ -1373,13 +1330,15 @@ static int ocierofs_download_blob_range(struct ocierofs_ctx *ctx, off_t offset, if (length) snprintf(rangehdr, sizeof(rangehdr), "Range: bytes=%lld-%lld", (long long)offset, (long long)(offset + (off_t)length - 1)); - else + else if (offset > 0) snprintf(rangehdr, sizeof(rangehdr), "Range: bytes=%lld-", (long long)offset); if (ctx->auth_header && strstr(ctx->auth_header, "Bearer")) req.headers = curl_slist_append(req.headers, ctx->auth_header); - req.headers = curl_slist_append(req.headers, rangehdr); + + if (wctx.range_req || offset > 0) + req.headers = curl_slist_append(req.headers, rangehdr); curl_easy_reset(ctx->curl); @@ -1389,8 +1348,8 @@ static int ocierofs_download_blob_range(struct ocierofs_ctx *ctx, off_t offset, ret = ocierofs_curl_setup_rq(ctx->curl, req.url, OCIEROFS_HTTP_GET, req.headers, - ocierofs_write_callback, - &resp, NULL, NULL); + ocierofs_fd_write_callback, + &wctx, NULL, NULL); if (ret) goto out; @@ -1398,30 +1357,10 @@ static int ocierofs_download_blob_range(struct ocierofs_ctx *ctx, off_t offset, if (ret) goto out; - ret = 0; - if (http_code == 206) { - *out_buf = resp.data; - *out_size = resp.size; - resp.data = NULL; - } else if (http_code == 200) { - if (!offset) { - *out_buf = resp.data; - *out_size = resp.size; - resp.data = NULL; - } else if (offset < resp.size) { - available = resp.size - offset; - copy_size = length ? min_t(size_t, length, available) : available; - - *out_buf = malloc(copy_size); - if (!*out_buf) { - ret = -ENOMEM; - goto out; - } - memcpy(*out_buf, resp.data + offset, copy_size); - *out_size = copy_size; - } + if (http_code == 206 || (http_code == 200 && !wctx.range_req && offset == 0)) { + ret = 0; } else { - erofs_err("HTTP range request failed with code %ld", http_code); + erofs_err("HTTP request failed with code %ld", http_code); ret = -EIO; } @@ -1429,15 +1368,12 @@ out: if (req.headers) curl_slist_free_all(req.headers); free(req.url); - free(resp.data); return ret; } static int ocierofs_cache(struct ocierofs_iostream *oci_iostream, off_t offset, size_t needed) { struct ocierofs_ctx *ctx = oci_iostream->ctx; - void *download_buf = NULL; - size_t download_size = 0; int ret = 0; off_t hole, align_offset; size_t download_len; @@ -1479,32 +1415,8 @@ static int ocierofs_cache(struct ocierofs_iostream *oci_iostream, off_t offset, align_offset = round_down(hole, OCIEROFS_IO_CHUNK_SIZE); download_len = max_t(size_t, offset + needed - align_offset, OCIEROFS_IO_CHUNK_SIZE); - ret = ocierofs_download_blob_range(ctx, align_offset, download_len, - &download_buf, &download_size); - if (ret < 0) - return ret; - - if (download_buf && download_size > 0) { - char *p = download_buf; - size_t to_write = download_size; - ssize_t written = 0; - - while (to_write > 0) { - ssize_t w = pwrite(oci_iostream->cache_fd, p, to_write, align_offset + written); - if (w < 0) { - if (errno == EINTR) - continue; - ret = -errno; - goto out_free; - } - written += w; - p += w; - to_write -= w; - } - } - -out_free: - free(download_buf); + ret = ocierofs_download_blob_range(ctx, ctx->blob_digest, align_offset, download_len, + oci_iostream->cache_fd); return ret; } -- 2.47.1
