Changeset - e0b7e9dd5b54
[Not reviewed]
default
0 11 0
Nathan Brink (binki) - 15 years ago 2010-07-31 00:57:35
ohnobinki@ohnopublishing.net
File uploads are now supported using DISTREN_REQUEST_FILE_POST and friends.
11 files changed with 875 insertions and 20 deletions:
0 comments (0 inline, 0 general)
configure.ac
Show inline comments
 
@@ -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]) ] )
src/client/distren.h
Show inline comments
 
@@ -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);
 

	
 
/**
src/client/libdistren.c
Show inline comments
 
@@ -29,6 +29,8 @@
 

	
 
#include "libdistren.h"
 

	
 
#include <errno.h>
 
#include <libgen.h>
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 
@@ -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)
src/client/libdistren.h
Show inline comments
 
@@ -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
src/common/protocol.h
Show inline comments
 
@@ -20,6 +20,7 @@
 
#ifndef DISTREN_PROTOCOL_H
 
#define DISTREN_PROTOCOL_H
 

	
 
#include <openssl/evp.h>
 
#include <stddef.h>
 
#include <stdint.h>
 

	
 
@@ -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
 
 */
src/common/request.c
Show inline comments
 
@@ -19,8 +19,12 @@
 

	
 
#include "common/config.h"
 

	
 
#include "common/request.h"
 

	
 
#include "common/protocol.h"
 

	
 
#include <arpa/inet.h>
 
#include <openssl/evp.h>
 
#include <stdlib.h>
 
#include <string.h>
 

	
 
@@ -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;
 
}
src/common/request.h
Show inline comments
 
@@ -22,6 +22,8 @@
 

	
 
#include "common/protocol.h"
 

	
 
#include <openssl/sha.h>
 

	
 
/**
 
 * @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 */
src/server/distrend.c
Show inline comments
 
@@ -33,6 +33,7 @@
 
#include "common/protocol.h"
 
#include "common/request.h"
 

	
 
#include <arpa/inet.h>
 
#include <confuse.h>
 
#include <stdio.h>
 
#include <stdlib.h>
 
@@ -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 <ethanzonca@gmail.com>\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 <ethanzonca@gmail.com>\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;
 
}
 

	
src/server/distrend.h
Show inline comments
 
@@ -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
src/server/listen.c
Show inline comments
 
@@ -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;
 
}
src/server/listen.h
Show inline comments
 
@@ -39,6 +39,7 @@ struct distrend_client;
 
#include "common/multiio.h"
 
#include "common/protocol.h"
 
#include "common/remoteio.h"
 
#include "common/request.h"
 

	
 
#include <queue.h>
 
#include <time.h>
 
@@ -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.
0 comments (0 inline, 0 general)