package devmapper

/*
#cgo LDFLAGS: -L. -ldevmapper
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libdevmapper.h>
#include <linux/loop.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <errno.h>

#ifndef LOOP_CTL_GET_FREE
#define LOOP_CTL_GET_FREE       0x4C82
#endif

// FIXME: this could easily be rewritten in go
char*			attach_loop_device(const char *filename, int *loop_fd_out)
{
  struct loop_info64	loopinfo = {0};
  struct stat		st;
  char			buf[64];
  int			i, loop_fd, fd, start_index;
  char*			loopname;


  *loop_fd_out = -1;

  start_index = 0;
  fd = open("/dev/loop-control", O_RDONLY);
  if (fd >= 0) {
    start_index = ioctl(fd, LOOP_CTL_GET_FREE);
    close(fd);

    if (start_index < 0)
      start_index = 0;
  }

  fd = open(filename, O_RDWR);
  if (fd < 0) {
    perror("open");
    return NULL;
  }

  loop_fd = -1;
  for (i = start_index ; loop_fd < 0 ; i++ ) {
    if (sprintf(buf, "/dev/loop%d", i) < 0) {
	close(fd);
	return NULL;
    }

    if (stat(buf, &st)) {
      if (!S_ISBLK(st.st_mode)) {
	 fprintf(stderr, "[error] Loopback device %s is not a block device.\n", buf);
      } else if (errno == ENOENT) {
	fprintf(stderr, "[error] There are no more loopback device available.\n");
      } else {
	fprintf(stderr, "[error] Unkown error trying to stat the loopback device %s (errno: %d).\n", buf, errno);
      }
      close(fd);
      return NULL;
    }

    loop_fd = open(buf, O_RDWR);
    if (loop_fd < 0 && errno == ENOENT) {
      fprintf(stderr, "[error] The loopback device %s does not exists.\n", buf);
      close(fd);
      return NULL;
    } else if (loop_fd < 0) {
	fprintf(stderr, "[error] Unkown error openning the loopback device %s. (errno: %d)\n", buf, errno);
	continue;
    }

    if (ioctl(loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) {
      int errsv = errno;
      close(loop_fd);
      loop_fd = -1;
      if (errsv != EBUSY) {
        close(fd);
        fprintf(stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv));
        return NULL;
      }
      continue;
    }

    close(fd);

    strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE);
    loopinfo.lo_offset = 0;
    loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR;

    if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) {
      perror("ioctl LOOP_SET_STATUS64");
      if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) {
        perror("ioctl LOOP_CLR_FD");
      }
      close(loop_fd);
      fprintf (stderr, "cannot set up loopback device info");
      return (NULL);
    }

    loopname = strdup(buf);
    if (loopname == NULL) {
      close(loop_fd);
      return (NULL);
    }

    *loop_fd_out = loop_fd;
    return (loopname);
  }

  return (NULL);
}

extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str);

static void	log_cb(int level, const char *file, int line,
		       int dm_errno_or_class, const char *f, ...)
{
  char buffer[256];
  va_list ap;

  va_start(ap, f);
  vsnprintf(buffer, 256, f, ap);
  va_end(ap);

  DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer);
}

static void	log_with_errno_init()
{
  dm_log_with_errno_init(log_cb);
}

*/
import "C"

import (
	"unsafe"
)

type (
	CDmTask C.struct_dm_task
)

var (
	DmTaskDestory       = dmTaskDestroyFct
	DmTaskCreate        = dmTaskCreateFct
	DmTaskRun           = dmTaskRunFct
	DmTaskSetName       = dmTaskSetNameFct
	DmTaskSetMessage    = dmTaskSetMessageFct
	DmTaskSetSector     = dmTaskSetSectorFct
	DmTaskSetCookie     = dmTaskSetCookieFct
	DmTaskSetAddNode    = dmTaskSetAddNodeFct
	DmTaskSetRo         = dmTaskSetRoFct
	DmTaskAddTarget     = dmTaskAddTargetFct
	DmTaskGetInfo       = dmTaskGetInfoFct
	DmGetNextTarget     = dmGetNextTargetFct
	DmGetBlockSize      = dmGetBlockSizeFct
	DmAttachLoopDevice  = dmAttachLoopDeviceFct
	DmUdevWait          = dmUdevWaitFct
	DmLogInitVerbose    = dmLogInitVerboseFct
	DmSetDevDir         = dmSetDevDirFct
	DmGetLibraryVersion = dmGetLibraryVersionFct
	LogWithErrnoInit    = logWithErrnoInitFct
	GetBlockSize        = getBlockSizeFct
)

func free(p *C.char) {
	C.free(unsafe.Pointer(p))
}

func dmTaskDestroyFct(task *CDmTask) {
	C.dm_task_destroy((*C.struct_dm_task)(task))
}

