Browse code

Update libcontainer and make it the source of truth on logrus version

To help avoid version mismatches between libcontainer and Docker, this updates libcontainer to be the source of truth for which version of logrus the project is using. This should help avoid potential incompatibilities in the future, too. :+1:

Signed-off-by: Andrew "Tianon" Page <admwiggin@gmail.com>

Tianon Gravi authored on 2015/05/05 02:02:44
Showing 58 changed files
... ...
@@ -32,7 +32,7 @@ func initializer() {
32 32
 	if err != nil {
33 33
 		fatal(err)
34 34
 	}
35
-	if err := factory.StartInitialization(3); err != nil {
35
+	if err := factory.StartInitialization(); err != nil {
36 36
 		fatal(err)
37 37
 	}
38 38
 
... ...
@@ -53,8 +53,6 @@ clone hg code.google.com/p/gosqlite 74691fb6f837
53 53
 
54 54
 clone git github.com/docker/libtrust 230dfd18c232
55 55
 
56
-clone git github.com/Sirupsen/logrus v0.7.2
57
-
58 56
 clone git github.com/go-fsnotify/fsnotify v1.2.0
59 57
 
60 58
 clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673
... ...
@@ -69,8 +67,8 @@ mv tmp-digest src/github.com/docker/distribution/digest
69 69
 mkdir -p src/github.com/docker/distribution/registry
70 70
 mv tmp-api src/github.com/docker/distribution/registry/api
71 71
 
72
-clone git github.com/docker/libcontainer bd8ec36106086f72b66e1be85a81202b93503e44
72
+clone git github.com/docker/libcontainer 6607689b1d06743003a45a722d9fe0bef36b274e
73 73
 # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
74 74
 rm -rf src/github.com/docker/libcontainer/vendor
75
-eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')"
75
+eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')"
76 76
 # we exclude "github.com/codegangsta/cli" here because it's only needed for "nsinit", which Docker doesn't include
... ...
@@ -1,3 +1,7 @@
1
+# 0.7.3
2
+
3
+formatter/\*: allow configuration of timestamp layout
4
+
1 5
 # 0.7.2
2 6
 
3 7
 formatter/text: Add configuration option for time format (#158)
... ...
@@ -108,6 +108,16 @@ func main() {
108 108
     "omg":    true,
109 109
     "number": 100,
110 110
   }).Fatal("The ice breaks!")
111
+
112
+  // A common pattern is to re-use fields between logging statements by re-using
113
+  // the logrus.Entry returned from WithFields()
114
+  contextLogger := log.WithFields(log.Fields{
115
+    "common": "this is a common field",
116
+    "other": "I also should be logged always",
117
+  })
118
+
119
+  contextLogger.Info("I'll be logged with common and other field")
120
+  contextLogger.Info("Me too")
111 121
 }
112 122
 ```
113 123
 
... ...
@@ -189,31 +199,18 @@ func init() {
189 189
 }
190 190
 ```
191 191
 
192
-* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go)
193
-  Send errors to an exception tracking service compatible with the Airbrake API.
194
-  Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes.
195
-
196
-* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go)
197
-  Send errors to the Papertrail hosted logging service via UDP.
198
-
199
-* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
200
-  Send errors to remote syslog server.
201
-  Uses standard library `log/syslog` behind the scenes.
202
-
203
-* [`github.com/Sirupsen/logrus/hooks/bugsnag`](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go)
204
-  Send errors to the Bugsnag exception tracking service.
205
-
206
-* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
207
-  Send errors to a channel in hipchat.
208
-
209
-* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly)
210
-  Send logs to Loggly (https://www.loggly.com/)
211
-
212
-* [`github.com/johntdyer/slackrus`](https://github.com/johntdyer/slackrus)
213
-  Hook for Slack chat.
214 192
 
215
-* [`github.com/wercker/journalhook`](https://github.com/wercker/journalhook).
216
-  Hook for logging to `systemd-journald`.
193
+| Hook  | Description |
194
+| ----- | ----------- |
195
+| [Airbrake](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) | Send errors to an exception tracking service compatible with the Airbrake API. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
196
+| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. |
197
+| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
198
+| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
199
+| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
200
+| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
201
+| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
202
+| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
203
+| [Graylog](https://github.com/gemnasium/logrus-hooks/tree/master/graylog) | Hook for logging to [Graylog](http://graylog2.org/) |
217 204
 
218 205
 #### Level logging
219 206
 
... ...
@@ -1,5 +1,9 @@
1 1
 package logrus
2 2
 
3
+import "time"
4
+
5
+const DefaultTimestampFormat = time.RFC3339
6
+
3 7
 // The Formatter interface is used to implement a custom Formatter. It takes an
4 8
 // `Entry`. It exposes all the fields, including the default ones:
5 9
 //
... ...
@@ -3,19 +3,27 @@ package logstash
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6
+
6 7
 	"github.com/Sirupsen/logrus"
7
-	"time"
8 8
 )
9 9
 
10 10
 // Formatter generates json in logstash format.
11 11
 // Logstash site: http://logstash.net/
12 12
 type LogstashFormatter struct {
13 13
 	Type string // if not empty use for logstash type field.
14
+
15
+	// TimestampFormat sets the format used for timestamps.
16
+	TimestampFormat string
14 17
 }
15 18
 
16 19
 func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) {
17 20
 	entry.Data["@version"] = 1
18
-	entry.Data["@timestamp"] = entry.Time.Format(time.RFC3339)
21
+
22
+	if f.TimestampFormat == "" {
23
+		f.TimestampFormat = logrus.DefaultTimestampFormat
24
+	}
25
+
26
+	entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat)
19 27
 
20 28
 	// set message field
21 29
 	v, ok := entry.Data["message"]
... ...
@@ -3,10 +3,12 @@ package logrus
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6
-	"time"
7 6
 )
8 7
 
9
-type JSONFormatter struct{}
8
+type JSONFormatter struct {
9
+	// TimestampFormat sets the format used for marshaling timestamps.
10
+	TimestampFormat string
11
+}
10 12
 
11 13
 func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
12 14
 	data := make(Fields, len(entry.Data)+3)
... ...
@@ -21,7 +23,12 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
21 21
 		}
22 22
 	}
23 23
 	prefixFieldClashes(data)
24
-	data["time"] = entry.Time.Format(time.RFC3339)
24
+
25
+	if f.TimestampFormat == "" {
26
+		f.TimestampFormat = DefaultTimestampFormat
27
+	}
28
+
29
+	data["time"] = entry.Time.Format(f.TimestampFormat)
25 30
 	data["msg"] = entry.Message
26 31
 	data["level"] = entry.Level.String()
27 32
 
... ...
@@ -18,9 +18,8 @@ const (
18 18
 )
19 19
 
20 20
 var (
21
-	baseTimestamp          time.Time
22
-	isTerminal             bool
23
-	defaultTimestampFormat = time.RFC3339
21
+	baseTimestamp time.Time
22
+	isTerminal    bool
24 23
 )
25 24
 
26 25
 func init() {
... ...
@@ -47,7 +46,7 @@ type TextFormatter struct {
47 47
 	// the time passed since beginning of execution.
48 48
 	FullTimestamp bool
49 49
 
50
-	// Timestamp format to use for display, if a full timestamp is printed
50
+	// TimestampFormat to use for display when a full timestamp is printed
51 51
 	TimestampFormat string
52 52
 
53 53
 	// The fields are sorted by default for a consistent output. For applications
... ...
@@ -73,7 +72,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
73 73
 	isColored := (f.ForceColors || isTerminal) && !f.DisableColors
74 74
 
75 75
 	if f.TimestampFormat == "" {
76
-		f.TimestampFormat = defaultTimestampFormat
76
+		f.TimestampFormat = DefaultTimestampFormat
77 77
 	}
78 78
 	if isColored {
79 79
 		f.printColored(b, entry, keys)
... ...
@@ -14,8 +14,10 @@ import (
14 14
 
15 15
 func IsEnabled() bool {
16 16
 	if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
17
-		buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
18
-		return err == nil && len(buf) > 1 && buf[0] == 'Y'
17
+		if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
18
+			buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
19
+			return err == nil && len(buf) > 1 && buf[0] == 'Y'
20
+		}
19 21
 	}
20 22
 	return false
21 23
 }
... ...
@@ -1,6 +1,8 @@
1 1
 package fs
2 2
 
3 3
 import (
4
+	"fmt"
5
+	"io"
4 6
 	"io/ioutil"
5 7
 	"os"
6 8
 	"path/filepath"
... ...
@@ -19,6 +21,7 @@ var (
19 19
 		"cpuset":     &CpusetGroup{},
20 20
 		"cpuacct":    &CpuacctGroup{},
21 21
 		"blkio":      &BlkioGroup{},
22
+		"hugetlb":    &HugetlbGroup{},
22 23
 		"perf_event": &PerfEventGroup{},
23 24
 		"freezer":    &FreezerGroup{},
24 25
 	}
... ...
@@ -75,10 +78,13 @@ type data struct {
75 75
 }
76 76
 
77 77
 func (m *Manager) Apply(pid int) error {
78
+
78 79
 	if m.Cgroups == nil {
79 80
 		return nil
80 81
 	}
81 82
 
83
+	var c = m.Cgroups
84
+
82 85
 	d, err := getCgroupData(m.Cgroups, pid)
83 86
 	if err != nil {
84 87
 		return err
... ...
@@ -108,6 +114,12 @@ func (m *Manager) Apply(pid int) error {
108 108
 	}
109 109
 	m.Paths = paths
110 110
 
111
+	if paths["cpu"] != "" {
112
+		if err := CheckCpushares(paths["cpu"], c.CpuShares); err != nil {
113
+			return err
114
+		}
115
+	}
116
+
111 117
 	return nil
112 118
 }
113 119
 
... ...
@@ -119,19 +131,6 @@ func (m *Manager) GetPaths() map[string]string {
119 119
 	return m.Paths
120 120
 }
121 121
 
122
-// Symmetrical public function to update device based cgroups.  Also available
123
-// in the systemd implementation.
124
-func ApplyDevices(c *configs.Cgroup, pid int) error {
125
-	d, err := getCgroupData(c, pid)
126
-	if err != nil {
127
-		return err
128
-	}
129
-
130
-	devices := subsystems["devices"]
131
-
132
-	return devices.Apply(d)
133
-}
134
-
135 122
 func (m *Manager) GetStats() (*cgroups.Stats, error) {
136 123
 	stats := cgroups.NewStats()
137 124
 	for name, path := range m.Paths {
... ...
@@ -280,3 +279,27 @@ func removePath(p string, err error) error {
280 280
 	}
281 281
 	return nil
282 282
 }
283
+
284
+func CheckCpushares(path string, c int64) error {
285
+	var cpuShares int64
286
+
287
+	fd, err := os.Open(filepath.Join(path, "cpu.shares"))
288
+	if err != nil {
289
+		return err
290
+	}
291
+	defer fd.Close()
292
+
293
+	_, err = fmt.Fscanf(fd, "%d", &cpuShares)
294
+	if err != nil && err != io.EOF {
295
+		return err
296
+	}
297
+	if c != 0 {
298
+		if c > cpuShares {
299
+			return fmt.Errorf("The maximum allowed cpu-shares is %d", cpuShares)
300
+		} else if c < cpuShares {
301
+			return fmt.Errorf("The minimum allowed cpu-shares is %d", cpuShares)
302
+		}
303
+	}
304
+
305
+	return nil
306
+}
... ...
@@ -35,6 +35,32 @@ func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error {
35 35
 		}
36 36
 	}
37 37
 
38
+	if cgroup.BlkioWeightDevice != "" {
39
+		if err := writeFile(path, "blkio.weight_device", cgroup.BlkioWeightDevice); err != nil {
40
+			return err
41
+		}
42
+	}
43
+	if cgroup.BlkioThrottleReadBpsDevice != "" {
44
+		if err := writeFile(path, "blkio.throttle.read_bps_device", cgroup.BlkioThrottleReadBpsDevice); err != nil {
45
+			return err
46
+		}
47
+	}
48
+	if cgroup.BlkioThrottleWriteBpsDevice != "" {
49
+		if err := writeFile(path, "blkio.throttle.write_bps_device", cgroup.BlkioThrottleWriteBpsDevice); err != nil {
50
+			return err
51
+		}
52
+	}
53
+	if cgroup.BlkioThrottleReadIOpsDevice != "" {
54
+		if err := writeFile(path, "blkio.throttle.read_iops_device", cgroup.BlkioThrottleReadIOpsDevice); err != nil {
55
+			return err
56
+		}
57
+	}
58
+	if cgroup.BlkioThrottleWriteIOpsDevice != "" {
59
+		if err := writeFile(path, "blkio.throttle.write_iops_device", cgroup.BlkioThrottleWriteIOpsDevice); err != nil {
60
+			return err
61
+		}
62
+	}
63
+
38 64
 	return nil
39 65
 }
40 66
 
... ...
@@ -67,6 +67,8 @@ Total 22061056`
67 67
 252:0 Async 164
68 68
 252:0 Total 164
69 69
 Total 328`
70
+	throttleBefore = `8:0 1024`
71
+	throttleAfter  = `8:0 2048`
70 72
 )
71 73
 
72 74
 func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) {
... ...
@@ -102,6 +104,35 @@ func TestBlkioSetWeight(t *testing.T) {
102 102
 	}
103 103
 }
104 104
 
105
+func TestBlkioSetWeightDevice(t *testing.T) {
106
+	helper := NewCgroupTestUtil("blkio", t)
107
+	defer helper.cleanup()
108
+
109
+	const (
110
+		weightDeviceBefore = "8:0 400"
111
+		weightDeviceAfter  = "8:0 500"
112
+	)
113
+
114
+	helper.writeFileContents(map[string]string{
115
+		"blkio.weight_device": weightDeviceBefore,
116
+	})
117
+
118
+	helper.CgroupData.c.BlkioWeightDevice = weightDeviceAfter
119
+	blkio := &BlkioGroup{}
120
+	if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
121
+		t.Fatal(err)
122
+	}
123
+
124
+	value, err := getCgroupParamString(helper.CgroupPath, "blkio.weight_device")
125
+	if err != nil {
126
+		t.Fatalf("Failed to parse blkio.weight_device - %s", err)
127
+	}
128
+
129
+	if value != weightDeviceAfter {
130
+		t.Fatal("Got the wrong value, set blkio.weight_device failed.")
131
+	}
132
+}
133
+
105 134
 func TestBlkioStats(t *testing.T) {
106 135
 	helper := NewCgroupTestUtil("blkio", t)
107 136
 	defer helper.cleanup()
... ...
@@ -442,3 +473,96 @@ func TestNonCFQBlkioStats(t *testing.T) {
442 442
 
443 443
 	expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
444 444
 }
445
+
446
+func TestBlkioSetThrottleReadBpsDevice(t *testing.T) {
447
+	helper := NewCgroupTestUtil("blkio", t)
448
+	defer helper.cleanup()
449
+
450
+	helper.writeFileContents(map[string]string{
451
+		"blkio.throttle.read_bps_device": throttleBefore,
452
+	})
453
+
454
+	helper.CgroupData.c.BlkioThrottleReadBpsDevice = throttleAfter
455
+	blkio := &BlkioGroup{}
456
+	if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
457
+		t.Fatal(err)
458
+	}
459
+
460
+	value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_bps_device")
461
+	if err != nil {
462
+		t.Fatalf("Failed to parse blkio.throttle.read_bps_device - %s", err)
463
+	}
464
+
465
+	if value != throttleAfter {
466
+		t.Fatal("Got the wrong value, set blkio.throttle.read_bps_device failed.")
467
+	}
468
+}
469
+func TestBlkioSetThrottleWriteBpsDevice(t *testing.T) {
470
+	helper := NewCgroupTestUtil("blkio", t)
471
+	defer helper.cleanup()
472
+
473
+	helper.writeFileContents(map[string]string{
474
+		"blkio.throttle.write_bps_device": throttleBefore,
475
+	})
476
+
477
+	helper.CgroupData.c.BlkioThrottleWriteBpsDevice = throttleAfter
478
+	blkio := &BlkioGroup{}
479
+	if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
480
+		t.Fatal(err)
481
+	}
482
+
483
+	value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_bps_device")
484
+	if err != nil {
485
+		t.Fatalf("Failed to parse blkio.throttle.write_bps_device - %s", err)
486
+	}
487
+
488
+	if value != throttleAfter {
489
+		t.Fatal("Got the wrong value, set blkio.throttle.write_bps_device failed.")
490
+	}
491
+}
492
+func TestBlkioSetThrottleReadIOpsDevice(t *testing.T) {
493
+	helper := NewCgroupTestUtil("blkio", t)
494
+	defer helper.cleanup()
495
+
496
+	helper.writeFileContents(map[string]string{
497
+		"blkio.throttle.read_iops_device": throttleBefore,
498
+	})
499
+
500
+	helper.CgroupData.c.BlkioThrottleReadIOpsDevice = throttleAfter
501
+	blkio := &BlkioGroup{}
502
+	if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
503
+		t.Fatal(err)
504
+	}
505
+
506
+	value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_iops_device")
507
+	if err != nil {
508
+		t.Fatalf("Failed to parse blkio.throttle.read_iops_device - %s", err)
509
+	}
510
+
511
+	if value != throttleAfter {
512
+		t.Fatal("Got the wrong value, set blkio.throttle.read_iops_device failed.")
513
+	}
514
+}
515
+func TestBlkioSetThrottleWriteIOpsDevice(t *testing.T) {
516
+	helper := NewCgroupTestUtil("blkio", t)
517
+	defer helper.cleanup()
518
+
519
+	helper.writeFileContents(map[string]string{
520
+		"blkio.throttle.write_iops_device": throttleBefore,
521
+	})
522
+
523
+	helper.CgroupData.c.BlkioThrottleWriteIOpsDevice = throttleAfter
524
+	blkio := &BlkioGroup{}
525
+	if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
526
+		t.Fatal(err)
527
+	}
528
+
529
+	value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_iops_device")
530
+	if err != nil {
531
+		t.Fatalf("Failed to parse blkio.throttle.write_iops_device - %s", err)
532
+	}
533
+
534
+	if value != throttleAfter {
535
+		t.Fatal("Got the wrong value, set blkio.throttle.write_iops_device failed.")
536
+	}
537
+}
... ...
@@ -32,6 +32,17 @@ func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error {
32 32
 				return err
33 33
 			}
