#include <ctype.h>
#include <errno.h>
#include <stdio.h>
+#include <signal.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#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;
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 )
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':
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;
*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 );
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 ) ) )
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 */
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 );
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;
}
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;
}
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;
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;
usage( argv[0] );
break;
default:
- die( "ERROR: Huh?!", 0 );
+ fprintf( stderr, "ERROR: option '-%c' not implemented, programmer PEBKAC.\n", c );
break;
}
}
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 );