diff options
Diffstat (limited to 'src/gateleen_resclone/gateleen_resclone.c')
-rw-r--r-- | src/gateleen_resclone/gateleen_resclone.c | 926 |
1 files changed, 926 insertions, 0 deletions
diff --git a/src/gateleen_resclone/gateleen_resclone.c b/src/gateleen_resclone/gateleen_resclone.c new file mode 100644 index 0000000..6f03b64 --- /dev/null +++ b/src/gateleen_resclone/gateleen_resclone.c @@ -0,0 +1,926 @@ + +/* This Unit */ +#include "gateleen_resclone.h" + +/* System */ +#include <assert.h> +#include <errno.h> +#include <libgen.h> +#include <regex.h> +#include <stdbool.h> +#include <string.h> + +/* Libs */ +#include "archive.h" +#include "archive_entry.h" +#include <curl/curl.h> +#include <yajl/yajl_parse.h> + +/* Project */ +#include "array.h" +#include "log.h" +#include "mime.h" +#include "util_string.h" + + +/* Types *********************************************************************/ + +#define ERR_PARSE_DIR_LIST -2 + + + +/** Operation mode. */ +typedef enum opMode { + MODE_NULL =0, + MODE_FETCH=1, + MODE_PUSH =2 +} opMode_t; + + +/** Main module handle representing an instance. */ +typedef struct this { + enum opMode mode; + /** Base URL where to upload to / download from. */ + char *url; + /** Array of regex patterns to use per segment. */ + regex_t *filter; + size_t filter_len; + /** Says if only start or whole path needs to match the pattern. */ + bool isFilterFull; + /* Path to archive file to use. Using stdin/stdout if NULL. */ + char *file; +} this_t; + + +/** Closure for a download instructed by external caller. */ +typedef struct cls_dload { + struct this *this; + char *rootUrl; + struct archive *dstArchive; + struct archive_entry *tmpEntry; + char *archiveFile; + CURL *curl; +} cls_dload_t; + + +/** Closure for a collection (directory) download. */ +typedef struct cls_resourceDir { + struct cls_dload *dload; + yajl_handle yajl; + struct cls_resourceDir *parentDir; + short rspCode; + char *name; + char **childNames; + size_t childNames_count; + size_t childNames_size; +} cls_resourceDir_t; + + +/** Closure for a file download. */ +typedef struct cls_resourceFile { + struct cls_dload *dload; + size_t srcChunkIdx; + char *url; + char *buf; + int buf_len; + int buf_memSz; +} cls_resourceFile_t; + + +/** Closure for an upload instructed by external caller. */ +typedef struct cls_upload { + struct this *this; + char *rootUrl; + char *archiveFile; + struct archive *srcArchive; + CURL *curl; +} cls_upload_t; + + +/** Closure for a PUT of a single resource. */ +typedef struct cls_put { + struct cls_upload *upload; + /* Path (relative to rootUrl) of the resource to be uploaded. */ + char *name; +} cls_put_t; + + + +/* Operations ****************************************************************/ + + +TPL_ARRAY( str, char*, 16 ); + + +static void +printHelp( void ) +{ + char filename[sizeof(__FILE__)]; + strcpy( filename, __FILE__ ); + printf("%s%s%s", + "\n" + " ", basename(filename), " - " STR_QUOT(PROJECT_VERSION) "\n" + "\n" + "Options:\n" + "\n" + " --pull|--push\n" + " Choose to download or upload.\n" + "\n" + " --url <url>\n" + " Root node of remote tree.\n" + "\n" + " --filter-part <path-filter>\n" + " Regex pattern applied as predicate to the path starting after the path\n" + " specified in '--url'. Each path segment will be handled as its\n" + " individual pattern. If there are longer paths to process, they will be\n" + " accepted, as long they at least start-with specified filter.\n" + " Example: /foo/[0-9]+/bar\n" + "\n" + " --filter-full <path-filter>\n" + " Nearly same as '--filter-part'. But paths with more segments than the\n" + " pattern, will be rejected.\n" + "\n" + " --file <path.tar>\n" + " (optional) Path to the archive file to read/write. Defaults to\n" + " stdin/stdout if ommitted.\n" + "\n" + ); +} + + +static int +parseArgs( int argc, char**argv, opMode_t*mode, char**url, regex_t**filter, size_t*filter_cnt, + bool*isFilterFull, char**file ) +{ + ssize_t err, ret=0; + char *filterRaw = NULL; + if( argc == -1 ){ // -1 indicates the call to free our resources. So simply jump + goto fail; // to 'fail' because that has the same effect. + } + *mode = 0; + *url = NULL; + *filter = NULL; + *filter_cnt = 0; + *isFilterFull = false; + *file = NULL; + + for( int i=1 ; i<argc ; ++i ){ + char *arg = argv[i]; + if( !strcmp(arg,"--help") ){ + printHelp(); + ret = -1; goto fail; + }else if( !strcmp(arg,"--pull") ){ + if( *mode ){ + printf("%s\n","ERROR: Mode already specified. Won't set '--pull'."); + ret = -1; goto fail; + } + *mode = MODE_FETCH; + }else if( !strcmp(arg,"--push") ){ + if( *mode ){ + printf("%s\n","ERROR: Mode already specified. Won't set '--push'."); + ret = -1; goto fail; + } + *mode = MODE_PUSH; + }else if( !strcmp(arg,"--url") ){ + if(!( arg=argv[++i]) ){ + printf("%s\n","ERROR: Arg '--url' needs a value."); + ret = -1; goto fail; + } + *url = arg; + }else if( !strcmp(arg,"--filter-full") ){ + if(!( arg=argv[++i] )){ + printf("%s\n","ERROR: Arg '--filter-full' needs a value."); + ret = -1; goto fail; } + if( filterRaw ){ + printf("%s\n","ERROR: Cannot use '--filter-full' because a filter is already set."); + ret=-1; goto fail; } + filterRaw = arg; + *isFilterFull = true; + }else if( !strcmp(arg,"--filter-part") ){ + if(!( arg=argv[++i] )){ + printf("%s\n","ERROR: Arg '--filter-part' needs a value."); + ret = -1; goto fail; } + if( filterRaw ){ + printf("%s\n","ERROR: Cannot use '--filter-part' because a filter is already set."); + ret=-1; goto fail; } + filterRaw = arg; + *isFilterFull = false; + }else if( !strcmp(arg,"--file") ){ + if(!( arg=argv[++i]) ){ + printf("%s\n","ERROR: Arg '--file' needs a value."); + ret = -1; goto fail; + } + *file = arg; + }else{ + printf("%s%s\n", "ERROR: Unknown arg ",arg); + ret = -1; goto fail; + } + } + + if( *mode==0 ){ + printf("%s\n", "ERROR: One of --push or --pull required."); + ret = -1; goto fail; + } + + if( *url==NULL ){ + printf("%s\n", "ERROR: Arg --url missing."); + ret = -1; goto fail; + } + uint_t urlFromArgs_len = strlen( *url ); + if( ((*url)[urlFromArgs_len-1]) != '/' ){ + char *urlFromArgs = *url; + uint_t url_len = urlFromArgs_len + 1; + *url = malloc(url_len+1); /* TODO: Should we free this? */ + memcpy( *url, urlFromArgs, urlFromArgs_len ); + (*url)[url_len-1] = '/'; + (*url)[url_len] = '\0'; + }else{ + *url = strdup( *url ); // Make our own string (so we've no fear to call + // free on it later) + } + + if( filterRaw ){ + uint_t buf_len = strlen( filterRaw ); + char *buf = malloc( 1+ buf_len +2 ); + buf[0] = '_'; // <- Match whole segment. + memcpy( buf+1, filterRaw, buf_len+1 ); + char *beg, *end; + size_t filter_cap = 0; + end = buf+1; // <- Initialize at begin for 1st iteration. + for( uint_t iSegm=0 ;; ++iSegm ){ + for( beg=end ; *beg=='/' ; ++beg ); // <- Search for begin and + for( end=beg ; *end!='/' && *end!='\0' ; ++end ); // <- end of current segment. + char origBeg = beg[-1]; + char origSep = *end; + char origNext = end[1]; + beg[-1] = '^'; // <- Add 'match-start' so we MUST match whole segment. + *end = '$'; // <- Add 'match-end' so we must match whole segment. + end[1] = '\0'; // <- Temporary terminate to compile segment only. + if( iSegm >= filter_cap ){ + filter_cap += 8; + *filter = realloc( *filter, filter_cap*sizeof(**filter) ); + //LOG_DEBUG("%s%u%s%p\n", "realloc( NULL, ", filter_cap*sizeof(**filter)," ) -> ", *filter ); + if( !*filter ){ LOG_ERROR("%s%d%s\n","realloc(",filter_cap*sizeof(**filter),")"); ret=-ENOMEM; goto fail; } + } + //LOG_DEBUG("%s%d%s%s%s\n", "filter[", iSegm, "] -> '", beg-1, "'"); + err = regcomp( (*filter)+iSegm, beg-1, REG_EXTENDED); + if( err ){ LOG_ERROR("%s%s%s%d\n","regcomp(",beg,") -> ", err); return -1; } + // Restore surrounding stuff. + beg[-1] = origBeg; + *end = origSep; // <- Restore tmp 'end-of-match' ($). + end[1] = origNext; // <- Restore tmp termination. + if( *end=='\0' ){ // EOF + *filter_cnt = iSegm +1; + *filter = realloc( *filter, *filter_cnt *sizeof(**filter) ); // Trim result. + free( buf ); buf=NULL; + break; + } + } + } + + if( *mode==MODE_PUSH && *filter ){ + printf("%s\n", "ERROR: Filtering not supported for push mode."); + ret = -1; goto fail; + } + + return 0; + fail: + free( *url ); *url=NULL; + for( uint_t i=0 ; i<*filter_cnt ; ++i ){ + regfree( &(filter[0][i]) ); + } + *filter_cnt = 0; + free( *filter ); *filter=NULL; + return ret; +} + + +static int +onYajlString(void*cls_resourceDir_, const unsigned char*str_, size_t str_len) +{ + ssize_t err, ret=str_len; + cls_resourceDir_t *resourceDir = cls_resourceDir_; + const char *str = (void*)str_; + + //LOG_DEBUG("%s%s%.*s%s\n", __func__, "(): Add dirent '",(int)str_len,str,"'"); + + char *newStr = malloc( str_len +1 ); + memcpy( newStr, str, str_len ); + newStr[str_len] = '\0'; + err = array_add_str( &resourceDir->childNames, &resourceDir->childNames_count, &resourceDir->childNames_size, newStr ); + //err = arrStr_add( &resourceDir->childNames , &resourceDir->childNames_count , &resourceDir->childNames_size , &newStr ); + if( err ){ assert(!err); ret=-1; goto endFn; } + + endFn: + return ret; +} + + +static size_t +onCurlDirRsp( char*buf , size_t size , size_t nmemb , void*cls_resourceDir_ ) +{ + ssize_t err, ret = size*nmemb; + cls_resourceDir_t *resourceDir = cls_resourceDir_; + cls_dload_t *dload = resourceDir->dload; + CURL *curl = dload->curl; + const size_t buf_len = size * nmemb; + unsigned char *bufUnsigned = (void*)buf; + + long rspCode; + curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &rspCode ); + resourceDir->rspCode = rspCode; + if( rspCode != 200 ){ + goto endFn; + } + + err = yajl_parse(resourceDir->yajl, bufUnsigned, buf_len); + if( err ){ + unsigned char *errMsg = yajl_get_error(resourceDir->yajl, /*verbose=*/1, bufUnsigned, buf_len); + LOG_WARN("%s%ld%s%s\n", "Failed to parse dir listing (code ",err,"): ", errMsg); + yajl_free_error( resourceDir->yajl , errMsg ); + resourceDir->rspCode = ERR_PARSE_DIR_LIST; + goto endFn; + } + + endFn: + return ret; +} + + +static size_t +onResourceChunk( char*buf , size_t size , size_t nmemb , void*cls_resourceFile_ ) +{ + int buf_len = size * nmemb; + int ret = buf_len; + cls_resourceFile_t *resourceFile = cls_resourceFile_; + + int avail = resourceFile->buf_memSz - resourceFile->buf_len; + if( avail <= buf_len ){ + resourceFile->buf_memSz += buf_len - avail +1; + resourceFile->buf = realloc( resourceFile->buf , resourceFile->buf_memSz ); + } + char *it = resourceFile->buf + resourceFile->buf_len; + memcpy( it , buf , buf_len ); it+=buf_len; + *it = '\0'; + resourceFile->buf_len = it - resourceFile->buf; + + return ret; +} + + +static ssize_t +collectResourceIntoMemory( cls_resourceFile_t*resourceFile , char*url ) +{ + ssize_t err, ret=0; + cls_dload_t *dload = resourceFile->dload; + CURL *curl = dload->curl; + + err = CURLE_OK!= curl_easy_setopt(curl, CURLOPT_URL, url) + || CURLE_OK!= curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L ) + || CURLE_OK!= curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, onResourceChunk) + || CURLE_OK!= curl_easy_setopt(curl, CURLOPT_WRITEDATA, resourceFile) + ; + if( err ){ assert(!err); ret=-1; goto finally; } + + err = curl_easy_perform( curl ); + if( err!=CURLE_OK ){ + LOG_ERROR("%s%s%s%s%ld%s%s\n", __func__, "(): '",url,"' (code ", err, "): ", curl_easy_strerror(err)); + ret=-1; goto finally; + } + + finally: + return ret; +} + + +static ssize_t +copyBufToArchive( cls_resourceFile_t*resourceFile ) +{ + ssize_t err, ret=0; + cls_dload_t *dload = resourceFile->dload; + char *fileName = resourceFile->url + strlen(resourceFile->dload->rootUrl); + + if( ! dload->dstArchive ){ + // Setup archive if not setup yet. + dload->dstArchive = archive_write_new(); + err = archive_write_set_format_pax_restricted( dload->dstArchive ) + || archive_write_open_filename( dload->dstArchive , dload->archiveFile ) + ; + if( err ){ + LOG_ERROR("%s%s%s\n", __func__, "(): Failed to setup tar output: ", archive_error_string(dload->dstArchive)); + ret = -1; goto endFn; + } + } + + if( ! dload->tmpEntry ){ + dload->tmpEntry = archive_entry_new(); + }else{ + dload->tmpEntry = archive_entry_clear( dload->tmpEntry ); + } + archive_entry_set_pathname(dload->tmpEntry, fileName); + archive_entry_set_filetype(dload->tmpEntry, AE_IFREG); + archive_entry_set_size(dload->tmpEntry, resourceFile->buf_len); + archive_entry_set_perm(dload->tmpEntry, 0644); + err = archive_write_header(dload->dstArchive, dload->tmpEntry); + if( err ){ ret=-1; goto endFn; } + + ssize_t written = archive_write_data( dload->dstArchive , resourceFile->buf , resourceFile->buf_len ); + if( written < 0 ){ + LOG_ERROR("%s%s%s\n", __func__, "Failed to archive_write_data: ", archive_error_string(dload->dstArchive)); + ret = -1; goto endFn; + }else if( written != resourceFile->buf_len ){ + LOG_ERROR("%s%s%u%s%lu\n", __func__, "(): archive_write_data failed to write all ", resourceFile->buf_len, " bytes. Instead it wrote ", written); + ret = -1; goto endFn; + } + resourceFile->buf_len = 0; + + endFn: + return ret; +} + + +/** @return 0:Reject, 1:Accept, <0:ERROR */ +static ssize_t +pathFilterAcceptsEntry( cls_dload_t*dload, cls_resourceDir_t*resourceDir, size_t childIdx ) +{ + ssize_t err, ret=1; // <- Accept per default. + char *name = resourceDir->childNames[childIdx]; + uint_t name_len = strlen( name ); + + if( dload->this->filter ){ + // Count parents to find correct regex to apply. + uint_t idx = 0; + for( cls_resourceDir_t*it=resourceDir->parentDir ; it ; it=it->parentDir ){ ++idx; } + // Check if we even have such a long filter at all. + if( idx >= dload->this->filter_len ){ + if( dload->this->isFilterFull ){ + //LOG_DEBUG("%s\n", "Path longer than --filter-full -> reject."); + ret = 0; goto finally; + }else{ + //LOG_DEBUG("%s\n", "Path longer than --filter-part -> accept."); + ret = 1; goto finally; + } + } + // We have a regex. Setup the check. + bool restoreEndSlash = false; + if( name[name_len-1]=='/' ){ + restoreEndSlash = true; + name[name_len-1] = '\0'; + } + //LOG_DEBUG("%s%u%s%s%s\n", "idx=",idx," name='", name, "'"); + regex_t *filterArr = dload->this->filter; + regex_t *r = filterArr + (idx); + err = regexec( r, name, 0, 0, 0 ); + if( ! err ){ + //LOG_DEBUG("%s\n", "Segment accepted by filter."); + ret = 1; + }else if( err==REG_NOMATCH ){ + //LOG_DEBUG("%s\n", "Segment rejected by filter."); + ret = 0; + }else{ + LOG_ERROR("%s%.*s%s%d\n", "regexec(rgx, '",(int)name_len,name,"') -> ", err ); + ret = -1; + } + if( restoreEndSlash ){ + name[name_len-1] = '/'; + } + goto finally; + } + finally: return ret; +} + + +/** Gets called for every resource to scan/download. + * HINT: Gets called recursively. */ +static ssize_t +gateleenResclone_download( cls_dload_t*dload , cls_resourceDir_t*parentResourceDir , char*entryName ) +{ + ssize_t err, ret=0; + char *url = NULL; + int url_len = 0; + int resUrl_len = 0; + cls_resourceDir_t *resourceDir = NULL; + cls_resourceFile_t *resourceFile = NULL; + + if( !entryName ){ + // Is the case when its the root call and not a recursive one. + entryName = dload->rootUrl; + } + + // Stack-Alloc resourceDir-closure. + cls_resourceDir_t _1={0}; resourceDir =&_1; + resourceDir->dload = dload; + resourceDir->parentDir = parentResourceDir; + resourceDir->name = strdup( entryName ); + + static yajl_callbacks yajlCbacks = {0}; + yajlCbacks.yajl_string = onYajlString; + resourceDir->yajl = yajl_alloc( &yajlCbacks , NULL , resourceDir ); + + // Configure client + { + url_len = 0; + for( cls_resourceDir_t*d=resourceDir ; d ; d=d->parentDir ){ + int len = strlen(d->name) - strspn(d->name, "/"); + url_len += len; + } + url = malloc( url_len +1 /*MightPreventReallocLaterForName*/+24 ); + char *u = url + url_len; + for( cls_resourceDir_t*d=resourceDir ; d ; d=d->parentDir ){ + char *name = d->name + strspn( d->name , "/" ); + int name_len = strlen(name); + memcpy( u-name_len , name , name_len ); u-=name_len; + } + url[url_len] = '\0'; + //LOG_DEBUG("%s%s%s%s\n", __func__, "(): URL '",url,"'"); + err = CURLE_OK != curl_easy_setopt(dload->curl, CURLOPT_URL, url) + || CURLE_OK != curl_easy_setopt(dload->curl, CURLOPT_FOLLOWLOCATION, 0L ) + || CURLE_OK != curl_easy_setopt(dload->curl, CURLOPT_WRITEFUNCTION, onCurlDirRsp) + || CURLE_OK != curl_easy_setopt(dload->curl, CURLOPT_WRITEDATA, resourceDir) + ; + if( err ){ assert(!err); ret=-1; goto finally; } + } + + err = curl_easy_perform( dload->curl ); + if( err!=CURLE_OK ){ + LOG_ERROR("%s%s%s%s%ld%s%s\n", __func__, "(): '",url,"' (code ", err, "): ", curl_easy_strerror(err)); + ret=-1; goto finally; + } + + if( resourceDir->rspCode == ERR_PARSE_DIR_LIST ){ + goto finally; // Already logged by sub-ctxt. Simply skip to next entry. + } + if( resourceDir->rspCode != 200 ){ + // Ugh? Just one request earlier, server said there's a directory on + // that URL. Never mind. Just skip it and at least download the other + // stuff. + LOG_WARN("%s%d%s%s%s\n", "Skip HTTP ", resourceDir->rspCode ," -> '",url,"'"); + goto finally; + } + + err = yajl_complete_parse( resourceDir->yajl ); + if( err ){ + LOG_ERROR("%s\n", "Failed to parse response json."); + assert( !err ); + ret=-1; goto finally; + } + + cls_resourceFile_t _2={0}; resourceFile =&_2; + resourceFile->dload = dload; + for( unsigned i=0 ; i<resourceDir->childNames_count ; ++i ){ + char *name = resourceDir->childNames[i]; + int name_len = strlen( name ); + + err = pathFilterAcceptsEntry( dload, resourceDir, i ); + if( err<0 ){ // ERROR + ret = err; goto finally; + }else if( err==0 ){ // REJECT + LOG_INFO("%s%s%s%s\n", "Skip '", url,resourceDir->childNames[i],"' (filtered)"); + continue; + }else{ // ACCEPT + // Go ahead. + } + + if( name[name_len-1]=='/' ){ + LOG_DEBUG("%s%s%s%s\n", "Scan '", url, name,"'"); + err = gateleenResclone_download( dload , resourceDir , name ); + if( err ){ ret=err; goto finally; } + } + else{ + int requiredLen = url_len + 1/*slash*/ + name_len; + if( resUrl_len < requiredLen ){ + resourceFile->url = realloc( resourceFile->url , requiredLen +1 ); + resUrl_len = requiredLen; + } + sprintf(resourceFile->url, "%s%s", url , name ); + LOG_INFO("%s%s%s\n", "Download '", resourceFile->url, "'"); + resourceFile->buf_len = 0; // <- Reset before use. + collectResourceIntoMemory( resourceFile , resourceFile->url ); + copyBufToArchive( resourceFile ); + } + } + + finally: + if( resourceFile ){ + free(resourceFile->buf); resourceFile->buf = NULL; + free( resourceFile->url ); resourceFile->url = NULL; + } + if( resourceDir ){ + yajl_free( resourceDir->yajl ); resourceDir->yajl = NULL; + free( resourceDir->name ); resourceDir->name = NULL; + for( size_t i=0 ; i<resourceDir->childNames_count ; ++i ){ + free( resourceDir->childNames[i] ); resourceDir->childNames[i] = NULL; + } + free( resourceDir->childNames ); resourceDir->childNames = NULL; + } + free( url ); + return ret; +} + + +static void +this_free( this_t*this ) +{ + if( this==NULL ) return; + + //for( dirEntry_t*dirent=this->dirStack ; dirent ;){ + // for( size_t iChild=0 ; iChild<dirent->names_count ; ++iChild ){ + // free( dirent->names[iChild] ); dirent->names[iChild] = NULL; + // } + // free( dirent->names ); dirent->names = NULL; + // free( dirent->name ); dirent->name = NULL; + // { void*n=dirent->parent; free(dirent); dirent=n; } + //} + + free( this ); +} + +static this_t* +this_alloc() +{ + ssize_t err; + this_t *this = NULL; + + err = curl_global_init( CURL_GLOBAL_ALL ); + if( err ){ assert(!err); goto fail; } + + this = calloc(1, sizeof(*this) ); + + return this; + fail: + this_free( this ); + return NULL; +} + + +static size_t +onUploadChunkRequested( char*buf , size_t size , size_t count , void*cls_put_ ) +{ + cls_put_t *put = cls_put_; + cls_upload_t *upload = put->upload; + const size_t buf_len = size * count; + ssize_t ret = buf_len; + + ssize_t readLen = archive_read_data(upload->srcArchive, buf, buf_len); + //LOG_DEBUG("%s%lu%s\n", "Cpy ",readLen," bytes."); + if( readLen<0 ){ + LOG_ERROR("%s%ld%s%s\n", "Failed to read from archive (code ",readLen,"): ", archive_error_string(upload->srcArchive)); + assert(0); ret=-1; goto endFn; + } + else if( readLen>0 ){ + // Regular read. Data already written to 'buf'. Only need to adjust + // return val. + ret = readLen; + } + else{ // readLen==0 -> EOF + ret = 0; // Nothing more to read. + } + + + endFn: + //LOG_DEBUG("%s%s%ld\n", __func__, "() -> ", ret); + return ret>=0 ? ret : CURL_READFUNC_ABORT; +} + + +static ssize_t +addContentTypeHeader( cls_put_t*put , struct curl_slist *reqHdrs ) +{ + ssize_t err, ret=0; + char *contentTypeHdr = NULL; + cls_upload_t *upload = put->upload; + const char *name = put->name; + + uint_t name_len = strlen( put->name ); + // Find file extension. + const char *ext = name + name_len; + for(; ext>name && *ext!='.' && *ext!='/' ; --ext ); + // Convert it to mime type. + const char *mimeType; + if( *ext=='.' ){ + mimeType = fileExtToMime( ext+1 ); // <- +1, to skip the (useless) dot. + if( mimeType ){ + LOG_DEBUG("%s%s%s%s%s\n", "Resolved file ext '", ext+1,"' to mime '", mimeType?mimeType:"<null>", "'."); + } + } + else if( *ext=='/' || ext==name || *ext=='\0' ){ // TODO Explain why 0x00. + mimeType = "application/json"; + LOG_DEBUG("%s\n", "No file extension. Fallback to json (gateleen default)"); + } + else{ + mimeType = NULL; + } + if( mimeType==NULL ){ + LOG_DEBUG("%s%s%s\n", "Unknown file extension '", ext+1, "'. Will NOT add Content-Type header."); + mimeType = ""; // <- Need to 'remove' header. To do this, pass an empty value to curl. + } + uint_t mimeType_len = strlen( mimeType ); + static const char contentTypePrefix[] = "Content-Type: "; + static const uint_t contentTypePrefix_len = sizeof(contentTypePrefix)-1; + contentTypeHdr = malloc( contentTypePrefix_len + mimeType_len +1 ); + memcpy( contentTypeHdr , contentTypePrefix , contentTypePrefix_len ); + memcpy( contentTypeHdr+contentTypePrefix_len , mimeType , mimeType_len+1 ); + reqHdrs = curl_slist_append( reqHdrs, contentTypeHdr ); + err = curl_easy_setopt( upload->curl, CURLOPT_HTTPHEADER, reqHdrs ); + if( err ){ assert(!err); ret=-1; goto endFn; } + + endFn: + free( contentTypeHdr ); + return ret; +} + + +static ssize_t +httpPutEntry( cls_put_t*put ) +{ + ssize_t err, ret = 0; + cls_upload_t *upload = put->upload; + char *url = NULL; + struct curl_slist *reqHdrs = NULL; + + int rootUrl_len = strlen(upload->rootUrl); + if( upload->rootUrl[rootUrl_len-1]=='/' ){ + rootUrl_len -= 1; + } + int url_len = strlen(upload->rootUrl) + strlen(put->name); + url = malloc( url_len +2 ); + sprintf(url, "%.*s/%s", rootUrl_len,upload->rootUrl, put->name); + err = CURLE_OK != curl_easy_setopt(upload->curl, CURLOPT_URL, url ) + || addContentTypeHeader( put , reqHdrs ) + ; + if( err ){ assert(!err); ret=-1; goto endFn; } + + LOG_INFO("%s%s%s\n", "Upload '",url,"'"); + err = curl_easy_perform( upload->curl ); + if( err!=CURLE_OK ){ + LOG_ERROR("%s%s%s%ld%s%s\n", "PUT '",url,"' (code ", err, "): ", curl_easy_strerror(err)); + ret=-1; goto endFn; + } + long rspCode; + curl_easy_getinfo(upload->curl, CURLINFO_RESPONSE_CODE, &rspCode); + if( rspCode<=199 || rspCode>=300 ){ + LOG_WARN("%s%ld%s%s%s\n", "Got RspCode ", rspCode, " for 'PUT ", url, "'"); + }else{ + //LOG_DEBUG("%s%ld%s%s%s\n", "Got RspCode ", rspCode, " for 'PUT ", url, "'"); + } + + endFn: + curl_slist_free_all( reqHdrs ); + free( url ); + return ret; +} + + +static ssize_t +readArchive( cls_upload_t*upload ) +{ + ssize_t err, ret=0; + cls_put_t *put = NULL; + + upload->srcArchive = archive_read_new(); + if( ! upload->srcArchive ){ assert(upload->srcArchive); ret=-1; goto endFn; } + + const int blockSize = 16384; // <- Because curl examples use this value too. + err = archive_read_support_format_all( upload->srcArchive ) + || archive_read_open_filename( upload->srcArchive , upload->archiveFile , blockSize ) + ; + if( err ){ + LOG_ERROR("%s%ld%s%s\n", "Failed to open src archive (code ",err,"): ", curl_easy_strerror(err) ); + ret=-1; goto endFn; + } + + err = curl_easy_setopt( upload->curl, CURLOPT_UPLOAD, 1L) + || curl_easy_setopt( upload->curl, CURLOPT_READFUNCTION, onUploadChunkRequested ) + ; + if( err ){ assert(!err); ret=-1; goto endFn; } + for( struct archive_entry*entry ; archive_read_next_header(upload->srcArchive,&entry)==ARCHIVE_OK ;){ + const char *name = archive_entry_pathname( entry ); + int ftype = archive_entry_filetype( entry ); + if( ftype == AE_IFDIR ){ + continue; // Ignore dirs because gateleen doesn't know 'dirs' as such. + } + if( ftype != AE_IFREG ){ + LOG_WARN("%s%s%s\n", "Ignore non-regular file '", name, "'"); + continue; + } + //LOG_DEBUG("%s%s%s\n", "Reading '",name,"'"); + cls_put_t _1={ + .upload = upload, + .name = (char*)name + }; put =&_1; + err = curl_easy_setopt( upload->curl, CURLOPT_READDATA, put ) + || httpPutEntry( put ); + //curl = upload->curl; // Sync back. TODO: Still needed? + if( err ){ ret=-1; goto endFn; } + } + + endFn: + return ret; +} + + +static ssize_t +pull( this_t*this ) +{ + ssize_t err, ret = 0; + cls_dload_t *dload = NULL; + + if( this->file==NULL && isatty(1) ){ + LOG_ERROR("%s\n", "Are you sure you wanna write binary content to tty?"); + ret = -1; goto finally; + } + + cls_dload_t _1={0}; dload =&_1; + dload->this = this; + dload->rootUrl = this->url; + dload->archiveFile = this->file; + dload->curl = curl_easy_init(); + if( ! dload->curl ){ + LOG_ERROR("%s\n", "curl_easy_init() -> NULL"); + ret = -1; goto finally; + } + + err = gateleenResclone_download( dload , NULL , NULL ); + if( err ){ ret=-1; goto finally; } + + if( dload->dstArchive && archive_write_close(dload->dstArchive) ){ + LOG_ERROR("%s%s%ld%s%s\n", __func__, "(): archive_write_close failed (code ", err, "): ", archive_error_string(dload->dstArchive)); + ret = -1; goto finally; + } + + finally: + if( dload ){ + curl_easy_cleanup( dload->curl ); + archive_entry_free( dload->tmpEntry ); dload->tmpEntry = NULL; + archive_write_free( dload->dstArchive ); dload->dstArchive = NULL; + } + return ret; +} + + +static ssize_t +push( this_t*this ) +{ + ssize_t err, ret = 0; + cls_upload_t *upload = NULL; + + cls_upload_t _1={0}; upload =&_1; + upload->this = this; + upload->archiveFile = this->file; + upload->rootUrl = this->url; + upload->curl = curl_easy_init(); + if( ! upload->curl ){ + LOG_ERROR("%s\n", "curl_easy_init() -> NULL" ); + ret = -1; goto finally; + } + + err = readArchive( upload ); + if( err ){ ret=-1; goto finally; } + + finally: + if( upload ){ + curl_easy_cleanup( upload->curl ); + archive_read_free( upload->srcArchive ); + } + return ret; +} + + +ssize_t +gateleenResclone_run( int argc , char**argv ) +{ + ssize_t err, ret=0; + this_t *this = NULL; + + this = this_alloc(); + if( this==NULL ){ ret=-1; goto finally; } + + err = parseArgs( argc, argv, &this->mode, &this->url, &this->filter, &this->filter_len, + &this->isFilterFull, &this->file ); + if( err ){ ret=-1; goto finally; } + + if( this->mode == MODE_FETCH ){ + ret = pull(this); goto finally; + }else if( this->mode == MODE_PUSH ){ + ret = push(this); goto finally; + }else{ + ret = -1; goto finally; + } + + finally: + parseArgs( -1, argv, &this->mode, &this->url, &this->filter, &this->filter_len, + &this->isFilterFull, &this->file ); + this->mode=MODE_NULL; this->url=NULL; this->file=NULL; + this_free( this ); + return ret; +} + + +int +gateleenResclone_main( int argc , char**argv ) +{ + int ret; + ret = gateleenResclone_run( argc , argv ); + return ret<0||ret>127 ? 1 : ret; +} + |