瀏覽代碼

Merge pull request #35518 from cyphar/libdm-dlsym-deferred_remove

pkg: devmapper: dynamically load dm_task_deferred_remove
Vincent Demeester 7 年之前
父節點
當前提交
5219725890

+ 10 - 2
hack/make.sh

@@ -103,6 +103,12 @@ if [ ! "$GOPATH" ]; then
 	exit 1
 fi
 
+# Adds $1_$2 to DOCKER_BUILDTAGS unless it already
+# contains a word starting from $1_
+add_buildtag() {
+	[[ " $DOCKER_BUILDTAGS" == *" $1_"* ]] || DOCKER_BUILDTAGS+=" $1_$2"
+}
+
 if ${PKG_CONFIG} 'libsystemd >= 209' 2> /dev/null ; then
 	DOCKER_BUILDTAGS+=" journald"
 elif ${PKG_CONFIG} 'libsystemd-journal' 2> /dev/null ; then
@@ -118,12 +124,14 @@ if \
 fi
 
 # test whether "libdevmapper.h" is new enough to support deferred remove
-# functionality.
+# functionality. We favour libdm_dlsym_deferred_remove over
+# libdm_no_deferred_remove in dynamic cases because the binary could be shipped
+# with a newer libdevmapper than the one it was built wih.
 if \
 	command -v gcc &> /dev/null \
 	&& ! ( echo -e  '#include <libdevmapper.h>\nint main() { dm_task_deferred_remove(NULL); }'| gcc -xc - -o /dev/null $(pkg-config --libs devmapper) &> /dev/null ) \
 ; then
-	DOCKER_BUILDTAGS+=' libdm_no_deferred_remove'
+	add_buildtag libdm dlsym_deferred_remove
 fi
 
 # Use these flags when compiling the tests and final binary

+ 7 - 3
pkg/devicemapper/devmapper_wrapper_deferred_remove.go → pkg/devicemapper/devmapper_wrapper_dynamic_deferred_remove.go

@@ -1,11 +1,15 @@
-// +build linux,cgo,!libdm_no_deferred_remove
+// +build linux,cgo,!static_build
+// +build !libdm_dlsym_deferred_remove,!libdm_no_deferred_remove
 
 package devicemapper // import "github.com/docker/docker/pkg/devicemapper"
 
-// #include <libdevmapper.h>
+/*
+#include <libdevmapper.h>
+*/
 import "C"
 
-// LibraryDeferredRemovalSupport tells if the feature is enabled in the build
+// LibraryDeferredRemovalSupport tells if the feature is supported by the
+// current Docker invocation.
 const LibraryDeferredRemovalSupport = true
 
 func dmTaskDeferredRemoveFct(task *cdmTask) int {

+ 128 - 0
pkg/devicemapper/devmapper_wrapper_dynamic_dlsym_deferred_remove.go

@@ -0,0 +1,128 @@
+// +build linux,cgo,!static_build
+// +build libdm_dlsym_deferred_remove,!libdm_no_deferred_remove
+
+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))))
+}

+ 4 - 2
pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go

@@ -1,8 +1,10 @@
-// +build linux,cgo,libdm_no_deferred_remove
+// +build linux,cgo
+// +build !libdm_dlsym_deferred_remove,libdm_no_deferred_remove
 
 package devicemapper // import "github.com/docker/docker/pkg/devicemapper"
 
-// LibraryDeferredRemovalSupport tells if the feature is enabled in the build
+// LibraryDeferredRemovalSupport tells if the feature is supported by the
+// current Docker invocation.
 const LibraryDeferredRemovalSupport = false
 
 func dmTaskDeferredRemoveFct(task *cdmTask) int {