/*
  Copyright (C) 2009-2018  Brazil
  Copyright (C) 2018-2025  Sutou Kouhei <kou@clear-code.com>

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

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "grn.h"

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

#include "grn_ctx.h"
#include "grn_io.h"
#include "grn_plugin.h"
#include "grn_hash.h"
#include "grn_ctx_impl.h"
#include "grn_util.h"

#ifdef WIN32
#  include <io.h>
#  include <share.h>
#endif /* WIN32 */

#define GRN_IO_IDSTR           "GROONGA:IO:00001"
#define GRN_IO_IDSTR_LEN       (sizeof(GRN_IO_IDSTR) - 1)

#define GRN_IO_VERSION_DEFAULT 1

#define GRN_IO_FILE_SIZE_V1    1073741824UL

#ifdef WIN32
#  define GRN_IO_FILE_SIZE_V0 134217728L
#else /* WIN32 */
#  define GRN_IO_FILE_SIZE_V0 GRN_IO_FILE_SIZE_V1
#endif /* WIN32 */

#ifndef WIN32
#  ifdef HAVE_FUTIMENS
#    define grn_futimens(fd, time_values, time_specs)                          \
      futimens((fd), (time_specs))
#    define grn_futimens_name "futimens"
#  elif defined(HAVE_FUTIMES) /* HAVE_FUTIMENS */
#    define grn_futimens(fd, time_values, time_specs)                          \
      futimes((fd), (time_values))
#    define grn_futimens_name "futimes"
#  else /* HAVE_FUTIMENS */
#    error Support for either the futimens() or futimens() function is required
#  endif /* HAVE_FUTIMENS */
#endif   /* WIN32 */

typedef struct _grn_io_fileinfo {
  char *path;
#ifdef WIN32
  HANDLE fh;
  HANDLE fmo;
  grn_critical_section cs;
#else  /* WIN32 */
  int fd;
  dev_t dev;
  ino_t inode;
#endif /* WIN32 */
} fileinfo;

#define IO_HEADER_SIZE 64

static uint32_t grn_io_version_default = GRN_IO_VERSION_DEFAULT;

static inline grn_rc
grn_fileinfo_open(grn_ctx *ctx, fileinfo *fi, const char *path, int flags);
static inline void
grn_fileinfo_init(fileinfo *fis, uint32_t nfis);
static inline int
grn_fileinfo_opened(fileinfo *fi);
static inline grn_rc
grn_fileinfo_close(grn_ctx *ctx, fileinfo *fi);
#ifdef WIN32
inline static void *
grn_mmap(grn_ctx *ctx,
         grn_io *io,
         HANDLE *fmo,
         fileinfo *fi,
         int64_t offset,
         size_t length,
         const char *file,
         int line,
         const char *func);
static inline int
grn_munmap(grn_ctx *ctx,
           grn_io *io,
           HANDLE *fmo,
           fileinfo *fi,
           void *start,
           size_t length);
#  define GRN_MMAP(ctx, io, fmo, fi, offset, length)                           \
    (grn_mmap((ctx),                                                           \
              (io),                                                            \
              (fmo),                                                           \
              (fi),                                                            \
              (offset),                                                        \
              (length),                                                        \
              __FILE__,                                                        \
              __LINE__,                                                        \
              __FUNCTION__))
#  define GRN_MUNMAP(ctx, io, fmo, fi, start, length)                          \
    (grn_munmap((ctx), (io), (fmo), (fi), (start), (length)))
#else /* WIN32 */
inline static void *
grn_mmap(grn_ctx *ctx,
         grn_io *io,
         fileinfo *fi,
         int64_t offset,
         size_t length,
         const char *file,
         int line,
         const char *func);
static inline int
grn_munmap(grn_ctx *ctx, grn_io *io, fileinfo *fi, void *start, size_t length);
#  define GRN_MMAP(ctx, io, fmo, fi, offset, length)                           \
    (grn_mmap((ctx),                                                           \
              (io),                                                            \
              (fi),                                                            \
              (offset),                                                        \
              (length),                                                        \
              __FILE__,                                                        \
              __LINE__,                                                        \
              __FUNCTION__))
#  define GRN_MUNMAP(ctx, io, fmo, fi, start, length)                          \
    (grn_munmap((ctx), (io), (fi), (start), (length)))
#endif /* WIN32 */

inline static int
grn_msync(grn_ctx *ctx, fileinfo *fi, void *start, size_t length);
#define GRN_MSYNC(ctx, fi, start, length)                                      \
  (grn_msync((ctx), (fi), (start), (length)))

inline static grn_rc
grn_pread(grn_ctx *ctx, fileinfo *fi, void *buf, size_t count, off_t offset);
static inline grn_rc
grn_pwrite(grn_ctx *ctx, fileinfo *fi, void *buf, size_t count, off_t offset);

void
grn_io_init_from_env(void)
{
  {
    char version_env[GRN_ENV_BUFFER_SIZE];

    grn_getenv("GRN_IO_VERSION", version_env, GRN_ENV_BUFFER_SIZE);
    if (version_env[0]) {
      grn_io_version_default = (uint32_t)atoi(version_env);
    }
  }
}

static inline uint32_t
grn_io_compute_base(uint32_t header_size)
{
  uint32_t total_header_size;
  total_header_size = IO_HEADER_SIZE + header_size;
  return (total_header_size + (uint32_t)grn_pagesize - 1) &
         (uint32_t)(~(grn_pagesize - 1));
}

static inline uint32_t
grn_io_compute_base_segment(uint32_t base, uint32_t segment_size)
{
  return (base + segment_size - 1) / segment_size;
}

static uint32_t
grn_io_compute_max_n_files(uint32_t segment_size,
                           uint32_t max_segment,
                           unsigned int base_segument,
                           uint32_t file_size)
{
  uint64_t last_segment_end;
  last_segment_end = ((uint64_t)segment_size) * (max_segment + base_segument);
  return (uint32_t)((last_segment_end + file_size - 1) / file_size);
}

static inline uint32_t
grn_io_compute_file_size(uint32_t version)
{
  if (version == 0) {
    return GRN_IO_FILE_SIZE_V0;
  } else {
    return GRN_IO_FILE_SIZE_V1;
  }
}

static inline uint32_t
grn_io_max_segment(grn_io *io)
{
  if (io->header->segment_tail) {
    return io->header->segment_tail;
  } else {
    return io->header->max_segment;
  }
}

static uint32_t
grn_io_max_n_files(grn_io *io)
{
  uint32_t file_size = grn_io_compute_file_size(io->header->version);
  return grn_io_compute_max_n_files(io->header->segment_size,
                                    grn_io_max_segment(io),
                                    io->base_seg,
                                    file_size);
}

static inline uint32_t
grn_io_compute_nth_file_info(grn_io *io, uint32_t nth_segment)
{
  uint32_t segment_size;
  uint32_t file_size;
  uint32_t n_segments_per_file;
  uint32_t resolved_nth_segment;

  segment_size = io->header->segment_size;
  file_size = grn_io_compute_file_size(io->header->version);
  n_segments_per_file = file_size / segment_size;
  resolved_nth_segment = nth_segment + io->base_seg;
  return resolved_nth_segment / n_segments_per_file;
}

static grn_io *
grn_io_create_tmp(grn_ctx *ctx,
                  uint32_t header_size,
                  uint32_t segment_size,
                  uint32_t max_segment,
                  grn_io_mode mode,
                  uint32_t flags)
{
  grn_io *io;
  uint32_t b;
  struct _grn_io_header *header;
  b = grn_io_compute_base(header_size);
  header = (struct _grn_io_header *)GRN_MMAP(ctx, NULL, NULL, NULL, 0, b);
  if (header) {
    header->version = grn_io_version_default;
    header->header_size = header_size;
    header->segment_size = segment_size;
    header->max_segment = max_segment;
    header->n_arrays = 0;
    header->flags = flags;
    header->lock = 0;
    grn_memcpy(header->idstr, GRN_IO_IDSTR, 16);
    if ((io = GRN_CALLOC(sizeof(grn_io)))) {
      grn_io_mapinfo *maps = NULL;
      if ((maps = GRN_CALLOC(sizeof(grn_io_mapinfo) * max_segment))) {
        io->header = header;
        io->user_header = (((byte *)header) + IO_HEADER_SIZE);
        io->maps = maps;
        io->base = b;
        io->base_seg = 0;
        io->mode = mode;
        io->header->curr_size = b;
        io->fis = NULL;
        io->ainfo = NULL;
        io->max_map_seg = 0;
        io->nmaps = 0;
        io->count = 0;
        io->flags = GRN_IO_TEMPORARY;
        io->lock = &header->lock;
        io->path[0] = '\0';
        return io;
      }
      GRN_FREE(io);
    }
    GRN_MUNMAP(ctx, NULL, NULL, NULL, header, b);
  }
  return NULL;
}

static void
grn_io_register(grn_ctx *ctx, grn_io *io)
{
  if (io->fis && (io->flags & (GRN_IO_EXPIRE_GTICK | GRN_IO_EXPIRE_SEGMENT))) {
    bool succeeded = false;
    CRITICAL_SECTION_ENTER(grn_glock);
    if (grn_gctx.impl && grn_gctx.impl->ios &&
        grn_hash_add(&grn_gctx,
                     grn_gctx.impl->ios,
                     io->path,
                     (unsigned int)strlen(io->path),
                     (void **)&io,
                     NULL)) {
      succeeded = true;
    }
    CRITICAL_SECTION_LEAVE(grn_glock);
    if (!succeeded) {
      GRN_LOG(ctx, GRN_LOG_WARNING, "grn_io_register(%s) failed", io->path);
    }
  }
}

static void
grn_io_unregister(grn_ctx *ctx, grn_io *io)
{
  if (io->fis && (io->flags & (GRN_IO_EXPIRE_GTICK | GRN_IO_EXPIRE_SEGMENT))) {
    bool succeeded = false;
    CRITICAL_SECTION_ENTER(grn_glock);
    if (grn_gctx.impl && grn_gctx.impl->ios) {
      grn_hash_delete(&grn_gctx,
                      grn_gctx.impl->ios,
                      io->path,
                      (unsigned int)strlen(io->path),
                      NULL);
      succeeded = true;
    }
    CRITICAL_SECTION_LEAVE(grn_glock);
    if (!succeeded) {
      GRN_LOG(ctx, GRN_LOG_WARNING, "grn_io_unregister(%s) failed", io->path);
    }
  }
}

