Next: , Previous: , Up: Using External Event Loops   [Contents][Index]


7.9.2.3 I/O Callback Example

To actually use an external event loop, you have to implement the I/O callback functions that are used by GPGME to register and unregister file descriptors. Furthermore, you have to actually monitor these file descriptors for activity and call the appropriate I/O callbacks.

The following example illustrates how to do that. The example uses locking to show in which way the callbacks and the event loop can run concurrently. For the event loop, we use a fixed array. For a real-world implementation, you should use a dynamically sized structure because the number of file descriptors needed for a crypto operation in GPGME is not predictable.

#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <gpgme.h>

/* The following structure holds the result of a crypto operation.  */
struct op_result
{
  int done;
  gpgme_error_t err;
};

/* The following structure holds the data associated with one I/O
callback.  */
struct one_fd
{
  int fd;
  int dir;
  gpgme_io_cb_t fnc;
  void *fnc_data;
  void *loop;
};

struct event_loop
{
  pthread_mutex_t lock;
#define MAX_FDS 32
  /* Unused slots are marked with FD being -1.  */
  struct one_fd fds[MAX_FDS];
};

The following functions implement the I/O callback interface.

gpgme_error_t
add_io_cb (void *data, int fd, int dir, gpgme_io_cb_t fnc, void *fnc_data,
	   void **r_tag)
{
  struct event_loop *loop = data;
  struct one_fd *fds = loop->fds;
  int i;

  pthread_mutex_lock (&loop->lock);
  for (i = 0; i < MAX_FDS; i++)
    {
      if (fds[i].fd == -1)
	{
	  fds[i].fd = fd;
	  fds[i].dir = dir;
	  fds[i].fnc = fnc;
	  fds[i].fnc_data = fnc_data;
	  fds[i].loop = loop;
	  break;
	}
    }
  pthread_mutex_unlock (&loop->lock);
  if (i == MAX_FDS)
    return gpg_error (GPG_ERR_GENERAL);
  *r_tag = &fds[i];
  return 0;
}

void
remove_io_cb (void *tag)
{
  struct one_fd *fd = tag;
  struct event_loop *loop = fd->loop;

  pthread_mutex_lock (&loop->lock);
  fd->fd = -1;
  pthread_mutex_unlock (&loop->lock);
}

void
event_io_cb (void *data, gpgme_event_io_t type, void *type_data)
{
  struct op_result *result = data;

  /* We don't support list operations here.  */
  if (type == GPGME_EVENT_DONE)
    {
      result->done = 1;
      result->err = *type_data;
    }
}

The final missing piece is the event loop, which will be presented next. We only support waiting for the success of a single operation.

int
do_select (struct event_loop *loop)
{
  fd_set rfds;
  fd_set wfds;
  int i, n;
  int any = 0;
  struct timeval tv;
  struct one_fd *fdlist = loop->fds;

  pthread_mutex_lock (&loop->lock);
  FD_ZERO (&rfds);
  FD_ZERO (&wfds);
  for (i = 0; i < MAX_FDS; i++)
    if (fdlist[i].fd != -1)
      FD_SET (fdlist[i].fd, fdlist[i].dir ? &rfds : &wfds);
  pthread_mutex_unlock (&loop->lock);

  tv.tv_sec = 0;
  tv.tv_usec = 1000;

  do
    {
      n = select (FD_SETSIZE, &rfds, &wfds, NULL, &tv);
    }
  while (n < 0 && errno == EINTR);

  if (n < 0)
    return n;	/* Error or timeout.  */

  pthread_mutex_lock (&loop->lock);
  for (i = 0; i < MAX_FDS && n; i++)
    {
      if (fdlist[i].fd != -1)
	{
	  if (FD_ISSET (fdlist[i].fd, fdlist[i].dir ? &rfds : &wfds))
	    {
	      assert (n);
	      n--;
	      any = 1;
              /* The I/O callback handler can register/remove callbacks,
                 so we have to unlock the file descriptor list.  */
              pthread_mutex_unlock (&loop->lock);
	      (*fdlist[i].fnc) (fdlist[i].fnc_data, fdlist[i].fd);
              pthread_mutex_lock (&loop->lock);
	    }
	}
    }
  pthread_mutex_unlock (&loop->lock);
  return any;
}

void
wait_for_op (struct event_loop *loop, struct op_result *result)
{
  int ret;

  do
    {
      ret = do_select (loop);
    }
  while (ret >= 0 && !result->done);
}

The main function shows how to put it all together.

int
main (int argc, char *argv[])
{
  struct event_loop loop;
  struct op_result result;
  gpgme_ctx_t ctx;
  gpgme_error_t err;
  gpgme_data_t sig, text;
  int i;
  pthread_mutexattr_t attr;
  struct gpgme_io_cbs io_cbs =
  {
    add_io_cb,
    &loop,
    remove_io_cb,
    event_io_cb,
    &result
  };

  init_gpgme ();

  /* Initialize the loop structure.  */

  /* The mutex must be recursive, since remove_io_cb (which acquires a
     lock) can be called while holding a lock acquired in do_select.  */
  pthread_mutexattr_init (&attr);
  pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
  pthread_mutex_init (&loop.lock, &attr);
  pthread_mutexattr_destroy (&attr);

  for (i = 0; i < MAX_FDS; i++)
    loop.fds[i].fd = -1;

  /* Initialize the result structure.  */
  result.done = 0;

  err = gpgme_data_new_from_file (&sig, "signature", 1);
  if (!err)
    err = gpgme_data_new_from_file (&text, "text", 1);
  if (!err)
    err = gpgme_new (&ctx);
  if (!err)
    {
       gpgme_set_io_cbs (ctx, &io_cbs);
       err = gpgme_op_verify_start (ctx, sig, text, NULL);
    }
  if (err)
    {
      fprintf (stderr, "gpgme error: %s: %s\n",
               gpgme_strsource (err), gpgme_strerror (err));
      exit (1);
    }

  wait_for_op (&loop, &result);
  if (!result.done)
    {
      fprintf (stderr, "select error\n");
      exit (1);
    }
  if (!result.err)
    {
      fprintf (stderr, "verification failed: %s: %s\n",
               gpgme_strsource (result.err), gpgme_strerror (result.err));
      exit (1);
    }
  /* Evaluate verify result.  */
  …
  return 0;
}

Next: , Previous: , Up: Using External Event Loops   [Contents][Index]