Richard Chaney
Richard Chaney

Reputation: 11

Aborting an SFTP upload leaves corrupted data at the end of the partially uploaded file, making resume upload impossible

My application uses libcurl to upload files to an SFTP server, and sometimes I need to close the application before the current upload is complete. I would like to abort the upload, and resume it later. However, when the upload is aborted there is corrupted data at the end of the partially uploaded file on the server. The amount various, up to about 10 bytes. This makes resuming the upload by appending data impossible.

See some sample code below. Press Ctrl-C to abort the upload, then manually download the partial file and compare it with the original file. The partial file will usually contain incorrect data at the end.

#include <stdio.h>
#include <signal.h>
#include "curl.h"

// Handle Ctrl-C to abort.
static bool s_bAborted = false;
static void __cdecl ctrlCHandler(int)
{
    printf("Aborting...\n");
    s_bAborted = true;
}

// File reading function.
static size_t readFunction(char * ptr, size_t size, size_t nmemb, void * stream)
{
    FILE * pFile = (FILE *)stream;
    if (ferror(pFile))
    {
        return CURL_READFUNC_ABORT;
    }
    return fread(ptr, size, nmemb, pFile);
}

// Progress function so transfers can be aborted.
static int progressFunction(void * clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
    if (s_bAborted)
    {
        return 1;
    }
    return CURL_PROGRESSFUNC_CONTINUE;
}

int main()
{
    // Add your local file and server URL.
    // Note: This problem was observed using an SFTP server.
    const char * szLocalFile = "Test.bin";
    const char * szRemoteUrl = "sftp://user:password@host/Test.bin";

    curl_global_init(CURL_GLOBAL_ALL);
    CURL * curl = curl_easy_init();
    FILE * pFile = fopen(szLocalFile, "rb");

    curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
    curl_easy_setopt(curl, CURLOPT_URL, szRemoteUrl);
    curl_easy_setopt(curl, CURLOPT_READFUNCTION, readFunction);
    curl_easy_setopt(curl, CURLOPT_READDATA, pFile);
    curl_easy_setopt(curl, CURLOPT_APPEND, 0L);
    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
    curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progressFunction);

    printf("Uploading... Press Ctrl-C to abort\n");
    signal(SIGINT, ctrlCHandler);

    CURLcode eResult = curl_easy_perform(curl);
    fclose(pFile);

    if (eResult == CURLE_OK)
    {
        printf("Uploaded OK\n");
    }
    else if (eResult == CURLE_ABORTED_BY_CALLBACK)
    {
        printf("Upload aborted\n");
    }
    else
    {
        printf("Error: Upload failed: %d\n", eResult);
    }

    curl_easy_cleanup(curl);
    curl_global_cleanup();

    // After aborting the upload, use something else to download the partial file.
    // The last few bytes of the partial file are usually incorrect,
    // i.e. different to the original file in that position.
    // This makes resuming the upload impossible, since it will append after incorrect bytes.
}

Please can anyone help? Thank you.

Upvotes: 0

Views: 394

Answers (1)

Richard Chaney
Richard Chaney

Reputation: 11

Following the advice of rustyx above, I modified it to abort using the read function, and removed the progress callback. This appears to fix the problem (after testing about 15 times).

Note: This method means the partial file is always a multiple of 64 kB. When aborting from the progress callback it was any random size. Perhaps this has something to do with it.

See updated code below:

#include <stdio.h>
#include <signal.h>
#include "curl.h"

// Handle Ctrl-C to abort.
static bool s_bAborted = false;
static void __cdecl ctrlCHandler(int)
{
    s_bAborted = true;
}

// File reading function.
static size_t readFunction(char * ptr, size_t size, size_t nmemb, void * stream)
{
    FILE * pFile = (FILE *)stream;
    if (s_bAborted || ferror(pFile))
    {
        return CURL_READFUNC_ABORT;
    }
    return fread(ptr, size, nmemb, pFile);
}

int main()
{
    // Add your local file and server URL.
    // Note: This problem was observed using an SFTP server.
    const char * szLocalFile = "Test.bin";
    const char * szRemoteUrl = "sftp://user:password@host/Test.bin";

    curl_global_init(CURL_GLOBAL_ALL);
    CURL * curl = curl_easy_init();
    FILE * pFile = fopen(szLocalFile, "rb");

    curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
    curl_easy_setopt(curl, CURLOPT_URL, szRemoteUrl);
    curl_easy_setopt(curl, CURLOPT_READFUNCTION, readFunction);
    curl_easy_setopt(curl, CURLOPT_READDATA, pFile);
    curl_easy_setopt(curl, CURLOPT_APPEND, 0L);

    printf("Uploading... Press Ctrl-C to abort\n");
    signal(SIGINT, ctrlCHandler);

    CURLcode eResult = curl_easy_perform(curl);
    fclose(pFile);

    if (eResult == CURLE_OK)
    {
        printf("Uploaded OK\n");
    }
    else if (eResult == CURLE_ABORTED_BY_CALLBACK)
    {
        printf("Upload aborted\n");
    }
    else
    {
        printf("Error: Upload failed: %d\n", eResult);
    }

    curl_easy_cleanup(curl);
    curl_global_cleanup();
}

Upvotes: 1

Related Questions