richardphibel / rpms / librepo

Forked from rpms/librepo 2 years ago
Clone
e660d2
From 63f9932642bcd7c617faf8b38f4976945e694883 Mon Sep 17 00:00:00 2001
e660d2
From: Matthew Almond <malmond@fb.com>
e660d2
Date: Mon, 21 Sep 2020 15:20:07 -0700
e660d2
Subject: [PATCH 1/2] Add support for rpm2extents transcoder
e660d2
e660d2
Two related parts:
e660d2
e660d2
1. If `LIBREPO_TRANSCODE_RPMS` environment is set to a program (with parameters) then downloads are piped through it.
e660d2
2. Transcoded RPMS by definition will not have the same bits on disk as downloaded. This is inherent. The transcoder is tasked with measuring the bits that enter stdin and storing a copy of the digest(s) seen in the footer. `librepo` can then use these stored digests instead if the environment variable is set.
e660d2
e660d2
This is part of changes described in https://fedoraproject.org/wiki/Changes/RPMCoW
e660d2
---
e660d2
 librepo/checksum.c   | 111 ++++++++++++++++++++++++++++++++-
e660d2
 librepo/downloader.c | 145 ++++++++++++++++++++++++++++++++++++++++++-
e660d2
 librepo/rcodes.h     |   2 +
e660d2
 3 files changed, 254 insertions(+), 4 deletions(-)
e660d2
e660d2
diff --git a/librepo/checksum.c b/librepo/checksum.c
e660d2
index 47e59467..0f27815e 100644
e660d2
--- a/librepo/checksum.c
e660d2
+++ b/librepo/checksum.c
e660d2
@@ -39,6 +39,9 @@
e660d2
 #define BUFFER_SIZE             2048
e660d2
 #define MAX_CHECKSUM_NAME_LEN   7
e660d2
 
e660d2
+/* magic value at end of file (64 bits) that indicates this is a transcoded rpm */
e660d2
+#define MAGIC 3472329499408095051
e660d2
+
e660d2
 LrChecksumType
e660d2
 lr_checksum_type(const char *type)
e660d2
 {
e660d2
@@ -102,6 +105,100 @@ lr_checksum_type_to_str(LrChecksumType type)
e660d2
     return NULL;
e660d2
 }
e660d2
 
