/*******************************************
 * Copyright(c) 2000-2003 Mikio Hirabayashi
 *******************************************/


#include "curia.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>

#undef TRUE
#define TRUE           1
#undef FALSE
#define FALSE          0

#define CR_NAMEMAX     512
#define CR_DPMAX       256
#define CR_DIRMODE     00755
#define CR_FILEMODE    00644
#define CR_PATHBUFSIZ  1024
#define CR_NUMBUFSIZ   32
#define CR_DEFDNUM     5
#define CR_DPNAME      "depot"
#define CR_KEYDNUM     "dnum"
#define CR_KEYLRNUM    "lrnum"
#define CR_LOBDIR      "lob"
#define CR_LOBDLEVEL   2


static char *crstrdup(const char *str);
static int crdpgetnum(DEPOT *depot, const char *kbuf, int ksiz);
static char *crgetlobpath(CURIA *curia, const char *kbuf, int ksiz);
static int crmklobdir(const char *path);
static int crrmlobdir(const char *path);
static int crwrite(int fd, const void *buf, int size);
static int crread(int fd, void *buf, int size);



/*************************************************************************************************
 * public objects
 *************************************************************************************************/


CURIA *cropen(const char *name, int omode, int bnum, int dnum){
  DEPOT *attr, **depots;
  int dpomode;
  char path[CR_PATHBUFSIZ], *tname;
  int i, j, inode, lrnum;
  struct stat sbuf;
  CURIA *curia;
  assert(name);
  if(dnum < 1 || dnum >= CR_DPMAX) dnum = CR_DEFDNUM;
  if(strlen(name) > CR_NAMEMAX){
    dpecode = DP_EMISC;
    return NULL;
  }
  dpomode = DP_OREADER;
  if(omode & CR_OWRITER){
    dpomode = DP_OWRITER;
    if(omode & CR_OCREAT) dpomode |= DP_OCREAT;
    if(omode & CR_OTRUNC) dpomode |= DP_OTRUNC;
  }
  attr = NULL;
  lrnum = 0;
  if((omode & CR_OWRITER) && (omode & CR_OCREAT)){
    if(mkdir(name, CR_DIRMODE) == -1 && errno != EEXIST){
      dpecode = DP_EMKDIR;
      return NULL;
    }
    for(i = 0; i < dnum; i++){
      sprintf(path, "%s/%04d", name, i + 1);
      if(mkdir(path, CR_DIRMODE) == -1 && errno != EEXIST){
        dpecode = DP_EMKDIR;
        return NULL;
      }
    }
    sprintf(path, "%s/%s", name, CR_DPNAME);
    if(!(attr = dpopen(path, dpomode, 1))) return NULL;
    if(!dpput(attr, CR_KEYDNUM, -1, (char *)&dnum, sizeof(int), DP_DOVER)){
      dpclose(attr);
      return NULL;
    }
  }
  if(!attr){
    sprintf(path, "%s/%s", name, CR_DPNAME);
    if(!(attr = dpopen(path, dpomode, 1))) return NULL;
    if(!(omode & CR_OTRUNC)){
      if((dnum = crdpgetnum(attr, CR_KEYDNUM, -1)) < 1 ||
         (lrnum = crdpgetnum(attr, CR_KEYLRNUM, -1)) < 0){
        dpclose(attr);
        dpecode = DP_EBROKEN;
        return NULL;
      }
    }
  }
  if(omode & CR_OTRUNC){
    for(i = 0; i < CR_DPMAX; i++){
      sprintf(path, "%s/%04d/%s", name, i + 1, CR_DPNAME);
      if(unlink(path) == -1 && errno != ENOENT){
        dpclose(attr);
        dpecode = DP_EUNLINK;
        return NULL;
      }
      sprintf(path, "%s/%04d/%s", name, i + 1, CR_LOBDIR);
      if(!crrmlobdir(path)){
        dpclose(attr);
        return NULL;
      }
      if(i >= dnum){
        sprintf(path, "%s/%04d", name, i + 1);
        if(rmdir(path) == -1 && errno != ENOENT){
          dpclose(attr);
          dpecode = DP_ERMDIR;
          return NULL;
        }
      }
    }
    errno = 0;
  }
  if(stat(name, &sbuf) == -1){
    dpclose(attr);
    dpecode = DP_ESTAT;
    return NULL;
  }
  inode = sbuf.st_ino;
  if(!(depots = malloc(dnum * sizeof(DEPOT *)))){
    dpclose(attr);
    dpecode = DP_EALLOC;
    return NULL;
  }
  for(i = 0; i < dnum; i++){
    sprintf(path, "%s/%04d/%s", name, i + 1, CR_DPNAME);
    if(!(depots[i] = dpopen(path, dpomode, bnum))){
      for(j = 0; j < i; j++){
        dpclose(depots[j]);
      }
      free(depots);
      dpclose(attr);
      return NULL;
    }
  }
  curia = malloc(sizeof(CURIA));
  tname = crstrdup(name);
  if(!curia || !tname){
    free(curia);
    free(tname);
    for(i = 0; i < dnum; i++){
      dpclose(depots[i]);
    }
    free(depots);
    dpclose(attr);
    dpecode = DP_EALLOC;
    return NULL;
  }
  curia->name = tname;
  curia->wmode = (omode & CR_OWRITER);
  curia->inode = inode;
  curia->attr = attr;
  curia->depots = depots;
  curia->dnum = dnum;
  curia->inum = 0;
  curia->lrnum = lrnum;
  return curia;
}