grn_io *
grn_io_create(grn_ctx *ctx,
              const char *path,
              uint32_t header_size,
              uint32_t segment_size,
              uint32_t max_segment,
              grn_io_mode mode,
              uint32_t flags)
{
  grn_io *io;
  fileinfo *fis;
  size_t path_length;
  uint32_t b, max_nfiles;
  uint32_t bs;
  struct _grn_io_header *header;
  uint32_t version = grn_io_version_default;
  uint32_t file_size;

  if (!path) {
    return grn_io_create_tmp(ctx,
                             header_size,
                             segment_size,
                             max_segment,
                             mode,
                             flags);
  }
  path_length = strlen(path);
  if (path_length == 0 || (path_length > PATH_MAX - 4)) {
    return NULL;
  }
  b = grn_io_compute_base(header_size);
  bs = grn_io_compute_base_segment(b, segment_size);
  file_size = grn_io_compute_file_size(version);
  max_nfiles =
    grn_io_compute_max_n_files(segment_size, max_segment, bs, file_size);
  if ((fis = GRN_MALLOCN(fileinfo, max_nfiles))) {
    grn_fileinfo_init(fis, max_nfiles);
    if (!grn_fileinfo_open(ctx, fis, path, O_RDWR | O_CREAT | O_EXCL)) {
      header =
        (struct _grn_io_header *)GRN_MMAP(ctx, NULL, &fis->fmo, fis, 0, b);
      if (header) {
        header->version = version;
        header->header_size = header_size;
        header->segment_size = segment_size;
        header->max_segment = max_segment;
        header->n_arrays = 0;
        header->flags = flags;
        header->lock = 0;
        grn_memcpy(header->idstr, GRN_IO_IDSTR, 16);
        GRN_MSYNC(ctx, &(fis[0]), header, b);
        if ((io = GRN_CALLOC(sizeof(grn_io)))) {
          grn_io_mapinfo *maps = NULL;
          if ((maps = GRN_CALLOC(sizeof(grn_io_mapinfo) * max_segment))) {
            grn_strncpy(io->path, PATH_MAX, path, path_length + 1);
            io->header = header;
            io->user_header = (((byte *)header) + IO_HEADER_SIZE);
            io->maps = maps;
            io->base = b;
            io->base_seg = bs;
            io->mode = mode;
            io->header->curr_size = b;
            io->fis = fis;
            io->ainfo = NULL;
            io->max_map_seg = 0;
            io->nmaps = 0;
            io->count = 0;
            io->flags = flags;
            io->lock = &header->lock;
            grn_io_register(ctx, io);
            return io;
          }
          GRN_FREE(io);
        }
        GRN_MUNMAP(ctx, NULL, &fis->fmo, fis, header, b);
      }
      grn_fileinfo_close(ctx, fis);
      if (grn_unlink(path) == 0) {
        GRN_LOG(ctx,
                GRN_LOG_INFO,
                "[io][create][error] removed path: <%s>",
                path);
      } else {
        ERRNO_ERR("[io][create][error] failed to remove path: <%s>", path);
      }
    }
    GRN_FREE(fis);
  }
  return NULL;
}

static grn_rc
array_init_(
  grn_ctx *ctx, grn_io *io, uint32_t n_arrays, size_t hsize, size_t msize)
{
  uint32_t i;
  uint32_t ws;
  byte *hp, *mp;
  grn_io_array_spec *array_specs = (grn_io_array_spec *)io->user_header;
  hp = io->user_header;
  if (!(mp = GRN_CALLOC(msize))) {
    return GRN_NO_MEMORY_AVAILABLE;
  }
  io->ainfo = (grn_io_array_info *)mp;
  hp += sizeof(grn_io_array_spec) * n_arrays;
  mp += sizeof(grn_io_array_info) * n_arrays;
  for (ws = 0; (uint32_t)(1 << ws) < io->header->segment_size; ws++)
    ;
  for (i = 0; i < n_arrays; i++) {
    uint32_t we = ws - array_specs[i].w_of_element;
    io->ainfo[i].w_of_elm_in_a_segment = we;
    io->ainfo[i].elm_mask_in_a_segment = (1U << we) - 1U;
    io->ainfo[i].max_n_segments = array_specs[i].max_n_segments;
    io->ainfo[i].element_size = 1 << array_specs[i].w_of_element;
    io->ainfo[i].segments = (uint32_t *)hp;
    io->ainfo[i].addrs = (void **)mp;
    hp += sizeof(uint32_t) * array_specs[i].max_n_segments;
    mp += sizeof(void *) * array_specs[i].max_n_segments;
  }
  io->user_header += hsize;
  return GRN_SUCCESS;
}

static grn_rc
array_init(grn_ctx *ctx, grn_io *io, uint32_t n_arrays)
{
  if (n_arrays) {
    uint32_t i;
    grn_io_array_spec *array_specs = (grn_io_array_spec *)io->user_header;
    size_t hsize = sizeof(grn_io_array_spec) * n_arrays;
    size_t msize = sizeof(grn_io_array_info) * n_arrays;
    for (i = 0; i < n_arrays; i++) {
      hsize += sizeof(uint32_t) * array_specs[i].max_n_segments;
      msize += sizeof(void *) * array_specs[i].max_n_segments;
    }
    return array_init_(ctx, io, n_arrays, hsize, msize);
  }
  return GRN_SUCCESS;
}

grn_io *
grn_io_create_with_array(grn_ctx *ctx,
                         const char *path,
                         uint32_t header_size,
                         uint32_t segment_size,
                         grn_io_mode mode,
                         uint32_t n_arrays,
                         grn_io_array_spec *array_specs)
{
  if (n_arrays) {
    uint32_t i;
    grn_io *io;
    byte *hp;
    uint32_t nsegs = 0;
    size_t hsize = sizeof(grn_io_array_spec) * n_arrays;
    size_t msize = sizeof(grn_io_array_info) * n_arrays;
    for (i = 0; i < n_arrays; i++) {
      nsegs += array_specs[i].max_n_segments;
      hsize += sizeof(uint32_t) * array_specs[i].max_n_segments;
      msize += sizeof(void *) * array_specs[i].max_n_segments;
    }
    if ((io = grn_io_create(ctx,
                            path,
                            (uint32_t)(header_size + hsize),
                            segment_size,
                            nsegs,
                            mode,
                            GRN_IO_EXPIRE_GTICK))) {
      grn_rc rc;
      hp = io->user_header;
      grn_memcpy(hp, array_specs, sizeof(grn_io_array_spec) * n_arrays);
      io->header->n_arrays = n_arrays;
      io->header->segment_tail = 1;
      rc = array_init_(ctx, io, n_arrays, hsize, msize);
      if (rc == GRN_SUCCESS) {
        return io;
      }
      ERR(GRN_NO_MEMORY_AVAILABLE, "grn_io_create_with_array failed");
      grn_io_close(ctx, io);
    }
  }
  return NULL;
}

static inline uint32_t
segment_alloc(grn_ctx *ctx, grn_io *io)
{
  uint32_t n, s;
  grn_io_array_info *ai;
  if (io->header->segment_tail) {
    if (io->header->segment_tail > io->header->max_segment) {
      s = 0;
    } else {
      s = io->header->segment_tail++;
    }
  } else {
    char *used = GRN_CALLOC(io->header->max_segment + 1);
    if (!used) {
      return 0;
    }
    for (n = io->header->n_arrays, ai = io->ainfo; n; n--, ai++) {
      for (s = 0; s < ai->max_n_segments; s++) {
        used[ai->segments[s]] = 1;
      }
    }
    for (s = 1;; s++) {
      if (s > io->header->max_segment) {
        io->header->segment_tail = s;
        s = 0;
        break;
      }
      if (!used[s]) {
        io->header->segment_tail = s + 1;
        break;
      }
    }
    GRN_FREE(used);
  }
  return s;
}

void
grn_io_segment_alloc(grn_ctx *ctx,
                     grn_io *io,
                     grn_io_array_info *ai,
                     uint32_t lseg,
                     int *flags,
                     void **p)
{
  uint32_t *sp = &ai->segments[lseg];
  if (!*sp) {
    if ((*flags & GRN_TABLE_ADD)) {
      if ((*sp = segment_alloc(ctx, io))) {
        *flags |= GRN_TABLE_ADDED;
      }
    }
  }
  if (*sp) {
    uint32_t pseg = *sp - 1;
    *p = grn_io_seg_ref(ctx, io, pseg);
    if (*p) {
      grn_io_seg_unref(ctx, io, pseg);
    };
  }
}

uint32_t
grn_io_detect_type(grn_ctx *ctx, const char *path)
{
  struct _grn_io_header h;
  uint32_t res = 0;
  int fd;
  grn_open(fd, path, O_RDONLY | GRN_OPEN_FLAG_BINARY);
  if (fd != -1) {
    struct stat s;
    if (fstat(fd, &s) != -1 &&
        (size_t)(s.st_size) >= sizeof(struct _grn_io_header)) {
      if (grn_read(fd, &h, sizeof(struct _grn_io_header)) ==
          sizeof(struct _grn_io_header)) {
        if (!memcmp(h.idstr, GRN_IO_IDSTR, GRN_IO_IDSTR_LEN)) {
          res = h.type;
        } else {
          ERR(GRN_INCOMPATIBLE_FILE_FORMAT,
              "failed to detect type: format ID is different: <%s>: <%.*s>",
              path,
              (int)GRN_IO_IDSTR_LEN,
              GRN_IO_IDSTR);
        }
      } else {
        SERR("failed to read enough data for detecting type: <%s>", path);
      }
    } else {
      ERR(GRN_INVALID_FORMAT, "grn_io_detect_type failed");
    }
    grn_close(fd);
  } else {
    ERRNO_ERR("failed to open path for detecting type: <%s>", path);
  }
  return res;
}