34 34
 		}
35
+		return nil
36
+	}
37
+
38
+	if err := writeFile(path, "devices.allow", "a"); err != nil {
39
+		return err
40
+	}
41
+
42
+	for _, dev := range cgroup.DeniedDevices {
43
+		if err := writeFile(path, "devices.deny", dev.CgroupString()); err != nil {
44
+			return err
45
+		}
35 46
 	}
36 47
 
37 48
 	return nil
... ...
@@ -17,7 +17,18 @@ var (
17 17
 			FileMode:    0666,
18 18
 		},
19 19
 	}
20
-	allowedList = "c 1:5 rwm"
20
+	allowedList   = "c 1:5 rwm"
21
+	deniedDevices = []*configs.Device{
22
+		{
23
+			Path:        "/dev/null",
24
+			Type:        'c',
25
+			Major:       1,
26
+			Minor:       3,
27
+			Permissions: "rwm",
28
+			FileMode:    0666,
29
+		},
30
+	}
31
+	deniedList = "c 1:3 rwm"
21 32
 )
22 33
 
23 34
 func TestDevicesSetAllow(t *testing.T) {
... ...
@@ -44,3 +55,28 @@ func TestDevicesSetAllow(t *testing.T) {
44 44
 		t.Fatal("Got the wrong value, set devices.allow failed.")
45 45
 	}
46 46
 }
47
+
48
+func TestDevicesSetDeny(t *testing.T) {
49
+	helper := NewCgroupTestUtil("devices", t)
50
+	defer helper.cleanup()
51
+
52
+	helper.writeFileContents(map[string]string{
53
+		"devices.allow": "a",
54
+	})
55
+
56
+	helper.CgroupData.c.AllowAllDevices = true
57
+	helper.CgroupData.c.DeniedDevices = deniedDevices
58
+	devices := &DevicesGroup{}
59
+	if err := devices.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
60
+		t.Fatal(err)
61
+	}
62
+
63
+	value, err := getCgroupParamString(helper.CgroupPath, "devices.deny")
64
+	if err != nil {
65
+		t.Fatalf("Failed to parse devices.deny - %s", err)
66
+	}
67
+
68
+	if value != deniedList {
69
+		t.Fatal("Got the wrong value, set devices.deny failed.")
70
+	}
71
+}
47 72
new file mode 100644
... ...
@@ -0,0 +1,29 @@
0
+package fs
1
+
2
+import (
3
+	"github.com/docker/libcontainer/cgroups"
4
+	"github.com/docker/libcontainer/configs"
5
+)
6
+
7
+type HugetlbGroup struct {
8
+}
9
+
10
+func (s *HugetlbGroup) Apply(d *data) error {
11
+	// we just want to join this group even though we don't set anything
12
+	if _, err := d.join("hugetlb"); err != nil && !cgroups.IsNotFound(err) {
13
+		return err
14
+	}
15
+	return nil
16
+}
17
+
18
+func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error {
19
+	return nil
20
+}
21
+
22
+func (s *HugetlbGroup) Remove(d *data) error {
23
+	return removePath(d.path("hugetlb"))
24
+}
25
+
26
+func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error {
27
+	return nil
28
+}
... ...
@@ -95,6 +95,7 @@ func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
95 95
 		return fmt.Errorf("failed to parse memory.usage_in_bytes - %v", err)
96 96
 	}
97 97
 	stats.MemoryStats.Usage = value
98
+	stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]
98 99
 	value, err = getCgroupParamUint(path, "memory.max_usage_in_bytes")
99 100
 	if err != nil {
100 101
 		return fmt.Errorf("failed to parse memory.max_usage_in_bytes - %v", err)
... ...
@@ -128,7 +128,7 @@ func TestMemoryStats(t *testing.T) {
128 128
 	if err != nil {
129 129
 		t.Fatal(err)
130 130
 	}
131
-	expectedStats := cgroups.MemoryStats{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Stats: map[string]uint64{"cache": 512, "rss": 1024}}
131
+	expectedStats := cgroups.MemoryStats{Usage: 2048, Cache: 512, MaxUsage: 4096, Failcnt: 100, Stats: map[string]uint64{"cache": 512, "rss": 1024}}
132 132
 	expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats)
133 133
 }
134 134
 
... ...
@@ -2,9 +2,9 @@ package fs
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"log"
6 5
 	"testing"
7 6
 
7
+	log "github.com/Sirupsen/logrus"
8 8
 	"github.com/docker/libcontainer/cgroups"
9 9
 )
10 10
 
... ...
@@ -33,6 +33,8 @@ type CpuStats struct {
33 33
 type MemoryStats struct {
34 34
 	// current res_counter usage for memory
35 35
 	Usage uint64 `json:"usage,omitempty"`
36
+	// memory used for cache
37
+	Cache uint64 `json:"cache,omitempty"`
36 38
 	// maximum usage ever recorded.
37 39
 	MaxUsage uint64 `json:"max_usage,omitempty"`
38 40
 	// TODO(vishh): Export these as stronger types.
... ...
@@ -46,10 +46,6 @@ func (m *Manager) Freeze(state configs.FreezerState) error {
46 46
 	return fmt.Errorf("Systemd not supported")
47 47
 }
48 48
 
49
-func ApplyDevices(c *configs.Cgroup, pid int) error {
50
-	return fmt.Errorf("Systemd not supported")
51
-}
52
-
53 49
 func Freeze(c *configs.Cgroup, state configs.FreezerState) error {
54 50
 	return fmt.Errorf("Systemd not supported")
55 51
 }
... ...
@@ -38,6 +38,7 @@ var subsystems = map[string]subsystem{
38 38
 	"cpuset":     &fs.CpusetGroup{},
39 39
 	"cpuacct":    &fs.CpuacctGroup{},
40 40
 	"blkio":      &fs.BlkioGroup{},
41
+	"hugetlb":    &fs.HugetlbGroup{},
41 42
 	"perf_event": &fs.PerfEventGroup{},
42 43
 	"freezer":    &fs.FreezerGroup{},
43 44
 }
... ...
@@ -216,6 +217,13 @@ func (m *Manager) Apply(pid int) error {
216 216
 		return err
217 217
 	}
218 218
 
219
+	// FIXME: Systemd does have `BlockIODeviceWeight` property, but we got problem
220
+	// using that (at least on systemd 208, see https://github.com/docker/libcontainer/pull/354),
221
+	// so use fs work around for now.
222
+	if err := joinBlkio(c, pid); err != nil {
223
+		return err
224
+	}
225
+
219 226
 	paths := make(map[string]string)
220 227
 	for sysname := range subsystems {
221 228
 		subsystemPath, err := getSubsystemPath(m.Cgroups, sysname)
... ...
@@ -228,9 +236,14 @@ func (m *Manager) Apply(pid int) error {
228 228
 		}
229 229
 		paths[sysname] = subsystemPath
230 230
 	}
231
-
232 231
 	m.Paths = paths
233 232
 
233
+	if paths["cpu"] != "" {
234
+		if err := fs.CheckCpushares(paths["cpu"], c.CpuShares); err != nil {
235
+			return err
236
+		}
237
+	}
238
+
234 239
 	return nil
235 240
 }
236 241
 
... ...
@@ -350,7 +363,17 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) {
350 350
 }
351 351
 