e660d2
+char *
e660d2
+lr_checksum_cow_fd(LrChecksumType type, int fd, GError **err)
e660d2
+{
e660d2
+    struct __attribute__ ((__packed__)) csum_offset_magic {
e660d2
+        off64_t csum_offset;
e660d2
+        uint64_t magic;
e660d2
+    };
e660d2
+    struct __attribute__ ((__packed__)) orig_size_algos_len {
e660d2
+        ssize_t orig_size;
e660d2
+        uint32_t algos_len;
e660d2
+    };
e660d2
+    struct __attribute__ ((__packed__)) algo_len_digest_len {
e660d2
+        uint32_t algo_len;
e660d2
+        uint32_t digest_len;
e660d2
+    };
e660d2
+
e660d2
+    struct csum_offset_magic csum_offset_magic;
e660d2
+    struct orig_size_algos_len orig_size_algos_len;
e660d2
+    struct algo_len_digest_len algo_len_digest_len;
e660d2
+    char *algo, *checksum;
e660d2
+    unsigned char *digest;
e660d2
+    size_t len = sizeof(csum_offset_magic);
e660d2
+
e660d2
+    if (g_getenv("LIBREPO_TRANSCODE_RPMS") == NULL) {
e660d2
+        g_debug("Transcoding not enabled, skipping path");
e660d2
+        return NULL;
e660d2
+    }
e660d2
+    if (lseek(fd, -len, SEEK_END) == -1) {
e660d2
+        g_warning("seek for transcode failed, probably too small");
e660d2
+        return NULL;
e660d2
+    }
e660d2
+    if (read(fd, &csum_offset_magic, len) != len) {
e660d2
+        g_set_error(err, LR_CHECKSUM_ERROR, LRE_TRANSCODE,
e660d2
+                    "Cannot read csum_offset, magic. size = %lu", len);
e660d2
+        return NULL;
e660d2
+    }
e660d2
+    if (csum_offset_magic.magic != MAGIC) {
e660d2
+        g_debug("Not transcoded");
e660d2
+        return NULL;
e660d2
+    }
e660d2
+    g_debug("Is transcoded");
e660d2
+    if (lseek(fd, csum_offset_magic.csum_offset, SEEK_SET) == -1) {
e660d2
+        g_set_error(err, LR_CHECKSUM_ERROR, LRE_TRANSCODE,
e660d2
+                    "seek for transcode csum_offset failed");
e660d2
+        return NULL;
e660d2
+    }
e660d2
+    len = sizeof(orig_size_algos_len);
e660d2
+    if (read(fd, &orig_size_algos_len, len) != len) {
e660d2
+        g_set_error(err, LR_CHECKSUM_ERROR, LRE_TRANSCODE,
e660d2
+                    "Cannot read orig_size_algos_len");
e660d2
+        return NULL;
e660d2
+    }
e660d2
+    while (orig_size_algos_len.algos_len > 0) {
e660d2
+        len = sizeof(algo_len_digest_len);
e660d2
+        if (read(fd, &algo_len_digest_len, len) != len) {
e660d2
+            g_set_error(err, LR_CHECKSUM_ERROR, LRE_TRANSCODE,
e660d2
+                        "Cannot read algo_len_digest_len");
e660d2
+            return NULL;
e660d2
+        }
e660d2
+
e660d2
+        len = algo_len_digest_len.algo_len;
e660d2
+        algo = lr_malloc0(len + 1);
e660d2
+        if (read(fd, algo, len) != len) {
e660d2
+            g_set_error(err, LR_CHECKSUM_ERROR, LRE_TRANSCODE,
e660d2
+                        "Cannot read algo");
e660d2
+            lr_free(algo);
e660d2
+            return NULL;
e660d2
+        }
e660d2
+        len = algo_len_digest_len.digest_len;
e660d2
+        digest = lr_malloc0(len);
e660d2
+        if (read(fd, digest, len) != len) {
e660d2
+            g_set_error(err, LR_CHECKSUM_ERROR, LRE_TRANSCODE,
e660d2
+                        "Cannot read digest");
e660d2
+            lr_free(algo);
e660d2
+            lr_free(digest);
e660d2
+            return NULL;
e660d2
+        }
e660d2
+        if (lr_checksum_type(algo) == type) {
e660d2
+            /* found it, do the same as lr_checksum_fd does */
e660d2
+            checksum = lr_malloc0(sizeof(char) * (len * 2 + 1));
e660d2
+            for (size_t x = 0; x < len; x++) {
e660d2
+                sprintf(checksum+(x*2), "%02x", digest[x]);
e660d2
+            }
e660d2
+            lr_free(algo);
e660d2
+            lr_free(digest);
e660d2
+            return checksum;
e660d2
+        }
e660d2
+        lr_free(algo);
e660d2
+        lr_free(digest);
e660d2
+        orig_size_algos_len.algos_len--;
e660d2
+    }
e660d2
+    return NULL;
e660d2
+}
e660d2
+
e660d2
 char *
e660d2
 lr_checksum_fd(LrChecksumType type, int fd, GError **err)
e660d2
 {
e660d2
@@ -257,9 +354,17 @@ lr_checksum_fd_compare(LrChecksumType type,
e660d2
         }
e660d2
     }
e660d2
 
e660d2
-    checksum = lr_checksum_fd(type, fd, err);
e660d2
-    if (!checksum)
e660d2
-        return FALSE;
e660d2
+    checksum = lr_checksum_cow_fd(type, fd, err);
e660d2
+    if (checksum) {
e660d2
+        // if checksum is found in CoW package, do not cache it in xattr
e660d2
+        // because looking this up is nearly constant time (cheap) but
e660d2
+        // is not valid when CoW is not enabled in RPM.
e660d2
+        caching = FALSE;
e660d2
+    } else {
e660d2
+        checksum = lr_checksum_fd(type, fd, err);
e660d2
+        if (!checksum)
e660d2
+            return FALSE;
e660d2
+    }
e660d2
 
e660d2
     *matches = (strcmp(expected, checksum)) ? FALSE : TRUE;
