Changeset - eb391af42d62
[Not reviewed]
default
0 13 4
Nathan Brink (binki) - 15 years ago 2010-07-21 21:18:20
ohnobinki@ohnopublishing.net
Non-working tabletennis, better poll() resiliency, some fixups to the slave, etc.
16 files changed with 710 insertions and 91 deletions:
0 comments (0 inline, 0 general)
Makefile.am
Show inline comments
 
ACLOCAL_AMFLAGS = -I m4
 

	
 

	
 
AM_CPPFLAGS = -DSYSCONFDIR='"$(sysconfdir)"' \
 
	-DLOCALSTATEDIR='"$(localstatedir)"' \
 
	-I$(top_srcdir)/src
 
AM_CFLAGS = $(DISTLIBS_CFLAGS)
 
LIBS = $(DISTLIBS_LIBS)
 
LDADD = libdistrencommon.la
 

	
 
bin_PROGRAMS = 
 
if ENABLE_SERVER
 
bin_PROGRAMS += distrend distrenslave distrensimpleslave
 
endif
 

	
 
pkglib_LTLIBRARIES = libdistrencommon.la
 

	
 
# libdistrencommon.la:
 
libdistrencommon_la_SOURCES = src/common/asprintf.c src/common/asprintf.h \
 
	src/common/execio.c src/common/execio.h \
 
	src/common/misc.c src/common/misc.h \
 
	src/common/multiio.c src/common/multiio.h \
 
	src/common/options.c src/common/options.h \
 
	src/common/protocol.c src/common/protocol.h \
 
	src/common/remoteio.h \
 
	src/common/remoteio.c src/common/libremoteio.h
 
	src/common/remoteio.c src/common/libremoteio.h \
 
	src/common/request.c src/common/request.h
 
#see http://sources.redhat.com/autobook/autobook/autobook_91.html
 
# either increase the revision number or the interface number each release!
 
libdistrencommon_la_LDFLAGS = $(AM_LDFLAGS) -version-info 0:0:0
 

	
 
# shared server sources:
 
SERVER_SOURCES = src/server/slavefuncs.c \
 
	src/server/slavefuncs.h \
 
	src/server/distrenjob.c \
 
	src/server/distrenjob.h
 
SERVER_SOURCES = \
 
	src/server/slavefuncs.c	src/server/slavefuncs.h \
 
	src/server/distrenjob.c	src/server/distrenjob.h
 
# distrend:
 
distrend_CFLAGS = $(AM_CFLAGS) $(MYSQL_CFLAGS)
 
distrend_LDFLAGS = $(AM_LDFLAGS) $(MYSQL_LDFLAGS)
 
distrend_SOURCES = $(SERVER_SOURCES) \
 
	src/server/distrend.c \
 
	src/server/distrend.h \
 
	src/server/user_mgr.c \
 
	src/server/user_mgr.h \
 
	src/server/listen.h \
 
	src/server/listen.c \
 
	src/server/mysql.h \
 
	src/server/mysql.c
 
	src/server/distrend.c	src/server/distrend.h \
 
	src/server/listen.h	src/server/listen.c \
 
	src/server/mysql.h	src/server/mysql.c \
 
	src/server/tabletennis.c	src/server/tabletennis.h \
 
	src/server/user_mgr.c	src/server/user_mgr.h
 

	
 
distrend_LDADD = libdistrencommon.la
 
# distrenslave:
 
distrenslave_SOURCES = $(SERVER_SOURCES) \
 
	src/server/slave.c
 
distrenslave_LDADD = libdistrencommon.la
 

	
 
# distrensimpleslave:
 
distrensimpleslave_SOURCES = $(SERVER_SOURCES) \
 
        src/server/simpleslave.c
 
distrensimpleslave_LDADD = libdistrencommon.la
 

	
 

	
 
# configuration files:
 
dist_sysconf_DATA = etc/distrencommon.conf
 
nodist_sysconf_DATA = etc/distrendaemon.conf \
 
	etc/distrenslave.conf
 
EXTRA_DIST = etc/distrendaemon.conf.in \
 
	etc/distrenslave.conf.in
 

	
 

	
 
# tests
 
TESTS=test/check_execio test/check_asprintf
 
check_PROGRAMS=$(TESTS)
 

	
 
#check_execio_LIBS = $(CHECK_LIBS)
 
#check_asprintf_LIBS = $(CHECK_LIBS)
 
test_check_execio_LDADD = $(CHECK_LIBS) libdistrencommon.la
 
test_check_asprintf_LDADD = $(CHECK_LIBS) libdistrencommon.la
 
test_check_execio_CFLAGS = $(AM_CFLAGS) $(CHECK_CFLAGS)
 
test_check_asprintf_CFLAGS = $(AM_CFLAGS) $(CHECK_CFLAGS)
 

	
src/common/libremoteio.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_LIBREMOTEIO_H
 
#define _DISTREN_LIBREMOTEIO_H
 

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

	
 
#include <confuse.h>
 
#include <list.h>
 
#include <queue.h>
 

	
 
/**
 
  private declarations for remoteio, to be included by options.c and remoteio.c
 
 */
 

	
 
enum remoteio_method
 
  {
 
    REMOTEIO_METHOD_SSH = 0,
 
#ifndef WINDOWS
 
    REMOTEIO_METHOD_UNIX = 1,
 
#endif
 
    REMOTEIO_METHOD_TCP = 2,
 
    /*    REMOETIO_METHOD_XMLRPC */ /*< maybe someday */
 
    /** method for the remoteio_open_socket() function, where we don't call open() ourselves: */
 
    REMOTEIO_METHOD_SOCKET = 3,
 
    REMOTEIO_METHOD_MAX = 4 /*< This is a number used to check the consitency of remoteio_server structs */
 
  };
 

	
 
struct remoteio_server
 
{
 
  char *name; /*< The friendly named passed to remoteio_open() */
 
  char *hostname;
 
  char *username;
 
  enum remoteio_method method;
 
  unsigned int types; /*< See ``Server types'' in protocol.h */
 
};
 

	
 
struct remoteio_opts
 
{
 
  char *ssh_command;
 
  list_t servers;  /* type: (struct remoteio_server *) */
 
  /* store the multiio context for general use */
 
  multiio_context_t multiio;
 
  /* store remoteio's socket_type */
 
  multiio_socket_type_t socket_type;
 

	
 
  /* an argument for the remoteio_read_handle_func_t */
 
  void *generic_handler_data;
 
};
 

	
 
/**
 
 * Used to describe the nature of the data stored in the
 
 * outbound and message queues that power remoteio_write()
 
 * and remoteio_read() (?).
 
 */
 
struct remoteio_packet
 
{
 
  size_t len;
 
  char *data;
 
};
 

	
 

	
 
struct remoteio
 
{
 
  enum remoteio_method method;
 
  struct remoteio_opts *opts;
 
  struct execio *execio;
 
#ifndef WINDOWS
 
  int sock;
 
#endif
 

	
 
  remoteio_read_handle_func_t read_handler;
 
  /* for the read_handler */
 
  void *read_handler_data;
 
  /* so that read_handler_data can be cleaned up */
 
  remoteio_close_handle_func_t close_handler;
 

	
 
  /**
 
   * Store a buffer of data waiting to be processed.
 
   */
 
  struct remoteio_packet inbuf;
 

	
 
  /**
 
   * Provide the asynchronosity abstraction by queuing outgoing messages.
 
   */
 
  queue_t outmsgs;
 

	
 
  /**
 
   * This is disappointingly hacky. If this variable is 0, then
 
   * remoteio_close() will act normal. If set to 1, then
 
   * remoteio_close() will not actually free this struct but instead
 
   * increment this variable to 2. This is so that read_handler can
 
   * call remoteio_close() without segfaulting us.
 
   */
 
  short careful_free;
 
};
 

	
 

	
 
/* lookup table */
 
typedef int remoteio_open_func_t(struct remoteio *rem, struct remoteio_server *server);
 
typedef int remoteio_read_func_t(struct remoteio *rem, void *buf, size_t len, size_t *bytesread);
 
typedef int remoteio_write_func_t(struct remoteio *rem, void *buf, size_t len, size_t *bytesread);
 
typedef int remoteio_close_func_t(struct remoteio *rem);
 

	
 
struct remoteio_method_funcmap
 
{
 
  enum remoteio_method method;
 

	
 
  remoteio_open_func_t *open_func;
 
  remoteio_read_func_t *read_func;
 
  remoteio_write_func_t *write_func;
 
  remoteio_close_func_t *close_func;
 

	
 
  char *name; /*< used for error messages */
 
};
 

	
 
int remoteio_config(cfg_t *cfg, struct remoteio_opts *opts);
 

	
 
#endif
src/common/multiio.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/multiio.h"
 

	
 
#include <list.h>
 
#include <fcntl.h>
 
#include <malloc.h>
 
#include <poll.h>
 
#include <stdio.h>
 
#include <string.h>
 

	
 
struct multiio_socket_info
 
{
 
  /* the type of socket */
 
  multiio_socket_type_t socket_type;
 
  /* to be passed to the socket handler */
 
  void *socket_data;
 
};
 

	
 
struct multiio_socket_type_info
 
{
 
  /* type: struct multiio_socket_type_handler_info */
 
  list_t type_handlers;
 
};
 

	
 
/*
 
  The basic concept is that pollfds is an array. Thus, we have to
 
  have another array reflecting that array to hold socket-specific
 
  information.
 
 */
 
struct multiio_context
 
{
 
  /* the array passed to poll() */
 
  struct pollfd *pollfds;
 
  /* the number of entries pollfds could hold */
 
  nfds_t pollfds_len;
 
  /* the number of entries that pollfds does hold */
 
  nfds_t nfds;
 

	
 
  /*
 
    an array whose order mirrors pollfds containing information
 
    about the fd mentioned in pollfds.
 
  */
 
  struct multiio_socket_info *socket_infos;
 

	
 
  /* the number of socket types registered (equivilent to the type ID of the next registered type) */
 
  multiio_socket_type_t num_socket_types;
 
  /* the information about each individual socket type keyed by the type ID */
 
  struct multiio_socket_type_info *socket_types;
 
};
 

	
 
/*
 
  stores information about a handler associated with a socket type/class.
 
 */
 
struct multiio_socket_type_handler_info
 
{
 
  /* the poll() event to which this handler responds */
 
  short event;
 
  /* the handler function to call when the event is matched */
 
  multiio_event_handler_func_t handler;
 
  /* the pointer passed to multiio_event_handler_register() */
 
  void *handler_data;
 
};
 

	
 
/**
 
   Finds the index in the context->pollfds array of a particular socket.
 

	
 
   @param context the multiio context
 
   @param fd the socket to search for
 
   @param index a pointer which will be filled with the index of the socket if found
 
   @return 0 if the pollfds entry is found, 1 if the entry is not found
 
 */
 
int multiio_pollfd_index_by_fd(const multiio_context_t context, int fd, size_t *index);
 

	
 
multiio_context_t multiio_context_new()
 
{
 
  struct multiio_context *context;
 

	
 
  context = malloc(sizeof(struct multiio_context));
 
  if(!context)
 
    return NULL;
 

	
 
  context->pollfds = malloc(sizeof(struct pollfd) * 2);
 
  if(!context->pollfds)
 
    {
 
      free(context);
 
      return NULL;
 
    }
 
  context->pollfds_len = 2;
 
  context->nfds = 0;
 
  context->socket_infos = malloc(sizeof(struct multiio_socket_info) * 2);
 

	
 
  context->num_socket_types = 0;
 
  context->socket_types = NULL;
 

	
 
  if(!context->pollfds
 
     || !context->socket_infos)
 
    {
 
      free(context->pollfds);
 
      free(context->socket_infos);
 
      free(context);
 

	
 
      return NULL;
 
    }
 

	
 
  return context;
 
}
 

	
 
int multiio_context_free(multiio_context_t context)
 
{
 
  size_t counter;
 
  /*
 
    clean up pollfds and socket_infos
 
   */
 
  free(context->pollfds);
 
  free(context->socket_infos);
 

	
 
  /*
 
    wipe up the socket_types
 
   */
 
  for(counter = 0; counter < context->num_socket_types; counter ++)
 
    list_free(context->socket_types[counter].type_handlers, LIST_DEALLOC);
 
  free(context->socket_types);
 

	
 
  /*
 
    bye!
 
   */
 
  free(context);
 

	
 
  return 0;
 
}
 

	
 
struct multiio_poll_travinfo
 
{
 
  multiio_context_t multiio;
 
  struct multiio_socket_info *socket_info;
 
  struct pollfd *pollfd;
 
};
 
/**
 
   list traverser for multiio_poll()
 
 */
 
int multiio_poll_invoke_handlers(struct multiio_poll_travinfo *travinfo, struct multiio_socket_type_handler_info *handler_info)
 
{
 
  short event;
 

	
 
  event = travinfo->pollfd->revents & handler_info->event;
 
  if(!event)
 
    return TRUE;
 

	
 
  handler_info->handler(travinfo->multiio,
 
			travinfo->pollfd->fd,
 
			event,
 
			handler_info->handler_data,
 
			travinfo->socket_info->socket_data);
 

	
 
  return TRUE;
 
}
 

	
 
int multiio_poll(multiio_context_t context)
 
