/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "uv.h"
#include "internal.h"
#include "eio.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <utime.h>
#include <sys/time.h>


#define ARGS1(a)       (a)
#define ARGS2(a,b)     (a), (b)
#define ARGS3(a,b,c)   (a), (b), (c)
#define ARGS4(a,b,c,d) (a), (b), (c), (d)

#define WRAP_EIO(type, eiofunc, func, args) \
  uv_fs_req_init(loop, req, type, path, cb); \
  if (cb) { \
    /* async */ \
    req->eio = eiofunc(args, EIO_PRI_DEFAULT, uv__fs_after, req, &loop->uv_eio_channel); \
    if (!req->eio) { \
      uv__set_sys_error(loop, ENOMEM); \
      return -1; \
    } \
  } else { \
    /* sync */ \
    req->result = func(args); \
    if (req->result) { \
      uv__set_sys_error(loop, errno); \
    }  \
    return req->result; \
  } \
  return 0;


static void uv_fs_req_init(uv_loop_t* loop, uv_fs_t* req, uv_fs_type fs_type,
    const char* path, uv_fs_cb cb) {
  /* Make sure the thread pool is initialized. */
  uv_eio_init(loop);

  uv__req_init(loop, req, UV_FS);
  req->loop = loop;
  req->fs_type = fs_type;
  req->cb = cb;
  req->result = 0;
  req->ptr = NULL;
  req->path = path ? strdup(path) : NULL;
  req->errorno = 0;
  req->eio = NULL;

  /* synchronous requests don't increase the reference count */
  if (!req->cb)
    uv__req_unregister(req->loop, req);
}


void uv_fs_req_cleanup(uv_fs_t* req) {
  if (req->cb)
    uv__req_unregister(req->loop, req);

  free(req->path);
  req->path = NULL;

  switch (req->fs_type) {
    case UV_FS_READDIR:
      assert(req->result > 0 ? (req->ptr != NULL) : (req->ptr == NULL));
      free(req->ptr);
      req->ptr = NULL;
      break;

    case UV_FS_STAT:
    case UV_FS_LSTAT:
      req->ptr = NULL;
      break;

    default:
      break;
  }
}


static int uv__fs_after(eio_req* eio) {
  char* name;
  int namelen;
  int buflen = 0;
  uv_fs_t* req = eio->data;
  int i;

  assert(req->cb);

  req->result = req->eio->result;
  req->errorno = uv_translate_sys_error(req->eio->errorno);

  switch (req->fs_type) {
    case UV_FS_READDIR:
      /*
       * XXX This is pretty bad.
       * We alloc and copy the large null terminated string list from libeio.
       * This is done because libeio is going to free eio->ptr2 after this
       * callback. We must keep it until uv_fs_req_cleanup. If we get rid of
       * libeio this can be avoided.
       */
      buflen = 0;
      name = req->eio->ptr2;

      for (i = 0; i < req->result; i++) {
        namelen = strlen(name);
        buflen += namelen + 1;
        name += namelen;
        assert(*name == '\0');
        name++;
      }

      if (buflen) {
        if ((req->ptr = malloc(buflen)))
          memcpy(req->ptr, req->eio->ptr2, buflen);
        else
          uv__set_sys_error(req->loop, ENOMEM);
      }
      break;

    case UV_FS_STAT:
    case UV_FS_LSTAT:
    case UV_FS_FSTAT:
      req->ptr = req->eio->ptr2;
      break;

    case UV_FS_READLINK:
      if (req->result == -1) {
        req->ptr = NULL;
        break;
      }
      assert(req->result > 0);

      /* Make zero-terminated copy of req->eio->ptr2 */
      if ((req->ptr = name = malloc(req->result + 1))) {
        memcpy(name, req->eio->ptr2, req->result);
        name[req->result] = '\0';
        req->result = 0;
      }
      else {
        req->errorno = ENOMEM;
        req->result = -1;
      }
      break;

    default:
      break;
  }

  req->eio = NULL; /* Freed by libeio */
  req->cb(req);

  return 0;
}


int uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb) {
  char* path = NULL;
  WRAP_EIO(UV_FS_CLOSE, eio_close, close, ARGS1(file));
}


int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags,
    int mode, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_OPEN, path, cb);

  if (cb) {
    /* async */
    req->eio = eio_open(path, flags, mode, EIO_PRI_DEFAULT, uv__fs_after, req, &loop->uv_eio_channel);
    if (!req->eio) {
      uv__set_sys_error(loop, ENOMEM);
      return -1;
    }

  } else {
    /* sync */
    req->result = open(path, flags, mode);
    if (req->result < 0) {
      uv__set_sys_error(loop, errno);
      return -1;
    }

    uv__cloexec(req->result, 1);

    return req->result;
  }

  return 0;
}