grn_io *
grn_io_open(grn_ctx *ctx, const char *path, grn_io_mode mode)
{
  size_t max_path_len = PATH_MAX - 4;
  size_t path_length;
  struct stat s;
  fileinfo fi;
  uint32_t flags = 0;
  uint32_t b;
  uint32_t header_size = 0, segment_size = 0, max_segment = 0, bs;
  if (!path || !*path) {
    ERR(GRN_INVALID_ARGUMENT, "[io][open] path is missing");
    return NULL;
  }
  path_length = strlen(path);
  if (path_length > max_path_len) {
    int truncate_length = 10;
    ERR(GRN_INVALID_ARGUMENT,
        "[io][open] path is too long: "
        "<%" GRN_FMT_SIZE ">(max: %" GRN_FMT_SIZE "): <%.*s...>",
        path_length,
        max_path_len,
        truncate_length,
        path);
    return NULL;
  }
  {
    struct _grn_io_header h;
    int fd;
    ssize_t read_bytes;
    grn_open(fd, path, O_RDWR | GRN_OPEN_FLAG_BINARY);
    if (fd == -1) {
      ERRNO_ERR("[io][open] failed to open path: <%s>", path);
      return NULL;
    }
    if (fstat(fd, &s) == -1) {
      ERRNO_ERR("[io][open] failed to file status: <%s>", path);
      grn_close(fd);
      return NULL;
    }
    if ((size_t)(s.st_size) < sizeof(struct _grn_io_header)) {
      ERR(GRN_INCOMPATIBLE_FILE_FORMAT,
          "[io][open] file size is too small: "
          "<%" GRN_FMT_INT64D ">(required: >= %" GRN_FMT_SIZE "): <%s>",
          (int64_t)(s.st_size),
          sizeof(struct _grn_io_header),
          path);
      grn_close(fd);
      return NULL;
    }
    read_bytes = grn_read(fd, &h, sizeof(struct _grn_io_header));
    if (read_bytes != sizeof(struct _grn_io_header)) {
      ERRNO_ERR("[io][open] failed to read header data: "
                "<%" GRN_FMT_SSIZE ">(expected: %" GRN_FMT_SSIZE "): <%s>",
                read_bytes,
                sizeof(struct _grn_io_header),
                path);
      grn_close(fd);
      return NULL;
    }
    if (memcmp(h.idstr, GRN_IO_IDSTR, GRN_IO_IDSTR_LEN) != 0) {
      ERR(GRN_INCOMPATIBLE_FILE_FORMAT,
          "[io][open] failed to open: format ID is different: <%s>: <%.*s>",
          path,
          (int)GRN_IO_IDSTR_LEN,
          GRN_IO_IDSTR);
      grn_close(fd);
      return NULL;
    }
    header_size = h.header_size;
    segment_size = h.segment_size;
    max_segment = h.max_segment;
    flags = h.flags;
    grn_close(fd);
    if (segment_size == 0) {
      ERR(GRN_INCOMPATIBLE_FILE_FORMAT,
          "[io][open] failed to open: segment size is 0: <%s>",
          path);
      return NULL;
    }
  }
  b = grn_io_compute_base(header_size);
  bs = grn_io_compute_base_segment(b, segment_size);
  grn_fileinfo_init(&fi, 1);
  grn_rc rc = grn_fileinfo_open(ctx, &fi, path, O_RDWR);
  if (rc != GRN_SUCCESS) {
    ERR(rc, "[io][open] failed to open fileinfo: <%s>", path);
    return NULL;
  }

  struct _grn_io_header *header;
  header = GRN_MMAP(ctx, NULL, &(fi.fmo), &fi, 0, b);
  if (!header) {
    grn_fileinfo_close(ctx, &fi);
    ERR(GRN_NO_MEMORY_AVAILABLE, "[io][open] failed to map: <%s>", path);
    return NULL;
  }

  uint32_t file_size = grn_io_compute_file_size(header->version);
  uint32_t max_nfiles =
    grn_io_compute_max_n_files(segment_size, max_segment, bs, file_size);
  fileinfo *fis = GRN_MALLOCN(fileinfo, max_nfiles);
  if (!fis) {
    GRN_MUNMAP(ctx, NULL, &(fi.fmo), &fi, header, b);
    grn_fileinfo_close(ctx, &fi);
    ERR(GRN_NO_MEMORY_AVAILABLE,
        "[io][open] failed to allocate fileinfo: <%s>",
        path);
    return NULL;
  }

  grn_fileinfo_init(fis, max_nfiles);
  grn_memcpy(fis, &fi, sizeof(fileinfo));
  grn_io *io = GRN_CALLOC(sizeof(grn_io));
  if (!io) {
    GRN_FREE(fis);
    GRN_MUNMAP(ctx, NULL, &(fi.fmo), &fi, header, b);
    grn_fileinfo_close(ctx, &fi);
    ERR(GRN_NO_MEMORY_AVAILABLE,
        "[io][open] failed to allocate grn_io: <%s>",
        path);
    return NULL;
  }

  grn_io_mapinfo *maps = GRN_CALLOC(sizeof(grn_io_mapinfo) * max_segment);
  if (!maps) {
    GRN_FREE(io);
    GRN_FREE(fis);
    GRN_MUNMAP(ctx, NULL, &(fi.fmo), &fi, header, b);
    grn_fileinfo_close(ctx, &fi);
    ERR(GRN_NO_MEMORY_AVAILABLE,
        "[io][open] failed to allocate grn_io_mapinfo: <%s>",
        path);
    return NULL;
  }

  grn_strncpy(io->path, PATH_MAX, path, path_length + 1);
  io->header = header;
  io->user_header = (((byte *)header) + IO_HEADER_SIZE);
  io->maps = maps;
  io->base = b;
  io->base_seg = bs;
  io->mode = mode;
  io->fis = fis;
  io->ainfo = NULL;
  io->max_map_seg = 0;
  io->nmaps = 0;
  io->count = 0;
  io->flags = header->flags;
  io->lock = &header->lock;
  rc = array_init(ctx, io, io->header->n_arrays);
  if (rc != GRN_SUCCESS) {
    GRN_FREE(io->maps);
    GRN_FREE(io);
    GRN_FREE(fis);
    GRN_MUNMAP(ctx, NULL, &(fi.fmo), &fi, header, b);
    grn_fileinfo_close(ctx, &fi);
    ERR(rc, "[io][open] failed to initialize array: <%s>", path);
    return NULL;
  }

  grn_io_register(ctx, io);
  return io;
}

grn_rc
grn_io_close(grn_ctx *ctx, grn_io *io)
{
  uint32_t max_nfiles;

  max_nfiles = grn_io_max_n_files(io);
  grn_io_unregister(ctx, io);
  if (io->ainfo) {
    GRN_FREE(io->ainfo);
  }
  if (io->maps) {
    uint32_t i;
    uint32_t max_segment;
    uint32_t segment_size;
    uint32_t file_size;
    uint32_t n_segments_per_file;

    max_segment = grn_io_max_segment(io);
    segment_size = io->header->segment_size;
    file_size = grn_io_compute_file_size(io->header->version);
    n_segments_per_file = file_size / segment_size;
    for (i = 0; i < max_segment; i++) {
      grn_io_mapinfo *mi;
      mi = &(io->maps[i]);
      if (mi->map) {
        fileinfo *fi = NULL;
        /* if (atomic_read(mi->nref)) { return STILL_IN_USE ; } */
        if (io->fis) {
          uint32_t bseg = i + io->base_seg;
          uint32_t fno = bseg / n_segments_per_file;
          fi = &io->fis[fno];
        }
        GRN_MUNMAP(ctx, io, &mi->fmo, fi, mi->map, segment_size);
      }
    }
    GRN_FREE(io->maps);
  }
  GRN_MUNMAP(ctx,
             io,
             (io->fis ? &io->fis->fmo : NULL),
             io->fis,
             io->header,
             io->base);
  if (io->fis) {
    uint32_t i;
    for (i = 0; i < max_nfiles; i++) {
      fileinfo *fi = &(io->fis[i]);
      grn_fileinfo_close(ctx, fi);
    }
    GRN_FREE(io->fis);
  }
  GRN_FREE(io);
  return GRN_SUCCESS;
}

uint32_t
grn_io_base_seg(grn_io *io)
{
  return io->base_seg;
}

const char *
grn_io_path(grn_io *io)
{
  return io->path;
}

void *
grn_io_header(grn_io *io)
{
  return io->user_header;
}

grn_rc
grn_io_set_type(grn_io *io, uint32_t type)
{
  if (!io || !io->header) {
    return GRN_INVALID_ARGUMENT;
  }
  io->header->type = type;
  return GRN_SUCCESS;
}

uint32_t
grn_io_get_type(grn_io *io)
{
  if (!io || !io->header) {
    return GRN_VOID;
  }
  return io->header->type;
}

static inline void
gen_pathname(const char *path, char *buffer, uint32_t fno)
{
  size_t len = strlen(path);
  grn_memcpy(buffer, path, len);
  if (fno) {
    buffer[len] = '.';
    grn_itoh(fno, buffer + len + 1, 3);
    buffer[len + 4] = '\0';
  } else {
    buffer[len] = '\0';
  }
}

static uint32_t
grn_io_n_files(grn_ctx *ctx, grn_io *io)
{
  unsigned long file_size;
  file_size = grn_io_compute_file_size(io->header->version);
  return (uint32_t)((io->header->curr_size + file_size - 1) / file_size);
}

grn_rc
grn_io_size(grn_ctx *ctx, grn_io *io, uint64_t *size)
{
  uint32_t fno;
  struct stat s;
  uint64_t tsize = 0;
  char buffer[PATH_MAX];
  uint32_t n_files;

  n_files = grn_io_n_files(ctx, io);
  for (fno = 0; fno < n_files; fno++) {
    gen_pathname(io->path, buffer, fno);
    if (stat(buffer, &s)) {
      SERR("failed to stat path to compute size: <%s>", buffer);
    } else {
      tsize += (uint64_t)(s.st_size);
    }
  }
  *size = tsize;
  return GRN_SUCCESS;
}

grn_rc
grn_io_remove_raw(grn_ctx *ctx, const char *path)
{
  grn_rc rc = GRN_SUCCESS;
  uint32_t fno;
  char buffer[PATH_MAX];

  if (grn_unlink(path) != 0) {
    ERRNO_ERR("[io][remove] failed to remove path: <%s>", path);
    return ctx->rc;
  }
  GRN_LOG(ctx, GRN_LOG_INFO, "[io][remove] removed path: <%s>", path);

  for (fno = 1;; fno++) {
    struct stat s;
    gen_pathname(path, buffer, fno);
    if (stat(buffer, &s) != 0) {
      break;
    }
    if (grn_unlink(buffer) == 0) {
      GRN_LOG(ctx,
              GRN_LOG_INFO,
              "[io][remove] removed numbered path: <%d>: <%s>",
              fno,
              buffer);
    } else {
      ERRNO_ERR("[io][remove] failed to remove numbered path: <%d>: <%s>",
                fno,
                buffer);
      rc = ctx->rc;
    }
  }
  return rc;
}