{
 
  size_t counter;
 
  struct multiio_poll_travinfo travinfo;
 

	
 
  poll(context->pollfds, context->nfds, -1);
 
  int ret;
 

	
 
  ret = poll(context->pollfds, context->nfds, -1);
 
  if(ret == -1)
 
    {
 
      perror("poll");
 
    }
 

	
 
  for(counter = 0; counter < context->nfds; counter ++)
 
    if(context->pollfds[counter].revents)
 
      {
 
	travinfo.multiio = context;
 
	travinfo.socket_info = &context->socket_infos[counter];
 
	travinfo.pollfd = &context->pollfds[counter];
 
	list_traverse(context->socket_types[context->socket_infos[counter].socket_type].type_handlers,
 
		      &travinfo,
 
		      (list_traverse_func_t)&multiio_poll_invoke_handlers,
 
		      LIST_FRNT | LIST_SAVE);
 

	
 
	context->pollfds[counter].revents = 0;
 
      }
 

	
 
  return 0;
 
}
 

	
 
int multiio_socket_type_register(multiio_context_t context, multiio_socket_type_t *new_type)
 
{
 
  struct multiio_socket_type_info *new_socket_types;
 

	
 
  new_socket_types = malloc(sizeof(struct multiio_socket_type_info) * (context->num_socket_types + 1));
 
  if(!new_socket_types)
 
    return 1;
 

	
 
  *new_type = context->num_socket_types;
 
  new_socket_types[*new_type].type_handlers = list_init();
 
  if(!new_socket_types[*new_type].type_handlers)
 
    {
 
      free(new_socket_types);
 
      return 2;
 
    }
 

	
 

	
 
  if(context->num_socket_types)
 
    {
 
      memcpy(new_socket_types, context->socket_types, sizeof(struct multiio_socket_type_info) * context->num_socket_types);
 
      free(context->socket_types);
 
    }
 

	
 
  context->socket_types = new_socket_types;
 
  context->num_socket_types ++;
 

	
 
  return 0;
 
}
 

	
 
int multiio_event_handler_register(multiio_context_t context, multiio_socket_type_t socket_type, short event, multiio_event_handler_func_t handler_func, void *handler_data)
 
{
 
  struct multiio_socket_type_handler_info handler_info;
 

	
 
  if(socket_type >= context->num_socket_types)
 
    return 1;
 

	
 
  if(!event)
 
    return 2;
 

	
 
  handler_info.event = event;
 
  handler_info.handler = handler_func;
 
  handler_info.handler_data = handler_data;
 

	
 
  list_insert_after(context->socket_types[socket_type].type_handlers, &handler_info, sizeof(struct multiio_socket_type_handler_info));
 

	
 
  return 0;
 
}
 

	
 
int multiio_socket_add(multiio_context_t context, int fd, multiio_socket_type_t socket_type, void *socket_data, short events)
 
{
 
  struct pollfd *pollfds;
 
  struct multiio_socket_info *socket_infos;
 
  size_t new_pollfds_len;
 

	
 
  int fdstatus;
 
  int tmp;
 

	
 
  fdstatus = fcntl(fd, F_GETFL);
 
  fdstatus |= O_NONBLOCK;
 
  tmp = fcntl(fd, F_SETFL, fdstatus);
 
  if(tmp == -1)
 
    {
 
      perror("fcntl");
 
      return 1;
 
    }
 

	
 
  if(socket_type >= context->num_socket_types)
 
    return 1;
 

	
 
  /**
 
     extend sockfds array
 
   */
 
  if(context->nfds >= context->pollfds_len)
 
    {
 
      new_pollfds_len = (context->pollfds_len + 1) * 2;
 

	
 
      pollfds = malloc(sizeof(struct pollfd) * new_pollfds_len);
 
      socket_infos = malloc(sizeof(struct multiio_socket_info) * new_pollfds_len);
 

	
 
      if(!pollfds
 
	 || !socket_infos)
 
	return 1;
 

	
 
      memcpy(pollfds, context->pollfds, sizeof(struct pollfd) * context->pollfds_len);
 
      memcpy(socket_infos, context->socket_infos, sizeof(struct multiio_socket_info) * context->pollfds_len);
 

	
 
      free(context->pollfds);
 
      free(context->socket_infos);
 

	
 
      context->pollfds = pollfds;
 
      context->socket_infos = socket_infos;
 
      context->pollfds_len = new_pollfds_len;
 
    }
 

	
 
  pollfds = context->pollfds;
 
  socket_infos = context->socket_infos;
 

	
 
  memset(&pollfds[context->nfds], 0, sizeof(struct pollfd));
 
  pollfds[context->nfds].fd = fd;
 
  pollfds[context->nfds].events = events;
 
  socket_infos[context->nfds].socket_type = socket_type;
 
  socket_infos[context->nfds].socket_data = socket_data;
 

	
 
  context->nfds ++;
 

	
 
  return 0;
 
}
 

	
 
int multiio_socket_del(multiio_context_t context, int fd)
 
{
 
  size_t index;
 
  int tmp;
 

	
 
  tmp = multiio_pollfd_index_by_fd(context, fd, &index);
 
  if(tmp)
 
    return 1;
 

	
 
  /**
 
   * Special case: one left, if we tried filling in holes... segfault ;-)
 
   */
 
  if(context->nfds == 1)
 
    {
 
      context->nfds --;
 
      return 0;
 
    }
 

	
 
  /**
 
   * For now we'll unconditionally fill in holes.
 
   * In fact, it looks simple enough that this method
 
   * may be what we always use ;-).
 
   */
 
  memcpy(&context->pollfds[index], &context->pollfds[context->nfds - 1], sizeof(struct pollfd));
 
  memcpy(&context->socket_infos[index], &context->socket_infos[context->nfds - 1], sizeof(struct multiio_socket_info));
 
  context->nfds --;
 

	
 
  return 0;
 
}
 

	
 
int multiio_socket_event_enable(multiio_context_t context, int fd, short event)
 
{
 
  size_t index;
 
  int tmp;
 

	
 
  tmp = multiio_pollfd_index_by_fd(context, fd, &index);
 
  if(tmp)
 
    return 1;
 

	
 
  context->pollfds[index].events |= event;
 

	
 
  return 0;
 
}
 

	
 
int multiio_socket_event_disable(multiio_context_t context, int fd, short event)
 
{
 
  size_t index;
 
  int tmp;
 

	
 
  tmp = multiio_pollfd_index_by_fd(context, fd, &index);
 
  if(tmp)
 
    return 1;
 

	
 
  context->pollfds[index].events &= ~event;
 

	
 
  return 0;
 
}
 

	
 

	
 

	
 
int multiio_pollfd_index_by_fd(const multiio_context_t context, int fd, size_t *index)
 
{
 
  size_t counter;
 

	
 
  for(counter = 0; counter < context->nfds; counter ++)
 
    if(context->pollfds[counter].fd == fd)
 
      {
 
	*index = counter;
 
	return 0;
 
      }
 

	
 
  return 1;
 
}
src/common/protocol.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 "protocol.h"
 
#include "remoteio.h"
 

	
 
#include <malloc.h>
 
#include <stdio.h>
 
#include <string.h>
 

	
 
#define DISTREN_REQUEST_MAGIC (0x32423434)
 
#define DISTREN_REQUEST_MAGIC ((uint32_t)0x32423434)
 

	
 
int distren_request_new(struct distren_request **req, uint32_t len, enum distren_request_type type)
 
{
 
  struct distren_request *newreq;
 

	
 
  newreq = malloc(sizeof(struct distren_request));
 
  if(!newreq)
 
    {
 
      (*req) = NULL;
 
      return 1;
 
    }
 

	
 
  newreq->magic = DISTREN_REQUEST_MAGIC;
 
  newreq->len = len;
 
  newreq->type = type;
 

	
 
  (*req) = newreq;
 
  return 0;
 
}
 

	
 
int distren_request_send(struct remoteio *rem, struct distren_request *req, void *data)
 
{
 
  void *packet;
 
  size_t len;
 
  int write_err;
 

	
 
  if(req->magic != DISTREN_REQUEST_MAGIC)
 
    fprintf(stderr, "distren_request_send got a bad req\n");
 

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

	
 
  packet = malloc(len);
 
  if(!packet)
 
    {
 
      fprintf(stderr, "Error allocating memory for packet\n");
 
      return 1;
 
    }
 
  memcpy(packet, req, sizeof(struct distren_request));
 
  memcpy(packet + sizeof(struct distren_request), data, req->len);
 

	
 
  write_err = remoteio_write(rem, packet, len);
 

	
 
  free(packet);
 

	
 
  return 0;
 
}
 

	
 
int distren_request_new_fromdata(struct distren_request **req, void *data, size_t len)
 
{
 
  struct distren_request *newreq;
 

	
 
  if(len < sizeof(struct distren_request))
 
    return 1;
 

	
 
  if( ((struct distren_request *)data)->magic != DISTREN_REQUEST_MAGIC )
 
    {
 
      fprintf(stderr, "packet doesn't match magic stuffs\n");
 
      return 1;
 
    }
 

	
 
  newreq = malloc(sizeof(struct distren_request));
 
  if(!newreq)
 
    {
 
      fprintf(stderr, "OOM\n");
 
      return 1;
 
    }
 

	
 
  memcpy(newreq, data, sizeof(struct distren_request));
 
  (*req) = newreq;
 
  return 0;
 
}
 

	
 
int distren_request_free(struct distren_request *req)
 
{
 
  free(req);
 
  return 0;
 
}
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 <stddef.h>
 
#include <stdint.h>
 

	
 
/**
 
   Server types:
 
 */
 
#define DISTREN_SERVERTYPE_SUBMIT (0x1)
 
#define DISTREN_SERVERTYPE_DISTRIBUTE (0x2)
 
#define DISTREN_SERVERTYPE_RENDER (0x4)
 

	
 
/**
 
   This file defines the constants and structs that the client uses to talk to the server and that the servers use to talk to eachother.
 
 */
 

	
 
/**
 
   generic, shared requests
 
 */
 
enum distren_request_type
 
  {
 
    /**
 
       identifies the version of software being
 
       used by the sender and tells if it is a client or server.
 
       Just send PACKAGE_STRING.
 
    */
 
    DISTREN_REQUEST_VERSION = 1,
 
    DISTREN_REQUEST_PING = 2,
 
    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.
 
     */
 
    DISTREN_REQUEST_DISCONNECT = 4,
 

	
 
    /**
 
       client->server only requests
 
    */
 
    DISTREN_REQUEST_SUBMIT = 5,
 

	
 
    /**
 
       anything->server requests
 
     */
 
    DISTREN_REQUEST_JOBINFO = 6, /*< retrieves information about a job based on its number */
 

	
 
    /**
 
       server->anything
 
     */
 
    DISTREN_REQUEST_JOBINFO_RESPONSE = 7, /*< returns information about a job */
 

	
 
    /**
 
       server->server
 
    */
 
    DISTREN_REQUEST_RENDERFRAME = 8,
 
    DISTREN_REQUEST_DONEFRAME = 9, /* server should check to make sure the
 
slave is repoting on a frame it's actually assigned to */
 
    DISTREN_REQUEST_PROGRESS = 10, /*< tells another server of the progress of the first server's work at rendering */
 
    DISTREN_REQUEST_GETWORK = 11,
 
    DISTREN_REQUEST_GETVERSION = 12, /*< returns version of software that slave should be running */
 
    DISTREN_REQUEST_GETRENDERPOWER = 13, /* returns the render power of a slave */
 
    DISTREN_REQUEST_SETRENDERPOWER = 14, /* sets renderpower in server
 
database */
 
    /**
 
       sets a frame back to unassigned,
 
       happens if the slave quits for some reason. server code should only allow
 
       resetting of a frame assigned to the slave calling the request (see php
 
       code)
 
    */ 
 
    DISTREN_REQUEST_RESETFRAME = 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;
 
};
 

	
 
/**
 
 * 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);
 

	
 
/**
 
 * 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/remoteio.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 "libremoteio.h"
 
#include "execio.h"
 
#include "asprintf.h"
 

	
 
#include <list.h>
 

	
 
#include <errno.h>
 
#ifndef _WIN32
 
#include <netdb.h>
 
#endif
 
#include <poll.h>
 
#include <stdlib.h>
 
#include <stdio.h>
 
#include <string.h>
 
#include <sys/types.h>
 
#ifdef _WIN32
 
#include <winsock2.h>
 
#include <ws2tcpip.h>
 
#else
 
#include <sys/socket.h>
 
#endif
 
#include <unistd.h>
 
#include <queue.h>
 

	
 
#ifndef _WIN32
 
#include <sys/un.h>
 
#endif
 

	
 
/* local */
 

	
 
#define REMOTEIO_DEFAULT_PORT "4050"
 

	
 
int _remoteio_handle_write(multiio_context_t multiio,
 
			   int fd,
 
			   short revent,
 
			   struct remoteio_opts *opts,
 
			   struct remoteio *rem);
 
int _remoteio_handle_read(multiio_context_t multiio,
 
			  int fd,
 
			  short revent,
 
			  struct remoteio_opts *opts,
 
			  struct remoteio *rem);
 

	
 
int _remoteio_ssh_open(struct remoteio *rem, struct remoteio_server *server);
 
int _remoteio_ssh_read(struct remoteio *rem, void *buf, size_t len, size_t *bytesread);
 
int _remoteio_ssh_write(struct remoteio *rem, void *buf, size_t len, size_t *byteswritten);
 