int crclose(CURIA *curia){
  int i, err;
  assert(curia);
  err = FALSE;
  for(i = 0; i < curia->dnum; i++){
    if(!dpclose(curia->depots[i])) err = TRUE;
  }
  free(curia->depots);
  if(curia->wmode){
    if(!dpput(curia->attr, CR_KEYLRNUM, -1, (char *)&(curia->lrnum), sizeof(int), DP_DOVER))
      err = TRUE;
  }
  if(!dpclose(curia->attr)) err = TRUE;
  free(curia->name);
  free(curia);
  return err ? FALSE : TRUE;
}


int crput(CURIA *curia, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode){
  int dpdmode;
  int tnum;
  assert(curia && kbuf && vbuf);
  if(!curia->wmode){
    dpecode = DP_EMODE;
    return FALSE;
  }
  switch(dmode){
  case CR_DKEEP: dpdmode = DP_DKEEP; break;
  case CR_DCAT: dpdmode = DP_DCAT; break;
  default: dpdmode = DP_DOVER; break;
  }
  tnum = dpouterhash(kbuf, ksiz) % curia->dnum;
  return dpput(curia->depots[tnum], kbuf, ksiz, vbuf, vsiz, dpdmode);
}


int crout(CURIA *curia, const char *kbuf, int ksiz){
  int tnum;
  assert(curia && kbuf);
  if(!curia->wmode){
    dpecode = DP_EMODE;
    return FALSE;
  }
  tnum = dpouterhash(kbuf, ksiz) % curia->dnum;
  return dpout(curia->depots[tnum], kbuf, ksiz);
}


char *crget(CURIA *curia, const char *kbuf, int ksiz, int start, int max, int *sp){
  int tnum;
  assert(curia && kbuf && start >= 0);
  tnum = dpouterhash(kbuf, ksiz) % curia->dnum;
  return dpget(curia->depots[tnum], kbuf, ksiz, start, max, sp);
}


int crvsiz(CURIA *curia, const char *kbuf, int ksiz){
  int tnum;
  assert(curia && kbuf);
  tnum = dpouterhash(kbuf, ksiz) % curia->dnum;
  return dpvsiz(curia->depots[tnum], kbuf, ksiz);
}


int criterinit(CURIA *curia){
  int i, err;
  assert(curia);
  err = FALSE;
  for(i = 0; i < curia->dnum; i++){
    if(!dpiterinit(curia->depots[i])){
      err = TRUE;
      break;
    }
  }
  curia->inum = 0;
  return err ? FALSE : TRUE;
}


char *criternext(CURIA *curia, int *sp){
  char *vbuf;
  assert(curia);
  vbuf = NULL;
  while(curia->inum < curia->dnum && !(vbuf = dpiternext(curia->depots[curia->inum], sp))){
    if(dpecode != DP_ENOITEM) return NULL;
    (curia->inum)++;
  }
  return vbuf;
}