352 352
 func (m *Manager) Set(container *configs.Config) error {
353
-	panic("not implemented")
353
+	for name, path := range m.Paths {
354
+		sys, ok := subsystems[name]
355
+		if !ok || !cgroups.PathExists(path) {
356
+			continue
357
+		}
358
+		if err := sys.Set(path, container.Cgroups); err != nil {
359
+			return err
360
+		}
361
+	}
362
+
363
+	return nil
354 364
 }
355 365
 
356 366
 func getUnitName(c *configs.Cgroup) string {
... ...
@@ -362,7 +385,7 @@ func getUnitName(c *configs.Cgroup) string {
362 362
 // * Support for wildcards to allow /dev/pts support
363 363
 //
364 364
 // The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is
365
-// in wide use. When both these are availalable we will be able to switch, but need to keep the old
365
+// in wide use. When both these are available we will be able to switch, but need to keep the old
366 366
 // implementation for backwards compat.
367 367
 //
368 368
 // Note: we can't use systemd to set up the initial limits, and then change the cgroup
... ...
@@ -375,17 +398,7 @@ func joinDevices(c *configs.Cgroup, pid int) error {
375 375
 	}
376 376
 
377 377
 	devices := subsystems["devices"]
378
-	if err := devices.Set(path, c); err != nil {
379
-		return err
380
-	}
381
-
382
-	return nil
383
-}
384
-
385
-// Symmetrical public function to update device based cgroups.  Also available
386
-// in the fs implementation.
387
-func ApplyDevices(c *configs.Cgroup, pid int) error {
388
-	return joinDevices(c, pid)
378
+	return devices.Set(path, c)
389 379
 }
390 380
 
391 381
 func joinMemory(c *configs.Cgroup, pid int) error {
... ...
@@ -417,3 +430,40 @@ func joinCpuset(c *configs.Cgroup, pid int) error {
417 417
 
418 418
 	return s.ApplyDir(path, c, pid)
419 419
 }
420
+
421
+// `BlockIODeviceWeight` property of systemd does not work properly, and systemd
422
+// expects device path instead of major minor numbers, which is also confusing
423
+// for users. So we use fs work around for now.
424
+func joinBlkio(c *configs.Cgroup, pid int) error {
425
+	path, err := getSubsystemPath(c, "blkio")
426
+	if err != nil {
427
+		return err
428
+	}
429
+	if c.BlkioWeightDevice != "" {
430
+		if err := writeFile(path, "blkio.weight_device", c.BlkioWeightDevice); err != nil {
431
+			return err
432
+		}
433
+	}
434
+	if c.BlkioThrottleReadBpsDevice != "" {
435
+		if err := writeFile(path, "blkio.throttle.read_bps_device", c.BlkioThrottleReadBpsDevice); err != nil {
436
+			return err
437
+		}
438
+	}
439
+	if c.BlkioThrottleWriteBpsDevice != "" {
440
+		if err := writeFile(path, "blkio.throttle.write_bps_device", c.BlkioThrottleWriteBpsDevice); err != nil {
441
+			return err
442
+		}
443
+	}
444
+	if c.BlkioThrottleReadIOpsDevice != "" {
445
+		if err := writeFile(path, "blkio.throttle.read_iops_device", c.BlkioThrottleReadIOpsDevice); err != nil {
446
+			return err
447
+		}
448
+	}
449
+	if c.BlkioThrottleWriteIOpsDevice != "" {
450
+		if err := writeFile(path, "blkio.throttle.write_iops_device", c.BlkioThrottleWriteIOpsDevice); err != nil {
451
+			return err
452
+		}
453
+	}
454
+
455
+	return nil
456
+}
... ...
@@ -19,6 +19,8 @@ type Cgroup struct {
19 19
 
20 20
 	AllowedDevices []*Device `json:"allowed_devices"`
21 21
 
22
+	DeniedDevices []*Device `json:"denied_devices"`
23
+
22 24
 	// Memory limit (in bytes)
23 25
 	Memory int64 `json:"memory"`
24 26
 
... ...
@@ -43,9 +45,24 @@ type Cgroup struct {
43 43
 	// MEM to use
44 44
 	CpusetMems string `json:"cpuset_mems"`
45 45
 
46
+	// IO read rate limit per cgroup per device, bytes per second.
47
+	BlkioThrottleReadBpsDevice string `json:"blkio_throttle_read_bps_device"`
48
+
49
+	// IO write rate limit per cgroup per divice, bytes per second.
50
+	BlkioThrottleWriteBpsDevice string `json:"blkio_throttle_write_bps_device"`
51
+
52
+	// IO read rate limit per cgroup per device, IO per second.
53
+	BlkioThrottleReadIOpsDevice string `json:"blkio_throttle_read_iops_device"`
54
+
55
+	// IO write rate limit per cgroup per device, IO per second.
56
+	BlkioThrottleWriteIOpsDevice string `json:"blkio_throttle_write_iops_device"`
57
+
46 58
 	// Specifies per cgroup weight, range is from 10 to 1000.
47 59
 	BlkioWeight int64 `json:"blkio_weight"`
48 60
 
61
+	// Weight per cgroup per device, can override BlkioWeight.
62
+	BlkioWeightDevice string `json:"blkio_weight_device"`
63
+
49 64
 	// set the freeze value for the process
50 65
 	Freezer FreezerState `json:"freezer"`
51 66
 
... ...
@@ -37,6 +37,9 @@ type Config struct {
37 37
 	// bind mounts are writtable.
38 38
 	Readonlyfs bool `json:"readonlyfs"`
39 39
 
40
+	// Privatefs will mount the container's rootfs as private where mount points from the parent will not propogate
41
+	Privatefs bool `json:"privatefs"`
42
+
40 43
 	// Mounts specify additional source and destination paths that will be mounted inside the container's
41 44
 	// rootfs and mount namespace if specified
42 45
 	Mounts []*Mount `json:"mounts"`
... ...
@@ -96,6 +99,10 @@ type Config struct {
96 96
 	// ReadonlyPaths specifies paths within the container's rootfs to remount as read-only
97 97
 	// so that these files prevent any writes.
98 98
 	ReadonlyPaths []string `json:"readonly_paths"`
99
+
100
+	// SystemProperties is a map of properties and their values. It is the equivalent of using
101
+	// sysctl -w my.property.name value in Linux.
102
+	SystemProperties map[string]string `json:"system_properties"`
99 103
 }
100 104
 
101 105
 // Gets the root uid for the process on host which could be non-zero
... ...
@@ -18,4 +18,17 @@ type Mount struct {
18 18
 
19 19
 	// Relabel source if set, "z" indicates shared, "Z" indicates unshared.
20 20
 	Relabel string `json:"relabel"`
21
+
22
+	// Optional Command to be run before Source is mounted.
23
+	PremountCmds []Command `json:"premount_cmds"`
24
+
25
+	// Optional Command to be run after Source is mounted.
26
+	PostmountCmds []Command `json:"postmount_cmds"`
27
+}
28
+
29
+type Command struct {
30
+	Path string   `json:"path"`
31
+	Args []string `json:"args"`
32
+	Env  []string `json:"env"`
33
+	Dir  string   `json:"dir"`
21 34
 }
... ...
@@ -1,9 +1,6 @@
1 1
 package configs
2 2
 
3
-import (
4
-	"fmt"
5
-	"syscall"
6
-)
3
+import "fmt"
7 4
 
8 5
 type NamespaceType string
9 6
 
... ...
@@ -34,10 +31,6 @@ type Namespace struct {
34 34
 	Path string        `json:"path"`
35 35
 }
36 36
 
37
-func (n *Namespace) Syscall() int {
38
-	return namespaceInfo[n.Type]
39
-}
40
-
41 37
 func (n *Namespace) GetPath(pid int) string {
42 38
 	if n.Path != "" {
43 39
 		return n.Path
... ...
@@ -96,25 +89,3 @@ func (n *Namespaces) index(t NamespaceType) int {
96 96
 func (n *Namespaces) Contains(t NamespaceType) bool {
97 97
 	return n.index(t) != -1
98 98
 }
99
-
100
-var namespaceInfo = map[NamespaceType]int{
101
-	NEWNET:  syscall.CLONE_NEWNET,
102
-	NEWNS:   syscall.CLONE_NEWNS,
103
-	NEWUSER: syscall.CLONE_NEWUSER,
104
-	NEWIPC:  syscall.CLONE_NEWIPC,
105
-	NEWUTS:  syscall.CLONE_NEWUTS,
106
-	NEWPID:  syscall.CLONE_NEWPID,
107
-}
108
-
109
-// CloneFlags parses the container's Namespaces options to set the correct
110
-// flags on clone, unshare. This functions returns flags only for new namespaces.
111
-func (n *Namespaces) CloneFlags() uintptr {
112
-	var flag int
113
-	for _, v := range *n {
114
-		if v.Path != "" {
115
-			continue
116
-		}
117
-		flag |= namespaceInfo[v.Type]
118
-	}
119
-	return uintptr(flag)
120
-}
121 99
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+// +build linux
1
+
2
+package configs
3
+
4
+import "syscall"
5
+
6
+func (n *Namespace) Syscall() int {
7
+	return namespaceInfo[n.Type]
8
+}
9
+
10
+var namespaceInfo = map[NamespaceType]int{
11
+	NEWNET:  syscall.CLONE_NEWNET,
12
+	NEWNS:   syscall.CLONE_NEWNS,
13
+	NEWUSER: syscall.CLONE_NEWUSER,
14
+	NEWIPC:  syscall.CLONE_NEWIPC,
15
+	NEWUTS:  syscall.CLONE_NEWUTS,
16
+	NEWPID:  syscall.CLONE_NEWPID,
17
+}
18
+
19
+// CloneFlags parses the container's Namespaces options to set the correct
20
+// flags on clone, unshare. This functions returns flags only for new namespaces.
21
+func (n *Namespaces) CloneFlags() uintptr {
22
+	var flag int
23
+	for _, v := range *n {
24
+		if v.Path != "" {
25
+			continue
26
+		}
27
+		flag |= namespaceInfo[v.Type]
28
+	}
29
+	return uintptr(flag)
30
+}
0 31
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+// +build !linux
1
+
2
+package configs
3
+
4
+func (n *Namespace) Syscall() int {
5
+	panic("No namespace syscall support")
6
+	return 0
7
+}
8
+
9
+// CloneFlags parses the container's Namespaces options to set the correct
10
+// flags on clone, unshare. This functions returns flags only for new namespaces.
11
+func (n *Namespaces) CloneFlags() uintptr {
12
+	panic("No namespace syscall support")
13
+	return uintptr(0)
14
+}
... ...
@@ -2,7 +2,7 @@ package configs
2 2
 
3 3
 // Network defines configuration for a container's networking stack
4 4
 //
5
-// The network configuration can be omited from a container causing the
5
+// The network configuration can be omitted from a container causing the
6 6
 // container to be setup with the host's networking stack
7 7
 type Network struct {
8 8
 	// Type sets the networks type, commonly veth and loopback
... ...
@@ -53,7 +53,7 @@ type Network struct {
53 53
 // Routes can be specified to create entries in the route table as the container is started
54 54
 //
55 55
 // All of destination, source, and gateway should be either IPv4 or IPv6.
56
-// One of the three options must be present, and ommitted entries will use their
56
+// One of the three options must be present, and omitted entries will use their
57 57
 // IP family default for the route table.  For IPv4 for example, setting the
58 58
 // gateway to 1.2.3.4 and the interface to eth0 will set up a standard
59 59
 // destination of 0.0.0.0(or *) when viewed in the route table.
... ...
@@ -38,7 +38,7 @@ func newConsole(uid, gid int) (Console, error) {
38 38
 	}, nil
39 39
 }
40 40
 
41
-// newConsoleFromPath is an internal fucntion returning an initialzied console for use inside
41
+// newConsoleFromPath is an internal function returning an initialized console for use inside
42 42
 // a container's MNT namespace.
43 43
 func newConsoleFromPath(slavePath string) *linuxConsole {
44 44
 	return &linuxConsole{
... ...
@@ -67,7 +67,7 @@ type Container interface {
67 67
 	// State returns the current container's state information.
68 68
 	//
69 69
 	// errors:
70
-	// Systemerror - System erroor.
70
+	// Systemerror - System error.
71 71
 	State() (*State, error)
72 72
 
73 73
 	// Returns the current config of the container.
... ...
@@ -16,6 +16,8 @@ import (
16 16
 	"github.com/docker/libcontainer/configs"
17 17
 )
18 18
 
19
+const stdioFdCount = 3
20
+
19 21
 type linuxContainer struct {
20 22
 	id            string
21 23
 	root          string
... ...
@@ -139,7 +141,8 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
139 139
 	if cmd.SysProcAttr == nil {
140 140
 		cmd.SysProcAttr = &syscall.SysProcAttr{}
141 141
 	}
142
-	cmd.ExtraFiles = []*os.File{childPipe}
142
+	cmd.ExtraFiles = append(p.ExtraFiles, childPipe)
143
+	cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1))
143 144
 	// NOTE: when running a container with no PID namespace and the parent process spawning the container is
144 145
 	// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
145 146
 	// even with the parent still running.
... ...
@@ -178,11 +181,9 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
178 178
 		fmt.Sprintf("_LIBCONTAINER_INITPID=%d", c.initProcess.pid()),
179 179
 		"_LIBCONTAINER_INITTYPE=setns",
180 180
 	)
181
-
182 181
 	if p.consolePath != "" {
183 182
 		cmd.Env = append(cmd.Env, "_LIBCONTAINER_CONSOLE_PATH="+p.consolePath)
184 183
 	}
185
-
186 184
 	// TODO: set on container for process management
187 185
 	return &setnsProcess{
188 186
 		cmd:         cmd,
... ...
@@ -195,13 +196,14 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
195 195
 
196 196
 func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
197 197
 	return &initConfig{
198
-		Config:       c.config,
199
-		Args:         process.Args,
200
-		Env:          process.Env,
201
-		User:         process.User,
202
-		Cwd:          process.Cwd,
203
-		Console:      process.consolePath,
204
-		Capabilities: process.Capabilities,
198
+		Config:           c.config,
199
+		Args:             process.Args,
200
+		Env:              process.Env,
201
+		User:             process.User,
202
+		Cwd:              process.Cwd,
203
+		Console:          process.consolePath,
204
+		Capabilities:     process.Capabilities,
205
+		PassedFilesCount: len(process.ExtraFiles),
205 206
 	}
206 207
 }
207 208
 
... ...
@@ -21,7 +21,7 @@ var (
21 21
 	ioutilReadDir = ioutil.ReadDir
22 22
 )
23 23
 
24
-// Given the path to a device and it's cgroup_permissions(which cannot be easilly queried) look up the information about a linux device and return that information as a Device struct.
24
+// Given the path to a device and it's cgroup_permissions(which cannot be easily queried) look up the information about a linux device and return that information as a Device struct.
25 25
 func DeviceFromPath(path, permissions string) (*configs.Device, error) {
26 26
 	fileInfo, err := osLstat(path)
27 27
 	if err != nil {
... ...
@@ -32,15 +32,13 @@ type Factory interface {
32 32
 	// System error
33 33
 	Load(id string) (Container, error)
34 34
 
35
-	// StartInitialization is an internal API to libcontainer used during the rexec of the
36
-	// container.  pipefd is the fd to the child end of the pipe used to syncronize the
37
-	// parent and child process providing state and configuration to the child process and
38
-	// returning any errors during the init of the container
35
+	// StartInitialization is an internal API to libcontainer used during the reexec of the
36
+	// container.
39 37
 	//
40 38
 	// Errors:
41
-	// pipe connection error
42
-	// system error
43
-	StartInitialization(pipefd uintptr) error
39
+	// Pipe connection error
40
+	// System error
41
+	StartInitialization() error
44 42
 
45 43
 	// Type returns info string about factory type (e.g. lxc, libcontainer...)
46 44
 	Type() string
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"os/exec"
11 11
 	"path/filepath"
12 12
 	"regexp"
13
+	"strconv"
13 14
 	"syscall"
14 15
 
15 16
 	"github.com/docker/docker/pkg/mount"
... ...
@@ -194,7 +195,11 @@ func (l *LinuxFactory) Type() string {
194 194
 
195 195
 // StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state
196 196
 // This is a low level implementation detail of the reexec and should not be consumed externally
197
-func (l *LinuxFactory) StartInitialization(pipefd uintptr) (err error) {
197
+func (l *LinuxFactory) StartInitialization() (err error) {
198
+	pipefd, err := strconv.Atoi(os.Getenv("_LIBCONTAINER_INITPIPE"))
199
+	if err != nil {
200
+		return err
201
+	}
198 202
 	var (
199 203
 		pipe = os.NewFile(uintptr(pipefd), "pipe")
200 204
 		it   = initType(os.Getenv("_LIBCONTAINER_INITTYPE"))
... ...
@@ -40,14 +40,15 @@ type network struct {
40 40
 
41 41
 // initConfig is used for transferring parameters from Exec() to Init()
42 42
 type initConfig struct {
43
-	Args         []string        `json:"args"`
44
-	Env          []string        `json:"env"`
45
-	Cwd          string          `json:"cwd"`
46
-	Capabilities []string        `json:"capabilities"`
47
-	User         string          `json:"user"`
48
-	Config       *configs.Config `json:"config"`
49
-	Console      string          `json:"console"`
50
-	Networks     []*network      `json:"network"`
43
+	Args             []string        `json:"args"`
44
+	Env              []string        `json:"env"`
45
+	Cwd              string          `json:"cwd"`
46
+	Capabilities     []string        `json:"capabilities"`
47
+	User             string          `json:"user"`
48
+	Config           *configs.Config `json:"config"`
49
+	Console          string          `json:"console"`
50
+	Networks         []*network      `json:"network"`
51
+	PassedFilesCount int             `json:"passed_files_count"`
51 52
 }
52 53
 
53 54
 type initer interface {
... ...
@@ -95,10 +96,10 @@ func populateProcessEnvironment(env []string) error {
95 95
 // and working dir, and closes any leaked file descriptors
96 96
 // before executing the command inside the namespace
97 97
 func finalizeNamespace(config *initConfig) error {
98
-	// Ensure that all non-standard fds we may have accidentally
98
+	// Ensure that all unwanted fds we may have accidentally
99 99
 	// inherited are marked close-on-exec so they stay out of the
100 100
 	// container
101
-	if err := utils.CloseExecFrom(3); err != nil {
101
+	if err := utils.CloseExecFrom(config.PassedFilesCount + 3); err != nil {
102 102
 		return err
103 103
 	}
104 104
 
... ...
@@ -4,8 +4,10 @@ import (
4 4
 	"bytes"
5 5
 	"io/ioutil"
6 6
 	"os"
7
+	"path/filepath"
7 8
 	"strconv"
8 9
 	"strings"
10
+	"syscall"
9 11
 	"testing"
10 12
 
11 13
 	"github.com/docker/libcontainer"
... ...
@@ -29,9 +31,7 @@ func testExecPS(t *testing.T, userns bool) {
29 29
 		return
30 30
 	}
31 31
 	rootfs, err := newRootfs()
32
-	if err != nil {
33
-		t.Fatal(err)
34
-	}
32
+	ok(t, err)
35 33
 	defer remove(rootfs)
36 34
 	config := newTemplateConfig(rootfs)
37 35
 	if userns {
... ...
@@ -64,21 +64,15 @@ func TestIPCPrivate(t *testing.T) {
64 64
 	}
65 65
 
66 66
 	rootfs, err := newRootfs()
67
-	if err != nil {
68
-		t.Fatal(err)
69
-	}
67
+	ok(t, err)
70 68
 	defer remove(rootfs)
71 69
 
72 70
 	l, err := os.Readlink("/proc/1/ns/ipc")
73
-	if err != nil {
74
-		t.Fatal(err)
75
-	}
71
+	ok(t, err)
76 72
 
77 73
 	config := newTemplateConfig(rootfs)
78 74
 	buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
79
-	if err != nil {
80
-		t.Fatal(err)
81
-	}
75
+	ok(t, err)
82 76
 
83 77
 	if exitCode != 0 {
84 78
 		t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
... ...
@@ -95,22 +89,16 @@ func TestIPCHost(t *testing.T) {
95 95
 	}
96 96
 
97 97
 	rootfs, err := newRootfs()
98
-	if err != nil {
99
-		t.Fatal(err)
100
-	}
98
+	ok(t, err)
101 99
 	defer remove(rootfs)
102 100
 
103 101
 	l, err := os.Readlink("/proc/1/ns/ipc")
104
-	if err != nil {
105
-		t.Fatal(err)
106
-	}
102
+	ok(t, err)
107 103
 
108 104
 	config := newTemplateConfig(rootfs)
109 105
 	config.Namespaces.Remove(configs.NEWIPC)
110 106
 	buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
111
-	if err != nil {
112
-		t.Fatal(err)
113
-	}
107
+	ok(t, err)
114 108
 
115 109
 	if exitCode != 0 {
116 110
 		t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
... ...
@@ -127,23 +115,17 @@ func TestIPCJoinPath(t *testing.T) {
127 127
 	}
128 128
 
129 129
 	rootfs, err := newRootfs()
130
-	if err != nil {
131
-		t.Fatal(err)
132
-	}
130
+	ok(t, err)
133 131
 	defer remove(rootfs)
134 132
 
135 133
 	l, err := os.Readlink("/proc/1/ns/ipc")
136
-	if err != nil {
137
-		t.Fatal(err)
138
-	}
134
+	ok(t, err)
139 135
 
140 136
 	config := newTemplateConfig(rootfs)
141 137
 	config.Namespaces.Add(configs.NEWIPC, "/proc/1/ns/ipc")
142 138
 
143 139
 	buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
144
-	if err != nil {
145
-		t.Fatal(err)
146
-	}
140
+	ok(t, err)
147 141
 
148 142
 	if exitCode != 0 {
149 143
 		t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
... ...
@@ -160,9 +142,7 @@ func TestIPCBadPath(t *testing.T) {
160 160
 	}
161 161
 
162 162
 	rootfs, err := newRootfs()
163
-	if err != nil {
164
-		t.Fatal(err)
165
-	}
163
+	ok(t, err)
166 164
 	defer remove(rootfs)
167 165
 
168 166
 	config := newTemplateConfig(rootfs)
... ...
@@ -180,16 +160,12 @@ func TestRlimit(t *testing.T) {
180 180
 	}
181 181
 
182 182
 	rootfs, err := newRootfs()
183
-	if err != nil {
184
-		t.Fatal(err)
185
-	}
183
+	ok(t, err)
186 184
 	defer remove(rootfs)
187 185
 
188 186
 	config := newTemplateConfig(rootfs)
189 187
 	out, _, err := runContainer(config, "", "/bin/sh", "-c", "ulimit -n")
190
-	if err != nil {
191
-		t.Fatal(err)
192
-	}
188
+	ok(t, err)
193 189
 	if limit := strings.TrimSpace(out.Stdout.String()); limit != "1025" {
194 190
 		t.Fatalf("expected rlimit to be 1025, got %s", limit)
195 191
 	}
... ...
@@ -208,9 +184,7 @@ func newTestRoot() (string, error) {
208 208
 
209 209
 func waitProcess(p *libcontainer.Process, t *testing.T) {
210 210
 	status, err := p.Wait()
211
-	if err != nil {
212
-		t.Fatal(err)
213
-	}
211
+	ok(t, err)
214 212
 	if !status.Success() {
215 213
 		t.Fatal(status)
216 214
 	}
... ...
@@ -221,35 +195,25 @@ func TestEnter(t *testing.T) {
221 221
 		return
222 222
 	}
223 223
 	root, err := newTestRoot()
224
-	if err != nil {
225
-		t.Fatal(err)
226
-	}
224
+	ok(t, err)
227 225
 	defer os.RemoveAll(root)
228 226
 
229 227
 	rootfs, err := newRootfs()
230
-	if err != nil {
231
-		t.Fatal(err)
232
-	}
228
+	ok(t, err)
233 229
 	defer remove(rootfs)
234 230
 
235 231
 	config := newTemplateConfig(rootfs)
236 232
 
237 233
 	factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
238
-	if err != nil {
239
-		t.Fatal(err)
240
-	}
234
+	ok(t, err)
241 235
 
242 236
 	container, err := factory.Create("test", config)
243
-	if err != nil {
244
-		t.Fatal(err)
245
-	}
237
+	ok(t, err)
246 238
 	defer container.Destroy()
247 239
 
248 240
 	// Execute a first process in the container
249 241
 	stdinR, stdinW, err := os.Pipe()
250
-	if err != nil {
251
-		t.Fatal(err)
252
-	}
242
+	ok(t, err)
253 243
 
254 244
 	var stdout, stdout2 bytes.Buffer
255 245
 
... ...
@@ -262,19 +226,13 @@ func TestEnter(t *testing.T) {
262 262
 	err = container.Start(&pconfig)
263 263
 	stdinR.Close()
264 264
 	defer stdinW.Close()
265
-	if err != nil {
266
-		t.Fatal(err)
267
-	}
265
+	ok(t, err)
268 266
 	pid, err := pconfig.Pid()
269
-	if err != nil {
270
-		t.Fatal(err)
271
-	}
267
+	ok(t, err)
272 268
 
273 269
 	// Execute another process in the container
274 270
 	stdinR2, stdinW2, err := os.Pipe()
275
-	if err != nil {
276
-		t.Fatal(err)
277
-	}
271
+	ok(t, err)
278 272
 	pconfig2 := libcontainer.Process{
279 273
 		Env: standardEnvironment,
280 274
 	}
... ...
@@ -285,19 +243,13 @@ func TestEnter(t *testing.T) {
285 285
 	err = container.Start(&pconfig2)
286 286
 	stdinR2.Close()
287 287
 	defer stdinW2.Close()
288
-	if err != nil {
289
-		t.Fatal(err)
290
-	}
288
+	ok(t, err)
291 289
 
292 290
 	pid2, err := pconfig2.Pid()
293
-	if err != nil {
294
-		t.Fatal(err)
295
-	}
291
+	ok(t, err)
296 292
 
297 293
 	processes, err := container.Processes()
298
-	if err != nil {
299
-		t.Fatal(err)
300
-	}
294
+	ok(t, err)
301 295
 
302 296
 	n := 0
303 297
 	for i := range processes {
... ...
@@ -318,14 +270,10 @@ func TestEnter(t *testing.T) {
318 318
 
319 319
 	// Check that both processes live in the same pidns
320 320
 	pidns := string(stdout.Bytes())
321
-	if err != nil {
322
-		t.Fatal(err)
323
-	}
321
+	ok(t, err)
324 322
 
325 323
 	pidns2 := string(stdout2.Bytes())
326
-	if err != nil {
327
-		t.Fatal(err)
328
-	}
324
+	ok(t, err)
329 325
 
330 326
 	if pidns != pidns2 {
331 327
 		t.Fatal("The second process isn't in the required pid namespace", pidns, pidns2)
... ...
@@ -337,28 +285,20 @@ func TestProcessEnv(t *testing.T) {
337 337
 		return
338 338
 	}
339 339
 	root, err := newTestRoot()
340
-	if err != nil {
341
-		t.Fatal(err)
342
-	}
340
+	ok(t, err)
343 341
 	defer os.RemoveAll(root)
344 342
 
345 343
 	rootfs, err := newRootfs()
346
-	if err != nil {
347
-		t.Fatal(err)
348
-	}
344
+	ok(t, err)
349 345
 	defer remove(rootfs)
350 346
 
351 347
 	config := newTemplateConfig(rootfs)
352 348
 
353 349
 	factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
354
-	if err != nil {
355
-		t.Fatal(err)
356
-	}
350
+	ok(t, err)
357 351
 
358 352
 	container, err := factory.Create("test", config)
359
-	if err != nil {
360
-		t.Fatal(err)
361
-	}
353
+	ok(t, err)
362 354
 	defer container.Destroy()
363 355
 
364 356
 	var stdout bytes.Buffer
... ...
@@ -374,17 +314,12 @@ func TestProcessEnv(t *testing.T) {
374 374
 		Stdout: &stdout,
375 375
 	}
376 376
 	err = container.Start(&pconfig)
377
-	if err != nil {
378
-		t.Fatal(err)
379
-	}
377
+	ok(t, err)
380 378
 
381 379
 	// Wait for process
382 380
 	waitProcess(&pconfig, t)
383 381
 
384 382
 	outputEnv := string(stdout.Bytes())
385
-	if err != nil {
386
-		t.Fatal(err)
387
-	}
388 383
 
389 384
 	// Check that the environment has the key/value pair we added
390 385
 	if !strings.Contains(outputEnv, "FOO=BAR") {
... ...
@@ -402,28 +337,20 @@ func TestProcessCaps(t *testing.T) {
402 402
 		return
403 403
 	}
404 404
 	root, err := newTestRoot()
405
-	if err != nil {
406
-		t.Fatal(err)
407
-	}
405
+	ok(t, err)
408 406
 	defer os.RemoveAll(root)
409 407
 
410 408
 	rootfs, err := newRootfs()
411
-	if err != nil {
412
-		t.Fatal(err)
413
-	}
409
+	ok(t, err)
414 410
 	defer remove(rootfs)
415 411
 
416 412
 	config := newTemplateConfig(rootfs)
417 413
 
418 414
 	factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
419
-	if err != nil {
420
-		t.Fatal(err)
421
-	}
415
+	ok(t, err)
422 416
 
423 417
 	container, err := factory.Create("test", config)
424
-	if err != nil {
425
-		t.Fatal(err)
426
-	}
418
+	ok(t, err)
427 419
 	defer container.Destroy()
428 420
 
429 421
 	processCaps := append(config.Capabilities, "NET_ADMIN")
... ...
@@ -437,17 +364,12 @@ func TestProcessCaps(t *testing.T) {
437 437
 		Stdout:       &stdout,
438 438
 	}
439 439
 	err = container.Start(&pconfig)
440
-	if err != nil {
441
-		t.Fatal(err)
442
-	}
440
+	ok(t, err)
443 441
 
444 442
 	// Wait for process
445 443
 	waitProcess(&pconfig, t)
446 444
 
447 445
 	outputStatus := string(stdout.Bytes())
448
-	if err != nil {
449
-		t.Fatal(err)
450
-	}
451 446
 
452 447
 	lines := strings.Split(outputStatus, "\n")
453 448
 
... ...
@@ -497,37 +419,28 @@ func testFreeze(t *testing.T, systemd bool) {
497 497
 		return
498 498
 	}
499 499
 	root, err := newTestRoot()
500
-	if err != nil {
501
-		t.Fatal(err)
502
-	}
500
+	ok(t, err)
503 501
 	defer os.RemoveAll(root)
504 502
 
505 503
 	rootfs, err := newRootfs()
506
-	if err != nil {
507
-		t.Fatal(err)
508
-	}
504
+	ok(t, err)
509 505
 	defer remove(rootfs)
510 506
 
511 507
 	config := newTemplateConfig(rootfs)
508
+	cgm := libcontainer.Cgroupfs
512 509
 	if systemd {
513
-		config.Cgroups.Slice = "system.slice"
510
+		cgm = libcontainer.SystemdCgroups
514 511
 	}
515 512
 
516
-	factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
517
-	if err != nil {
518
-		t.Fatal(err)
519
-	}
513
+	factory, err := libcontainer.New(root, cgm)
514
+	ok(t, err)
520 515
 
521 516
 	container, err := factory.Create("test", config)
522
-	if err != nil {
523
-		t.Fatal(err)
524
-	}
517
+	ok(t, err)
525 518
 	defer container.Destroy()
526 519
 
527 520
 	stdinR, stdinW, err := os.Pipe()
528
-	if err != nil {
529
-		t.Fatal(err)
530
-	}
521
+	ok(t, err)
531 522
 
532 523
 	pconfig := libcontainer.Process{
533 524
 		Args:  []string{"cat"},
... ...
@@ -537,44 +450,64 @@ func testFreeze(t *testing.T, systemd bool) {
537 537
 	err = container.Start(&pconfig)
538 538
 	stdinR.Close()
539 539
 	defer stdinW.Close()
540
-	if err != nil {
541
-		t.Fatal(err)
542
-	}
540
+	ok(t, err)
543 541
 
544 542
 	pid, err := pconfig.Pid()
545
-	if err != nil {
546
-		t.Fatal(err)
547
-	}
543
+	ok(t, err)
548 544
 
549 545
 	process, err := os.FindProcess(pid)
550
-	if err != nil {
551
-		t.Fatal(err)
552
-	}
546
+	ok(t, err)
553 547
 
554
-	if err := container.Pause(); err != nil {
555
-		t.Fatal(err)
556
-	}
548
+	err = container.Pause()
549
+	ok(t, err)
557 550
 	state, err := container.Status()
558
-	if err != nil {
559
-		t.Fatal(err)
560
-	}
561
-	if err := container.Resume(); err != nil {
562
-		t.Fatal(err)
563
-	}
551
+	ok(t, err)
552
+	err = container.Resume()
553
+	ok(t, err)
564 554
 	if state != libcontainer.Paused {
565 555
 		t.Fatal("Unexpected state: ", state)
566 556
 	}
567 557
 
568 558
 	stdinW.Close()
569 559
 	s, err := process.Wait()
570
-	if err != nil {
571
-		t.Fatal(err)
572
-	}
560
+	ok(t, err)
561
+
573 562
 	if !s.Success() {
574 563
 		t.Fatal(s.String())
575 564
 	}
576 565
 }
577 566
 
567
+func TestCpuShares(t *testing.T) {
568
+	testCpuShares(t, false)
569
+}
570
+
571
+func TestSystemdCpuShares(t *testing.T) {
572
+	if !systemd.UseSystemd() {
573
+		t.Skip("Systemd is unsupported")
574
+	}
575
+	testCpuShares(t, true)
576
+}
577
+
578
+func testCpuShares(t *testing.T, systemd bool) {
579
+	if testing.Short() {
580
+		return
581
+	}
582
+	rootfs, err := newRootfs()
583
+	ok(t, err)
584
+	defer remove(rootfs)
585
+
586
+	config := newTemplateConfig(rootfs)
587
+	if systemd {
588
+		config.Cgroups.Slice = "system.slice"
589
+	}
590
+	config.Cgroups.CpuShares = 1
591
+
592
+	_, _, err = runContainer(config, "", "ps")
593
+	if err == nil {
594
+		t.Fatalf("runContainer should failed with invalid CpuShares")
595
+	}
596
+}
597
+
578 598
 func TestContainerState(t *testing.T) {
579 599
 	if testing.Short() {
580 600
 		return
... ...
@@ -648,3 +581,185 @@ func TestContainerState(t *testing.T) {
648 648
 	stdinW.Close()
649 649
 	p.Wait()
650 650
 }
651
+
652
+func TestPassExtraFiles(t *testing.T) {
653
+	if testing.Short() {
654
+		return
655
+	}
656
+
657
+	rootfs, err := newRootfs()
658
+	if err != nil {
659
+		t.Fatal(err)
660
+	}
661
+	defer remove(rootfs)
662
+
663
+	config := newTemplateConfig(rootfs)
664
+
665
+	factory, err := libcontainer.New(rootfs, libcontainer.Cgroupfs)
666
+	if err != nil {
667
+		t.Fatal(err)
668
+	}
669
+
670
+	container, err := factory.Create("test", config)
671
+	if err != nil {
672
+		t.Fatal(err)
673
+	}
674
+	defer container.Destroy()
675
+
676
+	var stdout bytes.Buffer
677
+	pipeout1, pipein1, err := os.Pipe()
678
+	pipeout2, pipein2, err := os.Pipe()
679
+	process := libcontainer.Process{
680
+		Args:       []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"},
681
+		Env:        []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
682
+		ExtraFiles: []*os.File{pipein1, pipein2},
683
+		Stdin:      nil,
684
+		Stdout:     &stdout,
685
+	}
686
+	err = container.Start(&process)
687
+	if err != nil {
688
+		t.Fatal(err)
689
+	}
690
+
691
+	waitProcess(&process, t)
692
+
693
+	out := string(stdout.Bytes())
694
+	// fd 5 is the directory handle for /proc/$$/fd
695
+	if out != "0 1 2 3 4 5" {
696
+		t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to init, got '%s'", out)
697
+	}
698
+	var buf = []byte{0}
699
+	_, err = pipeout1.Read(buf)
700
+	if err != nil {
701
+		t.Fatal(err)
702
+	}
703
+	out1 := string(buf)
704
+	if out1 != "1" {
705
+		t.Fatalf("expected first pipe to receive '1', got '%s'", out1)
706
+	}
707
+
708
+	_, err = pipeout2.Read(buf)
709
+	if err != nil {
710
+		t.Fatal(err)
711
+	}
712
+	out2 := string(buf)
713
+	if out2 != "2" {
714
+		t.Fatalf("expected second pipe to receive '2', got '%s'", out2)
715
+	}
716
+}
717
+
718
+func TestMountCmds(t *testing.T) {
719
+	if testing.Short() {
720
+		return
721
+	}
722
+	root, err := newTestRoot()
723
+	if err != nil {
724
+		t.Fatal(err)
725
+	}
726
+	defer os.RemoveAll(root)
727
+
728
+	rootfs, err := newRootfs()
729
+	if err != nil {
730
+		t.Fatal(err)
731
+	}
732
+	defer remove(rootfs)
733
+
734
+	tmpDir, err := ioutil.TempDir("", "tmpdir")
735
+	if err != nil {
736
+		t.Fatal(err)
737
+	}
738
+	defer os.RemoveAll(tmpDir)
739
+
740
+	config := newTemplateConfig(rootfs)
741
+	config.Mounts = append(config.Mounts, &configs.Mount{
742
+		Source:      tmpDir,
743
+		Destination: filepath.Join(rootfs, "tmp"),
744
+		Device:      "bind",
745
+		Flags:       syscall.MS_BIND | syscall.MS_REC,
746
+		PremountCmds: []configs.Command{
747
+			{Path: "touch", Args: []string{filepath.Join(tmpDir, "hello")}},
748
+			{Path: "touch", Args: []string{filepath.Join(tmpDir, "world")}},
749
+		},
750
+		PostmountCmds: []configs.Command{
751
+			{Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "hello"), filepath.Join(rootfs, "tmp", "hello-backup")}},
752
+			{Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "world"), filepath.Join(rootfs, "tmp", "world-backup")}},
753
+		},
754
+	})
755
+
756
+	factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
757
+	if err != nil {
758
+		t.Fatal(err)
759
+	}
760
+
761
+	container, err := factory.Create("test", config)
762
+	if err != nil {
763
+		t.Fatal(err)
764
+	}
765
+	defer container.Destroy()
766
+
767
+	pconfig := libcontainer.Process{
768
+		Args: []string{"sh", "-c", "env"},
769
+		Env:  standardEnvironment,
770
+	}
771
+	err = container.Start(&pconfig)
772
+	if err != nil {
773
+		t.Fatal(err)
774
+	}
775
+
776
+	// Wait for process
777
+	waitProcess(&pconfig, t)
778
+
779
+	entries, err := ioutil.ReadDir(tmpDir)
780
+	if err != nil {
781
+		t.Fatal(err)
782
+	}
783
+	expected := []string{"hello", "hello-backup", "world", "world-backup"}
784
+	for i, e := range entries {
785
+		if e.Name() != expected[i] {
786
+			t.Errorf("Got(%s), expect %s", e.Name(), expected[i])
787
+		}
788
+	}
789
+}
790
+
791
+func TestSystemProperties(t *testing.T) {
792
+	if testing.Short() {
793
+		return
794
+	}
795
+	root, err := newTestRoot()
796
+	ok(t, err)
797
+	defer os.RemoveAll(root)
798
+
799
+	rootfs, err := newRootfs()
800
+	ok(t, err)
801
+	defer remove(rootfs)
802
+
803
+	config := newTemplateConfig(rootfs)
804
+	config.SystemProperties = map[string]string{
805
+		"kernel.shmmni": "8192",
806
+	}
807
+
808
+	factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
809
+	ok(t, err)
810
+
811
+	container, err := factory.Create("test", config)
812
+	ok(t, err)
813
+	defer container.Destroy()
814
+
815
+	var stdout bytes.Buffer
816
+	pconfig := libcontainer.Process{
817
+		Args:   []string{"sh", "-c", "cat /proc/sys/kernel/shmmni"},
818
+		Env:    standardEnvironment,
819
+		Stdin:  nil,
820
+		Stdout: &stdout,
821
+	}
822
+	err = container.Start(&pconfig)
823
+	ok(t, err)
824
+
825
+	// Wait for process
826
+	waitProcess(&pconfig, t)
827
+
828
+	shmmniOutput := strings.TrimSpace(string(stdout.Bytes()))
829
+	if shmmniOutput != "8192" {
830
+		t.Fatalf("kernel.shmmni property expected to be 8192, but is %s", shmmniOutput)
831
+	}
832
+}
... ...
@@ -16,22 +16,16 @@ func TestExecIn(t *testing.T) {
16 16
 		return
17 17
 	}
18 18
 	rootfs, err := newRootfs()
19
-	if err != nil {
20
-		t.Fatal(err)
21
-	}
19
+	ok(t, err)
22 20
 	defer remove(rootfs)
23 21
 	config := newTemplateConfig(rootfs)
24 22
 	container, err := newContainer(config)
25
-	if err != nil {
26
-		t.Fatal(err)
27
-	}
23
+	ok(t, err)
28 24
 	defer container.Destroy()
29 25
 
30 26
 	// Execute a first process in the container
31 27
 	stdinR, stdinW, err := os.Pipe()
32
-	if err != nil {
33
-		t.Fatal(err)
34
-	}
28
+	ok(t, err)
35 29
 	process := &libcontainer.Process{
36 30
 		Args:  []string{"cat"},
37 31
 		Env:   standardEnvironment,
... ...
@@ -40,9 +34,7 @@ func TestExecIn(t *testing.T) {
40 40
 	err = container.Start(process)
41 41
 	stdinR.Close()
42 42
 	defer stdinW.Close()
43
-	if err != nil {
44
-		t.Fatal(err)
45
-	}
43
+	ok(t, err)
46 44
 
47 45
 	buffers := newStdBuffers()
48 46
 	ps := &libcontainer.Process{
... ...
@@ -53,12 +45,9 @@ func TestExecIn(t *testing.T) {
53 53
 		Stderr: buffers.Stderr,
54 54
 	}
55 55
 	err = container.Start(ps)
56
-	if err != nil {
57
-		t.Fatal(err)
58
-	}
59
-	if _, err := ps.Wait(); err != nil {
60
-		t.Fatal(err)
61
-	}
56
+	ok(t, err)
57
+	_, err = ps.Wait()
58
+	ok(t, err)
62 59
 	stdinW.Close()
63 60
 	if _, err := process.Wait(); err != nil {
64 61
 		t.Log(err)
... ...
@@ -74,21 +63,15 @@ func TestExecInRlimit(t *testing.T) {
74 74
 		return
75 75
 	}
76 76
 	rootfs, err := newRootfs()
77
-	if err != nil {
78
-		t.Fatal(err)
79
-	}
77
+	ok(t, err)
80 78
 	defer remove(rootfs)
81 79
 	config := newTemplateConfig(rootfs)
82 80
 	container, err := newContainer(config)
83
-	if err != nil {
84
-		t.Fatal(err)
85
-	}
81
+	ok(t, err)
86 82
 	defer container.Destroy()
87 83
 
88 84
 	stdinR, stdinW, err := os.Pipe()
89
-	if err != nil {
90
-		t.Fatal(err)
91
-	}
85
+	ok(t, err)
92 86
 	process := &libcontainer.Process{
93 87
 		Args:  []string{"cat"},
94 88
 		Env:   standardEnvironment,
... ...
@@ -97,9 +80,7 @@ func TestExecInRlimit(t *testing.T) {
97 97
 	err = container.Start(process)
98 98
 	stdinR.Close()
99 99
 	defer stdinW.Close()
100
-	if err != nil {
101
-		t.Fatal(err)
102
-	}
100
+	ok(t, err)
103 101
 
104 102
 	buffers := newStdBuffers()
105 103
 	ps := &libcontainer.Process{
... ...
@@ -110,12 +91,9 @@ func TestExecInRlimit(t *testing.T) {
110 110
 		Stderr: buffers.Stderr,
111 111
 	}
112 112
 	err = container.Start(ps)
113
-	if err != nil {
114
-		t.Fatal(err)
115
-	}
116
-	if _, err := ps.Wait(); err != nil {
117
-		t.Fatal(err)
118
-	}
113
+	ok(t, err)
114
+	_, err = ps.Wait()
115
+	ok(t, err)
119 116
 	stdinW.Close()
120 117
 	if _, err := process.Wait(); err != nil {
121 118
 		t.Log(err)
... ...
@@ -131,22 +109,16 @@ func TestExecInError(t *testing.T) {
131 131
 		return
132 132
 	}
133 133
 	rootfs, err := newRootfs()
134
-	if err != nil {
135
-		t.Fatal(err)
136
-	}
134
+	ok(t, err)
137 135
 	defer remove(rootfs)
138 136
 	config := newTemplateConfig(rootfs)
139 137
 	container, err := newContainer(config)
140
-	if err != nil {
141
-		t.Fatal(err)
142
-	}
138
+	ok(t, err)
143 139
 	defer container.Destroy()
144 140
 
145 141
 	// Execute a first process in the container
146 142
 	stdinR, stdinW, err := os.Pipe()
147
-	if err != nil {
148
-		t.Fatal(err)
149
-	}
143
+	ok(t, err)
150 144
 	process := &libcontainer.Process{
151 145
 		Args:  []string{"cat"},
152 146
 		Env:   standardEnvironment,
... ...
@@ -160,9 +132,7 @@ func TestExecInError(t *testing.T) {
160 160
 			t.Log(err)
161 161
 		}
162 162
 	}()
163
-	if err != nil {
164
-		t.Fatal(err)
165
-	}
163
+	ok(t, err)
166 164
 
167 165
 	unexistent := &libcontainer.Process{
168 166
 		Args: []string{"unexistent"},
... ...
@@ -182,22 +152,16 @@ func TestExecInTTY(t *testing.T) {
182 182
 		return
183 183
 	}
184 184
 	rootfs, err := newRootfs()
185
-	if err != nil {
186
-		t.Fatal(err)
187
-	}
185
+	ok(t, err)
188 186
 	defer remove(rootfs)
189 187
 	config := newTemplateConfig(rootfs)
190 188
 	container, err := newContainer(config)
191
-	if err != nil {
192
-		t.Fatal(err)
193
-	}
189
+	ok(t, err)
194 190
 	defer container.Destroy()
195 191
 
196 192
 	// Execute a first process in the container
197 193
 	stdinR, stdinW, err := os.Pipe()
198
-	if err != nil {
199
-		t.Fatal(err)
200
-	}
194
+	ok(t, err)
201 195
 	process := &libcontainer.Process{
202 196
 		Args:  []string{"cat"},
203 197
 		Env:   standardEnvironment,
... ...
@@ -206,9 +170,7 @@ func TestExecInTTY(t *testing.T) {
206 206
 	err = container.Start(process)
207 207
 	stdinR.Close()
208 208
 	defer stdinW.Close()
209
-	if err != nil {
210
-		t.Fatal(err)
211
-	}
209
+	ok(t, err)
212 210
 
213 211
 	var stdout bytes.Buffer
214 212
 	ps := &libcontainer.Process{
... ...
@@ -221,21 +183,16 @@ func TestExecInTTY(t *testing.T) {
221 221
 		io.Copy(&stdout, console)
222 222
 		close(copy)
223 223
 	}()
224
-	if err != nil {
225
-		t.Fatal(err)
226
-	}
224
+	ok(t, err)
227 225
 	err = container.Start(ps)
228
-	if err != nil {
229
-		t.Fatal(err)
230
-	}
226
+	ok(t, err)
231 227
 	select {
232 228
 	case <-time.After(5 * time.Second):
233 229
 		t.Fatal("Waiting for copy timed out")
234 230
 	case <-copy:
235 231
 	}
236
-	if _, err := ps.Wait(); err != nil {
237
-		t.Fatal(err)
238
-	}
232
+	_, err = ps.Wait()
233
+	ok(t, err)
239 234
 	stdinW.Close()
240 235
 	if _, err := process.Wait(); err != nil {
241 236
 		t.Log(err)
... ...
@@ -251,22 +208,16 @@ func TestExecInEnvironment(t *testing.T) {
251 251
 		return
252 252
 	}
253 253
 	rootfs, err := newRootfs()
254
-	if err != nil {
255
-		t.Fatal(err)
256
-	}
254
+	ok(t, err)
257 255
 	defer remove(rootfs)
258 256
 	config := newTemplateConfig(rootfs)
259 257
 	container, err := newContainer(config)
260
-	if err != nil {
261
-		t.Fatal(err)
262
-	}
258
+	ok(t, err)
263 259
 	defer container.Destroy()
264 260
 
265 261
 	// Execute a first process in the container
266 262
 	stdinR, stdinW, err := os.Pipe()
267
-	if err != nil {
268
-		t.Fatal(err)
269
-	}
263
+	ok(t, err)
270 264
 	process := &libcontainer.Process{
271 265
 		Args:  []string{"cat"},
272 266
 		Env:   standardEnvironment,
... ...
@@ -275,9 +226,7 @@ func TestExecInEnvironment(t *testing.T) {
275 275
 	err = container.Start(process)
276 276
 	stdinR.Close()
277 277
 	defer stdinW.Close()
278
-	if err != nil {
279
-		t.Fatal(err)
280
-	}
278
+	ok(t, err)
281 279
 
282 280
 	buffers := newStdBuffers()
283 281
 	process2 := &libcontainer.Process{
... ...
@@ -293,9 +242,7 @@ func TestExecInEnvironment(t *testing.T) {
293 293
 		Stderr: buffers.Stderr,
294 294
 	}
295 295
 	err = container.Start(process2)
296
-	if err != nil {
297
-		t.Fatal(err)
298
-	}
296
+	ok(t, err)
299 297
 	if _, err := process2.Wait(); err != nil {
300 298
 		out := buffers.Stdout.String()
301 299
 		t.Fatal(err, out)
... ...
@@ -314,3 +261,80 @@ func TestExecInEnvironment(t *testing.T) {
314 314
 		t.Fatalf("unexpected running process, output %q", out)
315 315
 	}
316 316
 }
317
+
318
+func TestExecinPassExtraFiles(t *testing.T) {
319
+	if testing.Short() {
320
+		return
321
+	}
322
+	rootfs, err := newRootfs()
323
+	if err != nil {
324
+		t.Fatal(err)
325
+	}
326
+	defer remove(rootfs)
327
+	config := newTemplateConfig(rootfs)
328
+	container, err := newContainer(config)
329
+	if err != nil {
330
+		t.Fatal(err)
331
+	}
332
+	defer container.Destroy()
333
+
334
+	// Execute a first process in the container
335
+	stdinR, stdinW, err := os.Pipe()
336
+	if err != nil {
337
+		t.Fatal(err)
338
+	}
339
+	process := &libcontainer.Process{
340
+		Args:  []string{"cat"},
341
+		Env:   standardEnvironment,
342
+		Stdin: stdinR,
343
+	}
344
+	err = container.Start(process)
345
+	stdinR.Close()
346
+	defer stdinW.Close()
347
+	if err != nil {
348
+		t.Fatal(err)
349
+	}
350
+
351
+	var stdout bytes.Buffer
352
+	pipeout1, pipein1, err := os.Pipe()
353
+	pipeout2, pipein2, err := os.Pipe()
354
+	inprocess := &libcontainer.Process{
355
+		Args:       []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"},
356
+		Env:        []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
357
+		ExtraFiles: []*os.File{pipein1, pipein2},
358
+		Stdin:      nil,
359
+		Stdout:     &stdout,
360
+	}
361
+	err = container.Start(inprocess)
362
+	if err != nil {
363
+		t.Fatal(err)
364
+	}
365
+
366
+	waitProcess(inprocess, t)
367
+	stdinW.Close()
368
+	waitProcess(process, t)
369
+
370
+	out := string(stdout.Bytes())
371
+	// fd 5 is the directory handle for /proc/$$/fd
372
+	if out != "0 1 2 3 4 5" {
373
+		t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to exec, got '%s'", out)
374
+	}
375
+	var buf = []byte{0}
376
+	_, err = pipeout1.Read(buf)
377
+	if err != nil {
378
+		t.Fatal(err)
379
+	}
380
+	out1 := string(buf)
381
+	if out1 != "1" {
382
+		t.Fatalf("expected first pipe to receive '1', got '%s'", out1)
383
+	}
384
+
385
+	_, err = pipeout2.Read(buf)
386
+	if err != nil {
387
+		t.Fatal(err)
388
+	}
389
+	out2 := string(buf)
390
+	if out2 != "2" {
391
+		t.Fatalf("expected second pipe to receive '2', got '%s'", out2)
392
+	}
393
+}
... ...
@@ -21,7 +21,7 @@ func init() {
21 21
 	if err != nil {
22 22
 		log.Fatalf("unable to initialize for container: %s", err)
23 23
 	}
24
-	if err := factory.StartInitialization(3); err != nil {
24
+	if err := factory.StartInitialization(); err != nil {
25 25
 		log.Fatal(err)
26 26
 	}
27 27
 }
... ...
@@ -6,8 +6,11 @@ import (
6 6
 	"io/ioutil"
7 7
 	"os"
8 8
 	"os/exec"
9
+	"path/filepath"
10
+	"runtime"
9 11
 	"strings"
10 12
 	"syscall"
13
+	"testing"
11 14
 
12 15
 	"github.com/docker/libcontainer"
13 16
 	"github.com/docker/libcontainer/configs"
... ...
@@ -38,6 +41,14 @@ func (b *stdBuffers) String() string {
38 38
 	return strings.Join(s, "|")
39 39
 }
40 40
 
41
+// ok fails the test if an err is not nil.
42
+func ok(t testing.TB, err error) {
43
+	if err != nil {
44
+		_, file, line, _ := runtime.Caller(1)
45
+		t.Fatalf("%s:%d: unexpected error: %s\n\n", filepath.Base(file), line, err.Error())
46
+	}
47
+}
48
+
41 49
 // newRootfs creates a new tmp directory and copies the busybox root filesystem
42 50
 func newRootfs() (string, error) {
43 51
 	dir, err := ioutil.TempDir("", "")
... ...
@@ -101,10 +101,22 @@ func SetFileCreateLabel(fileLabel string) error {
101 101
 // the MCS label should continue to be used.  SELinux will use this field
102 102
 // to make sure the content can not be shared by other containes.
103 103
 func Relabel(path string, fileLabel string, relabel string) error {
104
+	exclude_path := []string{"/", "/usr", "/etc"}
104 105
 	if fileLabel == "" {
105 106
 		return nil
106 107
 	}
107
-	if relabel == "z" {
108
+	for _, p := range exclude_path {
109
+		if path == p {
110
+			return fmt.Errorf("Relabeling of %s is not allowed", path)
111
+		}
112
+	}
113
+	if !strings.ContainsAny(relabel, "zZ") {
114
+		return nil
115
+	}
116
+	if strings.Contains(relabel, "z") && strings.Contains(relabel, "Z") {
117
+		return fmt.Errorf("Bad SELinux option z and Z can not be used together")
118
+	}
119
+	if strings.Contains(relabel, "z") {
108 120
 		c := selinux.NewContext(fileLabel)
109 121
 		c["level"] = "s0"
110 122
 		fileLabel = c.Get()
... ...
@@ -87,3 +87,31 @@ func TestDuplicateLabel(t *testing.T) {
87 87
 		t.Errorf("DisableSecOpt Failed level incorrect")
88 88
 	}
89 89
 }
90
+func TestRelabel(t *testing.T) {
91
+	testdir := "/tmp/test"
92
+	label := "system_u:system_r:svirt_sandbox_file_t:s0:c1,c2"
93
+	if err := Relabel(testdir, "", "z"); err != nil {
94
+		t.Fatal("Relabel with no label failed: %v", err)
95
+	}
96
+	if err := Relabel(testdir, label, ""); err != nil {
97
+		t.Fatal("Relabel with no relabel field failed: %v", err)
98
+	}
99
+	if err := Relabel(testdir, label, "z"); err != nil {
100
+		t.Fatal("Relabel shared failed: %v", err)
101
+	}
102
+	if err := Relabel(testdir, label, "Z"); err != nil {
103
+		t.Fatal("Relabel unshared failed: %v", err)
104
+	}
105
+	if err := Relabel(testdir, label, "zZ"); err == nil {
106
+		t.Fatal("Relabel with shared and unshared succeeded")
107
+	}
108
+	if err := Relabel("/etc", label, "zZ"); err == nil {
109
+		t.Fatal("Relabel /etc succeeded")
110
+	}
111
+	if err := Relabel("/", label, ""); err == nil {
112
+		t.Fatal("Relabel / succeeded")
113
+	}
114
+	if err := Relabel("/usr", label, "Z"); err == nil {
115
+		t.Fatal("Relabel /usr succeeded")
116
+	}
117
+}
... ...
@@ -1,6 +1,25 @@
1 1
 ## nsenter
2 2
 
3
-The `nsenter` package registers a special init constructor that is called before the Go runtime has 
4
-a chance to boot.  This provides us the ability to `setns` on existing namespaces and avoid the issues
5
-that the Go runtime has with multiple threads.  This constructor is only called if this package is 
6
-registered, imported, in your go application and the argv 0 is `nsenter`.
3
+The `nsenter` package registers a special init constructor that is called before 
4
+the Go runtime has a chance to boot.  This provides us the ability to `setns` on 
5
+existing namespaces and avoid the issues that the Go runtime has with multiple 
6
+threads.  This constructor will be called if this package is registered, 
7
+imported, in your go application.
8
+
9
+The `nsenter` package will `import "C"` and it uses [cgo](https://golang.org/cmd/cgo/)
10
+package. In cgo, if the import of "C" is immediately preceded by a comment, that comment, 
11
+called the preamble, is used as a header when compiling the C parts of the package.
12
+So every time we  import package `nsenter`, the C code function `nsexec()` would be 
13
+called. And package `nsenter` is now only imported in Docker execdriver, so every time 
14
+before we call `execdriver.Exec()`, that C code would run.
15
+
16
+`nsexec()` will first check the environment variable `_LIBCONTAINER_INITPID` 
17
+which will give the process of the container that should be joined. Namespaces fd will 
18
+be found from `/proc/[pid]/ns` and set by `setns` syscall.
19
+
20
+And then get the pipe number from `_LIBCONTAINER_INITPIPE`, error message could
21
+be transfered through it. If tty is added, `_LIBCONTAINER_CONSOLE_PATH` will 
22
+have value and start a console for output.
23
+
24
+Finally, `nsexec()` will clone a child process , exit the parent process and let 
25
+the Go runtime take over.
... ...
@@ -24,7 +24,7 @@ func TestNsenterAlivePid(t *testing.T) {
24 24
 		Path:       os.Args[0],
25 25
 		Args:       args,
26 26
 		ExtraFiles: []*os.File{w},
27
-		Env:        []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", os.Getpid())},
27
+		Env:        []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", os.Getpid()), "_LIBCONTAINER_INITPIPE=3"},
28 28
 	}
29 29
 
30 30
 	if err := cmd.Start(); err != nil {
... ...
@@ -66,7 +66,7 @@ void nsexec()
66 66
 	const int num = sizeof(namespaces) / sizeof(char *);
67 67
 	jmp_buf env;
68 68
 	char buf[PATH_MAX], *val;
69
-	int i, tfd, child, len, consolefd = -1;
69
+	int i, tfd, child, len, pipenum, consolefd = -1;
70 70
 	pid_t pid;
71 71
 	char *console;
72 72
 
... ...
@@ -81,6 +81,19 @@ void nsexec()
81 81
 		exit(1);
82 82
 	}
83 83
 
84
+	val = getenv("_LIBCONTAINER_INITPIPE");
85
+	if (val == NULL) {
86
+		pr_perror("Child pipe not found");
87
+		exit(1);
88
+	}
89
+
90
+	pipenum = atoi(val);
91
+	snprintf(buf, sizeof(buf), "%d", pipenum);
92
+	if (strcmp(val, buf)) {
93
+		pr_perror("Unable to parse _LIBCONTAINER_INITPIPE");
94
+		exit(1);
95
+	}
96
+
84 97
 	console = getenv("_LIBCONTAINER_CONSOLE_PATH");
85 98
 	if (console != NULL) {
86 99
 		consolefd = open(console, O_RDWR);
... ...
@@ -124,6 +137,8 @@ void nsexec()
124 124
 	}
125 125
 
126 126
 	if (setjmp(env) == 1) {
127
+		// Child
128
+
127 129
 		if (setsid() == -1) {
128 130
 			pr_perror("setsid failed");
129 131
 			exit(1);
... ...
@@ -149,7 +164,11 @@ void nsexec()
149 149
 		// Finish executing, let the Go runtime take over.
150 150
 		return;
151 151
 	}
152
+	// Parent
152 153
 
154
+	// We must fork to actually enter the PID namespace, use CLONE_PARENT
155
+	// so the child can have the right parent, and we don't need to forward
156
+	// the child's exit code or resend its death signal.
153 157
 	child = clone_parent(&env);
154 158
 	if (child < 0) {
155 159
 		pr_perror("Unable to fork");
... ...
@@ -158,7 +177,7 @@ void nsexec()
158 158
 
159 159
 	len = snprintf(buf, sizeof(buf), "{ \"pid\" : %d }\n", child);
160 160
 
161
-	if (write(3, buf, len) != len) {
161
+	if (write(pipenum, buf, len) != len) {
162 162
 		pr_perror("Unable to send a child pid");
163 163
 		kill(child, SIGKILL);
164 164
 		exit(1);
... ...
@@ -65,3 +65,48 @@ You can identify if a process is running in a container by looking to see if
65 65
    
66 66
 You may also specify an alternate root directory from where the `container.json`
67 67
 file is read and where the `state.json` file will be saved.
68
+
69
+### How to use?
70
+
71
+Currently nsinit has 9 commands. Type `nsinit -h` to list all of them. 
72
+And for every alternative command, you can also use `--help` to get more 
73
+detailed help documents. For example, `nsinit config --help`.
74
+
75
+`nsinit` cli application is implemented using [cli.go](https://github.com/codegangsta/cli). 
76
+Lots of details are handled in cli.go, so the implementation of `nsinit` itself 
77
+is very clean and clear.
78
+
79
+*   **config**	
80
+It will generate a standard configuration file for a container.  By default, it 
81
+will generate as the template file in [config.go](https://github.com/docker/libcontainer/blob/master/nsinit/config.go#L192). 
82
+It will modify the template if you have specified some configuration by options.
83
+*   **exec**	
84
+Starts a container and execute a new command inside it. Besides common options, it
85
+has some special options as below.
86
+	- `--tty,-t`: allocate a TTY to the container.
87
+	- `--config`: you can specify a configuration file. By default, it will use 
88
+	template configuration.
89
+	- `--id`: specify the ID for a container. By default, the id is "nsinit".
90
+	- `--user,-u`: set the user, uid, and/or gid for the process. By default the 
91
+	value is "root".
92
+	- `--cwd`: set the current working dir.
93
+	- `--env`: set environment variables for the process.
94
+*   **init**		
95
+It's an internal command that is called inside the container's namespaces to 
96
+initialize the namespace and exec the user's process. It should not be called 
97
+externally.
98
+*   **oom**		
99
+Display oom notifications for a container, you should specify container id.
100
+*   **pause**	
101
+Pause the container's processes, you should specify container id. It will use 
102
+cgroup freeze subsystem to help.
103
+*   **unpause**		
104
+Unpause the container's processes. Same with `pause`.
105
+*   **stats**	
106
+Display statistics for the container, it will mainly show cgroup and network 
107
+statistics.
108
+*   **state**	
109
+Get the container's current state. You can also read the state from `state.json`
110
+ in your container_id folder.
111
+*   **help, h**		
112
+Shows a list of commands or help for one command.
... ...
@@ -43,6 +43,7 @@ var createFlags = []cli.Flag{
43 43
 	cli.StringFlag{Name: "veth-address", Usage: "veth ip address"},
44 44
 	cli.StringFlag{Name: "veth-gateway", Usage: "veth gateway address"},
45 45
 	cli.IntFlag{Name: "veth-mtu", Usage: "veth mtu"},
46
+	cli.BoolFlag{Name: "cgroup", Usage: "mount the cgroup data for the container"},
46 47
 }
47 48
 
48 49
 var configCommand = cli.Command{
... ...
@@ -187,6 +188,12 @@ func modify(config *configs.Config, context *cli.Context) {
187 187
 		}
188 188
 		config.Networks = append(config.Networks, network)
189 189
 	}
190
+	if context.Bool("cgroup") {
191
+		config.Mounts = append(config.Mounts, &configs.Mount{
192
+			Destination: "/sys/fs/cgroup",
193
+			Device:      "cgroup",
194
+		})
195
+	}
190 196
 }
191 197
 
192 198
 func getTemplate() *configs.Config {
... ...
@@ -23,6 +23,7 @@ var execCommand = cli.Command{
23 23
 	Action: execAction,
24 24
 	Flags: append([]cli.Flag{
25 25
 		cli.BoolFlag{Name: "tty,t", Usage: "allocate a TTY to the container"},
26
+		cli.BoolFlag{Name: "systemd", Usage: "Use systemd for managing cgroups, if available"},
26 27
 		cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"},
27 28
 		cli.StringFlag{Name: "config", Value: "", Usage: "path to the configuration file"},
28 29
 		cli.StringFlag{Name: "user,u", Value: "root", Usage: "set the user, uid, and/or gid for the process"},
... ...
@@ -20,7 +20,7 @@ var initCommand = cli.Command{
20 20
 		if err != nil {
21 21
 			fatal(err)
22 22
 		}
23
-		if err := factory.StartInitialization(3); err != nil {
23
+		if err := factory.StartInitialization(); err != nil {
24 24
 			fatal(err)
25 25
 		}
26 26
 		panic("This line should never been executed")
... ...
@@ -1,8 +1,7 @@
1 1
 package main
2 2
 
3 3
 import (
4
-	"log"
5
-
4
+	log "github.com/Sirupsen/logrus"
6 5
 	"github.com/codegangsta/cli"
7 6
 )
8 7
 
... ...
@@ -1,8 +1,7 @@
1 1
 package main
2 2
 
3 3
 import (
4
-	"log"
5
-
4
+	log "github.com/Sirupsen/logrus"
6 5
 	"github.com/codegangsta/cli"
7 6
 )
8 7
 
... ...
@@ -3,10 +3,12 @@ package main
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6
+	log "github.com/Sirupsen/logrus"
6 7
 	"os"
7 8
 
8 9
 	"github.com/codegangsta/cli"
9 10
 	"github.com/docker/libcontainer"
11
+	"github.com/docker/libcontainer/cgroups/systemd"
10 12
 	"github.com/docker/libcontainer/configs"
11 13
 )
12 14
 
... ...
@@ -29,7 +31,15 @@ func loadConfig(context *cli.Context) (*configs.Config, error) {
29 29
 }
30 30
 
31 31
 func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
32
-	return libcontainer.New(context.GlobalString("root"), libcontainer.Cgroupfs)
32
+	cgm := libcontainer.Cgroupfs
33
+	if context.Bool("systemd") {
34
+		if systemd.UseSystemd() {
35
+			cgm = libcontainer.SystemdCgroups
36
+		} else {
37
+			log.Warn("systemd cgroup flag passed, but systemd support for managing cgroups is not available.")
38
+		}
39
+	}
40
+	return libcontainer.New(context.GlobalString("root"), cgm)
33 41
 }
34 42
 
35 43
 func getContainer(context *cli.Context) (libcontainer.Container, error) {
... ...
@@ -23,7 +23,7 @@ type Process struct {
23 23
 	Env []string
24 24
 
25 25
 	// User will set the uid and gid of the executing process running inside the container
26
-	// local to the contaienr's user and group configuration.
26
+	// local to the container's user and group configuration.
27 27
 	User string
28 28
 
29 29
 	// Cwd will change the processes current working directory inside the container's rootfs.
... ...
@@ -38,11 +38,14 @@ type Process struct {
38 38
 	// Stderr is a pointer to a writer which receives the standard error stream.
39 39
 	Stderr io.Writer
40 40
 
41
+	// ExtraFiles specifies additional open files to be inherited by the container
42
+	ExtraFiles []*os.File
43
+
41 44
 	// consolePath is the path to the console allocated to the container.
42 45
 	consolePath string
43 46
 
44 47
 	// Capabilities specify the capabilities to keep when executing the process inside the container
45
-	// All capbilities not specified will be dropped from the processes capability mask
48
+	// All capabilities not specified will be dropped from the processes capability mask
46 49
 	Capabilities []string
47 50
 
48 51
 	ops processOperations
... ...
@@ -119,6 +119,9 @@ func (p *setnsProcess) execSetns() error {
119 119
 // terminate sends a SIGKILL to the forked process for the setns routine then waits to
120 120
 // avoid the process becomming a zombie.
121 121
 func (p *setnsProcess) terminate() error {
122
+	if p.cmd.Process == nil {
123
+		return nil
124
+	}
122 125
 	err := p.cmd.Process.Kill()
123 126
 	if _, werr := p.wait(); err == nil {
124 127
 		err = werr
... ...
@@ -6,11 +6,14 @@ import (
6 6
 	"fmt"
7 7
 	"io/ioutil"
8 8
 	"os"
9
+	"os/exec"
10
+	"path"
9 11
 	"path/filepath"
10 12
 	"strings"
11 13
 	"syscall"
12 14
 	"time"
13 15
 
16
+	"github.com/docker/libcontainer/cgroups"
14 17
 	"github.com/docker/libcontainer/configs"
15 18
 	"github.com/docker/libcontainer/label"
16 19
 )
... ...
@@ -24,9 +27,20 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
24 24
 		return newSystemError(err)
25 25
 	}
26 26
 	for _, m := range config.Mounts {
27
+		for _, precmd := range m.PremountCmds {
28
+			if err := mountCmd(precmd); err != nil {
29
+				return newSystemError(err)
30
+			}
31
+		}
27 32
 		if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil {
28 33
 			return newSystemError(err)
29 34
 		}
35
+
36
+		for _, postcmd := range m.PostmountCmds {
37
+			if err := mountCmd(postcmd); err != nil {
38
+				return newSystemError(err)
39
+			}
40
+		}
30 41
 	}
31 42
 	if err := createDevices(config); err != nil {
32 43
 		return newSystemError(err)
... ...
@@ -62,6 +76,18 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
62 62
 	return nil
63 63
 }
64 64
 
65
+func mountCmd(cmd configs.Command) error {
66
+
67
+	command := exec.Command(cmd.Path, cmd.Args[:]...)
68
+	command.Env = cmd.Env
69
+	command.Dir = cmd.Dir
70
+	if out, err := command.CombinedOutput(); err != nil {
71
+		return fmt.Errorf("%#v failed: %s: %v", cmd, string(out), err)
72
+	}
73
+
74
+	return nil
75
+}
76
+
65 77
 func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
66 78
 	var (
67 79
 		dest = m.Destination
... ...
@@ -72,11 +98,19 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
72 72
 	}
73 73
 
74 74
 	switch m.Device {
75
-	case "proc", "mqueue", "sysfs":
75
+	case "proc", "sysfs":
76 76
 		if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) {
77 77
 			return err
78 78
 		}
79 79
 		return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), "")
80
+	case "mqueue":
81
+		if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) {
82
+			return err
83
+		}
84
+		if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), ""); err != nil {
85
+			return err
86
+		}
87
+		return label.SetFileLabel(dest, mountLabel)
80 88
 	case "tmpfs":
81 89
 		stat, err := os.Stat(dest)
82 90
 		if err != nil {
... ...
@@ -126,6 +160,37 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
126 126
 				return err
127 127
 			}
128 128
 		}
129
+	case "cgroup":
130
+		mounts, err := cgroups.GetCgroupMounts()
131
+		if err != nil {
132
+			return err
133
+		}
134
+		var binds []*configs.Mount
135
+		for _, mm := range mounts {
136
+			dir, err := mm.GetThisCgroupDir()
137
+			if err != nil {
138
+				return err
139
+			}
140
+			binds = append(binds, &configs.Mount{
141
+				Device:      "bind",
142
+				Source:      filepath.Join(mm.Mountpoint, dir),
143
+				Destination: filepath.Join(m.Destination, strings.Join(mm.Subsystems, ",")),
144
+				Flags:       syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY,
145
+			})
146
+		}
147
+		tmpfs := &configs.Mount{
148
+			Device:      "tmpfs",
149
+			Destination: m.Destination,
150
+			Flags:       syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV,
151
+		}
152
+		if err := mountToRootfs(tmpfs, rootfs, mountLabel); err != nil {
153
+			return err
154
+		}
155
+		for _, b := range binds {
156
+			if err := mountToRootfs(b, rootfs, mountLabel); err != nil {
157
+				return err
158
+			}
159
+		}
129 160
 	default:
130 161
 		return fmt.Errorf("unknown mount device %q to %q", m.Device, m.Destination)
131 162
 	}
... ...
@@ -240,9 +305,9 @@ func mknodDevice(dest string, node *configs.Device) error {
240 240
 }
241 241
 
242 242
 func prepareRoot(config *configs.Config) error {
243
-	flag := syscall.MS_PRIVATE | syscall.MS_REC
244
-	if config.NoPivotRoot {
245
-		flag = syscall.MS_SLAVE | syscall.MS_REC
243
+	flag := syscall.MS_SLAVE | syscall.MS_REC
244
+	if config.Privatefs {
245
+		flag = syscall.MS_PRIVATE | syscall.MS_REC
246 246
 	}
247 247
 	if err := syscall.Mount("", "/", "", uintptr(flag), ""); err != nil {
248 248
 		return err
... ...
@@ -355,3 +420,10 @@ func maskFile(path string) error {
355 355
 	}
356 356
 	return nil
357 357
 }
358
+
359
+// writeSystemProperty writes the value to a path under /proc/sys as determined from the key.
360
+// For e.g. net.ipv4.ip_forward translated to /proc/sys/net/ipv4/ip_forward.
361
+func writeSystemProperty(key, value string) error {
362
+	keyPath := strings.Replace(key, ".", "/", -1)
363
+	return ioutil.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0644)
364
+}
... ...
@@ -64,6 +64,13 @@ func (l *linuxStandardInit) Init() error {
64 64
 	if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil {
65 65
 		return err
66 66
 	}
67
+
68
+	for key, value := range l.config.Config.SystemProperties {
69
+		if err := writeSystemProperty(key, value); err != nil {
70
+			return err
71
+		}
72
+	}
73
+
67 74
 	for _, path := range l.config.Config.ReadonlyPaths {
68 75
 		if err := remountReadonly(path); err != nil {
69 76
 			return err
... ...
@@ -12,8 +12,10 @@ import (
12 12
 // We are declaring the macro here because the SETNS syscall does not exist in th stdlib
13 13
 var setNsMap = map[string]uintptr{
14 14
 	"linux/386":     346,
15
+	"linux/arm64":   268,
15 16
 	"linux/amd64":   308,
16
-	"linux/arm":     374,
17
+	"linux/arm":     375,
18
+	"linux/ppc":     350,
17 19
 	"linux/ppc64":   350,
18 20
 	"linux/ppc64le": 350,
19 21
 	"linux/s390x":   339,
... ...
@@ -1,4 +1,4 @@
1
-// +build linux,amd64 linux,ppc64 linux,ppc64le linux,s390x
1
+// +build linux,arm64 linux,amd64 linux,ppc linux,ppc64 linux,ppc64le linux,s390x
2 2
 
3 3
 package system
4 4
 
... ...
@@ -43,7 +43,7 @@ clone() {
43 43
 clone git github.com/codegangsta/cli 1.1.0
44 44
 clone git github.com/coreos/go-systemd v2
45 45
 clone git github.com/godbus/dbus v2
46
-clone git github.com/Sirupsen/logrus v0.6.6
46
+clone git github.com/Sirupsen/logrus v0.7.3
47 47
 clone git github.com/syndtr/gocapability 8e4cdcb
48 48
 
49 49
 # intentionally not vendoring Docker itself...  that'd be a circle :)