diff --git a/configure.ac b/configure.ac --- a/configure.ac +++ b/configure.ac @@ -48,7 +48,7 @@ AM_CONDITIONAL([ENABLE_SERVER], dnl package dependencies: -PKG_CHECK_MODULES([DISTLIBS], [libconfuse >= 2.5 libcurl libxml-2.0 liblist >= 2.3.1 libarchive >= 2.8.0]) +PKG_CHECK_MODULES([DISTLIBS], [libconfuse >= 2.5 libcurl libxml-2.0 liblist >= 2.3.1 libarchive >= 2.8.0 libcrypto]) AX_LIB_MYSQL AS_IF( [test "x${MYSQL_VERSION}" = "x"], [ AC_MSG_ERROR([I need mysql]) ] ) diff --git a/src/client/distren.h b/src/client/distren.h --- a/src/client/distren.h +++ b/src/client/distren.h @@ -36,10 +36,15 @@ typedef struct distren_job *distren_job_ int distren_init(distren_t *handle); /** - Submits a file to distren. - @param job must not refer to a valid job. - @TODO create a stream-based interface -*/ + * Submits a file to distren. + * + * Blocks. + * + * @param handle distren handle. + * @param job must not refer to a valid job. + * @param filename path to a tarball representing a job. + * @return 0 on success, 1 on error. + */ int distren_submit_file(distren_t handle, distren_job_t *job, const char *filename); /** diff --git a/src/client/libdistren.c b/src/client/libdistren.c --- a/src/client/libdistren.c +++ b/src/client/libdistren.c @@ -29,6 +29,8 @@ #include "libdistren.h" +#include +#include #include #include #include @@ -109,12 +111,100 @@ static void distren_init_cleanup(distren distren_free(distren); } -/** - * @todo Stub - */ +#ifdef _WIN32 +#define FOPEN_BINARY "b" +#else +#define FOPEN_BINARY "" +#endif int distren_submit_file(distren_t handle, distren_job_t *job, const char *filename) { - return 1; + FILE *in; + char buf[DISTREN_REQUEST_FILE_POST_DATA_LEN]; + size_t len; + void *data; + + struct distren_request *req; + struct distren_request_file_post data_header; + distren_request_file_post_context_t post_context; + + /* for basename() to play with */ + char *my_filename; + + errno = 0; + in = fopen(filename, "r" FOPEN_BINARY); + if(!in) + { + perror("fopen"); + return 1; + } + + fprintf(stderr, "info: Starting upload of %s...\n", filename); + + /* prepare the server for a file upload */ + my_filename = strdup(filename); + distren_request_file_post_start(&req, + &data, + &post_context, + handle->file_post_id, + basename(my_filename)); + free(my_filename); + distren_request_send(handle->rem, req, data); + distren_request_free_with_data(req, data); + handle->file_post_id ++; + + /* send file body */ + while(in + && !ferror(in) + && !feof(in) + && handle->rem) + { + len = fread(buf, 1, DISTREN_REQUEST_FILE_POST_DATA_LEN, in); + if(len == 0) + continue; + distren_request_file_post(&req, + &data_header, + post_context, + buf, + len); + remoteio_write(handle->rem, req, sizeof(struct distren_request)); + /* should we check of handle->rem is NULL or not here...? */ + remoteio_write(handle->rem, &data_header, sizeof(struct distren_request_file_post)); + remoteio_write(handle->rem, buf, len); + distren_request_free(req); + + /* ensure we have no more than a megabyte waiting to be sent. */ + while(handle->rem + && remoteio_sendq_len(handle->rem) / 3 > (1024 * 1024 / DISTREN_REQUEST_FILE_POST_DATA_LEN)) + { + fprintf(stderr, "info: %d packets waiting to be sent...\n", remoteio_sendq_len(handle->rem) / 3); + multiio_poll(handle->multiio, -1); + } + } + + /* + * declare the upload as finished, marking it as cancelled on I/O + * error. + */ + distren_request_file_post_finish(&req, &data, post_context, (!in || ferror(in)) ? 1 : 0); + if(handle->rem) + distren_request_send(handle->rem, req, data); + distren_request_free_with_data(req, data); + + if(in) + fclose(in); + if(!handle->rem) + return 1; + + /* let's block until the file's gone. */ + while(handle->rem + && remoteio_sendq_len(handle->rem)) + { + fprintf(stderr, "info: %d packets waiting to be sent...\n", remoteio_sendq_len(handle->rem) / 3); + multiio_poll(handle->multiio, -1); + } + fprintf(stderr, "info: %s successfully uploaded, as far as we know.\n", filename); + + return 0; } int distren_free(distren_t handle) diff --git a/src/client/libdistren.h b/src/client/libdistren.h --- a/src/client/libdistren.h +++ b/src/client/libdistren.h @@ -72,7 +72,11 @@ struct distren /* something on which to call multiio_poll() ;-) */ multiio_context_t multiio; - + /* + * The last-used per-connection DISTREN_REQUEST_FILE_POST + * identifier. + */ + uint32_t file_post_id; }; struct distren_job diff --git a/src/common/protocol.h b/src/common/protocol.h --- a/src/common/protocol.h +++ b/src/common/protocol.h @@ -20,6 +20,7 @@ #ifndef DISTREN_PROTOCOL_H #define DISTREN_PROTOCOL_H +#include #include #include @@ -138,12 +139,34 @@ enum distren_request_type DISTREN_REQUEST_STATUS_GET = 10, /** + * Declare that a client is preparing to post a file using a + * particular post-id and with a user-friendly filename. + * + * Yes, this is evil. Yes, this tries to replicate + * well-established protocols like FTP and HTTP (which is + * sometimes itself mistreated as FTP). Yes, this will be + * buggy. Why is it needed? For my personal coding experience and + * because I don't know off-hand of any libraries that allow a + * file upload to be interleaved over an existing connection with + * its own protocol. Over TCP? We aren't doing real-time streaming + * or anything :-p. + * + * Possible future expansions: ``transparent'' zlib compression. + * + * DATA: struct distren_request_file_post_start + * + * REQUIRED: DISTREN_SERVERTYPE_SUBMIT, DISTREN_SERVERTYPE_DISTRIBUTE + */ + DISTREN_REQUEST_FILE_POST_START = 11, + + /** * Allow a client to upload a job tarball over a remoteio line. A * client that wants to do this must take care not to overbuffer * his sendqueue so as to be able to respond to PING packets in * time. A server receiving such a message will want to write the - * file as directly to disk as possible to avoid bagging a server - * down in swap. + * file as directly to disk as possible to avoid bogging the + * server down in swap. This packet may only be sent if + * DISTREN_REQUEST_FILE_POST_START has previously been sent. * * It is potential that if a server is DISTREN_SERVERTYPE_SUBMIT * but not also DISTREN_SERVERTYPE_DISTRIBUTE, that this request @@ -159,7 +182,18 @@ enum distren_request_type * * REQUIRED: DISTREN_SERVERTYPE_SUBMIT, DISTREN_SERVERTYPE_DISTRIBUTE */ - DISTREN_REQUEST_FILE_POST = 11, + DISTREN_REQUEST_FILE_POST = 12, + + /** + * Marks a post-id's file as having completely uploaded. Provides + * verification information so that the file's integrity may be + * verified. + * + * DATA: struct distren_request_file_post_finish + * + * REQUIRED: DISTREN_SERVERTYPE_SUBMIT, DISTREN_SERVERTYPE_DISTRIBUTE + */ + DISTREN_REQUEST_FILE_POST_FINISH = 13, /** * Request information about obtaining a file (such as a @@ -169,7 +203,7 @@ enum distren_request_type * * REQUIRED: DISTREN_SERVERTYPE_DISTRIBUTE */ - DISTREN_REQUEST_FILE_FIND = 12, + DISTREN_REQUEST_FILE_FIND = 14, /** * Provide information about obtaining a file (such as a URL). @@ -178,7 +212,7 @@ enum distren_request_type * * REQUIRED: DISTREN_SERVERTYPE_DISTRIBUTE */ - DISTREN_REQUEST_FILE = 13, + DISTREN_REQUEST_FILE = 15, }; struct distren_request @@ -202,6 +236,45 @@ struct distren_request_version char package_string[DISTREN_REQUEST_VERSION_PACKAGE_STRING_LEN + 1]; }; +#define DISTREN_REQUEST_FILE_POST_NAME_LEN (64) +struct distren_request_file_post_start +{ + /** + * Uniquely identify this file upload (per connection). + */ + uint32_t post_id; + /** + * The user-friendly filename + */ + char filename[DISTREN_REQUEST_FILE_POST_NAME_LEN]; +}; + +#define DISTREN_REQUEST_FILE_POST_DATA_LEN (1024*128) +struct distren_request_file_post +{ + /** + * Uniquely identify this file upload (per connection). + */ + uint32_t post_id; +}; + +struct distren_request_file_post_finish +{ + /** + * Uniquely identify this file upload (per connection). + */ + uint32_t post_id; + /** + * Marks this upload as failed/cancelled. + */ + uint32_t cancel; + /** + * An SHA1sum of the entire file. Only set if last_packet is + * nonzero. Should be otherwise ignored. + */ + unsigned char sha[EVP_MAX_MD_SIZE]; +}; + /** * initializes and allocates request */ diff --git a/src/common/request.c b/src/common/request.c --- a/src/common/request.c +++ b/src/common/request.c @@ -19,8 +19,12 @@ #include "common/config.h" +#include "common/request.h" + #include "common/protocol.h" +#include +#include #include #include @@ -79,3 +83,230 @@ int distren_request_poing(struct distren return 0; } + +/* + * file posting stuffs + */ +struct distren_request_file_post_context +{ + EVP_MD_CTX *digest_ctx; + /* stored in network-byte order */ + uint32_t post_id; +}; + +/** + * Helper for distren_request_file_post_start() and + * distren_request_parse_file_post_start(). + */ +static int _distren_request_file_post_context_new(distren_request_file_post_context_t *post_context, + uint32_t post_id) +{ + *post_context = malloc(sizeof(struct distren_request_file_post_context)); + if(!*post_context) + return 1; + + (*post_context)->post_id = htonl(post_id); + (*post_context)->digest_ctx = EVP_MD_CTX_create(); + EVP_DigestInit_ex((*post_context)->digest_ctx, EVP_sha1(), NULL); + + return 0; +} + +/** + * Frees a post_context and sets the pointer to NULL. + */ +static int _distren_request_file_post_context_free(distren_request_file_post_context_t *post_context) +{ + if(!*post_context) + return 0; + + EVP_MD_CTX_destroy((*post_context)->digest_ctx); + free(*post_context); + + *post_context = NULL; + + return 0; +} + +int distren_request_file_post_context_free(distren_request_file_post_context_t post_context) +{ + /* + * for the ``public'' function, we don't need to have post_context + * set to NULL. Thus, we have this wrapper around + * _distren_request_file_post_context_free(). + */ + return _distren_request_file_post_context_free(&post_context); +} + +int distren_request_file_post_start(struct distren_request **req, + void **data, + distren_request_file_post_context_t *post_context, + uint32_t post_id, + const char *filename) +{ + struct distren_request_file_post_start *file_post_start; + + distren_request_new(req, sizeof(struct distren_request_file_post_start), DISTREN_REQUEST_FILE_POST_START); + file_post_start = malloc(sizeof(struct distren_request_file_post_start)); + _distren_request_file_post_context_new(post_context, post_id); + if(!*req + || !file_post_start) + { + if(*req) + distren_request_free(*req); + free(file_post_start); + _distren_request_file_post_context_free(post_context); + return 1; + } + + file_post_start->post_id = (*post_context)->post_id; + strncpy(file_post_start->filename, filename, DISTREN_REQUEST_FILE_POST_NAME_LEN); + + *data = file_post_start; + return 0; +} + +int distren_request_parse_file_post_start(struct distren_request *req, + void *data, + distren_request_file_post_context_t *post_context, + uint32_t *post_id, + char **filename) +{ + int tmp; + struct distren_request_file_post_start *file_post_start; + + /* keep our promises ;-) */ + *post_context = NULL; + *filename = NULL; + + if(req->len < sizeof(struct distren_request_file_post_start)) + return 1; + + file_post_start = data; + + tmp = _distren_request_file_post_context_new(post_context, file_post_start->post_id); + *filename = malloc(DISTREN_REQUEST_FILE_POST_NAME_LEN + 1); + if(tmp + || !*filename) + { + _distren_request_file_post_context_free(post_context); + free(*filename); + } + + memcpy(*filename, file_post_start->filename, DISTREN_REQUEST_FILE_POST_NAME_LEN); + (*filename)[DISTREN_REQUEST_FILE_POST_NAME_LEN] = '\0'; + + *post_id = ntohl(file_post_start->post_id); + + return 0; +} + +int distren_request_file_post(struct distren_request **req, + struct distren_request_file_post *data_header, + distren_request_file_post_context_t post_context, + const void *data, + size_t len) +{ + distren_request_new(req, sizeof(struct distren_request_file_post) + len, DISTREN_REQUEST_FILE_POST); + if(!*req) + return 1; + + data_header->post_id = post_context->post_id; + + EVP_DigestUpdate(post_context->digest_ctx, data, len); + + return 0; +} + +int distren_request_parse_file_post(struct distren_request *req, + void *data, + uint32_t *post_id, + distren_request_parse_file_post_find_context_func_t find_context_func, + void *find_post_context_data, + void **file_data, + size_t *file_data_len) +{ + struct distren_request_file_post *file_post; + distren_request_file_post_context_t post_context; + + if(req->type != DISTREN_REQUEST_FILE_POST + || req->len < sizeof(struct distren_request_file_post)) + return 1; + + file_post = data; + *post_id = ntohl(file_post->post_id); + post_context = (*find_context_func)(*post_id, find_post_context_data); + if(!post_context + || *post_id != post_context->post_id) + return 1; + + *file_data = data + sizeof(struct distren_request_file_post); + *file_data_len = req->len - sizeof(struct distren_request_file_post); + EVP_DigestUpdate(post_context->digest_ctx, *file_data, *file_data_len); + + return 0; +} + +int distren_request_file_post_finish(struct distren_request **req, + void **data, + distren_request_file_post_context_t post_context, + uint32_t cancel) +{ + struct distren_request_file_post_finish *file_post_finish; + + distren_request_new(req, sizeof(struct distren_request_file_post_finish), DISTREN_REQUEST_FILE_POST_FINISH); + file_post_finish = malloc(sizeof(struct distren_request_file_post_finish)); + if(!*req + || !file_post_finish) + { + if(*req) + distren_request_free(*req); + free(file_post_finish); + + _distren_request_file_post_context_free(&post_context); + return 1; + } + file_post_finish->post_id = post_context->post_id; + file_post_finish->cancel = htonl(cancel); + + EVP_DigestFinal(post_context->digest_ctx, file_post_finish->sha, NULL); + _distren_request_file_post_context_free(&post_context); + + *data = file_post_finish; + return 0; +} + +int distren_request_parse_file_post_finish(struct distren_request *req, + void *data, + uint32_t *post_id, + distren_request_parse_file_post_find_context_func_t find_context_func, + void *find_post_context_data) +{ + struct distren_request_file_post_finish *file_post_finish; + distren_request_file_post_context_t post_context; + + unsigned int checksum_len; + unsigned char checksum[EVP_MAX_MD_SIZE]; + + if(req->type != DISTREN_REQUEST_FILE_POST_FINISH + || req->len < sizeof(struct distren_request_file_post_finish)) + return 1; + + file_post_finish = data; + *post_id = ntohl(file_post_finish->post_id); + if(ntohl(file_post_finish->cancel)) + return 2; + + post_context = find_context_func(*post_id, find_post_context_data); + if(!post_context) + return 1; + + /* finish up checksumming */ + EVP_DigestFinal_ex(post_context->digest_ctx, checksum, &checksum_len); + if(checksum_len > EVP_MAX_MD_SIZE) + checksum_len = EVP_MAX_MD_SIZE; + if(memcmp(checksum, file_post_finish->sha, checksum_len)) + return 3; + + return 0; +} diff --git a/src/common/request.h b/src/common/request.h --- a/src/common/request.h +++ b/src/common/request.h @@ -22,6 +22,8 @@ #include "common/protocol.h" +#include + /** * @file functions to initialize various requests that the server and * client may both use. @@ -62,6 +64,152 @@ int distren_request_parse_version(struct * @param poing_data_len bytes in the poing_cookie * @return the length of the data allocated for this request */ -uint32_t distren_request_poing(struct distren_request **req, void **data, short is_ping, const void *poing_cookie, size_t poing_data_len); +int distren_request_poing(struct distren_request **req, void **data, short is_ping, const void *poing_cookie, size_t poing_data_len); + +struct distren_request_file_post_context; +typedef struct distren_request_file_post_context *distren_request_file_post_context_t; + +/** + * Frees a post_context for if a client is disconnected or an upload + * otherwise interrupted. + */ +int distren_request_file_post_context_free(distren_request_file_post_context_t post_context); + +/** + * Prepares for a file upload and initializes a + * distren_request_file_post_context_t. + * + * If you call this function and you never finish an upload, you + * _must_ call distren_request_file_post_finish() on post_context at + * least to free the post_context. + * + * @param req where to store the new distren_request pointer. + * @param data where to store the new request's data pointer. + * @param post_context where the newly allocated distren_request_file_post_context_t's addrss should be stored. + * @param post_id a number that uniquely identifies this upload for this connection. + * @param filename a user-friendly filename for the file posting. + */ +int distren_request_file_post_start(struct distren_request **req, + void **data, + distren_request_file_post_context_t *post_context, + uint32_t post_id, + const char *filename); + +/** + * Parses a DISTREN_REQUEST_FILE_POST_START request. + * + * @param req the recieved request. + * @param data the request's data. + * @param post_context a post_context to be initialized for a continued download. Must be free()d with a call to distren_request_parse_file_post_finish() or distren_request_file_post_context_free(), however. + * @param post_id will be set to the post_id contained in the packet. + * @param filename will be made to point at the filename the client requested. *filename must be free()d. + * @return 0 upon no problem, 1 if there's an error. If 1, then *filename should not be used (it will be set NULL, so no problem calling free() on it though). + */ +int distren_request_parse_file_post_start(struct distren_request *req, + void *data, + distren_request_file_post_context_t *post_context, + uint32_t *post_id, + char **filename); + +/** + * Initializes a DISTREN_REQUEST_FILE_POST packet's header. + * + * As file posting requires an acceptable chunk of memory, we don't + * malloc() the data portion of the packet here to encourage reading + * files in through static buffers. + * + * As a single file takes multiple DISTREN_REQUEST_FILE_POST packets + * before it's completely uploaded, certain state information has to + * persist between calls to distren_request_file_post(). This is what + * the post_context argument is for. This pointer must be preserved + * between calls to distren_request_file_post(). + * + * @param req place to store the new req's pointer + * @param data_header where the data's header should be stored + * @param post_context what this points to must be preserved between calls to distren_request_file_post(), for keeping state. +FILE_POST_DATA_LEN. + * @param data a hunk of the file being posted. + * @param len the length of the data argument. This must be less than or equal to DISTREN_REQUEST_ */ +int distren_request_file_post(struct distren_request **req, + struct distren_request_file_post *data_header, + distren_request_file_post_context_t post_context, + const void *data, + size_t len); +/** + * A callback to find a distren_request_file_post_context_t for a + * specified post_id. + * + * This exists because it is recognized that two servers may be + * transfering more than one file at a time between eachother. At the + * time that distren_request_parse_file_post() is called, the caller + * doesn't know post_id and thus can't provide the proper + * post_context. Yet distren_request_parse_file_post() needs the + * post_context to get its job done ;-). + * + * @param post_id the post_id for which to find a distren_request_file_post_context_t + * @param find_post_context_data the same find_post_context_data pointer passed to distren_request_parse_file_post() or distren_request_parse_file_post_finish(). + * @return NULL if the context could not be found or the context. + */ +typedef distren_request_file_post_context_t(*distren_request_parse_file_post_find_context_func_t)(uint32_t post_id, void *find_post_context_data); + +/** + * Parse a DISTREN_REQUEST_FILE_POST packet and update the + * post_context. + * + * @param req the request header. + * @param data the request data section. + * @param post_id will be filled with the request's post_id. + * @param find_context_func a function that can look up the necessary context_post given a post_id. + * @param find_post_context_data the pointer to pass as context data to find_context_func + * @param file_data will be set to the beginning of the file data portion of the packet. Do not free() this pointer. + * @param file_data_len the number of bytes of file data that may be accessed at *file_data. + * @return 0 on success and 1 on failure, in which case the client should be kicked and you should free the post_context with distren_request_file_post_context_free(). + */ +int distren_request_parse_file_post(struct distren_request *req, + void *data, + uint32_t *post_id, + distren_request_parse_file_post_find_context_func_t find_context_func, + void *find_post_context_data, + void **file_data, + size_t *file_data_len); + +/** + * Construct a packet signalling that a file posting is finished. + * + * Frees the memory used to store the post_context. Must be called + * once for every call to distren_request_file_post_start(), + * regardless of whether or not you actually call + * distren_request_send() on the result. + * + * @param req where to store the pointer of the newly allocated request + * @param data where to store the pointer t othe newly allocated request data + * @param post_context the post_context which will be used and then freed. + * @param cancel 0 if the file was uploaded successfully, 1 if the upload is cancelled or failed. + */ +int distren_request_file_post_finish(struct distren_request **req, + void **data, + distren_request_file_post_context_t post_context, + uint32_t cancel); + +/** + * Parses a DISTREN_REQUEST_FILE_POST_FINISH packet. + * + * You must still call distren_request_file_post_context_free() on the + * post_context which you must find yourself. If you are a server, you + * must keep a list of these contexts per-client so that when a client + * exits, you may reclaim these contexts. + * + * @param req the request header + * @param data the data section of the packet + * @param post_id a place where the post_id will be set. + * @param find_context_func a function pointer to let me find a post_context based on the post_id of the request. + * @param find_post_context_data the pointer to pass to the find_context_func callback. + * @return 0 for a good packet, 1 for a bad packet or if the post_context can't be found (post_id won't be valid), 2 if the request asks for the user to cancel the file upload (post_id will be valid), and 3 if the checksums don't match (post_id will be valid). + */ +int distren_request_parse_file_post_finish(struct distren_request *req, + void *data, + uint32_t *post_id, + distren_request_parse_file_post_find_context_func_t find_context_func, + void *find_post_context_data); #endif /* _DISTREN_REQUEST_H */ diff --git a/src/server/distrend.c b/src/server/distrend.c --- a/src/server/distrend.c +++ b/src/server/distrend.c @@ -33,6 +33,7 @@ #include "common/protocol.h" #include "common/request.h" +#include #include #include #include @@ -61,6 +62,12 @@ struct general_info { /** general_info.xml */ char *geninfo; + /** + * where to store in-progress uploads or things that should + * otherwise be on the same filesystem as the rest of the datadir + * so that it may be rename()d. + */ + char *tmpdir; } files; @@ -88,9 +95,15 @@ int distrend_config_free(struct distrend int distrend_handle_request(struct distrend_listens *listens, struct distrend_client *client, struct distren_request *req, void *reqdata, struct general_info *geninfo); /** - client request handlers + * client request handlers */ int distrend_handle_version(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); +int distrend_handle_file_post_start(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); +int distrend_handle_file_post(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); +int distrend_handle_file_post_finish(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); + +/* functions of some generic sort ...ish */ +int distrend_handle_successful_upload(struct distrend_client *client, struct distrend_client_file_post *client_file_post); /* **************XML Functions**************** */ void update_general_info(struct general_info *geninfo); @@ -154,6 +167,10 @@ Ethan Zonca \n\ _distren_asprintf(&general_info.files.geninfo, "%s/general_info.xml", general_info.config->datadir); + _distren_asprintf(&general_info.files.tmpdir, "%s/tmp", + general_info.config->datadir); + distren_mkdir_recurse(general_info.files.tmpdir); + /** MySQL Connection */ fprintf(stderr,"Connecting to mysql...\n"); if(mysqlConnect(&general_info.conn, @@ -190,6 +207,9 @@ Ethan Zonca \n\ } distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_VERSION, &distrend_handle_version); + distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST_START, &distrend_handle_file_post_start); + distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST, &distrend_handle_file_post); + distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST_FINISH, &distrend_handle_file_post_finish); /* Main Loop */ general_info.config->die = 0; @@ -265,6 +285,259 @@ int distrend_handle_version(struct gener } /** + * Traversal helper for distrend_client_find_post(). + */ +int distrend_client_find_post_traverse(uint32_t *post_id, struct distrend_client_file_post *client_file_post) +{ + if(*post_id == client_file_post->post_id) + return FALSE; + + return TRUE; +} + +/** + * Find the record for an in-progress client's file posting. + */ +struct distrend_client_file_post *distrend_client_find_post(struct distrend_client *client, uint32_t post_id) +{ + if(list_traverse(client->file_post_list, &post_id, (list_traverse_func_t)&distrend_client_find_post_traverse, LIST_ALTR | LIST_FORW | LIST_FRNT) != LIST_EXTENT) + return list_curr(client->file_post_list); + return NULL; +} + +/** + * Finds a post_context based on the post_id and client. + * + * Compatible the distren_request_parse_file_post_find_context_func_t. + */ +static distren_request_file_post_context_t distrend_client_find_file_post_context(uint32_t post_id, void *client) +{ + struct distrend_client_file_post *client_file_post; + + client_file_post = distrend_client_find_post(client, post_id); + if(client_file_post) + return client_file_post->post_context; + return NULL; +} + +/** + * Clean up and free a client_file_post + * + * Whenever calling this functino, you almost _always_ have to call + * list_remove_element(client->file_post_list, client_file_post); + * first. + */ +void distrend_client_file_post_free(struct distrend_client_file_post *client_file_post) +{ + fclose(client_file_post->fd); + free(client_file_post->filename); + unlink(client_file_post->file_save_path); + free(client_file_post->file_save_path); + + free(client_file_post); +} + +int distrend_handle_file_post_start(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data) +{ + struct distrend_client_file_post *client_file_post; + + distren_request_file_post_context_t post_context; + uint32_t post_id; + char *filename; + + char *str_tmp; + + int ret; + + /** + * @todo access check! + */ + fprintf(stderr, __FILE__ ":%d:distrend_handle_file_post_start(): You need to check if a client is actually allowed to upload files somehow!\n", __LINE__); + + /* + * other servers should be excluded from this check, but we don't + * store the servertype in client yet. + */ + if(list_size(client->file_post_list) > 1) + { + distrend_send_disconnect(client, "You are trying to upload too many files at once!"); + return 1; + } + + ret = distren_request_parse_file_post_start(req, req_data, &post_context, &post_id, &filename); + if(ret) + { + distrend_send_disconnect(client, "You sent me an invalid DISTREN_REQUEST_FILE_POST_START packet"); + return 1; + } + + if(distrend_client_find_post(client, post_id)) + { + _distren_asprintf(&str_tmp, "Err accepting file: You are trying to upload using post_id=%d while you have already started another upload using the same post_id", post_id); + distrend_send_disconnect(client, str_tmp); + free(str_tmp); + + distren_request_file_post_context_free(post_context); + + return 1; + } + + client_file_post = malloc(sizeof(struct distrend_client_file_post)); + if(!client_file_post) + { + distrend_send_disconnect(client, "Error accepting file: out of memory"); + + distren_request_file_post_context_free(post_context); + + return 1; + } + + client_file_post->post_context = post_context; + client_file_post->post_id = post_id; + client_file_post->filename = filename; + _distren_asprintf(&client_file_post->file_save_path, "%s/conn-%d_file_post-%d", + geninfo->files.tmpdir, client->connection_id, post_id); + client_file_post->fd = fopen(client_file_post->file_save_path, "w"); + if(!client_file_post->fd) + { + perror("fopen"); + + fprintf(stderr, "error: Unable to open ``%s''. See above ``fopen'' error for more details.\n", client_file_post->file_save_path); + distrend_send_disconnect(client, "Error accepting file: unable to store the file n disk"); + + distren_request_file_post_context_free(post_context); + + list_remove_element(client->file_post_list, client_file_post); + distrend_client_file_post_free(client_file_post); + return 1; + } + + list_insert_after(client->file_post_list, client_file_post, 0); + + return 0; +} + +int distrend_handle_file_post(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data) +{ + struct distrend_client_file_post *client_file_post; + + void *file_data; + size_t file_data_len; + + uint32_t post_id; + char *tmp_str; + + size_t written_len; + + int ret; + + ret = distren_request_parse_file_post(req, req_data, &post_id, + &distrend_client_find_file_post_context, client, + &file_data, &file_data_len); + if(ret) + { + distrend_send_disconnect(client, "You sent me an invalid DISTREN_REQUEST_FILE_POST packet"); + return 1; + } + + client_file_post = distrend_client_find_post(client, post_id); + + if(!client_file_post) + { + _distren_asprintf(&tmp_str, "You are attempting to upload post_id=%d when you haven't given a DISTREN_REQUEST_FILE_POST_START packet", post_id); + distrend_send_disconnect(client, tmp_str); + free(tmp_str); + return 1; + } + + written_len = fwrite(file_data, 1, file_data_len, client_file_post->fd); + if(written_len < file_data_len) + { + distrend_send_disconnect(client, "Error saving upload: error while writing to the temporary upload file"); + + list_remove_element(client->file_post_list, client_file_post);; + /* closes the file being written, and free()s everything, unlinks the file */ + distrend_client_file_post_free(client_file_post); + + return 1; + } + + return 0; +} + +/** + * here be magic + */ +int distrend_handle_file_post_finish(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data) +{ + struct distrend_client_file_post *client_file_post; + + uint32_t post_id; + int ret; + + ret = distren_request_parse_file_post_finish(req, req_data, &post_id, + &distrend_client_find_file_post_context, client); + if(ret) + { + switch(ret) + { + case 2: + /* client asked to cancel a file upload */ + client_file_post = distrend_client_find_post(client, post_id); + if(client_file_post) + { + list_remove_element(client->file_post_list, client_file_post); + distrend_client_file_post_free(client_file_post); + } + return 0; + + case 3: + /* checksuming failed */ + distrend_send_disconnect(client, "You have uploaded a file that doesn't match its checksum somehow... which should be pretty much impossible"); + client_file_post = distrend_client_find_post(client, post_id); + if(client_file_post) + { + list_remove_element(client->file_post_list, client_file_post); + distrend_client_file_post_free(client_file_post); + } + return 1; + + default: + distrend_send_disconnect(client, "You sent me an invalid DISTREN_REQUEST_FILE_POST_FINISH packet"); + } + return 1; + } + + client_file_post = distrend_client_find_post(client, post_id); + if(!client_file_post) + return 1; + + /* + * Here it is... manage a file being submitted for rendering... or + * for whatever purpose it was uploaded for somehow... + */ + distrend_handle_successful_upload(client, client_file_post); + + list_remove_element(client->file_post_list, client_file_post); + distrend_client_file_post_free(client_file_post); + + return 0; +} + +/** + * Does stuff with file uploads after they've been successfully acquired. + * + * @todo this should probably be genericized to handle 1. file uploads and 2. server-initiated file _downloads_, i.e., using libcurl. In that case, struct distrend_client_file_post shouldn't be passed directly to ourself but some common struct might be passed here. + */ +int distrend_handle_successful_upload(struct distrend_client *client, struct distrend_client_file_post *client_file_post) +{ + fprintf(stderr, __FILE__ ":%d: STUB: I don't know what to do with %s[%s] :-/\n", __LINE__, + client_file_post->filename, client_file_post->file_save_path); + + return 0; +} + +/** Performs command stored in a client's request. @TODO: Fill stub */ int distrend_do() @@ -582,3 +855,4 @@ int interactiveTest(int test, multiio_co } return 0; } + diff --git a/src/server/distrend.h b/src/server/distrend.h --- a/src/server/distrend.h +++ b/src/server/distrend.h @@ -43,4 +43,7 @@ struct distrend_config char *mysql_database; }; +struct distrend_client_file_post; +void distrend_client_file_post_free(struct distrend_client_file_post *client_file_post); + #endif diff --git a/src/server/listen.c b/src/server/listen.c --- a/src/server/listen.c +++ b/src/server/listen.c @@ -46,7 +46,8 @@ struct distrend_request_handler_info struct distrend_client *distrend_client_new(struct distrend_listens *listens, enum distrend_client_state state, - struct remoteio *rem); + struct remoteio *rem, + int connection_id); int distrend_client_free(struct distrend_client *client); int distrend_dispatch_request(struct distrend_listens *listens, struct remoteio *rem, struct distrend_client *client, struct distren_request *req, void *reqdata); struct distrend_polled_sock *distrend_polled_sock_get_by_offset(struct distrend_listens *listens, size_t pollfds_offset); @@ -290,7 +291,7 @@ int listen_handle_accept(multiio_context newclientsock = accept(fd, (struct sockaddr *)NULL, (socklen_t *)NULL); /* used to call int distrend_client_add(struct distrend_listens *listens, int sock, DISTREND_CLIENT_PREVERSION)*/ - newclient = distrend_client_new(listens, DISTREND_CLIENT_PREVERSION, NULL); + newclient = distrend_client_new(listens, DISTREND_CLIENT_PREVERSION, NULL, newclientsock); if(remoteio_open_socket(&rem, listens->options->remoteio, (remoteio_read_handle_func_t)&distrend_listen_read_handle, newclient, (remoteio_close_handle_func_t)&distrend_listen_remoteio_handle_close, newclientsock)) { @@ -436,6 +437,7 @@ size_t distrend_listen_read_handle(struc return used_len; } + /** * Handle cleaning up after remoteio_close() has been called. This includes cleaning up the struct distrend_client and stuffs */ @@ -447,6 +449,7 @@ void distrend_listen_remoteio_handle_clo */ tabletennis_del_client(listens->tabletennis, client); + list_free(client->file_post_list, (list_dealloc_func_t)&distrend_client_file_post_free); free(client); } @@ -471,7 +474,7 @@ void remotio_send_to_client(struct distr * * */ -struct distrend_client *distrend_client_new(struct distrend_listens *listens, enum distrend_client_state state, struct remoteio *rem) +struct distrend_client *distrend_client_new(struct distrend_listens *listens, enum distrend_client_state state, struct remoteio *rem, int connection_id) { struct distrend_client *client; @@ -486,7 +489,9 @@ struct distrend_client *distrend_client_ client->cleanup_time = time(NULL) + DISTREND_LISTEN_AUTHTIME; client->inlen = 0; client->expectlen = 0; + client->file_post_list = list_init(); client->rem = rem; + client->connection_id = connection_id; return client; } diff --git a/src/server/listen.h b/src/server/listen.h --- a/src/server/listen.h +++ b/src/server/listen.h @@ -39,6 +39,7 @@ struct distrend_client; #include "common/multiio.h" #include "common/protocol.h" #include "common/remoteio.h" +#include "common/request.h" #include #include @@ -104,6 +105,16 @@ struct distrend_listens multiio_socket_type_t socket_type; }; +struct distrend_client_file_post +{ + distren_request_file_post_context_t post_context; + uint32_t post_id; + /** user-friendly filename */ + char *filename; + /** on-disk filename */ + char *file_save_path; + FILE *fd; +}; /** The information necessary to recieve data from and send data @@ -127,6 +138,17 @@ struct distrend_client struct tabletennis_client tabletennis_client; /** + * File postings (uploads) are per-connection, and thus stored + * here. Of type struct distrend_client_file_post. + */ + list_t file_post_list; + + /** + * Yes, in some places we need a unique key for this connection. + */ + int connection_id; + + /** * Yes, even though the remoteio will have a void *pointer to this * struct, we need a reverse pointer for something like * distrend_client_write() to work.