int crsetalign(CURIA *curia, int align){
  int i, err;
  assert(curia && align >= 0);
  if(!curia->wmode){
    dpecode = DP_EMODE;
    return FALSE;
  }
  err = FALSE;
  for(i = 0; i < curia->dnum; i++){
    if(!dpsetalign(curia->depots[i], align)){
      err = TRUE;
      break;
    }
  }
  return err ? FALSE : TRUE;
}


int crsync(CURIA *curia){
  int i, err;
  assert(curia);
  if(!curia->wmode){
    dpecode = DP_EMODE;
    return FALSE;
  }
  err = FALSE;
  for(i = 0; i < curia->dnum; i++){
    if(!dpsync(curia->depots[i])){
      err = TRUE;
      break;
    }
  }
  return err ? FALSE : TRUE;
}


int croptimize(CURIA *curia, int bnum){
  int i, err;
  assert(curia);
  if(!curia->wmode){
    dpecode = DP_EMODE;
    return FALSE;
  }
  err = FALSE;
  for(i = 0; i < curia->dnum; i++){
    if(!dpoptimize(curia->depots[i], bnum)){
      err = TRUE;
      break;
    }
  }
  curia->inum = 0;
  return err ? FALSE : TRUE;
}


char *crname(CURIA *curia){
  char *name;
  assert(curia);
  if(!(name = crstrdup(curia->name))){
    dpecode = DP_EALLOC;
    return NULL;
  }
  return name;
}


int crfsiz(CURIA *curia){
  int i, sum, rv;
  assert(curia);
  sum = 0;
  for(i = 0; i < curia->dnum; i++){
    rv = dpfsiz(curia->depots[i]);
    if(rv == -1) return -1;
    sum += rv;
  }
  return sum;
}


int crbnum(CURIA *curia){
  int i, sum, rv;
  assert(curia);
  sum = 0;
  for(i = 0; i < curia->dnum; i++){
    rv = dpbnum(curia->depots[i]);
    if(rv == -1) return -1;
    sum += rv;
  }
  return sum;
}


int crbusenum(CURIA *curia){
  int i, sum, rv;
  assert(curia);
  sum = 0;
  for(i = 0; i < curia->dnum; i++){
    rv = dpbusenum(curia->depots[i]);
    if(rv == -1) return -1;
    sum += rv;
  }
  return sum;
}


int crrnum(CURIA *curia){
  int i, sum, rv;
  assert(curia);
  sum = 0;
  for(i = 0; i < curia->dnum; i++){
    rv = dprnum(curia->depots[i]);
    if(rv == -1) return -1;
    sum += rv;
  }
  return sum;
}


int crwritable(CURIA *curia){
  assert(curia);
  return curia->wmode;
}


int crfatalerror(CURIA *curia){
  int i;
  assert(curia);
  if(dpfatalerror(curia->attr)) return TRUE;
  for(i = 0; i < curia->dnum; i++){
    if(dpfatalerror(curia->depots[i])) return TRUE;
  }
  return FALSE;
}


int crinode(CURIA *curia){
  assert(curia);
  return curia->inode;
}


int crremove(const char *name){
  struct stat sbuf;
  CURIA *curia;
  char path[CR_PATHBUFSIZ];
  assert(name);
  if(stat(name, &sbuf) == -1){
    dpecode = DP_ESTAT;
    return FALSE;
  }
  if((curia = cropen(name, CR_OWRITER | CR_OTRUNC, 1, 1)) != NULL) crclose(curia);
  sprintf(path, "%s/0001/%s", name, CR_DPNAME);
  dpremove(path);
  sprintf(path, "%s/0001", name);
  if(rmdir(path) == -1){
    dpecode = DP_ERMDIR;
    return FALSE;
  }
  sprintf(path, "%s/%s", name, CR_DPNAME);
  if(!dpremove(path)) return FALSE;
  if(rmdir(name) == -1){
    dpecode = DP_ERMDIR;
    return FALSE;
  }
  return TRUE;
}