grn_rc
grn_io_remove(grn_ctx *ctx, const char *path)
{
  struct stat s;

  if (stat(path, &s) != 0) {
    SERR("failed to stat: <%s>", path);
    return ctx->rc;
  }

  return grn_io_remove_raw(ctx, path);
}

grn_rc
grn_io_remove_if_exist(grn_ctx *ctx, const char *path)
{
  struct stat s;
  if (stat(path, &s) == 0) {
    return grn_io_remove_raw(ctx, path);
  }
  return GRN_SUCCESS;
}

grn_rc
grn_io_rename(grn_ctx *ctx, const char *old_name, const char *new_name)
{
  const char *tag = "[io][rename]";
  struct stat s;
  if (stat(old_name, &s)) {
    SERR("%s failed to stat path to be renamed: <%s>", tag, old_name);
    return ctx->rc;
  } else if (rename(old_name, new_name)) {
    SERR("%s failed to rename path: <%s> -> <%s>", tag, old_name, new_name);
    return ctx->rc;
  } else {
    uint32_t fno;
    char old_buffer[PATH_MAX];
    char new_buffer[PATH_MAX];
    for (fno = 1;; fno++) {
      gen_pathname(old_name, old_buffer, fno);
      if (stat(old_buffer, &s) == 0) {
        break;
      }
      gen_pathname(new_name, new_buffer, fno);
      if (rename(old_buffer, new_buffer)) {
        SERR("%s failed to rename path: <%s> -> <%s>",
             tag,
             old_buffer,
             new_buffer);
      }
    }
    return GRN_SUCCESS;
  }
}

typedef struct {
  grn_io_ja_ehead head;
  char body[256];
} ja_element;

grn_rc
grn_io_read_ja(grn_io *io,
               grn_ctx *ctx,
               grn_io_ja_einfo *einfo,
               uint32_t epos,
               uint32_t key,
               uint32_t segment,
               uint32_t offset,
               void **value,
               uint32_t *value_len)
{
  size_t rest = 0;
  size_t size = *value_len + sizeof(grn_io_ja_ehead);
  uint32_t segment_size = io->header->segment_size;
  uint32_t file_size = grn_io_compute_file_size(io->header->version);
  uint32_t n_segments_per_file = file_size / segment_size;
  uint32_t bseg = segment + io->base_seg;
  uint32_t fno = bseg / n_segments_per_file;
  fileinfo *fi = &io->fis[fno];
  off_t base = fno ? 0 : (off_t)(io->base - segment_size * io->base_seg);
  off_t pos = segment_size * (bseg % n_segments_per_file) + offset + base;
  ja_element *v = GRN_CALLOC(size);
  if (!v) {
    *value = NULL;
    *value_len = 0;
    return GRN_NO_MEMORY_AVAILABLE;
  }
  if (pos + (off_t)size > (off_t)file_size) {
    rest = (size_t)(pos + (off_t)size - file_size);
    size = (size_t)(file_size - pos);
  }
  if (!grn_fileinfo_opened(fi)) {
    char path[PATH_MAX];
    gen_pathname(io->path, path, fno);
    if (grn_fileinfo_open(ctx, fi, path, O_RDWR | O_CREAT)) {
      *value = NULL;
      *value_len = 0;
      GRN_FREE(v);
      return ctx->rc;
    }
  }
  if (grn_pread(ctx, fi, v, size, pos)) {
    *value = NULL;
    *value_len = 0;
    GRN_FREE(v);
    return ctx->rc;
  }
  if (einfo->pos != epos) {
    GRN_LOG(ctx,
            GRN_LOG_WARNING,
            "einfo pos changed %x => %x",
            einfo->pos,
            epos);
    *value = NULL;
    *value_len = 0;
    GRN_FREE(v);
    return GRN_FILE_CORRUPT;
  }
  if (einfo->size != *value_len) {
    GRN_LOG(ctx,
            GRN_LOG_WARNING,
            "einfo size changed %d => %d",
            einfo->size,
            *value_len);
    *value = NULL;
    *value_len = 0;
    GRN_FREE(v);
    return GRN_FILE_CORRUPT;
  }
  if (v->head.key != key) {
    GRN_LOG(ctx, GRN_LOG_ERROR, "ehead key unmatch %x => %x", key, v->head.key);
    *value = NULL;
    *value_len = 0;
    GRN_FREE(v);
    return GRN_INVALID_FORMAT;
  }
  if (v->head.size != *value_len) {
    GRN_LOG(ctx,
            GRN_LOG_ERROR,
            "ehead size unmatch %d => %d",
            *value_len,
            v->head.size);
    *value = NULL;
    *value_len = 0;
    GRN_FREE(v);
    return GRN_INVALID_FORMAT;
  }
  if (rest) {
    byte *vr = (byte *)v + size;
    do {
      fi = &io->fis[++fno];
      if (!grn_fileinfo_opened(fi)) {
        char path[PATH_MAX];
        gen_pathname(io->path, path, fno);
        if (grn_fileinfo_open(ctx, fi, path, O_RDWR | O_CREAT)) {
          *value = NULL;
          *value_len = 0;
          GRN_FREE(v);
          return ctx->rc;
        }
      }
      size = rest > file_size ? file_size : rest;
      if (grn_pread(ctx, fi, vr, size, 0)) {
        *value = NULL;
        *value_len = 0;
        GRN_FREE(v);
        return ctx->rc;
      }
      vr += size;
      rest -= size;
    } while (rest);
  }
  *value = v->body;
  return GRN_SUCCESS;
}

grn_rc
grn_io_write_ja(grn_io *io,
                grn_ctx *ctx,
                uint32_t key,
                uint32_t segment,
                uint32_t offset,
                void *value,
                uint32_t value_len)
{
  grn_rc rc;
  size_t rest = 0;
  size_t size = value_len + sizeof(grn_io_ja_ehead);
  uint32_t segment_size = io->header->segment_size;
  uint32_t file_size = grn_io_compute_file_size(io->header->version);
  uint32_t n_segments_per_file = file_size / segment_size;
  uint32_t bseg = segment + io->base_seg;
  uint32_t fno = bseg / n_segments_per_file;
  fileinfo *fi = &io->fis[fno];
  off_t base = fno ? 0 : (off_t)(io->base) - segment_size * io->base_seg;
  off_t pos = segment_size * (bseg % n_segments_per_file) + offset + base;
  if (pos + (off_t)size > (off_t)file_size) {
    rest = (size_t)(pos + (off_t)size - file_size);
    size = (size_t)(file_size - pos);
  }
  if (!grn_fileinfo_opened(fi)) {
    char path[PATH_MAX];
    gen_pathname(io->path, path, fno);
    if ((rc = grn_fileinfo_open(ctx, fi, path, O_RDWR | O_CREAT))) {
      return rc;
    }
  }
  if (value_len <= 256) {
    ja_element je;
    je.head.size = value_len;
    je.head.key = key;
    grn_memcpy(je.body, value, value_len);
    rc = grn_pwrite(ctx, fi, &je, size, pos);
  } else {
    grn_io_ja_ehead eh;
    eh.size = value_len;
    eh.key = key;
    if ((rc = grn_pwrite(ctx, fi, &eh, sizeof(grn_io_ja_ehead), pos))) {
      return rc;
    }
    pos += (off_t)sizeof(grn_io_ja_ehead);
    rc = grn_pwrite(ctx, fi, value, size - sizeof(grn_io_ja_ehead), pos);
  }
  if (rc) {
    return rc;
  }
  if (rest) {
    byte *vr = (byte *)value + size - sizeof(grn_io_ja_ehead);
    do {
      fi = &io->fis[++fno];
      if (!grn_fileinfo_opened(fi)) {
        char path[PATH_MAX];
        gen_pathname(io->path, path, fno);
        if ((rc = grn_fileinfo_open(ctx, fi, path, O_RDWR | O_CREAT))) {
          return rc;
        }
      }
      size = rest > file_size ? file_size : rest;
      if ((rc = grn_pwrite(ctx, fi, vr, size, 0))) {
        return rc;
      }
      vr += size;
      rest -= size;
    } while (rest);
  }
  return rc;
}

grn_rc
grn_io_write_ja_ehead(grn_io *io,
                      grn_ctx *ctx,
                      uint32_t key,
                      uint32_t segment,
                      uint32_t offset,
                      uint32_t value_len)
{
  grn_rc rc;
  uint32_t segment_size = io->header->segment_size;
  uint32_t file_size = grn_io_compute_file_size(io->header->version);
  uint32_t n_segments_per_file = file_size / segment_size;
  uint32_t bseg = segment + io->base_seg;
  uint32_t fno = bseg / n_segments_per_file;
  fileinfo *fi = &io->fis[fno];
  off_t base = fno ? 0 : (off_t)(io->base) - segment_size + io->base_seg;
  off_t pos = segment_size * (bseg % n_segments_per_file) + offset + base;
  if (!grn_fileinfo_opened(fi)) {
    char path[PATH_MAX];
    gen_pathname(io->path, path, fno);
    if ((rc = grn_fileinfo_open(ctx, fi, path, O_RDWR | O_CREAT))) {
      return rc;
    }
  }
  {
    grn_io_ja_ehead eh;
    eh.size = value_len;
    eh.key = key;
    return grn_pwrite(ctx, fi, &eh, sizeof(grn_io_ja_ehead), pos);
  }
}