int _remoteio_ssh_close(struct remoteio *rem);
 

	
 
/**
 
   ``named pipes''
 
 */
 
#ifndef _WIN32
 
int _remoteio_sock_open(struct remoteio *rem, struct remoteio_server *server);
 
int _remoteio_sock_close(struct remoteio *rem);
 
#endif
 
int _remoteio_sock_read(struct remoteio *rem, void *buf, size_t len, size_t *bytesread);
 
int _remoteio_sock_write(struct remoteio *rem, void *buf, size_t len, size_t *byteswritten);
 

	
 
/**
 
   These borrow from _remoteio_sock_read() and _remoteio_sock_write().
 
 */
 
int _remoteio_tcp_open(struct remoteio *rem, struct remoteio_server *server);
 
int _remoteio_tcp_close(struct remoteio *rem);
 

	
 
/**
 
  lookup table for different methods of remoteio:
 
  the enum remoteio_method is the index of the entry to use for that method. 
 
  Regardless, a NULL terminator is required because the configuration function
 
  searches through this table for the method specified in the config file.
 
*/
 
struct remoteio_method_funcmap funcmap[] = 
 
  {
 
    /* [REMOTEIO_METHOD_SSH] */
 
    {REMOTEIO_METHOD_SSH, &_remoteio_ssh_open, &_remoteio_ssh_read, &_remoteio_ssh_write, &_remoteio_ssh_close, "ssh"},
 
#ifndef _WIN32
 
    {REMOTEIO_METHOD_UNIX, &_remoteio_sock_open, &_remoteio_sock_read, &_remoteio_sock_write, &_remoteio_sock_close, "unix"},
 
#endif
 
    {REMOTEIO_METHOD_TCP, &_remoteio_tcp_open, &_remoteio_sock_read, &_remoteio_sock_write, &_remoteio_tcp_close, "tcp"},
 
    {REMOTEIO_METHOD_SOCKET, NULL, &_remoteio_sock_read, &_remoteio_sock_write, &_remoteio_sock_close},
 
    {REMOTEIO_METHOD_MAX, NULL, NULL, NULL, NULL, NULL}
 
  };
 

	
 
struct remoteio_server *remoteio_getserver(const struct remoteio_opts *opts, const char *servername);
 

	
 
void remoteio_packet_free(struct remoteio_packet *packet);
 

	
 
int remoteio_config(cfg_t *cfg, struct remoteio_opts *opts)
 
{
 
  size_t numservers;
 
  size_t counter, counter2;
 
  static int haslisted_methods = 0;
 
  
 
  struct remoteio_server aserver;
 

	
 
  multiio_socket_type_register(opts->multiio, &opts->socket_type);
 

	
 
  multiio_event_handler_register(opts->multiio,
 
				 opts->socket_type,
 
				 POLLWRNORM,
 
				 (multiio_event_handler_func_t)&_remoteio_handle_write,
 
				 opts);
 
  multiio_event_handler_register(opts->multiio,
 
				 opts->socket_type,
 
				 POLLRDNORM,
 
				 (multiio_event_handler_func_t)&_remoteio_handle_read,
 
				 opts);
 

	
 
  opts->servers = list_init();
 
  if(!opts->servers)
 
    {
 
      fprintf(stderr, "@todo cleanup!\n");
 
      abort();
 
    }
 
  
 
  numservers = cfg_size(cfg, "server");
 
  for(counter = 0; counter < numservers; counter ++)
 
    {
 
      cfg_t *cfg_aserver;
 
      char *method;
 
      
 
      cfg_aserver = cfg_getnsec(cfg, "server", counter);
 
      
 
      aserver.name = strdup(cfg_title(cfg_aserver));
 
      aserver.hostname = strdup(cfg_getstr(cfg_aserver, "hostname"));
 
      aserver.username = strdup(cfg_getstr(cfg_aserver, "username"));
 

	
 
      aserver.method = REMOTEIO_METHOD_MAX;
 
      method = cfg_getstr(cfg_aserver, "method");
 
      for(counter2 = 0; funcmap[counter2].name; counter2 ++)
 
	if(strcmp(method, funcmap[counter2].name) == 0)
 
	  aserver.method = funcmap[counter2].method;
 
      if(aserver.method == REMOTEIO_METHOD_MAX)
 
	{
 
	  fprintf(stderr, "No such method as %s\n", method);
 
	  if(!haslisted_methods)
 
	    {
 
	      fprintf(stderr, "Available methods:\n");
 
	      for(counter2 = 0; funcmap[counter2].name; counter2 ++)
 
		fprintf(stderr, "\t%s\n", funcmap[counter2].name);
 
	      
 
	      haslisted_methods ++;
 
	    }
 
	  abort();
 
	}
 
      list_insert_after(opts->servers, &aserver, sizeof(struct remoteio_server));
 
    }
 
  
 
  return 0;
 
}
 

	
 
int remoteio_generic_data_set(struct remoteio_opts *opts, void *generic_data)
 
{
 
  opts->generic_handler_data = generic_data;
 

	
 
  return 0;
 
}
 

	
 
int remoteio_open_common(struct remoteio **remoteio,
 
			 enum remoteio_method method,
 
			 struct remoteio_opts *opts,
 
			 remoteio_read_handle_func_t read_handler,
 
			 void *read_handler_data)
 
			 void *read_handler_data,
 
			 remoteio_close_handle_func_t close_handler)
 
{
 
  struct remoteio *rem;
 

	
 
  rem = malloc(sizeof(struct remoteio));
 
  if(!rem)
 
    {
 
      fprintf(stderr, "OOM\n");
 
      return 2;
 
    }
 

	
 
  *remoteio = rem;
 

	
 
  rem->execio = NULL;
 
  rem->method = method;
 
  rem->opts = opts;
 
  rem->inbuf.data = NULL;
 
  rem->inbuf.len = 0;
 
  rem->outmsgs = q_init();
 
  rem->read_handler = read_handler;
 
  rem->read_handler_data = read_handler_data;
 
  rem->close_handler = close_handler;
 
  /*
 
   * the following initialization is very important... though the
 
   * others are too, I suppose :-p. See remoteio_close()
 
   */
 
  rem->careful_free = 0;
 

	
 
  return 0;
 
}
 

	
 
int remoteio_open_socket(struct remoteio **remoteio,
 
			 struct remoteio_opts *opts,
 
			 remoteio_read_handle_func_t read_handler,
 
			 void *read_handler_data,
 
			 remoteio_close_handle_func_t close_handler,
 
			 int fd)
 
{
 
  struct remoteio *rem;
 
  if(remoteio_open_common(remoteio, REMOTEIO_METHOD_SOCKET, opts, read_handler, read_handler_data))
 
  if(remoteio_open_common(remoteio, REMOTEIO_METHOD_SOCKET, opts, read_handler, read_handler_data, close_handler))
 
    return 1;
 
  rem = *remoteio;
 

	
 
  rem->sock = fd;
 
  multiio_socket_add(opts->multiio, rem->sock, opts->socket_type, rem, POLLRDNORM);
 

	
 
  return 0;
 
}
 

	
 
int remoteio_open_server(struct remoteio **remoteio,
 
			 struct remoteio_opts *opts,
 
			 remoteio_read_handle_func_t read_handler,
 
			 void *read_handler_data,
 
			 remoteio_close_handle_func_t close_handler,
 
			 const char *servername)
 
{
 
  struct remoteio_server *theserver;
 
  struct remoteio *rem;
 

	
 
  int tmp;
 

	
 
  if(!opts)
 
    {
 
      fprintf(stderr, "%s:%d: no null opts!\n\tThis is a bug, please report it (after making sure it isn't already reported)\n", __FILE__, __LINE__);
 
      return 1;
 
    }
 

	
 
  theserver = remoteio_getserver(opts, servername);
 
  if(!theserver)
 
    {
 
      fprintf(stderr, "%s:%d: Could not find server named ``%s''\n", __FILE__, __LINE__, servername);
 
      return 1;
 
    }
 

	
 
  if(theserver->method >= REMOTEIO_METHOD_MAX
 
     || theserver->method < 0)
 
    {
 
      fprintf(stderr, "%s:%d: Unsupported remoteio method %d\n\tThis is a bug, probably indicating memory corruption. This is, of course, probably my fault (not your hardware's) ;-)\n", __FILE__, __LINE__, theserver->method);
 
      return 1;
 
    }
 

	
 
  if(remoteio_open_common(remoteio, theserver->method, opts, read_handler, read_handler_data))
 
  if(remoteio_open_common(remoteio, theserver->method, opts, read_handler, read_handler_data, close_handler))
 
    return 1;
 
  rem = *remoteio;
 

	
 
  tmp = funcmap[theserver->method].open_func(rem, theserver);
 
  if(tmp)
 
    {
 
      fprintf(stderr, "Error using method %s for server ``%s''", funcmap[theserver->method].name, servername);
 
      free(rem->inbuf.data);
 
      q_free(rem->outmsgs, QUEUE_NODEALLOC);
 
      free(rem);
 
      *remoteio = NULL;
 
      return tmp;
 
    }
 

	
 
  /**
 
   * @todo make this code slightly more generic... able to handle
 
   * execio's multi-sockets by letting execio register itself with
 
   * multiio instead of us registering here perhaps
 
   */
 
  multiio_socket_add(opts->multiio, rem->sock, opts->socket_type, rem, POLLRDNORM);
 
  
 
  return 0;
 
}
 

	
 
/**
 
 * Implementation of multiio_event_handler_func_t
 
 */
 
int _remoteio_handle_read(multiio_context_t multiio,
 
			  int fd,
 
			  short revent,
 
			  struct remoteio_opts *opts,
 
			  struct remoteio *rem)
 
{
 
  struct remoteio_packet packet;
 
  size_t readlen;
 
  char buf[8192];
 

	
 
  int tmp;
 

	
 
  packet.len = 0;
 
  packet.data = NULL;
 

	
 
  if(rem->sock != fd)
 
    fprintf(stderr, "%d != %d\n", rem->sock, fd);
 

	
 
  tmp = funcmap[rem->method].read_func(rem, buf, sizeof(buf), &readlen);
 
  if(tmp)
 
    {
 
      remoteio_close(rem);
 
      return 1;
 
    }
 

	
 
  /* expand the input buffer */
 
  packet.len = rem->inbuf.len + readlen;
 
  packet.data = malloc(rem->inbuf.len + readlen);
 
  if(!packet.data)
 
    {
 
      fprintf(stderr, "OOM!\n");
 

	
 
      return 1;
 
    }
 
  if(rem->inbuf.data)
 
    memcpy(packet.data, rem->inbuf.data, rem->inbuf.len);
 
  memcpy(packet.data + rem->inbuf.len, buf, readlen);
 
  free(rem->inbuf.data);
 
  memcpy(&rem->inbuf, &packet, sizeof(struct remoteio_packet));
 

	
 
  /*
 
   * readlen wil now keeps track of how many bytes the handler
 
   * function has read.
 
   *
 
   * Call the read_handler. Set careful_free, see remoteio_close(), so
 
   * that rem->read_handler() may call remoteio_close() without
 
   * segfaulting us ;-).
 
   */
 
  rem->careful_free = 1;
 
  readlen = (*rem->read_handler)(rem, rem->opts->generic_handler_data, rem->inbuf.data, rem->inbuf.len, rem->read_handler_data);
 
  if(rem->careful_free == 2)
 
    {
 
      rem->careful_free = 0;
 
      remoteio_close(rem);
 

	
 
      return 0;
 
    }
 
  rem->careful_free = 0;
 

	
 
  memmove(rem->inbuf.data, rem->inbuf.data + readlen, rem->inbuf.len - readlen);
 
  rem->inbuf.len -= readlen;
 

	
 
  return 0;
 
}
 

	
 

	
 
int remoteio_write(struct remoteio *rem, const void *buf, size_t len)
 
{
 
  struct remoteio_packet *packet;
 
  struct pollfd pollfd;
 
  ssize_t bytes_written;
 

	
 
  /**
 
   * This is probably about the only optimization that exists in
 
   * distren.... :-D
 
   *
 
   * Write to the client immediately if there are no other messages
 
   * waiting and if the client will accept it.
 
   */
 
  if(q_empty(rem->outmsgs))
 
    {
 
      pollfd.fd = rem->sock;
 
      pollfd.revents = POLLWRNORM;
 
      pollfd.events = 0;
 
      poll(&pollfd, 1, 0);
 
      if(pollfd.events & POLLWRNORM)
 
	{
 
	  bytes_written = write(rem->sock, buf, len);
 
	  if(bytes_written > 0)
 
	    {
 
	      len -= bytes_written;
 
	      buf += bytes_written;
 
	    }
 
	}
 
    }
 

	
 
  /**
 
   * zero length is easy... and might be possible if the above
 
   * optimization works ;-)
 
   */
 
  if(!len)
 
    return 0;
 

	
 
  packet = malloc(sizeof(struct remoteio_packet));
 
  if(!packet)
 
    {
 
      fprintf(stderr, "OOM\n");
 
      return 1;
 
    }
 

	
 
  packet->len = len;
 
  packet->data = malloc(len);
 
  if(!packet->data)
 
    {
 
      free(packet);
 
      fprintf(stderr, "OOM\n");
 
      return 1;
 
    }
 

	
 
  memcpy(packet->data, buf, len);
 

	
 
  q_enqueue(rem->outmsgs, packet, 0);
 
  multiio_socket_event_enable(rem->opts->multiio, rem->sock, POLLWRNORM);
 

	
 
  return 0;
 
}
 

	
 
