docker/bsc1021227-0001-pkg-devmapper-dynamically-load-dm_task_deferred_remo.patch

244 lines
9.1 KiB
Diff
Raw Normal View History

From e57d7270deb50c31ac1f732d8f28812e5b809062 Mon Sep 17 00:00:00 2001
From: Aleksa Sarai <asarai@suse.de>
Date: Thu, 16 Nov 2017 17:09:16 +1100
Subject: [PATCH] pkg: devmapper: dynamically load dm_task_deferred_remove
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.
This can only be done if Docker was built dynamically, for obvious
reasons.
In order to avoid having issues arise when dlsym(3) was unnecessary,
gate the whole dlsym(3) logic behind a buildflag that we disable by
default (libdm_dlsym_deferred_remove).
SUSE-Bugs: bsc#1021227 bsc#1029320 bsc#1058173
Signed-off-by: Aleksa Sarai <asarai@suse.de>
---
components/engine/hack/make.sh | 12 +-
...> devmapper_wrapper_dynamic_deferred_remove.go} | 10 +-
...mapper_wrapper_dynamic_dlsym_deferred_remove.go | 128 +++++++++++++++++++++
.../devmapper_wrapper_no_deferred_remove.go | 6 +-
4 files changed, 149 insertions(+), 7 deletions(-)
rename components/engine/pkg/devicemapper/{devmapper_wrapper_deferred_remove.go => devmapper_wrapper_dynamic_deferred_remove.go} (78%)
create mode 100644 components/engine/pkg/devicemapper/devmapper_wrapper_dynamic_dlsym_deferred_remove.go
diff --git a/components/engine/hack/make.sh b/components/engine/hack/make.sh
index 58e0d8cd628a..3b78ddef30b0 100755
--- a/components/engine/hack/make.sh
+++ b/components/engine/hack/make.sh
@@ -112,6 +112,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
@@ -127,12 +133,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
diff --git a/components/engine/pkg/devicemapper/devmapper_wrapper_deferred_remove.go b/components/engine/pkg/devicemapper/devmapper_wrapper_dynamic_deferred_remove.go
similarity index 78%
rename from components/engine/pkg/devicemapper/devmapper_wrapper_deferred_remove.go
rename to components/engine/pkg/devicemapper/devmapper_wrapper_dynamic_deferred_remove.go
index 7f793c270868..bf57371ff4cf 100644
--- a/components/engine/pkg/devicemapper/devmapper_wrapper_deferred_remove.go
+++ b/components/engine/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
-// #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 {
diff --git a/components/engine/pkg/devicemapper/devmapper_wrapper_dynamic_dlsym_deferred_remove.go b/components/engine/pkg/devicemapper/devmapper_wrapper_dynamic_dlsym_deferred_remove.go
new file mode 100644
index 000000000000..5dfb369f1ff8
--- /dev/null
+++ b/components/engine/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))))
+}
diff --git a/components/engine/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go b/components/engine/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go
index a880fec8c499..80b034b3ff17 100644
--- a/components/engine/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go
+++ b/components/engine/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
-// 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 {
--
2.16.1