/* -*- c-basic-offset: 2 -*- */
/* Copyright(C) 2009-2015 Brazil

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License version 2.1 as published by the Free Software Foundation.

  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"

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

typedef struct _grn_io_fileinfo {
#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;

inline static grn_rc grn_fileinfo_open(grn_ctx *ctx, fileinfo *fi, const char *path, int flags);
inline static void grn_fileinfo_init(fileinfo *fis, int nfis);
inline static int grn_fileinfo_opened(fileinfo *fi);
inline static 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,
                              off_t offset, size_t length);
inline static 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)))
# 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,
                              off_t offset, size_t length);
inline static int grn_munmap(grn_ctx *ctx, grn_io *io, fileinfo *fi,
                             void *start, size_t length);
# define GRN_MUNMAP(ctx,io,fmo,fi,start,length) \
  (grn_munmap((ctx), (io), (fi), (start), (length)))
# ifdef USE_FAIL_MALLOC
inline static void * grn_fail_mmap(grn_ctx *ctx, grn_io *io, fileinfo *fi,
                                   off_t offset, size_t length,
                                   const char* file, int line, const char *func);
#  define GRN_MMAP(ctx,io,fmo,fi,offset,length)\
  (grn_fail_mmap((ctx), (io), (fi), (offset), (length),\
                 __FILE__, __LINE__, __FUNCTION__))
# else /* USE_FAIL_MALLOC */
#  define GRN_MMAP(ctx,io,fmo,fi,offset,length)\
  (grn_mmap((ctx), (io), (fi), (offset), (length)))
# endif /* USE_FAIL_MALLOC */
#endif  /* WIN32 */
inline static int grn_msync(grn_ctx *ctx, void *start, size_t length);
inline static grn_rc grn_pread(grn_ctx *ctx, fileinfo *fi, void *buf, size_t count, off_t offset);
inline static 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 = 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 + grn_pagesize - 1) & ~(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, unsigned long 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 unsigned long
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)
{
  unsigned long file_size;

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

grn_io *
grn_io_create_tmp(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(&grn_gctx, 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_GMALLOCN(grn_io, 1))) {
      grn_io_mapinfo *maps = NULL;
      if ((maps = GRN_GCALLOC(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_GFREE(io);
    }
    GRN_MUNMAP(&grn_gctx, NULL, NULL, NULL, header, b);
  }
  return NULL;
}

static void
grn_io_register(grn_io *io)
{
  if (io->fis && (io->flags & (GRN_IO_EXPIRE_GTICK|GRN_IO_EXPIRE_SEGMENT))) {
    grn_bool succeeded = GRN_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, strlen(io->path),
                     (void **)&io, NULL)) {
      succeeded = GRN_TRUE;
    }
    CRITICAL_SECTION_LEAVE(grn_glock);
    if (!succeeded) {
      GRN_LOG(&grn_gctx, GRN_LOG_WARNING,
              "grn_io_register(%s) failed", io->path);
    }
  }
}

static void
grn_io_unregister(grn_io *io)
{
  if (io->fis && (io->flags & (GRN_IO_EXPIRE_GTICK|GRN_IO_EXPIRE_SEGMENT))) {
    grn_bool succeeded = GRN_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, strlen(io->path), NULL);
      succeeded = GRN_TRUE;
    }
    CRITICAL_SECTION_LEAVE(grn_glock);
    if (!succeeded) {
      GRN_LOG(&grn_gctx, 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;
  uint32_t b, max_nfiles;
  uint32_t bs;
  struct _grn_io_header *header;
  uint32_t version = grn_io_version_default;
  unsigned long file_size;

  if (!path) {
    return grn_io_create_tmp(header_size, segment_size, max_segment, mode, flags);
  }
  if (!*path || (strlen(path) > 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_GMALLOCN(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(&grn_gctx, 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, header, b);
        if ((io = GRN_GMALLOCN(grn_io, 1))) {
          grn_io_mapinfo *maps = NULL;
          if ((maps = GRN_GCALLOC(sizeof(grn_io_mapinfo) * max_segment))) {
            grn_strncpy(io->path, PATH_MAX, path, PATH_MAX);
            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(io);
            return io;
          }
          GRN_GFREE(io);
        }
        GRN_MUNMAP(&grn_gctx, NULL, &fis->fmo, fis, header, b);
      }
      grn_fileinfo_close(ctx, fis);
      if (grn_unlink(path) == -1) {
        GRN_LOG(ctx, GRN_LOG_ERROR,
                "failed to grn_unlink() path on grn_io_create() error: "
                "<%s>: <%s>",
                path, grn_strerror(errno));
      }
    }
    GRN_GFREE(fis);
  }
  return NULL;
}

static grn_rc
array_init_(grn_io *io, int n_arrays, size_t hsize, size_t msize)
{
  int 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_GCALLOC(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; (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 = (1 << we) - 1;
    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_io *io, int n_arrays)
{
  if (n_arrays) {
    int 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_(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, int n_arrays, grn_io_array_spec *array_specs)
{
  if (n_arrays) {
    int 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, header_size + hsize,
                            segment_size, nsegs, mode, GRN_IO_EXPIRE_GTICK))) {
      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;
      if (!array_init_(io, n_arrays, hsize, msize)) {
        return io;
      }
      ERR(GRN_NO_MEMORY_AVAILABLE, "grn_io_create_with_array failed");
      grn_io_close(ctx, io);
    }
  }
  return NULL;
}

inline static uint32_t
segment_alloc(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_GCALLOC(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_GFREE(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(io))) {
        *flags |= GRN_TABLE_ADDED;
      }
    }
  }
  if (*sp) {
    uint32_t pseg = *sp - 1;
    GRN_IO_SEG_REF(io, pseg, *p);
    if (*p) { GRN_IO_SEG_UNREF(io, pseg); };
  }
}