void *
grn_io_win_map(grn_ctx *ctx,
               grn_io *io,
               grn_io_win *iw,
               uint32_t segment,
               uint32_t offset,
               uint32_t size,
               grn_io_rw_mode mode)
{
  uint32_t nseg, segment_size = io->header->segment_size;
  if (offset >= segment_size) {
    segment += offset / segment_size;
    offset = offset % segment_size;
  }
  nseg = (offset + size + segment_size - 1) / segment_size;
  if (!size || !ctx || segment + nseg > io->header->max_segment) {
    return NULL;
  }
  iw->ctx = ctx;
  iw->diff = 0;
  iw->io = io;
  iw->mode = (uint8_t)mode;
  iw->tiny_p = 0;
  iw->segment = segment;
  iw->offset = offset;
  iw->nseg = nseg;
  iw->size = size;
  if (nseg == 1) {
    byte *addr = grn_io_seg_ref(ctx, io, segment);
    if (!addr) {
      return NULL;
    }
    iw->cached = 1;
    iw->addr = addr + offset;
  } else {
    if (!(iw->addr = GRN_CALLOC(size))) {
      return NULL;
    }
    iw->cached = 0;
    switch (mode) {
    case GRN_IO_RDONLY:
    case GRN_IO_RDWR:
      {
        byte *p, *q = NULL;
        uint32_t s, r;
        for (p = iw->addr, r = size; r; p += s, r -= s, segment++, offset = 0) {
          q = grn_io_seg_ref(ctx, io, segment);
          if (!q) {
            GRN_FREE(iw->addr);
            return NULL;
          }
          s = (offset + r > segment_size) ? segment_size - offset : r;
          grn_memcpy(p, q + offset, s);
          grn_io_seg_unref(ctx, io, segment);
        }
      }
      break;
    case GRN_IO_WRONLY:
      break;
    default:
      return NULL;
    }
  }
  return iw->addr;
}

grn_rc
grn_io_win_unmap(grn_ctx *ctx, grn_io_win *iw)
{
  if (!iw || !iw->io || !iw->ctx) {
    return GRN_INVALID_ARGUMENT;
  }
  if (iw->cached) {
    if (!iw->tiny_p) {
      grn_io_seg_unref(ctx, iw->io, iw->segment);
    }
    return GRN_SUCCESS;
  }
  {
    grn_io *io = iw->io;
    grn_ctx *ctx = iw->ctx;
    switch (iw->mode) {
    case GRN_IO_RDONLY:
      if (!iw->addr) {
        return GRN_INVALID_ARGUMENT;
      }
      GRN_FREE(iw->addr);
      return GRN_SUCCESS;
    case GRN_IO_RDWR:
    case GRN_IO_WRONLY:
      {
        byte *p, *q = NULL;
        uint32_t segment_size = io->header->segment_size;
        uint32_t s, r, offset = iw->offset, segment = iw->segment;
        for (p = iw->addr, r = iw->size; r;
             p += s, r -= s, segment++, offset = 0) {
          q = grn_io_seg_ref(ctx, io, segment);
          if (!q) {
            return GRN_NO_MEMORY_AVAILABLE;
          }
          s = (offset + r > segment_size) ? segment_size - offset : r;
          grn_memcpy(q + offset, p, s);
          grn_io_seg_unref(ctx, io, segment);
        }
      }
      GRN_FREE(iw->addr);
      return GRN_SUCCESS;
    default:
      return GRN_INVALID_ARGUMENT;
    }
  }
}

#define DO_MAP(io, fmo, fi, pos, size, segno, res)                             \
  do {                                                                         \
    (res) = GRN_MMAP(ctx, (io), (fmo), (fi), (pos), (size));                   \
    if ((res)) {                                                               \
      uint32_t nmaps;                                                          \
      if (io->max_map_seg < segno) {                                           \
        io->max_map_seg = segno;                                               \
      }                                                                        \
      GRN_ATOMIC_ADD_EX(&io->nmaps, 1, nmaps);                                 \
      {                                                                        \
        uint64_t tail = io->base + (uint64_t)(size) * ((segno) + 1);           \
        if (tail > io->header->curr_size) {                                    \
          io->header->curr_size = tail;                                        \
        }                                                                      \
      }                                                                        \
    }                                                                          \
  } while (0)

void
grn_io_seg_map_(grn_ctx *ctx, grn_io *io, uint32_t segno, grn_io_mapinfo *info)
{
  uint32_t segment_size = io->header->segment_size;
  if ((io->flags & GRN_IO_TEMPORARY)) {
    DO_MAP(io, &info->fmo, NULL, 0, segment_size, segno, info->map);
  } else {
    uint32_t file_size = grn_io_compute_file_size(io->header->version);
    uint32_t n_segments_per_file = file_size / segment_size;
    uint32_t bseg = segno + io->base_seg;
    uint32_t fno = bseg / n_segments_per_file;
    off_t base = fno ? 0 : (off_t)(io->base) - segment_size * io->base_seg;
    off_t pos = segment_size * (bseg % n_segments_per_file) + base;
    fileinfo *fi = &io->fis[fno];
    if (!grn_fileinfo_opened(fi)) {
      char path[PATH_MAX];
      gen_pathname(io->path, path, fno);
      bool path_exist = grn_path_exist(path);
      if (!grn_fileinfo_open(ctx, fi, path, O_RDWR | O_CREAT)) {
        DO_MAP(io, &info->fmo, fi, pos, segment_size, segno, info->map);
        if (!info->map && !path_exist) {
          grn_fileinfo_close(ctx, fi);
          if (grn_unlink(path) == 0) {
            GRN_LOG(ctx,
                    GRN_LOG_INFO,
                    "[io][map][error] memory mapping is failed and then "
                    "removed created map file: <%s>",
                    path);
          } else {
            ERRNO_ERR("[io][map][error] memory mapping is failed and then "
                      "failed to remove created map file: <%s>",
                      path);
          }
        }
      }
    } else {
      DO_MAP(io, &info->fmo, fi, pos, segment_size, segno, info->map);
    }
  }
}

grn_rc
grn_io_seg_expire(grn_ctx *ctx, grn_io *io, uint32_t segno, uint32_t nretry)
{
  uint32_t retry, *pnref;
  grn_io_mapinfo *info;
  if (!io->maps || segno >= io->header->max_segment) {
    return GRN_INVALID_ARGUMENT;
  }
  info = &io->maps[segno];
  if (!info->map) {
    return GRN_INVALID_ARGUMENT;
  }
  pnref = &info->nref;
  for (retry = 0;; retry++) {
    uint32_t nref;
    GRN_ATOMIC_ADD_EX(pnref, 1, nref);
    if (nref) {
      GRN_ATOMIC_ADD_EX(pnref, -1, nref);
      if (retry >= GRN_IO_MAX_RETRY) {
        GRN_LOG(ctx,
                GRN_LOG_CRIT,
                "deadlock detected! in grn_io_seg_expire(%p, %u, %u)",
                io,
                segno,
                nref);
        return GRN_RESOURCE_DEADLOCK_AVOIDED;
      }
    } else {
      GRN_ATOMIC_ADD_EX(pnref, GRN_IO_MAX_REF, nref);
      uint32_t reset_max_ref_by_overflow = GRN_IO_MAX_REF - 1;
      if (nref > 1) {
        GRN_ATOMIC_ADD_EX(pnref, reset_max_ref_by_overflow, nref);
        GRN_FUTEX_WAKE(pnref);
        if (retry >= GRN_IO_MAX_RETRY) {
          GRN_LOG(ctx,
                  GRN_LOG_CRIT,
                  "deadlock detected!! in grn_io_seg_expire(%p, %u, %u)",
                  io,
                  segno,
                  nref);
          return GRN_RESOURCE_DEADLOCK_AVOIDED;
        }
      } else {
        uint32_t nmaps;
        fileinfo *fi = &(io->fis[segno]);
        GRN_MUNMAP(ctx,
                   io,
                   &info->fmo,
                   fi,
                   info->map,
                   io->header->segment_size);
        info->map = NULL;
        GRN_ATOMIC_ADD_EX(pnref, reset_max_ref_by_overflow, nref);
        GRN_ATOMIC_ADD_EX(&io->nmaps, -1, nmaps);
        GRN_FUTEX_WAKE(pnref);
        return GRN_SUCCESS;
      }
    }
    if (retry >= nretry) {
      return GRN_RESOURCE_DEADLOCK_AVOIDED;
    }
    GRN_FUTEX_WAIT(pnref);
  }
}

uint32_t
grn_io_expire(grn_ctx *ctx, grn_io *io, uint32_t count_thresh, uint32_t limit)
{
  uint32_t m, n = 0, ln = io->nmaps;
  switch ((io->flags & (GRN_IO_EXPIRE_GTICK | GRN_IO_EXPIRE_SEGMENT))) {
  case GRN_IO_EXPIRE_GTICK:
    {
      uint32_t nref, nmaps, *pnref = &io->nref;
      GRN_ATOMIC_ADD_EX(pnref, 1, nref);
      if (!nref && grn_gtick - io->count > count_thresh) {
        {
          uint32_t i = io->header->n_arrays;
          grn_io_array_spec *array_specs = (grn_io_array_spec *)io->user_header;
          while (i--) {
            memset(io->ainfo[i].addrs,
                   0,
                   sizeof(void *) * array_specs[i].max_n_segments);
          }
        }
        {
          uint32_t fno;
          for (fno = 0; fno < io->max_map_seg; fno++) {
            grn_io_mapinfo *info = &(io->maps[fno]);
            if (info->map) {
              fileinfo *fi = &(io->fis[fno]);
              GRN_MUNMAP(ctx,
                         io,
                         &info->fmo,
                         fi,
                         info->map,
                         io->header->segment_size);
              info->map = NULL;
              info->nref = 0;
              info->count = grn_gtick;
              GRN_ATOMIC_ADD_EX(&io->nmaps, -1, nmaps);
              n++;
            }
          }
        }
      }
      GRN_ATOMIC_ADD_EX(pnref, -1, nref);
    }
    break;
  case GRN_IO_EXPIRE_SEGMENT:
    for (m = io->max_map_seg; n < limit && m; m--) {
      if (!grn_io_seg_expire(ctx, io, m, 0)) {
        n++;
      }
    }
    break;
  case (GRN_IO_EXPIRE_GTICK | GRN_IO_EXPIRE_SEGMENT):
    {
      grn_io_mapinfo *info = io->maps;
      for (m = io->max_map_seg; n < limit && m; info++, m--) {
        if (info->map && (grn_gtick - info->count) > count_thresh) {
          uint32_t nmaps, nref, *pnref = &info->nref;
          GRN_ATOMIC_ADD_EX(pnref, 1, nref);
          if (!nref && info->map && (grn_gtick - info->count) > count_thresh) {
            GRN_MUNMAP(ctx,
                       io,
                       &info->fmo,
                       NULL,
                       info->map,
                       io->header->segment_size);
            GRN_ATOMIC_ADD_EX(&io->nmaps, -1, nmaps);
            info->map = NULL;
            info->count = grn_gtick;
            n++;
          }
          GRN_ATOMIC_ADD_EX(pnref, -1, nref);
        }
      }
    }
    break;
  }
  if (n) {
    GRN_LOG(ctx,
            GRN_LOG_DEBUG,
            "<%p:%x> expired i=%p max=%d (%d/%d)",
            ctx,
            grn_gtick,
            io,
            io->max_map_seg,
            n,
            ln);
  }
  return n;
}

