static dsk_boolean
resize_mmap (TrivialTableCheckpoint *cp,
             unsigned                min_needed,
             DskError              **error)
{
  unsigned new_size = (min_needed + MMAP_MIN_RESIZE_GROW + MMAP_GRANULARITY - 1)
                    / MMAP_GRANULARITY
                    * MMAP_GRANULARITY;
  void *mmapped;
  if (munmap ((void*) cp->mmapped, cp->mmapped_size) < 0)
    dsk_warning ("error calling munmap(): %s", strerror (errno));
  if (ftruncate (cp->fd, new_size) < 0)
    {
      dsk_set_error (error, "error expanding file to %u bytes: %s",
                     new_size, strerror (errno));
      return DSK_FALSE;
    }
  mmapped = mmap (NULL, new_size, PROT_READ|PROT_WRITE,
                  MAP_SHARED, cp->fd, 0);
  if (mmapped == MAP_FAILED)
    {
      dsk_set_error (error, "mmap of %u bytes failed: %s",
                     new_size, strerror (errno));
      return DSK_FALSE;
    }
  cp->mmapped = mmapped;
  cp->mmapped_size = new_size;
  return DSK_TRUE;
}
Esempio n. 2
0
static dsk_boolean
handle_incoming_connection (DskOctetListener *listener)
{
  DskHttpServerStream *stream;

  /* accept connection */
  switch (dsk_octet_listener_accept (listener, NULL,
                                     &source, &sink, &error))
    {
    case DSK_IO_RESULT_SUCCESS:
      /* create http-server stream */
      stream = dsk_http_server_stream_new (sink, source, &server_stream_options);
      dsk_assert (stream != NULL);
      dsk_hook_trap (&stream->request_available,
                     (DskHookFunc) handle_http_stream_request_available,
                     stream,
                     dsk_object_unref);
      break;
    case DSK_IO_RESULT_AGAIN:
      break;
    case DSK_IO_RESULT_ERROR:
      dsk_main_exit (1);
      dsk_warning ("cannot accept a new socket: %s",
                   error->message);
      dsk_error_unref (error);
      error = NULL;
      return DSK_FALSE;
    case DSK_IO_RESULT_EOF:
      dsk_assert_not_reached ();
    }

  /* invoke this handler when the next incoming connection is available. */
  return DSK_TRUE;
}
static void
table_checkpoint_trivial__destroy(DskTableCheckpoint *checkpoint)
{
  TrivialTableCheckpoint *cp = (TrivialTableCheckpoint *) checkpoint;
  if (munmap ((void*) cp->mmapped, cp->mmapped_size) < 0)
    dsk_warning ("error calling munmap(): %s", strerror (errno));
  close (cp->fd);
  dsk_free (cp);
}
Esempio n. 4
0
DskOctetFilter *dsk_bz2lib_decompressor_new   (void)
{
  DskBz2libDecompressor *rv = dsk_object_new (&dsk_bz2lib_decompressor_class);
  int zrv;
  zrv = BZ2_bzDecompressInit (&rv->bz2lib, DSK_FALSE, DSK_FALSE);
  if (zrv != BZ_OK)
    {
      dsk_warning ("BZ2_bzDecompressInit returned error");
      dsk_object_unref (rv);
      return NULL;
    }
  rv->initialized = DSK_TRUE;
  return DSK_OCTET_FILTER (rv);
}
Esempio n. 5
0
DskOctetFilter *dsk_bz2lib_compressor_new   (unsigned    level)
{
  DskBz2libCompressor *rv = dsk_object_new (&dsk_bz2lib_compressor_class);
  int zrv;
  zrv = BZ2_bzCompressInit (&rv->bz2lib, level, DSK_FALSE, 0);
  if (zrv != BZ_OK)
    {
      dsk_warning ("deflateInit2 returned error: %s", bzrv_to_string (zrv));
      dsk_object_unref (rv);
      return NULL;
    }
  rv->initialized = DSK_TRUE;
  return DSK_OCTET_FILTER (rv);
}
Esempio n. 6
0
void dsk_daemon_maybe_fork (void)
{
  int fork_pipe_fds[2] = {-1,-1};
  if (dsk_daemon_do_fork)
    {
      int pid;
    retry_pipe:
      if (pipe (fork_pipe_fds) < 0)
        {
	  if (errno == EINTR)
	    goto retry_pipe;
          dsk_fd_creation_failed (errno);
	  dsk_die ("error creating pipe: %s", strerror (errno));
	}
    retry_daemon_fork:
      pid = fork ();
      if (pid < 0)
        {
	  if (errno == EINTR)
	    goto retry_daemon_fork;
	  dsk_die ("error forking daemon: %s", strerror (errno));
	}
      else if (pid > 0)
        {
	  /* wait for EOF on pipe */
	  close (fork_pipe_fds[1]);
	  char buf[1];
	  for (;;)
	    {
	      int nread = read (fork_pipe_fds[0], buf, 1);
	      if (nread < 0)
	        {
		  if (errno == EINTR)
		    continue;
		  dsk_die ("error reading from semaphore pipe: %s", strerror (errno));
		}
              else if (nread > 0)
		dsk_die ("somehow got data on semaphore pipe: %s:%u", __FILE__, __LINE__);
	      else
	        break;
	    }
	  _exit (0);
        }
      else
        {
	  /* child process: continue as the non-forking case. */
	  close (fork_pipe_fds[0]);
          setsid ();
	}
    }
  int pid_file_fd = -1;
  if (dsk_daemon_pid_filename)
    {
      dsk_boolean must_truncate = DSK_FALSE;
      dsk_boolean made_dir = DSK_FALSE;

retry_outer_pid_file_open:
      if ((pid_file_fd=open (dsk_daemon_pid_filename, O_CREAT|O_EXCL|O_WRONLY, 0666)) < 0)
        {
          if (errno == EINTR)
            goto retry_outer_pid_file_open;
          else if (errno == EEXIST)
            {
              /* open / lock-nonblocking / rewrite we get lock */
retry_inner_pid_file_open:
              if ((pid_file_fd=open (dsk_daemon_pid_filename, O_WRONLY, 0666)) < 0)
                {
                  if (errno == EINTR)
                    goto retry_inner_pid_file_open;
                  dsk_die ("daemonize: error opening lock file %s: %s", dsk_daemon_pid_filename,
                           strerror (errno));
                }
              must_truncate = DSK_TRUE;
            }
          else if (errno == ENOENT && !made_dir)
            {
              /* make directories, retry */
              char *slash = strrchr (dsk_daemon_pid_filename, '/');
              if (slash == NULL)
                dsk_die ("daemonize: error creating %s: no such file or dir (cwd does not exist?)", dsk_daemon_pid_filename);
              char *dir = dsk_strdup_slice (dsk_daemon_pid_filename, slash);
              DskError *error = NULL;
              if (!dsk_mkdir_recursive (dir, 0777, &error))
                dsk_die ("error making directory %s: %s", dir, error->message);
              dsk_free (dir);
              made_dir = DSK_TRUE;
              goto retry_outer_pid_file_open;
            }
          else
            {
              dsk_fd_creation_failed (errno);
              dsk_die ("daemonize: error creating PID file %s: %s",
                       dsk_daemon_pid_filename, strerror (errno));
            }
        }
retry_flock:
      if (flock (pid_file_fd, LOCK_EX|LOCK_NB) < 0)
        {
          if (errno == EINTR)
            goto retry_flock;
          if (errno == EWOULDBLOCK)
            {
              /* TODO: print PID */
              dsk_die ("daemonize: process already running");
            }
          dsk_die ("daemonize: error locking: %s", strerror (errno));
        }
      if (must_truncate)
        {
          ftruncate (pid_file_fd, 0);
        }
      char buf[32];
      snprintf (buf, sizeof (buf), "%u\n", (unsigned)getpid ());
      unsigned len = strlen (buf);
      unsigned written = 0;
      while (written < len)
        {
          int write_rv = write (pid_file_fd, buf + written, len - written);
          if (write_rv < 0)
            {
              if (errno == EINTR)
                continue;
              dsk_die ("error writing pid file %s", dsk_daemon_pid_filename);
            }
          written += write_rv;
        }
    }
  if (fork_pipe_fds[1] != -1)
    {
      close (fork_pipe_fds[1]);
    }

  if (dsk_daemon_watchdog)
    {
      int alert_pid = 0;
      unsigned last_alert_time = 0;
      for (;;)
        {
	  /* NOTE: must never die, i guess */
          int pid;
          int status;

retry_watchdog_fork:
	  pid = fork ();
	  if (pid < 0)
	    {
	      if (errno == EINTR)
	        goto retry_watchdog_fork;
              dsk_die ("error forking watchdogged process: %s", strerror (errno));
	    }
	  else if (pid == 0)
	    {
              if (pid_file_fd >= 0)
                close (pid_file_fd);
              maybe_redirect_stdouterr ();
              add_maybe_redirect_timer ();
	      return;
	    }
	  maybe_redirect_stdouterr ();
          char time_str[TIME_STR_LENGTH];
	  make_time_str (time_str);
	  fprintf (stderr, "%s: watchdog: forked process %u\n",
	           time_str, (unsigned) pid);
retry_waitpid:
          if (waitpid (pid, &status, 0) < 0)
	    {
	      if (errno == EINTR)
		goto retry_waitpid;
	      dsk_die ("error running waitpid %u: %s", pid, strerror (errno));
	    }
	  maybe_redirect_stdouterr ();
	  make_time_str (time_str);
	  if (WIFEXITED (status))
	    fprintf (stderr, "%s: watchdog: process %u exited with status %u\n",
		     time_str, pid, WEXITSTATUS (status));
	  else if (WIFSIGNALED (status))
	    fprintf (stderr, "%s: watchdog: process %u killed by signal %u%s\n",
		     time_str, pid, WTERMSIG (status),
		     WCOREDUMP (status) ? " [core dumped]" : "");
          else
	    fprintf (stderr, "%s: watchdog: process %u died in some creative way\n",
		     time_str, pid);

          /* configurable? */
          sleep (1);

	  /* send alert */
	  if (dsk_daemon_alert_script)
	    {
	      int time_delta = time (NULL) - last_alert_time;
              unsigned clamped_delta = time_delta < 0 ? 0 : time_delta;
              if (alert_pid > 0)
                {
                  int rv = waitpid (alert_pid, &status, WNOHANG);
                  if (rv < 0)
                    {
                      if (errno == EINTR)
                        goto retry_waitpid;
                      else
                        dsk_die ("error waiting for alert process");
                    }
                  else if (rv == 0)
                    {
                      /* process has not terminated */
                    }
                  else
                    {
                      /* process terminated (ignore status?) */
                      alert_pid = 0;
                    }
                }
	      if (alert_pid == 0 && clamped_delta > dsk_daemon_alert_interval)
	        {
                  retry_alert_fork:
                  alert_pid = fork ();
                  if (alert_pid < 0)
                    {
                      if (errno == EINTR)
                        goto retry_alert_fork;
                      dsk_warning ("error forking alert process: %s", strerror (errno));
                      alert_pid = 0;
                    }
                  else if (alert_pid == 0)
                    {
                      execl ("/bin/sh", "/bin/sh", "-c", dsk_daemon_alert_script, NULL);
                      _exit (127);
                    }
                  else
                    dsk_daemon_n_alerts_suppressed = 0;
		}
              else
	        ++dsk_daemon_n_alerts_suppressed;
	    }
	}
    }
}
Esempio n. 7
0
static void
test_various_write_seek_1 (const char *name,
                           unsigned    n_entries,
                           TestEntry  *entries,
                           unsigned    n_negative,
                           TestEntry  *neg_entries)
{
  DskError *error = NULL;
  unsigned i;
  DskTableFileInterface *iface = &dsk_table_file_interface_trivial;
  DskTableFileWriter *writer;
  DskTableFileSeeker *seeker;

  if (cmdline_verbose)
    fprintf (stderr, "running dataset %s [%u]\n", name, n_entries);
  else
    fprintf (stderr, ".");

  writer = iface->new_writer (iface, location, "base", &error);
  if (writer == NULL)
    dsk_die ("%s", error->message);
  for (i = 0; i < n_entries; i++)
    {
      TestEntry *e = entries + i;
      if (!writer->write (writer,
                          strlen (e->key), (uint8_t*) e->key,
                          strlen (e->value), (uint8_t*) e->value,
                          &error))
        dsk_die ("error writing: %s", error->message);
    }
  if (!writer->close (writer, &error))
    dsk_die ("error closing writer: %s", error->message);
  writer->destroy (writer);

  /* --- now test seeker --- */

  /* pick the step size. */
  {
  static unsigned prime_table[] = { 29, 31, 37, 41, 43, 47, 53, 59, 61, 67 };
  unsigned *p_ptr = prime_table + DSK_N_ELEMENTS (prime_table) - 1;
  unsigned max_test, n_test, test_i, step;
  while (n_entries % *p_ptr == 0)
    p_ptr--;
  step = *p_ptr % n_entries;

  /* create seeker */
  seeker = iface->new_seeker (iface, location, "base", &error);
  if (seeker == NULL)
    dsk_die ("error creating seeker from newly finished writer: %s",
             error->message);

  max_test = cmdline_slow ? 100000 : 1000;
  n_test = DSK_MIN (n_entries, max_test);
  test_i = step;
  for (i = 0; i < n_test; i++)
    {
      unsigned key_len, value_len;
      const uint8_t *key_data, *value_data;
      /* do the seek */
      if (!seeker->find (seeker,
                         str_test_func,
                         (void*) entries[test_i].key,
                         DSK_TABLE_FILE_FIND_ANY,
                         &key_len, &key_data,
                         &value_len, &value_data,
                         &error))
        {
          if (error)
            dsk_die ("error doing find that should have succeeded: %s",
                     error->message);
          else
            dsk_die ("not found doing find that should have succeeded");
        }
#if 0
      dsk_warning ("test=%s, got result %.*s", entries[test_i].key,
                   (int) key_len, key_data);
#endif
      dsk_assert (key_len == strlen (entries[test_i].key));
      dsk_assert (value_len == strlen (entries[test_i].value));
      dsk_assert (memcmp (key_data, entries[test_i].key, key_len) == 0);
      dsk_assert (memcmp (value_data, entries[test_i].value, value_len) == 0);

      /* advance test_i */
      test_i += step;
      if (test_i >= n_entries)
        test_i -= n_entries;
    }

  /* do negative tests */
  for (i = 0; i < n_negative; i++)
    {
      unsigned key_len, value_len;
      const uint8_t *key_data, *value_data;
      /* do the seek */
      if (!seeker->find (seeker,
                         str_test_func,
                         (void*) neg_entries[i].key,
                         DSK_TABLE_FILE_FIND_ANY,
                         &key_len, &key_data,
                         &value_len, &value_data,
                         &error))
        {
          if (error)
            dsk_die ("error doing find that should have returned nothing: %s",
                     error->message);
        }
      else if (key_len == strlen (neg_entries[i].key)
               && memcmp (key_data, neg_entries[i].key, key_len) == 0)
        {
          dsk_die ("found result when none expected");
        }
    }


  seeker->destroy (seeker);
  }
}
Esempio n. 8
0
static Game *
create_game (const char *name,
             unsigned    width,
             unsigned    height)