void *
grn_io_array_at(grn_ctx *ctx, grn_io *io, uint32_t array, off_t offset, int *flags)
{
  void *res;
  GRN_IO_ARRAY_AT(io,array,offset,flags,res);
  return res;
}

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_RDWR | GRN_OPEN_FLAG_BINARY);
  if (fd != -1) {
    struct stat s;
    if (fstat(fd, &s) != -1 && 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(path);
        GRN_LOG(ctx, GRN_LOG_ERROR,
                "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(path);
    GRN_LOG(ctx, GRN_LOG_ERROR,
            "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)
{
  grn_io *io;
  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 || (strlen(path) > PATH_MAX - 4)) { return NULL; }
  {
    struct _grn_io_header h;
    int fd;
    grn_open(fd, path, O_RDWR | GRN_OPEN_FLAG_BINARY);
    if (fd == -1) {
      ERRNO_ERR(path);
      GRN_LOG(ctx, GRN_LOG_ERROR,
              "failed to open path: <%s>",
              path);
      return NULL;
    }
    if (fstat(fd, &s) != -1 && 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)) {
          header_size = h.header_size;
          segment_size = h.segment_size;
          max_segment = h.max_segment;
          flags = h.flags;
        } else {
          ERR(GRN_INCOMPATIBLE_FILE_FORMAT,
              "failed to open: format ID is different: <%s>: <%.*s>",
              path,
              (int)GRN_IO_IDSTR_LEN, GRN_IO_IDSTR);
        }
      }
    }
    grn_close(fd);
    if (!segment_size) { return NULL; }
  }
  b = grn_io_compute_base(header_size);
  bs = grn_io_compute_base_segment(b, segment_size);
  grn_fileinfo_init(&fi, 1);
  if (!grn_fileinfo_open(ctx, &fi, path, O_RDWR)) {
    struct _grn_io_header *header;
    header = GRN_MMAP(&grn_gctx, NULL, &(fi.fmo), &fi, 0, b);
    if (header) {
      unsigned long file_size;
      unsigned int max_nfiles;
      fileinfo *fis;

      file_size = grn_io_compute_file_size(header->version);
      max_nfiles = grn_io_compute_max_n_files(segment_size, max_segment,
                                              bs, file_size);
      fis = GRN_GMALLOCN(fileinfo, max_nfiles);
      if (!fis) {
        GRN_MUNMAP(&grn_gctx, NULL, &(fi.fmo), &fi, header, b);
        grn_fileinfo_close(ctx, &fi);
        return NULL;
      }
      grn_fileinfo_init(fis, max_nfiles);
      grn_memcpy(fis, &fi, sizeof(fileinfo));
      if ((io = GRN_GMALLOC(sizeof(grn_io)))) {
        grn_io_mapinfo *maps = NULL;
        if ((maps = GRN_GCALLOC(sizeof(grn_io_mapinfo) * max_segment))) {
          grn_strncpy(io->path, PATH_MAX, path, PATH_MAX);
          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;
            if (!array_init(io, io->header->n_arrays)) {
              grn_io_register(io);
              return io;
            }
          }
          if (io->maps) { GRN_GFREE(io->maps); }
        }
        GRN_GFREE(io);
      }
      GRN_GFREE(fis);
      GRN_MUNMAP(&grn_gctx, NULL, &(fi.fmo), &fi, header, b);
    }
    grn_fileinfo_close(ctx, &fi);
  }
  return NULL;
}

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(io);
  if (io->ainfo) { GRN_GFREE(io->ainfo); }
  if (io->maps) {
    int i;
    uint32_t max_segment;
    uint32_t segment_size;
    unsigned long file_size;
    uint32_t 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);
    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 / segments_per_file;
          fi = &io->fis[fno];
        }
        GRN_MUNMAP(&grn_gctx, io, &mi->fmo, fi, mi->map, segment_size);
      }
    }
    GRN_GFREE(io->maps);
  }
  GRN_MUNMAP(&grn_gctx, io, (io->fis ? &io->fis->fmo : NULL),
             io->fis, io->header, io->base);
  if (io->fis) {
    int i;
    for (i = 0; i < max_nfiles; i++) {
      fileinfo *fi = &(io->fis[i]);
      grn_fileinfo_close(ctx, fi);
    }
    GRN_GFREE(io->fis);
  }
  GRN_GFREE(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;
}