int _remoteio_handle_write(multiio_context_t multiio,
 
			   int fd,
 
			   short revent,
 
			   struct remoteio_opts *opts,
 
			   struct remoteio *rem)
 
{
 
  struct remoteio_packet *packet;
 
  size_t written_amount;
 

	
 
  int tmp;
 

	
 
  fprintf(stderr, "%s:%d: <del>My</del><ins>Someone else's</ins> traversal says that sock %d is available for writing\n",
 
	  __FILE__, __LINE__, fd);
 

	
 
  /*
 
   * check if we're out of stuff to write.
 
   */
 
  if(q_empty(rem->outmsgs))
 
    {
 
      multiio_socket_event_disable(multiio, fd, POLLWRNORM);
 
      return 0;
 
    }
 

	
 
  packet = q_front(rem->outmsgs);
 
  tmp = funcmap[rem->method].write_func(rem, packet->data, packet->len, &written_amount);
 

	
 
  /**
 
     Disconnect in case of write error.
 
  */
 
  if(tmp)
 
    {
 
      fprintf(stderr, __FILE__ ":%d: error handling for write() needs to be inserted into remoteio.... perhaps.. ;-)\n", __LINE__);
 
    }
 
  if(packet->len == written_amount)
 
    {
 
      q_dequeue(rem->outmsgs);
 
      remoteio_packet_free(packet);
 

	
 
      if(q_empty(rem->outmsgs))
 
	multiio_socket_event_disable(multiio, fd, POLLWRNORM);
 
    }
 
  else
 
    {
 
      /**
 
       * shifting seems the simplest solution.
 
       */
 
      packet->len -= written_amount;
 
      memmove(packet->data, packet->data + written_amount, packet->len);
 
    }
 

	
 
  return 0;
 
}
 

	
 

	
 
int remoteio_close(struct remoteio *rem)
 
{
 
  int rtn;
 
  
 
  /**
 
   * See careful_free's and _remoteio_handle_read()'s docs.  If
 
   * careful_free is nonzero, then we shouldn't free it here because
 
   * such a free would cause a segfault. However, whoever set
 
   * rem->careful_free to nonzero will handle resetting
 
   * rem->careful_free to zero and calling remoteio_close() if
 
   * necessary.
 
   */
 
  if(rem->careful_free)
 
    {
 
      rem->careful_free = 2;
 
      return 0;
 
    }
 

	
 
  /* call close handler */
 
  if(rem->close_handler)
 
    (*rem->close_handler)(rem->opts->generic_handler_data, rem->read_handler_data);
 

	
 
  /* cleanup multiiio stuff */
 
  multiio_socket_del(rem->opts->multiio, rem->sock);
 

	
 
  /* backend-specific cleanup */
 
  rtn = funcmap[rem->method].close_func(rem);
 

	
 
  /* this part is normal ;-) */
 
  free(rem->inbuf.data);
 
  q_free(rem->outmsgs, (list_dealloc_func_t)remoteio_packet_free);
 

	
 
  free(rem);
 

	
 
  return rtn;
 
}
 

	
 
/**
 
 * Frees an entire packet, including the passed pointer. If you just
 
 * want the contents of the packet free()ed, just do
 
 * free(packet.data);
 
 */
 
void remoteio_packet_free(struct remoteio_packet *packet)
 
{
 
  free(packet->data);
 
  free(packet); 
 
}
 

	
 

	
 
int _remoteio_getserver_traverse(char *servername, struct remoteio_server *aserver)
 
{
 
  if(!strcmp(aserver->name, servername))
 
    return FALSE; /* stop traversal */
 

	
 
  return TRUE;
 
}
 

	
 
struct remoteio_server *remoteio_getserver(const struct remoteio_opts *opts, const char *servername)
 
{
 
  int traversal_result;
 
  char *dispensible_servername;
 

	
 
  dispensible_servername = strdup(servername); /* for the sake of constness... */
 
  traversal_result = list_traverse(opts->servers, dispensible_servername, (list_traverse_func_t)&_remoteio_getserver_traverse, LIST_FRNT | LIST_ALTR);
 
  free(dispensible_servername);
 

	
 
  if(traversal_result == LIST_OK)
 
    return (struct remoteio_server *)list_curr(opts->servers);
 

	
 
  return (struct remoteio_server *)NULL;
 
}
 

	
 

	
 

	
 

	
 
/**
 
   different remoteio methods' implementations:
 
 */
 

	
 
/*
 
  SSH, via execio
 
*/
 

	
 
int _remoteio_ssh_open(struct remoteio *rem, struct remoteio_server *server)
 
{
 
  char *userhost;
 
  char *sshargs[] = {rem->opts->ssh_command, NULL /* userhost */, "distrend", "-d", (char *)NULL};
 

	
 
  int rtn;
 

	
 
  if(server->username)
 
    _distren_asprintf(&userhost, "%s@%s", server->username, server->hostname);
 
  else
 
    userhost = strdup(server->hostname);
 
  sshargs[1] = userhost;
 

	
 
  rtn = execio_open( &rem->execio, "ssh", sshargs);
 
  if(rtn)
 
    {
 
      fprintf(stderr, "error opening remoteio channel to ssh userhost ``%s''\n" , userhost);
 
      free(userhost);
 
      return 1;
 
    }
 
  free(userhost);
 
  
 
  return 0;
 
}
 

	
 
int _remoteio_ssh_read(struct remoteio *rem, void *buf, size_t len, size_t *bytesread)
 
{
 
  return execio_read(rem->execio, buf, len, bytesread);
 
}
 

	
 
int _remoteio_ssh_write(struct remoteio *rem, void *buf, size_t len, size_t *byteswritten)
 
{
 
  return execio_write(rem->execio, buf, len, byteswritten);
 
}
 

	
 
int _remoteio_ssh_close(struct remoteio *rem)
 
{
 
  int rtn;
 
  
 
  rtn = execio_close(rem->execio);
 
  if(rtn)
 
    fprintf(stderr, "%s:%d: error closing execio\n", __FILE__, __LINE__);
 
  
 
  return rtn;
 
}
 

	
 
#ifndef _WIN32
 
/*
 
  local sockets implementation (``named pipes''), unix-only
 
 */
 
int _remoteio_sock_open(struct remoteio *rem, struct remoteio_server *server)
 
{
 
  int sock;
 
  struct sockaddr_un sockaddr;
 

	
 
  /*
 
    The POSIX docs pretty much say that I can't depend on sockpath being able to be longer than 
 
    some proprietary length. So, if the compiler specifies a long path for RUNSTATEDIR, it could
 
    cause a buffer overflow.
 
   */
 
  char *sockpath = RUNSTATEDIR "/distrend.sock";
 
  unsigned int sockaddr_len;
 

	
 
  sock = socket(AF_UNIX, SOCK_STREAM, 0);
 
  if(sock == -1)
 
    {
 
      perror("socket");
 
      return 1;
 
    }
 

	
 
  sockaddr.sun_family = AF_UNIX;
 
  /*
 
    The terminating NULL should not be included in what's copied to sun_path,
 
    although it won't hurt as long as strlen(sockpath) < max socket length
 
   */
 
  for(sockaddr_len = 0; sockpath[sockaddr_len]; sockaddr_len ++)
 
    sockaddr.sun_path[sockaddr_len] = sockpath[sockaddr_len];
 

	
 
  if(connect(sock, (struct sockaddr *)&sockaddr, sockaddr_len) == -1)
 
    {
 
      perror("connect");
 
      close(sock);
 
      return 1;
 
    }
 

	
 
  rem->sock = sock;
 

	
 
  return 0;
 
}
 

	
 
#endif /* _WIN32 */
 

	
 
int _remoteio_sock_close(struct remoteio *rem)
 
{
 
  close(rem->sock);
 

	
 
  return 0;
 
}
 

	
 
int _remoteio_sock_read(struct remoteio *rem, void *buf, size_t len, size_t *bytesread)
 
{
 
  ssize_t readrtn;
 

	
 
  *bytesread = 0;
 
  readrtn = read(rem->sock, buf, len);
 
  /*
 
    The following is valid for blocking sockets:
 
   */
 
  if(readrtn == -1)
 
    {
 
      /*
 
	in this case, we may have been interrupted by a signal and errno == EINTR
 
	or the connection was reset and errno = ECONNRESET
 

	
 
	Some of these are not error conditions:
 
       */
 
      perror("read");
 

	
 
      if(errno != EINTR)
 
	{
 
	  fprintf(stderr, "error reading socket in remoteio_sock_read\n");
 
	  return 1;
 
	}
 

	
 
      return 0;
 
    }
 

	
 
  *bytesread = readrtn;
 
  if(!readrtn)
 
    {
 
      /*
src/common/remoteio.h
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/>.
 
*/
 

	
 
#ifndef _DISTREN_REMOTEIO_H
 
#define _DISTREN_REMOTEIO_H
 

	
 
#include <stdlib.h>
 

	
 
/**
 
 * @file RemoteIO provides an abstraction to the method of talking to a remote distrend. It is a layer on top of execio that should provide an equivalent interface.
 
 *
 
 * RemoteIO works on top of multiio, so for it to work you write your
 
 * program in an event-oriented fashion and call multiio_poll().
 
 */
 

	
 
struct remoteio_opts;
 
struct remoteio;
 

	
 
/**
 
 * asynchronous read handler signature.
 
 *
 
 * This is the signature of the callback that is called when data is
 
 * available for reading. The handler may process as much of the
 
 * available data as it wants. When it has processed a chunk of data,
 
 * it must return the length of the data that it processed. If the
 
 * handler returns 0, the handler is essentially signalling ``I can't
 
 * figure out what this means until I see more''.
 
 *
 
 * If you need to close a socket after reading certain data from it,
 
 * you may call remoteio_close() from inside of this function.
 
 *
 
 * @param rem the associated remoteio handle
 
 * @param generic_data a pointer that is stored in remoteio's struct remoteio_opts which isn't client-specific
 
 * @param buf a pointer to the buffer containing data waiting to be processed
 
 * @param len the size of buf that may be accessed
 
 * @param data the pointer passed to remoteio_open. _NOT_ the data just received on the socket.
 
 * @param data the pointer passed to remoteio_open(). _NOT_ the data just received on the socket.
 
 * @return the number of bytes that the function accepted and thus should be removed from the rem handle.
 
 */
 
typedef size_t(*remoteio_read_handle_func_t)(struct remoteio *rem, void *generic_data, void *buf, size_t len, void *data);
 

	
 
/**
 
 * asynchronous close handler which is called whenever remoteio_close() is called.
 
 *
 
 * As reading is now event-oriented and as libremoteio may itself call
 
 * remoteio_close(), you need a way to be informed that a socket is
 
 * being closed. This is particularly important if you have to clean
 
 * up your read_handler_data.
 
 *
 
 * @param generic_data a pointer set by a call to remoteio_generic_data_set().
 
 * @param data the same pointer passed to remoteio_open().
 
 */
 
typedef void (*remoteio_close_handle_func_t)(void *generic_data, void *data);
 

	
 
/**
 
 * Determines the value of generic_data which is passed to
 
 * remoteio_read_handle_func_t
 
 *
 
 * @param opts the remoteio runtime options
 
 */
 
int remoteio_generic_data_set(struct remoteio_opts *opts, void *generic_data);
 

	
 
/**
 
   Opens connection with to a remote distrend. Returns 1 on error.
 

	
 
   @todo should this be asynchronous? YES! but optionally, perhaps
 
   @param opts the configuration settings for remoteio gotten from options_init().
 
   @param read_handler the function to call when data has been read from the server.
 
   @param read_handler_data the data to pass to the read_handler function.
 
   @param servername the name of the configuration file entry for the server.
 
   From this, information about how to make the outgoing connection is derived.
 
 */
 
int remoteio_open_server(struct remoteio **rem,
 
			 struct remoteio_opts *opts,
 
			 remoteio_read_handle_func_t read_handler,
 
			 void *read_handler_data,
 
			 remoteio_close_handle_func_t close_handler,
 
			 const char *servername);
 

	
 
/**
 
 * Initializes a remoteio instance for a socket that's already been floating
 
 * around for a while. I.e., this socket probably came from accept().
 
 *
 
 * @param rem a pointer to where the poiner to the newly allocated struct remoteio should be stored.
 
 * @param opts remoteio's options
 
 * @param read_handler the function to call when data has been read from the server.
 
 * @param read_handler_data the data to pass to the read_handler function.
 
 * @param opts self explanatory.
 
 */
 
int remoteio_open_socket(struct remoteio **rem,
 
			 struct remoteio_opts *opts,
 
			 remoteio_read_handle_func_t read_handler,
 
			 void *read_handler_data,
 
			 remoteio_close_handle_func_t close_handler,
 
			 int fd);
 

	
 
/**
 
 * Queue bytes to be written to the remote host.
 
 *
 
 * @param rem the remoteio handle
 
 * @param buf a buffer to be queued for writing. We will copy this, so the caller has to handle its memory (and free() it if necessary).
 
 * @param len number of bytes to grab from buf
 
 * @return 0 on success, 1 on failure
 
 */
 
int remoteio_write(struct remoteio *rem, const void *buf, size_t len);
 

	
 
/**
 
 * Closes a remoteio session.
 
 *
 
 * It is safe to call this function from within
 
 * remoteio_read_handle_func_t.
 
 *
 
 * @return nonzero on error
 
*/
 
int remoteio_close(struct remoteio *rem);
 

	
 
#endif
src/common/request.c
Show inline comments
 
new file 100644
 
/*
 
 * 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/protocol.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);
 
}
 

	
 
uint32_t 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 (uint32_t)poing_data_len;
 
}
src/common/request.h
Show inline comments
 
new file 100644
 
/*
 
 * 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
 

	
 
/**
 
 * @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 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);
 

	
 
#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 "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 <confuse.h>
 
#include <malloc.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;
 
    
 
  } 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
 
 */
 
int distrend_handle_version(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data);
 
int distrend_handle_ping(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data);
 

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

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

	
 
  /** 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_PING, &distrend_handle_ping);
 

	
 
  int slaveKey = 0; // Remotio should set me on a per-slave basis
 
  /* Main Loop */
 
  general_info.config->die = 0;
 
  while(!general_info.config->die)
 
    {
 
      int clientrequest = 0; /*< temporary example variable, will be replaced when we can handle messages */
 

	
 
      multiio_poll(multiio);
 
      /*      distrend_accept(general_info.config->listens); */
 

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

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

	
 
      struct frameset frame;
 
      struct distrenjob *job;
 
      distrenjob_new(&job);
 

	
 
      memset(&frame, '\0', sizeof(frame));
 

	
 
      /** If client requests work */
 
      if(clientrequest == DISTREN_REQUEST_GETWORK)
 
	{
 
	  int returnnum = find_jobframe(general_info.conn, slaveKey, &job->jobnum, &frame.num); // Finds a frame to render @FIXME: Slavenum :D
 
	  if(returnnum); /* No work on server */
 
	  else
 
	    remotio_send_to_client(frame.num, job->jobnum); // Pseudo-sends data to client
 
	}
 
      /* If the client states that they finished the frame */
 
      else if(clientrequest == DISTREN_REQUEST_DONEFRAME)
 
	{
 
	  clientstatus = CLIENTSTATUS_IDLE; // Sets the client back to idle
 
	  finish_frame(general_info.conn, 0, job->jobnum, frame.num); // @TODO: Make sure this actually works.
 
	}
 
      distrenjob_free(&job);
 
    }
 

	
 
  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, size_t req_len, void *req_data)
 
{
 
  char *tmp_str;
 
  char fixedbuf[32];
 

	
 
  struct distren_request *newreq;
 

	
 
  if(client->state != DISTREND_CLIENT_PREVERSION)
 
    {
 
      distrend_send_disconnect(client, "You have already sent the VERSION command.");
 
    }
 
  if(strlen(PACKAGE_STRING) == req_len
 
     && !strncmp(PACKAGE_STRING, req_data, req_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;
 

	
 
      distren_request_new(&newreq, strlen(PACKAGE_STRING), DISTREN_REQUEST_VERSION);
 
      distrend_client_write_request(client, newreq, PACKAGE_STRING);
 
      distren_request_free(newreq);
 
    }
 
  else
 
    {
 
      /**
 
	 The client claims to be of a different version of distren.
 
	 Now we will just send a disconnect packet and disconnect the client.
 
      */
 
      strncpy(fixedbuf, req_data, 31);
 
      fixedbuf[31] = '\0';
 
      if(req_len < 31)
 
	fixedbuf[req_len] = '\0';
 

	
 
      _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, fixedbuf);
 
      distrend_send_disconnect(client, tmp_str);
 
    }
 

	
 
  return 0;
 
}
 

	
 
int distrend_handle_ping(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data)
 
{
 
  struct distren_request *pong_req;
 

	
 
  if(req_len > 32)
 
    distrend_send_disconnect(client, "You have tried to send a PING packet with a length longer than 32 bytes.");
 

	
 
  /**
 
      respond to the client using the data he sent in his PONG
 
      command.
 
   */
 
  distren_request_new(&pong_req, req_len, DISTREN_REQUEST_PONG);
 
  distrend_client_write_request(client, pong_req, req_data);
 
  distren_request_free(pong_req);
 

	
 
  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);
 
  free(tmp);
 

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

	
 
  xmlTextWriterEndDocument(writer);
 
  xmlFreeTextWriter(writer);
 
}
 

	
 
