// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2014 Red Hat
 *
 * This 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.  See file COPYING.
 *
 */

#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// from include/linux/falloc.h:
#ifndef FALLOC_FL_PUNCH_HOLE
# define FALLOC_FL_PUNCH_HOLE 0x2
#endif

#include "FS.h"

#include "acconfig.h"

#ifdef HAVE_LIBXFS
#include "XFS.h"
#endif

#if defined(DARWIN) || defined(__FreeBSD__)
#include <sys/mount.h>
#else
#include <sys/vfs.h>
#endif
#include "include/compat.h"

// ---------------

FS *FS::create(uint64_t f_type)
{
  switch (f_type) {
#ifdef HAVE_LIBXFS
  case XFS_SUPER_MAGIC:
    return new XFS;
#endif
  default:
    return new FS;
  }
}

FS *FS::create_by_fd(int fd)
{
  struct statfs st;
  ::fstatfs(fd, &st);
  return create(st.f_type);
}

// ---------------

int FS::set_alloc_hint(int fd, uint64_t hint)
{
  return 0;  // no-op
}

#ifdef HAVE_NAME_TO_HANDLE_AT
int FS::get_handle(int fd, std::string *h)
{
  char buf[sizeof(struct file_handle) + MAX_HANDLE_SZ];
  struct file_handle *fh = (struct file_handle *)buf;
  int mount_id;

  fh->handle_bytes = MAX_HANDLE_SZ;
  int r = name_to_handle_at(fd, "", fh, &mount_id, AT_EMPTY_PATH);
  if (r < 0) {
    return -errno;
  }
  *h = std::string(buf, fh->handle_bytes + sizeof(struct file_handle));
  return 0;
}

int FS::open_handle(int mount_fd, const std::string& h, int flags)
{
  if (h.length() < sizeof(struct file_handle)) {
    return -EINVAL;
  }
  struct file_handle *fh = (struct file_handle *)h.data();
  if (fh->handle_bytes > h.length()) {
    return -ERANGE;
  }
  int fd = open_by_handle_at(mount_fd, fh, flags);
  if (fd < 0)
    return -errno;
  return fd;
}

#else // HAVE_NAME_TO_HANDLE_AT

int FS::get_handle(int fd, std::string *h)
{
  return -EOPNOTSUPP;
}

int FS::open_handle(int mount_fd, const std::string& h, int flags)
{
  return -EOPNOTSUPP;
}

#endif // HAVE_NAME_TO_HANDLE_AT

int FS::copy_file_range(int to_fd, uint64_t to_offset,
			int from_fd,
			uint64_t from_offset, uint64_t from_len)
{
  assert(0 == "write me");
}

int FS::zero(int fd, uint64_t offset, uint64_t length)
{
  int r;

  /*

    From the fallocate(2) man page:

       Specifying the FALLOC_FL_PUNCH_HOLE flag (available since Linux 2.6.38)
       in mode deallocates space (i.e., creates a  hole)  in  the  byte  range
       starting  at offset and continuing for len bytes.  Within the specified
       range, partial filesystem  blocks  are  zeroed,  and  whole  filesystem
       blocks  are removed from the file.  After a successful call, subsequent
       reads from this range will return zeroes.

       The FALLOC_FL_PUNCH_HOLE flag must be ORed with FALLOC_FL_KEEP_SIZE  in
       mode;  in  other words, even when punching off the end of the file, the
       file size (as reported by stat(2)) does not change.

       Not all  filesystems  support  FALLOC_FL_PUNCH_HOLE;  if  a  filesystem
       doesn't  support the operation, an error is returned.  The operation is
       supported on at least the following filesystems:

       *  XFS (since Linux 2.6.38)

       *  ext4 (since Linux 3.0)

       *  Btrfs (since Linux 3.7)

       *  tmpfs (since Linux 3.5)

   So: we only do this is PUNCH_HOLE *and* KEEP_SIZE are defined.

  */
#if !defined(DARWIN) && !defined(__FreeBSD__)
# ifdef CEPH_HAVE_FALLOCATE
#  ifdef FALLOC_FL_KEEP_SIZE
  // first try fallocate
  r = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, offset, length);
  if (r < 0) {
    r = -errno;
  }
  if (r != -EOPNOTSUPP) {
    goto out;  // a real error
  }
  // if that failed (-EOPNOTSUPP), fall back to writing zeros.
#  endif
# endif
#endif

  {
    // fall back to writing zeros
    bufferlist bl;
    bl.append_zero(length);
    r = ::lseek64(fd, offset, SEEK_SET);
    if (r < 0) {
      r = -errno;
      goto out;
    }
    r = bl.write_fd(fd);
  }

 out:
  return r;
}