inline static void
gen_pathname(const char *path, char *buffer, int 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';
  }
}

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

  if (io->header->curr_size) {
    unsigned long file_size;
    file_size = grn_io_compute_file_size(io->header->version);
    nfiles = (uint32_t) ((io->header->curr_size + file_size - 1) / file_size);
  } else {
    nfiles = grn_io_max_n_files(io);
  }
  for (fno = 0; fno < nfiles; fno++) {
    gen_pathname(io->path, buffer, fno);
    if (stat(buffer, &s)) {
      SERR(buffer);
      GRN_LOG(ctx, GRN_LOG_ERROR,
              "failed to stat path to compute size: <%s>",
              buffer);
    } else {
      tsize += s.st_size;
    }
  }
  *size = tsize;
  return GRN_SUCCESS;
}

grn_rc
grn_io_remove(grn_ctx *ctx, const char *path)
{
  struct stat s;
  if (stat(path, &s)) {
    SERR("stat");
    return ctx->rc;
  } else if (grn_unlink(path)) {
    ERRNO_ERR(path);
    GRN_LOG(ctx, GRN_LOG_ERROR,
            "failed to remove path: <%s>",
            path);
    return ctx->rc;
  } else {
    int fno;
    char buffer[PATH_MAX];
    for (fno = 1; ; fno++) {
      gen_pathname(path, buffer, fno);
      if (!stat(buffer, &s)) {
        if (grn_unlink(buffer)) {
          ERRNO_ERR(buffer);
          GRN_LOG(ctx, GRN_LOG_ERROR,
                  "failed to remove path: <%s>",
                  buffer);
        }
      } else {
        break;
      }
    }
    return GRN_SUCCESS;
  }
}

