* Improved and fixed kaouplod.c: signal handling; adaptive upload buffer size; -c...
authorUrban Wallasch <urban.wallasch@freenet.de>
Sat, 17 Apr 2021 18:29:16 +0000 (20:29 +0200)
committerUrban Wallasch <urban.wallasch@freenet.de>
Sat, 17 Apr 2021 18:29:16 +0000 (20:29 +0200)
kaoupload.c

index 91dbc222dfc32fa7f751365f51ee6ad894112e09..472dbebfeed26b690b2337de79c35f38b770ce61 100644 (file)
@@ -1,6 +1,7 @@
 #include <ctype.h>
 #include <errno.h>
 #include <stdio.h>
+#include <signal.h>
 #include <stdlib.h>
 #include <stdint.h>
 #include <string.h>
@@ -17,6 +18,9 @@
 #define URL_BASE "https://kaotan.latice.de/uploadx.php"
 #define CFG_BASE ".config/kaoconf"
 
+#define CSIZE_HARDLIMIT 5200000L
+
+
 static struct kaoconf {
     char *user;
     char *pass;
@@ -34,6 +38,19 @@ struct file_info_t {
     curl_off_t speed;
 };
 
+struct membuf_t {
+    char *memory;
+    size_t size;
+};
+
+
+static sig_atomic_t gotsig = 0;
+
+static void sig_handler( int signum ){
+  gotsig = 1;
+  fprintf( stderr, "\nReceived signal %d, wrapping up gracefully.\n", signum );
+}
+
 static void cerr( const char *msg, int eno ) {
     fprintf( stderr, "ERROR: %s", msg );
     if ( eno )
@@ -57,7 +74,9 @@ static curl_off_t str_to_bwl( const char *s ) {
     curl_off_t byps = 0;
     char *end;
 
-    byps = strtoul( s, &end, 10 );
+    byps = strtol( s, &end, 10 );
+    if ( 0 > byps )
+        byps = 0;
     switch ( *end ) {
     case 'k':
     case 'K':
@@ -77,15 +96,11 @@ static curl_off_t str_to_bwl( const char *s ) {
     return byps;
 }
 
-static void ie( const char *home ) {
+static void parse_cfg( const char *confpath ) {
     FILE *fp;
-    char confpath[256];
     char buf[256];
 
-    if ( NULL == home )
-        return;
-    snprintf( confpath, sizeof confpath, "%s/%s", home, CFG_BASE );
-    if ( NULL == ( fp = fopen( confpath, "r" ) ) )
+    if ( NULL == confpath || NULL == ( fp = fopen( confpath, "r" ) ) )
         return;
     while ( fgets( buf, sizeof buf - 1, fp ) ) {
         char *key, *val, *x;
@@ -108,7 +123,6 @@ static void ie( const char *home ) {
             *x-- = '\0';
         if ( '"' == *x || '\'' == *x )
             *x = '\0';
-        // don't overwrite environment, so "PASS=1234 kaoupload ..." is possible
         if ( 0 == strcmp( key, "USER" ) ) {
             free( cfg.user );
             cfg.user = strdup( val );
@@ -127,17 +141,9 @@ static void ie( const char *home ) {
     fclose( fp );
 }
 
-/* WriteMemoryCallback and accompanying code were initially  borrowed from
- * https://curl.haxx.se/libcurl/c/getinmemory.html
- */
-struct MemoryStruct {
-    char *memory;
-    size_t size;
-};
-
-static size_t WriteMemoryCallback( void *contents, size_t size, size_t nmemb, void *userp ) {
+static size_t write_memory_cb( void *contents, size_t size, size_t nmemb, void *userp ) {
     size_t realsize = size * nmemb;
-    struct MemoryStruct *mem = (struct MemoryStruct *)userp;
+    struct membuf_t *mem = userp;
     void *newmem;
 
     if ( NULL == ( newmem = realloc( mem->memory, mem->size + realsize + 1 ) ) )
@@ -149,15 +155,20 @@ static size_t WriteMemoryCallback( void *contents, size_t size, size_t nmemb, vo
     return realsize;
 }
 
-static int loadNext( CURL *curl, struct file_info_t *fi, off_t csize ) {
+
+#define RESP_HAVE       "Have "
+#define RESP_COMPLETE   "Upload complete, "
+#define RESP_SKIP       "Skip-to-position: "
+
+static int upload_chunk( CURL *curl, struct file_info_t *fi, off_t csize ) {
     int res = -1;
     int response_code;
     ssize_t br;
     char url[2048];
     void *slice;
-    char *s;
+    off_t completed = 0;
     CURLcode error;
-    struct MemoryStruct reply;
+    struct membuf_t reply;
 
     reply.memory = NULL;  /* will be grown as needed */
     reply.size = 0;       /* no data at this point */
@@ -176,20 +187,20 @@ static int loadNext( CURL *curl, struct file_info_t *fi, off_t csize ) {
     snprintf( url, sizeof url, "%s?fname=%s&fpos=%lu&fsize=%lu", cfg.url, fi->esc_name, fi->coff, fi->size );
 
     curl_easy_reset( curl );
-    error = curl_easy_setopt( curl, CURLOPT_POST, 1 );
-    error |= curl_easy_setopt( curl, CURLOPT_POSTFIELDSIZE, csize );
+    error = curl_easy_setopt( curl, CURLOPT_POST, 1L );
+    error |= curl_easy_setopt( curl, CURLOPT_POSTFIELDSIZE, (long)csize );
     error |= curl_easy_setopt( curl, CURLOPT_POSTFIELDS, slice );
     error |= curl_easy_setopt( curl, CURLOPT_URL, url );
     error |= curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC );
     error |= curl_easy_setopt( curl, CURLOPT_USERNAME, cfg.user );
     error |= curl_easy_setopt( curl, CURLOPT_PASSWORD, cfg.pass );
-    error |= curl_easy_setopt( curl, CURLOPT_MAX_SEND_SPEED_LARGE, cfg.bwlup );
-    error |= curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback );
+    error |= curl_easy_setopt( curl, CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t)cfg.bwlup );
+    error |= curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, write_memory_cb );
     error |= curl_easy_setopt( curl, CURLOPT_WRITEDATA, (void *)&reply );
     error |= curl_easy_setopt( curl, CURLOPT_TIMEOUT, 300L );
     error |= curl_easy_setopt( curl, CURLOPT_CONNECTTIMEOUT, 60L );
-    error |= curl_easy_setopt( curl, CURLOPT_SSL_VERIFYPEER, 0 );
-    error |= curl_easy_setopt( curl, CURLOPT_SSL_VERIFYHOST, 0 );
+    error |= curl_easy_setopt( curl, CURLOPT_SSL_VERIFYPEER, 0L );
+    error |= curl_easy_setopt( curl, CURLOPT_SSL_VERIFYHOST, 0L );
     if ( CURLE_OK != error )
         die( "curl_easy_setopt failed", 0 );
 
@@ -202,26 +213,34 @@ static int loadNext( CURL *curl, struct file_info_t *fi, off_t csize ) {
     curl_easy_getinfo( curl, CURLINFO_SPEED_UPLOAD_T, &fi->speed );
     curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response_code );
 
-    puts(reply.memory);
+    // puts(reply.memory);
 
     switch ( response_code ) {
     case 100:
     case 200:
     case 201:
         // simplest case, uploaded one chunk
-        if ( 0 == strncmp( reply.memory, "Have ", strlen("Have ") )
-          || 0 == strncmp( reply.memory, "Upload complete", strlen("Upload complete") ) ) {
+        if ( 0 == strncmp( reply.memory, RESP_HAVE, strlen( RESP_HAVE ) ) ) {
             fi->coff += csize;
             fi->remn -= csize;
-            res = 0;
+            completed = strtoul( reply.memory + strlen( RESP_HAVE ), NULL, 10 );
+            if ( completed == fi->coff )
+                res = 0;
+        }
+        else if ( 0 == strncmp( reply.memory, RESP_COMPLETE, strlen( RESP_COMPLETE ) ) ) {
+            fi->coff += csize;
+            fi->remn -= csize;
+            completed = strtoul( reply.memory + strlen( RESP_COMPLETE ), NULL, 10 );
+            if ( completed == fi->coff && 0 == fi->remn )
+                res = 0;
         }
         else
             err( "unexpected response, check credentials?", 0 );
         break;
     case 428:
         // remote already has data up to Skip-to-position
-        if ( NULL != ( s = strstr( reply.memory, "Skip-to-position: " ) ) ) {
-            fi->coff = strtoul( s + strlen( "Skip-to-position: " ), NULL, 10 );
+        if ( 0 == strncmp( reply.memory, RESP_SKIP, strlen( RESP_SKIP ) ) ) {
+            fi->coff = strtoul( reply.memory + strlen( RESP_SKIP ), NULL, 10 );
             fi->remn = fi->size - fi->coff;
             res = 0;
         }
@@ -229,7 +248,7 @@ static int loadNext( CURL *curl, struct file_info_t *fi, off_t csize ) {
             err( "response code 428 w/o 'Skip-to-position'", 0 );
         break;
     default:
-        fprintf( stderr, "WARNING: unexpected response code: %d\n", response_code );
+        fprintf( stderr, "ERROR: unexpected response code: %d\n", response_code );
         break;
     }
 
@@ -239,9 +258,9 @@ static int loadNext( CURL *curl, struct file_info_t *fi, off_t csize ) {
     return res;
 }
 
-static int uploadFile( const char *fn ) {
-    int res;
-    int csize = 1024;
+static int upload_file( const char *fn ) {
+    int res = 0;
+    off_t csize = 1024;     // first chunk is always short
     struct file_info_t fi;
     struct stat stbuf;
     CURL *curl;
@@ -253,65 +272,95 @@ static int uploadFile( const char *fn ) {
     if ( NULL == ( fi.esc_name = curl_easy_escape( curl, fn, 0 ) ) )
         die( "curl_easy_escape failed", 0 );
     fi.fd = open( fn, O_RDONLY );
-    if ( fi.fd < 0 )
-        err( fn, errno );
-    if ( fstat(fi.fd, &stbuf ) < 0 ) {
+    if ( 0 > fi.fd || 0 > fstat( fi.fd, &stbuf ) ) {
         err( fn, errno );
-        return -1;
+        goto err_out;
+    }
+    if ( 0 == stbuf.st_size ) {
+        fprintf( stderr, "WARNING: %s: Not uploading empty file\n", fn );
+        goto err_out;
     }
     fi.remn = fi.size = stbuf.st_size;
     fi.coff = 0;
-    printf( "File: %s\nSize: %lu bytes\n", fi.name, fi.size );
+    printf( "File: %s, %lu bytes\n", fi.name, (unsigned long)fi.size );
 
     while ( fi.remn ) {
-        res = loadNext( curl, &fi, csize );
-        if ( res < 0 )
+        if ( gotsig ) {
+            res = -1;
             break;
-        if ( 1024 == csize )
-            csize = 5 * cfg.bwlup;
-        printf( "\r%s: %d%% (%5.2f kB/s) ", fi.name, (int)(100.0 * fi.coff / fi.size), fi.speed / 1024.0 );
+        }
+        if ( 0 > ( res = upload_chunk( curl, &fi, csize ) ) )
+            break;
+        if ( 0 < fi.speed )
+            csize = 2 * fi.speed;
+        if ( CSIZE_HARDLIMIT < csize )
+            csize = CSIZE_HARDLIMIT;
+        printf( "\r%3.f%% (%5.1f kB/s) ", (double)fi.coff * 100 / fi.size, (double)fi.speed / 1024 );
+        // printf( "[csize: %ld] {offset: %ld} ", (long)csize, (long)fi.coff );
         fflush( stdout );
     }
+    if ( res < 0 ) {
+        puts( "" );
+        err( "file upload incomplete", 0 );
+    }
+    else
+        printf( "\r%-20s\n", "Upload complete." );
 
+  err_out:
     curl_free( fi.esc_name );
     curl_easy_cleanup( curl );
     close( fi.fd );
-    puts( "" );
-    if ( res < 0 )
-        err( "file upload aborted", 0 );
     return res;
 }
 
 static void usage( char *pname ) {
-    printf( "Usage:  %s [OPTIONS] FILE ...\n", basename( pname ) );
-    printf( "Options:\n"
-            "  -h         display this help\n"
-            "  -l maxbps  limit upload speed in byte per second, e.g. 100k\n"
+    printf( "%s - upload files to Kaotan\n", basename( pname ) );
+    printf( "USAGE:  %s [OPTIONS] FILE ...\n", basename( pname ) );
+    printf( "OPTIONS:\n"
+            "  -h         display this help text and exit\n"
+            "  -c file    specify configuration file (default: ~/%s)\n"
+            "  -l maxbps  upload speed limit in bytes per second, e.g. 100k\n"
             "  -u user    set user name\n"
             "  -p passwd  set password\n"
             "  -U URL     set base URL\n"
+            , CFG_BASE
     );
-    printf( "Defaults for user, password and URL are taken from environment and\n"
-            "configuration file, in that order\n" );
+    printf( "Defaults for bandwidth limit, user, password and URL are taken "
+            "from environment and configuration file, in this order.\n" );
     exit( EXIT_FAILURE );
 }
 
 int main( int argc, char *argv[] ) {
     int res = 0;
     int c;
-
-    if ( argc < 2 )
-        usage( argv[0] );
+    char confpath[256];
 
     cfg.user = strdup( getenv( "USER" ) ? getenv( "USER" ) : "" );
     cfg.pass = strdup( getenv( "PASS" ) ? getenv( "PASS" ) : "" );
     cfg.url = strdup( getenv( "URL" ) ? getenv( "URL" ) : URL_BASE );
-    ie( getenv( "HOME" ) );
-
-    opterr = 0;
+    cfg.bwlup = str_to_bwl( getenv( "BWLUP" ) ? getenv( "BWLUP" ) : "0" );
+    snprintf( confpath, sizeof confpath, "%s/%s", getenv( "HOME" ), CFG_BASE );
+    while ( ( c = getopt( argc, argv, ":c:" ) ) != -1 ) {
+        switch (c) {
+        case 'c':
+            snprintf( confpath, sizeof confpath, "%s", optarg );
+            break;
+        case ':':
+            fprintf( stderr, "ERROR: missing argument for option '-%c'\n", optopt );
+            usage( argv[0] );
+            break;
+        default:
+            break;
+        }
+    }
+    parse_cfg( confpath );
 
-    while ( ( c = getopt( argc, argv, ":hl:p:u:U:" ) ) != -1 ) {
+    optind = 1;
+    while ( ( c = getopt( argc, argv, ":c:hl:p:u:U:" ) ) != -1 ) {
         switch (c) {
+        case 'c':
+            // already handled above
+            break;
         case 'h':
             usage( argv[0] );
             break;
@@ -339,7 +388,7 @@ int main( int argc, char *argv[] ) {
             usage( argv[0] );
             break;
         default:
-            die( "ERROR: Huh?!", 0 );
+            fprintf( stderr, "ERROR: option '-%c' not implemented, programmer PEBKAC.\n", c );
             break;
         }
     }
@@ -352,8 +401,12 @@ int main( int argc, char *argv[] ) {
     if ( CURLE_OK != curl_global_init( CURL_GLOBAL_SSL ) )
         die( "curl_global_init", 0 );
 
-    while ( optind < argc )
-        res = uploadFile( argv[optind++] );
+    signal( SIGINT, sig_handler );
+    signal( SIGTERM, sig_handler );
+    signal( SIGHUP, sig_handler );
+
+    while ( optind < argc && !gotsig && 0 == res )
+        res = upload_file( argv[optind++] );
 
     curl_global_cleanup();
     free( cfg.user );