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 873 insertions and 18 deletions:
0 comments (0 inline, 0 general)
configure.ac
Show inline comments
 
# Copyright 2010 Nathan Phillip Brink, Ethan Zonca
 
#
 
# This file is a part of DistRen.
 
#
 
# Distren is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU Affero General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# DistRen is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU Affero General Public License for more details.
 
#
 
# You should have received a copy of the GNU Affero General Public License
 
# along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
AC_PREREQ(2.61)
 
AC_INIT([distren],[0.0],[http://bugs.ohnopub.net/], [], [http://ohnopub.net/distren/])
 
AC_CONFIG_HEADERS([src/common/config.h])
 
AC_CONFIG_SRCDIR([src/server/distrend.c])
 
AC_CONFIG_MACRO_DIR([m4])
 

	
 
AC_PROG_CC
 
AC_PROG_LIBTOOL
 

	
 
AM_INIT_AUTOMAKE([gnu dist-bzip2 subdir-objects -Wall])
 
AM_PROG_CC_C_O
 

	
 
dnl these macros force the refered to types to be available without me
 
dnl writing my own magic :-)
 
AC_TYPE_PID_T
 
AC_TYPE_SIZE_T
 

	
 
AC_TYPE_UINT8_T
 
AC_TYPE_UINT16_T
 
AC_TYPE_UINT32_T
 

	
 
dnl selective compilation
 
dnl For now, this is only left for when the C-based client is
 
dnl reintroducded.
 
AC_ARG_ENABLE([server],
 
	[AS_HELP_STRING([--disable-server],[Don't build the distren server])],
 
	[enable_server=$enableval],
 
	[enable_server=yes])
 
AM_CONDITIONAL([ENABLE_SERVER],
 
	[test "x$enable_server" = "xyes"])
 

	
 
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]) ] )
 

	
 
LIBS_save="$LIBS"
 
CFLAGS_save="$CFLAGS"
 
LIBS="$LIBS $DISTLIBS_LIBS"
 
CFLAGS="$CFLAGS $DISTLIBS_CFLAGS"
 
AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <list.h>
 
	#include <stdio.h>
 
	], [printf(stderr, "%s", list_brag);
 
	return 0;])], [AC_DEFINE([HAVE_LIST_BRAG], [], [Define if liblist has list_brag.])], [])
 
LIBS="$LIBS_save"
 
CFLAGS="$CFLAGS_save"
 

	
 
PKG_CHECK_MODULES([CHECK], [check >= 0.9.3])
 

	
 
dnl define paths for configuration files until a better arrangement is
 
dnl made:
 

	
 
AC_DEFINE_DIR([LOCALSTATEDIR], [localstatedir], [Default directory for storing state information])
 
AC_DEFINE_DIR([RUNSTATEDIR], [localstatedir/run], [Default directory for registering runtime information like pid-files])
 

	
 
AC_CONFIG_FILES([Makefile
 
	etc/distrendaemon.conf
 
	etc/distrenslave.conf
 
])
 

	
 
AC_OUTPUT
src/client/distren.h
Show inline comments
 
/*
 
  Copyright 2010 Nathan Phillip Brink, Ethan Zonca, Matt Orlando
 

	
 
  This file is a part of DistRen.
 

	
 
  DistRen is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU Affero General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  DistRen is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU Affero General Public License for more details.
 

	
 
  You should have received a copy of the GNU Affero General Public License
 
  along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef DISTREN_H
 
#define DISTREN_H 1
 

	
 
/*
 
  Public API for DistRen
 
 */
 

	
 
#include <stddef.h> /* size_t */
 

	
 
typedef struct distren *distren_t;
 

	
 
typedef struct distren_job *distren_job_t;
 

	
 
/**
 
 * Initializes a libdistren handle.
 
 */
 
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);
 

	
 
/**
 
   Retrieve a job-ID that can be used to refer to the job after the client's process has recycled. Currently, this will be the only way to retrieve a job handle (a distren_job_t) and, consequently, the only way to retrieve a finished render.
 
   A job ID is representable using a character string. Hopefully, they will be kept at a reasonably small length. It should be made up of alphanumeric characters so as to be useful in most situations. 
 
   @param jobid This will be set to a string allocated by the specified malloc implementation
 
   @param job The distren_job_t from which to retrieve a jobid
 
*/
 
int distren_job_geturi(distren_job_t job, char **joburi);
 

	
 
/**
 
   Retrieves a tarball of a rendering's result.
 
   @param outfile Filename to save the result to
 
 */
 
int distren_job_retrieve_file(distren_job_t job, const char *outfile);
 

	
 
/*
 
 TODO: extraction of a job-ID, retrieving job handle by job-ID, getting information
 
 for a job handle, retrieving results of a job, callbacks for progress (i.e., job_is_finished callback)
 
 -- threads? -- noooo!
 
*/
 

	
 
/**
 
 Destroys and frees a job handle
 
*/
 
int distren_job_free(distren_job_t job);
 

	
 
/**
 
 * Destroys and frees a libdistren handle
 
 */
 
int distren_free(distren_t handle);
 

	
 
#endif
src/client/libdistren.c
Show inline comments
 
/*
 
  Copyright 2010 Nathan Phillip Brink, Ethan Zonca, Matt Orlando
 

	
 
  This file is a part of DistRen.
 

	
 
  DistRen is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU Affero General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  DistRen is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU Affero General Public License for more details.
 

	
 
  You should have received a copy of the GNU Affero General Public License
 
  along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
/*
 
  Implementation of distren_* functions from distren.h excluding distren_job_* functions.
 
 */
 

	
 
#include "common/config.h"
 
#include "common/options.h"
 
#include "common/protocol.h"
 
#include "common/remoteio.h"
 
#include "common/request.h"
 

	
 
#include "libdistren.h"
 

	
 
#include <errno.h>
 
#include <libgen.h>
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 

	
 
/**
 
 * Handle common cleanup actions for distren_init().
 
 */
 
static void distren_init_cleanup(distren_t distren);
 

	
 
int distren_init(distren_t *handle)
 
{
 
  int tmp;
 

	
 
  struct distren_request *req;
 
  void *data;
 

	
 
  if(!handle)
 
    return 1;
 

	
 
  *handle = malloc(sizeof(struct distren));
 
  if(!*handle)
 
    return 1;
 

	
 
  memset(*handle, 0, sizeof(struct distren));
 

	
 
  /* now the environment is ready for general use */
 
  if(_distren_getoptions(*handle))
 
    {
 
      fprintf(stderr, "error getting configuration\n");
 
      distren_init_cleanup(*handle);
 
      return 1;
 
    }
 

	
 
  tmp = remoteio_open_server(&(*handle)->rem,
 
			     (*handle)->options->remoteio,
 
			     (remoteio_read_handle_func_t)&libdistren_remoteio_read_handle,
 
			     *handle,
 
			     (remoteio_close_handle_func_t)&libdistren_remoteio_close_handle,
 
			     (*handle)->server);
 
  if(tmp)
 
    {
 
      fprintf(stderr, "error: unable to connect to server\n");
 

	
 
      distren_init_cleanup(*handle);
 
      return 1;
 
    }
 

	
 
  /* send off a DISTREN_REQUEST_VERSION to the server */
 
  tmp = distren_request_version(&req, &data, DISTREN_SERVERTYPE_CLIENT, PACKAGE_STRING);
 
  if(tmp)
 
    {
 
      fprintf(stderr, "error: unable to allocate request");
 

	
 
      distren_init_cleanup(*handle);
 
      return 1;
 
    }
 
  distren_request_send((*handle)->rem, req, data);
 
  distren_request_free_with_data(req, data);
 

	
 
  while((*handle)->rem
 
	&& (*handle)->state == DISTREN_STATE_VERSION)
 
      multiio_poll((*handle)->multiio, 500);
 

	
 
  if(!(*handle)->rem)
 
    {
 
      distren_init_cleanup(*handle);
 
      return 1;
 
    }
 

	
 
  return 0;
 
}
 

	
 
static void distren_init_cleanup(distren_t distren)
 
{
 
  if(distren->rem)
 
    remoteio_close(distren->rem);
 
  distren->rem = NULL;
 
  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)
 
