static gboolean
plain_sockets_client_side_watch (GIOChannel   *source,
                                 GIOCondition  condition,
                                 gpointer      data)
{
  ClientData *cd = data;
  int fd = g_io_channel_unix_get_fd (source);

  if (condition & G_IO_IN)
    {
      read_and_drop_on_floor (fd, echo_return_size, cd->vtable->fake_malloc_overhead);
    }
  else if (condition & G_IO_OUT)
    {
      cd->iterations += 1;
      if (cd->iterations >= N_ITERATIONS)
        {
          g_printerr ("\nCompleted %d iterations\n", N_ITERATIONS);
          g_main_loop_quit (cd->loop);
        }
      else if (cd->iterations % (N_ITERATIONS/N_PROGRESS_UPDATES) == 0)
        {
          g_printerr ("%d%% ", (int) (cd->iterations/(double)N_ITERATIONS * 100.0));
        }
      
      write_junk (fd, echo_call_size, cd->vtable->fake_malloc_overhead);
    }
  else
    {
      g_printerr ("Unexpected IO condition in client thread\n");
      exit (1);
    }

  return TRUE;
}
static gboolean
plain_sockets_talk_to_client_watch (GIOChannel   *source,
                                    GIOCondition  condition,
                                    gpointer      data)
{
  PlainSocketServer *server = data;
  int client_fd = g_io_channel_unix_get_fd (source);
  
  if (condition & G_IO_HUP)
    {
      g_printerr ("Client disconnected from server\n");
      server->sd->n_clients -= 1;
      if (server->sd->n_clients == 0)
        g_main_loop_quit (server->sd->loop);

      return FALSE; /* remove watch */
    }
  else if (condition & G_IO_IN)
    {
      server->sd->handled += 1;

      read_and_drop_on_floor (client_fd, echo_call_size, server->vtable->fake_malloc_overhead);
      write_junk (client_fd, echo_return_size, server->vtable->fake_malloc_overhead);
    }
  else
    {
      g_printerr ("Unexpected IO condition in server thread\n");
      exit (1);
    }

  return TRUE;
}
int main(int argc, char *argv[])
{
	int opt, fd;
	unsigned long i, mthreads = 0, nthreads = 1;
	char *fname = NULL;
	int flags = 0, sync_options = 0;
	uint64_t file_size = 0;
	pid_t pid;
	int status;

	while ((opt = getopt(argc, argv, "b:dn:l:srfm:")) != -1) {
		switch (opt) {
		case 'd':
			flags |= O_DIRECT;
			break;
		case 'n':
			nthreads = strtoul(optarg, NULL, 0);
			break;
		case 'm':
			mthreads = strtoul(optarg, NULL, 0);
			break;
		case 'l':
			file_size = strtoull(optarg, NULL, 0);
			break;
		case 's':
			flags |= O_SYNC;
			break;
		case 'b':
			bufsize = strtoul(optarg, NULL, 0);
			break;
		case 'r':
			sync_options |= SYNC_RANGE;
			break;
		case 'f':
			sync_options |= SYNC_FILE;
			break;
		default:
			help(argv[0]);
			return 4;
		}
	}

	if (optind != argc - 1 || file_size < 1) {
		help(argv[0]);
		return 1;
	}

	fname = argv[optind];

	if (!seed_random())
		return 2;

	// truncate first
	fd = open(fname, flags | O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
	if (fd < 0) {
		perror(fname);
		return 3;
	}
	status = posix_fallocate(fd, 0, file_size);
#if 0
	if (status) {
		perror(fname);
		return 4;
	}
#endif
	close(fd);

	// spawn threads and go to town
	if (nthreads == 1)
		return write_junk(fname, flags, sync_options, file_size);

	for (i = 0; i < nthreads; i++) {
		pid = fork();
		if (!pid)
			return write_junk(fname, flags, sync_options, file_size);
	}

	for (i = 0; i < mthreads; i++) {
		pid = fork();
		if (!pid)
			return mmap_junk(fname, flags, sync_options, file_size);
	}

	for (i = 0; i < mthreads + nthreads; i++) {
		pid = wait(&status);
		if (WIFEXITED(status))
			printf("Child %d exited with status %d\n", pid, WEXITSTATUS(status));
		else if (WIFSIGNALED(status))
			printf("Child %d exited with signal %d\n", pid, WTERMSIG(status));
	}

	return 0;
}
static void*
plain_sockets_thread_func (void *data)
{
  GMainContext *context;
  ClientData cd;
  int fd;
  struct sockaddr_un addr;
  GIOChannel *channel;
  GSource *gsource;
  
  g_printerr ("Starting client thread %p\n",
              g_thread_self());
  
  fd = socket (PF_UNIX, SOCK_STREAM, 0);
  
  if (fd < 0)
    {
      g_printerr ("Failed to create socket: %s",
                  strerror (errno)); 
      exit (1);
    }

  _DBUS_ZERO (addr);
  addr.sun_family = AF_UNIX;

#ifdef HAVE_ABSTRACT_SOCKETS
  /* remember that abstract names aren't nul-terminated so we rely
   * on sun_path being filled in with zeroes above.
   */
  addr.sun_path[0] = '\0'; /* this is what says "use abstract" */
  strncpy (&addr.sun_path[1], plain_sockets_address, _DBUS_MAX_SUN_PATH_LENGTH - 2);
  /* _dbus_verbose_bytes (addr.sun_path, sizeof (addr.sun_path)); */
#else /* HAVE_ABSTRACT_SOCKETS */
  strncpy (addr.sun_path, plain_sockets_address, _DBUS_MAX_SUN_PATH_LENGTH - 1);
#endif /* ! HAVE_ABSTRACT_SOCKETS */
  
  if (connect (fd, (struct sockaddr*) &addr, sizeof (addr)) < 0)
    {      
      g_printerr ("Failed to connect to socket %s: %s",
                  plain_sockets_address, strerror (errno));
      exit (1);
    }

  context = g_main_context_new ();

  cd.iterations = 1;
  cd.loop = g_main_loop_new (context, FALSE);
  cd.vtable = data;

  channel = g_io_channel_unix_new (fd);
  
  gsource = g_io_create_watch (channel,
                               G_IO_IN | G_IO_OUT |
                               G_IO_ERR | G_IO_HUP | G_IO_NVAL | G_IO_PRI);

  g_source_set_callback (gsource,
                         (GSourceFunc)plain_sockets_client_side_watch,
                         &cd, NULL);

  g_source_attach (gsource, context);

  g_io_channel_unref (channel);

  g_printerr ("Client thread writing to prime pingpong\n");
  write_junk (fd, echo_call_size, cd.vtable->fake_malloc_overhead);
  g_printerr ("Client thread done writing primer\n");

  g_printerr ("Client thread entering main loop\n");
  g_main_loop_run (cd.loop);
  g_printerr ("Client thread %p exiting main loop\n",
              g_thread_self());

  g_source_destroy (gsource);
  
  close (fd);
  
  g_main_loop_unref (cd.loop);
  g_main_context_unref (context);

  return NULL;
}