grn_rc
grn_io_rename(grn_ctx *ctx, const char *old_name, const char *new_name)
{
  struct stat s;
  if (stat(old_name, &s)) {
    SERR("stat");
    return ctx->rc;
  } else if (rename(old_name, new_name)) {
    SERR(old_name);
    GRN_LOG(ctx, GRN_LOG_ERROR,
            "failed to rename path: <%s> -> <%s>",
            old_name, new_name);
    return ctx->rc;
  } else {
    int 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)) {
        gen_pathname(new_name, new_buffer, fno);
        if (rename(old_buffer, new_buffer)) {
          SERR(old_buffer);
          GRN_LOG(ctx, GRN_LOG_ERROR,
                  "failed to rename path: <%s> -> <%s>",
                  old_buffer, new_buffer);
        }
      } else {
        SERR("stat");
        GRN_LOG(ctx, GRN_LOG_ERROR,
                "failed to stat path to rename: <%s>",
                old_buffer);
        return ctx->rc;
      }
    }
    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)
{
  uint32_t rest = 0, size = *value_len + sizeof(grn_io_ja_ehead);
  uint32_t segment_size = io->header->segment_size;
  unsigned long file_size = grn_io_compute_file_size(io->header->version);
  uint32_t segments_per_file = file_size / segment_size;
  uint32_t bseg = segment + io->base_seg;
  int fno = bseg / segments_per_file;
  fileinfo *fi = &io->fis[fno];
  off_t base = fno ? 0 : io->base - (uint64_t)segment_size * io->base_seg;
  off_t pos = (uint64_t)segment_size * (bseg % segments_per_file) + offset + base;
  ja_element *v = GRN_MALLOC(size);
  if (!v) {
    *value = NULL;
    *value_len = 0;
    return GRN_NO_MEMORY_AVAILABLE;
  }
  if (pos + size > file_size) {
    rest = pos + size - file_size;
    size = 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;
  uint32_t rest = 0, size = value_len + sizeof(grn_io_ja_ehead);
  uint32_t segment_size = io->header->segment_size;
  unsigned long file_size = grn_io_compute_file_size(io->header->version);
  uint32_t segments_per_file = file_size / segment_size;
  uint32_t bseg = segment + io->base_seg;
  int fno = bseg / segments_per_file;
  fileinfo *fi = &io->fis[fno];
  off_t base = fno ? 0 : io->base - (uint64_t)segment_size * io->base_seg;
  off_t pos = (uint64_t)segment_size * (bseg % segments_per_file) + offset + base;
  if (pos + size > file_size) {
    rest = pos + size - file_size;
    size = 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 += 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;
  unsigned long file_size = grn_io_compute_file_size(io->header->version);
  uint32_t segments_per_file = file_size / segment_size;
  uint32_t bseg = segment + io->base_seg;
  int fno = bseg / segments_per_file;
  fileinfo *fi = &io->fis[fno];
  off_t base = fno ? 0 : io->base - (uint64_t)segment_size + io->base_seg;
  off_t pos = (uint64_t)segment_size * (bseg % 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_io *io, grn_ctx *ctx, 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 = mode;
  iw->tiny_p = 0;
  iw->segment = segment;
  iw->offset = offset;
  iw->nseg = nseg;
  iw->size = size;
  if (nseg == 1) {
    byte *addr = NULL;
    GRN_IO_SEG_REF(io, segment, addr);
    if (!addr) { return NULL; }
    iw->cached = 1;
    iw->addr = addr + offset;
  } else {
    if (!(iw->addr = GRN_MALLOC(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) {
          GRN_IO_SEG_REF(io, segment, q);
          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(io, segment);
        }
      }
      break;
    case grn_io_wronly:
      break;
    default :
      return NULL;
    }
  }
  return iw->addr;
}

grn_rc
grn_io_win_unmap(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(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) {
          GRN_IO_SEG_REF(io, segment, q);
          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(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 {\
  if (((res) = GRN_MMAP(&grn_gctx, (io), (fmo), (fi), (pos), (size)))) {\
    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 {
    unsigned long file_size = grn_io_compute_file_size(io->header->version);
    uint32_t segments_per_file = file_size / segment_size;
    uint32_t bseg = segno + io->base_seg;
    uint32_t fno = bseg / segments_per_file;
    off_t base = fno ? 0 : io->base - (uint64_t)segment_size * io->base_seg;
    off_t pos = (uint64_t)segment_size * (bseg % segments_per_file) + base;
    fileinfo *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)) {
        DO_MAP(io, &info->fmo, fi, pos, segment_size, segno, info->map);
      }
    } 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);
      if (nref > 1) {
        GRN_ATOMIC_ADD_EX(pnref, -(GRN_IO_MAX_REF + 1), 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(&grn_gctx, io, &info->fmo, fi,
                   info->map, io->header->segment_size);
        info->map = NULL;
        GRN_ATOMIC_ADD_EX(pnref, -(GRN_IO_MAX_REF + 1), 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, int 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(&grn_gctx, 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(&grn_gctx, 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_INFO, "<%p:%x> expired i=%p max=%d (%d/%d)",
            ctx, grn_gtick, io, io->max_map_seg, n, ln);
  }
  return n;
}

static uint32_t
grn_expire_(grn_ctx *ctx, int count_thresh, uint32_t limit)
{
  uint32_t n = 0;
  grn_io *io;
  GRN_HASH_EACH(ctx, grn_gctx.impl->ios, id, NULL, NULL, (void **)&io, {
    grn_plugin_close(ctx, id);
    n += grn_io_expire(ctx, io, count_thresh, limit);
    if (n >= limit) { break; }
  });
  return n;
}

uint32_t
grn_expire(grn_ctx *ctx, int count_thresh, uint32_t limit)
{
  grn_ctx *c;
  uint32_t n = 0;
  CRITICAL_SECTION_ENTER(grn_glock);
  if (grn_gtick) {
    for (c = grn_gctx.next;; c = ctx->next) {
      if (c == &grn_gctx) {
        CRITICAL_SECTION_LEAVE(grn_glock);
        n = grn_expire_(ctx, count_thresh, limit);
        CRITICAL_SECTION_ENTER(grn_glock);
        break;
      }
      if ((c->seqno & 1) && (c->seqno == c->seqno2)) { break; }
    }
  }
  grn_gtick++;
  for (c = grn_gctx.next; c != &grn_gctx; c = ctx->next) { c->seqno2 = c->seqno; }
  CRITICAL_SECTION_LEAVE(grn_glock);
  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;
  _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 && 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);
        }
      }
      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_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) {
    return GRN_SUCCESS;
  }

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

  if (grn_msync(ctx, 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]);
      if (!info) {
        continue;
      }
      if (!info->map) {
        continue;
      }
      if (grn_msync(ctx, info->map, segment_size) != 0) {
        rc = ctx->rc;
        break;
      }
    }
  }

  return rc;
}

/** mmap abstraction **/

static size_t mmap_size = 0;

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

inline static void *
grn_mmap_v1(grn_ctx *ctx, HANDLE *fmo, fileinfo *fi, off_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_GCALLOC(length);
  }
  /* CRITICAL_SECTION_ENTER(fi->cs); */
  /* try to create fmo */
  *fmo = CreateFileMapping(fi->fh, NULL, PAGE_READWRITE, 0, offset + length, NULL);
  if (!*fmo) {
    SERR("CreateFileMapping");
    GRN_LOG(ctx, GRN_LOG_ERROR,
            "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");
    GRN_LOG(ctx, GRN_LOG_ERROR,
            "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;
}

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

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

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

  if (*fmo) {
    if (UnmapViewOfFile(start)) {
      mmap_size -= length;
    } else {
      SERR("UnmapViewOfFile");
      GRN_LOG(ctx, GRN_LOG_ERROR,
              "UnmapViewOfFile(%p,%" GRN_FMT_SIZE ") failed <%" GRN_FMT_SIZE ">",
              start, length, mmap_size);
      r = -1;
    }
    if (!CloseHandle(*fmo)) {
      SERR("CloseHandle");
      GRN_LOG(ctx, GRN_LOG_ERROR,
              "CloseHandle(%p,%" GRN_FMT_SIZE ") failed <%" GRN_FMT_SIZE ">",
              start, length, mmap_size);
    }
    *fmo = NULL;
  } else {
    GRN_GFREE(start);
  }

  return r;
}

inline static 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");
  return ctx->rc;
}