int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_file fd, void* buf,
    size_t length, int64_t offset, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_READ, NULL, cb);

  if (cb) {
    /* async */
    req->eio = eio_read(fd, buf, length, offset, EIO_PRI_DEFAULT,
        uv__fs_after, req,  &loop->uv_eio_channel);

    if (!req->eio) {
      uv__set_sys_error(loop, ENOMEM);
      return -1;
    }

  } else {
    /* sync */
    req->result = offset < 0 ?
      read(fd, buf, length) :
      pread(fd, buf, length, offset);

    if (req->result < 0) {
      uv__set_sys_error(loop, errno);
      return -1;
    }

    return req->result;
  }

  return 0;
}


int uv_fs_unlink(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
  WRAP_EIO(UV_FS_UNLINK, eio_unlink, unlink, ARGS1(path))
}


int uv_fs_write(uv_loop_t* loop, uv_fs_t* req, uv_file file, void* buf,
    size_t length, int64_t offset, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_WRITE, NULL, cb);

  if (cb) {
    /* async */
    req->eio = eio_write(file, buf, length, offset, EIO_PRI_DEFAULT,
        uv__fs_after, req,  &loop->uv_eio_channel);
    if (!req->eio) {
      uv__set_sys_error(loop, ENOMEM);
      return -1;
    }

  } else {
    /* sync */
    req->result = offset < 0 ?
        write(file, buf, length) :
        pwrite(file, buf, length, offset);

    if (req->result < 0) {
      uv__set_sys_error(loop, errno);
      return -1;
    }

    return req->result;
  }

  return 0;
}


int uv_fs_mkdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode,
    uv_fs_cb cb) {
  WRAP_EIO(UV_FS_MKDIR, eio_mkdir, mkdir, ARGS2(path, mode))
}


int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
  WRAP_EIO(UV_FS_RMDIR, eio_rmdir, rmdir, ARGS1(path))
}


int uv_fs_readdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags,
    uv_fs_cb cb) {
  int r;
  struct dirent* entry;
  size_t size = 0;
  size_t d_namlen = 0;

  uv_fs_req_init(loop, req, UV_FS_READDIR, path, cb);

  if (cb) {
    /* async */
    req->eio = eio_readdir(path, flags, EIO_PRI_DEFAULT, uv__fs_after, req,  &loop->uv_eio_channel);
    if (!req->eio) {
      uv__set_sys_error(loop, ENOMEM);
      return -1;
    }

  } else {
    /* sync */
    DIR* dir = opendir(path);
    if (!dir) {
      uv__set_sys_error(loop, errno);
      req->result = -1;
      return -1;
    }

    /* req->result stores number of entries */
    req->result = 0;

    while ((entry = readdir(dir))) {
      d_namlen = strlen(entry->d_name);

      /* Skip . and .. */
      if ((d_namlen == 1 && entry->d_name[0] == '.') ||
          (d_namlen == 2 && entry->d_name[0] == '.' &&
           entry->d_name[1] == '.')) {
        continue;
      }

      req->ptr = realloc(req->ptr, size + d_namlen + 1);
      /* TODO check ENOMEM */
      memcpy((char*)req->ptr + size, entry->d_name, d_namlen);
      size += d_namlen;
      ((char*)req->ptr)[size] = '\0';
      size++;
      req->result++;
    }

    r = closedir(dir);
    if (r) {
      uv__set_sys_error(loop, errno);
      req->result = -1;
      return -1;
    }

    return req->result;
  }

  return 0;
}


int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
  char* pathdup;
  int pathlen;

  uv_fs_req_init(loop, req, UV_FS_STAT, path, cb);

  /* TODO do this without duplicating the string. */
  /* TODO security */
  pathdup = strdup(path);
  pathlen = strlen(path);

  if (pathlen > 0 && path[pathlen - 1] == '\\') {
    /* TODO do not modify input string */
    pathdup[pathlen - 1] = '\0';
  }

  if (cb) {
    /* async */
    req->eio = eio_stat(pathdup, EIO_PRI_DEFAULT, uv__fs_after, req,  &loop->uv_eio_channel);

    free(pathdup);

    if (!req->eio) {
      uv__set_sys_error(loop, ENOMEM);
      return -1;
    }

  } else {
    /* sync */
    req->result = stat(pathdup, &req->statbuf);

    free(pathdup);

    if (req->result < 0) {
      uv__set_sys_error(loop, errno);
      return -1;
    }

    req->ptr = &req->statbuf;
    return req->result;
  }

  return 0;
}