func dmTaskCreateFct(taskType int) *CDmTask {
	return (*CDmTask)(C.dm_task_create(C.int(taskType)))
}

func dmTaskRunFct(task *CDmTask) int {
	return int(C.dm_task_run((*C.struct_dm_task)(task)))
}

func dmTaskSetNameFct(task *CDmTask, name string) int {
	Cname := C.CString(name)
	defer free(Cname)

	return int(C.dm_task_set_name((*C.struct_dm_task)(task),
		Cname))
}

func dmTaskSetMessageFct(task *CDmTask, message string) int {
	Cmessage := C.CString(message)
	defer free(Cmessage)

	return int(C.dm_task_set_message((*C.struct_dm_task)(task),
		Cmessage))
}

func dmTaskSetSectorFct(task *CDmTask, sector uint64) int {
	return int(C.dm_task_set_sector((*C.struct_dm_task)(task),
		C.uint64_t(sector)))
}

func dmTaskSetCookieFct(task *CDmTask, cookie *uint, flags uint16) int {
	cCookie := C.uint32_t(*cookie)
	defer func() {
		*cookie = uint(cCookie)
	}()
	return int(C.dm_task_set_cookie((*C.struct_dm_task)(task), &cCookie,
		C.uint16_t(flags)))
}

func dmTaskSetAddNodeFct(task *CDmTask, addNode AddNodeType) int {
	return int(C.dm_task_set_add_node((*C.struct_dm_task)(task),
		C.dm_add_node_t(addNode)))
}

func dmTaskSetRoFct(task *CDmTask) int {
	return int(C.dm_task_set_ro((*C.struct_dm_task)(task)))
}

func dmTaskAddTargetFct(task *CDmTask,
	start, size uint64, ttype, params string) int {

	Cttype := C.CString(ttype)
	defer free(Cttype)

	Cparams := C.CString(params)
	defer free(Cparams)

	return int(C.dm_task_add_target((*C.struct_dm_task)(task),
		C.uint64_t(start), C.uint64_t(size), Cttype, Cparams))
}

func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, sysErrno) {
	var lo64 C.struct_loop_info64
	_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_GET_STATUS64,
		uintptr(unsafe.Pointer(&lo64)))
	return uint64(lo64.lo_device), uint64(lo64.lo_inode), sysErrno(err)
}

func dmLoopbackSetCapacity(fd uintptr) sysErrno {
	_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_SET_CAPACITY, 0)
	return sysErrno(err)
}

func dmGetBlockSizeFct(fd uintptr) (int64, sysErrno) {
	var size int64
	_, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size)))
	return size, sysErrno(err)
}

func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
	Cinfo := C.struct_dm_info{}
	defer func() {
		info.Exists = int(Cinfo.exists)
		info.Suspended = int(Cinfo.suspended)
		info.LiveTable = int(Cinfo.live_table)
		info.InactiveTable = int(Cinfo.inactive_table)
		info.OpenCount = int32(Cinfo.open_count)
		info.EventNr = uint32(Cinfo.event_nr)
		info.Major = uint32(Cinfo.major)
		info.Minor = uint32(Cinfo.minor)
		info.ReadOnly = int(Cinfo.read_only)
		info.TargetCount = int32(Cinfo.target_count)
	}()
	return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
}

func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
	var (
		Cstart, Clength      C.uint64_t
		CtargetType, Cparams *C.char
	)
	defer func() {
		*start = uint64(Cstart)
		*length = uint64(Clength)
		*target = C.GoString(CtargetType)
		*params = C.GoString(Cparams)
	}()

	nextp := C.dm_get_next_target((*C.struct_dm_task)(task),
		unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams)
	return uintptr(nextp)
}

func dmAttachLoopDeviceFct(filename string, fd *int) string {
	cFilename := C.CString(filename)
	defer free(cFilename)

	var cFd C.int
	defer func() {
		*fd = int(cFd)
	}()

	ret := C.attach_loop_device(cFilename, &cFd)
	defer free(ret)
	return C.GoString(ret)
}

func getBlockSizeFct(fd uintptr, size *uint64) sysErrno {
	_, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size)))
	return sysErrno(err)
}

func dmUdevWaitFct(cookie uint) int {
	return int(C.dm_udev_wait(C.uint32_t(cookie)))
}

func dmLogInitVerboseFct(level int) {
	C.dm_log_init_verbose(C.int(level))
}

func logWithErrnoInitFct() {
	C.log_with_errno_init()
}

func dmSetDevDirFct(dir string) int {
	Cdir := C.CString(dir)
	defer free(Cdir)

	return int(C.dm_set_dev_dir(Cdir))
}

func dmGetLibraryVersionFct(version *string) int {
	buffer := C.CString(string(make([]byte, 128)))
	defer free(buffer)
	defer func() {
		*version = C.GoString(buffer)
	}()
	return int(C.dm_get_library_version(buffer, 128))
}