inline static void *
grn_mmap_v0(grn_ctx *ctx, fileinfo *fi, off_t offset, size_t length)
{
  void *res;
  if (!fi) { return GRN_GCALLOC(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;
  }
  */
  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_current_error_message());
    return NULL;
  }
  mmap_size += length;
  return res;
}

inline static int
grn_munmap_v0(grn_ctx *ctx, fileinfo *fi, void *start, size_t length)
{
  if (!fi) {
    GRN_GFREE(start);
    return 0;
  }

  if (UnmapViewOfFile(start)) {
    mmap_size -= length;
    return 0;
  } else {
    SERR("UnmapViewOfFile");
    GRN_LOG(ctx, GRN_LOG_ERROR,
            "UnmapViewOfFile(%p,%" GRN_FMT_SIZE ") failed <%" GRN_FMT_SIZE ">",
            start, length, mmap_size);
    return -1;
  }
}

inline static grn_rc
grn_fileinfo_open_common(grn_ctx *ctx, fileinfo *fi, const char *path, int flags)
{
  /* may be wrong if flags is just only O_RDWR */
  if ((flags & O_CREAT)) {
    DWORD dwCreationDisposition;
    const char *flags_description;
    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, NULL,
                        dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, 0);
    if (fi->fh == INVALID_HANDLE_VALUE) {
      SERR("CreateFile");
      GRN_LOG(ctx, GRN_LOG_ERROR,
              "CreateFile(<%s>, <%s>) failed",
              path, flags_description);
      goto exit;
    }
    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, NULL,
                        TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if (fi->fh == INVALID_HANDLE_VALUE) {
      SERR("CreateFile");
      GRN_LOG(ctx, GRN_LOG_ERROR,
              "CreateFile(<%s>, <O_RDWR|O_TRUNC>) failed",
              path);
      goto exit;
    }
    goto exit;
  }
  /* O_RDWR only */
  fi->fh = CreateFile(path, GRN_IO_FILE_CREATE_MODE,
                      FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
                      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if (fi->fh == INVALID_HANDLE_VALUE) {
    SERR("CreateFile");
    GRN_LOG(ctx, GRN_LOG_ERROR,
            "CreateFile(<%s>, <O_RDWR>) failed",
            path);
    goto exit;
  }

exit :
  return ctx->rc;
}

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

  rc = grn_fileinfo_open_common(ctx, fi, path, flags);
  if (rc != GRN_SUCCESS) {
    return rc;
  }

  if (!(flags & O_CREAT)) {
    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);
  }
}

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