/**
 
   Reads general state information from general_info.xml
 
   into the general_info structure.
 
*/
 
int import_general_info(struct general_info *general_info)
 
{
 
  xmlDocPtr doc;
 
  xmlNodePtr cur;
 

	
 
  doc = xmlParseFile(general_info->files.geninfo);
 
  cur = xmlDocGetRootElement(doc);
 
  if (xmlStrcmp(cur->name, (xmlChar*)"general_info"))
 
    {
 
      fprintf(stderr, "xml document is wrong type");
 
      xmlFreeDoc(doc);
 
      return 1;
 
    }
 

	
 
  cur = cur->xmlChildrenNode;
 
  general_info->jobs_in_queue = atoi((char*)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1));
 
  cur = cur->next;
 
  general_info->total_finished_jobs = atoi((char*)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1));
 
  cur = cur->next;
 
  general_info->total_frames_rendered = atoi((char*)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1));
 
  cur = cur->next;
 
  general_info->highest_jobnum = atoi((char*)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1));
 

	
 
  general_info->hibernate = 0;
 
  general_info->free_clients = 0;
 
  general_info->rendering_clients = 0;
 
  general_info->timestamp = 0;
 
  general_info->total_render_power = 0;
 
  general_info->total_priority_pieces = 0;
 

	
 
  xmlFreeDoc(doc);
 

	
 
  return 0;
 
}
 

	
 

	
 
/**
 
   updates job_list.xml which lists all the jobs in the queue
 
   @return 0 means success
 
*/
 
// @QUERY: Likely obselete (don't remove at request of ohnobinki)
 
int update_xml_joblist(struct general_info *geninfo)
 
{
 
  struct distrenjob *job;
 
  xmlTextWriterPtr writer;
 
  char *tmp;
 
  int counter;
 

	
 
  /**
 
     update timestamp
 
  */
 
  geninfo->timestamp ++;
 
  if(geninfo->timestamp > 65530)
 
    geninfo->timestamp = 0;
 

	
 
  _distren_asprintf(&tmp, "%s/job_list.xml",
 
                    geninfo->config->datadir);
 
  writer = xmlNewTextWriterFilename(tmp, 0);
 
  free(tmp);
 

	
 
  xmlTextWriterStartDocument(writer, NULL, "utf-8", NULL);
 

	
 
  /**
 
     create root element job_list
 
  */
 
  xmlTextWriterStartElement(writer, (xmlChar*)"job_list");
 

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

	
 
  geninfo->total_priority_pieces = 0;
 
  counter = 0;
 
  for(job = geninfo->head.next; job; job = job->next)
 
    {
 
      _distren_asprintf(&tmp, "%d", job->jobnum);
 
      xmlTextWriterWriteElement(writer, (xmlChar*)"jobnum", (xmlChar*)tmp);
 
      free(tmp);
 

	
 
      /**
 
        this is needed for the new frame finder to work
 
      
 
        Why the random constant numeral 11? --ohnobinki
 
      */
 
      geninfo->total_priority_pieces = geninfo->total_priority_pieces + job->priority;
 

	
 
      counter++;
 
    }
 

	
 
  xmlTextWriterEndElement(writer);
 

	
 
  /**
 
     close elements and end document
 
  */
 
  xmlTextWriterEndDocument(writer);
 

	
 
  /**
 
     free writer and save xml file to disk
 
  */
 
  xmlFreeTextWriter(writer);
 
  return 0;
 
}
 

	
 
/* ************************** Test Functions ************************* */
 

	
 

	
 
/** Interactive test for the queuing system */
 
/* @QUEUE: Test uses methods not present in C code using mysql web-based system */
 
int interactiveTest(int test, multiio_context_t multiio, struct general_info *geninfo)
 
{
 
  int command;
 
  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);
 
	   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/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 "listen.h"
 
#include "common/protocol.h"
 
#include "common/remoteio.h"
 

	
 
#include <errno.h>
 
#include <list.h>
 
#include <malloc.h>
 
#include <netinet/in.h>
 
#include <stdio.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);
 
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);
 
size_t distrend_listen_read_handle(struct remoteio *rem, struct distrend_listens *listens, void *buf, size_t len, struct distrend_client *client);
 
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_read(multiio_context_t multiio,
 
		       int fd,
 
		       short revent,
 
		       struct distrend_listens *listens,
 
		       struct distrend_client *client);
 
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,
 
				 POLLRDNORM,
 
				 (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)
 
    {
 
      perror("listen");
 
      close(fd);
 
      
 
      return 1;
 
    }
 

	
 
  saved_port = malloc(sizeof(int));
 
  if(!saved_port)
 
    {
 
      perror("malloc");
 
      close(fd);
 

	
 
      return 1;
 
    }
 
  *saved_port = port;
 

	
 
  tmp = multiio_socket_add(listens->multiio, fd, listens->socket_type, saved_port, POLLRDNORM);
 
  if(tmp)
 
    {
 
      close(fd);
 

	
 
      return 1;
 
    }
 

	
 
  /**
 
   * @todo perhaps we'll someday want the ability to unlisten()? :-p
 
   * Then we have to store the fd somewheres so that we can call
 
   * multiio_socket_del() on it. So far, however, that functionality
 
   * isn't needed.
 
   */
 

	
 
  return 0;
 
}
 

	
 
int listen_handle_error(multiio_context_t multiio,
 
			int fd,
 
			short revent,
 
			struct distrend_listens *listens,
 
			int *port)
 
{
 
  fprintf(stderr, "Port %d experienced an error or is closed. Closing it.\n", *port);
 
  multiio_socket_del(listens->multiio, fd);
 
  close(fd);
 

	
 
  free(port);
 

	
 
  return 0;
 
}
 

	
 

	
 
//int listen_handle_read(struct distrend_listens *listens,
 
//			struct distrend_client *client)
 

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

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

	
 
   if(remoteio_open_socket(&rem, listens->options->remoteio, (remoteio_read_handle_func_t)&distrend_listen_read_handle, newclient, 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, __FILE__ ":%d: This client SHOULD be registered in the table tennis system here\n", __LINE__);
 
   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);
 

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

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

	
 
      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 req->len + distrend_listen_read_handle(rem, listens, buf + req->len, len - req->len, client);
 
    }
 

	
 
  return 0;
 
}
 

	
 
/**
 
 * 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);
 

	
 
  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 *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->rem = rem;
 

	
 
  return client;
 
}
 

	
 
/**
 
   This function shall only be called after the appropriate
 
   distrend_listen_poll_deletefd() has been called
 

	
 
   @deprecated blah
 
   @todo kill this func?
 
 */
 
int distrend_client_free(struct distrend_client *client)
 
{
 
  fprintf(stderr, " Kaaaaaaa bOOOOOOOOMNMMMM!\n");
 
  abort();
 
  
 
  return 0;
 
}
 

	
 
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->len, data->req_data);
 

	
 
  return TRUE;
 
}
 

	
 
/**
 
   helper for listen_handle_read() which looks up the correct
 
   request handler and handles handing the the request to the
 
   handler. :-p
 
 */
 
int distrend_dispatch_request(struct distrend_listens *listens, struct remoteio *rem, struct distrend_client *client, struct distren_request *req, void *reqdata)
 
{
 
  struct distrend_dispatch_request_data data;
 

	
 
  data.geninfo = listens->geninfo;
 
  data.client = client;
 
  data.req = req;
 
  data.req_data = reqdata;
 

	
 
  list_traverse(listens->request_handlers, &data, (list_traverse_func_t)&_distrend_dispatch_request_trav, LIST_FRNT | LIST_SAVE);
 

	
 
  return 0;
 
}
 

	
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 <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;
 
};
 

	
 

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

	
 
  /**
 
   * 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 len the length of the message in bytes
 
   @param data the message received from the client
 
 */
 