void *
grn_io_anon_map(grn_ctx *ctx, grn_io_mapinfo *mi, size_t length)
{
  return (mi->map = GRN_MMAP(ctx, NULL, &mi->fmo, NULL, 0, length));
}

void
grn_io_anon_unmap(grn_ctx *ctx, grn_io_mapinfo *mi, size_t length)
{
  GRN_MUNMAP(ctx, NULL, &mi->fmo, NULL, mi->map, length);
}

grn_rc
grn_io_lock(grn_ctx *ctx, grn_io *io, int timeout)
{
  static int _ncalls = 0, _ncolls = 0;
  uint32_t count, count_log_border = 1000;
  uint32_t rc_check_interval = 1000;
  _ncalls++;
  if (!io) {
    return GRN_INVALID_ARGUMENT;
  }
  for (count = 0;; count++) {
    uint32_t lock;
    GRN_ATOMIC_ADD_EX(io->lock, 1, lock);
    if (lock) {
      GRN_ATOMIC_ADD_EX(io->lock, -1, lock);
      if (count == count_log_border) {
        GRN_LOG(ctx,
                GRN_LOG_NOTICE,
                "io(%s) collisions(%d/%d): lock failed %d times",
                io->path,
                _ncolls,
                _ncalls,
                count_log_border);
      }
      if (!timeout || (timeout > 0 && (uint32_t)timeout == count)) {
        GRN_LOG(ctx,
                GRN_LOG_WARNING,
                "[DB Locked] time out(%d): io(%s) collisions(%d/%d)",
                timeout,
                io->path,
                _ncolls,
                _ncalls);
        break;
      }
      if (!(++_ncolls % 1000000) && (_ncolls > _ncalls)) {
        if (_ncolls < 0 || _ncalls < 0) {
          _ncolls = 0;
          _ncalls = 0;
        } else {
          GRN_LOG(ctx,
                  GRN_LOG_NOTICE,
                  "io(%s) collisions(%d/%d)",
                  io->path,
                  _ncolls,
                  _ncalls);
        }
      }
      if ((count % rc_check_interval) == 0) {
        if (ctx->rc != GRN_SUCCESS) {
          return ctx->rc;
        }
      }
      grn_nanosleep(GRN_LOCK_WAIT_TIME_NANOSECOND);
      continue;
    }
    return GRN_SUCCESS;
  }
  ERR(GRN_RESOURCE_DEADLOCK_AVOIDED, "grn_io_lock failed");
  return ctx->rc;
}

void
grn_io_unlock(grn_ctx *ctx, grn_io *io)
{
  if (io) {
    uint32_t lock;
    GRN_ATOMIC_ADD_EX(io->lock, -1, lock);
  }
}

void
grn_io_clear_lock(grn_io *io)
{
  if (io) {
    *io->lock = 0;
  }
}

uint32_t
grn_io_is_locked(grn_io *io)
{
  return io ? *io->lock : 0;
}

grn_rc
grn_io_flush(grn_ctx *ctx, grn_io *io)
{
  grn_rc rc = GRN_SUCCESS;
  struct _grn_io_header *header;
  uint32_t aligned_header_size;

  if (io->path[0] == '\0') {
    return GRN_SUCCESS;
  }

  header = io->header;
  aligned_header_size = grn_io_compute_base(header->header_size);

  if (GRN_MSYNC(ctx, &(io->fis[0]), header, aligned_header_size) != 0) {
    return ctx->rc;
  }

  if (io->maps) {
    uint32_t i;
    uint32_t max_mapped_segment;
    uint32_t segment_size;

    max_mapped_segment = grn_io_max_segment(io);
    segment_size = header->segment_size;
    for (i = 0; i < max_mapped_segment; i++) {
      grn_io_mapinfo *info = &(io->maps[i]);
      uint32_t nth_file_info;
      uint32_t *pnref;
      uint32_t nref;
      int msync_result;

      if (!info) {
        continue;
      }

      pnref = &info->nref;
      GRN_ATOMIC_ADD_EX(pnref, 1, nref);
      if (nref != 0) {
        GRN_ATOMIC_ADD_EX(pnref, -1, nref);
        continue;
      }

      if (!info->map) {
        GRN_ATOMIC_ADD_EX(pnref, -1, nref);
        GRN_FUTEX_WAKE(pnref);
        continue;
      }

      nth_file_info = grn_io_compute_nth_file_info(io, i);
      msync_result =
        GRN_MSYNC(ctx, &(io->fis[nth_file_info]), info->map, segment_size);
      GRN_ATOMIC_ADD_EX(pnref, -1, nref);
      GRN_FUTEX_WAKE(pnref);

      if (msync_result != 0) {
        rc = ctx->rc;
        break;
      }
    }
  }

  return rc;
}

bool
grn_io_is_corrupt(grn_ctx *ctx, grn_io *io)
{
  uint32_t i;
  uint32_t n_files;

  if (!io) {
    return false;
  }

  n_files = grn_io_n_files(ctx, io);
  for (i = 0; i < n_files; i++) {
    char path[PATH_MAX];
    struct stat s;
    gen_pathname(io->path, path, i);
    if (stat(path, &s) != 0) {
      SERR("[io][corrupt] used path doesn't exist: <%s>", path);
      return true;
    }
  }

  return false;
}

size_t
grn_io_get_disk_usage(grn_ctx *ctx, grn_io *io)
{
  size_t usage = 0;
  uint32_t i;
  uint32_t n_files;

  if (!io) {
    return usage;
  }

  n_files = grn_io_n_files(ctx, io);
  for (i = 0; i < n_files; i++) {
    char path[PATH_MAX];
    struct stat s;
    gen_pathname(io->path, path, i);
    if (stat(path, &s) != 0) {
      continue;
    }
    usage += (size_t)(s.st_size);
  }

  return usage;
}

/** mmap abstraction **/

static size_t mmap_size = 0;

size_t
grn_get_memory_map_size(void)
{
  return mmap_size;
}

#ifdef WIN32

inline static grn_rc
grn_fileinfo_open_v1(grn_ctx *ctx, fileinfo *fi, const char *path, int flags)
{
  CRITICAL_SECTION_INIT(fi->cs);
  return GRN_SUCCESS;
}

static inline void *
grn_mmap_v1(
  grn_ctx *ctx, HANDLE *fmo, fileinfo *fi, int64_t offset, size_t length)
{
  void *res;
  if (!fi) {
    if (fmo) {
      *fmo = NULL;
    }
    /* TODO: Try to support VirtualAlloc() as anonymous mmap in POSIX.
     * If VirtualAlloc() provides better performance rather than malloc(),
     * we'll use it.
     */
    return GRN_CALLOC(length);
  }
  /* CRITICAL_SECTION_ENTER(fi->cs); */
  /* try to create fmo */
  /* TODO: Add support for 32bit over offset by using dwMaximumSizeHigh. */
  *fmo = CreateFileMapping(fi->fh,
                           NULL,
                           PAGE_READWRITE,
                           0,
                           (DWORD)(offset + length),
                           NULL);
  if (!*fmo) {
    SERR("CreateFileMapping(%lu + %" GRN_FMT_SIZE ") failed "
         "<%" GRN_FMT_SIZE ">",
         (DWORD)offset,
         length,
         mmap_size);
    return NULL;
  }
  res = MapViewOfFile(*fmo, FILE_MAP_WRITE, 0, (DWORD)offset, (SIZE_T)length);
  if (!res) {
    SERR("MapViewOfFile(%lu,%" GRN_FMT_SIZE ") failed <%" GRN_FMT_SIZE ">",
         (DWORD)offset,
         length,
         mmap_size);
    return NULL;
  }
  /* CRITICAL_SECTION_LEAVE(fi->cs); */
  mmap_size += length;
  return res;
}

static inline int
grn_munmap_v1(
  grn_ctx *ctx, HANDLE *fmo, fileinfo *fi, void *start, size_t length)
{
  int r = 0;

  if (!fi) {
    GRN_FREE(start);
    return r;
  }

  if (!fmo) {
    GRN_FREE(start);
    return r;
  }

  if (*fmo) {
    if (!FlushViewOfFile(start, length)) {
      SERR("FlushViewOfFile(<%p>, <%" GRN_FMT_SIZE ">) failed on unmap",
           start,
           length);
      r = -1;
    }
    if (UnmapViewOfFile(start)) {
      mmap_size -= length;
    } else {
      SERR("UnmapViewOfFile(%p,%" GRN_FMT_SIZE ") failed <%" GRN_FMT_SIZE ">",
           start,
           length,
           mmap_size);
      r = -1;
    }
    if (!CloseHandle(*fmo)) {
      SERR("CloseHandle(%p,%" GRN_FMT_SIZE ") failed <%" GRN_FMT_SIZE ">",
           start,
           length,
           mmap_size);
    }
    *fmo = NULL;
  } else {
    GRN_FREE(start);
  }

  return r;
}

static inline grn_rc
grn_fileinfo_open_v0(grn_ctx *ctx, fileinfo *fi, const char *path, int flags)
{
  /* signature may be wrong.. */
  fi->fmo = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, NULL);
  /* open failed */
  if (fi->fmo == NULL) {
    // flock
    /* retry to open */
    fi->fmo = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, NULL);
    /* failed again */
    if (fi->fmo == NULL) {
      /* try to create fmo */
      fi->fmo = CreateFileMapping(fi->fh,
                                  NULL,
                                  PAGE_READWRITE,
                                  0,
                                  GRN_IO_FILE_SIZE_V0,
                                  NULL);
    }
    // funlock
  }
  if (fi->fmo != NULL) {
    if (GetLastError() != ERROR_ALREADY_EXISTS) {
      CRITICAL_SECTION_INIT(fi->cs);
      return GRN_SUCCESS;
    } else {
      GRN_LOG(ctx,
              GRN_LOG_ERROR,
              "fmo object already exists! handle=%p",
              fi->fh);
      CloseHandle(fi->fmo);
    }
  } else {
    GRN_LOG(ctx,
            GRN_LOG_ALERT,
            "failed to get FileMappingObject #%lu",
            GetLastError());
  }
  CloseHandle(fi->fh);
  SERR("OpenFileMapping");
  if (ctx->rc != GRN_SUCCESS && fi->path) {
    free(fi->path);
    fi->path = NULL;
  }
  return ctx->rc;
}