{
 
  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)
 
{
 
  if(handle->rem)
 
    remoteio_close(handle->rem);
 
  free(handle);
 
  return 0;
 
}
src/client/libdistren.h
Show inline comments
 
/*
 
  Copyright 2010 Nathan Phillip Brink, Ethan Zonca, Matt Orlando
 

	
 
  This file is a part of DistRen.
 

	
 
  DistRen is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU Affero General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  DistRen is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU Affero General Public License for more details.
 

	
 
  You should have received a copy of the GNU Affero General Public License
 
  along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef LIBDISTREN_H_
 
#define LIBDISTREN_H_ 1
 

	
 
/*
 
  Private definitions for libdistren.
 
 */
 

	
 
#include "distren.h"
 

	
 
#include "common/multiio.h"
 
#include "common/options.h"
 
#include "common/remoteio.h"
 

	
 
#include <stdint.h>
 

	
 
enum distren_state
 
  {
 
    /**
 
     * client is waiting for a VERSION packet from the server.
 
     */
 
    DISTREN_STATE_VERSION,
 
    /**
 
     * We are waiting to be authenticated.
 
     */
 
    DISTREN_STATE_AUTH,
 
    DISTREN_STATE_NORMAL,
 
    DISTREN_STATE_UPLOADING,
 
  };
 

	
 
struct distren
 
{
 
  struct options_common *options;
 
  char *server;
 

	
 
  enum distren_state state;
 

	
 
  /*
 
   * If rem is NULL, we're not connected to the server. This is the
 
   * way to detect a communication error.
 
   */
 
  struct remoteio *rem;
 
  /*
 
   * for libdistren_remoteio_read_handle(): determine whether or not
 
   * we've passed through the server's hacky MOTD
 
   */
 
  short done_ad;
 

	
 
  /*
 
   * The servertype bitmask of the remote server.
 
   */
 
  uint32_t servertype;
 

	
 
  /* 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
 
{
 
  char *joburi;
 
};
 

	
 
/*
 
  functions
 
*/
 

	
 
/**
 
   Avoid poluting the public namespace until we fix visibility.
 
 */
 
#define _malloc _distren_malloc
 
/**
 
   All of libdistren should use this rather than malloc.h's malloc.
 
 */
 
void *_malloc(distren_t distren, size_t size);
 

	
 
/**
 
   Avoid poluting the public namespace until we fix visibility.
 
 */
 
#define _free _distren_free
 
/**
 
   All of libdisren should use this instead of malloc.h's free()
 
*/
 
void _free(distren_t distren, void *tofree);
 

	
 
/**
 
 * Sets up the distren handle with information garnered from
 
 * configuration files, etc. Uses the environment variable
 
 * DISTREN_CONFIG or the built-in default config file location.
 
 *
 
 * Also initializes multiio.
 
 */
 
int _distren_getoptions(distren_t handle);
 

	
 
/**
 
 * Unsets-up the distren handle with options loadable from a config file.
 
 *
 
 * Also unloads multiio.
 
 */
 
int _distren_loseoptions(distren_t handle);
 

	
 
/**
 
 * Handle newly read data.
 
 *
 
 * Matches remoteio_read_handle_func_t
 
 */
 
size_t libdistren_remoteio_read_handle(struct remoteio *rem, void *garbage, void *buf, size_t len, distren_t distren);
 

	
 
/**
 
 * React to a remoteio-based connection closing.
 
 *
 
 * Matches remoteio_close_handle_func_t
 
 */
 
void libdistren_remoteio_close_handle(void *garbage, distren_t distren);
 

	
 
#endif
 

	
src/common/protocol.h
Show inline comments
 
/*
 
  Copyright 2010 Nathan Phillip Brink
 

	
 
  This file is a part of DistRen.
 

	
 
  DistRen is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU Affero General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  DistRen is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU Affero General Public License for more details.
 

	
 
  You should have received a copy of the GNU Affero General Public License
 
  along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef DISTREN_PROTOCOL_H
 
#define DISTREN_PROTOCOL_H
 

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

	
 
/**
 
 * Server types:
 
 *
 
 * It is assumed that any client has at least the functionality of a
 
 * normal client (i.e., accepts all of the REQUIRED requests). These
 
 * bitmask values add to the types of requests one may make to a
 
 * server. After first connecting to a server or receiving a
 
 * connection, a client shall send a DISTREN_REQUEST_VERSION packet
 
 * describing its version.
 
 */
 
#define DISTREN_SERVERTYPE_SUBMIT (0x1)
 
#define DISTREN_SERVERTYPE_DISTRIBUTE (0x2)
 
#define DISTREN_SERVERTYPE_RENDER (0x4)
 
#define DISTREN_SERVERTYPE_CLIENT (0x8)
 

	
 
/**
 
 * This file defines the constants and structs that distren clients
 
 * and server use to converse.
 
 */
 

	
 

	
 
/**
 
 * List of request types and metadata.
 
 */
 
enum distren_request_type
 
  {
 
    /**
 
     * identifies the version of software being used by the sender and
 
     * tells if it is a client or server. Sends PACKAGE_STRING as well
 
     * as informing the other server what type of server this is.
 
     *
 
     * DATA: struct distren_request followed by PACKAGE_STRING. The length of PACKAGE_STRING must be no longer than 32 bytes.
 
     *
 
     * REQUIRED: ALL
 
    */
 
    DISTREN_REQUEST_VERSION = 1,
 
    /**
 
     * DATA: up to 32 bytes of a PING cookie
 
     *
 
     * REQUIRED: ALL
 
     */
 
    DISTREN_REQUEST_PING = 2,
 
    /**
 
     * DATA: up to 32 bytes copied from a received PING cookie
 
     *
 
     * REQUIRED: ALL
 
     */
 
    DISTREN_REQUEST_PONG = 3,
 
    /**
 
     * The data is the a reason describing why the one end is
 
     * disconnecting. The other end should respectfully close()
 
     * the socket or the sender will timeout shortly.
 
     *
 
     * DATA: A string describing the reason for the connection's
 
     * termination.
 
     *
 
     * REQUIRED: ALL
 
     */
 
    DISTREN_REQUEST_DISCONNECT = 4,
 

	
 
    /**
 
     * DATA: struct distren_request_submit
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_SUBMIT
 
     */
 
    DISTREN_REQUEST_SUBMIT = 5,
 

	
 
    /**
 
     * Inform the other party about a job.
 
     *
 
     * DATA: struct distren_request_jobinfo
 
     *
 
     * REQUIRED: ALL
 
     */
 
    DISTREN_REQUEST_JOBINFO = 6,
 

	
 
    /**
 
     * Request a DISTREN_REQUEST_JOBINFO
 
     *
 
     * DATA: struct distren_request_jobinfo_get
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_SUBMIT, DISTREN_SERVERTYPE_DISTRIBUTE
 
     */
 
    DISTREN_REQUEST_JOBINFO_GET = 7,
 

	
 
    /**
 
     * Command the other party to render a frame
 
     *
 
     * DATA: struct distren_request_frame_render
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_RENDER
 
     */
 
    DISTREN_REQUEST_FRAME_RENDER = 8,
 
    /**
 
     * Inform the receiver of the sender's state, such as frames being
 
     * rendered or jobs that need to be completed.
 
     *
 
     * DATA: struct distren_request_status
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_RENDER, DISTREN_SERVERTYPE_DISTRIBUTE, DISTREN_SERVERTYPE_CLIENT
 
     */
 
    DISTREN_REQUEST_STATUS = 9,
 

	
 
    /**
 
     * Request that the receiver send a DISTREN_REQUEST_FRAME_STATUS
 
     * packet. No data because the sending party might not beforehand
 
     * know what frames/jobs the receiving server is managing.
 
     *
 
     * DATA: (none)
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_RENDER
 
     */
 
    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
 
     * would be relayed by the DISTREN_SERVERTYPE_SUBMIT server to a
 
     * DISTREN_SERVERTYPE_DISTRIBUTE server so that other clients can
 
     * obtain the file from the distribution server. In this case, the
 
     * file's URL is already known.
 
     *
 
     * Of course, having this sort of functionality at all is where
 
     * the nasty security issues start coming into play :-D.
 