int uv_fs_fstat(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_FSTAT, NULL, cb);

  if (cb) {
    /* async */
    req->eio = eio_fstat(file, EIO_PRI_DEFAULT, uv__fs_after, req,  &loop->uv_eio_channel);

    if (!req->eio) {
      uv__set_sys_error(loop, ENOMEM);
      return -1;
    }

  } else {
    /* sync */
    req->result = fstat(file, &req->statbuf);

    if (req->result < 0) {
      uv__set_sys_error(loop, errno);
      return -1;
    }

    req->ptr = &req->statbuf;
    return req->result;
  }

  return 0;
}


int uv_fs_rename(uv_loop_t* loop, uv_fs_t* req, const char* path, const char* new_path,
    uv_fs_cb cb) {
  WRAP_EIO(UV_FS_RENAME, eio_rename, rename, ARGS2(path, new_path))
}


int uv_fs_fsync(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb) {
  char* path = NULL;
  WRAP_EIO(UV_FS_FSYNC, eio_fsync, fsync, ARGS1(file))
}


int uv_fs_fdatasync(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb) {
  char* path = NULL;
#if defined(__FreeBSD__) \
  || (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1060)
  /* freebsd and pre-10.6 darwin don't have fdatasync,
   * do a full fsync instead.
   */
  WRAP_EIO(UV_FS_FDATASYNC, eio_fdatasync, fsync, ARGS1(file))
#else
  WRAP_EIO(UV_FS_FDATASYNC, eio_fdatasync, fdatasync, ARGS1(file))
#endif
}


int uv_fs_ftruncate(uv_loop_t* loop, uv_fs_t* req, uv_file file, int64_t offset,
    uv_fs_cb cb) {
  char* path = NULL;
  WRAP_EIO(UV_FS_FTRUNCATE, eio_ftruncate, ftruncate, ARGS2(file, offset))
}


int uv_fs_sendfile(uv_loop_t* loop, uv_fs_t* req, uv_file out_fd, uv_file in_fd,
    int64_t in_offset, size_t length, uv_fs_cb cb) {
  char* path = NULL;
  WRAP_EIO(UV_FS_SENDFILE, eio_sendfile, eio_sendfile_sync,
      ARGS4(out_fd, in_fd, in_offset, length))
}


int uv_fs_chmod(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode,
    uv_fs_cb cb) {
  WRAP_EIO(UV_FS_CHMOD, eio_chmod, chmod, ARGS2(path, mode))
}


static int _utime(const char* path, double atime, double mtime) {
  struct utimbuf buf;
  buf.actime = atime;
  buf.modtime = mtime;
  return utime(path, &buf);
}


int uv_fs_utime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime,
    double mtime, uv_fs_cb cb) {
  WRAP_EIO(UV_FS_UTIME, eio_utime, _utime, ARGS3(path, atime, mtime))
}


static int _futime(const uv_file fd, double atime, double mtime) {
#if __linux__
  /* utimesat() has nanosecond resolution but we stick to microseconds
   * for the sake of consistency with other platforms.
   */
  struct timespec ts[2];
  ts[0].tv_sec = atime;
  ts[0].tv_nsec = (unsigned long)(atime * 1000000) % 1000000 * 1000;
  ts[1].tv_sec = mtime;
  ts[1].tv_nsec = (unsigned long)(mtime * 1000000) % 1000000 * 1000;
  return uv__utimesat(fd, NULL, ts, 0);
#elif HAVE_FUTIMES
  struct timeval tv[2];
  tv[0].tv_sec = atime;
  tv[0].tv_usec = (unsigned long)(atime * 1000000) % 1000000;
  tv[1].tv_sec = mtime;
  tv[1].tv_usec = (unsigned long)(mtime * 1000000) % 1000000;
  return futimes(fd, tv);
#else /* !HAVE_FUTIMES */
  errno = ENOSYS;
  return -1;
#endif
}


int uv_fs_futime(uv_loop_t* loop, uv_fs_t* req, uv_file file, double atime,
    double mtime, uv_fs_cb cb) {
  const char* path = NULL;
  WRAP_EIO(UV_FS_FUTIME, eio_futime, _futime, ARGS3(file, atime, mtime))
}