typedef int(*distrend_handle_request_func_t)(struct general_info *geninfo, struct distrend_client *client, size_t req_len, 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
src/server/slave.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/>.
 

	
 
*/
 

	
 
#include "slavefuncs.h"
 

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

	
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 
#include <sys/stat.h>
 
#include <unistd.h>
 

	
 
#define DEBUG 0
 

	
 
struct slave_state
 
{
 
  /* whether or not we've gotten past the copyright line */
 
  short copyright_done;
 

	
 
  /* number of bytes that we need to read before we have a whole packet */
 
  size_t expectlen;
 
  /* whether or not we should exit */
 
  int quit;
 

	
 
  /* the server's remoteio handle */
 
  struct remoteio *rem;
 
};
 

	
 
static void distren_slave_remoteio_close_handle(void *blah, void *data);
 
static size_t distren_slave_remoteio_read_handle(struct remoteio *rem,
 
						 void *blah,
 
						 void *buf,
 
						 size_t len,
 
						 void *data);
 

	
 
int main(int argc, char *argv[])
 
{
 
  multiio_context_t multiio;
 

	
 
  struct slave_state slave_state;
 

	
 
  char *datadir;
 
  char *server;
 
  char *username;
 
  char *password;
 
  char *hostname;
 

	
 
  cfg_opt_t myopts[] = {
 
    CFG_SIMPLE_STR("username", &username),
 
    CFG_SIMPLE_STR("password", &password),
 
    CFG_SIMPLE_STR("datadir", &datadir),
 
    CFG_SIMPLE_STR("server", &server),
 
    CFG_SIMPLE_STR("hostname", &hostname),
 
    CFG_END()
 
  };
 
  cfg_t * my_cfg;
 

	
 
  struct options_common *commonopts;
 

	
 
  // struct distrenjob *myjob; /* Structure to hold data gathered from the XML file - not needed anymore? */
 

	
 
  struct remoteio *comm_slave;
 

	
 
  /**
 
     initializations
 
  */
 
  datadir = NULL;
 
  server = NULL;
 
  username = NULL;
 
  password = NULL;
 
  hostname = NULL;
 

	
 
  char curopt;
 
  int runBenchmark = 0;
 

	
 
  multiio = multiio_context_new();
 

	
 
  while(((char)-1) != (curopt = getopt(argc, argv, "u:rh")))
 
     {
 
       if(curopt == ':')
 
         {
 
           fprintf(stderr, "-%c: is missing an argument\n", optopt);
 
           return 1;
 
         }
 
       else if(curopt == '?')
 
         {
 
           fprintf(stderr, "-%c: invalid option specified\n", optopt);
 
           return 1;
 
         }
 
       else if(curopt == 'h')
 
         {
 
           fprintf(stderr, "Usage: distrenslave [option] \nStarts a distren slave\n\t-u\tset username (run after fresh install)\n\t-r\tRecalculate render power (benchmark)\n\t-h\tshow this help\n");
 
           return 2;
 
         }
 
       else if(curopt == 'r')
 
         {
 
           runBenchmark = 1;
 
           break;
 
         }
 
       else if(curopt == 'u')
 
         username = strdup(optarg);
 
         if(DEBUG)
 
           fprintf(stderr, "Putting username \"%s\" in distrenslave.conf\n", username);
 

	
 
         conf_replace("distrenslave.conf", "!username", username);
 
         fprintf(stderr, "Please invoke distrenslave with no arguments to run with the username you just set\n");
 
         return 0;
 
     }
 

	
 
  /* Get conf data */
 
  options_init(argc, argv, &my_cfg, myopts, "slave", &commonopts, multiio);
 

	
 
  if(!datadir)
 
    {
 
      fprintf(stderr, "datadir not set\n");
 
      return 1;
 
    }
 
  if(!server)
 
    {
 
      fprintf(stderr, "server not set\n");
 
      return 1;
 
    }
 
  if(!username)
 
    {
 
      fprintf(stderr, "username not set\n");
 
      return 1;
 
    }
 
  if(!password)
 
    {
 
      fprintf(stderr, "password not set\n");
 
      return 1;
 
    }
 
  if(!hostname)
 
    {
 
      fprintf(stderr, "hostname not set\n");
 
      return 1;
 
    }
 

	
 
  /* Notifies the user if there no username in .conf */
 
  if(checkUsername(username))
 
    return 1;
 
  if(!strncmp(password, "!password",10))
 
    {
 
      fprintf(stderr, "You haven't specified a password. Please edit distrenslave.conf!\n");
 
      return 1;
 
    }
 

	
 
  fprintf(stderr, "Connecting to server...\n");
 
  if(remoteio_open_server(&comm_slave, commonopts->remoteio, NULL, NULL, server))
 
    {
 
      fprintf(stderr, "Error connecting to server; exiting\n");
 
      return 1;
 
    }
 
  greet_server(comm_slave);
 

	
 
  // Variables needed for main loop
 
  int jobnum = 0;
 
  int framenum = 0;
 
  int slavekey = atoi(username); // @TODO: Make this more friendly
 

	
 
  char *urltoTar;      /* Full URL to the server-side location of job#.tgz */
 
  char *pathtoTar;     /* Full path to the location of the job#.tgz */
 
  char *pathtoTardir;
 

	
 
  char *urltoOutput;   /* Full URL where output is posted */
 
  char *pathtoOutput;  /* Full path to the output (rendered) file */
 
  char *pathtoOutdir;  /* Full path to output directory */
 
  char *pathtoRenderOutput;  /* Contains blender framenum placeholder */
 

	
 
  char *urltoJobfile; /* No longer used, url to .blend on server */
 

	
 
  char *pathtoJob;     /* Full path to job data folder */
 
  char *pathtoJobfile; /* Full path to the job's main file */
 
  char *outputExt = "jpg";     /* Output Extension (e.g., JPG) */
 

	
 
  int haveWork = 0;
 
  int quit = 0;
 

	
 
  fprintf(stderr,"\nDistRen Slave Pre-Alpha %s\n- Experimental build: Use at your own risk!\n\n", PACKAGE_VERSION);
 

	
 
  int benchmarkTime = 0;
 
  int renderPower = 0;
 

	
 
  slave_state.copyright_done = 0;
 
  slave_state.expectlen = 0;
 
  slave_state.quit = 0;
 

	
 
  fprintf(stderr, "Connecting to server...\n");
 
  if(remoteio_open_server(&slave_state.rem, commonopts->remoteio, &distren_slave_remoteio_read_handle, &slave_state, &distren_slave_remoteio_close_handle, server))
 
    {
 
      fprintf(stderr, "Error connecting to server; exiting\n");
 
      return 1;
 
    }
 
  greet_server(slave_state.rem);
 

	
 

	
 
  fprintf(stderr,"\nDistRen Slave Pre-Alpha %s\n- Experimental build: Use at your own risk!\n\n", PACKAGE_VERSION);
 

	
 
  // @TODO: add call to function to force recalc if $render_power == ""
 
  if(runBenchmark)
 
    {
 
      if(slaveBenchmark(datadir, &benchmarkTime, &renderPower))
 
        {
 
          fprintf(stderr,"Benchmark failed! Exiting.\n");
 
          return 1;
 
        }
 
      else
 
        {
 
          fprintf(stderr,"Benchmark successful, time taken was %d seconds, giving you a render power of %d.\n",
 
                  benchmarkTime, renderPower);
 
          _web_setrenderpower(slavekey, password, renderPower);
 
          return 0;
 
        }
 
    }
 

	
 
  if(!DEBUG)
 
    fprintf(stderr, "Running..");
 

	
 

	
 
  // Main loop
 
  while(!quit)
 
  while(!slave_state.quit)
 
    {
 
      multiio_poll(multiio);
 

	
 
      if(slave_state.quit)
 
	break;
 

	
 
    // request work
 
    fprintf(stderr, "Waiting...\n");
 
    haveWork = getwork(comm_slave, &jobnum, &framenum);
 
    haveWork = getwork(slave_state.rem, &jobnum, &framenum);
 

	
 
    /* If we got a frame */
 
    if(haveWork)
 
      {
 
        fprintf(stderr,"Got work from server...\n");
 
        /* @TODO: Add remotio hooks */
 
        // jobnum = remoteio_read(jobnum); /* Set jobnum from remoteio (we could use info from struct, but we need this info to download the xmlfile */
 
        // framenum = remoteio_read(jobnum); /* Set framenum from remoteio */
 
        // outputExt = remotio)read(outputExt); /* Set output extension from remotio */
 

	
 
        if(DEBUG)
 
          fprintf(stderr, "Preparing to render frame %d in job %d\n", framenum, jobnum);
 

	
 
        prepareJobPaths(jobnum, framenum, outputExt, datadir, &urltoTar, &pathtoTar,&pathtoTardir,&pathtoJob, &pathtoJobfile, &urltoJobfile, &urltoOutput, &pathtoOutput, &pathtoRenderOutput, &pathtoOutdir);
 
        free(outputExt);
 

	
 
        int dlret = downloadTar(urltoTar, pathtoTar);
 
        if(dlret == 0)
 
          fprintf(stderr,"Data retrieved successfully!\n");
 
        else if(dlret == 3){
 
          resetframe(comm_slave, jobnum, framenum);  // Unassign the frame on the server so other slaves can render it
 
          resetframe(slave_state.rem, jobnum, framenum);  // Unassign the frame on the server so other slaves can render it
 
          return 0; // ouput dir doesn't exist
 
        }
 
        else
 
          if(DEBUG)
 
            fprintf(stderr,"Using existing tarball %s...\n", pathtoTar);
 

	
 
        // Decompress tarball
 
        struct stat jbuffer;
 
        int jstatus = stat(pathtoTar, &jbuffer);
 
        if(jstatus == -1){
 
          if(DEBUG)
 
            fprintf(stderr,"Main job file does not exist, extracting...\n");
 

	
 
          // If error unpacking tarball
 
          if(unpackJob(pathtoJob, pathtoTar)){
 
            resetframe(comm_slave, jobnum, framenum);  // Unassign the frame on the server so other slaves can render it
 
            resetframe(slave_state.rem, jobnum, framenum);  // Unassign the frame on the server so other slaves can render it
 
            fprintf(stderr,"Error decompressing tarball! Exiting.\n");
 
            return 1;
 
          }
 
        }
 

	
 
        /* ignore return because directory may exist already */
 
        if(DEBUG)
 
          fprintf(stderr,"Creating output directory %s\n", pathtoOutdir);
 
        mkdir(pathtoOutdir, 0700);
 

	
 
        if(DEBUG)
 
          fprintf(stderr,"Marking frame started on server... ");
 
        _web_startframe(slavekey, password, jobnum, framenum);
 

	
 
        /* Execute blender */
 
        if(DEBUG){
 
          fprintf(stderr,"Executing blender on file %s\n", pathtoJobfile);
 
          fprintf(stderr,"Directing output to file %s\n", pathtoOutput);
 
        }
 

	
 
        /* Execute blender */
 
        if(exec_blender(pathtoJobfile, pathtoOutput, framenum))
 
          {
 
            fprintf(stderr,"Error running Blender. Check your installation and/or your PATH.\n");
 
            resetframe(comm_slave, jobnum, framenum);  // Unassign the frame on the server so other slaves can render it
 
            resetframe(slave_state.rem, jobnum, framenum);  // Unassign the frame on the server so other slaves can render it
 
            return 1;
 
          }
 
        free(pathtoJobfile);
 
        pathtoJobfile = NULL;
 

	
 
        struct stat buffer;
 
        int fstatus = stat(pathtoOutput, &buffer);
 
        if(fstatus == -1){
 
          fprintf(stderr,"*** %s doesn't exist! Scene may not have camera, or your blender installation is not working.\n", pathtoOutput);
 
          resetframe(comm_slave, jobnum, framenum);  // Unassign the frame on the server so other slaves can render it
 
          resetframe(slave_state.rem, jobnum, framenum);  // Unassign the frame on the server so other slaves can render it
 
          return 1;
 
        }
 
        else{
 
          /* Post-execution */
 
          if(DEBUG)
 
            fprintf(stderr, "Finished frame %d in job %d, uploading...\n", framenum, jobnum);
 
          else
 
            fprintf(stderr,"Finished frame.\n");
 
          uploadOutput(pathtoOutput, urltoOutput, jobnum, framenum, slavekey); // @TODO: Handle return value
 

	
 
          free(urltoOutput);
 
          free(pathtoOutput);
 
          urltoOutput = NULL;
 
          pathtoOutput = NULL;
 

	
 
          // Tell the server that rendering and upload are complete of "jobjum.framenum"
 
          finishframe(comm_slave, jobnum, framenum);
 
          finishframe(slave_state.rem, jobnum, framenum);
 

	
 
  free(urltoTar);
 
  free(pathtoTar);
 
  free(pathtoTardir);
 
  free(pathtoJob);
 
  free(pathtoJobfile);
 
  free(urltoJobfile);
 
  free(urltoOutput);
 
  free(pathtoRenderOutput);
 
  free(pathtoOutdir);
 

	
 
        }
 
     }
 
    else{
 
      if(DEBUG)
 
        fprintf(stderr,"Nothing to do. Idling...\n");
 
      else
 
        fprintf(stderr,".");
 

	
 
      /**
 
	 to prevent infinite loops from burning CPU, we just sleep(1) ;-)
 
      */
 
      sleep(1);
 
    }
 

	
 
    /* @TODO: If the server says that every frame for the last jobnum is finished, OR if the data is getting old */
 
    if(1 == 0)
 
      {
 
        // Note: individual frames are already deleted after uploading,
 
        // except for ones that couldn't be uploaded
 
        delete_jobdata(jobnum, datadir);
 
      }
 

	
 
    sleep(5); // Poll 5 seconds. @TODO: Remove all polling
 
  }
 

	
 
  fprintf(stderr,"Closing connection to server...\n");
 
  remoteio_close(comm_slave);
 
  fprintf(stderr, "Quitting...\n");
 

	
 
  free(my_cfg);
 
  free(outputExt);
 
  free(datadir);
 
  free(urltoTar);
 
  free(pathtoTar);
 
  free(pathtoTardir);
 
  free(pathtoJob);
 
  free(pathtoJobfile);
 
  free(urltoJobfile);
 
  free(urltoOutput);
 
  free(pathtoRenderOutput);
 
  free(pathtoOutdir);
 
  fprintf(stderr,"Goodbye!\n");
 
  return 0;
 
}
 

	
 
static void distren_slave_remoteio_close_handle(void *blah, void *data)
 
{
 
  struct slave_state *slave_state = (struct slave_state *)data;
 

	
 
  fprintf(stderr, "Lost connection to server\n");
 

	
 
  slave_state->quit = 1;
 
}
 

	
 
static size_t distren_slave_remoteio_read_handle(struct remoteio *rem,
 
						 void *blah,
 
						 void *buf,
 
						 size_t len,
 
						 void *data)
 
{
 
  struct slave_state *slave_state = (struct slave_state *)data;
 

	
 
  struct distren_request *req, *my_req;
 
  void *req_data, *my_req_data;
 

	
 
  size_t to_return;
 

	
 
  size_t counter;
 

	
 
  /* to_return shall record how much of buf we've eaten already */
 
  to_return = 0;
 

	
 
  if(!slave_state->copyright_done)
 
    {
 
      putchar('\n');
 
      /* we have to flush through data until we hit a newline */
 
      for(counter = 0;
 
	  counter + slave_state->expectlen < 256 && counter < len && ((char *)buf)[counter] != '\n';
 
	  counter ++)
 
	{
 
	  putchar(((char *)buf)[counter]);
 
	}
 
      slave_state->expectlen += counter - 1;
 
      if(slave_state->expectlen == 256)
 
	{
 
	  fprintf(stderr, "\nThe server's greeting is too long. Maybe it speaks a foreign language\n");
 
	  slave_state->quit = 1;
 
	}
 
      if(counter < len && ((char *)buf)[counter] == '\n')
 
	{
 
	  putchar('\n');
 
	  counter ++;
 
	  slave_state->expectlen = 0;
 
	  slave_state->copyright_done = 1;
 
	  to_return += counter;
 
	  buf += counter;
 
	  len -= counter;
 
	}
 
      if(!slave_state->copyright_done)
 
	return to_return;
 
    }
 

	
 
  while(1)
 
    {
 
      /* if we haven't read a full header yet: */
 
      if(!slave_state->expectlen)
 
	{
 
	  if(len < sizeof(struct distren_request))
 
	    return 0;
 

	
 
	  /* figure out how much we need to read in before we can get anywhere */
 
	  if(distren_request_new_fromdata(&req, buf, len))
 
	    {
 
	      fprintf(stderr, "Failing to interpret data from server, exiting\n");
 
	      slave_state->quit = 1;
 
	      return 0;
 
	    }
 
	  slave_state->expectlen = sizeof(struct distren_request) + req->len;
 
	  distren_request_free(req);
 
	}
 

	
 
      if(slave_state->expectlen
 
	    && slave_state->expectlen <= len)
 
	{
 
	  distren_request_new_fromdata(&req, buf, len);
 
	  req_data = buf + sizeof(struct distren_request);
 

	
 
	  switch((enum distren_request_type)req->type)
 
	    {
 
	    case DISTREN_REQUEST_VERSION:
 
	      fprintf(stderr, "The server runs ");
 
	      for(counter = 0; counter < req->len; counter ++)
 
		putc(((char *)req_data)[counter], stderr);
 
	      putc('\n', stderr);
 
	      break;
 

	
 
	    case DISTREN_REQUEST_PING:
 
	      fprintf(stderr, "PONG ! :-D\n");
 

	
 
	      distren_request_poing(&my_req, &my_req_data, 0, req_data, req->len);
 
	      remoteio_write(slave_state->rem, &my_req, sizeof(struct distren_request));
 
	      remoteio_write(slave_state->rem, &my_req_data, req->len);
 
	      distren_request_free_with_data(my_req, my_req_data);
 
	      break;
 

	
 
	    case DISTREN_REQUEST_DISCONNECT:
 
	      /* hopefully this ends up being a useful message... */
 
	      printf("You have been disconnected: \"");
 
	      for(counter = 0; counter < req->len; counter ++)
 
		putchar(((char *)buf)[counter]);
 
	      putchar('"');
 
	      putchar('\n');
 
	      break;
 

	
 
	    default:
 
	      fprintf(stderr, "something\n");
 
	      break;
 
	    }
 

	
 
	  distren_request_free(req);
 
	  slave_state->expectlen = 0;
 

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

	
 
	  len -= counter;
 
	  buf += counter;
 
	  to_return += counter;
 
	}
 
    }
 

	
 
  return to_return;
 
}
src/server/slavefuncs.c
Show inline comments
 
@@ -655,315 +655,322 @@ int _web_getwork(int slavekey, char *sla
 

	
 
      // @TODO: This should be called every time, not just on fail.
 
      if(strcmp(PACKAGE_VERSION,serverversion)){
 
        fprintf(stderr,"Your distren package is out of date! Please acquire a newer version. (%s local vs %s remote)\n", PACKAGE_VERSION, serverversion);
 
        return 0;
 
      }
 
      if(DEBUG)
 
        fprintf(stderr,"Software versions: %s local vs %s remote\n", PACKAGE_VERSION, serverversion);
 

	
 

	
 
      free(data.memory);
 
      return 1;
 
    }
 
    else
 
      return 0; // error
 
  }
 
}
 

	
 
