Browse code

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).

Signed-off-by: Aleksa Sarai <asarai@suse.de>

Aleksa Sarai authored on 2017/11/16 15:09:16
Showing 5 changed files
... ...
@@ -103,6 +103,12 @@ if [ ! "$GOPATH" ]; then
103 103
 	exit 1
104 104
 fi
105 105
 
106
+# Adds $1_$2 to DOCKER_BUILDTAGS unless it already
107
+# contains a word starting from $1_
108
+add_buildtag() {
109
+	[[ " $DOCKER_BUILDTAGS" == *" $1_"* ]] || DOCKER_BUILDTAGS+=" $1_$2"
110
+}
111
+
106 112
 if ${PKG_CONFIG} 'libsystemd >= 209' 2> /dev/null ; then
107 113
 	DOCKER_BUILDTAGS+=" journald"
108 114
 elif ${PKG_CONFIG} 'libsystemd-journal' 2> /dev/null ; then
... ...
@@ -118,12 +124,14 @@ if \
118 118
 fi
119 119
 
120 120
 # test whether "libdevmapper.h" is new enough to support deferred remove
121
-# functionality.
121
+# functionality. We favour libdm_dlsym_deferred_remove over
122
+# libdm_no_deferred_remove in dynamic cases because the binary could be shipped
123
+# with a newer libdevmapper than the one it was built wih.
122 124
 if \
123 125
 	command -v gcc &> /dev/null \
124 126
 	&& ! ( echo -e  '#include <libdevmapper.h>\nint main() { dm_task_deferred_remove(NULL); }'| gcc -xc - -o /dev/null $(pkg-config --libs devmapper) &> /dev/null ) \
125 127
 ; then
126
-	DOCKER_BUILDTAGS+=' libdm_no_deferred_remove'
128
+	add_buildtag libdm dlsym_deferred_remove
127 129
 fi
128 130
 
129 131
 # Use these flags when compiling the tests and final binary