     *
 
     * DATA: struct distren_request_file_post followed by a maximum of 131072 bytes (128kB).
 
     *
 
     * 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
 
     * cURL-compatible URL) based on a distren file URL.
 
     *
 
     * DATA: struct distren_request_file_find
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_DISTRIBUTE
 
     */
 
    DISTREN_REQUEST_FILE_FIND = 12,
 
    DISTREN_REQUEST_FILE_FIND = 14,
 

	
 
    /**
 
     * Provide information about obtaining a file (such as a URL).
 
     *
 
     * DATA: struct distren_request_file
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_DISTRIBUTE
 
     */
 
    DISTREN_REQUEST_FILE = 13,
 
    DISTREN_REQUEST_FILE = 15,
 
  };
 

	
 
struct distren_request
 
{
 
  uint32_t magic;
 
  /* the length of the data associated with the packet excluding the header */
 
  uint32_t len;
 
  /** treat type as an enum distren_request_type using casting */
 
  uint32_t /* enum distren_request_type */ type;
 
};
 

	
 
#define DISTREN_REQUEST_VERSION_PACKAGE_STRING_LEN (32)
 
/**
 
 * A DISTREN_REQUEST_VERSION is started with a bitmask specification
 
 * of the DISTREN_SERVERTYPE_* values.
 
 */
 
struct distren_request_version
 
{
 
  uint32_t servertype;
 
  /* + 1 is for terminating NULL */
 
  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
 
 */
 
int distren_request_new(struct distren_request **req, uint32_t len, enum distren_request_type type);
 

	
 
struct remoteio;
 
/**
 
   Takes a struct distren_request and its associated data, allocates
 
   a new block of data to hold the whole packet, and packets the req
 
   header and data together.
 

	
 
   @param rem A remoteio handle to ship this packet off to
 
   @param req Something you initialized with distren_request_new(). You are responsible for distren_request_free()ing this yourself.
 
   @param data A chunk of data the size of req->len. You are responsible for free()ing this yourself.
 
   @return 0 on success and 1 on failure.
 
 */
 
int distren_request_send(struct remoteio *rem, struct distren_request *req, void *data);
 

	
 
/**
 
 * initializes and allocates request based on raw input data
 
 * which includes the headers of the request.
 
 *
 
 * @return 0 on success, 1 on failure
 
 */
 
int distren_request_new_fromdata(struct distren_request **req, void *data, size_t len);
 

	
 
/**
 
 * Parses requests in a way suitable to be called from a
 
 * remoteio_read_handle_func_t.
 
 *
 
 * If this function returns non-zero, you should handle the request
 
 * and then call this function again (after shortening len and
 
 * incrementing buf, of course).
 
 *
 
 * @param expectlen A pointer to a state variable that, if not NULL, can speed up this function. If you want to use this, you must set the variable to 0 before the first call and preserve the variable.
 
 * @param buf the input buffer.
 
 * @param len the length of buf.
 
 * @param req a pointer to where we should set NULL if we don't have a full request or where we store the address of a newly allocated request. You should call distren_request_free() on this.
 
 * @param req_data a pointer to where the request data pointer should be stored. This will just be set to a pointer in buf, so don't free() this.
 
 * @param err this will be set to 0 if there is no error and to 1 if there is an error and the connection should be closed. You must check this.
 
 * @return number of bytes that we have used from buf and that should be marked used.
 
 */
 
size_t distren_request_handle(size_t *expectlen,
 
			      void *buf,
 
			      size_t len,
 
			      struct distren_request **req,
 
			      void **req_data,
 
			      short *err);
 

	
 
/**
 
 * frees request
 
 */
 
int distren_request_free(struct distren_request *req);
 

	
 
/**
 
 * An implementation of remoteio_read_handle_func_t for use with remoteio.
 
 *
 
 * To use this handler, first initialize a struct distren_request_remoteio_data.
 
 * (to be continued... or not...? ;-) )
 
 */
 
/* size_t distren_request_remoteio_handle(struct remoteio *rem, void *generic_data, void *buf, size_t len, void *data); */
 

	
 
#endif
src/common/request.c
Show inline comments
 
/*
 
 * Copyright 2010 Nathan Phillip Brink
 
 *
 
 * This file is a part of DistRen.
 
 *
 
 * DistRen is free software: you can redistribute it and/or modify
 
 * it under the terms of the GNU Affero General Public License as published by
 
 * the Free Software Foundation, either version 3 of the License, or
 
 * (at your option) any later version.
 
 *
 
 * DistRen is distributed in the hope that it will be useful,
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 * GNU Affero General Public License for more details.
 
 *
 
 * You should have received a copy of the GNU Affero General Public License
 
 * along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
#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>
 

	
 
int distren_request_free_with_data(struct distren_request *req, void *data)
 
{
 
  free(data);
 
  return distren_request_free(req);
 
}
 

	
 
int distren_request_version(struct distren_request **req, void **data, uint32_t servertype, const char *package_string)
 
{
 
  struct distren_request_version *version;
 

	
 
  distren_request_new(req, sizeof(struct distren_request_version), DISTREN_REQUEST_VERSION);
 
  version = malloc(sizeof(struct distren_request_version));
 
  if(!version || !*req)
 
    {
 
      free(version);
 
      if(*req)
 
	distren_request_free(*req);
 
      return 1;
 
    }
 

	
 
  memset(version, 0, sizeof(struct distren_request_version));
 
  version->servertype = servertype;
 
  strncpy(version->package_string, package_string, DISTREN_REQUEST_VERSION_PACKAGE_STRING_LEN);
 

	
 
  *data = version;
 

	
 
  return 0;
 
}
 

	
 
int distren_request_parse_version(struct distren_request *req, void *data, struct distren_request_version *version)
 
{
 
  if(req->len < sizeof(struct distren_request_version))
 
    return 1;
 

	
 
  memcpy(version, data, sizeof(struct distren_request_version));
 
  /* there is space for another '\0' */
 
  version->package_string[DISTREN_REQUEST_VERSION_PACKAGE_STRING_LEN] = '\0';
 

	
 
  return 0;
 
}
 

	
 
int distren_request_poing(struct distren_request **req, void **data, short is_ping, const void *poing_cookie, size_t poing_data_len)
 