e660d2
 
e660d2
diff --git a/librepo/downloader.c b/librepo/downloader.c
e660d2
index c5278fbc..5326eb7a 100644
e660d2
--- a/librepo/downloader.c
e660d2
+++ b/librepo/downloader.c
e660d2
@@ -32,6 +32,7 @@
e660d2
 #include <sys/types.h>
e660d2
 #include <sys/stat.h>
e660d2
 #include <sys/time.h>
e660d2
+#include <sys/wait.h>
e660d2
 #include <sys/xattr.h>
e660d2
 #include <fcntl.h>
e660d2
 #include <curl/curl.h>
e660d2
@@ -151,6 +152,10 @@ typedef struct {
e660d2
     FILE *f; /*!<
e660d2
         fdopened file descriptor from LrDownloadTarget and used
e660d2
         in curl_handle. */
e660d2
+    FILE *writef; /*!<
e660d2
+        the fd to write data to. Could be a subprocess. */
e660d2
+    pid_t pid; /*!<
e660d2
+        the pid of a transcoder. */
e660d2
     char errorbuffer[CURL_ERROR_SIZE]; /*!<
e660d2
         Error buffer used in curl handle */
e660d2
     GSList *tried_mirrors; /*!<
e660d2
@@ -614,7 +619,7 @@ lr_writecb(char *ptr, size_t size, size_t nmemb, void *userdata)
e660d2
     if (range_start <= 0 && range_end <= 0) {
e660d2
         // Write everything curl give to you
e660d2
         target->writecb_recieved += all;
e660d2
-        return fwrite(ptr, size, nmemb, target->f);
e660d2
+        return fwrite(ptr, size, nmemb, target->writef);
e660d2
     }
e660d2
 
e660d2
     /* Deal with situation when user wants only specific byte range of the
e660d2
@@ -1428,6 +1433,136 @@ open_target_file(LrTarget *target, GError **err)
e660d2
     return f;
e660d2
 }
e660d2
 
e660d2
+/** Maybe transcode the file
e660d2
+ */
e660d2
+void
e660d2
+maybe_transcode(LrTarget *target, GError **err)
e660d2
+{
e660d2
+    const char *e = g_getenv("LIBREPO_TRANSCODE_RPMS");
e660d2
+    int transcoder_stdin[2], fd;
e660d2
+    pid_t pid;
e660d2
+    FILE *out;
e660d2
+    _cleanup_strv_free_ gchar **args = NULL;
e660d2
+    target->writef = NULL;
e660d2
+    if (!e) {
e660d2
+        g_debug("Not transcoding");
e660d2
+        target->writef = target->f;
e660d2
+        return;
e660d2
+    }
e660d2
+    if (g_str_has_suffix(target->target->path, ".rpm") == FALSE) {
e660d2
+        g_debug("Not transcoding %s due to name", target->target->path);
e660d2
+        target->writef = target->f;
e660d2
+        return;
e660d2
+    }
e660d2
+    g_debug("Transcoding %s", target->target->path);
e660d2
+    args = g_strsplit(e, " ", -1);
e660d2
+    if (args[0] == NULL) {
e660d2
+        g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                    "transcode env empty");
e660d2
+        return;
e660d2
+    }
e660d2
+    if (pipe(transcoder_stdin) != 0) {
e660d2
+        g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                    "input pipe creation failed: %s",
e660d2
+                    g_strerror(errno));
e660d2
+        return;
e660d2
+    }
e660d2
+    /** librepo collects the 'write' ends of the pipes. We must mark these as
e660d2
+     * FD_CLOEXEC so a second download/transcode does not inherit them and
e660d2
+     * hold them open, as it'll prevent an EOF and cause a deadlock.
e660d2
+    */
e660d2
+    if (fcntl(transcoder_stdin[1], F_SETFD, FD_CLOEXEC) != 0) {
e660d2
+        g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                    "input pipe write close-on-fork failed: %s",
e660d2
+                    g_strerror(errno));
e660d2
+        return;
e660d2
+    }
e660d2
+    pid = fork();
e660d2
+    if (pid == -1) {
e660d2
+        g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                    "fork failed: %s",
e660d2
+                    g_strerror(errno));
e660d2
+        return;
e660d2
+    }
e660d2
+    if (pid == 0) {
e660d2
+        /* child */
e660d2
+        if (dup2(transcoder_stdin[0], STDIN_FILENO) == -1) {
e660d2
+            g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                        "dup2 of stdin failed: %s",
e660d2
+                        g_strerror(errno));
e660d2
+            return;
e660d2
+        }
e660d2
+        close(transcoder_stdin[0]);
e660d2
+        close(transcoder_stdin[1]);
e660d2
+        fd = fileno(target->f);
e660d2
+        if (fd == -1) {
e660d2
+            g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                        "fileno for target failed");
e660d2
+            return;
e660d2
+        }
e660d2
+        if (dup2(fd, STDOUT_FILENO) == -1) {
e660d2
+            g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                        "dup2 of stdout failed: %s",
e660d2
+                        g_strerror(errno));
e660d2
+            return;
e660d2
+        }
e660d2
+        if (execv(args[0], args) == -1) {
e660d2
+            g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                        "execv failed: %s", g_strerror(errno));
e660d2
+        }
e660d2
+        /* we never get here, but appease static analysis */
e660d2
+        return;
e660d2
+    } else {
e660d2
+        /* parent */
e660d2
+        close(transcoder_stdin[0]);
e660d2
+        out = fdopen(transcoder_stdin[1], "w");
e660d2
+        if (out == NULL) {
e660d2
+            g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                        "fdopen failed: %s",
e660d2
+                        g_strerror(errno));
e660d2
+            return;
e660d2
+        }
e660d2
+        target->pid = pid;
e660d2
+        target->writef = out;
e660d2
+        /* resuming a transcode is not yet implemented */
e660d2
+        target->resume = FALSE;
e660d2
+    }
e660d2
+}
e660d2
+
e660d2
+void
e660d2
+cleanup_transcode(LrTarget *target, GError **err)
e660d2
+{
e660d2
+    int wstatus, trc;
e660d2
+    if (!target->writef) {
e660d2
+        return;
e660d2
+    }
e660d2
+    if (target->writef == target->f) {
e660d2
+        return;
e660d2
+    }
e660d2
+    fclose(target->writef);
e660d2
+    if(waitpid(target->pid, &wstatus, 0) == -1) {
e660d2
+        g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                    "transcode waitpid failed: %s", g_strerror(errno));
e660d2
+    } else if (WIFEXITED(wstatus)) {
e660d2
+        trc = WEXITSTATUS(wstatus);
e660d2
+        if (trc != 0) {
e660d2
+            g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                        "transcode process non-zero exit code %d", trc);
e660d2
+        }
e660d2
+    } else if (WIFSIGNALED(wstatus)) {
e660d2
+        g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                    "transcode process was terminated with a signal: %d",
e660d2
+                    WTERMSIG(wstatus));
e660d2
+    } else {
e660d2
+        /* don't think this can happen, but covering all bases */
e660d2
+        g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+                    "transcode unhandled circumstance in waitpid");
e660d2
+    }
e660d2
+    target->writef = NULL;
e660d2
+    /* pid is only valid if writef is not NULL */
e660d2
+    /* target->pid = -1; */
e660d2
+}
e660d2
+
e660d2
 /** Prepare next transfer
e660d2
  */