130 132
deleted file mode 100644
... ...
@@ -1,31 +0,0 @@
1
-// +build linux,cgo,!libdm_no_deferred_remove
2
-
3
-package devicemapper // import "github.com/docker/docker/pkg/devicemapper"
4
-
5
-// #include <libdevmapper.h>
6
-import "C"
7
-
8
-// LibraryDeferredRemovalSupport tells if the feature is enabled in the build
9
-const LibraryDeferredRemovalSupport = true
10
-
11
-func dmTaskDeferredRemoveFct(task *cdmTask) int {
12
-	return int(C.dm_task_deferred_remove((*C.struct_dm_task)(task)))
13
-}
14
-
15
-func dmTaskGetInfoWithDeferredFct(task *cdmTask, info *Info) int {
16
-	Cinfo := C.struct_dm_info{}
17
-	defer func() {
18
-		info.Exists = int(Cinfo.exists)
19
-		info.Suspended = int(Cinfo.suspended)
20
-		info.LiveTable = int(Cinfo.live_table)
21
-		info.InactiveTable = int(Cinfo.inactive_table)
22
-		info.OpenCount = int32(Cinfo.open_count)
23
-		info.EventNr = uint32(Cinfo.event_nr)
24
-		info.Major = uint32(Cinfo.major)
25
-		info.Minor = uint32(Cinfo.minor)
26
-		info.ReadOnly = int(Cinfo.read_only)
27
-		info.TargetCount = int32(Cinfo.target_count)
28
-		info.DeferredRemove = int(Cinfo.deferred_remove)
29
-	}()
30
-	return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
31
-}
32 1
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+// +build linux,cgo,!static_build
1
+// +build !libdm_dlsym_deferred_remove,!libdm_no_deferred_remove
2
+
3
+package devicemapper // import "github.com/docker/docker/pkg/devicemapper"
4
+
5
+/*
6
+#include <libdevmapper.h>
7
+*/
8
+import "C"
9
+
10
+// LibraryDeferredRemovalSupport tells if the feature is supported by the
11
+// current Docker invocation.
12
+const LibraryDeferredRemovalSupport = true
13
+
14
+func dmTaskDeferredRemoveFct(task *cdmTask) int {
15
+	return int(C.dm_task_deferred_remove((*C.struct_dm_task)(task)))
16
+}
17
+
18
+func dmTaskGetInfoWithDeferredFct(task *cdmTask, info *Info) int {
19
+	Cinfo := C.struct_dm_info{}
20
+	defer func() {
21
+		info.Exists = int(Cinfo.exists)
22
+		info.Suspended = int(Cinfo.suspended)
23
+		info.LiveTable = int(Cinfo.live_table)
24
+		info.InactiveTable = int(Cinfo.inactive_table)
25
+		info.OpenCount = int32(Cinfo.open_count)
26
+		info.EventNr = uint32(Cinfo.event_nr)
27
+		info.Major = uint32(Cinfo.major)
28
+		info.Minor = uint32(Cinfo.minor)
29
+		info.ReadOnly = int(Cinfo.read_only)
30
+		info.TargetCount = int32(Cinfo.target_count)
31
+		info.DeferredRemove = int(Cinfo.deferred_remove)
32
+	}()
33
+	return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
34
+}
0 35
new file mode 100644
... ...
@@ -0,0 +1,128 @@
0
+// +build linux,cgo,!static_build
1
+// +build libdm_dlsym_deferred_remove,!libdm_no_deferred_remove
2
+
3
+package devicemapper
4
+
5
+/*
6
+#cgo LDFLAGS: -ldl
7
+#include <stdlib.h>
8
+#include <dlfcn.h>
9
+#include <libdevmapper.h>
10
+
11
+// Yes, I know this looks scary. In order to be able to fill our own internal
12
+// dm_info with deferred_remove we need to have a struct definition that is
13
+// correct (regardless of the version of libdm that was used to compile it). To
14
+// this end, we define struct_backport_dm_info. This code comes from lvm2, and
15
+// I have verified that the structure has only ever had elements *appended* to
16
+// it (since 2001).
17
+//
18
+// It is also important that this structure be _larger_ than the dm_info that
19
+// libdevmapper expected. Otherwise libdm might try to write to memory it
20
+// shouldn't (they don't have a "known size" API).
21
+struct backport_dm_info {
22
+	int exists;
23
+	int suspended;
24
+	int live_table;
25
+	int inactive_table;
26
+	int32_t open_count;
27
+	uint32_t event_nr;
28
+	uint32_t major;
29
+	uint32_t minor;
30
+	int read_only;
31
+
32
+	int32_t target_count;
33
+
34
+	int deferred_remove;
35
+	int internal_suspend;
36
+
37
+	// Padding, purely for our own safety. This is to avoid cases where libdm
38
+	// was updated underneath us and we call into dm_task_get_info() with too
39
+	// small of a buffer.
40
+	char _[512];
41
+};
42
+
43
+// We have to wrap this in CGo, because Go really doesn't like function pointers.
44
+int call_dm_task_deferred_remove(void *fn, struct dm_task *task)
45
+{
46
+	int (*_dm_task_deferred_remove)(struct dm_task *task) = fn;
47
+	return _dm_task_deferred_remove(task);
48
+}
49
+*/
50
+import "C"
51
+
52
+import (
53
+	"unsafe"
54
+
55
+	"github.com/sirupsen/logrus"
56
+)
57
+
58
+// dm_task_deferred_remove is not supported by all distributions, due to
59
+// out-dated versions of devicemapper. However, in the case where the
60
+// devicemapper library was updated without rebuilding Docker (which can happen
61
+// in some distributions) then we should attempt to dynamically load the
62
+// relevant object rather than try to link to it.
63
+
64
+// dmTaskDeferredRemoveFct is a "bound" version of dm_task_deferred_remove.
65
+// It is nil if dm_task_deferred_remove was not found in the libdevmapper that
66
+// is currently loaded.
67
+var dmTaskDeferredRemovePtr unsafe.Pointer
68
+
69
+// LibraryDeferredRemovalSupport tells if the feature is supported by the
70
+// current Docker invocation. This value is fixed during init.
71
+var LibraryDeferredRemovalSupport bool
72
+
73
+func init() {
74
+	// Clear any errors.
75
+	var err *C.char
76
+	C.dlerror()
77
+
78
+	// The symbol we want to fetch.
79
+	symName := C.CString("dm_task_deferred_remove")
80
+	defer C.free(unsafe.Pointer(symName))
81
+
82
+	// See if we can find dm_task_deferred_remove. Since we already are linked
83
+	// to libdevmapper, we can search our own address space (rather than trying
84
+	// to guess what libdevmapper is called). We use NULL here, as RTLD_DEFAULT
85
+	// is not available in CGO (even if you set _GNU_SOURCE for some reason).
86
+	// The semantics are identical on glibc.
87
+	sym := C.dlsym(nil, symName)
88
+	err = C.dlerror()
89
+	if err != nil {
90
+		logrus.Debugf("devmapper: could not load dm_task_deferred_remove: %s", C.GoString(err))
91
+		return
92
+	}
93
+
94
+	logrus.Debugf("devmapper: found dm_task_deferred_remove at %x", uintptr(sym))
95
+	dmTaskDeferredRemovePtr = sym
96
+	LibraryDeferredRemovalSupport = true
97
+}
98
+
99
+func dmTaskDeferredRemoveFct(task *cdmTask) int {
100
+	sym := dmTaskDeferredRemovePtr
101
+	if sym == nil || !LibraryDeferredRemovalSupport {
102
+		return -1
103
+	}
104
+	return int(C.call_dm_task_deferred_remove(sym, (*C.struct_dm_task)(task)))
105
+}
106
+
107
+func dmTaskGetInfoWithDeferredFct(task *cdmTask, info *Info) int {
108
+	if !LibraryDeferredRemovalSupport {
109
+		return -1
110
+	}
111
+
112
+	Cinfo := C.struct_backport_dm_info{}
113
+	defer func() {
114
+		info.Exists = int(Cinfo.exists)
115
+		info.Suspended = int(Cinfo.suspended)
116
+		info.LiveTable = int(Cinfo.live_table)
117
+		info.InactiveTable = int(Cinfo.inactive_table)
118
+		info.OpenCount = int32(Cinfo.open_count)
119
+		info.EventNr = uint32(Cinfo.event_nr)
120
+		info.Major = uint32(Cinfo.major)
121
+		info.Minor = uint32(Cinfo.minor)
122
+		info.ReadOnly = int(Cinfo.read_only)
123
+		info.TargetCount = int32(Cinfo.target_count)
124
+		info.DeferredRemove = int(Cinfo.deferred_remove)
125
+	}()
126
+	return int(C.dm_task_get_info((*C.struct_dm_task)(task), (*C.struct_dm_info)(unsafe.Pointer(&Cinfo))))
127
+}
... ...
@@ -1,8 +1,10 @@
1
-// +build linux,cgo,libdm_no_deferred_remove
1
+// +build linux,cgo
2
+// +build !libdm_dlsym_deferred_remove,libdm_no_deferred_remove
2 3
 
3 4
 package devicemapper // import "github.com/docker/docker/pkg/devicemapper"
4 5
 
5
-// LibraryDeferredRemovalSupport tells if the feature is enabled in the build
6
+// LibraryDeferredRemovalSupport tells if the feature is supported by the
7
+// current Docker invocation.
6 8
 const LibraryDeferredRemovalSupport = false
7 9
 
8 10
 func dmTaskDeferredRemoveFct(task *cdmTask) int {