From e189142fb111f9df2a11f548ac2ab863740c7139 Mon Sep 17 00:00:00 2001 From: Urban Wallasch Date: Sat, 17 Apr 2021 20:29:16 +0200 Subject: [PATCH] * Improved and fixed kaouplod.c: signal handling; adaptive upload buffer size; -c config_file command line option; better reply parsing; refactoring --- kaoupload.c | 185 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 119 insertions(+), 66 deletions(-) diff --git a/kaoupload.c b/kaoupload.c index 91dbc22..472dbeb 100644 --- a/kaoupload.c +++ b/kaoupload.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -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 ); -- 2.30.2