int crputlob(CURIA *curia, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode){
  char *path;
  int mode, fd, err, be;
  struct stat sbuf;
  assert(curia && kbuf && vbuf);
  if(!curia->wmode){
    dpecode = DP_EMODE;
    return FALSE;
  }
  if(ksiz < 0) ksiz = strlen(kbuf);
  if(vsiz < 0) vsiz = strlen(vbuf);
  if(!(path = crgetlobpath(curia, kbuf, ksiz))) return FALSE;
  if(!crmklobdir(path)){
    free(path);
    return FALSE;
  }
  be = stat(path, &sbuf) != -1 && S_ISREG(sbuf.st_mode);
  mode = O_RDWR | O_CREAT;
  if(dmode & CR_DKEEP) mode |= O_EXCL;
  if(dmode & CR_DCAT){
    mode |= O_APPEND;
  } else {
    mode |= O_TRUNC;
  }
  if((fd = open(path, mode | DP_OPENFLAGS, CR_FILEMODE)) == -1){
    free(path);
    dpecode = DP_EOPEN;
    if(dmode == CR_DKEEP) dpecode = DP_EKEEP;
    return FALSE;
  }
  free(path);
  err = FALSE;
  if(crwrite(fd, vbuf, vsiz) == -1){
    err = TRUE;
    dpecode = DP_EWRITE;
  }
  if(close(fd) == -1){
    err = TRUE;
    dpecode = DP_ECLOSE;
  }
  if(!err && !be) (curia->lrnum)++;
  return err ? FALSE : TRUE;
}


int croutlob(CURIA *curia, const char *kbuf, int ksiz){
  char *path;
  int err, be;
  struct stat sbuf;
  assert(curia && kbuf);
  if(!curia->wmode){
    dpecode = DP_EMODE;
    return FALSE;
  }
  if(ksiz < 0) ksiz = strlen(kbuf);
  if(!(path = crgetlobpath(curia, kbuf, ksiz))) return FALSE;
  be = stat(path, &sbuf) != -1 && S_ISREG(sbuf.st_mode);
  err = FALSE;
  if(unlink(path) == -1){
    err = TRUE;
    dpecode = DP_ENOITEM;
  }
  free(path);
  if(!err && be) (curia->lrnum)--;
  return err ? FALSE : TRUE;
}


char *crgetlob(CURIA *curia, const char *kbuf, int ksiz, int start, int max, int *sp){
  char *path, *buf;
  struct stat sbuf;
  int fd, size;
  assert(curia && kbuf && start >= 0);
  if(ksiz < 0) ksiz = strlen(kbuf);
  if(!(path = crgetlobpath(curia, kbuf, ksiz))) return NULL;
  if((fd = open(path, O_RDONLY | DP_OPENFLAGS, CR_FILEMODE)) == -1){
    free(path);
    dpecode = DP_ENOITEM;
    return NULL;
  }
  free(path);
  if(fstat(fd, &sbuf) == -1){
    close(fd);
    dpecode = DP_ESTAT;
    return NULL;
  }
  if(start > sbuf.st_size){
    close(fd);
    dpecode = DP_ENOITEM;
    return NULL;
  }
  if(lseek(fd, start, SEEK_SET) == -1){
    close(fd);
    dpecode = DP_ESEEK;
    return NULL;
  }
  if(max < 0) max = sbuf.st_size;
  if(!(buf = malloc(max + 1))){
    close(fd);
    dpecode = DP_EALLOC;
    return NULL;
  }
  size = crread(fd, buf, max);
  close(fd);
  if(size == -1){
    free(buf);
    dpecode = DP_EREAD;
    return NULL;
  }
  buf[size] = '\0';
  if(sp) *sp = size;
  return buf;
}


int crvsizlob(CURIA *curia, const char *kbuf, int ksiz){
  char *path;
  struct stat sbuf;
  assert(curia && kbuf);
  if(ksiz < 0) ksiz = strlen(kbuf);
  if(!(path = crgetlobpath(curia, kbuf, ksiz))) return -1;
  if(stat(path, &sbuf) == -1){
    free(path);
    dpecode = DP_ENOITEM;
    return -1;
  }
  free(path);
  return sbuf.st_size;
}


int crrnumlob(CURIA *curia){
  assert(curia);
  return curia->lrnum;
}