e660d2
 static gboolean
e660d2
@@ -1509,6 +1644,9 @@ prepare_next_transfer(LrDownload *dd, gboolean *candidatefound, GError **err)
e660d2
     target->f = open_target_file(target, err);
e660d2
     if (!target->f)
e660d2
         goto fail;
e660d2
+    maybe_transcode(target, err);
e660d2
+    if (!target->writef)
e660d2
+        goto fail;
e660d2
     target->writecb_recieved = 0;
e660d2
     target->writecb_required_range_written = FALSE;
e660d2
 
e660d2
@@ -1684,6 +1822,7 @@ prepare_next_transfer(LrDownload *dd, gboolean *candidatefound, GError **err)
e660d2
         curl_easy_cleanup(target->curl_handle);
e660d2
         target->curl_handle = NULL;
e660d2
     }
e660d2
+    cleanup_transcode(target, err);
e660d2
     if (target->f != NULL) {
e660d2
         fclose(target->f);
e660d2
         target->f = NULL;
e660d2
@@ -2254,6 +2393,8 @@ check_transfer_statuses(LrDownload *dd, GError **err)
e660d2
         if (transfer_err)  // Transfer was unsuccessful
e660d2
             goto transfer_error;
e660d2
 
e660d2
+        cleanup_transcode(target, err);
e660d2
+
e660d2
         //
e660d2
         // Checksum checking
e660d2
         //
e660d2
@@ -2348,6 +2489,7 @@ check_transfer_statuses(LrDownload *dd, GError **err)
e660d2
         target->curl_handle = NULL;
e660d2
         g_free(target->headercb_interrupt_reason);
e660d2
         target->headercb_interrupt_reason = NULL;
e660d2
+        cleanup_transcode(target, err);
e660d2
         fclose(target->f);
e660d2
         target->f = NULL;
e660d2
         if (target->curl_rqheaders) {
e660d2
@@ -2751,6 +2893,7 @@ lr_download(GSList *targets,
e660d2
             curl_multi_remove_handle(dd.multi_handle, target->curl_handle);
e660d2
             curl_easy_cleanup(target->curl_handle);
e660d2
             target->curl_handle = NULL;
e660d2
+            cleanup_transcode(target, err);
e660d2
             fclose(target->f);
e660d2
             target->f = NULL;
e660d2
             g_free(target->headercb_interrupt_reason);
e660d2
diff --git a/librepo/rcodes.h b/librepo/rcodes.h
e660d2
index dcbeb413..fd65bd60 100644
e660d2
--- a/librepo/rcodes.h
e660d2
+++ b/librepo/rcodes.h
e660d2
@@ -125,6 +125,8 @@ typedef enum {
e660d2
         key/group not found, ...) */
e660d2
     LRE_ZCK, /*!<
e660d2
         (41) Zchunk error (error reading zchunk file, ...) */
e660d2
+    LRE_TRANSCODE, /*!<
e660d2
+        (42) Transcode error (env empty, ...) */
e660d2
     LRE_UNKNOWNERROR, /*!<
e660d2
         (xx) unknown error - sentinel of error codes enum */
e660d2
 } LrRc; /*!< Return codes */
e660d2
e660d2
From cef644335d1c5068cd70cb93d2688b97663b3a8b Mon Sep 17 00:00:00 2001
e660d2
From: Matthew Almond <malmond@fb.com>
e660d2
Date: Fri, 29 Jan 2021 23:14:28 -0800
e660d2
Subject: [PATCH 2/2] Fix hard download error on 404 etc
e660d2
e660d2
I've observed errors when some mirrors are not completely synced. The
e660d2
library tries to download a file, but gets a 404 error. This means we
e660d2
need to retry with another mirror, not crash out. This was crashing
e660d2
because setting `err` in `clean_transcode()` was firing the assert at
e660d2
the start of `truncate_transfer_file()`. Note this failure mode was most
e660d2
common with 404's, but any transfer error could likely have turned
e660d2
fatal, for invalid reasons.
e660d2
e660d2
We use `cleanup_transcode()` in two contexts.
e660d2
e660d2
1. Within `check_transfer_statuses()`. The first call here happens
e660d2
   during a normal download after `check_finished_transfer_status()`.
e660d2
   The cleanup checks for errors, and any here will be flagged as a
e660d2
   `transfer_err` (not a general, err).
e660d2
2. In 3 other places where an error has already occurred. We need to
e660d2
   wait for the program to exit (and it should stop due to a SIGPIPE
e660d2
   or short read from stdin), but we don't need to set an error because
e660d2
   something already is handling one. It doesn't matter that the
e660d2
   transcoder crashed because we're not going to use the output anyway,
e660d2
   and we are likely to retry.
e660d2
---
e660d2
 librepo/downloader.c | 24 ++++++++++++++----------
e660d2
 1 file changed, 14 insertions(+), 10 deletions(-)
e660d2
e660d2
diff --git a/librepo/downloader.c b/librepo/downloader.c
e660d2
index 5326eb7a..39567a19 100644
e660d2
--- a/librepo/downloader.c
e660d2
+++ b/librepo/downloader.c
e660d2
@@ -1530,8 +1530,12 @@ maybe_transcode(LrTarget *target, GError **err)
e660d2
 }
e660d2
 
e660d2
 void
e660d2
-cleanup_transcode(LrTarget *target, GError **err)
e660d2
+cleanup_transcode(LrTarget *target, GError **transfer_err)
e660d2
 {
e660d2
+    /** transfer_err can be NULL if we're using this to clean up a failed
e660d2
+     * transfer. In that circumstance g_set_error does nothing which is fine,
e660d2
+     * we don't need to pile on a second failure reason.
e660d2
+     */
e660d2
     int wstatus, trc;
e660d2
     if (!target->writef) {
e660d2
         return;
e660d2
@@ -1541,21 +1545,21 @@ cleanup_transcode(LrTarget *target, GError **err)
e660d2
     }
e660d2
     fclose(target->writef);
e660d2
     if(waitpid(target->pid, &wstatus, 0) == -1) {
e660d2
-        g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+        g_set_error(transfer_err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
                     "transcode waitpid failed: %s", g_strerror(errno));
e660d2
     } else if (WIFEXITED(wstatus)) {
e660d2
         trc = WEXITSTATUS(wstatus);
e660d2
         if (trc != 0) {
e660d2
-            g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+            g_set_error(transfer_err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
                         "transcode process non-zero exit code %d", trc);
e660d2
         }
e660d2
     } else if (WIFSIGNALED(wstatus)) {
e660d2
-        g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+        g_set_error(transfer_err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
                     "transcode process was terminated with a signal: %d",
e660d2
                     WTERMSIG(wstatus));
e660d2
     } else {
e660d2
         /* don't think this can happen, but covering all bases */
e660d2
-        g_set_error(err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
+        g_set_error(transfer_err, LR_DOWNLOADER_ERROR, LRE_TRANSCODE,
e660d2
                     "transcode unhandled circumstance in waitpid");
e660d2
     }
e660d2
     target->writef = NULL;
e660d2
@@ -1822,7 +1826,7 @@ prepare_next_transfer(LrDownload *dd, gboolean *candidatefound, GError **err)
e660d2
         curl_easy_cleanup(target->curl_handle);
e660d2
         target->curl_handle = NULL;
e660d2
     }
e660d2
-    cleanup_transcode(target, err);
e660d2
+    cleanup_transcode(target, NULL);
e660d2
     if (target->f != NULL) {
e660d2
         fclose(target->f);
e660d2
         target->f = NULL;
e660d2
@@ -2390,11 +2394,11 @@ check_transfer_statuses(LrDownload *dd, GError **err)
e660d2
         if (!ret)  // Error
e660d2
             return FALSE;
e660d2
 
e660d2
+        cleanup_transcode(target, &transfer_err);
e660d2
+
e660d2
         if (transfer_err)  // Transfer was unsuccessful
e660d2
             goto transfer_error;
e660d2
 
e660d2
-        cleanup_transcode(target, err);
e660d2
-
e660d2
         //
e660d2
         // Checksum checking
e660d2
         //
e660d2
@@ -2489,7 +2493,7 @@ check_transfer_statuses(LrDownload *dd, GError **err)
e660d2
         target->curl_handle = NULL;
e660d2
         g_free(target->headercb_interrupt_reason);
e660d2
         target->headercb_interrupt_reason = NULL;
e660d2
-        cleanup_transcode(target, err);
e660d2
+        cleanup_transcode(target, NULL);
e660d2
         fclose(target->f);
e660d2
         target->f = NULL;
e660d2
         if (target->curl_rqheaders) {
e660d2
@@ -2893,7 +2897,7 @@ lr_download(GSList *targets,
e660d2
             curl_multi_remove_handle(dd.multi_handle, target->curl_handle);
e660d2
             curl_easy_cleanup(target->curl_handle);
e660d2
             target->curl_handle = NULL;
e660d2
-            cleanup_transcode(target, err);
e660d2
+            cleanup_transcode(target, NULL);
e660d2
             fclose(target->f);
e660d2
             target->f = NULL;
e660d2
             g_free(target->headercb_interrupt_reason);