|
35 | 35 | #include <sys/xattr.h> |
36 | 36 | #include <fcntl.h> |
37 | 37 | #include <curl/curl.h> |
| 38 | +#include <json-glib/json-glib.h> |
38 | 39 |
|
39 | 40 | #ifdef WITH_ZCHUNK |
40 | 41 | #include <zck.h> |
@@ -85,6 +86,16 @@ typedef enum { |
85 | 86 | All headers which we were looking for are already found*/ |
86 | 87 | } LrHeaderCbState; |
87 | 88 |
|
| 89 | +/** Enum with OCI file status */ |
| 90 | +typedef enum { |
| 91 | + LR_OCI_DL_WAITING, /*!< |
| 92 | + The OCI file is waiting to be processed. */ |
| 93 | + LR_OCI_DL_MANIFEST, /*!< |
| 94 | + The OCI manifest file is being downloaded. */ |
| 95 | + LR_OCI_DL_LAYER, /*!< |
| 96 | + The OCI layer is being downloaded. */ |
| 97 | +} LrOciState; |
| 98 | + |
88 | 99 | /** Enum with zchunk file status */ |
89 | 100 | typedef enum { |
90 | 101 | LR_ZCK_DL_HEADER_CK, /*!< |
@@ -185,6 +196,8 @@ typedef struct { |
185 | 196 | Last cb return code. */ |
186 | 197 | struct curl_slist *curl_rqheaders; /*!< |
187 | 198 | Extra headers for request. */ |
| 199 | + LrOciState oci_state; /*!< |
| 200 | + OCI download state. */ |
188 | 201 |
|
189 | 202 | #ifdef WITH_ZCHUNK |
190 | 203 | LrZckState zck_state; /*!< |
@@ -1399,7 +1412,19 @@ open_target_file(LrTarget *target, GError **err) |
1399 | 1412 | int fd; |
1400 | 1413 | FILE *f; |
1401 | 1414 |
|
1402 | | - if (target->target->fd != -1) { |
| 1415 | + if (target->oci_state == LR_OCI_DL_MANIFEST) { |
| 1416 | + if (target->target->fn == NULL) { |
| 1417 | + // Create a temporary file for the OCI manifest |
| 1418 | + const char *tmpdir = getenv("TMPDIR"); |
| 1419 | + if (tmpdir == NULL) |
| 1420 | + tmpdir = "/tmp"; |
| 1421 | + char *tmpname = g_strdup_printf("%s/librepo-oci-XXXXXX", tmpdir); |
| 1422 | + close (mkstemp(tmpname)); |
| 1423 | + target->target->fn = tmpname; |
| 1424 | + } |
| 1425 | + } |
| 1426 | + |
| 1427 | + if (target->oci_state != LR_OCI_DL_MANIFEST && target->target->fd != -1) { |
1403 | 1428 | // Use supplied filedescriptor |
1404 | 1429 | fd = dup(target->target->fd); |
1405 | 1430 | if (fd == -1) { |
@@ -1435,6 +1460,80 @@ open_target_file(LrTarget *target, GError **err) |
1435 | 1460 | return f; |
1436 | 1461 | } |
1437 | 1462 |
|
| 1463 | +/** Get the OCI manifest URL |
| 1464 | + */ |
| 1465 | +static char * |
| 1466 | +get_oci_manifest_url(char *oci_url, GError **err) |
| 1467 | +{ |
| 1468 | + assert(!err || *err == NULL); |
| 1469 | + |
| 1470 | + // Remove the 'oci://' prefix |
| 1471 | + char *hostname = strdup(oci_url + 6); |
| 1472 | + |
| 1473 | + char *first_slash = strchr(hostname, '/'); |
| 1474 | + if (first_slash == NULL || first_slash[1] == 0) { |
| 1475 | + g_set_error(err, LR_DOWNLOADER_ERROR, LRE_IO, |
| 1476 | + "invalid OCI URL format: %s", |
| 1477 | + oci_url); |
| 1478 | + return NULL; |
| 1479 | + } |
| 1480 | + *first_slash = 0; |
| 1481 | + |
| 1482 | + char *result = g_strdup_printf ("https://%s/v2/%s/manifests/latest", |
| 1483 | + hostname, |
| 1484 | + first_slash + 1); |
| 1485 | + free (hostname); |
| 1486 | + return result; |
| 1487 | +} |
| 1488 | + |
| 1489 | +static char * |
| 1490 | +get_oci_layer_url(char *oci_url, char *fn, GError **err) { |
| 1491 | + assert(!err || *err == NULL); |
| 1492 | + |
| 1493 | + // Load the JSON manifest file |
| 1494 | + JsonParser *parser = json_parser_new(); |
| 1495 | + |
| 1496 | + // Load the JSON manifest file |
| 1497 | + if (!json_parser_load_from_file(parser, fn, err)) { |
| 1498 | + g_object_unref(parser); |
| 1499 | + return NULL; |
| 1500 | + } |
| 1501 | + |
| 1502 | + // Delete the manifest file |
| 1503 | + unlink(fn); |
| 1504 | + |
| 1505 | + // Get the root object |
| 1506 | + JsonNode *root = json_parser_get_root(parser); |
| 1507 | + JsonObject *root_obj = json_node_get_object(root); |
| 1508 | + |
| 1509 | + // Navigate to the layers array |
| 1510 | + JsonArray *layers = json_object_get_array_member(root_obj, "layers"); |
| 1511 | + JsonObject *first_layer = json_array_get_object_element(layers, 0); |
| 1512 | + |
| 1513 | + // Extract the digest for the first layer |
| 1514 | + const char *digest = json_object_get_string_member(first_layer, "digest"); |
| 1515 | + |
| 1516 | + // Remove the 'oci://' prefix |
| 1517 | + char *hostname = strdup(oci_url + 6); |
| 1518 | + |
| 1519 | + char *first_slash = strchr(hostname, '/'); |
| 1520 | + if (first_slash == NULL || first_slash[1] == 0) { |
| 1521 | + g_set_error(err, LR_DOWNLOADER_ERROR, LRE_IO, |
| 1522 | + "invalid OCI URL format: %s", |
| 1523 | + oci_url); |
| 1524 | + return NULL; |
| 1525 | + } |
| 1526 | + *first_slash = 0; |
| 1527 | + |
| 1528 | + char *result = g_strdup_printf ("https://%s/v2/%s/blobs/%s", |
| 1529 | + hostname, |
| 1530 | + first_slash + 1, |
| 1531 | + digest); |
| 1532 | + free (hostname); |
| 1533 | + g_object_unref(parser); |
| 1534 | + return result; |
| 1535 | +} |
| 1536 | + |
1438 | 1537 | /** Prepare next transfer |
1439 | 1538 | */ |
1440 | 1539 | static gboolean |
@@ -1478,6 +1577,17 @@ prepare_next_transfer(LrDownload *dd, gboolean *candidatefound, GError **err) |
1478 | 1577 |
|
1479 | 1578 | protocol = lr_detect_protocol(full_url); |
1480 | 1579 |
|
| 1580 | + if (protocol == LR_PROTOCOL_OCI) { |
| 1581 | + if (target->oci_state == LR_OCI_DL_WAITING) { |
| 1582 | + target->oci_state = LR_OCI_DL_MANIFEST; |
| 1583 | + full_url = get_oci_manifest_url(full_url, err); |
| 1584 | + } else if (target->oci_state == LR_OCI_DL_LAYER) { |
| 1585 | + full_url = get_oci_layer_url(full_url, target->target->fn, err); |
| 1586 | + } |
| 1587 | + if (!full_url) |
| 1588 | + goto fail; |
| 1589 | + } |
| 1590 | + |
1481 | 1591 | // Prepare CURL easy handle |
1482 | 1592 | CURLcode c_rc; |
1483 | 1593 | CURL *h; |
@@ -1656,6 +1766,18 @@ prepare_next_transfer(LrDownload *dd, gboolean *candidatefound, GError **err) |
1656 | 1766 | if (!headers) |
1657 | 1767 | lr_out_of_memory(); |
1658 | 1768 | } |
| 1769 | + if (target->oci_state == LR_OCI_DL_MANIFEST) { |
| 1770 | + headers = curl_slist_append(headers, "Authorization: Bearer QQ=="); |
| 1771 | + if (!headers) |
| 1772 | + lr_out_of_memory(); |
| 1773 | + headers = curl_slist_append(headers, "Accept: application/vnd.oci.image.manifest.v1+json"); |
| 1774 | + if (!headers) |
| 1775 | + lr_out_of_memory(); |
| 1776 | + } else if (target->oci_state == LR_OCI_DL_LAYER) { |
| 1777 | + headers = curl_slist_append(headers, "Authorization: Bearer QQ=="); |
| 1778 | + if (!headers) |
| 1779 | + lr_out_of_memory(); |
| 1780 | + } |
1659 | 1781 | target->curl_rqheaders = headers; |
1660 | 1782 | c_rc = curl_easy_setopt(h, CURLOPT_HTTPHEADER, headers); |
1661 | 1783 | assert(c_rc == CURLE_OK); |
@@ -2324,15 +2446,17 @@ check_transfer_statuses(LrDownload *dd, GError **err) |
2324 | 2446 | // New file was downloaded - clear checksums cached in extended attributes |
2325 | 2447 | lr_checksum_clear_cache(fd); |
2326 | 2448 |
|
2327 | | - ret = check_finished_transfer_checksum(fd, |
2328 | | - target->target->checksums, |
2329 | | - &matches, |
2330 | | - &transfer_err, |
2331 | | - &tmp_err); |
| 2449 | + ret = target->oci_state == LR_OCI_DL_WAITING |
| 2450 | + ? check_finished_transfer_checksum(fd, |
| 2451 | + target->target->checksums, |
| 2452 | + &matches, |
| 2453 | + &transfer_err, |
| 2454 | + &tmp_err) |
| 2455 | + : 1; |
2332 | 2456 | if (!ret) { // Error |
2333 | 2457 | g_propagate_prefixed_error(err, tmp_err, "Downloading from %s" |
2334 | | - "was successful but error encountered while " |
2335 | | - "checksumming: ", effective_url); |
| 2458 | + "was successful but error encountered while " |
| 2459 | + "checksumming: ", effective_url); |
2336 | 2460 | return FALSE; |
2337 | 2461 | } |
2338 | 2462 | #ifdef WITH_ZCHUNK |
@@ -2506,31 +2630,38 @@ check_transfer_statuses(LrDownload *dd, GError **err) |
2506 | 2630 | target->tried_mirrors = g_slist_remove(target->tried_mirrors, target->mirror); |
2507 | 2631 | } else { |
2508 | 2632 | #endif /* WITH_ZCHUNK */ |
2509 | | - target->state = LR_DS_FINISHED; |
2510 | | - |
2511 | | - // Remove xattr that states that the file is being downloaded |
2512 | | - // by librepo, because the file is now completely downloaded |
2513 | | - // and the xattr is not needed (is is useful only for resuming) |
2514 | | - remove_librepo_xattr(target->target); |
2515 | | - |
2516 | | - // Call end callback |
2517 | | - LrEndCb end_cb = target->target->endcb; |
2518 | | - if (end_cb) { |
2519 | | - int rc = end_cb(target->target->cbdata, |
2520 | | - LR_TRANSFER_SUCCESSFUL, |
2521 | | - NULL); |
2522 | | - if (rc == LR_CB_ERROR) { |
2523 | | - target->cb_return_code = LR_CB_ERROR; |
2524 | | - g_debug("%s: Downloading was aborted by LR_CB_ERROR " |
2525 | | - "from end callback", __func__); |
2526 | | - g_set_error(&fail_fast_error, LR_DOWNLOADER_ERROR, |
2527 | | - LRE_CBINTERRUPTED, |
2528 | | - "Interrupted by LR_CB_ERROR from end callback"); |
2529 | | - } |
2530 | | - } |
2531 | | - if (target->mirror) |
2532 | | - lr_downloadtarget_set_usedmirror(target->target, |
2533 | | - target->mirror->mirror->url); |
| 2633 | + if (target->oci_state == LR_OCI_DL_MANIFEST) { |
| 2634 | + // Now that we have the manifest, let's download the |
| 2635 | + // layer blob. |
| 2636 | + target->oci_state = LR_OCI_DL_LAYER; |
| 2637 | + target->state = LR_DS_WAITING; |
| 2638 | + } else { |
| 2639 | + target->state = LR_DS_FINISHED; |
| 2640 | + |
| 2641 | + // Remove xattr that states that the file is being downloaded |
| 2642 | + // by librepo, because the file is now completely downloaded |
| 2643 | + // and the xattr is not needed (is is useful only for resuming) |
| 2644 | + remove_librepo_xattr(target->target); |
| 2645 | + |
| 2646 | + // Call end callback |
| 2647 | + LrEndCb end_cb = target->target->endcb; |
| 2648 | + if (end_cb) { |
| 2649 | + int rc = end_cb(target->target->cbdata, |
| 2650 | + LR_TRANSFER_SUCCESSFUL, |
| 2651 | + NULL); |
| 2652 | + if (rc == LR_CB_ERROR) { |
| 2653 | + target->cb_return_code = LR_CB_ERROR; |
| 2654 | + g_debug("%s: Downloading was aborted by LR_CB_ERROR " |
| 2655 | + "from end callback", __func__); |
| 2656 | + g_set_error(&fail_fast_error, LR_DOWNLOADER_ERROR, |
| 2657 | + LRE_CBINTERRUPTED, |
| 2658 | + "Interrupted by LR_CB_ERROR from end callback"); |
| 2659 | + } |
| 2660 | + } |
| 2661 | + if (target->mirror) |
| 2662 | + lr_downloadtarget_set_usedmirror(target->target, |
| 2663 | + target->mirror->mirror->url); |
| 2664 | + } |
2534 | 2665 | #ifdef WITH_ZCHUNK |
2535 | 2666 | } |
2536 | 2667 | #endif /* WITH_ZCHUNK */ |
|
0 commit comments