//go:build linux && cgo && !static_build && libdm_dlsym_deferred_remove && !libdm_no_deferred_remove && !no_devmapper
// +build linux,cgo,!static_build,libdm_dlsym_deferred_remove,!libdm_no_deferred_remove,!no_devmapper

package devicemapper

/*
#cgo LDFLAGS: -ldl
#include <stdlib.h>
#include <dlfcn.h>
#include <libdevmapper.h>

// Yes, I know this looks scary. In order to be able to fill our own internal
// dm_info with deferred_remove we need to have a struct definition that is
// correct (regardless of the version of libdm that was used to compile it). To
// this end, we define struct_backport_dm_info. This code comes from lvm2, and
// I have verified that the structure has only ever had elements *appended* to
// it (since 2001).
//
// It is also important that this structure be _larger_ than the dm_info that
// libdevmapper expected. Otherwise libdm might try to write to memory it
// shouldn't (they don't have a "known size" API).
struct backport_dm_info {
	int exists;
	int suspended;
	int live_table;
	int inactive_table;
	int32_t open_count;
	uint32_t event_nr;
	uint32_t major;
	uint32_t minor;
	int read_only;

	int32_t target_count;

	int deferred_remove;
	int internal_suspend;

	// Padding, purely for our own safety. This is to avoid cases where libdm
	// was updated underneath us and we call into dm_task_get_info() with too
	// small of a buffer.
	char _[512];
};

// We have to wrap this in CGo, because Go really doesn't like function pointers.
int call_dm_task_deferred_remove(void *fn, struct dm_task *task)
{
	int (*_dm_task_deferred_remove)(struct dm_task *task) = fn;
	return _dm_task_deferred_remove(task);
}
*/
import "C"

import (
	"unsafe"

	"github.com/sirupsen/logrus"
)

// dm_task_deferred_remove is not supported by all distributions, due to
// out-dated versions of devicemapper. However, in the case where the
// devicemapper library was updated without rebuilding Docker (which can happen
// in some distributions) then we should attempt to dynamically load the
// relevant object rather than try to link to it.

// dmTaskDeferredRemoveFct is a "bound" version of dm_task_deferred_remove.
// It is nil if dm_task_deferred_remove was not found in the libdevmapper that
// is currently loaded.
var dmTaskDeferredRemovePtr unsafe.Pointer

// LibraryDeferredRemovalSupport tells if the feature is supported by the
// current Docker invocation. This value is fixed during init.
var LibraryDeferredRemovalSupport bool

func init() {
	// Clear any errors.
	var err *C.char
	C.dlerror()

	// The symbol we want to fetch.
	symName := C.CString("dm_task_deferred_remove")
	defer C.free(unsafe.Pointer(symName))

	// See if we can find dm_task_deferred_remove. Since we already are linked
	// to libdevmapper, we can search our own address space (rather than trying
	// to guess what libdevmapper is called). We use NULL here, as RTLD_DEFAULT
	// is not available in CGO (even if you set _GNU_SOURCE for some reason).
	// The semantics are identical on glibc.
	sym := C.dlsym(nil, symName)
	err = C.dlerror()
	if err != nil {
		logrus.Debugf("devmapper: could not load dm_task_deferred_remove: %s", C.GoString(err))
		return
	}

	logrus.Debugf("devmapper: found dm_task_deferred_remove at %x", uintptr(sym))
	dmTaskDeferredRemovePtr = sym
	LibraryDeferredRemovalSupport = true
}

func dmTaskDeferredRemoveFct(task *cdmTask) int {
	sym := dmTaskDeferredRemovePtr
	if sym == nil || !LibraryDeferredRemovalSupport {
		return -1
	}
	return int(C.call_dm_task_deferred_remove(sym, (*C.struct_dm_task)(task)))
}

func dmTaskGetInfoWithDeferredFct(task *cdmTask, info *Info) int {
	if !LibraryDeferredRemovalSupport {
		return -1
	}

	Cinfo := C.struct_backport_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)
		info.DeferredRemove = int(Cinfo.deferred_remove)
	}()
	return int(C.dm_task_get_info((*C.struct_dm_task)(task), (*C.struct_dm_info)(unsafe.Pointer(&Cinfo))))
}