static inline void *
grn_mmap_v0(grn_ctx *ctx, fileinfo *fi, int64_t offset, size_t length)
{
  void *res;
  if (!fi) {
    return GRN_CALLOC(length);
  }
  /* file must be exceeded to GRN_IO_FILE_SIZE_V0 when FileMappingObject
  created. and, after fmo created, it's not allowed to expand the size of file.
  DWORD tail = (DWORD)(offset + length);
  DWORD filesize = GetFileSize(fi->fh, NULL);
  if (filesize < tail) {
    if (SetFilePointer(fi->fh, tail, NULL, FILE_BEGIN) != tail) {
      grn_log("SetFilePointer failed");
      return NULL;
    }
    if (!SetEndOfFile(fi->fh)) {
      grn_log("SetEndOfFile failed");
      return NULL;
    }
    filesize = tail;
  }
  */
  /* TODO: Add support for 32bit over offset by using dwFileOffsetHigh. */
  res =
    MapViewOfFile(fi->fmo, FILE_MAP_WRITE, 0, (DWORD)offset, (SIZE_T)length);
  if (!res) {
    MERR("MapViewOfFile failed: <%" GRN_FMT_SIZE ">: %s",
         mmap_size,
         grn_error_get_current_system_message());
    return NULL;
  }
  mmap_size += length;
  return res;
}

static inline int
grn_munmap_v0(grn_ctx *ctx, fileinfo *fi, void *start, size_t length)
{
  int r = 0;

  if (!fi) {
    GRN_FREE(start);
    return 0;
  }

  if (!FlushViewOfFile(start, length)) {
    SERR("FlushViewOfFile(<%p>, <%" GRN_FMT_SIZE ">) failed on unmap",
         start,
         length);
    r = -1;
  }
  if (UnmapViewOfFile(start)) {
    mmap_size -= length;
    return r;
  } else {
    SERR("UnmapViewOfFile(%p,%" GRN_FMT_SIZE ") failed <%" GRN_FMT_SIZE ">",
         start,
         length,
         mmap_size);
    return -1;
  }
}

static inline bool
grn_fileinfo_open_common(grn_ctx *ctx,
                         fileinfo *fi,
                         const char *path,
                         int flags)
{
  bool success = true;

  fi->path = grn_strdup_raw(path);

  /* may be wrong if flags is just only O_RDWR */
  if ((flags & O_CREAT)) {
    bool exist = false;
    DWORD dwCreationDisposition;
    const char *flags_description;

    {
      struct stat stat_buffer;
      if (stat(path, &stat_buffer) == 0) {
        exist = true;
      }
    }

    if (flags & O_EXCL) {
      dwCreationDisposition = CREATE_NEW;
      flags_description = "O_RDWR|O_CREAT|O_EXCL";
    } else {
      dwCreationDisposition = OPEN_ALWAYS;
      flags_description = "O_RDWR|O_CREAT";
    }
    fi->fh = CreateFile(path,
                        GRN_IO_FILE_CREATE_MODE,
                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                        NULL,
                        dwCreationDisposition,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);
    if (fi->fh == INVALID_HANDLE_VALUE) {
      SERR("CreateFile(<%s>, <%s>) failed", path, flags_description);
      success = false;
      if (!exist) {
        struct stat stat_buffer;
        if (stat(path, &stat_buffer) == 0) {
          GRN_LOG(ctx,
                  GRN_LOG_INFO,
                  "[io][open] "
                  "delete a newly created file because of open error: <%s>",
                  path);
          DeleteFile(path);
        }
      }
      goto exit;
    }

    switch (dwCreationDisposition) {
    case CREATE_NEW:
      GRN_LOG(ctx, GRN_LOG_INFO, "[io][open] create new file: <%s>", path);
      break;
    case OPEN_ALWAYS:
      if (GetLastError() == ERROR_ALREADY_EXISTS) {
        GRN_LOG(ctx,
                GRN_LOG_INFO,
                "[io][open] open existing file because it exists: <%s>",
                path);
      } else {
        GRN_LOG(ctx,
                GRN_LOG_INFO,
                "[io][open] create new file because it doesn't exist: <%s>",
                path);
      }
      break;
    default:
      break;
    }

    {
      FILE_SET_SPARSE_BUFFER buffer;
      buffer.SetSparse = TRUE;
      DWORD returned_bytes;
      if (!DeviceIoControl(fi->fh,
                           FSCTL_SET_SPARSE,
                           &buffer,
                           sizeof(FILE_SET_SPARSE_BUFFER),
                           NULL,
                           0,
                           &returned_bytes,
                           NULL)) {
        GRN_LOG(ctx,
                GRN_LOG_INFO,
                "Tried to make file sparse but failed: "
                "DeviceIoControl(FSCTL_SET_SPARSE): "
                "<%s>: <%s>",
                path,
                grn_error_get_current_system_message());
      }
    }

    goto exit;
  }

  if ((flags & O_TRUNC)) {
    CloseHandle(fi->fh);
    /* unable to assign OPEN_ALWAYS and TRUNCATE_EXISTING at once */
    fi->fh = CreateFile(path,
                        GRN_IO_FILE_CREATE_MODE,
                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                        NULL,
                        TRUNCATE_EXISTING,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);
    if (fi->fh == INVALID_HANDLE_VALUE) {
      SERR("CreateFile(<%s>, <O_RDWR|O_TRUNC>) failed", path);
      success = false;
      goto exit;
    }
    GRN_LOG(ctx, GRN_LOG_INFO, "[io][open] truncated: <%s>", path);
    goto exit;
  }
  /* O_RDWR only */
  fi->fh = CreateFile(path,
                      GRN_IO_FILE_CREATE_MODE,
                      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                      NULL,
                      OPEN_EXISTING,
                      FILE_ATTRIBUTE_NORMAL,
                      NULL);
  if (fi->fh == INVALID_HANDLE_VALUE) {
    SERR("CreateFile(<%s>, <O_RDWR>) failed", path);
    success = false;
    goto exit;
  }
  GRN_LOG(ctx, GRN_LOG_INFO, "[io][open] open existing file: <%s>", path);

exit:
  if (!success && fi->path) {
    free(fi->path);
    fi->path = NULL;
  }
  return success;
}

static inline grn_rc
grn_fileinfo_open(grn_ctx *ctx, fileinfo *fi, const char *path, int flags)
{
  struct _grn_io_header io_header;
  LARGE_INTEGER file_size;
  int version = grn_io_version_default;

  if (!grn_fileinfo_open_common(ctx, fi, path, flags)) {
    if (fi->fh != INVALID_HANDLE_VALUE) {
      CloseHandle(fi->fh);
      fi->fh = INVALID_HANDLE_VALUE;
    }
    return ctx->rc;
  }

  if (GetFileSizeEx(fi->fh, &file_size) && file_size.QuadPart > 0) {
    DWORD header_size;
    DWORD read_bytes;
    header_size = sizeof(struct _grn_io_header);
    ReadFile(fi->fh, &io_header, header_size, &read_bytes, NULL);
    if (read_bytes == header_size) {
      version = io_header.version;
    }
    SetFilePointer(fi->fh, 0, NULL, FILE_BEGIN);
  }

  if (version == 0) {
    return grn_fileinfo_open_v0(ctx, fi, path, flags);
  } else {
    return grn_fileinfo_open_v1(ctx, fi, path, flags);
  }
}

static inline int
grn_guess_io_version(grn_ctx *ctx, grn_io *io, fileinfo *fi)
{
  if (io) {
    return io->header->version;
  }

  if (fi) {
    if (fi->fmo) {
      return 0;
    } else {
      return 1;
    }
  }

  return grn_io_version_default;
}

static inline void *
grn_mmap(grn_ctx *ctx,
         grn_io *io,
         HANDLE *fmo,
         fileinfo *fi,
         int64_t offset,
         size_t length,
         const char *file,
         int line,
         const char *func)
{
  if (grn_fail_malloc_should_fail(length, file, line, func)) {
    MERR("[alloc][fail][mmap] <%u>: <%" GRN_FMT_SIZE ">: <%s>: "
         "<%p:%" GRN_FMT_INT64D ":%" GRN_FMT_SIZE ">: "
         "%s:%d: %s",
         grn_alloc_count(),
         mmap_size,
         io ? (io->path[0] ? io->path : "(memory)") : "(null)",
         fi ? fi->fh : NULL,
         offset,
         length,
         file,
         line,
         func);
    return NULL;
  } else {
    int version;

    version = grn_guess_io_version(ctx, io, fi);

    if (version == 0) {
      return grn_mmap_v0(ctx, fi, offset, length);
    } else {
      return grn_mmap_v1(ctx, fmo, fi, offset, length);
    }
  }
}

static inline int
grn_munmap(grn_ctx *ctx,
           grn_io *io,
           HANDLE *fmo,
           fileinfo *fi,
           void *start,
           size_t length)
{
  int version;

  version = grn_guess_io_version(ctx, io, fi);

  if (version == 0) {
    return grn_munmap_v0(ctx, fi, start, length);
  } else {
    return grn_munmap_v1(ctx, fmo, fi, start, length);
  }
}

static inline grn_rc
grn_fileinfo_close(grn_ctx *ctx, fileinfo *fi)
{
  if (fi->fmo != NULL) {
    CloseHandle(fi->fmo);
    fi->fmo = NULL;
  }
  if (fi->fh != INVALID_HANDLE_VALUE) {
    if (grn_logger_pass(ctx, GRN_LOG_DEBUG)) {
      GRN_LOG(ctx, GRN_LOG_DEBUG, "[io][close] <%s>", fi->path);
    }
    CloseHandle(fi->fh);
    CRITICAL_SECTION_FIN(fi->cs);
    fi->fh = INVALID_HANDLE_VALUE;
  }
  if (fi->path) {
    free(fi->path);
    fi->path = NULL;
  }
  return GRN_SUCCESS;
}

static inline void
grn_fileinfo_init(fileinfo *fis, uint32_t nfis)
{
  for (; nfis--; fis++) {
    fis->path = NULL;
    fis->fh = INVALID_HANDLE_VALUE;
    fis->fmo = NULL;
  }
}