{
 
  enum distren_request_type type;
 

	
 
  if(is_ping)
 
    type = DISTREN_REQUEST_PING;
 
  else
 
    type = DISTREN_REQUEST_PONG;
 
  distren_request_new(req, poing_data_len, type);
 
  (*data) = malloc(poing_data_len);
 
  memcpy(*data, poing_cookie, poing_data_len);
 

	
 
  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
 
/*
 
 * Copyright 2010 Nathan Phillip Brink
 
 *
 
 * This file is a part of DistRen.
 
 *
 
 * DistRen is free software: you can redistribute it and/or modify
 
 * it under the terms of the GNU Affero General Public License as published by
 
 * the Free Software Foundation, either version 3 of the License, or
 
 * (at your option) any later version.
 
 *
 
 * DistRen is distributed in the hope that it will be useful,
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 * GNU Affero General Public License for more details.
 
 *
 
 * You should have received a copy of the GNU Affero General Public License
 
 * along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
#ifndef _DISTREN_REQUEST_H
 
#define _DISTREN_REQUEST_H
 

	
 
#include "common/protocol.h"
 

	
 
#include <openssl/sha.h>
 

	
 
/**
 
 * @file functions to initialize various requests that the server and
 
 * client may both use.
 
 */
 

	
 

	
 
/**
 
 * Free any request made with one of the functions below
 
 */
 
int distren_request_free_with_data(struct distren_request *req, void *data);
 

	
 
/**
 
 * Initialize a VERSION request.
 
 *
 
 * @param req pointer to where the poitner to the new req should be stored..
 
 * @param data pointer to where the newly allocated data's address should go.
 
 * @param servertype the ORing of different DISTREN_SERVERTYPE_* constants.
 
 * @param package_string the PACKAGE_STRING constant.
 
 */
 
int distren_request_version(struct distren_request **req, void **data, uint32_t servertype, const char *package_string);
 

	
 
/**
 
 * Parses a DISTREN_REQUEST_VERSION packet.
 
 *
 
 * @param req the request to parse.
 
 * @param data the request's data.
 
 * @param version where the result should be stored.
 
 * @return 0 on success, 1 if the packet is invalid (if the length of package_version is longer than 32-bytes, for example).
 
 */
 
int distren_request_parse_version(struct distren_request *req, void *data, struct distren_request_version *version);
 

	
 
/**
 
 * Initialize a PING or PONG request.
 
 *
 
 * @param data a place to allocate storage for the data associated with this request
 
 * @param is_ping 1 if this is a DISTREN_REQUEST_PING or 0 if this is a DISTREN_REQUEST_PONG
 
 * @param poing_cookie chocolate chip, chocolate chunk, or oatmeal chocolate chip
 
 * @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
 
/*
 
  Copyright 2010 Nathan Phillip Brink, Ethan Zonca, Matthew Orlando
 

	
 
  This file is a part of DistRen.
 

	
 
  DistRen is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU Affero General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  DistRen is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU Affero General Public License for more details.
 

	
 
  You should have received a copy of the GNU Affero General Public License
 
  along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
*/
 

	
 
/* This file contains the code which both processes (renders) jobs as a slave, and the code which distributes frames to slaves after receiving them from the client portion of the codebase. */
 

	
 
#include "common/config.h"
 

	
 
#include "distrenjob.h"
 
#include "listen.h"
 
#include "slavefuncs.h"
 
#include "mysql.h"
 

	
 
#include "common/asprintf.h"
 
#include "common/execio.h"
 
#include "common/options.h"
 
#include "common/protocol.h"
 
#include "common/request.h"
 

	
 
#include <arpa/inet.h>
 
#include <confuse.h>
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 
#include <sys/stat.h>
 
#include <sys/types.h>
 
#include <time.h>
 
#include <unistd.h>
 

	
 
#include <libxml/encoding.h>
 
#include <libxml/parser.h>
 
#include <libxml/tree.h>
 
#include <libxml/xmlmemory.h>
 
#include <libxml/xmlreader.h>
 
#include <libxml/xmlwriter.h>
 

	
 
/* ******************* Structs ************************ */
 
struct general_info
 
{
 
  struct distrenjob head;
 
  distrend_mysql_conn_t conn;
 

	
 
  struct distrend_config *config;
 

	
 
  struct
 
  {
 
    /** 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;
 

	
 
  int jobs_in_queue;
 
  unsigned int free_clients;
 
  unsigned int rendering_clients;
 
  unsigned int total_finished_jobs;
 
  unsigned int total_frames_rendered;
 
  unsigned int highest_jobnum;
 
  int hibernate;
 
  time_t timestamp;
 
  unsigned long total_render_power;
 
  unsigned long total_priority_pieces;
 
};
 

	
 

	
 
/* *********************************************
 
   Function Prototypes
 
   ********************************************* */
 

	
 
/* ************General Functions************* */
 
int distrend_do();
 
int distrend_do_config(int argc, char *argv[], struct distrend_config **config, multiio_context_t multiio);
 
int distrend_config_free(struct distrend_config *config);
 
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);
 
int import_general_info(struct general_info *general_info);
 
int update_xml_joblist(struct general_info *geninfo);
 

	
 
/* **************Test Functions**************** */
 
int interactiveTest(int test, multiio_context_t multiio, struct general_info *general_info);
 

	
 
/* **************** Main ********************* */
 
int main(int argc, char *argv[])
 
{
 
  /* Parse arguments */
 
  int counter;
 
  int test = 0; /*< Interactive mode if 1 */
 
  int tmp;
 
  struct general_info general_info;
 
  multiio_context_t multiio;
 

	
 
  enum clientstatus
 
  {
 
    CLIENTSTATUS_UNINITIALIZED = 0,
 
    CLIENTSTATUS_BUSY = 1,
 
    CLIENTSTATUS_IDLE = 2
 
  } clientstatus;
 

	
 
  fprintf(stderr, PACKAGE_STRING "\n\
 
Nathan Phillip Brink <binki@ohnopub.net>\n\
 
Ethan Zonca <ethanzonca@gmail.com>\n\
 
\n");
 

	
 
#ifdef HAVE_LIST_BRAG
 
  fprintf(stderr, "Using %s\n", list_brag);
 
#endif
 

	
 
  clientstatus = CLIENTSTATUS_UNINITIALIZED;
 
  // xmlinit();
 

	
 
  for(counter = 0; counter < argc; counter ++)
 
    {
 
      if(strcmp(argv[counter], "-h") == 0)
 
      {
 
    	  fprintf(stderr, "Usage: distrend [option] \nStarts the distrend server\n\t-h\tshow this help\n\t-t\tlaunches queue testing interface \n");
 
	  return 2;
 
      }
 

	
 
      else if(strcmp(argv[counter], "-t") == 0)
 
      {
 
    	  fprintf(stderr, "Entering into test mode...\n\n");
 
    	  test = 1;
 
      }
 
    }
 

	
 

	
 
  multiio = multiio_context_new();
 

	
 
  if(distrend_do_config(argc, argv, &general_info.config, multiio))
 
    return 1;
 

	
 
  /** preset paths */
 
  _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,
 
		  general_info.config->mysql_user,
 
		  general_info.config->mysql_host,
 
		  general_info.config->mysql_pass,
 
		  general_info.config->mysql_database) )
 
    {
 
      fprintf(stderr, "%s:%d: mysqlConnect() failed\n", __FILE__, __LINE__);
 
      fprintf(stderr, "don't test mysql stuff\n");
 
      interactiveTest(test, multiio, &general_info);
 
      return 1;
 
    }
 
  fprintf(stderr,"Finished connecting!\n");
 

	
 
  /** Execute test function */
 
  interactiveTest(test, multiio, &general_info);
 

	
 
  general_info.config->listens = distrend_listens_new(multiio, &general_info, general_info.config->options);
 
  if(!general_info.config->listens)
 
    {
 
      fprintf(stderr, "error initializing listens\n");
 
      return 1;
 
    }
 
  remoteio_generic_data_set(general_info.config->options->remoteio, general_info.config->listens);
 
  for(counter = 0; general_info.config->listen_ports[counter]; counter ++)
 
    {
 
      tmp = distrend_listen_add(general_info.config->listens, general_info.config->listen_ports[counter]);
 
      if(tmp)
 
	{
 
	  fprintf(stderr, "Error listening on port %d\n", general_info.config->listen_ports[counter]);
 
	  return 1;
 
	}
 
    }
 

	
 
  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;
 
  while(!general_info.config->die)
 
    {
 
      multiio_poll(multiio, 15000);
 

	
 
      tabletennis_serve(general_info.config->listens->tabletennis);
 

	
 
      /* Run the watchdog, @TODO: like every 10 mins or something */
 
      frame_watchdog(general_info.conn);
 
    }
 

	
 
  distrend_listen_free(general_info.config->listens);
 
  distrend_config_free(general_info.config);
 

	
 
  xmlcleanup();
 

	
 
  /** free() paths */
 
  free(general_info.files.geninfo);
 
  mysqlDisconnect(general_info.conn);
 

	
 
  return 0;
 
}
 

	
 
/* ********************** Functions ************************* */
 

	
 
int distrend_handle_version(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data)
 
{
 
  char *tmp_str;
 
  struct distren_request_version version;
 

	
 
  if(distren_request_parse_version(req, req_data, &version))
 
    {
 
      distrend_send_disconnect(client, "Invalid DISTREN_REQUEST_VERSION packet.");
 
      return 1;
 
    }
 

	
 
  if(client->state != DISTREND_CLIENT_PREVERSION)
 
    {
 
      distrend_send_disconnect(client, "You have already sent the VERSION command.");
 
      return 1;
 
    }
 
  if(!strncmp(PACKAGE_STRING, version.package_string, DISTREN_REQUEST_VERSION_PACKAGE_STRING_LEN))
 
    {
 
      /**
 
       * The client and I claim to be of the same version of distren :-D
 
       * Now we will mark the client as valid.
 
       *
 
       * We won't increment his time to live, though, because it shouldn't take
 
       * him that long to auth.
 
       */
 
      client->state = DISTREND_CLIENT_PREAUTH;
 
    }
 
  else
 
    {
 
      /**
 
       * The client claims to be of a different version of distren.
 
       * Now we will just send a disconnect packet and disconnect the client.
 
       */
 
      _distren_asprintf(&tmp_str, "You have tried to connect to a %s server when your client claims to be running %s. Bye ;-)\n", PACKAGE_STRING, version.package_string);
 
      if(tmp_str)
 
	{
 
	  distrend_send_disconnect(client, tmp_str);
 
	  free(tmp_str);
 
	}
 
      else
 
	distrend_send_disconnect(client, "Invalid PACKAGE_VERSION :-|.");
 
      return 1;
 
    }
 

	
 
  return 0;
 
}
 

	
 
/**
 
 * 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()
 
{
 
  return 0;
 
}
 

	
 
/* Grabs config info from confs */
 
int distrend_do_config(int argc, char *argv[], struct distrend_config **config, multiio_context_t multiio)
 
{
 
  unsigned int counter;
 

	
 
  cfg_opt_t myopts_listen[] =
 
    {
 
      CFG_SIMPLE_STR("type", NULL),
 
      CFG_SIMPLE_STR("path", NULL),
 
      CFG_SIMPLE_INT("port", NULL),
 
      CFG_END()
 
    };
 
  cfg_opt_t myopts[] =
 
    {
 
      CFG_SEC("listen",  /* this must be imported into struct listens (which must still be declared) */
 
          myopts_listen,
 
          CFGF_MULTI),
 
      CFG_SIMPLE_STR("datadir", NULL),
 
      CFG_STR_LIST("render_types", NULL, CFGF_NONE),
 
      CFG_SIMPLE_STR("mysql_user", NULL),
 
      CFG_SIMPLE_STR("mysql_host", NULL),
 
      CFG_SIMPLE_STR("mysql_pass", NULL),
 
      CFG_SIMPLE_STR("mysql_database", NULL),
 
      CFG_END()
 
    };
 

	
 
  cfg_t *cfg_listen;
 

	
 
  fprintf(stderr, "%s:%d: running config\n", __FILE__, __LINE__);
 
  *config = malloc(sizeof(struct distrend_config));
 
  myopts[1].simple_value = &(*config)->datadir;
 
  myopts[3].simple_value = &(*config)->mysql_user;
 
  myopts[4].simple_value = &(*config)->mysql_host;
 
  myopts[5].simple_value = &(*config)->mysql_pass;
 
  myopts[6].simple_value = &(*config)->mysql_database;
 

	
 
  if(options_init(argc, argv, &(*config)->mycfg, myopts, "daemon", &(*config)->options, multiio))
 
    return 1;
 

	
 
  /**
 
     grab listen blocks:
 
   */
 
  (*config)->listen_ports = malloc(sizeof(int) * (cfg_size((*config)->mycfg, "listen") + 1));
 
  for(counter = 0; counter < cfg_size((*config)->mycfg, "listen"); counter ++)
 
    {
 
      cfg_listen = cfg_getnsec((*config)->mycfg, "listen", counter);
 
      (*config)->listen_ports[counter] = cfg_getint(cfg_listen, "port");
 
    }
 
  (*config)->listen_ports[counter] = 0;
 

	
 
  fprintf(stderr, "using %s as datadir\n", (*config)->datadir);
 

	
 
  return 0;
 
}
 

	
 
int distrend_config_free(struct distrend_config *config)
 
{
 
  distrend_listen_free(config->listens);
 
  options_free(config->options);
 
  free(config->listen_ports);
 
  free(config);
 

	
 
  return 0;
 
}
 

	
 
/* ************************** XML Functions ************************* */
 

	
 
// writes the general_info.xml file which is a copy of the general_info structure
 
// except that it doesn't hold free_clients and rendering_clients
 
void update_general_info(struct general_info *geninfo)
 
{
 
  xmlTextWriterPtr writer;
 
  char *tmp;
 

	
 
  writer = xmlNewTextWriterFilename(geninfo->files.geninfo, 0);
 
  xmlTextWriterStartDocument(writer, NULL, "utf-8", NULL);
 

	
 
  xmlTextWriterStartElement(writer, (xmlChar*)"general_info");
 

	
 
  _distren_asprintf(&tmp, "%d", geninfo->jobs_in_queue);
 
  xmlTextWriterWriteElement(writer, (xmlChar*)"jobs_in_queue", (xmlChar*)tmp);
 
  free(tmp);
 

	
 
  _distren_asprintf(&tmp, "%d", geninfo->total_finished_jobs);
 
  xmlTextWriterWriteElement(writer, (xmlChar*)"total_finished_jobs", (xmlChar*)tmp);
 
  free(tmp);
 

	
 
  _distren_asprintf(&tmp, "%d", geninfo->total_frames_rendered);
 
  xmlTextWriterWriteElement(writer, (xmlChar*)"total_frames_rendered", (xmlChar*)tmp);
 
@@ -489,96 +762,97 @@ int interactiveTest(int test, multiio_co
 
  int32_t slaveKey = 1;
 
  jobnum_t jobKey = 0;
 
  int32_t frameNum = 0;
 
  int32_t newPriority = 0;
 
  int tmp = 0;
 

	
 
  fprintf(stderr,"Hello!\n");
 

	
 
  while(test == 1)
 
   {
 
     fprintf(stderr, "Welcome to DistRen Alpha Interactive Test Mode\n\n");
 
     fprintf(stderr, "\t1 \tGet a frame to render\n");
 
     fprintf(stderr, "\t2 \tChange job priority\n");
 
     fprintf(stderr, "\t3 \tSet frame finished\n");
 
     fprintf(stderr, "\t4 \tSet frame started\n");
 
     fprintf(stderr, "\t5 \tStart listener\n");
 
     fprintf(stderr, "\t0 \tQuit\n");
 

	
 
     scanf("%d", &command);
 

	
 
     switch(command)
 
     {
 
     case 1:
 
       fprintf(stderr,"Slave Key: ");
 
       scanf("%d", &slaveKey);
 
       fprintf(stderr, "Got frame: ");
 
       if(find_jobframe(geninfo->conn, slaveKey, &jobKey, &frameNum))
 
         fprintf(stderr,"No frames available to render!\n");
 
       else if(jobKey == -1)
 
         fprintf(stderr,"Slave %d has no render power!", slaveKey);
 
       else
 
         fprintf(stderr, "jobKey: %d, frameNum: %d\n",jobKey,frameNum);
 
       break;
 
     case 2:
 
       fprintf(stderr,"Job key: ");
 
       scanf("%d", &tmp);
 
       jobKey = tmp;
 

	
 
       fprintf(stderr,"New priority: ");
 
       scanf("%d", &tmp);
 
       newPriority = tmp;
 

	
 
       change_job_priority(geninfo->conn, jobKey, newPriority);
 
       fprintf(stderr,"Changed!");
 
       break;
 
     case 3:
 
       fprintf(stderr,"Slave Key: ");
 
       scanf("%d", &tmp);
 
       slaveKey = tmp;
 

	
 
       fprintf(stderr,"Job Key: ");
 
       scanf("%d", &tmp);
 
       jobKey = tmp;
 

	
 
       fprintf(stderr,"Frame Number: ");
 
       scanf("%d", &tmp);
 
       frameNum = tmp;
 

	
 
       finish_frame(geninfo->conn, slaveKey, jobKey, frameNum);
 
       fprintf(stderr,"Finished Frame!\n");
 
       break;
 
     case 4:
 
       fprintf(stderr,"Slave Key: ");
 
       scanf("%d", &tmp);
 
       slaveKey = tmp;
 

	
 
       fprintf(stderr,"Job Key: ");
 
       scanf("%d", &tmp);
 
       jobKey = tmp;
 

	
 
       fprintf(stderr,"Frame Number: ");
 
       scanf("%d", &tmp);
 
       frameNum = tmp;
 

	
 
       start_frame(geninfo->conn, slaveKey, jobKey, frameNum);
 
       fprintf(stderr,"Started Frame!\n");
 
       break;
 

	
 
     case 5:
 
       while(1)
 
	 {
 
	   multiio_poll(multiio, 15000);
 
	   tabletennis_serve(geninfo->config->listens->tabletennis);
 
	 }
 
       break;
 
       
 
     case 0:
 
       test = 0;
 
       break;
 
     default:
 
       fprintf(stderr, "Invalid input, please try again.\n");
 
       break;
 
     }
 
   }
 
  return 0;
 
}
 

	
src/server/distrend.h
Show inline comments
 
/*
 
  Copyright 2010 Nathan Phillip Brink
 

	
 
  This file is a part of DistRen.
 

	
 
  DistRen is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU Affero General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  DistRen is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU Affero General Public License for more details.
 

	
 
  You should have received a copy of the GNU Affero General Public License
 
  along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
*/
 

	
 
struct distrend_config;
 

	
 
#ifndef _DISTREN_DISTREN_H_
 
#define _DISTREN_DISTREN_H_
 

	
 
#include <confuse.h>
 

	
 
#include "listen.h"
 

	
 
struct distrend_config
 
{
 
  cfg_t *mycfg;
 
  struct options_common *options;
 
  struct distrend_listens *listens;
 
  int *listen_ports;
 
  char *datadir;
 

	
 
  int die;
 

	
 
  char *mysql_user;
 
  char *mysql_host;
 
  char *mysql_pass;
 
  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
 
/*
 
  Copyright 2010 Nathan Phillip Brink
 

	
 
  This file is a part of DistRen.
 

	
 
  DistRen is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU Affero General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  DistRen is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU Affero General Public License for more details.
 

	
 
  You should have received a copy of the GNU Affero General Public License
 
  along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "common/config.h"
 

	
 
#include "listen.h"
 

	
 
#include "common/protocol.h"
 
#include "common/remoteio.h"
 
#include "common/request.h"
 

	
 
#include <errno.h>
 
#include <list.h>
 
#include <netinet/in.h>
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 
#include <sys/types.h>
 
#include <poll.h>
 
#include <sys/socket.h>
 
#include <unistd.h>
 

	
 
/* local */
 

	
 
struct distrend_request_handler_info
 
{
 
  enum distren_request_type request_type;
 
  distrend_handle_request_func_t handler;
 
};
 

	
 
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);
 
static size_t distrend_listen_read_handle(struct remoteio *rem, struct distrend_listens *listens, void *buf, size_t len, struct distrend_client *client);
 
static void distrend_listen_remoteio_handle_close(struct distrend_listens *listens, struct distrend_client *client);
 

	
 
int listen_handle_accept(multiio_context_t multiio,
 
			 int fd,
 
			 short revent,
 
			 struct distrend_listens *listens,
 
			 int *port);
 
int listen_handle_error(multiio_context_t multiio,
 
			int fd,
 
			short revent,
 
			struct distrend_listens *listens,
 
			int *port);
 

	
 
/*** TO BE MOVED TO REMOTEIO */
 
int listen_handle_existence(multiio_context_t multiio,
 
			    int fd,
 
			    short revent,
 
			    struct distrend_listens *listens,
 
			    struct distrend_client *client);
 

	
 
struct distrend_listens *distrend_listens_new(multiio_context_t multiio,
 
					      struct general_info *geninfo,
 
					      struct options_common *opts)
 
{
 
  struct distrend_listens *listens;
 

	
 
  listens = malloc(sizeof(struct distrend_listens));
 
  if(!listens)
 
    return NULL;
 

	
 
  listens->request_handlers = list_init();
 
  if(!listens->request_handlers)
 
    {
 
      free(listens);
 
      return NULL;
 
    }
 

	
 
  listens->options = opts;
 
  listens->geninfo = geninfo;
 

	
 
  /* multiio */
 
  listens->multiio = multiio;
 

	
 
  /* tabletennis */
 
  listens->tabletennis = tabletennis_new(listens, 32, 16);
 

	
 
  /* This type is used for accepting connections with accept() */
 
  multiio_socket_type_register(multiio, &listens->socket_type);
 

	
 
  multiio_event_handler_register(multiio,
 
				 listens->socket_type,
 
				 POLLERR | POLLHUP | POLLNVAL,
 
				 (multiio_event_handler_func_t)&listen_handle_error,
 
				 listens);
 
  multiio_event_handler_register(multiio,
 
				 listens->socket_type,
 
				 POLLIN,
 
				 (multiio_event_handler_func_t)&listen_handle_accept,
 
				 listens);
 

	
 
  return listens;
 
}
 

	
 
int distrend_listen_add(struct distrend_listens *listens, int port)
 
{
 
  int tmp;
 
  int fd;
 
  int *saved_port;
 

	
 
  struct sockaddr_in6 sockaddr =
 
    {
 
      .sin6_family = AF_INET6,
 
      .sin6_port = 0,
 
      .sin6_flowinfo = 0,
 
      .sin6_addr = IN6ADDR_ANY_INIT,
 
      .sin6_scope_id = 0
 
    };
 

	
 
  sockaddr.sin6_port = htons(port);
 

	
 
  fd = socket(AF_INET6, SOCK_STREAM, 0);
 
  tmp = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
 
  if(tmp == -1)
 
    {
 
      perror("bind");
 
      close(fd);
 

	
 
      return 1;
 
    }
 

	
 
  tmp = listen(fd, 1);
 
  if(tmp == -1)
 
@@ -197,389 +198,393 @@ int listen_handle_error(multiio_context_
 
/**
 
 * an important thing to handle
 
 *
 
 * @deprecated to be replaced with table_tennis
 
 */
 
int listen_handle_existence(multiio_context_t multiio,
 
			    int fd,
 
			    short revent,
 
			    struct distrend_listens *listens,
 
			    struct distrend_client *client)
 
{
 
  /**
 
   * handle dead/closed sockets here!
 
   */
 
  fprintf(stderr, __FILE__ ":%d: handle dead/closed sockets here!\n", __LINE__);
 

	
 
  /**
 
   * Manage timing-out clients.
 
   */
 
  if(time(NULL) > client->cleanup_time)
 
    switch(client->state)
 
      {
 
      case DISTREND_CLIENT_PREVERSION:
 
	distrend_send_disconnect(client, "You have failed to present version information in a timely manner. Cya :-p");
 
	break;
 
      case DISTREND_CLIENT_PREAUTH:
 
	distrend_send_disconnect(client, "You have failed to present authentication information in a timely manner. Cya ;-)");
 
	break;
 

	
 
      case DISTREND_CLIENT_GOOD:
 
	/*
 
	 * pings should be managed with two queues sharing this struct:
 
	 *   struct pingpong_queue { struct distrend_client *client; time_t ping_time };
 
	 *
 
	 * - queue to_ping: contains queue of clients to send pings to
 
	 *   with the client whose ping time is earliest at the front
 
	 *
 
	 * - queue to_be_ponged: contains a queue of clients to clean up
 
	 *   if they haven't recieved pongs. client with earliest cleanup
 
	 *   time is at the front. If a PONG packet is received in time, the
 
	 *   cleanup_time is bumped but the queue is left alone. When the
 
	 *   queue's element is encountered, cleanup_time is checked and then
 
	 *   the client is readded to the to_ping queue.
 
	 *
 
	 * Each queue shall be eaten as time passes and continue forever
 
	 * in circularity.
 
	 *
 
	 * data structures.
 
	 * fun.
 
	 * impossible to explain in text.
 
	 * easy to think up.
 
	 * impossible to determine the workings of existing ones.
 
	 * and... when you need them, screenshot utilities just aren't available :-/.
 
	 */
 
	distrend_send_disconnect(client, "Ping timeout :-p");
 
	break;
 

	
 
      case DISTREND_CLIENT_BAD:
 
	fprintf(stderr, __FILE__ ":%d: aaarrrrgh!\n :-D\n", __LINE__);
 
	break;
 

	
 
      default:
 
	break;
 
      }
 
  return 0;
 
}
 

	
 
struct distrend_accept_client_proc_data
 
{
 
  struct distrend_listens *listens;
 
  time_t current_time;
 
};
 

	
 

	
 
/**
 
 * Handle new connections.
 
 */
 
int listen_handle_accept(multiio_context_t multiio,
 
			 int fd,
 
			 short revent,
 
			 struct distrend_listens *listens,
 
			 int *port)
 
   //int distrend_accept(struct distrend_listens *listens)//, struct distrend_clientset *clients, distrend_handle_request_t handlereq, void *handlereqdata)
 
 {
 
   struct distrend_client *newclient;
 

	
 
   int newclientsock;
 

	
 
   struct remoteio *rem;
 

	
 
   struct distren_request *req;
 
   void *data;
 

	
 
   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))
 
     {
 
       fprintf(stderr, "error allocating/adding client struct\n");
 
       return 1;
 
     }
 
   newclient->rem = rem;
 

	
 
   fprintf(stderr, "accepted new connection; fd=%d\n", newclientsock);
 

	
 
   /* tabletennis */
 
   tabletennis_add_client(listens->tabletennis, newclient);
 

	
 
   /**
 
    * For those using netcat/telnet to debug their internets.
 
    */
 
 #ifndef PACKAGE_URL
 
 #define PACKAGE_URL "http://ohnopub.net/distren/"
 
 #endif
 
 #define DISTREN_GREETING PACKAGE_STRING " " PACKAGE_URL " : Nathan Phillip Brink && Ethan Michael Zonca\n"
 
   /* using sizeof() - 1 because the sizeof() includes a NULL byte we want to ignore. */
 
   remoteio_write(newclient->rem, DISTREN_GREETING, sizeof(DISTREN_GREETING) - 1);
 

	
 
   /* send a DISTREN_REQUEST_VERSION immediately as per protocol */
 
   distren_request_version(&req, &data,
 
			   DISTREN_SERVERTYPE_SUBMIT
 
			   | DISTREN_SERVERTYPE_DISTRIBUTE
 
			   | DISTREN_SERVERTYPE_RENDER,
 
			   PACKAGE_STRING);
 
   distren_request_send(rem, req, data);
 
   distren_request_free_with_data(req, data);
 

	
 
   /*
 
   list_mvfront(listens->clients);
 
   newclient = list_curr(listens->clients);
 
   while(newclient)
 
     {
 
       if(newclient->state == DISTREND_CLIENT_DEAD)
 
	 {
 
	   distrend_listen_poll_deletefd(listens, &newclient->sock);
 
	   distrend_client_free(newclient);
 
	   list_remove_curr(listens->clients);
 
	   fprintf(stderr, "removed dead connection\n");
 
	 }
 
       list_mvnext(listens->clients);
 
   *//* provide for termination of this loop *//*
 
       if(newclient == list_curr(listens->clients))
 
	 newclient = NULL;
 
       else
 
	 newclient = list_curr(listens->clients);
 
     }
 
					       */
 
   return 0;
 
 }
 

	
 
 /**
 
  * Handle read events from remoteio, remoteio_read_handle_func_t.
 
  *
 
  * This func requires that someone called remoteio_generic_data_set(remoteio_opts, listens);
 
  *
 
  * @param client the client associated with this remoteio instance.
 
  */
 
size_t distrend_listen_read_handle(struct remoteio *rem, struct distrend_listens *listens, void *buf, size_t len, struct distrend_client *client)
 
 {
 
   struct distren_request *req;
 
   void *reqdata;
 

	
 
   size_t used_len;
 

	
 
   used_len = 0;
 
   /**
 
    * Manage input, etc.
 
    */
 
   if(client->expectlen == 0)
 
     {
 
       /* search out header from input so far */
 
       if(len > sizeof(struct distren_request))
 
	 {
 
	   if(distren_request_new_fromdata(&req, buf, len))
 
	     {
 
	       fprintf(stderr, "Error handling data from client (magic likely did not match), closing connection\n");
 

	
 
	       /*
 
		* yes, this is safe and legal... because of hackishness
 
		* in remoteio_close() ;-)
 
		*/
 
	      remoteio_close(rem);
 
	      return 1;
 
	    }
 
	  client->expectlen = req->len + sizeof(struct distren_request);
 
	  distren_request_free(req);
 
	}
 
    }
 
  if(client->expectlen
 
     && len >= client->expectlen)
 
    {
 
      if(distren_request_new_fromdata(&req, buf, len))
 
	{
 
	  if(client->state == DISTREND_CLIENT_PREAUTH)
 
	    remoteio_close(rem);
 
	  else
 
	    distrend_send_disconnect(client, "Protocol error.");
 

	
 
	  return 1;
 
	}
 

	
 
      /*
 
       * this really shouldn't happen... reparsing the same data with
 
       * distren_request_new_fromdata() a second time shouldn't yeild
 
       * a different req->len than it did before.
 
       */
 
      if(len - sizeof(struct distren_request) < req->len)
 
	{
 
	  fprintf(stderr, "Unexpected error handling some data from client\n");
 
	  distren_request_free(req);
 

	
 
	  /* but we should pay homage to W3C if the impossible happens */
 
	  distrend_send_disconnect(client, "HTTP/1.1 503 Internal Server Error");
 
	  return 1;
 
	}
 

	
 
      reqdata = malloc(req->len);
 
      if(!reqdata)
 
	{
 
	  fprintf(stderr, "OOM\n");
 

	
 
	  distren_request_free(req);
 
	  return 1;
 
	}
 
      memcpy(reqdata, ((void *)buf) + sizeof(struct distren_request), req->len);
 

	
 
      client->expectlen = 0;
 
      used_len = sizeof(struct distren_request) + req->len;
 

	
 
      distrend_dispatch_request(listens, rem, client, req, reqdata);
 
      free(reqdata);
 
      distren_request_free(req);
 

	
 
      /* I actually just used recursion in non-LISP code! :-D */
 
      return used_len + distrend_listen_read_handle(rem, listens, buf + used_len, len - used_len, client);
 
    }
 

	
 
  return used_len;
 
}
 

	
 

	
 
/**
 
 * Handle cleaning up after remoteio_close() has been called. This includes cleaning up the struct distrend_client and stuffs
 
 */
 
void distrend_listen_remoteio_handle_close(struct distrend_listens *listens, struct distrend_client *client)
 
{
 
  /*
 
   * remoteio handles removing itself from multiio for us. We just
 
   * have to clean up tabletennis and the struct itself.
 
   */
 

	
 
  tabletennis_del_client(listens->tabletennis, client);
 
  list_free(client->file_post_list, (list_dealloc_func_t)&distrend_client_file_post_free);
 

	
 
  free(client);
 
}
 

	
 
int distrend_listen_free(struct distrend_listens *listens)
 
{
 
  tabletennis_free(listens->tabletennis);
 
  fprintf(stderr, "%s:%d: I am a stub that needn't be implemented 'til later\n", __FILE__, __LINE__);
 

	
 
  return 1;
 
}
 
/**
 
   This is probably just NOT a placeholder for remotio
 
*/
 
void remotio_send_to_client(struct distrend_client *client, const char *msg, size_t len)
 
{
 
    fprintf(stderr, "%s:%d: STUB I should queue data for writing to a client.... or should I? :-p\n", __FILE__, __LINE__);
 
}
 

	
 
/**
 
 * Allocates and initializes a struct distrend_client.
 
 *
 
 *
 
 */
 
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;
 

	
 
  client = malloc(sizeof(struct distrend_client));
 
  if(!client)
 
    {
 
      fprintf(stderr, "OOM\n");
 

	
 
      return NULL;
 
    }
 
  client->state = state;
 
  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;
 
}
 

	
 
int distrend_client_write_request(struct distrend_client *client, const struct distren_request *req, const void *data)
 
{
 
  char *towrite;
 
  int ret;
 
  size_t msglen;
 

	
 
  msglen = sizeof(struct distren_request) + req->len;
 

	
 
  towrite = malloc(msglen);
 
  if(!towrite)
 
    {
 
      fprintf(stderr, "OOM\n");
 
      return 1;
 
    }
 

	
 
  memcpy(towrite, req, sizeof(struct distren_request));
 
  memcpy(towrite + sizeof(struct distren_request), data, req->len);
 

	
 
  ret = remoteio_write(client->rem, towrite, msglen);
 

	
 
  free(towrite);
 
  return ret;
 
}
 

	
 
/*
 
int distrend_client_read(struct distrend_client *client, char **toread, size_t *lenread)
 
{
 
  struct distrend_packet *packet;
 

	
 
  *lenread = 0;
 
  *toread = NULL;
 

	
 
  if(q_empty(client->inmsgs))
 
    return 1;
 

	
 
  packet = q_dequeue(client->inmsgs);
 
  *lenread = packet->len;
 
  *toread = packet->data;
 
  free(packet);
 

	
 
  return 0;
 
}
 
*/
 

	
 

	
 
int distrend_send_disconnect(struct distrend_client *client, const char *quit_msg)
 
{
 
  struct distren_request *req;
 

	
 
  distren_request_new(&req, strlen(quit_msg), DISTREN_REQUEST_DISCONNECT);
 
  distrend_client_write_request(client, req, quit_msg);
 
  distren_request_free(req);
 

	
 
  client->state = DISTREND_CLIENT_BAD;
 
  client->cleanup_time = time(NULL) + DISTREND_LISTEN_DISCONNECT_GRACE;
 

	
 
  return 0;
 
}
 

	
 
int distrend_listen_handler_add(struct distrend_listens *listens, enum distren_request_type type, distrend_handle_request_func_t handler)
 
{
 
  struct distrend_request_handler_info *handler_info;
 

	
 
  handler_info = malloc(sizeof(struct distrend_request_handler_info));
 
  if(!handler_info)
 
    return 1;
 

	
 
  handler_info->request_type = type;
 
  handler_info->handler = handler;
 
  list_insert_after(listens->request_handlers, handler_info, 0);
 

	
 
  return 0;
 
}
 

	
 
struct distrend_dispatch_request_data
 
{
 
  struct general_info *geninfo;
 
  struct distrend_client *client;
 
  struct distren_request *req;
 
  void *req_data;
 
};
 

	
 
/**
 
   traversal function for distrend_dispatch_request().
 
 */
 
int _distrend_dispatch_request_trav(struct distrend_dispatch_request_data *data, struct distrend_request_handler_info *handler_info)
 
{
 
  if(handler_info->request_type == data->req->type)
 
    (*handler_info->handler)(data->geninfo, data->client, data->req, data->req_data);
 

	
 
  return TRUE;
 
}
src/server/listen.h
Show inline comments
 
/*
 
  Copyright 2010 Nathan Phillip Brink
 

	
 
  This file is a part of DistRen.
 

	
 
  DistRen is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU Affero General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  DistRen is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU Affero General Public License for more details.
 

	
 
  You should have received a copy of the GNU Affero General Public License
 
  along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
/**
 
   @file listen provides the ability to set up a listening socket
 
   through multiio's poll() interface. This means that if the listen()-ish
 
   libc calls we use are incompatible with windows, we don't have to
 
   throw ifdefs all throughout src/common. We just put that code in the
 
   only place that needs it: in src/server.
 
 */
 

	
 
struct general_info;
 
struct distrend_listens;
 
struct distrend_client;
 

	
 
#ifndef _DISTREN_LISTEN_H
 
#define _DISTREN_LISTEN_H
 

	
 
#include "distrend.h"
 
#include "tabletennis.h"
 

	
 
#include "common/options.h"
 
#include "common/multiio.h"
 
#include "common/protocol.h"
 
#include "common/remoteio.h"
 
#include "common/request.h"
 

	
 
#include <queue.h>
 
#include <time.h>
 

	
 
/**
 
   How long a client has after connecting to send
 
   authentication information before his connection is cleaned
 
   up.
 
 */
 
#define DISTREND_LISTEN_AUTHTIME 32
 

	
 
/**
 
   How long a client has when in DISTREND_CLIENT_BAD before
 
   his connection is dropped. This grace time is intended so that
 
   the client will actually see his disconnect message instead of
 
   just having his connection reset.
 
 */
 
#define DISTREND_LISTEN_DISCONNECT_GRACE 8
 

	
 
enum distrend_client_state
 
  {
 
    /**
 
       The client hasn't yet given us its version.
 
     */
 
    DISTREND_CLIENT_PREVERSION,
 
    /**
 
       We don't yet know the client. It may only use authentication
 
       commands.
 
     */
 
    DISTREND_CLIENT_PREAUTH,
 
    /**
 
       The client is authenticated, etc.
 
     */
 
    DISTREND_CLIENT_GOOD,
 
    /**
 
       The client is queued to be disconnected. (This state exists
 
       so that the client at least has a chance to recieve its
 
       disconnect message/error before being dumped).
 
     */
 
    DISTREND_CLIENT_BAD,
 
  };
 

	
 
struct distrend_listens
 
{
 
  /* of type (struct distrend_request_handler_info) */
 
  list_t request_handlers;
 
  /* the data to pass on to all request handlers */
 
  struct general_info *geninfo;
 
  /* the distrend config */
 
  struct options_common *options;
 

	
 
  tabletennis_t tabletennis;
 

	
 
  /* of type (struct distrend_client)  (multiio stores a pointer per socket, we'll store each strut distrend_client as that pointer instead of using this list) */
 
  /* list_t clients; */
 

	
 
  /* the multiio context the listening interface should use/initialize */
 
  multiio_context_t multiio;
 
  /*
 
   * the socket type reserved for us, i.e., the listen()/accept()
 
   * socket type whose events we handle.
 
  */
 
  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
 
   to a client.
 
 */
 
struct distrend_client
 
{
 
  enum distrend_client_state state;
 

	
 
  /**
 
     The absolute time at which this client's entry in the client list will be
 
     expired, closed, and marked as dead so that it may be cleaned up. This is
 
     used to implement ping timeouts (if state == DISTREND_CLIENT_GOOD) and 
 
     disconnect message grace time (if state == DISTREND_CLIENT_BAD).
 
   */
 
  time_t cleanup_time;
 

	
 
  size_t inlen; /*< number of bytes waiting to be processed */
 
  size_t expectlen; /*< number of bytes that inlen has to be for a complete request to be had, 0 when waiting on header */
 

	
 
  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.
 
   */
 
  struct remoteio *rem;
 
};
 

	
 

	
 

	
 
/**
 
   A function signature that may be registered as a client
 
   request handler.
 

	
 
   @param client the client that sent the request.
 
   @param req the distren request header.
 
   @param data the message received from the client.
 
 */
 
typedef int(*distrend_handle_request_func_t)(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data);
 

	
 
/**
 
   Initializes the listens member of struct distrend_config.
 

	
 
   @param multiio the multiio context in which we should register a new socket type and insert records for clients who connect.
 
   @param geninfo general info to apss to the request handler.
 
   @return Must be free()d with distrend_listen_free();
 
*/
 
struct distrend_listens *distrend_listens_new(multiio_context_t multiio, struct general_info *geninfo, struct options_common *opts);
 

	
 
/**
 
   Adds a socket and configures it to listen().
 

	
 

	
 
   @param listens The handle for this set of listens, obtained via distrend_listen_init().
 
 */
 
int distrend_listen_add(struct distrend_listens *listens, int port);
 

	
 
/**
 
 * Register a request handler with the listener.
 
 *
 
 * @param config distrend's configuration
 
 * @param type the request type for which this handler should be called
 
 * @param handler the handler to call when a request of type type is received.
 
 */
 
int distrend_listen_handler_add(struct distrend_listens *listens, enum distren_request_type type, distrend_handle_request_func_t handler);
 

	
 
/**
 
 * cleans listening sockets/frees main struct. Unnecessary for a working server, currently a stub.
 
 */
 
int distrend_listen_free(struct distrend_listens *listens);
 

	
 
/**
 
   writes request to client.
 
   @param client client to write to
 
   @param req the request struct. caller must free this.
 
   @param data the data of the request which is req->len bytes long. caller must free this.
 
 */
 
int distrend_client_write_request(struct distrend_client *client, const struct distren_request *req, const void *data);
 

	
 
/**
 
   This is probably just NOT a placeholder for remotio
 
*/
 
void remotio_send_to_client();
 

	
 
/**
 
 * Queue a DISTREN_REQUEST_DISCONNECT and prepare a client
 
 * to be disconnected.
 
 */
 
int distrend_send_disconnect(struct distrend_client *client, const char *quit_msg);
 

	
 

	
 
#endif
0 comments (0 inline, 0 general)