void _web_setrenderpower(int slavekey, char *slavepass, int renderpower){
 
  fprintf(stderr,"Setting render power on server... ");
 
  char *url;
 
  _distren_asprintf(&url,"http://dev.distren.org/slave/act.php?mode=setrenderpower&slavekey=%d&slavepass=%s&renderpower=%d", slavekey, slavepass, renderpower);
 
  struct _web_memorystruct data = _web_getrequest(url);
 
  free(url);
 

	
 
  fprintf(stderr,"%s\n", data.memory);
 
  if(data.memory)
 
    free(data.memory);
 
}
 

	
 
int slaveBenchmark(char *datadir, int *benchmarkTime, int *renderPower){
 
  int ret;
 
  int frameToRender = 1;
 
  char *frame_str;
 
  _distren_asprintf(&frame_str, "%d", frameToRender); // Render frame 1
 

	
 
  char *output;
 
  _distren_asprintf(&output, "%s/benchmark#.jpg", datadir); // Where to save benchmark output
 

	
 
  char *realOutput;
 
  _distren_asprintf(&realOutput, "%s/benchmark%d.jpg", datadir, frameToRender); // Where to save benchmark output
 

	
 

	
 
  char *input;
 
  _distren_asprintf(&input, "%s/benchmark.blend", datadir); // Input file
 

	
 
  fprintf(stderr,"Downloading benchmark data...\n");
 
  curlget("http://data.distren.org/benchmark.blend", input); // Download to input file location
 

	
 
  char *command = "blender"; // @TODO: We currently expect this to be in PATH
 
  char *cmd[] = { command, "-b", input, "-o", output, "-f", frame_str, "-t", "0", (char *)NULL }; // arguments for blender
 

	
 
  // fprintf(stderr,"Preparing to execute command: %s -b %s -o %s -f %s\n", command, input, output, frame_str);
 
  fprintf(stderr,"Running benchmark...\n");
 

	
 
  long startTime;
 
  long endTime;
 

	
 
  time(&startTime);
 

	
 
  char buf[20];
 
  struct execio *testrem;
 
  size_t readlen;
 

	
 
  ret = execio_open(&testrem, command, cmd);
 
  if(ret)
 
    {
 
      fprintf(stderr, "Error executing blender\n");
 
      return 1;
 
    }
 

	
 
  buf[19] = '\0';
 
  while(!execio_read(testrem, buf, 19, &readlen))
 
    {
 
       buf[readlen] = '\0';
 
       fprintf(stderr, "read \"%s\"\n", buf);
 
    }
 
  execio_close(testrem);
 

	
 
  time(&endTime);
 

	
 
  struct stat buffer;
 
  int ostatus = stat(realOutput, &buffer);
 
  if(ostatus == -1){
 
    ret = 1; // Return error if output wasn't generated
 
  }
 
  else
 
    remove(output);
 

	
 
  *benchmarkTime = abs(difftime(startTime,endTime));
 
  float tmp = *benchmarkTime;
 
  tmp = (1/tmp) * 50000;
 
  *renderPower = (int)tmp;
 
  
 
free(realOutput);
 
  free(frame_str);
 
  free(input);
 
  free(output);
 
  return ret;
 
}
 

	
 

	
 

	
 
/** *********************************************************************************************************/
 
/** Why hello ohnobinki! Normaldotcom has graciously prepared this portion of code for you to work on! Yay! */
 
/** *********************************************************************************************************/
 

	
 

	
 
/**
 
   Extracts archive to the specified directory, creating this directory
 
   if necessary (but this directory will not be recursively created).
 
   @param outdir output directory
 
   @param pathtoTar filename of the archive to extract
 

	
 
   ohnobinki: please make me work :-\
 
 */
 
int unpackJob(char *outdir, char *pathtoTar)
 
{
 
  int ret;
 

	
 
  struct archive *a;
 
  struct archive_entry *ae;
 
  int astatus;
 

	
 
  /* ignore return because directory may exist already */
 
  mkdir(outdir, 0700);
 
  ret = chdir(outdir);
 
  if(ret == -1)
 
    {
 
      perror("chdir");
 
      return 1;
 
    }
 

	
 
  a = archive_read_new();
 
  ae = archive_entry_new();
 

	
 
  archive_read_support_compression_all(a);
 
  archive_read_support_format_raw(a);
 
  astatus = archive_read_open_filename(a, pathtoTar, 8192);
 
  if (astatus != ARCHIVE_OK)
 
    {
 
      fprintf(stderr, "Error opening archive!\n");
 
      return 1;
 
    }
 

	
 
  for(astatus = ARCHIVE_OK;
 
      astatus == ARCHIVE_OK
 
        || astatus == ARCHIVE_WARN;
 
      )
 
    {
 
      astatus = archive_read_next_header2(a, ae);
 
      if(astatus == ARCHIVE_WARN)
 
        fprintf(stderr, "Encountered nonfatal read error somehow!\n");
 

	
 
      if(astatus == ARCHIVE_OK
 
         || astatus == ARCHIVE_WARN)
 
        astatus = archive_read_extract(a, ae,
 
                                       ARCHIVE_EXTRACT_NO_OVERWRITE
 
                                       | ARCHIVE_EXTRACT_SECURE_SYMLINKS
 
                                       | ARCHIVE_EXTRACT_SECURE_NODOTDOT);
 
    }
 
  archive_entry_free(ae);
 
  archive_read_finish(a);
 

	
 
  if(astatus != ARCHIVE_EOF)
 
    {
 
      fprintf(stderr, "Error reading archive!\n");
 
      return 1;
 
    }
 

	
 
  return 0;
 
}
 

	
 

	
 
/** Logs the user into the server after ensuring that keys exist
 
    ohnobinki: I assume you could use this for remoteio, or just kill it
 
*/
 
int login_user(char *username)
 
{
 
  // @TODO: Put some telnet-style auth code here unless this is obselete
 
  return 1; // success
 
}
 

	
 
/**
 
 * Sends the server a single request (see protocol.h)
 
 * ohnobinki: This should hopefully work, maybe ;D
 
 *
 
 * @deprecated THIS FUNCTION SHOULD DIE VERY, VERY SOON!
 
 * (and painfully :-p)
 
*/
 
int sendSignal(struct remoteio *rem, char signal)
 
{
 

	
 
  fprintf(stderr, __FILE__ ":%d: Ignoring call to DEPRECATED and WRONG sendSignal() function. See similar message for sendExtSignal() for details\n", __LINE__);
 
  return 1;
 
  remoteio_write(rem, &signal, 1);
 

	
 
  /**
 
     if remoteio_write returned 1, the connection
 
     is probably dead or there was a real error
 
   */
 
  return 1;
 
}
 

	
 
/**
 
 * Sends the server an extended signal (request + data)
 
 * ohnobinki: I have no clue how you really want to handle this. Please clarify/edit
 
 * normaldotcom: I see more and more how clueless you are, I hope to get to his soon ;-)
 
 *
 
 * @deprecated lol
 
 */
 
int sendExtSignal(struct remoteio *rem, char signal, char *data)
 
{
 
  size_t len;
 
  char *ssignal;
 

	
 
  fprintf(stderr, __FILE__ ":%d: Ignoring call to DEPRECATED and WRONG sendExtSignal() function. First of all, this function's name is camelCase. Secondly, it doesn't use protocol.h properly. I'll show how later. Thirdly, when protocol.h and request.h (still being written) are used properly, you don't need sendExtSignal() at all.\n", __LINE__);
 
  return 1;
 

	
 
  /**
 
   * Just append the data FIXME: We should do this differently
 
   */
 
  _distren_asprintf(&ssignal, "%c%s", signal, data);
 
  len = strlen(ssignal);
 
  remoteio_write(rem, ssignal, len);
 

	
 
  return 0;
 
}
 

	
 

	
 
/* Port of web functions for standard code
 

	
 
   Currently, most functions are stubs due to lack
 
   of socket reading code
 

	
 
   ohnobinki: I can take care of a fair amount of this, but the remotio reading and writing is where you should really lay down some code.
 
*/
 

	
 
/** marks frame finished on server */
 
void finishframe(struct remoteio *rem, int jobnum, int framenum){
 
  char* data;
 
  _distren_asprintf(&data, "%d%d", jobnum, framenum);
 
  sendExtSignal(rem, DISTREN_REQUEST_DONEFRAME, data);
 
}
 

	
 
/** resets frame to unassigned on server */
 