int uv_fs_lstat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
  char* pathdup;
  int pathlen;

  uv_fs_req_init(loop, req, UV_FS_LSTAT, path, cb);

  /* TODO do this without duplicating the string. */
  /* TODO security */
  pathdup = strdup(path);
  pathlen = strlen(path);

  if (pathlen > 0 && path[pathlen - 1] == '\\') {
    /* TODO do not modify input string */
    pathdup[pathlen - 1] = '\0';
  }

  if (cb) {
    /* async */
    req->eio = eio_lstat(pathdup, EIO_PRI_DEFAULT, uv__fs_after, req,  &loop->uv_eio_channel);

    free(pathdup);

    if (!req->eio) {
      uv__set_sys_error(loop, ENOMEM);
      return -1;
    }

  } else {
    /* sync */
    req->result = lstat(pathdup, &req->statbuf);

    free(pathdup);

    if (req->result < 0) {
      uv__set_sys_error(loop, errno);
      return -1;
    }

    req->ptr = &req->statbuf;
    return req->result;
  }

  return 0;
}


int uv_fs_link(uv_loop_t* loop, uv_fs_t* req, const char* path,
    const char* new_path, uv_fs_cb cb) {
  WRAP_EIO(UV_FS_LINK, eio_link, link, ARGS2(path, new_path))
}


int uv_fs_symlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
    const char* new_path, int flags, uv_fs_cb cb) {
  WRAP_EIO(UV_FS_SYMLINK, eio_symlink, symlink, ARGS2(path, new_path))
}


int uv_fs_readlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
    uv_fs_cb cb) {
  ssize_t size;
  char* buf;

  uv_fs_req_init(loop, req, UV_FS_READLINK, path, cb);

  if (cb) {
    if ((req->eio = eio_readlink(path, EIO_PRI_DEFAULT, uv__fs_after, req,  &loop->uv_eio_channel))) {
      return 0;
    } else {
      uv__set_sys_error(loop, ENOMEM);
      return -1;
    }
  } else {
    /* pathconf(_PC_PATH_MAX) may return -1 to signify that path
     * lengths have no upper limit or aren't suitable for malloc'ing.
     */
    if ((size = pathconf(path, _PC_PATH_MAX)) == -1) {
#if defined(PATH_MAX)
      size = PATH_MAX;
#else
      size = 4096;
#endif
    }

    if ((buf = malloc(size + 1)) == NULL) {
      uv__set_sys_error(loop, ENOMEM);
      return -1;
    }

    if ((size = readlink(path, buf, size)) == -1) {
      req->errorno = errno;
      req->result = -1;
      free(buf);
    } else {
      /* Cannot conceivably fail since it shrinks the buffer. */
      buf = realloc(buf, size + 1);
      buf[size] = '\0';
      req->result = 0;
      req->ptr = buf;
    }

    return req->result;
  }

  assert(0 && "unreachable");
}


int uv_fs_fchmod(uv_loop_t* loop, uv_fs_t* req, uv_file file, int mode,
    uv_fs_cb cb) {
  char* path = NULL;
  WRAP_EIO(UV_FS_FCHMOD, eio_fchmod, fchmod, ARGS2(file, mode))
}


int uv_fs_chown(uv_loop_t* loop, uv_fs_t* req, const char* path, int uid,
    int gid, uv_fs_cb cb) {
  WRAP_EIO(UV_FS_CHOWN, eio_chown, chown, ARGS3(path, uid, gid))
}


int uv_fs_fchown(uv_loop_t* loop, uv_fs_t* req, uv_file file, int uid, int gid,
    uv_fs_cb cb) {
  char* path = NULL;
  WRAP_EIO(UV_FS_FCHOWN, eio_fchown, fchown, ARGS3(file, uid, gid))
}


static void uv__work(eio_req* eio) {
  uv_work_t* req = eio->data;
  if (req->work_cb) {
    req->work_cb(req);
  }
}


static int uv__after_work(eio_req *eio) {
  uv_work_t* req = eio->data;
  uv__req_unregister(req->loop, req);
  if (req->after_work_cb) {
    req->after_work_cb(req);
  }
  return 0;
}


int uv_queue_work(uv_loop_t* loop, uv_work_t* req, uv_work_cb work_cb,
    uv_after_work_cb after_work_cb) {
  void* data = req->data;

  uv_eio_init(loop);

  uv__req_init(loop, req, UV_WORK);
  req->loop = loop;
  req->data = data;
  req->work_cb = work_cb;
  req->after_work_cb = after_work_cb;

  req->eio = eio_custom(uv__work,
                        EIO_PRI_DEFAULT,
                        uv__after_work,
                        req,
                        &loop->uv_eio_channel);

  if (!req->eio) {
    uv__set_sys_error(loop, ENOMEM);
    return -1;
  }

  return 0;
}