inline static void *
grn_mmap(grn_ctx *ctx, grn_io *io, HANDLE *fmo,
         fileinfo *fi, off_t offset, size_t length)
{
  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);
  }
}

inline static 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);
  }
}

inline static 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) {
    CloseHandle(fi->fh);
    CRITICAL_SECTION_FIN(fi->cs);
    fi->fh = INVALID_HANDLE_VALUE;
  }
  return GRN_SUCCESS;
}

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

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

inline static int
grn_msync(grn_ctx *ctx, void *start, size_t length)
{
  BOOL succeeded;

  succeeded = FlushViewOfFile(start, length);
  if (succeeded) {
    return 0;
  } else {
    SERR("FlushViewOfFile");
    GRN_LOG(ctx, GRN_LOG_ERROR,
            "FlushViewOfFile(<%p>, <%" GRN_FMT_SIZE ">) failed",
            start, length);
    return -1;
  }
}

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

inline static 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;
  grn_open(fi->fd, path, flags);
  if (fi->fd == -1) {
    ERRNO_ERR(path);
    GRN_LOG(ctx, GRN_LOG_ERROR,
            "failed to open file info path: <%s>",
            path);
    return ctx->rc;
  }
  if (fstat(fi->fd, &st) == -1) {
    ERRNO_ERR(path);
    GRN_LOG(ctx, GRN_LOG_ERROR,
            "failed to stat file info path: <%s>",
            path);
    return ctx->rc;
  }
  fi->dev = st.st_dev;
  fi->inode = st.st_ino;
  return GRN_SUCCESS;
}

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

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

inline static 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;
  }
  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, off_t offset, size_t length)
{
  void *res;
  int fd, flags;
  if (fi) {
    struct stat s;
    off_t tail = offset + 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;
  }
  res = mmap(NULL, length, PROT_READ|PROT_WRITE, flags, fd, offset);
  if (MAP_FAILED == res) {
    MERR("mmap(%" GRN_FMT_LLU ",%d,%" GRN_FMT_LLD ")=%s <%" GRN_FMT_LLU ">",
         (unsigned long long int)length, fd, (long long int)offset, strerror(errno),
         (unsigned long long int)mmap_size);
    return NULL;
  }
  mmap_size += length;
  return res;
}

#ifdef USE_FAIL_MALLOC
inline static void *
grn_fail_mmap(grn_ctx *ctx, grn_io *io, fileinfo *fi,
              off_t offset, size_t length,
              const char* file, int line, const char *func)
{
  if (grn_fail_malloc_check(length, file, line, func)) {
    return grn_mmap(ctx, io, fi, offset, length);
  } else {
    MERR("fail_mmap(%" GRN_FMT_SIZE ",%d,%" GRN_FMT_LLU ") "
         "(%s:%d@%s) <%" GRN_FMT_SIZE ">",
         length,
         fi ? fi->fd : 0,
         (long long unsigned int)offset,
         file,
         line,
         func,
         mmap_size);
    return NULL;
  }
}
#endif /* USE_FAIL_MALLOC */

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

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

inline static 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 != count) {
    if (r == -1) {
      SERR("pread");
    } else {
      /* todo : should retry ? */
      ERR(GRN_INPUT_OUTPUT_ERROR, "pread returned %" GRN_FMT_LLD " != %" GRN_FMT_LLU,
          (long long int)r, (unsigned long long int)count);
    }
    return ctx->rc;
  }
  return GRN_SUCCESS;
}

inline static 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 != count) {
    if (r == -1) {
      SERR("pwrite");
    } else {
      /* todo : should retry ? */
      ERR(GRN_INPUT_OUTPUT_ERROR, "pwrite returned %" GRN_FMT_LLD " != %" GRN_FMT_LLU,
          (long long int)r, (unsigned long long int)count);
    }
    return ctx->rc;
  }
  return GRN_SUCCESS;
}

#endif /* WIN32 */