{
  Game *game = dsk_malloc (sizeof (Game));
  unsigned usize;
  unsigned i;

  game->name = dsk_strdup (name);
  game->next_game = all_games;
  all_games = game;
  game->universe_width = width;
  game->universe_height = height;
  usize = width * height;
  game->h_walls = generate_ones (usize);
  game->v_walls = generate_ones (usize);
  for (i = 0; i < N_OBJECT_TYPES; i++)
    game->objects[i] = NULL;
  game->generators = NULL;
  game->cells = dsk_malloc0 (sizeof (Cell) * width * height);
  game->latest_update = 0;
  game->wrap = DSK_TRUE;
  game->diag_bullets_bounce = DSK_TRUE;
  game->bullet_kills_player = DSK_TRUE;
  game->bullet_kills_generator = DSK_TRUE;
  game->pending_updates = NULL;

  /* Generate with Modified Kruskals Algorithm, see 
   *    http://en.wikipedia.org/wiki/Maze_generation_algorithm
   */
  TmpWall *tmp_walls = dsk_malloc (sizeof (TmpWall) * usize * 2);
  TmpSetInfo *sets = dsk_malloc (sizeof (TmpSetInfo) * usize);

  /* connect the walls together in random order */
  unsigned *scramble;
  scramble = dsk_malloc (sizeof (unsigned) * usize * 2);
  for (i = 0; i < usize * 2; i++)
    scramble[i] = i;
  for (i = 0; i < usize * 2; i++)
    swap_ints (scramble + random_int_range (usize * 2), scramble + random_int_range (usize * 2));

  TmpWall *wall_list = NULL;
  for (i = 0; i < usize * 2; i++)
    {
      unsigned e = scramble[i];
      unsigned h = e % 2;
      unsigned x = (e / 2) % width;
      unsigned y = e / (width * 2);
      if (!game->wrap)
        {
          if ((h && y == 0) || (!h && x == 0))
            continue;
        }
      tmp_walls[e].prev = NULL;
      tmp_walls[e].next = wall_list;
      if (wall_list)
        wall_list->prev = tmp_walls + e;
      wall_list = tmp_walls + e;
    }

  for (i = 0; i < usize; i++)
    {
      sets[i].set_number = i;
      sets[i].next_in_set = sets + i;
    }

  while (wall_list != NULL)
    {
      /* Invariants:
           - the sets are in a ring by set number.
           - The wall_list only consists of walls that separate distinct sets.
       */

      /* remove wall */
      unsigned e = wall_list - tmp_walls;
      unsigned h = e % 2;
      unsigned x = (e / 2) % width;
      unsigned y = e / (width * 2);
      TmpSetInfo *si = sets + e / 2;
      TmpSetInfo *osi;
      if (h)
        {
          if (y == 0)
            osi = si + (height - 1) * width;
          else
            osi = si - width;
          game->h_walls[x + y * width] = 0;
        }
      else
        {
          if (x == 0)
            osi = si + width - 1;
          else
            osi = si - 1;
          game->v_walls[x + y * width] = 0;
        }
      dsk_assert (osi->set_number != si->set_number);
      TmpSetInfo *kring = osi->set_number < si->set_number ? osi : si;              /* ring to keep */
      TmpSetInfo *dring = osi->set_number < si->set_number ? si : osi;              /* ring to change */
      TmpSetInfo *dring_start = dring;

      /* combine sets (removing any walls that no longer separate different sets
         from the list of walls to remove) */
      unsigned set = kring->set_number;
      do
        {
          unsigned x = (dring - sets) % width;
          unsigned y = (dring - sets) / width;
          int wall_idx;
          dring->set_number = set;

#if 0
          if (wall_list)
            {
              dsk_assert (wall_list->prev == NULL);
              TmpWall *t;
              for (t = wall_list; t; t = t->next)
                if (t->next)
                  dsk_assert (t->next->prev == t);
            }
#endif

          /* Maybe remove left wall from candidate set of walls. */
          wall_idx = -1;
          if (x > 0 && (dring-1)->set_number == set)
            wall_idx = 2 * (x + y * width);
          else if (x == 0 && game->wrap && (dring+width-1)->set_number == set)
            wall_idx = 2 * (x + y * width);
          if (wall_idx >= 0)
            remove_tmp_wall (tmp_walls, wall_idx, &wall_list);

          /* Maybe remove right wall from candidate set of walls. */
          wall_idx = -1;
          if (x < width - 1 && (dring+1)->set_number == set)
            wall_idx = 2 * ((x+1) + y * width);
          else if (x == width - 1 && game->wrap && (dring-width+1)->set_number == set)
            wall_idx = 2 * (0 + y * width);
          if (wall_idx >= 0)
            remove_tmp_wall (tmp_walls, wall_idx, &wall_list);

          /* Maybe remove top wall from candidate set of walls. */
          wall_idx = -1;
          if (y > 0 && (dring-width)->set_number == dring->set_number)
            wall_idx = 2 * (x + y * width) + 1;
          else if (y == 0 && game->wrap && (dring+width*(height-1))->set_number == set)
            wall_idx = 2 * (x + y * width) + 1;
          if (wall_idx >= 0)
            remove_tmp_wall (tmp_walls, wall_idx, &wall_list);

          /* Maybe remove bottom wall from candidate set of walls. */
          wall_idx = -1;
          if (y < height - 1 && (dring+width)->set_number == set)
            wall_idx = 2 * (x + (y+1) * width) + 1;
          else if (y == height - 1 && game->wrap && (dring-(height-1)*width)->set_number == set)
            wall_idx = 2 * (x + 0 * width) + 1;
          if (wall_idx >= 0)
            remove_tmp_wall (tmp_walls, wall_idx, &wall_list);

          dring = dring->next_in_set;
        }
      while (dring != dring_start);

      /* Merge the rings */
      TmpSetInfo *old_dring_next = dring->next_in_set;
      dring->next_in_set = kring->next_in_set;
      kring->next_in_set = old_dring_next;

    }

  dsk_free (tmp_walls);
  dsk_free (sets);
  dsk_free (scramble);

  /* generate generators */
  unsigned n_generators = 12 + rand () % 6;
  dsk_warning ("%u generators", n_generators);
  i = 0;
  while (i < n_generators)
    {
      unsigned idx = random_int_range (usize);
      Cell *cell = game->cells + idx;
      if (cell->generator == NULL)
        {
          cell->generator = dsk_malloc (sizeof (Generator));
          cell->generator->game = game;
          cell->generator->x = (idx % width) * CELL_SIZE + CELL_SIZE/2;
          cell->generator->y = (idx / width) * CELL_SIZE + CELL_SIZE/2;
          dsk_warning ("created generator at %u,%u",cell->generator->x ,cell->generator->y);
          cell->generator->generator_prob = 0.01;
          cell->generator->next_in_game = game->generators;
          cell->generator->prev_in_game = NULL;
          if (game->generators)
            game->generators->prev_in_game = cell->generator;
          game->generators = cell->generator;

          i++;
        }
    }

  game->timer = dsk_main_add_timer_millis (update_period_msecs,
                                    (DskTimerFunc) game_update_timer_callback,
                                    game);
  return game;
}
static DskTableCheckpoint *
table_checkpoint_trivial__open   (DskTableCheckpointInterface *iface,
                                  DskDir             *dir,
                                  const char         *basename,
                                  unsigned           *cp_data_len_out,
                                  uint8_t           **cp_data_out,
                                  DskTableCheckpointReplayFunc func,
                                  void               *func_data,
                                  DskError          **error)
{
  int fd = -1;
  struct stat stat_buf;
  void *mmapped = NULL;
  TrivialTableCheckpoint *rv;
  unsigned version, cp_data_len;
  unsigned at;

  DSK_UNUSED (iface);

  /* open fd */
  fd = dsk_dir_openfd (dir, basename,
                       DSK_DIR_OPENFD_WRITABLE, 0, error);
  if (fd < 0)
    return NULL;

  /* fstat */
  if (fstat (fd, &stat_buf) < 0)
    {
      dsk_set_error (error, "fstat of %s failed: %s",
                     basename, strerror (errno));
      goto error_cleanup;
    }

  /* mmap */
  mmapped = mmap (NULL, stat_buf.st_size, PROT_READ|PROT_WRITE,
                  MAP_SHARED, fd, 0);
  if (mmapped == MAP_FAILED)
    {
      dsk_set_error (error, "mmap of %u bytes (file %s) failed: %s",
                     (unsigned) stat_buf.st_size, basename, strerror (errno));
      goto error_cleanup;
    }
  
  /* check format */
  if (((uint32_t*)mmapped)[0] != UINT32_TO_LE (TRIVIAL_CP_MAGIC))
    {
      dsk_set_error (error, "checkpoint file %s has bad magic", basename);
      goto error_cleanup;
    }
  version = UINT32_FROM_LE (((uint32_t*)mmapped)[1]);
  if (version != 1)
    {
      dsk_set_error (error, "checkpoint file %s has bad version %u",
                     basename, version);
      goto error_cleanup;
    }
  cp_data_len = UINT32_FROM_LE (((uint32_t*)mmapped)[2]);

  /* replay */
  at = 12 + (cp_data_len + 3) / 4 * 4;
  if (at + 4 > stat_buf.st_size)
    {
      dsk_set_error (error, "checkpoint data length (%u) too big for file's length (%u)",
                     cp_data_len, (unsigned) stat_buf.st_size);
      goto error_cleanup;
    }
  while (* (uint32_t*) (mmapped+at) != 0xffffffff)
    {
      uint32_t key_len, value_len;
      uint32_t kv_len, kvp_len;
      if (at + 8 > stat_buf.st_size)
        {
          dsk_set_error (error, "checkpoint entry header too long");
          goto error_cleanup;
        }
      key_len = UINT32_FROM_LE (((uint32_t *) (mmapped+at))[0]);
      value_len = UINT32_FROM_LE (((uint32_t *) (mmapped+at))[1]);
      kv_len = key_len + value_len;
      if (kv_len < key_len)
        {
          dsk_set_error (error, "key+value length greater than 4G");
          goto error_cleanup;
        }
      kvp_len = (kv_len + 3) / 4 * 4;
      if (at + 8 + kvp_len + 4 > stat_buf.st_size)
        {
          dsk_set_error (error, "checkpoint entry data too long");
          goto error_cleanup;
        }
      if (!func (key_len, mmapped + at + 8,
                 value_len, mmapped + at + 8 + key_len,
                 func_data, error))
        {
          if (error && !*error)
            dsk_set_error (error, "replay handler returned false but didn't set error");
          goto error_cleanup;
        }
      at += 8 + kvp_len;
    }

  /* copy cp_data */
  if (cp_data_len_out != NULL)
    *cp_data_len_out = cp_data_len;
  if (cp_data_out != NULL)
    {
      *cp_data_out = dsk_malloc (cp_data_len);
      memcpy (*cp_data_out, mmapped + 12, cp_data_len);
    }
  rv = DSK_NEW (TrivialTableCheckpoint);
  rv->base = table_checkpoint_trivial__vfuncs;
  rv->fd = fd;
  rv->mmapped = mmapped;
  rv->mmapped_size = stat_buf.st_size;
  rv->cur_size = at;
  return &rv->base;

error_cleanup:
  if (mmapped != NULL && munmap ((void*) mmapped, stat_buf.st_size) < 0)
    dsk_warning ("error calling munmap(): %s", strerror (errno));
  if (fd >= 0)
    close (fd);
  return NULL;
}