static inline int
grn_fileinfo_opened(fileinfo *fi)
{
  return fi->fh != INVALID_HANDLE_VALUE;
}

static inline int
grn_msync(grn_ctx *ctx, fileinfo *fi, void *start, size_t length)
{
  BOOL succeeded;
  SYSTEMTIME system_time;
  FILETIME file_time;

  succeeded = FlushViewOfFile(start, length);
  if (!succeeded) {
    SERR("FlushViewOfFile(<%p>, <%" GRN_FMT_SIZE ">) failed on sync",
         start,
         length);
    return -1;
  }

  if (fi->fh == INVALID_HANDLE_VALUE) {
    return 0;
  }

  GetSystemTime(&system_time);
  succeeded = SystemTimeToFileTime(&system_time, &file_time);
  if (!succeeded) {
    SERR("SystemTimeToFileTime(<%04u-%02u-%02uT%02u:%02u:%02u.%03u>) failed",
         system_time.wYear,
         system_time.wMonth,
         system_time.wDay,
         system_time.wHour,
         system_time.wMinute,
         system_time.wSecond,
         system_time.wMilliseconds);
    return -1;
  }

  succeeded = SetFileTime(fi->fh, NULL, NULL, &file_time);
  if (!succeeded) {
    SERR("SetFileTime(<%p>, <%p>, <%" GRN_FMT_SIZE ">) failed",
         fi->fh,
         start,
         length);
    return -1;
  }

  return 0;
}

static inline grn_rc
grn_pread(grn_ctx *ctx, fileinfo *fi, void *buf, size_t count, off_t offset)
{
  DWORD r, len;
  CRITICAL_SECTION_ENTER(fi->cs);
  r = SetFilePointer(fi->fh, offset, NULL, FILE_BEGIN);
  if (r == INVALID_SET_FILE_POINTER) {
    SERR("SetFilePointer");
  } else {
    if (!ReadFile(fi->fh, buf, (DWORD)count, &len, NULL)) {
      SERR("ReadFile");
    } else if (len != count) {
      /* todo : should retry ? */
      ERR(GRN_INPUT_OUTPUT_ERROR,
          "ReadFile %" GRN_FMT_SIZE " != %lu",
          count,
          len);
    }
  }
  CRITICAL_SECTION_LEAVE(fi->cs);
  return ctx->rc;
}

static inline grn_rc
grn_pwrite(grn_ctx *ctx, fileinfo *fi, void *buf, size_t count, off_t offset)
{
  DWORD r, len;
  CRITICAL_SECTION_ENTER(fi->cs);
  r = SetFilePointer(fi->fh, offset, NULL, FILE_BEGIN);
  if (r == INVALID_SET_FILE_POINTER) {
    SERR("SetFilePointer");
  } else {
    if (!WriteFile(fi->fh, buf, (DWORD)count, &len, NULL)) {
      SERR("WriteFile");
    } else if (len != count) {
      /* todo : should retry ? */
      ERR(GRN_INPUT_OUTPUT_ERROR,
          "WriteFile %" GRN_FMT_SIZE " != %lu",
          count,
          len);
    }
  }
  CRITICAL_SECTION_LEAVE(fi->cs);
  return ctx->rc;
}

#else /* WIN32 */

inline static grn_rc
grn_fileinfo_open(grn_ctx *ctx, fileinfo *fi, const char *path, int flags)
{
  struct stat st;
  fi->path = grn_strdup_raw(path);
  grn_open(fi->fd, path, flags);
  if (fi->fd == -1) {
    ERRNO_ERR("failed to open file info path: <%s>", path);
    free(fi->path);
    fi->path = NULL;
    return ctx->rc;
  }
  if (fstat(fi->fd, &st) == -1) {
    ERRNO_ERR("failed to stat file info path: <%s>", path);
    grn_close(fi->fd);
    free(fi->path);
    fi->path = NULL;
    return ctx->rc;
  }
  fi->dev = st.st_dev;
  fi->inode = st.st_ino;
  GRN_LOG(ctx, GRN_LOG_DUMP, "[io][open] <%s>", path);
  return GRN_SUCCESS;
}

static inline void
grn_fileinfo_init(fileinfo *fis, uint32_t nfis)
{
  for (; nfis--; fis++) {
    fis->path = NULL;
    fis->fd = -1;
  }
}

static inline int
grn_fileinfo_opened(fileinfo *fi)
{
  return fi->fd != -1;
}

static inline grn_rc
grn_fileinfo_close(grn_ctx *ctx, fileinfo *fi)
{
  if (fi->fd != -1) {
    if (grn_close(fi->fd) == -1) {
      SERR("close");
      return ctx->rc;
    }
    fi->fd = -1;
    if (fi->path) {
      GRN_LOG(ctx, GRN_LOG_DUMP, "[io][close] <%s>", fi->path);
    }
  }
  if (fi->path) {
    free(fi->path);
    fi->path = NULL;
  }
  return GRN_SUCCESS;
}

#  if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
#    define MAP_ANONYMOUS MAP_ANON
#  endif

#  include <sys/mman.h>

inline static void *
grn_mmap(grn_ctx *ctx,
         grn_io *io,
         fileinfo *fi,
         int64_t offset,
         size_t length,
         const char *file,
         int line,
         const char *func)
{
  if (grn_fail_malloc_should_fail(length, file, line, func)) {
    MERR("[alloc][fail][mmap] <%u>: <%" GRN_FMT_SIZE ">: <%s>: "
         "<%d:%" GRN_FMT_INT64D ":%" GRN_FMT_SIZE ">: "
         "%s:%d: %s",
         grn_alloc_count(),
         mmap_size,
         io ? (io->path[0] ? io->path : "(memory)") : "(null)",
         fi ? fi->fd : 0,
         offset,
         length,
         file,
         line,
         func);
    return NULL;
  } else {
    void *res;
    int fd, flags;
    if (fi) {
      struct stat s;
      int64_t tail = offset + (int64_t)length;
      fd = fi->fd;
      if ((fstat(fd, &s) == -1) ||
          (s.st_size < tail && ftruncate(fd, tail) == -1)) {
        SERR("fstat");
        return NULL;
      }
      flags = MAP_SHARED;
    } else {
      fd = -1;
      flags = MAP_PRIVATE | MAP_ANONYMOUS;
#  ifdef MAP_ALIGNED_SUPER
      flags |= MAP_ALIGNED_SUPER;
#  endif
    }
    res = mmap(NULL, length, PROT_READ | PROT_WRITE, flags, fd, offset);
    if (MAP_FAILED == res) {
      MERR("mmap(%" GRN_FMT_SIZE ",%d,%" GRN_FMT_INT64D ")=%s <%" GRN_FMT_SIZE
           ">",
           length,
           fd,
           offset,
           strerror(errno),
           mmap_size);
      return NULL;
    }
    mmap_size += length;
    return res;
  }
}

static inline int
grn_msync(grn_ctx *ctx, fileinfo *fi, void *start, size_t length)
{
  int r = msync(start, length, MS_SYNC);
  if (r == -1) {
    SERR("msync");
    return r;
  }

  if (fi->fd > 0) {
    struct timeval time_values[2];
    struct timespec time_specs[2];
    gettimeofday(&(time_values[0]), NULL);
    time_values[1] = time_values[0];
    time_specs[0].tv_sec = time_values[0].tv_sec;
    time_specs[0].tv_nsec = time_values[0].tv_usec * 1000;
    time_specs[1] = time_specs[0];
    r = grn_futimens(fi->fd, time_values, time_specs);
    if (r == -1) {
      SERR("%s failed: <%d>", grn_futimens_name, fi->fd);
    }
  }

  return r;
}

static inline int
grn_munmap(grn_ctx *ctx, grn_io *io, fileinfo *fi, void *start, size_t length)
{
  int res;
  res = munmap(start, length);
  if (res == 0) {
    mmap_size -= length;
  } else {
    SERR("munmap(%p,%" GRN_FMT_LLU ") failed <%" GRN_FMT_LLU ">",
         start,
         (unsigned long long int)length,
         (unsigned long long int)mmap_size);
  }
  return res;
}

static inline grn_rc
grn_pread(grn_ctx *ctx, fileinfo *fi, void *buf, size_t count, off_t offset)
{
  ssize_t r = pread(fi->fd, buf, count, offset);
  if (r == -1) {
    SERR("pread");
    return ctx->rc;
  }
  if ((size_t)r != count) {
    /* todo : should retry ? */
    ERR(GRN_INPUT_OUTPUT_ERROR,
        "pread returned %" GRN_FMT_SSIZE " != %" GRN_FMT_SIZE,
        r,
        count);
    return ctx->rc;
  }
  return GRN_SUCCESS;
}

static inline grn_rc
grn_pwrite(grn_ctx *ctx, fileinfo *fi, void *buf, size_t count, off_t offset)
{
  ssize_t r = pwrite(fi->fd, buf, count, offset);
  if (r == -1) {
    SERR("pwrite");
    return ctx->rc;
  }
  if ((size_t)r != count) {
    /* todo : should retry ? */
    ERR(GRN_INPUT_OUTPUT_ERROR,
        "pwrite returned %" GRN_FMT_SSIZE " != %" GRN_FMT_SIZE,
        r,
        count);
    return ctx->rc;
  }
  return GRN_SUCCESS;
}

#endif /* WIN32 */

bool
grn_io_warm_path(grn_ctx *ctx, grn_io *io, const char *path)
{
  FILE *input = grn_fopen(path, "rb");
  if (!input) {
    SERR("[io][warm] failed to open a file: <%s>", path);
    return false;
  }

  GRN_LOG(ctx, GRN_LOG_DUMP, "[io][warm] <%s>", path);
  char buffer[4096];
  while (fread(buffer, 1, sizeof(buffer), input) != 0) {
    /* Do nothing */
  }
  fclose(input);
  return true;
}

grn_rc
grn_io_warm(grn_ctx *ctx, grn_io *io)
{
  if (io->path[0] == '\0') {
    return GRN_SUCCESS;
  }

  char buffer[PATH_MAX];
  uint32_t i;
  uint32_t n_files = grn_io_n_files(ctx, io);
  for (i = 0; i < n_files; i++) {
    gen_pathname(io->path, buffer, i);
    if (!grn_io_warm_path(ctx, io, buffer)) {
      break;
    }
  }
  return ctx->rc;
}