void resetframe(struct remoteio *rem, int jobnum, int framenum){
 
  fprintf(stderr,"Resetting frame %d in job %d on server... ",framenum,jobnum);
 
  char* data;
 
  _distren_asprintf(&data, "%d%d", jobnum, framenum);
 
 sendExtSignal(rem, DISTREN_REQUEST_RESETFRAME, data);
 

	
 
}
 

	
 
/** marks frame assigned on server */
 
void startframe(struct remoteio *rem, int jobnum, int framenum){
 
  if(DEBUG)
 
    fprintf(stderr,"Marking frame %d started on server... ",framenum);
 
  char* data;
 
  _distren_asprintf(&data, "%d%d", jobnum, framenum);
 
  sendExtSignal(rem, DISTREN_REQUEST_RENDERFRAME, data);
 

	
 
}
 

	
 
/**
 
   Greets the server.
 

	
 
   We send PACKAGE_STRING as our version ping thing.
 
 */
 
int greet_server(struct remoteio *rem)
 
{
 
  int err;
 
  struct distren_request *req;
 

	
 
  err = 0;
 

	
 
  fprintf(stderr, "Saying hello to the server ;-)...\n");
 

	
 
  err += distren_request_new(&req, strlen(PACKAGE_STRING), DISTREN_REQUEST_VERSION);
 
  err += distren_request_send(rem, req, PACKAGE_STRING);
 
  err += distren_request_free(req);
 

	
 
  return err;
 
}
 

	
 
/** retrieves job from server */
 
int getwork(struct remoteio *rem, int *jobnum, int *framenum)
 
{
 
  struct distren_request *req;
 
  char *data;
 
  int len; /*< asprintf() uses int, not size_t */
 
  len = _distren_asprintf(&data, "%d.%d", jobnum, framenum);
 

	
 
  //distren_request_new(&req, 
 

	
 

	
 
  sendExtSignal(rem, DISTREN_REQUEST_GETWORK, data);
 
  return 0;
 
}
 

	
 
/** sets render power of slave on server */
 
void setrenderpower(struct remoteio *rem, int renderpower){
 
  fprintf(stderr,"Setting render power on server... ");
 
  char* data;
 
  _distren_asprintf(&data, "%d", renderpower);
 
  sendExtSignal(rem,DISTREN_REQUEST_SETRENDERPOWER, data);
 
}
 

	
 
/** retrieves render power of slave from server */
 
int getrenderpower(struct remoteio *rem){
 
  sendSignal(rem, DISTREN_REQUEST_GETRENDERPOWER);
 
  return 0;
 
}
 

	
 
/** compares slave software version with server software version */
 
int checkslaveversion(struct remoteio *rem){
 
  sendSignal(rem, DISTREN_REQUEST_GETVERSION);
 
  char* serverVersionFromRemotio = "9000";
 
  if(!strcmp(PACKAGE_VERSION, serverVersionFromRemotio)) // compare versions
 
    return 1;
 
  return 0;
 
}
src/server/tabletennis.c
Show inline comments
 
new file 100644
 
/*
 
 * 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 "distrend.h"
 
#include "listen.h"
 
#include "tabletennis.h"
 

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

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

	
 
struct tabletennis
 
{
 
  unsigned int ping_interval;
 
  unsigned int pong_time;
 

	
 
  /* of type (struct distrend_client *) */
 
  queue_t clients_to_ping;
 

	
 
  /* of type (struct distrend_client *) */
 
  queue_t clients_need_pong;
 

	
 
  struct timespec time_last_check;
 
};
 

	
 
static int tabletennis_pong_request_handle(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data);
 
static int tabletennis_ping_request_handle(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data);
 

	
 
tabletennis_t tabletennis_new(struct distrend_listens *listens, unsigned int ping_interval, unsigned int pong_time)
 
{
 
  tabletennis_t tabletennis;
 

	
 
  tabletennis = malloc(sizeof(struct tabletennis));
 

	
 
  tabletennis->ping_interval = ping_interval;
 
  tabletennis->pong_time = pong_time;
 
  tabletennis->clients_to_ping = q_init();
 
  tabletennis->clients_need_pong = q_init();
 
  clock_gettime(CLOCK_MONOTONIC, &tabletennis->time_last_check);
 

	
 
  distrend_listen_handler_add(listens, DISTREN_REQUEST_PING, &tabletennis_ping_request_handle);
 
  distrend_listen_handler_add(listens, DISTREN_REQUEST_PONG, &tabletennis_pong_request_handle);
 

	
 
  return tabletennis;
 
}
 

	
 
int tabletennis_add_client(tabletennis_t tabletennis, struct distrend_client *client)
 
{
 
  client->tabletennis_client.state = TABLETENNIS_NEED_PING;
 
  client->tabletennis_client.time_next_check = tabletennis->time_last_check.tv_sec
 
    + tabletennis->ping_interval;
 

	
 
  q_enqueue(tabletennis->clients_to_ping, client, 0);
 

	
 
  return 0;
 
}
 

	
 
int tabletennis_serve(tabletennis_t tabletennis)
 
{
 
  struct timespec time_now;
 
  struct distrend_client *client;
 
  time_t time_next_check;
 

	
 
  struct distren_request *req;
 
  void *req_data;
 
  size_t req_len;
 

	
 
  clock_gettime(CLOCK_MONOTONIC, &time_now);
 

	
 
  time_next_check = time_now.tv_sec + tabletennis->pong_time;
 
  for(client = q_front(tabletennis->clients_to_ping);
 
      client && client->tabletennis_client.time_next_check < time_now.tv_sec;
 
      client = q_front(tabletennis->clients_to_ping))
 
    {
 
      q_dequeue(tabletennis->clients_to_ping);
 

	
 
      /* use time_next_check as the ping data */
 
      req_len = distren_request_poing(&req, &req_data, 1, &time_next_check, sizeof(time_next_check));
 
      distrend_client_write_request(client, req, req_data);
 
      distren_request_free_with_data(req, req_data);
 

	
 
      client->tabletennis_client.state = TABLETENNIS_NEED_PONG;
 

	
 
      client->tabletennis_client.time_next_check = time_next_check;
 

	
 
      q_enqueue(tabletennis->clients_need_pong, client, 0);
 
    }
 

	
 
  time_next_check = time_now.tv_sec + tabletennis->ping_interval;
 
  for(client = q_front(tabletennis->clients_need_pong);
 
      client && client->tabletennis_client.time_next_check < time_now.tv_sec;
 
      client = q_front(tabletennis->clients_need_pong))
 
    {
 
      q_dequeue(tabletennis->clients_need_pong);
 

	
 
      if(client->tabletennis_client.state == TABLETENNIS_NEED_PONG)
 
	{
 
	  distrend_send_disconnect(client, "You failed to respond to a PING packet after some seconds!");
 
	  client->tabletennis_client.state = TABLETENNIS_DELINQUENT;
 
	}
 
      else /* state must be TABLETENNIS_HAS_PONG */
 
	{
 
	  client->tabletennis_client.time_next_check = time_next_check;
 
	  client->tabletennis_client.state = TABLETENNIS_NEED_PING;
 

	
 
	  q_enqueue(tabletennis->clients_to_ping, client, 0);
 
	}
 
    }
 

	
 
  /* for tabletennis_add_client() */
 
  memcpy(&tabletennis->time_last_check, &time_now, sizeof(struct timespec));
 

	
 
  return 0;
 
}
 

	
 
/**
 
 * helper for tabletennis_del_client() which just looks for a
 
 * particular pointer in the queue (a.k.a., list).
 
 */
 
static int tabletennis_del_client_traverse(void *trav_data, void *client)
 
{
 
  if(trav_data == client)
 
    return FALSE;
 

	
 
  return TRUE;
 
}
 

	
 
int tabletennis_del_client(tabletennis_t tabletennis, struct distrend_client *client)
 
{
 
  list_t thelist;
 

	
 
  thelist = NULL;
 
  switch(client->tabletennis_client.state)
 
    {
 
    case TABLETENNIS_NEED_PING:
 
      thelist = tabletennis->clients_to_ping;
 
      break;
 

	
 
    case TABLETENNIS_NEED_PONG:
 
    case TABLETENNIS_HAS_PONG:
 
      thelist = tabletennis->clients_need_pong;
 
      break;
 

	
 
    case TABLETENNIS_DELINQUENT:
 
      thelist = NULL;
 
      break;
 
    }
 

	
 
  if(!thelist)
 
    return 0;
 

	
 
  list_traverse(thelist, (void *)client, (list_traverse_func_t)&tabletennis_del_client_traverse,
 
		LIST_FORW | LIST_FRNT | LIST_SAVE);
 
  if(list_curr(thelist) == client)
 
    list_remove_curr(thelist);
 

	
 
  return 0;
 
}
 

	
 
void tabletennis_free(tabletennis_t tabletennis)
 
{
 
  q_free(tabletennis->clients_to_ping, LIST_NODEALLOC);
 
  q_free(tabletennis->clients_need_pong, LIST_NODEALLOC);
 

	
 
  free(tabletennis);
 
}
 

	
 
/* implementations of locals */
 

	
 
/**
 
 * Handles a DISTREN_REQUEST_PING from a client.
 
 *
 
 * @todo throttling?
 
 */
 
static int tabletennis_ping_request_handle(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data)
 
{
 
  struct distren_request *pong_req;
 

	
 
  if(req_len > 32)
 
    distrend_send_disconnect(client, "You have tried to send a PING packet with a length longer than 32 bytes.");
 

	
 
  /**
 
      respond to the client using the data he sent in his PONG
 
      command.
 
   */
 
  distren_request_new(&pong_req, req_len, DISTREN_REQUEST_PONG);
 
  distrend_client_write_request(client, pong_req, req_data);
 
  distren_request_free(pong_req);
 

	
 
  return 0;
 
}
 

	
 
static int tabletennis_pong_request_handle(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data)
 
{
 
  fprintf(stderr, "got pong\n");
 

	
 
  /*
 
   * The only place that sends PINGs from distrend is
 
   * tabletennis_serve() and it uses
 
   * client->tabletennis_client.time_next_check as the cookie.
 
   */
 
  if(client->tabletennis_client.state == TABLETENNIS_NEED_PONG
 
     && req_len == sizeof(client->tabletennis_client.time_next_check)
 
     && !memcmp(req_data, &client->tabletennis_client.time_next_check, sizeof(client->tabletennis_client.time_next_check)))
 
    {
 
      /* valid match */
 
      client->tabletennis_client.state = TABLETENNIS_HAS_PONG;
 

	
 
      return 0;
 
    }
 
  /* no match or invalid state */
 
  distrend_send_disconnect(client, "You tried to send a PONG packet without first receiving a PING packet.");
 
  return 0;
 
}
src/server/tabletennis.h
Show inline comments
 
new file 100644
 
/*
 
 * 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_TABLETENNIS_H
 
#define _DISTREN_TABLETENNIS_H
 

	
 
/**
 
 * @file For managing the PINGs and PONGs of our present life.
 
 *
 
 * Do not call ping pong ping pong! Any professional ping-ponger will
 
 * be immediately offended. Use tabletennis instead and prosper.
 
 */
 

	
 
#include "distrend.h"
 

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

	
 
struct tabletennis;
 
typedef struct tabletennis *tabletennis_t;
 

	
 
/**
 
 * this struct should be embedded into struct distrend_client
 
 */
 
struct tabletennis_client
 
{
 
  /*
 
   * the time that the client should be processed from the
 
   * clients_to_ping or clients_need_pong queues.
 
   */
 
  time_t time_next_check;
 

	
 
  enum tabletennis_client_state
 
    {
 
      /* in clients_to_ping */
 
      TABLETENNIS_NEED_PING,
 
      /* in clients_need_pong */
 
      TABLETENNIS_NEED_PONG,
 
      TABLETENNIS_HAS_PONG,
 
      /* not in any queue */
 
      TABLETENNIS_DELINQUENT,
 
    } state;
 
};
 

	
 
/**
 
 * Initializes the context data for tabletennis.
 
 *
 
 * @param listens to register tabletennis's PING and PONG handlers
 
 * @param ping_interval The number of seconds to wait before sending a client a PING packet.
 
 * @param pong_time The number of seconds to wait before checking if a client sent a PONG packet.
 
 */
 
tabletennis_t tabletennis_new(struct distrend_listens *listens, unsigned int ping_interval, unsigned int pong_time);
 

	
 
/**
 
 * Add a client to the tabletennis game.
 
 *
 
 * @param tabletennis the tabletennis context.
 
 * @param client the client
 
 */
 
int tabletennis_add_client(tabletennis_t tabletennis, struct distrend_client *client);
 

	
 
/**
 
 * Sends PING packets to clients that need them and checks that
 
 * clients have sent PONG packets on time.
 
 *
 
 * @param clients_to_ping a queue initialized
 
 */
 
int tabletennis_serve(tabletennis_t tabletennis);
 

	
 
/**
 
 * Remove a client to the tabletennis game.
 
 *
 
 * @param tabletennis the tabletennis context.
 
 * @param client the client
 
 */
 
int tabletennis_del_client(tabletennis_t tabletennis, struct distrend_client *client);
 

	
 
/**
 
 * Deallocates and frees the tabletennis context data.
 
 *
 
 * Does not free the distren_client structs
 
 *
 
 * @param tabletennis the tabletennis context.
 
 */
 
void tabletennis_free(tabletennis_t tabletennis);
 

	
 
#endif
0 comments (0 inline, 0 general)