/*************************************************************************************************
 * private objects
 *************************************************************************************************/


static char *crstrdup(const char *str){
  int len;
  char *buf;
  assert(str);
  len = strlen(str);
  if(!(buf = malloc(len + 1))) return NULL;
  memcpy(buf, str, len + 1);
  return buf;
}


static int crdpgetnum(DEPOT *depot, const char *kbuf, int ksiz){
  char *vbuf;
  int vsiz, rv;
  if(!(vbuf = dpget(depot, kbuf, ksiz, 0, -1, &vsiz)) || vsiz != sizeof(int)){
    free(vbuf);
    return INT_MIN;
  }
  rv = *(int *)vbuf;
  free(vbuf);
  return rv;
}


static char *crgetlobpath(CURIA *curia, const char *kbuf, int ksiz){
  char prefix[CR_PATHBUFSIZ], *wp, *path;
  int i, hash;
  assert(curia && kbuf && ksiz >= 0);
  wp = prefix;
  wp += sprintf(wp, "%s/%04d/%s/",
                curia->name, dpouterhash(kbuf, ksiz) % curia->dnum + 1, CR_LOBDIR);
  hash = dpinnerhash(kbuf, ksiz);
  for(i = 0; i < CR_LOBDLEVEL; i++){
    wp += sprintf(wp, "%02X/", hash % 0x100);
    hash /= 0x100;
  }
  if(!(path = malloc(strlen(prefix) + ksiz * 2 + 1))){
    dpecode = DP_EALLOC;
    return NULL;
  }
  wp = path;
  wp += sprintf(path, "%s", prefix);
  for(i = 0; i < ksiz; i++){
    wp += sprintf(wp, "%02X", ((unsigned char *)kbuf)[i]);
  }
  return path;
}


static int crmklobdir(const char *path){
  char elem[CR_PATHBUFSIZ], *wp;
  const char *dp;
  int err, len;
  wp = elem;
  err = FALSE;
  while(*path != '\0' && (dp = strchr(path, '/')) != NULL){
    len = dp - path;
    if(wp != elem) wp += sprintf(wp, "/");
    memcpy(wp, path, len);
    wp[len] = '\0';
    wp += len;
    if(mkdir(elem, CR_DIRMODE) == -1 && errno != EEXIST) err = TRUE;
    path = dp + 1;
  }
  if(err) dpecode = DP_EMKDIR;
  return err ? FALSE : TRUE;
}


static int crrmlobdir(const char *path){
  char elem[CR_PATHBUFSIZ];
  DIR *DD;
  struct dirent *dp;
  assert(path);
  if(unlink(path) != -1){
    return TRUE;
  } else {
    if(errno == ENOENT) return TRUE;
    if(!(DD = opendir(path))){
      dpecode = DP_EMISC;
      return FALSE;
    }
    while((dp = readdir(DD)) != NULL){
      if(!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
      sprintf(elem, "%s/%s", path, dp->d_name);
      if(!crrmlobdir(elem)){
        closedir(DD);
        return FALSE;
      }
    }
  }
  if(closedir(DD) == -1){
    dpecode = DP_EMISC;
    return FALSE;
  }
  if(rmdir(path) == -1){
    dpecode = DP_ERMDIR;
    return FALSE;
  }
  return TRUE;
}


static int crwrite(int fd, const void *buf, int size){
  char *lbuf;
  int rv, wb;
  assert(fd >= 0 && buf && size >= 0);
  lbuf = (char *)buf;
  rv = 0;
  do {
    wb = write(fd, lbuf, size);
    switch(wb){
    case -1: if(errno != EINTR) return -1;
    case 0: break;
    default:
      lbuf += wb;
      size -= wb;
      rv += wb;
      break;
    }
  } while(size > 0);
  return rv;
}


static int crread(int fd, void *buf, int size){
  char *lbuf;
  int i, bs;
  assert(fd >= 0 && buf && size >= 0);
  lbuf = buf;
  for(i = 0; i < size && (bs = read(fd, lbuf + i, size - i)) != 0; i += bs){
    if(bs == -1 && errno != EINTR) return -1;
  }
  return i;
}



/* END OF FILE */
