Merge pull request #682 from vmarmol/update-libcontainer

Update libcontainer godep
This commit is contained in:
Rohit Jnagal 2015-05-05 14:49:54 -07:00
commit ed19fa9c98
78 changed files with 1974 additions and 700 deletions

6
Godeps/Godeps.json generated
View File

@ -97,8 +97,8 @@
}, },
{ {
"ImportPath": "github.com/docker/libcontainer", "ImportPath": "github.com/docker/libcontainer",
"Comment": "v1.4.0-412-g4ea9039", "Comment": "v1.4.0-501-ga1fe3f1",
"Rev": "4ea9039ff269fc8675409df7f1a192c94eab2f52" "Rev": "a1fe3f1c7ad2e8eebe6d59e573f04d2b10961cf6"
}, },
{ {
"ImportPath": "github.com/fsouza/go-dockerclient", "ImportPath": "github.com/fsouza/go-dockerclient",
@ -178,7 +178,7 @@
}, },
{ {
"ImportPath": "github.com/syndtr/gocapability/capability", "ImportPath": "github.com/syndtr/gocapability/capability",
"Rev": "3c85049eaeb429febe7788d9c7aac42322a377fe" "Rev": "8e4cdcb3c22b40d5e330ade0b68cb2e2a3cf6f98"
}, },
{ {
"ImportPath": "golang.org/x/exp/inotify", "ImportPath": "golang.org/x/exp/inotify",

View File

@ -1 +1,2 @@
bundles
nsinit/nsinit nsinit/nsinit

View File

@ -29,3 +29,5 @@ local:
validate: validate:
hack/validate.sh hack/validate.sh
binary: all
docker run --rm --privileged -v $(CURDIR)/bundles:/go/bin dockercore/libcontainer make direct-install

View File

@ -141,6 +141,9 @@ container.Resume()
It is able to spawn new containers or join existing containers. A root It is able to spawn new containers or join existing containers. A root
filesystem must be provided for use along with a container configuration file. filesystem must be provided for use along with a container configuration file.
To build `nsinit`, run `make binary`. It will save the binary into
`bundles/nsinit`.
To use `nsinit`, cd into a Linux rootfs and copy a `container.json` file into To use `nsinit`, cd into a Linux rootfs and copy a `container.json` file into
the directory with your specified configuration. Environment, networking, the directory with your specified configuration. Environment, networking,
and different capabilities for the container are specified in this file. and different capabilities for the container are specified in this file.

View File

@ -14,9 +14,11 @@ import (
func IsEnabled() bool { func IsEnabled() bool {
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" { if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled") buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
return err == nil && len(buf) > 1 && buf[0] == 'Y' return err == nil && len(buf) > 1 && buf[0] == 'Y'
} }
}
return false return false
} }

View File

@ -67,12 +67,12 @@ func generateProfile(out io.Writer) error {
data := &data{ data := &data{
Name: "docker-default", Name: "docker-default",
} }
if tuntablesExists() { if tunablesExists() {
data.Imports = append(data.Imports, "#include <tunables/global>") data.Imports = append(data.Imports, "#include <tunables/global>")
} else { } else {
data.Imports = append(data.Imports, "@{PROC}=/proc/") data.Imports = append(data.Imports, "@{PROC}=/proc/")
} }
if abstrctionsEsists() { if abstractionsExists() {
data.InnerImports = append(data.InnerImports, "#include <abstractions/base>") data.InnerImports = append(data.InnerImports, "#include <abstractions/base>")
} }
if err := compiled.Execute(out, data); err != nil { if err := compiled.Execute(out, data); err != nil {
@ -82,13 +82,13 @@ func generateProfile(out io.Writer) error {
} }
// check if the tunables/global exist // check if the tunables/global exist
func tuntablesExists() bool { func tunablesExists() bool {
_, err := os.Stat("/etc/apparmor.d/tunables/global") _, err := os.Stat("/etc/apparmor.d/tunables/global")
return err == nil return err == nil
} }
// check if abstractions/base exist // check if abstractions/base exist
func abstrctionsEsists() bool { func abstractionsExists() bool {
_, err := os.Stat("/etc/apparmor.d/abstractions/base") _, err := os.Stat("/etc/apparmor.d/abstractions/base")
return err == nil return err == nil
} }

View File

@ -49,6 +49,7 @@ var capabilityList = map[string]capability.Cap{
"SETFCAP": capability.CAP_SETFCAP, "SETFCAP": capability.CAP_SETFCAP,
"WAKE_ALARM": capability.CAP_WAKE_ALARM, "WAKE_ALARM": capability.CAP_WAKE_ALARM,
"BLOCK_SUSPEND": capability.CAP_BLOCK_SUSPEND, "BLOCK_SUSPEND": capability.CAP_BLOCK_SUSPEND,
"AUDIT_READ": capability.CAP_AUDIT_READ,
} }
func newCapWhitelist(caps []string) (*whitelist, error) { func newCapWhitelist(caps []string) (*whitelist, error) {

View File

@ -34,9 +34,6 @@ type Manager interface {
// Set the cgroup as configured. // Set the cgroup as configured.
Set(container *configs.Config) error Set(container *configs.Config) error
// Enters the specified process into these cgroups.
EnterProcess(pid int) error
} }
type NotFoundError struct { type NotFoundError struct {

View File

@ -1,6 +1,8 @@
package fs package fs
import ( import (
"fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -19,6 +21,7 @@ var (
"cpuset": &CpusetGroup{}, "cpuset": &CpusetGroup{},
"cpuacct": &CpuacctGroup{}, "cpuacct": &CpuacctGroup{},
"blkio": &BlkioGroup{}, "blkio": &BlkioGroup{},
"hugetlb": &HugetlbGroup{},
"perf_event": &PerfEventGroup{}, "perf_event": &PerfEventGroup{},
"freezer": &FreezerGroup{}, "freezer": &FreezerGroup{},
} }
@ -75,10 +78,13 @@ type data struct {
} }
func (m *Manager) Apply(pid int) error { func (m *Manager) Apply(pid int) error {
if m.Cgroups == nil { if m.Cgroups == nil {
return nil return nil
} }
var c = m.Cgroups
d, err := getCgroupData(m.Cgroups, pid) d, err := getCgroupData(m.Cgroups, pid)
if err != nil { if err != nil {
return err return err
@ -108,6 +114,12 @@ func (m *Manager) Apply(pid int) error {
} }
m.Paths = paths m.Paths = paths
if paths["cpu"] != "" {
if err := CheckCpushares(paths["cpu"], c.CpuShares); err != nil {
return err
}
}
return nil return nil
} }
@ -119,19 +131,6 @@ func (m *Manager) GetPaths() map[string]string {
return m.Paths return m.Paths
} }
// Symmetrical public function to update device based cgroups. Also available
// in the systemd implementation.
func ApplyDevices(c *configs.Cgroup, pid int) error {
d, err := getCgroupData(c, pid)
if err != nil {
return err
}
devices := subsystems["devices"]
return devices.Apply(d)
}
func (m *Manager) GetStats() (*cgroups.Stats, error) { func (m *Manager) GetStats() (*cgroups.Stats, error) {
stats := cgroups.NewStats() stats := cgroups.NewStats()
for name, path := range m.Paths { for name, path := range m.Paths {
@ -161,20 +160,6 @@ func (m *Manager) Set(container *configs.Config) error {
return nil return nil
} }
func (m *Manager) EnterProcess(pid int) error {
for name, path := range m.Paths {
_, ok := subsystems[name]
if !ok || !cgroups.PathExists(path) {
continue
}
if err := writeFile(path, CgroupProcesses, strconv.Itoa(pid)); err != nil {
return err
}
}
return nil
}
// Freeze toggles the container's freezer cgroup depending on the state // Freeze toggles the container's freezer cgroup depending on the state
// provided // provided
func (m *Manager) Freeze(state configs.FreezerState) error { func (m *Manager) Freeze(state configs.FreezerState) error {
@ -294,3 +279,27 @@ func removePath(p string, err error) error {
} }
return nil return nil
} }
func CheckCpushares(path string, c int64) error {
var cpuShares int64
fd, err := os.Open(filepath.Join(path, "cpu.shares"))
if err != nil {
return err
}
defer fd.Close()
_, err = fmt.Fscanf(fd, "%d", &cpuShares)
if err != nil && err != io.EOF {
return err
}
if c != 0 {
if c > cpuShares {
return fmt.Errorf("The maximum allowed cpu-shares is %d", cpuShares)
} else if c < cpuShares {
return fmt.Errorf("The minimum allowed cpu-shares is %d", cpuShares)
}
}
return nil
}

View File

@ -35,6 +35,32 @@ func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error {
} }
} }
if cgroup.BlkioWeightDevice != "" {
if err := writeFile(path, "blkio.weight_device", cgroup.BlkioWeightDevice); err != nil {
return err
}
}
if cgroup.BlkioThrottleReadBpsDevice != "" {
if err := writeFile(path, "blkio.throttle.read_bps_device", cgroup.BlkioThrottleReadBpsDevice); err != nil {
return err
}
}
if cgroup.BlkioThrottleWriteBpsDevice != "" {
if err := writeFile(path, "blkio.throttle.write_bps_device", cgroup.BlkioThrottleWriteBpsDevice); err != nil {
return err
}
}
if cgroup.BlkioThrottleReadIOpsDevice != "" {
if err := writeFile(path, "blkio.throttle.read_iops_device", cgroup.BlkioThrottleReadIOpsDevice); err != nil {
return err
}
}
if cgroup.BlkioThrottleWriteIOpsDevice != "" {
if err := writeFile(path, "blkio.throttle.write_iops_device", cgroup.BlkioThrottleWriteIOpsDevice); err != nil {
return err
}
}
return nil return nil
} }

View File

@ -67,6 +67,8 @@ Total 22061056`
252:0 Async 164 252:0 Async 164
252:0 Total 164 252:0 Total 164
Total 328` Total 328`
throttleBefore = `8:0 1024`
throttleAfter = `8:0 2048`
) )
func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) { func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) {
@ -102,6 +104,35 @@ func TestBlkioSetWeight(t *testing.T) {
} }
} }
func TestBlkioSetWeightDevice(t *testing.T) {
helper := NewCgroupTestUtil("blkio", t)
defer helper.cleanup()
const (
weightDeviceBefore = "8:0 400"
weightDeviceAfter = "8:0 500"
)
helper.writeFileContents(map[string]string{
"blkio.weight_device": weightDeviceBefore,
})
helper.CgroupData.c.BlkioWeightDevice = weightDeviceAfter
blkio := &BlkioGroup{}
if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
t.Fatal(err)
}
value, err := getCgroupParamString(helper.CgroupPath, "blkio.weight_device")
if err != nil {
t.Fatalf("Failed to parse blkio.weight_device - %s", err)
}
if value != weightDeviceAfter {
t.Fatal("Got the wrong value, set blkio.weight_device failed.")
}
}
func TestBlkioStats(t *testing.T) { func TestBlkioStats(t *testing.T) {
helper := NewCgroupTestUtil("blkio", t) helper := NewCgroupTestUtil("blkio", t)
defer helper.cleanup() defer helper.cleanup()
@ -442,3 +473,96 @@ func TestNonCFQBlkioStats(t *testing.T) {
expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats) expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
} }
func TestBlkioSetThrottleReadBpsDevice(t *testing.T) {
helper := NewCgroupTestUtil("blkio", t)
defer helper.cleanup()
helper.writeFileContents(map[string]string{
"blkio.throttle.read_bps_device": throttleBefore,
})
helper.CgroupData.c.BlkioThrottleReadBpsDevice = throttleAfter
blkio := &BlkioGroup{}
if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
t.Fatal(err)
}
value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_bps_device")
if err != nil {
t.Fatalf("Failed to parse blkio.throttle.read_bps_device - %s", err)
}
if value != throttleAfter {
t.Fatal("Got the wrong value, set blkio.throttle.read_bps_device failed.")
}
}
func TestBlkioSetThrottleWriteBpsDevice(t *testing.T) {
helper := NewCgroupTestUtil("blkio", t)
defer helper.cleanup()
helper.writeFileContents(map[string]string{
"blkio.throttle.write_bps_device": throttleBefore,
})
helper.CgroupData.c.BlkioThrottleWriteBpsDevice = throttleAfter
blkio := &BlkioGroup{}
if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
t.Fatal(err)
}
value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_bps_device")
if err != nil {
t.Fatalf("Failed to parse blkio.throttle.write_bps_device - %s", err)
}
if value != throttleAfter {
t.Fatal("Got the wrong value, set blkio.throttle.write_bps_device failed.")
}
}
func TestBlkioSetThrottleReadIOpsDevice(t *testing.T) {
helper := NewCgroupTestUtil("blkio", t)
defer helper.cleanup()
helper.writeFileContents(map[string]string{
"blkio.throttle.read_iops_device": throttleBefore,
})
helper.CgroupData.c.BlkioThrottleReadIOpsDevice = throttleAfter
blkio := &BlkioGroup{}
if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
t.Fatal(err)
}
value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_iops_device")
if err != nil {
t.Fatalf("Failed to parse blkio.throttle.read_iops_device - %s", err)
}
if value != throttleAfter {
t.Fatal("Got the wrong value, set blkio.throttle.read_iops_device failed.")
}
}
func TestBlkioSetThrottleWriteIOpsDevice(t *testing.T) {
helper := NewCgroupTestUtil("blkio", t)
defer helper.cleanup()
helper.writeFileContents(map[string]string{
"blkio.throttle.write_iops_device": throttleBefore,
})
helper.CgroupData.c.BlkioThrottleWriteIOpsDevice = throttleAfter
blkio := &BlkioGroup{}
if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
t.Fatal(err)
}
value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_iops_device")
if err != nil {
t.Fatalf("Failed to parse blkio.throttle.write_iops_device - %s", err)
}
if value != throttleAfter {
t.Fatal("Got the wrong value, set blkio.throttle.write_iops_device failed.")
}
}

View File

@ -32,6 +32,17 @@ func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error {
return err return err
} }
} }
return nil
}
if err := writeFile(path, "devices.allow", "a"); err != nil {
return err
}
for _, dev := range cgroup.DeniedDevices {
if err := writeFile(path, "devices.deny", dev.CgroupString()); err != nil {
return err
}
} }
return nil return nil

View File

@ -18,6 +18,17 @@ var (
}, },
} }
allowedList = "c 1:5 rwm" allowedList = "c 1:5 rwm"
deniedDevices = []*configs.Device{
{
Path: "/dev/null",
Type: 'c',
Major: 1,
Minor: 3,
Permissions: "rwm",
FileMode: 0666,
},
}
deniedList = "c 1:3 rwm"
) )
func TestDevicesSetAllow(t *testing.T) { func TestDevicesSetAllow(t *testing.T) {
@ -44,3 +55,28 @@ func TestDevicesSetAllow(t *testing.T) {
t.Fatal("Got the wrong value, set devices.allow failed.") t.Fatal("Got the wrong value, set devices.allow failed.")
} }
} }
func TestDevicesSetDeny(t *testing.T) {
helper := NewCgroupTestUtil("devices", t)
defer helper.cleanup()
helper.writeFileContents(map[string]string{
"devices.allow": "a",
})
helper.CgroupData.c.AllowAllDevices = true
helper.CgroupData.c.DeniedDevices = deniedDevices
devices := &DevicesGroup{}
if err := devices.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
t.Fatal(err)
}
value, err := getCgroupParamString(helper.CgroupPath, "devices.deny")
if err != nil {
t.Fatalf("Failed to parse devices.deny - %s", err)
}
if value != deniedList {
t.Fatal("Got the wrong value, set devices.deny failed.")
}
}

View File

@ -0,0 +1,29 @@
package fs
import (
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/configs"
)
type HugetlbGroup struct {
}
func (s *HugetlbGroup) Apply(d *data) error {
// we just want to join this group even though we don't set anything
if _, err := d.join("hugetlb"); err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
}
func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *HugetlbGroup) Remove(d *data) error {
return removePath(d.path("hugetlb"))
}
func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View File

@ -95,6 +95,7 @@ func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
return fmt.Errorf("failed to parse memory.usage_in_bytes - %v", err) return fmt.Errorf("failed to parse memory.usage_in_bytes - %v", err)
} }
stats.MemoryStats.Usage = value stats.MemoryStats.Usage = value
stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]
value, err = getCgroupParamUint(path, "memory.max_usage_in_bytes") value, err = getCgroupParamUint(path, "memory.max_usage_in_bytes")
if err != nil { if err != nil {
return fmt.Errorf("failed to parse memory.max_usage_in_bytes - %v", err) return fmt.Errorf("failed to parse memory.max_usage_in_bytes - %v", err)

View File

@ -128,7 +128,7 @@ func TestMemoryStats(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expectedStats := cgroups.MemoryStats{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Stats: map[string]uint64{"cache": 512, "rss": 1024}} expectedStats := cgroups.MemoryStats{Usage: 2048, Cache: 512, MaxUsage: 4096, Failcnt: 100, Stats: map[string]uint64{"cache": 512, "rss": 1024}}
expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats) expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats)
} }

View File

@ -2,9 +2,9 @@ package fs
import ( import (
"fmt" "fmt"
"log"
"testing" "testing"
log "github.com/Sirupsen/logrus"
"github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/cgroups"
) )

View File

@ -33,6 +33,8 @@ type CpuStats struct {
type MemoryStats struct { type MemoryStats struct {
// current res_counter usage for memory // current res_counter usage for memory
Usage uint64 `json:"usage,omitempty"` Usage uint64 `json:"usage,omitempty"`
// memory used for cache
Cache uint64 `json:"cache,omitempty"`
// maximum usage ever recorded. // maximum usage ever recorded.
MaxUsage uint64 `json:"max_usage,omitempty"` MaxUsage uint64 `json:"max_usage,omitempty"`
// TODO(vishh): Export these as stronger types. // TODO(vishh): Export these as stronger types.

View File

@ -42,18 +42,10 @@ func (m *Manager) Set(container *configs.Config) error {
return nil, fmt.Errorf("Systemd not supported") return nil, fmt.Errorf("Systemd not supported")
} }
func (m *Manager) EnterProcess(pid int) error {
return nil, fmt.Errorf("Systemd not supported")
}
func (m *Manager) Freeze(state configs.FreezerState) error { func (m *Manager) Freeze(state configs.FreezerState) error {
return fmt.Errorf("Systemd not supported") return fmt.Errorf("Systemd not supported")
} }
func ApplyDevices(c *configs.Cgroup, pid int) error {
return fmt.Errorf("Systemd not supported")
}
func Freeze(c *configs.Cgroup, state configs.FreezerState) error { func Freeze(c *configs.Cgroup, state configs.FreezerState) error {
return fmt.Errorf("Systemd not supported") return fmt.Errorf("Systemd not supported")
} }

View File

@ -3,7 +3,6 @@
package systemd package systemd
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -39,6 +38,7 @@ var subsystems = map[string]subsystem{
"cpuset": &fs.CpusetGroup{}, "cpuset": &fs.CpusetGroup{},
"cpuacct": &fs.CpuacctGroup{}, "cpuacct": &fs.CpuacctGroup{},
"blkio": &fs.BlkioGroup{}, "blkio": &fs.BlkioGroup{},
"hugetlb": &fs.HugetlbGroup{},
"perf_event": &fs.PerfEventGroup{}, "perf_event": &fs.PerfEventGroup{},
"freezer": &fs.FreezerGroup{}, "freezer": &fs.FreezerGroup{},
} }
@ -217,6 +217,13 @@ func (m *Manager) Apply(pid int) error {
return err return err
} }
// FIXME: Systemd does have `BlockIODeviceWeight` property, but we got problem
// using that (at least on systemd 208, see https://github.com/docker/libcontainer/pull/354),
// so use fs work around for now.
if err := joinBlkio(c, pid); err != nil {
return err
}
paths := make(map[string]string) paths := make(map[string]string)
for sysname := range subsystems { for sysname := range subsystems {
subsystemPath, err := getSubsystemPath(m.Cgroups, sysname) subsystemPath, err := getSubsystemPath(m.Cgroups, sysname)
@ -229,9 +236,14 @@ func (m *Manager) Apply(pid int) error {
} }
paths[sysname] = subsystemPath paths[sysname] = subsystemPath
} }
m.Paths = paths m.Paths = paths
if paths["cpu"] != "" {
if err := fs.CheckCpushares(paths["cpu"], c.CpuShares); err != nil {
return err
}
}
return nil return nil
} }
@ -247,6 +259,21 @@ func writeFile(dir, file, data string) error {
return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700)
} }
func join(c *configs.Cgroup, subsystem string, pid int) (string, error) {
path, err := getSubsystemPath(c, subsystem)
if err != nil {
return "", err
}
if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
return "", err
}
if err := writeFile(path, "cgroup.procs", strconv.Itoa(pid)); err != nil {
return "", err
}
return path, nil
}
func joinCpu(c *configs.Cgroup, pid int) error { func joinCpu(c *configs.Cgroup, pid int) error {
path, err := getSubsystemPath(c, "cpu") path, err := getSubsystemPath(c, "cpu")
if err != nil { if err != nil {
@ -266,16 +293,11 @@ func joinCpu(c *configs.Cgroup, pid int) error {
} }
func joinFreezer(c *configs.Cgroup, pid int) error { func joinFreezer(c *configs.Cgroup, pid int) error {
path, err := getSubsystemPath(c, "freezer") if _, err := join(c, "freezer", pid); err != nil {
if err != nil {
return err return err
} }
if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { return nil
return err
}
return ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700)
} }
func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) { func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) {
@ -303,22 +325,16 @@ func (m *Manager) Freeze(state configs.FreezerState) error {
return err return err
} }
if err := ioutil.WriteFile(filepath.Join(path, "freezer.state"), []byte(state), 0); err != nil { prevState := m.Cgroups.Freezer
return err
}
for {
state_, err := ioutil.ReadFile(filepath.Join(path, "freezer.state"))
if err != nil {
return err
}
if string(state) == string(bytes.TrimSpace(state_)) {
break
}
time.Sleep(1 * time.Millisecond)
}
m.Cgroups.Freezer = state m.Cgroups.Freezer = state
freezer := subsystems["freezer"]
err = freezer.Set(path, m.Cgroups)
if err != nil {
m.Cgroups.Freezer = prevState
return err
}
return nil return nil
} }
@ -347,16 +363,12 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) {
} }
func (m *Manager) Set(container *configs.Config) error { func (m *Manager) Set(container *configs.Config) error {
panic("not implemented")
}
func (m *Manager) EnterProcess(pid int) error {
for name, path := range m.Paths { for name, path := range m.Paths {
_, ok := subsystems[name] sys, ok := subsystems[name]
if !ok || !cgroups.PathExists(path) { if !ok || !cgroups.PathExists(path) {
continue continue
} }
if err := writeFile(path, fs.CgroupProcesses, strconv.Itoa(pid)); err != nil { if err := sys.Set(path, container.Cgroups); err != nil {
return err return err
} }
} }
@ -373,43 +385,20 @@ func getUnitName(c *configs.Cgroup) string {
// * Support for wildcards to allow /dev/pts support // * Support for wildcards to allow /dev/pts support
// //
// The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is // The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is
// in wide use. When both these are availalable we will be able to switch, but need to keep the old // in wide use. When both these are available we will be able to switch, but need to keep the old
// implementation for backwards compat. // implementation for backwards compat.
// //
// Note: we can't use systemd to set up the initial limits, and then change the cgroup // Note: we can't use systemd to set up the initial limits, and then change the cgroup
// because systemd will re-write the device settings if it needs to re-apply the cgroup context. // because systemd will re-write the device settings if it needs to re-apply the cgroup context.
// This happens at least for v208 when any sibling unit is started. // This happens at least for v208 when any sibling unit is started.
func joinDevices(c *configs.Cgroup, pid int) error { func joinDevices(c *configs.Cgroup, pid int) error {
path, err := getSubsystemPath(c, "devices") path, err := join(c, "devices", pid)
if err != nil { if err != nil {
return err return err
} }
if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { devices := subsystems["devices"]
return err return devices.Set(path, c)
}
if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil {
return err
}
if !c.AllowAllDevices {
if err := writeFile(path, "devices.deny", "a"); err != nil {
return err
}
}
for _, dev := range c.AllowedDevices {
if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil {
return err
}
}
return nil
}
// Symmetrical public function to update device based cgroups. Also available
// in the fs implementation.
func ApplyDevices(c *configs.Cgroup, pid int) error {
return joinDevices(c, pid)
} }
func joinMemory(c *configs.Cgroup, pid int) error { func joinMemory(c *configs.Cgroup, pid int) error {
@ -441,3 +430,40 @@ func joinCpuset(c *configs.Cgroup, pid int) error {
return s.ApplyDir(path, c, pid) return s.ApplyDir(path, c, pid)
} }
// `BlockIODeviceWeight` property of systemd does not work properly, and systemd
// expects device path instead of major minor numbers, which is also confusing
// for users. So we use fs work around for now.
func joinBlkio(c *configs.Cgroup, pid int) error {
path, err := getSubsystemPath(c, "blkio")
if err != nil {
return err
}
if c.BlkioWeightDevice != "" {
if err := writeFile(path, "blkio.weight_device", c.BlkioWeightDevice); err != nil {
return err
}
}
if c.BlkioThrottleReadBpsDevice != "" {
if err := writeFile(path, "blkio.throttle.read_bps_device", c.BlkioThrottleReadBpsDevice); err != nil {
return err
}
}
if c.BlkioThrottleWriteBpsDevice != "" {
if err := writeFile(path, "blkio.throttle.write_bps_device", c.BlkioThrottleWriteBpsDevice); err != nil {
return err
}
}
if c.BlkioThrottleReadIOpsDevice != "" {
if err := writeFile(path, "blkio.throttle.read_iops_device", c.BlkioThrottleReadIOpsDevice); err != nil {
return err
}
}
if c.BlkioThrottleWriteIOpsDevice != "" {
if err := writeFile(path, "blkio.throttle.write_iops_device", c.BlkioThrottleWriteIOpsDevice); err != nil {
return err
}
}
return nil
}

View File

@ -19,6 +19,8 @@ type Cgroup struct {
AllowedDevices []*Device `json:"allowed_devices"` AllowedDevices []*Device `json:"allowed_devices"`
DeniedDevices []*Device `json:"denied_devices"`
// Memory limit (in bytes) // Memory limit (in bytes)
Memory int64 `json:"memory"` Memory int64 `json:"memory"`
@ -43,9 +45,24 @@ type Cgroup struct {
// MEM to use // MEM to use
CpusetMems string `json:"cpuset_mems"` CpusetMems string `json:"cpuset_mems"`
// IO read rate limit per cgroup per device, bytes per second.
BlkioThrottleReadBpsDevice string `json:"blkio_throttle_read_bps_device"`
// IO write rate limit per cgroup per divice, bytes per second.
BlkioThrottleWriteBpsDevice string `json:"blkio_throttle_write_bps_device"`
// IO read rate limit per cgroup per device, IO per second.
BlkioThrottleReadIOpsDevice string `json:"blkio_throttle_read_iops_device"`
// IO write rate limit per cgroup per device, IO per second.
BlkioThrottleWriteIOpsDevice string `json:"blkio_throttle_write_iops_device"`
// Specifies per cgroup weight, range is from 10 to 1000. // Specifies per cgroup weight, range is from 10 to 1000.
BlkioWeight int64 `json:"blkio_weight"` BlkioWeight int64 `json:"blkio_weight"`
// Weight per cgroup per device, can override BlkioWeight.
BlkioWeightDevice string `json:"blkio_weight_device"`
// set the freeze value for the process // set the freeze value for the process
Freezer FreezerState `json:"freezer"` Freezer FreezerState `json:"freezer"`

View File

@ -37,6 +37,9 @@ type Config struct {
// bind mounts are writtable. // bind mounts are writtable.
Readonlyfs bool `json:"readonlyfs"` Readonlyfs bool `json:"readonlyfs"`
// Privatefs will mount the container's rootfs as private where mount points from the parent will not propogate
Privatefs bool `json:"privatefs"`
// Mounts specify additional source and destination paths that will be mounted inside the container's // Mounts specify additional source and destination paths that will be mounted inside the container's
// rootfs and mount namespace if specified // rootfs and mount namespace if specified
Mounts []*Mount `json:"mounts"` Mounts []*Mount `json:"mounts"`
@ -96,6 +99,10 @@ type Config struct {
// ReadonlyPaths specifies paths within the container's rootfs to remount as read-only // ReadonlyPaths specifies paths within the container's rootfs to remount as read-only
// so that these files prevent any writes. // so that these files prevent any writes.
ReadonlyPaths []string `json:"readonly_paths"` ReadonlyPaths []string `json:"readonly_paths"`
// SystemProperties is a map of properties and their values. It is the equivalent of using
// sysctl -w my.property.name value in Linux.
SystemProperties map[string]string `json:"system_properties"`
} }
// Gets the root uid for the process on host which could be non-zero // Gets the root uid for the process on host which could be non-zero

View File

@ -18,4 +18,17 @@ type Mount struct {
// Relabel source if set, "z" indicates shared, "Z" indicates unshared. // Relabel source if set, "z" indicates shared, "Z" indicates unshared.
Relabel string `json:"relabel"` Relabel string `json:"relabel"`
// Optional Command to be run before Source is mounted.
PremountCmds []Command `json:"premount_cmds"`
// Optional Command to be run after Source is mounted.
PostmountCmds []Command `json:"postmount_cmds"`
}
type Command struct {
Path string `json:"path"`
Args []string `json:"args"`
Env []string `json:"env"`
Dir string `json:"dir"`
} }

View File

@ -1,9 +1,6 @@
package configs package configs
import ( import "fmt"
"fmt"
"syscall"
)
type NamespaceType string type NamespaceType string
@ -16,6 +13,17 @@ const (
NEWUSER NamespaceType = "NEWUSER" NEWUSER NamespaceType = "NEWUSER"
) )
func NamespaceTypes() []NamespaceType {
return []NamespaceType{
NEWNET,
NEWPID,
NEWNS,
NEWUTS,
NEWIPC,
NEWUSER,
}
}
// Namespace defines configuration for each namespace. It specifies an // Namespace defines configuration for each namespace. It specifies an
// alternate path that is able to be joined via setns. // alternate path that is able to be joined via setns.
type Namespace struct { type Namespace struct {
@ -23,10 +31,6 @@ type Namespace struct {
Path string `json:"path"` Path string `json:"path"`
} }
func (n *Namespace) Syscall() int {
return namespaceInfo[n.Type]
}
func (n *Namespace) GetPath(pid int) string { func (n *Namespace) GetPath(pid int) string {
if n.Path != "" { if n.Path != "" {
return n.Path return n.Path
@ -85,25 +89,3 @@ func (n *Namespaces) index(t NamespaceType) int {
func (n *Namespaces) Contains(t NamespaceType) bool { func (n *Namespaces) Contains(t NamespaceType) bool {
return n.index(t) != -1 return n.index(t) != -1
} }
var namespaceInfo = map[NamespaceType]int{
NEWNET: syscall.CLONE_NEWNET,
NEWNS: syscall.CLONE_NEWNS,
NEWUSER: syscall.CLONE_NEWUSER,
NEWIPC: syscall.CLONE_NEWIPC,
NEWUTS: syscall.CLONE_NEWUTS,
NEWPID: syscall.CLONE_NEWPID,
}
// CloneFlags parses the container's Namespaces options to set the correct
// flags on clone, unshare. This functions returns flags only for new namespaces.
func (n *Namespaces) CloneFlags() uintptr {
var flag int
for _, v := range *n {
if v.Path != "" {
continue
}
flag |= namespaceInfo[v.Type]
}
return uintptr(flag)
}

View File

@ -0,0 +1,31 @@
// +build linux
package configs
import "syscall"
func (n *Namespace) Syscall() int {
return namespaceInfo[n.Type]
}
var namespaceInfo = map[NamespaceType]int{
NEWNET: syscall.CLONE_NEWNET,
NEWNS: syscall.CLONE_NEWNS,
NEWUSER: syscall.CLONE_NEWUSER,
NEWIPC: syscall.CLONE_NEWIPC,
NEWUTS: syscall.CLONE_NEWUTS,
NEWPID: syscall.CLONE_NEWPID,
}
// CloneFlags parses the container's Namespaces options to set the correct
// flags on clone, unshare. This functions returns flags only for new namespaces.
func (n *Namespaces) CloneFlags() uintptr {
var flag int
for _, v := range *n {
if v.Path != "" {
continue
}
flag |= namespaceInfo[v.Type]
}
return uintptr(flag)
}

View File

@ -0,0 +1,15 @@
// +build !linux
package configs
func (n *Namespace) Syscall() int {
panic("No namespace syscall support")
return 0
}
// CloneFlags parses the container's Namespaces options to set the correct
// flags on clone, unshare. This functions returns flags only for new namespaces.
func (n *Namespaces) CloneFlags() uintptr {
panic("No namespace syscall support")
return uintptr(0)
}

View File

@ -2,7 +2,7 @@ package configs
// Network defines configuration for a container's networking stack // Network defines configuration for a container's networking stack
// //
// The network configuration can be omited from a container causing the // The network configuration can be omitted from a container causing the
// container to be setup with the host's networking stack // container to be setup with the host's networking stack
type Network struct { type Network struct {
// Type sets the networks type, commonly veth and loopback // Type sets the networks type, commonly veth and loopback
@ -53,7 +53,7 @@ type Network struct {
// Routes can be specified to create entries in the route table as the container is started // Routes can be specified to create entries in the route table as the container is started
// //
// All of destination, source, and gateway should be either IPv4 or IPv6. // All of destination, source, and gateway should be either IPv4 or IPv6.
// One of the three options must be present, and ommitted entries will use their // One of the three options must be present, and omitted entries will use their
// IP family default for the route table. For IPv4 for example, setting the // IP family default for the route table. For IPv4 for example, setting the
// gateway to 1.2.3.4 and the interface to eth0 will set up a standard // gateway to 1.2.3.4 and the interface to eth0 will set up a standard
// destination of 0.0.0.0(or *) when viewed in the route table. // destination of 0.0.0.0(or *) when viewed in the route table.

View File

@ -38,7 +38,7 @@ func newConsole(uid, gid int) (Console, error) {
}, nil }, nil
} }
// newConsoleFromPath is an internal fucntion returning an initialzied console for use inside // newConsoleFromPath is an internal function returning an initialized console for use inside
// a container's MNT namespace. // a container's MNT namespace.
func newConsoleFromPath(slavePath string) *linuxConsole { func newConsoleFromPath(slavePath string) *linuxConsole {
return &linuxConsole{ return &linuxConsole{

View File

@ -67,7 +67,7 @@ type Container interface {
// State returns the current container's state information. // State returns the current container's state information.
// //
// errors: // errors:
// Systemerror - System erroor. // Systemerror - System error.
State() (*State, error) State() (*State, error)
// Returns the current config of the container. // Returns the current config of the container.
@ -108,14 +108,6 @@ type Container interface {
// Systemerror - System error. // Systemerror - System error.
Start(process *Process) (err error) Start(process *Process) (err error)
// Charge the resource usage of the specified process to this container.
// Does not join the namespaces of the container.
//
// errors:
// ContainerDestroyed - Container no longer exists,
// Systemerror - System error.
ChargeProcess(pid int) error
// Destroys the container after killing all running processes. // Destroys the container after killing all running processes.
// //
// Any event registrations are removed before the container is destroyed. // Any event registrations are removed before the container is destroyed.

View File

@ -16,6 +16,8 @@ import (
"github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/configs"
) )
const stdioFdCount = 3
type linuxContainer struct { type linuxContainer struct {
id string id string
root string root string
@ -112,10 +114,6 @@ func (c *linuxContainer) Start(process *Process) error {
return nil return nil
} }
func (c *linuxContainer) ChargeProcess(pid int) error {
return c.cgroupManager.EnterProcess(pid)
}
func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) { func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) {
parentPipe, childPipe, err := newPipe() parentPipe, childPipe, err := newPipe()
if err != nil { if err != nil {
@ -143,8 +141,11 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
if cmd.SysProcAttr == nil { if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{} cmd.SysProcAttr = &syscall.SysProcAttr{}
} }
cmd.ExtraFiles = []*os.File{childPipe} cmd.ExtraFiles = append(p.ExtraFiles, childPipe)
cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1))
// NOTE: when running a container with no PID namespace and the parent process spawning the container is
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
// even with the parent still running.
if c.config.ParentDeathSignal > 0 { if c.config.ParentDeathSignal > 0 {
cmd.SysProcAttr.Pdeathsig = syscall.Signal(c.config.ParentDeathSignal) cmd.SysProcAttr.Pdeathsig = syscall.Signal(c.config.ParentDeathSignal)
} }
@ -180,11 +181,9 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
fmt.Sprintf("_LIBCONTAINER_INITPID=%d", c.initProcess.pid()), fmt.Sprintf("_LIBCONTAINER_INITPID=%d", c.initProcess.pid()),
"_LIBCONTAINER_INITTYPE=setns", "_LIBCONTAINER_INITTYPE=setns",
) )
if p.consolePath != "" { if p.consolePath != "" {
cmd.Env = append(cmd.Env, "_LIBCONTAINER_CONSOLE_PATH="+p.consolePath) cmd.Env = append(cmd.Env, "_LIBCONTAINER_CONSOLE_PATH="+p.consolePath)
} }
// TODO: set on container for process management // TODO: set on container for process management
return &setnsProcess{ return &setnsProcess{
cmd: cmd, cmd: cmd,
@ -204,6 +203,7 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
Cwd: process.Cwd, Cwd: process.Cwd,
Console: process.consolePath, Console: process.consolePath,
Capabilities: process.Capabilities, Capabilities: process.Capabilities,
PassedFilesCount: len(process.ExtraFiles),
} }
} }
@ -308,5 +308,11 @@ func (c *linuxContainer) currentState() (*State, error) {
for _, ns := range c.config.Namespaces { for _, ns := range c.config.Namespaces {
state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid()) state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid())
} }
for _, nsType := range configs.NamespaceTypes() {
if _, ok := state.NamespacePaths[nsType]; !ok {
ns := configs.Namespace{Type: nsType}
state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid())
}
}
return state, nil return state, nil
} }

View File

@ -33,10 +33,6 @@ func (m *mockCgroupManager) Set(container *configs.Config) error {
return nil return nil
} }
func (m *mockCgroupManager) ChargeProcess(pid int) error {
return nil
}
func (m *mockCgroupManager) Destroy() error { func (m *mockCgroupManager) Destroy() error {
return nil return nil
} }
@ -134,7 +130,8 @@ func TestGetContainerState(t *testing.T) {
{Type: configs.NEWNS}, {Type: configs.NEWNS},
{Type: configs.NEWNET, Path: expectedNetworkPath}, {Type: configs.NEWNET, Path: expectedNetworkPath},
{Type: configs.NEWUTS}, {Type: configs.NEWUTS},
{Type: configs.NEWIPC}, // emulate host for IPC
//{Type: configs.NEWIPC},
}, },
}, },
initProcess: &mockProcess{ initProcess: &mockProcess{

View File

@ -21,7 +21,7 @@ var (
ioutilReadDir = ioutil.ReadDir ioutilReadDir = ioutil.ReadDir
) )
// 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. // 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.
func DeviceFromPath(path, permissions string) (*configs.Device, error) { func DeviceFromPath(path, permissions string) (*configs.Device, error) {
fileInfo, err := osLstat(path) fileInfo, err := osLstat(path)
if err != nil { if err != nil {

View File

@ -32,15 +32,13 @@ type Factory interface {
// System error // System error
Load(id string) (Container, error) Load(id string) (Container, error)
// StartInitialization is an internal API to libcontainer used during the rexec of the // StartInitialization is an internal API to libcontainer used during the reexec of the
// container. pipefd is the fd to the child end of the pipe used to syncronize the // container.
// parent and child process providing state and configuration to the child process and
// returning any errors during the init of the container
// //
// Errors: // Errors:
// pipe connection error // Pipe connection error
// system error // System error
StartInitialization(pipefd uintptr) error StartInitialization() error
// Type returns info string about factory type (e.g. lxc, libcontainer...) // Type returns info string about factory type (e.g. lxc, libcontainer...)
Type() string Type() string

View File

@ -10,6 +10,7 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv"
"syscall" "syscall"
"github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/mount"
@ -194,7 +195,11 @@ func (l *LinuxFactory) Type() string {
// StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state // StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state
// This is a low level implementation detail of the reexec and should not be consumed externally // This is a low level implementation detail of the reexec and should not be consumed externally
func (l *LinuxFactory) StartInitialization(pipefd uintptr) (err error) { func (l *LinuxFactory) StartInitialization() (err error) {
pipefd, err := strconv.Atoi(os.Getenv("_LIBCONTAINER_INITPIPE"))
if err != nil {
return err
}
var ( var (
pipe = os.NewFile(uintptr(pipefd), "pipe") pipe = os.NewFile(uintptr(pipefd), "pipe")
it = initType(os.Getenv("_LIBCONTAINER_INITTYPE")) it = initType(os.Getenv("_LIBCONTAINER_INITTYPE"))

View File

@ -48,6 +48,7 @@ type initConfig struct {
Config *configs.Config `json:"config"` Config *configs.Config `json:"config"`
Console string `json:"console"` Console string `json:"console"`
Networks []*network `json:"network"` Networks []*network `json:"network"`
PassedFilesCount int `json:"passed_files_count"`
} }
type initer interface { type initer interface {
@ -69,6 +70,7 @@ func newContainerInit(t initType, pipe *os.File) (initer, error) {
}, nil }, nil
case initStandard: case initStandard:
return &linuxStandardInit{ return &linuxStandardInit{
parentPid: syscall.Getppid(),
config: config, config: config,
}, nil }, nil
} }
@ -94,10 +96,10 @@ func populateProcessEnvironment(env []string) error {
// and working dir, and closes any leaked file descriptors // and working dir, and closes any leaked file descriptors
// before executing the command inside the namespace // before executing the command inside the namespace
func finalizeNamespace(config *initConfig) error { func finalizeNamespace(config *initConfig) error {
// Ensure that all non-standard fds we may have accidentally // Ensure that all unwanted fds we may have accidentally
// inherited are marked close-on-exec so they stay out of the // inherited are marked close-on-exec so they stay out of the
// container // container
if err := utils.CloseExecFrom(3); err != nil { if err := utils.CloseExecFrom(config.PassedFilesCount + 3); err != nil {
return err return err
} }

View File

@ -4,11 +4,14 @@ import (
"bytes" "bytes"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"syscall"
"testing" "testing"
"github.com/docker/libcontainer" "github.com/docker/libcontainer"
"github.com/docker/libcontainer/cgroups/systemd"
"github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/configs"
) )
@ -28,9 +31,7 @@ func testExecPS(t *testing.T, userns bool) {
return return
} }
rootfs, err := newRootfs() rootfs, err := newRootfs()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer remove(rootfs) defer remove(rootfs)
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
if userns { if userns {
@ -63,21 +64,15 @@ func TestIPCPrivate(t *testing.T) {
} }
rootfs, err := newRootfs() rootfs, err := newRootfs()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer remove(rootfs) defer remove(rootfs)
l, err := os.Readlink("/proc/1/ns/ipc") l, err := os.Readlink("/proc/1/ns/ipc")
if err != nil { ok(t, err)
t.Fatal(err)
}
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
if err != nil { ok(t, err)
t.Fatal(err)
}
if exitCode != 0 { if exitCode != 0 {
t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
@ -94,22 +89,16 @@ func TestIPCHost(t *testing.T) {
} }
rootfs, err := newRootfs() rootfs, err := newRootfs()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer remove(rootfs) defer remove(rootfs)
l, err := os.Readlink("/proc/1/ns/ipc") l, err := os.Readlink("/proc/1/ns/ipc")
if err != nil { ok(t, err)
t.Fatal(err)
}
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
config.Namespaces.Remove(configs.NEWIPC) config.Namespaces.Remove(configs.NEWIPC)
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
if err != nil { ok(t, err)
t.Fatal(err)
}
if exitCode != 0 { if exitCode != 0 {
t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
@ -126,23 +115,17 @@ func TestIPCJoinPath(t *testing.T) {
} }
rootfs, err := newRootfs() rootfs, err := newRootfs()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer remove(rootfs) defer remove(rootfs)
l, err := os.Readlink("/proc/1/ns/ipc") l, err := os.Readlink("/proc/1/ns/ipc")
if err != nil { ok(t, err)
t.Fatal(err)
}
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
config.Namespaces.Add(configs.NEWIPC, "/proc/1/ns/ipc") config.Namespaces.Add(configs.NEWIPC, "/proc/1/ns/ipc")
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
if err != nil { ok(t, err)
t.Fatal(err)
}
if exitCode != 0 { if exitCode != 0 {
t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
@ -159,9 +142,7 @@ func TestIPCBadPath(t *testing.T) {
} }
rootfs, err := newRootfs() rootfs, err := newRootfs()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer remove(rootfs) defer remove(rootfs)
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
@ -179,16 +160,12 @@ func TestRlimit(t *testing.T) {
} }
rootfs, err := newRootfs() rootfs, err := newRootfs()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer remove(rootfs) defer remove(rootfs)
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
out, _, err := runContainer(config, "", "/bin/sh", "-c", "ulimit -n") out, _, err := runContainer(config, "", "/bin/sh", "-c", "ulimit -n")
if err != nil { ok(t, err)
t.Fatal(err)
}
if limit := strings.TrimSpace(out.Stdout.String()); limit != "1025" { if limit := strings.TrimSpace(out.Stdout.String()); limit != "1025" {
t.Fatalf("expected rlimit to be 1025, got %s", limit) t.Fatalf("expected rlimit to be 1025, got %s", limit)
} }
@ -207,9 +184,7 @@ func newTestRoot() (string, error) {
func waitProcess(p *libcontainer.Process, t *testing.T) { func waitProcess(p *libcontainer.Process, t *testing.T) {
status, err := p.Wait() status, err := p.Wait()
if err != nil { ok(t, err)
t.Fatal(err)
}
if !status.Success() { if !status.Success() {
t.Fatal(status) t.Fatal(status)
} }
@ -220,35 +195,22 @@ func TestEnter(t *testing.T) {
return return
} }
root, err := newTestRoot() root, err := newTestRoot()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer os.RemoveAll(root) defer os.RemoveAll(root)
rootfs, err := newRootfs() rootfs, err := newRootfs()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer remove(rootfs) defer remove(rootfs)
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
if err != nil {
t.Fatal(err)
}
container, err := factory.Create("test", config) container, err := factory.Create("test", config)
if err != nil { ok(t, err)
t.Fatal(err)
}
defer container.Destroy() defer container.Destroy()
// Execute a first process in the container // Execute a first process in the container
stdinR, stdinW, err := os.Pipe() stdinR, stdinW, err := os.Pipe()
if err != nil { ok(t, err)
t.Fatal(err)
}
var stdout, stdout2 bytes.Buffer var stdout, stdout2 bytes.Buffer
@ -261,19 +223,13 @@ func TestEnter(t *testing.T) {
err = container.Start(&pconfig) err = container.Start(&pconfig)
stdinR.Close() stdinR.Close()
defer stdinW.Close() defer stdinW.Close()
if err != nil { ok(t, err)
t.Fatal(err)
}
pid, err := pconfig.Pid() pid, err := pconfig.Pid()
if err != nil { ok(t, err)
t.Fatal(err)
}
// Execute another process in the container // Execute another process in the container
stdinR2, stdinW2, err := os.Pipe() stdinR2, stdinW2, err := os.Pipe()
if err != nil { ok(t, err)
t.Fatal(err)
}
pconfig2 := libcontainer.Process{ pconfig2 := libcontainer.Process{
Env: standardEnvironment, Env: standardEnvironment,
} }
@ -284,19 +240,13 @@ func TestEnter(t *testing.T) {
err = container.Start(&pconfig2) err = container.Start(&pconfig2)
stdinR2.Close() stdinR2.Close()
defer stdinW2.Close() defer stdinW2.Close()
if err != nil { ok(t, err)
t.Fatal(err)
}
pid2, err := pconfig2.Pid() pid2, err := pconfig2.Pid()
if err != nil { ok(t, err)
t.Fatal(err)
}
processes, err := container.Processes() processes, err := container.Processes()
if err != nil { ok(t, err)
t.Fatal(err)
}
n := 0 n := 0
for i := range processes { for i := range processes {
@ -317,14 +267,10 @@ func TestEnter(t *testing.T) {
// Check that both processes live in the same pidns // Check that both processes live in the same pidns
pidns := string(stdout.Bytes()) pidns := string(stdout.Bytes())
if err != nil { ok(t, err)
t.Fatal(err)
}
pidns2 := string(stdout2.Bytes()) pidns2 := string(stdout2.Bytes())
if err != nil { ok(t, err)
t.Fatal(err)
}
if pidns != pidns2 { if pidns != pidns2 {
t.Fatal("The second process isn't in the required pid namespace", pidns, pidns2) t.Fatal("The second process isn't in the required pid namespace", pidns, pidns2)
@ -336,28 +282,17 @@ func TestProcessEnv(t *testing.T) {
return return
} }
root, err := newTestRoot() root, err := newTestRoot()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer os.RemoveAll(root) defer os.RemoveAll(root)
rootfs, err := newRootfs() rootfs, err := newRootfs()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer remove(rootfs) defer remove(rootfs)
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
if err != nil {
t.Fatal(err)
}
container, err := factory.Create("test", config) container, err := factory.Create("test", config)
if err != nil { ok(t, err)
t.Fatal(err)
}
defer container.Destroy() defer container.Destroy()
var stdout bytes.Buffer var stdout bytes.Buffer
@ -373,17 +308,12 @@ func TestProcessEnv(t *testing.T) {
Stdout: &stdout, Stdout: &stdout,
} }
err = container.Start(&pconfig) err = container.Start(&pconfig)
if err != nil { ok(t, err)
t.Fatal(err)
}
// Wait for process // Wait for process
waitProcess(&pconfig, t) waitProcess(&pconfig, t)
outputEnv := string(stdout.Bytes()) outputEnv := string(stdout.Bytes())
if err != nil {
t.Fatal(err)
}
// Check that the environment has the key/value pair we added // Check that the environment has the key/value pair we added
if !strings.Contains(outputEnv, "FOO=BAR") { if !strings.Contains(outputEnv, "FOO=BAR") {
@ -401,28 +331,17 @@ func TestProcessCaps(t *testing.T) {
return return
} }
root, err := newTestRoot() root, err := newTestRoot()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer os.RemoveAll(root) defer os.RemoveAll(root)
rootfs, err := newRootfs() rootfs, err := newRootfs()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer remove(rootfs) defer remove(rootfs)
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
if err != nil {
t.Fatal(err)
}
container, err := factory.Create("test", config) container, err := factory.Create("test", config)
if err != nil { ok(t, err)
t.Fatal(err)
}
defer container.Destroy() defer container.Destroy()
processCaps := append(config.Capabilities, "NET_ADMIN") processCaps := append(config.Capabilities, "NET_ADMIN")
@ -436,17 +355,12 @@ func TestProcessCaps(t *testing.T) {
Stdout: &stdout, Stdout: &stdout,
} }
err = container.Start(&pconfig) err = container.Start(&pconfig)
if err != nil { ok(t, err)
t.Fatal(err)
}
// Wait for process // Wait for process
waitProcess(&pconfig, t) waitProcess(&pconfig, t)
outputStatus := string(stdout.Bytes()) outputStatus := string(stdout.Bytes())
if err != nil {
t.Fatal(err)
}
lines := strings.Split(outputStatus, "\n") lines := strings.Split(outputStatus, "\n")
@ -481,6 +395,108 @@ func TestProcessCaps(t *testing.T) {
} }
func TestFreeze(t *testing.T) { func TestFreeze(t *testing.T) {
testFreeze(t, false)
}
func TestSystemdFreeze(t *testing.T) {
if !systemd.UseSystemd() {
t.Skip("Systemd is unsupported")
}
testFreeze(t, true)
}
func testFreeze(t *testing.T, systemd bool) {
if testing.Short() {
return
}
root, err := newTestRoot()
ok(t, err)
defer os.RemoveAll(root)
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
f := factory
if systemd {
f = systemdFactory
}
container, err := f.Create("test", config)
ok(t, err)
defer container.Destroy()
stdinR, stdinW, err := os.Pipe()
ok(t, err)
pconfig := libcontainer.Process{
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
}
err = container.Start(&pconfig)
stdinR.Close()
defer stdinW.Close()
ok(t, err)
pid, err := pconfig.Pid()
ok(t, err)
process, err := os.FindProcess(pid)
ok(t, err)
err = container.Pause()
ok(t, err)
state, err := container.Status()
ok(t, err)
err = container.Resume()
ok(t, err)
if state != libcontainer.Paused {
t.Fatal("Unexpected state: ", state)
}
stdinW.Close()
s, err := process.Wait()
ok(t, err)
if !s.Success() {
t.Fatal(s.String())
}
}
func TestCpuShares(t *testing.T) {
testCpuShares(t, false)
}
func TestSystemdCpuShares(t *testing.T) {
if !systemd.UseSystemd() {
t.Skip("Systemd is unsupported")
}
testCpuShares(t, true)
}
func testCpuShares(t *testing.T, systemd bool) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
if systemd {
config.Cgroups.Slice = "system.slice"
}
config.Cgroups.CpuShares = 1
_, _, err = runContainer(config, "", "ps")
if err == nil {
t.Fatalf("runContainer should failed with invalid CpuShares")
}
}
func TestContainerState(t *testing.T) {
if testing.Short() { if testing.Short() {
return return
} }
@ -496,13 +512,21 @@ func TestFreeze(t *testing.T) {
} }
defer remove(rootfs) defer remove(rootfs)
config := newTemplateConfig(rootfs) l, err := os.Readlink("/proc/1/ns/ipc")
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
config := newTemplateConfig(rootfs)
config.Namespaces = configs.Namespaces([]configs.Namespace{
{Type: configs.NEWNS},
{Type: configs.NEWUTS},
// host for IPC
//{Type: configs.NEWIPC},
{Type: configs.NEWPID},
{Type: configs.NEWNET},
})
container, err := factory.Create("test", config) container, err := factory.Create("test", config)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -513,49 +537,199 @@ func TestFreeze(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
p := &libcontainer.Process{
pconfig := libcontainer.Process{
Args: []string{"cat"}, Args: []string{"cat"},
Env: standardEnvironment, Env: standardEnvironment,
Stdin: stdinR, Stdin: stdinR,
} }
err = container.Start(&pconfig) err = container.Start(p)
if err != nil {
t.Fatal(err)
}
stdinR.Close() stdinR.Close()
defer stdinW.Close() defer p.Signal(os.Kill)
st, err := container.State()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
pid, err := pconfig.Pid() l1, err := os.Readlink(st.NamespacePaths[configs.NEWIPC])
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if l1 != l {
process, err := os.FindProcess(pid) t.Fatal("Container using non-host ipc namespace")
if err != nil {
t.Fatal(err)
} }
if err := container.Pause(); err != nil {
t.Fatal(err)
}
state, err := container.Status()
if err != nil {
t.Fatal(err)
}
if err := container.Resume(); err != nil {
t.Fatal(err)
}
if state != libcontainer.Paused {
t.Fatal("Unexpected state: ", state)
}
stdinW.Close() stdinW.Close()
s, err := process.Wait() p.Wait()
}
func TestPassExtraFiles(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !s.Success() { defer remove(rootfs)
t.Fatal(s.String())
config := newTemplateConfig(rootfs)
container, err := factory.Create("test", config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()
var stdout bytes.Buffer
pipeout1, pipein1, err := os.Pipe()
pipeout2, pipein2, err := os.Pipe()
process := libcontainer.Process{
Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"},
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
ExtraFiles: []*os.File{pipein1, pipein2},
Stdin: nil,
Stdout: &stdout,
}
err = container.Start(&process)
if err != nil {
t.Fatal(err)
}
waitProcess(&process, t)
out := string(stdout.Bytes())
// fd 5 is the directory handle for /proc/$$/fd
if out != "0 1 2 3 4 5" {
t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to init, got '%s'", out)
}
var buf = []byte{0}
_, err = pipeout1.Read(buf)
if err != nil {
t.Fatal(err)
}
out1 := string(buf)
if out1 != "1" {
t.Fatalf("expected first pipe to receive '1', got '%s'", out1)
}
_, err = pipeout2.Read(buf)
if err != nil {
t.Fatal(err)
}
out2 := string(buf)
if out2 != "2" {
t.Fatalf("expected second pipe to receive '2', got '%s'", out2)
}
}
func TestMountCmds(t *testing.T) {
if testing.Short() {
return
}
root, err := newTestRoot()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(root)
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
tmpDir, err := ioutil.TempDir("", "tmpdir")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
config := newTemplateConfig(rootfs)
config.Mounts = append(config.Mounts, &configs.Mount{
Source: tmpDir,
Destination: filepath.Join(rootfs, "tmp"),
Device: "bind",
Flags: syscall.MS_BIND | syscall.MS_REC,
PremountCmds: []configs.Command{
{Path: "touch", Args: []string{filepath.Join(tmpDir, "hello")}},
{Path: "touch", Args: []string{filepath.Join(tmpDir, "world")}},
},
PostmountCmds: []configs.Command{
{Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "hello"), filepath.Join(rootfs, "tmp", "hello-backup")}},
{Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "world"), filepath.Join(rootfs, "tmp", "world-backup")}},
},
})
container, err := factory.Create("test", config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()
pconfig := libcontainer.Process{
Args: []string{"sh", "-c", "env"},
Env: standardEnvironment,
}
err = container.Start(&pconfig)
if err != nil {
t.Fatal(err)
}
// Wait for process
waitProcess(&pconfig, t)
entries, err := ioutil.ReadDir(tmpDir)
if err != nil {
t.Fatal(err)
}
expected := []string{"hello", "hello-backup", "world", "world-backup"}
for i, e := range entries {
if e.Name() != expected[i] {
t.Errorf("Got(%s), expect %s", e.Name(), expected[i])
}
}
}
func TestSystemProperties(t *testing.T) {
if testing.Short() {
return
}
root, err := newTestRoot()
ok(t, err)
defer os.RemoveAll(root)
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
config.SystemProperties = map[string]string{
"kernel.shmmni": "8192",
}
container, err := factory.Create("test", config)
ok(t, err)
defer container.Destroy()
var stdout bytes.Buffer
pconfig := libcontainer.Process{
Args: []string{"sh", "-c", "cat /proc/sys/kernel/shmmni"},
Env: standardEnvironment,
Stdin: nil,
Stdout: &stdout,
}
err = container.Start(&pconfig)
ok(t, err)
// Wait for process
waitProcess(&pconfig, t)
shmmniOutput := strings.TrimSpace(string(stdout.Bytes()))
if shmmniOutput != "8192" {
t.Fatalf("kernel.shmmni property expected to be 8192, but is %s", shmmniOutput)
} }
} }

View File

@ -16,22 +16,16 @@ func TestExecIn(t *testing.T) {
return return
} }
rootfs, err := newRootfs() rootfs, err := newRootfs()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer remove(rootfs) defer remove(rootfs)
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
container, err := newContainer(config) container, err := newContainer(config)
if err != nil { ok(t, err)
t.Fatal(err)
}
defer container.Destroy() defer container.Destroy()
// Execute a first process in the container // Execute a first process in the container
stdinR, stdinW, err := os.Pipe() stdinR, stdinW, err := os.Pipe()
if err != nil { ok(t, err)
t.Fatal(err)
}
process := &libcontainer.Process{ process := &libcontainer.Process{
Args: []string{"cat"}, Args: []string{"cat"},
Env: standardEnvironment, Env: standardEnvironment,
@ -40,9 +34,7 @@ func TestExecIn(t *testing.T) {
err = container.Start(process) err = container.Start(process)
stdinR.Close() stdinR.Close()
defer stdinW.Close() defer stdinW.Close()
if err != nil { ok(t, err)
t.Fatal(err)
}
buffers := newStdBuffers() buffers := newStdBuffers()
ps := &libcontainer.Process{ ps := &libcontainer.Process{
@ -53,12 +45,9 @@ func TestExecIn(t *testing.T) {
Stderr: buffers.Stderr, Stderr: buffers.Stderr,
} }
err = container.Start(ps) err = container.Start(ps)
if err != nil { ok(t, err)
t.Fatal(err) _, err = ps.Wait()
} ok(t, err)
if _, err := ps.Wait(); err != nil {
t.Fatal(err)
}
stdinW.Close() stdinW.Close()
if _, err := process.Wait(); err != nil { if _, err := process.Wait(); err != nil {
t.Log(err) t.Log(err)
@ -74,21 +63,15 @@ func TestExecInRlimit(t *testing.T) {
return return
} }
rootfs, err := newRootfs() rootfs, err := newRootfs()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer remove(rootfs) defer remove(rootfs)
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
container, err := newContainer(config) container, err := newContainer(config)
if err != nil { ok(t, err)
t.Fatal(err)
}
defer container.Destroy() defer container.Destroy()
stdinR, stdinW, err := os.Pipe() stdinR, stdinW, err := os.Pipe()
if err != nil { ok(t, err)
t.Fatal(err)
}
process := &libcontainer.Process{ process := &libcontainer.Process{
Args: []string{"cat"}, Args: []string{"cat"},
Env: standardEnvironment, Env: standardEnvironment,
@ -97,9 +80,7 @@ func TestExecInRlimit(t *testing.T) {
err = container.Start(process) err = container.Start(process)
stdinR.Close() stdinR.Close()
defer stdinW.Close() defer stdinW.Close()
if err != nil { ok(t, err)
t.Fatal(err)
}
buffers := newStdBuffers() buffers := newStdBuffers()
ps := &libcontainer.Process{ ps := &libcontainer.Process{
@ -110,12 +91,9 @@ func TestExecInRlimit(t *testing.T) {
Stderr: buffers.Stderr, Stderr: buffers.Stderr,
} }
err = container.Start(ps) err = container.Start(ps)
if err != nil { ok(t, err)
t.Fatal(err) _, err = ps.Wait()
} ok(t, err)
if _, err := ps.Wait(); err != nil {
t.Fatal(err)
}
stdinW.Close() stdinW.Close()
if _, err := process.Wait(); err != nil { if _, err := process.Wait(); err != nil {
t.Log(err) t.Log(err)
@ -131,22 +109,16 @@ func TestExecInError(t *testing.T) {
return return
} }
rootfs, err := newRootfs() rootfs, err := newRootfs()
if err != nil { ok(t, err)
t.Fatal(err)
}
defer remove(rootfs) defer remove(rootfs)
config := newTemplateConfig(rootfs) config := newTemplateConfig(rootfs)
container, err := newContainer(config) container, err := newContainer(config)
if err != nil { ok(t, err)
t.Fatal(err)
}
defer container.Destroy() defer container.Destroy()
// Execute a first process in the container // Execute a first process in the container
stdinR, stdinW, err := os.Pipe() stdinR, stdinW, err := os.Pipe()
if err != nil { ok(t, err)
t.Fatal(err)
}
process := &libcontainer.Process{ process := &libcontainer.Process{
Args: []string{"cat"}, Args: []string{"cat"},
Env: standardEnvironment, Env: standardEnvironment,
@ -160,9 +132,7 @@ func TestExecInError(t *testing.T) {
t.Log(err) t.Log(err)
} }
}() }()
if err != nil { ok(t, err)
t.Fatal(err)
}
unexistent := &libcontainer.Process{ unexistent := &libcontainer.Process{
Args: []string{"unexistent"}, Args: []string{"unexistent"},
@ -178,6 +148,121 @@ func TestExecInError(t *testing.T) {
} }
func TestExecInTTY(t *testing.T) { func TestExecInTTY(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
container, err := newContainer(config)
ok(t, err)
defer container.Destroy()
// Execute a first process in the container
stdinR, stdinW, err := os.Pipe()
ok(t, err)
process := &libcontainer.Process{
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
}
err = container.Start(process)
stdinR.Close()
defer stdinW.Close()
ok(t, err)
var stdout bytes.Buffer
ps := &libcontainer.Process{
Args: []string{"ps"},
Env: standardEnvironment,
}
console, err := ps.NewConsole(0)
copy := make(chan struct{})
go func() {
io.Copy(&stdout, console)
close(copy)
}()
ok(t, err)
err = container.Start(ps)
ok(t, err)
select {
case <-time.After(5 * time.Second):
t.Fatal("Waiting for copy timed out")
case <-copy:
}
_, err = ps.Wait()
ok(t, err)
stdinW.Close()
if _, err := process.Wait(); err != nil {
t.Log(err)
}
out := stdout.String()
if !strings.Contains(out, "cat") || !strings.Contains(string(out), "ps") {
t.Fatalf("unexpected running process, output %q", out)
}
}
func TestExecInEnvironment(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
container, err := newContainer(config)
ok(t, err)
defer container.Destroy()
// Execute a first process in the container
stdinR, stdinW, err := os.Pipe()
ok(t, err)
process := &libcontainer.Process{
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
}
err = container.Start(process)
stdinR.Close()
defer stdinW.Close()
ok(t, err)
buffers := newStdBuffers()
process2 := &libcontainer.Process{
Args: []string{"env"},
Env: []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"DEBUG=true",
"DEBUG=false",
"ENV=test",
},
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
}
err = container.Start(process2)
ok(t, err)
if _, err := process2.Wait(); err != nil {
out := buffers.Stdout.String()
t.Fatal(err, out)
}
stdinW.Close()
if _, err := process.Wait(); err != nil {
t.Log(err)
}
out := buffers.Stdout.String()
// check execin's process environment
if !strings.Contains(out, "DEBUG=false") ||
!strings.Contains(out, "ENV=test") ||
!strings.Contains(out, "HOME=/root") ||
!strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") ||
strings.Contains(out, "DEBUG=true") {
t.Fatalf("unexpected running process, output %q", out)
}
}
func TestExecinPassExtraFiles(t *testing.T) {
if testing.Short() { if testing.Short() {
return return
} }
@ -211,106 +296,45 @@ func TestExecInTTY(t *testing.T) {
} }
var stdout bytes.Buffer var stdout bytes.Buffer
ps := &libcontainer.Process{ pipeout1, pipein1, err := os.Pipe()
Args: []string{"ps"}, pipeout2, pipein2, err := os.Pipe()
Env: standardEnvironment, inprocess := &libcontainer.Process{
Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"},
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
ExtraFiles: []*os.File{pipein1, pipein2},
Stdin: nil,
Stdout: &stdout,
} }
console, err := ps.NewConsole(0) err = container.Start(inprocess)
copy := make(chan struct{})
go func() {
io.Copy(&stdout, console)
close(copy)
}()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = container.Start(ps)
if err != nil { waitProcess(inprocess, t)
t.Fatal(err)
}
select {
case <-time.After(5 * time.Second):
t.Fatal("Waiting for copy timed out")
case <-copy:
}
if _, err := ps.Wait(); err != nil {
t.Fatal(err)
}
stdinW.Close() stdinW.Close()
if _, err := process.Wait(); err != nil { waitProcess(process, t)
t.Log(err)
out := string(stdout.Bytes())
// fd 5 is the directory handle for /proc/$$/fd
if out != "0 1 2 3 4 5" {
t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to exec, got '%s'", out)
} }
out := stdout.String() var buf = []byte{0}
if !strings.Contains(out, "cat") || !strings.Contains(string(out), "ps") { _, err = pipeout1.Read(buf)
t.Fatalf("unexpected running process, output %q", out) if err != nil {
} t.Fatal(err)
} }
out1 := string(buf)
func TestExecInEnvironment(t *testing.T) { if out1 != "1" {
if testing.Short() { t.Fatalf("expected first pipe to receive '1', got '%s'", out1)
return }
}
rootfs, err := newRootfs() _, err = pipeout2.Read(buf)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer remove(rootfs) out2 := string(buf)
config := newTemplateConfig(rootfs) if out2 != "2" {
container, err := newContainer(config) t.Fatalf("expected second pipe to receive '2', got '%s'", out2)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()
// Execute a first process in the container
stdinR, stdinW, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
process := &libcontainer.Process{
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
}
err = container.Start(process)
stdinR.Close()
defer stdinW.Close()
if err != nil {
t.Fatal(err)
}
buffers := newStdBuffers()
process2 := &libcontainer.Process{
Args: []string{"env"},
Env: []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"DEBUG=true",
"DEBUG=false",
"ENV=test",
},
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
}
err = container.Start(process2)
if err != nil {
t.Fatal(err)
}
if _, err := process2.Wait(); err != nil {
out := buffers.Stdout.String()
t.Fatal(err, out)
}
stdinW.Close()
if _, err := process.Wait(); err != nil {
t.Log(err)
}
out := buffers.Stdout.String()
// check execin's process environment
if !strings.Contains(out, "DEBUG=false") ||
!strings.Contains(out, "ENV=test") ||
!strings.Contains(out, "HOME=/root") ||
!strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") ||
strings.Contains(out, "DEBUG=true") {
t.Fatalf("unexpected running process, output %q", out)
} }
} }

View File

@ -1,11 +1,13 @@
package integration package integration
import ( import (
"log"
"os" "os"
"runtime" "runtime"
"testing"
log "github.com/Sirupsen/logrus"
"github.com/docker/libcontainer" "github.com/docker/libcontainer"
"github.com/docker/libcontainer/cgroups/systemd"
_ "github.com/docker/libcontainer/nsenter" _ "github.com/docker/libcontainer/nsenter"
) )
@ -21,7 +23,38 @@ func init() {
if err != nil { if err != nil {
log.Fatalf("unable to initialize for container: %s", err) log.Fatalf("unable to initialize for container: %s", err)
} }
if err := factory.StartInitialization(3); err != nil { if err := factory.StartInitialization(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
var (
factory libcontainer.Factory
systemdFactory libcontainer.Factory
)
func TestMain(m *testing.M) {
var (
err error
ret int = 0
)
log.SetOutput(os.Stderr)
log.SetLevel(log.InfoLevel)
factory, err = libcontainer.New(".", libcontainer.Cgroupfs)
if err != nil {
log.Error(err)
os.Exit(1)
}
if systemd.UseSystemd() {
systemdFactory, err = libcontainer.New(".", libcontainer.SystemdCgroups)
if err != nil {
log.Error(err)
os.Exit(1)
}
}
ret = m.Run()
os.Exit(ret)
}

View File

@ -6,8 +6,11 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"runtime"
"strings" "strings"
"syscall" "syscall"
"testing"
"github.com/docker/libcontainer" "github.com/docker/libcontainer"
"github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/configs"
@ -38,6 +41,14 @@ func (b *stdBuffers) String() string {
return strings.Join(s, "|") return strings.Join(s, "|")
} }
// ok fails the test if an err is not nil.
func ok(t testing.TB, err error) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
t.Fatalf("%s:%d: unexpected error: %s\n\n", filepath.Base(file), line, err.Error())
}
}
// newRootfs creates a new tmp directory and copies the busybox root filesystem // newRootfs creates a new tmp directory and copies the busybox root filesystem
func newRootfs() (string, error) { func newRootfs() (string, error) {
dir, err := ioutil.TempDir("", "") dir, err := ioutil.TempDir("", "")
@ -68,14 +79,13 @@ func copyBusybox(dest string) error {
} }
func newContainer(config *configs.Config) (libcontainer.Container, error) { func newContainer(config *configs.Config) (libcontainer.Container, error) {
factory, err := libcontainer.New(".", f := factory
libcontainer.InitArgs(os.Args[0], "init", "--"),
libcontainer.Cgroupfs, if config.Cgroups != nil && config.Cgroups.Slice == "system.slice" {
) f = systemdFactory
if err != nil {
return nil, err
} }
return factory.Create("testCT", config)
return f.Create("testCT", config)
} }
// runContainer runs the container with the specific config and arguments // runContainer runs the container with the specific config and arguments

View File

@ -101,10 +101,22 @@ func SetFileCreateLabel(fileLabel string) error {
// the MCS label should continue to be used. SELinux will use this field // the MCS label should continue to be used. SELinux will use this field
// to make sure the content can not be shared by other containes. // to make sure the content can not be shared by other containes.
func Relabel(path string, fileLabel string, relabel string) error { func Relabel(path string, fileLabel string, relabel string) error {
exclude_path := []string{"/", "/usr", "/etc"}
if fileLabel == "" { if fileLabel == "" {
return nil return nil
} }
if relabel == "z" { for _, p := range exclude_path {
if path == p {
return fmt.Errorf("Relabeling of %s is not allowed", path)
}
}
if !strings.ContainsAny(relabel, "zZ") {
return nil
}
if strings.Contains(relabel, "z") && strings.Contains(relabel, "Z") {
return fmt.Errorf("Bad SELinux option z and Z can not be used together")
}
if strings.Contains(relabel, "z") {
c := selinux.NewContext(fileLabel) c := selinux.NewContext(fileLabel)
c["level"] = "s0" c["level"] = "s0"
fileLabel = c.Get() fileLabel = c.Get()

View File

@ -87,3 +87,31 @@ func TestDuplicateLabel(t *testing.T) {
t.Errorf("DisableSecOpt Failed level incorrect") t.Errorf("DisableSecOpt Failed level incorrect")
} }
} }
func TestRelabel(t *testing.T) {
testdir := "/tmp/test"
label := "system_u:system_r:svirt_sandbox_file_t:s0:c1,c2"
if err := Relabel(testdir, "", "z"); err != nil {
t.Fatal("Relabel with no label failed: %v", err)
}
if err := Relabel(testdir, label, ""); err != nil {
t.Fatal("Relabel with no relabel field failed: %v", err)
}
if err := Relabel(testdir, label, "z"); err != nil {
t.Fatal("Relabel shared failed: %v", err)
}
if err := Relabel(testdir, label, "Z"); err != nil {
t.Fatal("Relabel unshared failed: %v", err)
}
if err := Relabel(testdir, label, "zZ"); err == nil {
t.Fatal("Relabel with shared and unshared succeeded")
}
if err := Relabel("/etc", label, "zZ"); err == nil {
t.Fatal("Relabel /etc succeeded")
}
if err := Relabel("/", label, ""); err == nil {
t.Fatal("Relabel / succeeded")
}
if err := Relabel("/usr", label, "Z"); err == nil {
t.Fatal("Relabel /usr succeeded")
}
}

View File

@ -1,6 +1,25 @@
## nsenter ## nsenter
The `nsenter` package registers a special init constructor that is called before the Go runtime has The `nsenter` package registers a special init constructor that is called before
a chance to boot. This provides us the ability to `setns` on existing namespaces and avoid the issues the Go runtime has a chance to boot. This provides us the ability to `setns` on
that the Go runtime has with multiple threads. This constructor is only called if this package is existing namespaces and avoid the issues that the Go runtime has with multiple
registered, imported, in your go application and the argv 0 is `nsenter`. threads. This constructor will be called if this package is registered,
imported, in your go application.
The `nsenter` package will `import "C"` and it uses [cgo](https://golang.org/cmd/cgo/)
package. In cgo, if the import of "C" is immediately preceded by a comment, that comment,
called the preamble, is used as a header when compiling the C parts of the package.
So every time we import package `nsenter`, the C code function `nsexec()` would be
called. And package `nsenter` is now only imported in Docker execdriver, so every time
before we call `execdriver.Exec()`, that C code would run.
`nsexec()` will first check the environment variable `_LIBCONTAINER_INITPID`
which will give the process of the container that should be joined. Namespaces fd will
be found from `/proc/[pid]/ns` and set by `setns` syscall.
And then get the pipe number from `_LIBCONTAINER_INITPIPE`, error message could
be transfered through it. If tty is added, `_LIBCONTAINER_CONSOLE_PATH` will
have value and start a console for output.
Finally, `nsexec()` will clone a child process , exit the parent process and let
the Go runtime take over.

View File

@ -24,7 +24,7 @@ func TestNsenterAlivePid(t *testing.T) {
Path: os.Args[0], Path: os.Args[0],
Args: args, Args: args,
ExtraFiles: []*os.File{w}, ExtraFiles: []*os.File{w},
Env: []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", os.Getpid())}, Env: []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", os.Getpid()), "_LIBCONTAINER_INITPIPE=3"},
} }
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {

View File

@ -66,7 +66,7 @@ void nsexec()
const int num = sizeof(namespaces) / sizeof(char *); const int num = sizeof(namespaces) / sizeof(char *);
jmp_buf env; jmp_buf env;
char buf[PATH_MAX], *val; char buf[PATH_MAX], *val;
int i, tfd, child, len, consolefd = -1; int i, tfd, child, len, pipenum, consolefd = -1;
pid_t pid; pid_t pid;
char *console; char *console;
@ -81,6 +81,19 @@ void nsexec()
exit(1); exit(1);
} }
val = getenv("_LIBCONTAINER_INITPIPE");
if (val == NULL) {
pr_perror("Child pipe not found");
exit(1);
}
pipenum = atoi(val);
snprintf(buf, sizeof(buf), "%d", pipenum);
if (strcmp(val, buf)) {
pr_perror("Unable to parse _LIBCONTAINER_INITPIPE");
exit(1);
}
console = getenv("_LIBCONTAINER_CONSOLE_PATH"); console = getenv("_LIBCONTAINER_CONSOLE_PATH");
if (console != NULL) { if (console != NULL) {
consolefd = open(console, O_RDWR); consolefd = open(console, O_RDWR);
@ -124,6 +137,8 @@ void nsexec()
} }
if (setjmp(env) == 1) { if (setjmp(env) == 1) {
// Child
if (setsid() == -1) { if (setsid() == -1) {
pr_perror("setsid failed"); pr_perror("setsid failed");
exit(1); exit(1);
@ -149,7 +164,11 @@ void nsexec()
// Finish executing, let the Go runtime take over. // Finish executing, let the Go runtime take over.
return; return;
} }
// Parent
// We must fork to actually enter the PID namespace, use CLONE_PARENT
// so the child can have the right parent, and we don't need to forward
// the child's exit code or resend its death signal.
child = clone_parent(&env); child = clone_parent(&env);
if (child < 0) { if (child < 0) {
pr_perror("Unable to fork"); pr_perror("Unable to fork");
@ -158,7 +177,7 @@ void nsexec()
len = snprintf(buf, sizeof(buf), "{ \"pid\" : %d }\n", child); len = snprintf(buf, sizeof(buf), "{ \"pid\" : %d }\n", child);
if (write(3, buf, len) != len) { if (write(pipenum, buf, len) != len) {
pr_perror("Unable to send a child pid"); pr_perror("Unable to send a child pid");
kill(child, SIGKILL); kill(child, SIGKILL);
exit(1); exit(1);

View File

@ -65,3 +65,48 @@ You can identify if a process is running in a container by looking to see if
You may also specify an alternate root directory from where the `container.json` You may also specify an alternate root directory from where the `container.json`
file is read and where the `state.json` file will be saved. file is read and where the `state.json` file will be saved.
### How to use?
Currently nsinit has 9 commands. Type `nsinit -h` to list all of them.
And for every alternative command, you can also use `--help` to get more
detailed help documents. For example, `nsinit config --help`.
`nsinit` cli application is implemented using [cli.go](https://github.com/codegangsta/cli).
Lots of details are handled in cli.go, so the implementation of `nsinit` itself
is very clean and clear.
* **config**
It will generate a standard configuration file for a container. By default, it
will generate as the template file in [config.go](https://github.com/docker/libcontainer/blob/master/nsinit/config.go#L192).
It will modify the template if you have specified some configuration by options.
* **exec**
Starts a container and execute a new command inside it. Besides common options, it
has some special options as below.
- `--tty,-t`: allocate a TTY to the container.
- `--config`: you can specify a configuration file. By default, it will use
template configuration.
- `--id`: specify the ID for a container. By default, the id is "nsinit".
- `--user,-u`: set the user, uid, and/or gid for the process. By default the
value is "root".
- `--cwd`: set the current working dir.
- `--env`: set environment variables for the process.
* **init**
It's an internal command that is called inside the container's namespaces to
initialize the namespace and exec the user's process. It should not be called
externally.
* **oom**
Display oom notifications for a container, you should specify container id.
* **pause**
Pause the container's processes, you should specify container id. It will use
cgroup freeze subsystem to help.
* **unpause**
Unpause the container's processes. Same with `pause`.
* **stats**
Display statistics for the container, it will mainly show cgroup and network
statistics.
* **state**
Get the container's current state. You can also read the state from `state.json`
in your container_id folder.
* **help, h**
Shows a list of commands or help for one command.

View File

@ -43,6 +43,7 @@ var createFlags = []cli.Flag{
cli.StringFlag{Name: "veth-address", Usage: "veth ip address"}, cli.StringFlag{Name: "veth-address", Usage: "veth ip address"},
cli.StringFlag{Name: "veth-gateway", Usage: "veth gateway address"}, cli.StringFlag{Name: "veth-gateway", Usage: "veth gateway address"},
cli.IntFlag{Name: "veth-mtu", Usage: "veth mtu"}, cli.IntFlag{Name: "veth-mtu", Usage: "veth mtu"},
cli.BoolFlag{Name: "cgroup", Usage: "mount the cgroup data for the container"},
} }
var configCommand = cli.Command{ var configCommand = cli.Command{
@ -187,6 +188,12 @@ func modify(config *configs.Config, context *cli.Context) {
} }
config.Networks = append(config.Networks, network) config.Networks = append(config.Networks, network)
} }
if context.Bool("cgroup") {
config.Mounts = append(config.Mounts, &configs.Mount{
Destination: "/sys/fs/cgroup",
Device: "cgroup",
})
}
} }
func getTemplate() *configs.Config { func getTemplate() *configs.Config {

View File

@ -23,6 +23,7 @@ var execCommand = cli.Command{
Action: execAction, Action: execAction,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
cli.BoolFlag{Name: "tty,t", Usage: "allocate a TTY to the container"}, cli.BoolFlag{Name: "tty,t", Usage: "allocate a TTY to the container"},
cli.BoolFlag{Name: "systemd", Usage: "Use systemd for managing cgroups, if available"},
cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"}, cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"},
cli.StringFlag{Name: "config", Value: "", Usage: "path to the configuration file"}, cli.StringFlag{Name: "config", Value: "", Usage: "path to the configuration file"},
cli.StringFlag{Name: "user,u", Value: "root", Usage: "set the user, uid, and/or gid for the process"}, cli.StringFlag{Name: "user,u", Value: "root", Usage: "set the user, uid, and/or gid for the process"},

View File

@ -20,7 +20,7 @@ var initCommand = cli.Command{
if err != nil { if err != nil {
fatal(err) fatal(err)
} }
if err := factory.StartInitialization(3); err != nil { if err := factory.StartInitialization(); err != nil {
fatal(err) fatal(err)
} }
panic("This line should never been executed") panic("This line should never been executed")

View File

@ -13,7 +13,7 @@ func main() {
app.Version = "2" app.Version = "2"
app.Author = "libcontainer maintainers" app.Author = "libcontainer maintainers"
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
cli.StringFlag{Name: "root", Value: ".", Usage: "root directory for containers"}, cli.StringFlag{Name: "root", Value: "/var/run/nsinit", Usage: "root directory for containers"},
cli.StringFlag{Name: "log-file", Value: "", Usage: "set the log file to output logs to"}, cli.StringFlag{Name: "log-file", Value: "", Usage: "set the log file to output logs to"},
cli.BoolFlag{Name: "debug", Usage: "enable debug output in the logs"}, cli.BoolFlag{Name: "debug", Usage: "enable debug output in the logs"},
} }

View File

@ -1,8 +1,7 @@
package main package main
import ( import (
"log" log "github.com/Sirupsen/logrus"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
) )

View File

@ -1,8 +1,7 @@
package main package main
import ( import (
"log" log "github.com/Sirupsen/logrus"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
) )

View File

@ -3,10 +3,12 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
log "github.com/Sirupsen/logrus"
"os" "os"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/docker/libcontainer" "github.com/docker/libcontainer"
"github.com/docker/libcontainer/cgroups/systemd"
"github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/configs"
) )
@ -29,7 +31,15 @@ func loadConfig(context *cli.Context) (*configs.Config, error) {
} }
func loadFactory(context *cli.Context) (libcontainer.Factory, error) { func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
return libcontainer.New(context.GlobalString("root"), libcontainer.Cgroupfs) cgm := libcontainer.Cgroupfs
if context.Bool("systemd") {
if systemd.UseSystemd() {
cgm = libcontainer.SystemdCgroups
} else {
log.Warn("systemd cgroup flag passed, but systemd support for managing cgroups is not available.")
}
}
return libcontainer.New(context.GlobalString("root"), cgm)
} }
func getContainer(context *cli.Context) (libcontainer.Container, error) { func getContainer(context *cli.Context) (libcontainer.Container, error) {

View File

@ -23,7 +23,7 @@ type Process struct {
Env []string Env []string
// User will set the uid and gid of the executing process running inside the container // User will set the uid and gid of the executing process running inside the container
// local to the contaienr's user and group configuration. // local to the container's user and group configuration.
User string User string
// Cwd will change the processes current working directory inside the container's rootfs. // Cwd will change the processes current working directory inside the container's rootfs.
@ -38,11 +38,14 @@ type Process struct {
// Stderr is a pointer to a writer which receives the standard error stream. // Stderr is a pointer to a writer which receives the standard error stream.
Stderr io.Writer Stderr io.Writer
// ExtraFiles specifies additional open files to be inherited by the container
ExtraFiles []*os.File
// consolePath is the path to the console allocated to the container. // consolePath is the path to the console allocated to the container.
consolePath string consolePath string
// Capabilities specify the capabilities to keep when executing the process inside the container // Capabilities specify the capabilities to keep when executing the process inside the container
// All capbilities not specified will be dropped from the processes capability mask // All capabilities not specified will be dropped from the processes capability mask
Capabilities []string Capabilities []string
ops processOperations ops processOperations

View File

@ -119,6 +119,9 @@ func (p *setnsProcess) execSetns() error {
// terminate sends a SIGKILL to the forked process for the setns routine then waits to // terminate sends a SIGKILL to the forked process for the setns routine then waits to
// avoid the process becomming a zombie. // avoid the process becomming a zombie.
func (p *setnsProcess) terminate() error { func (p *setnsProcess) terminate() error {
if p.cmd.Process == nil {
return nil
}
err := p.cmd.Process.Kill() err := p.cmd.Process.Kill()
if _, werr := p.wait(); err == nil { if _, werr := p.wait(); err == nil {
err = werr err = werr

View File

@ -6,11 +6,14 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/label" "github.com/docker/libcontainer/label"
) )
@ -24,9 +27,20 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
return newSystemError(err) return newSystemError(err)
} }
for _, m := range config.Mounts { for _, m := range config.Mounts {
for _, precmd := range m.PremountCmds {
if err := mountCmd(precmd); err != nil {
return newSystemError(err)
}
}
if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil { if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil {
return newSystemError(err) return newSystemError(err)
} }
for _, postcmd := range m.PostmountCmds {
if err := mountCmd(postcmd); err != nil {
return newSystemError(err)
}
}
} }
if err := createDevices(config); err != nil { if err := createDevices(config); err != nil {
return newSystemError(err) return newSystemError(err)
@ -62,6 +76,18 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
return nil return nil
} }
func mountCmd(cmd configs.Command) error {
command := exec.Command(cmd.Path, cmd.Args[:]...)
command.Env = cmd.Env
command.Dir = cmd.Dir
if out, err := command.CombinedOutput(); err != nil {
return fmt.Errorf("%#v failed: %s: %v", cmd, string(out), err)
}
return nil
}
func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
var ( var (
dest = m.Destination dest = m.Destination
@ -72,11 +98,19 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
} }
switch m.Device { switch m.Device {
case "proc", "mqueue", "sysfs": case "proc", "sysfs":
if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) { if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) {
return err return err
} }
return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), "") return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), "")
case "mqueue":
if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) {
return err
}
if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), ""); err != nil {
return err
}
return label.SetFileLabel(dest, mountLabel)
case "tmpfs": case "tmpfs":
stat, err := os.Stat(dest) stat, err := os.Stat(dest)
if err != nil { if err != nil {
@ -126,6 +160,37 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
return err return err
} }
} }
case "cgroup":
mounts, err := cgroups.GetCgroupMounts()
if err != nil {
return err
}
var binds []*configs.Mount
for _, mm := range mounts {
dir, err := mm.GetThisCgroupDir()
if err != nil {
return err
}
binds = append(binds, &configs.Mount{
Device: "bind",
Source: filepath.Join(mm.Mountpoint, dir),
Destination: filepath.Join(m.Destination, strings.Join(mm.Subsystems, ",")),
Flags: syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY,
})
}
tmpfs := &configs.Mount{
Device: "tmpfs",
Destination: m.Destination,
Flags: syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV,
}
if err := mountToRootfs(tmpfs, rootfs, mountLabel); err != nil {
return err
}
for _, b := range binds {
if err := mountToRootfs(b, rootfs, mountLabel); err != nil {
return err
}
}
default: default:
return fmt.Errorf("unknown mount device %q to %q", m.Device, m.Destination) return fmt.Errorf("unknown mount device %q to %q", m.Device, m.Destination)
} }
@ -240,9 +305,9 @@ func mknodDevice(dest string, node *configs.Device) error {
} }
func prepareRoot(config *configs.Config) error { func prepareRoot(config *configs.Config) error {
flag := syscall.MS_PRIVATE | syscall.MS_REC flag := syscall.MS_SLAVE | syscall.MS_REC
if config.NoPivotRoot { if config.Privatefs {
flag = syscall.MS_SLAVE | syscall.MS_REC flag = syscall.MS_PRIVATE | syscall.MS_REC
} }
if err := syscall.Mount("", "/", "", uintptr(flag), ""); err != nil { if err := syscall.Mount("", "/", "", uintptr(flag), ""); err != nil {
return err return err
@ -355,3 +420,10 @@ func maskFile(path string) error {
} }
return nil return nil
} }
// writeSystemProperty writes the value to a path under /proc/sys as determined from the key.
// For e.g. net.ipv4.ip_forward translated to /proc/sys/net/ipv4/ip_forward.
func writeSystemProperty(key, value string) error {
keyPath := strings.Replace(key, ".", "/", -1)
return ioutil.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0644)
}

View File

@ -13,6 +13,7 @@ import (
) )
type linuxStandardInit struct { type linuxStandardInit struct {
parentPid int
config *initConfig config *initConfig
} }
@ -63,6 +64,13 @@ func (l *linuxStandardInit) Init() error {
if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil { if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil {
return err return err
} }
for key, value := range l.config.Config.SystemProperties {
if err := writeSystemProperty(key, value); err != nil {
return err
}
}
for _, path := range l.config.Config.ReadonlyPaths { for _, path := range l.config.Config.ReadonlyPaths {
if err := remountReadonly(path); err != nil { if err := remountReadonly(path); err != nil {
return err return err
@ -85,9 +93,10 @@ func (l *linuxStandardInit) Init() error {
if err := pdeath.Restore(); err != nil { if err := pdeath.Restore(); err != nil {
return err return err
} }
// Signal self if parent is already dead. Does nothing if running in a new // compare the parent from the inital start of the init process and make sure that it did not change.
// PID namespace, as Getppid will always return 0. // if the parent changes that means it died and we were reparened to something else so we should
if syscall.Getppid() == 1 { // just kill ourself and not cause problems for someone else.
if syscall.Getppid() != l.parentPid {
return syscall.Kill(syscall.Getpid(), syscall.SIGKILL) return syscall.Kill(syscall.Getpid(), syscall.SIGKILL)
} }
return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ()) return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ())

View File

@ -12,8 +12,10 @@ import (
// We are declaring the macro here because the SETNS syscall does not exist in th stdlib // We are declaring the macro here because the SETNS syscall does not exist in th stdlib
var setNsMap = map[string]uintptr{ var setNsMap = map[string]uintptr{
"linux/386": 346, "linux/386": 346,
"linux/arm64": 268,
"linux/amd64": 308, "linux/amd64": 308,
"linux/arm": 374, "linux/arm": 375,
"linux/ppc": 350,
"linux/ppc64": 350, "linux/ppc64": 350,
"linux/ppc64le": 350, "linux/ppc64le": 350,
"linux/s390x": 339, "linux/s390x": 339,

View File

@ -1,4 +1,4 @@
// +build linux,amd64 linux,ppc64 linux,ppc64le linux,s390x // +build linux,arm64 linux,amd64 linux,ppc linux,ppc64 linux,ppc64le linux,s390x
package system package system

View File

@ -43,7 +43,7 @@ clone() {
clone git github.com/codegangsta/cli 1.1.0 clone git github.com/codegangsta/cli 1.1.0
clone git github.com/coreos/go-systemd v2 clone git github.com/coreos/go-systemd v2
clone git github.com/godbus/dbus v2 clone git github.com/godbus/dbus v2
clone git github.com/Sirupsen/logrus v0.6.6 clone git github.com/Sirupsen/logrus v0.7.3
clone git github.com/syndtr/gocapability 8e4cdcb clone git github.com/syndtr/gocapability 8e4cdcb
# intentionally not vendoring Docker itself... that'd be a circle :) # intentionally not vendoring Docker itself... that'd be a circle :)

View File

@ -0,0 +1,7 @@
# 0.7.3
formatter/\*: allow configuration of timestamp layout
# 0.7.2
formatter/text: Add configuration option for time format (#158)

View File

@ -37,11 +37,13 @@ attached, the output is compatible with the
[logfmt](http://godoc.org/github.com/kr/logfmt) format: [logfmt](http://godoc.org/github.com/kr/logfmt) format:
```text ```text
time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10 time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122 time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10 time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9 time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100 time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
exit status 1
``` ```
#### Example #### Example
@ -82,7 +84,7 @@ func init() {
// Use the Airbrake hook to report errors that have Error severity or above to // Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section. // an exception tracker. You can create custom hooks, see the Hooks section.
log.AddHook(&logrus_airbrake.AirbrakeHook{}) log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development"))
// Output to stderr instead of stdout, could also be a file. // Output to stderr instead of stdout, could also be a file.
log.SetOutput(os.Stderr) log.SetOutput(os.Stderr)
@ -106,6 +108,16 @@ func main() {
"omg": true, "omg": true,
"number": 100, "number": 100,
}).Fatal("The ice breaks!") }).Fatal("The ice breaks!")
// A common pattern is to re-use fields between logging statements by re-using
// the logrus.Entry returned from WithFields()
contextLogger := log.WithFields(log.Fields{
"common": "this is a common field",
"other": "I also should be logged always",
})
contextLogger.Info("I'll be logged with common and other field")
contextLogger.Info("Me too")
} }
``` ```
@ -164,43 +176,8 @@ You can add hooks for logging levels. For example to send errors to an exception
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
multiple places simultaneously, e.g. syslog. multiple places simultaneously, e.g. syslog.
```go Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
// Not the real implementation of the Airbrake hook. Just a simple sample. `init`:
import (
log "github.com/Sirupsen/logrus"
)
func init() {
log.AddHook(new(AirbrakeHook))
}
type AirbrakeHook struct{}
// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains
// the fields for the entry. See the Fields section of the README.
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
err := airbrake.Notify(entry.Data["error"].(error))
if err != nil {
log.WithFields(log.Fields{
"source": "airbrake",
"endpoint": airbrake.Endpoint,
}).Info("Failed to send error to Airbrake")
}
return nil
}
// `Levels()` returns a slice of `Levels` the hook is fired for.
func (hook *AirbrakeHook) Levels() []log.Level {
return []log.Level{
log.ErrorLevel,
log.FatalLevel,
log.PanicLevel,
}
}
```
Logrus comes with built-in hooks. Add those, or your custom hook, in `init`:
```go ```go
import ( import (
@ -211,7 +188,7 @@ import (
) )
func init() { func init() {
log.AddHook(new(logrus_airbrake.AirbrakeHook)) log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development"))
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err != nil { if err != nil {
@ -222,28 +199,18 @@ func init() {
} }
``` ```
* [`github.com/Sirupsen/logrus/hooks/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.
* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Hook | Description |
Send errors to the Papertrail hosted logging service via UDP. | ----- | ----------- |
| [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. |
* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. |
Send errors to remote syslog server. | [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. |
Uses standard library `log/syslog` behind the scenes. | [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus) | [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
Send errors to a channel in hipchat. | [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly) | [Graylog](https://github.com/gemnasium/logrus-hooks/tree/master/graylog) | Hook for logging to [Graylog](http://graylog2.org/) |
Send logs to Loggly (https://www.loggly.com/)
* [`github.com/johntdyer/slackrus`](https://github.com/johntdyer/slackrus)
Hook for Slack chat.
* [`github.com/wercker/journalhook`](https://github.com/wercker/journalhook).
Hook for logging to `systemd-journald`.
#### Level logging #### Level logging
@ -321,6 +288,11 @@ The built-in logging formatters are:
field to `true`. To force no colored output even if there is a TTY set the field to `true`. To force no colored output even if there is a TTY set the
`DisableColors` field to `true` `DisableColors` field to `true`
* `logrus.JSONFormatter`. Logs fields as JSON. * `logrus.JSONFormatter`. Logs fields as JSON.
* `logrus_logstash.LogstashFormatter`. Logs fields as Logstash Events (http://logstash.net).
```go
logrus.SetFormatter(&logrus_logstash.LogstashFormatter{Type: “application_name"})
```
Third party logging formatters: Third party logging formatters:

View File

@ -3,21 +3,16 @@ package main
import ( import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake" "github.com/Sirupsen/logrus/hooks/airbrake"
"github.com/tobi/airbrake-go"
) )
var log = logrus.New() var log = logrus.New()
func init() { func init() {
log.Formatter = new(logrus.TextFormatter) // default log.Formatter = new(logrus.TextFormatter) // default
log.Hooks.Add(new(logrus_airbrake.AirbrakeHook)) log.Hooks.Add(airbrake.NewHook("https://example.com", "xyz", "development"))
} }
func main() { func main() {
airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml"
airbrake.ApiKey = "whatever"
airbrake.Environment = "production"
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"animal": "walrus", "animal": "walrus",
"size": 10, "size": 10,

View File

@ -1,5 +1,9 @@
package logrus package logrus
import "time"
const DefaultTimestampFormat = time.RFC3339
// The Formatter interface is used to implement a custom Formatter. It takes an // The Formatter interface is used to implement a custom Formatter. It takes an
// `Entry`. It exposes all the fields, including the default ones: // `Entry`. It exposes all the fields, including the default ones:
// //

View File

@ -0,0 +1,56 @@
package logstash
import (
"encoding/json"
"fmt"
"github.com/Sirupsen/logrus"
)
// Formatter generates json in logstash format.
// Logstash site: http://logstash.net/
type LogstashFormatter struct {
Type string // if not empty use for logstash type field.
// TimestampFormat sets the format used for timestamps.
TimestampFormat string
}
func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) {
entry.Data["@version"] = 1
if f.TimestampFormat == "" {
f.TimestampFormat = logrus.DefaultTimestampFormat
}
entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat)
// set message field
v, ok := entry.Data["message"]
if ok {
entry.Data["fields.message"] = v
}
entry.Data["message"] = entry.Message
// set level field
v, ok = entry.Data["level"]
if ok {
entry.Data["fields.level"] = v
}
entry.Data["level"] = entry.Level.String()
// set type field
if f.Type != "" {
v, ok = entry.Data["type"]
if ok {
entry.Data["fields.type"] = v
}
entry.Data["type"] = f.Type
}
serialized, err := json.Marshal(entry.Data)
if err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
}
return append(serialized, '\n'), nil
}

View File

@ -0,0 +1,52 @@
package logstash
import (
"bytes"
"encoding/json"
"github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
"testing"
)
func TestLogstashFormatter(t *testing.T) {
assert := assert.New(t)
lf := LogstashFormatter{Type: "abc"}
fields := logrus.Fields{
"message": "def",
"level": "ijk",
"type": "lmn",
"one": 1,
"pi": 3.14,
"bool": true,
}
entry := logrus.WithFields(fields)
entry.Message = "msg"
entry.Level = logrus.InfoLevel
b, _ := lf.Format(entry)
var data map[string]interface{}
dec := json.NewDecoder(bytes.NewReader(b))
dec.UseNumber()
dec.Decode(&data)
// base fields
assert.Equal(json.Number("1"), data["@version"])
assert.NotEmpty(data["@timestamp"])
assert.Equal("abc", data["type"])
assert.Equal("msg", data["message"])
assert.Equal("info", data["level"])
// substituted fields
assert.Equal("def", data["fields.message"])
assert.Equal("ijk", data["fields.level"])
assert.Equal("lmn", data["fields.type"])
// formats
assert.Equal(json.Number("1"), data["one"])
assert.Equal(json.Number("3.14"), data["pi"])
assert.Equal(true, data["bool"])
}

View File

@ -1,51 +1,51 @@
package logrus_airbrake package airbrake
import ( import (
"errors"
"fmt"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/tobi/airbrake-go" "github.com/tobi/airbrake-go"
) )
// AirbrakeHook to send exceptions to an exception-tracking service compatible // AirbrakeHook to send exceptions to an exception-tracking service compatible
// with the Airbrake API. You must set: // with the Airbrake API.
// * airbrake.Endpoint type airbrakeHook struct {
// * airbrake.ApiKey APIKey string
// * airbrake.Environment Endpoint string
// Environment string
// Before using this hook, to send an error. Entries that trigger an Error, }
// Fatal or Panic should now include an "error" field to send to Airbrake.
type AirbrakeHook struct{}
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error { func NewHook(endpoint, apiKey, env string) *airbrakeHook {
if entry.Data["error"] == nil { return &airbrakeHook{
entry.Logger.WithFields(logrus.Fields{ APIKey: apiKey,
"source": "airbrake", Endpoint: endpoint,
"endpoint": airbrake.Endpoint, Environment: env,
}).Warn("Exceptions sent to Airbrake must have an 'error' key with the error")
return nil
} }
}
func (hook *airbrakeHook) Fire(entry *logrus.Entry) error {
airbrake.ApiKey = hook.APIKey
airbrake.Endpoint = hook.Endpoint
airbrake.Environment = hook.Environment
var notifyErr error
err, ok := entry.Data["error"].(error) err, ok := entry.Data["error"].(error)
if !ok { if ok {
entry.Logger.WithFields(logrus.Fields{ notifyErr = err
"source": "airbrake", } else {
"endpoint": airbrake.Endpoint, notifyErr = errors.New(entry.Message)
}).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`")
return nil
} }
airErr := airbrake.Notify(err) airErr := airbrake.Notify(notifyErr)
if airErr != nil { if airErr != nil {
entry.Logger.WithFields(logrus.Fields{ return fmt.Errorf("Failed to send error to Airbrake: %s", airErr)
"source": "airbrake",
"endpoint": airbrake.Endpoint,
"error": airErr,
}).Warn("Failed to send error to Airbrake")
} }
return nil return nil
} }
func (hook *AirbrakeHook) Levels() []logrus.Level { func (hook *airbrakeHook) Levels() []logrus.Level {
return []logrus.Level{ return []logrus.Level{
logrus.ErrorLevel, logrus.ErrorLevel,
logrus.FatalLevel, logrus.FatalLevel,

View File

@ -0,0 +1,133 @@
package airbrake
import (
"encoding/xml"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/Sirupsen/logrus"
)
type notice struct {
Error NoticeError `xml:"error"`
}
type NoticeError struct {
Class string `xml:"class"`
Message string `xml:"message"`
}
type customErr struct {
msg string
}
func (e *customErr) Error() string {
return e.msg
}
const (
testAPIKey = "abcxyz"
testEnv = "development"
expectedClass = "*airbrake.customErr"
expectedMsg = "foo"
unintendedMsg = "Airbrake will not see this string"
)
var (
noticeError = make(chan NoticeError, 1)
)
// TestLogEntryMessageReceived checks if invoking Logrus' log.Error
// method causes an XML payload containing the log entry message is received
// by a HTTP server emulating an Airbrake-compatible endpoint.
func TestLogEntryMessageReceived(t *testing.T) {
log := logrus.New()
ts := startAirbrakeServer(t)
defer ts.Close()
hook := NewHook(ts.URL, testAPIKey, "production")
log.Hooks.Add(hook)
log.Error(expectedMsg)
select {
case received := <-noticeError:
if received.Message != expectedMsg {
t.Errorf("Unexpected message received: %s", received.Message)
}
case <-time.After(time.Second):
t.Error("Timed out; no notice received by Airbrake API")
}
}
// TestLogEntryMessageReceived confirms that, when passing an error type using
// logrus.Fields, a HTTP server emulating an Airbrake endpoint receives the
// error message returned by the Error() method on the error interface
// rather than the logrus.Entry.Message string.
func TestLogEntryWithErrorReceived(t *testing.T) {
log := logrus.New()
ts := startAirbrakeServer(t)
defer ts.Close()
hook := NewHook(ts.URL, testAPIKey, "production")
log.Hooks.Add(hook)
log.WithFields(logrus.Fields{
"error": &customErr{expectedMsg},
}).Error(unintendedMsg)
select {
case received := <-noticeError:
if received.Message != expectedMsg {
t.Errorf("Unexpected message received: %s", received.Message)
}
if received.Class != expectedClass {
t.Errorf("Unexpected error class: %s", received.Class)
}
case <-time.After(time.Second):
t.Error("Timed out; no notice received by Airbrake API")
}
}
// TestLogEntryWithNonErrorTypeNotReceived confirms that, when passing a
// non-error type using logrus.Fields, a HTTP server emulating an Airbrake
// endpoint receives the logrus.Entry.Message string.
//
// Only error types are supported when setting the 'error' field using
// logrus.WithFields().
func TestLogEntryWithNonErrorTypeNotReceived(t *testing.T) {
log := logrus.New()
ts := startAirbrakeServer(t)
defer ts.Close()
hook := NewHook(ts.URL, testAPIKey, "production")
log.Hooks.Add(hook)
log.WithFields(logrus.Fields{
"error": expectedMsg,
}).Error(unintendedMsg)
select {
case received := <-noticeError:
if received.Message != unintendedMsg {
t.Errorf("Unexpected message received: %s", received.Message)
}
case <-time.After(time.Second):
t.Error("Timed out; no notice received by Airbrake API")
}
}
func startAirbrakeServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var notice notice
if err := xml.NewDecoder(r.Body).Decode(&notice); err != nil {
t.Error(err)
}
r.Body.Close()
noticeError <- notice.Error
}))
return ts
}

View File

@ -0,0 +1,68 @@
package logrus_bugsnag
import (
"errors"
"github.com/Sirupsen/logrus"
"github.com/bugsnag/bugsnag-go"
)
type bugsnagHook struct{}
// ErrBugsnagUnconfigured is returned if NewBugsnagHook is called before
// bugsnag.Configure. Bugsnag must be configured before the hook.
var ErrBugsnagUnconfigured = errors.New("bugsnag must be configured before installing this logrus hook")
// ErrBugsnagSendFailed indicates that the hook failed to submit an error to
// bugsnag. The error was successfully generated, but `bugsnag.Notify()`
// failed.
type ErrBugsnagSendFailed struct {
err error
}
func (e ErrBugsnagSendFailed) Error() string {
return "failed to send error to Bugsnag: " + e.err.Error()
}
// NewBugsnagHook initializes a logrus hook which sends exceptions to an
// exception-tracking service compatible with the Bugsnag API. Before using
// this hook, you must call bugsnag.Configure(). The returned object should be
// registered with a log via `AddHook()`
//
// Entries that trigger an Error, Fatal or Panic should now include an "error"
// field to send to Bugsnag.
func NewBugsnagHook() (*bugsnagHook, error) {
if bugsnag.Config.APIKey == "" {
return nil, ErrBugsnagUnconfigured
}
return &bugsnagHook{}, nil
}
// Fire forwards an error to Bugsnag. Given a logrus.Entry, it extracts the
// "error" field (or the Message if the error isn't present) and sends it off.
func (hook *bugsnagHook) Fire(entry *logrus.Entry) error {
var notifyErr error
err, ok := entry.Data["error"].(error)
if ok {
notifyErr = err
} else {
notifyErr = errors.New(entry.Message)
}
bugsnagErr := bugsnag.Notify(notifyErr)
if bugsnagErr != nil {
return ErrBugsnagSendFailed{bugsnagErr}
}
return nil
}
// Levels enumerates the log levels on which the error should be forwarded to
// bugsnag: everything at or above the "Error" level.
func (hook *bugsnagHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.ErrorLevel,
logrus.FatalLevel,
logrus.PanicLevel,
}
}

View File

@ -0,0 +1,64 @@
package logrus_bugsnag
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/Sirupsen/logrus"
"github.com/bugsnag/bugsnag-go"
)
type notice struct {
Events []struct {
Exceptions []struct {
Message string `json:"message"`
} `json:"exceptions"`
} `json:"events"`
}
func TestNoticeReceived(t *testing.T) {
msg := make(chan string, 1)
expectedMsg := "foo"
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var notice notice
data, _ := ioutil.ReadAll(r.Body)
if err := json.Unmarshal(data, &notice); err != nil {
t.Error(err)
}
_ = r.Body.Close()
msg <- notice.Events[0].Exceptions[0].Message
}))
defer ts.Close()
hook := &bugsnagHook{}
bugsnag.Configure(bugsnag.Configuration{
Endpoint: ts.URL,
ReleaseStage: "production",
APIKey: "12345678901234567890123456789012",
Synchronous: true,
})
log := logrus.New()
log.Hooks.Add(hook)
log.WithFields(logrus.Fields{
"error": errors.New(expectedMsg),
}).Error("Bugsnag will not see this string")
select {
case received := <-msg:
if received != expectedMsg {
t.Errorf("Unexpected message received: %s", received)
}
case <-time.After(time.Second):
t.Error("Timed out; no notice received by Bugsnag API")
}
}

View File

@ -3,24 +3,32 @@ package logrus
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"time"
) )
type JSONFormatter struct{} type JSONFormatter struct {
// TimestampFormat sets the format used for marshaling timestamps.
TimestampFormat string
}
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
data := make(Fields, len(entry.Data)+3) data := make(Fields, len(entry.Data)+3)
for k, v := range entry.Data { for k, v := range entry.Data {
switch v := v.(type) {
case error:
// Otherwise errors are ignored by `encoding/json` // Otherwise errors are ignored by `encoding/json`
// https://github.com/Sirupsen/logrus/issues/137 // https://github.com/Sirupsen/logrus/issues/137
if err, ok := v.(error); ok { data[k] = v.Error()
data[k] = err.Error() default:
} else {
data[k] = v data[k] = v
} }
} }
prefixFieldClashes(data) prefixFieldClashes(data)
data["time"] = entry.Time.Format(time.RFC3339)
if f.TimestampFormat == "" {
f.TimestampFormat = DefaultTimestampFormat
}
data["time"] = entry.Time.Format(f.TimestampFormat)
data["msg"] = entry.Message data["msg"] = entry.Message
data["level"] = entry.Level.String() data["level"] = entry.Level.String()

View File

@ -65,11 +65,15 @@ func (logger *Logger) WithFields(fields Fields) *Entry {
} }
func (logger *Logger) Debugf(format string, args ...interface{}) { func (logger *Logger) Debugf(format string, args ...interface{}) {
if logger.Level >= DebugLevel {
NewEntry(logger).Debugf(format, args...) NewEntry(logger).Debugf(format, args...)
}
} }
func (logger *Logger) Infof(format string, args ...interface{}) { func (logger *Logger) Infof(format string, args ...interface{}) {
if logger.Level >= InfoLevel {
NewEntry(logger).Infof(format, args...) NewEntry(logger).Infof(format, args...)
}
} }
func (logger *Logger) Printf(format string, args ...interface{}) { func (logger *Logger) Printf(format string, args ...interface{}) {
@ -77,31 +81,45 @@ func (logger *Logger) Printf(format string, args ...interface{}) {
} }
func (logger *Logger) Warnf(format string, args ...interface{}) { func (logger *Logger) Warnf(format string, args ...interface{}) {
if logger.Level >= WarnLevel {
NewEntry(logger).Warnf(format, args...) NewEntry(logger).Warnf(format, args...)
}
} }
func (logger *Logger) Warningf(format string, args ...interface{}) { func (logger *Logger) Warningf(format string, args ...interface{}) {
if logger.Level >= WarnLevel {
NewEntry(logger).Warnf(format, args...) NewEntry(logger).Warnf(format, args...)
}
} }
func (logger *Logger) Errorf(format string, args ...interface{}) { func (logger *Logger) Errorf(format string, args ...interface{}) {
if logger.Level >= ErrorLevel {
NewEntry(logger).Errorf(format, args...) NewEntry(logger).Errorf(format, args...)
}
} }
func (logger *Logger) Fatalf(format string, args ...interface{}) { func (logger *Logger) Fatalf(format string, args ...interface{}) {
if logger.Level >= FatalLevel {
NewEntry(logger).Fatalf(format, args...) NewEntry(logger).Fatalf(format, args...)
}
} }
func (logger *Logger) Panicf(format string, args ...interface{}) { func (logger *Logger) Panicf(format string, args ...interface{}) {
if logger.Level >= PanicLevel {
NewEntry(logger).Panicf(format, args...) NewEntry(logger).Panicf(format, args...)
}
} }
func (logger *Logger) Debug(args ...interface{}) { func (logger *Logger) Debug(args ...interface{}) {
if logger.Level >= DebugLevel {
NewEntry(logger).Debug(args...) NewEntry(logger).Debug(args...)
}
} }
func (logger *Logger) Info(args ...interface{}) { func (logger *Logger) Info(args ...interface{}) {
if logger.Level >= InfoLevel {
NewEntry(logger).Info(args...) NewEntry(logger).Info(args...)
}
} }
func (logger *Logger) Print(args ...interface{}) { func (logger *Logger) Print(args ...interface{}) {
@ -109,31 +127,45 @@ func (logger *Logger) Print(args ...interface{}) {
} }
func (logger *Logger) Warn(args ...interface{}) { func (logger *Logger) Warn(args ...interface{}) {
if logger.Level >= WarnLevel {
NewEntry(logger).Warn(args...) NewEntry(logger).Warn(args...)
}
} }
func (logger *Logger) Warning(args ...interface{}) { func (logger *Logger) Warning(args ...interface{}) {
if logger.Level >= WarnLevel {
NewEntry(logger).Warn(args...) NewEntry(logger).Warn(args...)
}
} }
func (logger *Logger) Error(args ...interface{}) { func (logger *Logger) Error(args ...interface{}) {
if logger.Level >= ErrorLevel {
NewEntry(logger).Error(args...) NewEntry(logger).Error(args...)
}
} }
func (logger *Logger) Fatal(args ...interface{}) { func (logger *Logger) Fatal(args ...interface{}) {
if logger.Level >= FatalLevel {
NewEntry(logger).Fatal(args...) NewEntry(logger).Fatal(args...)
}
} }
func (logger *Logger) Panic(args ...interface{}) { func (logger *Logger) Panic(args ...interface{}) {
if logger.Level >= PanicLevel {
NewEntry(logger).Panic(args...) NewEntry(logger).Panic(args...)
}
} }
func (logger *Logger) Debugln(args ...interface{}) { func (logger *Logger) Debugln(args ...interface{}) {
if logger.Level >= DebugLevel {
NewEntry(logger).Debugln(args...) NewEntry(logger).Debugln(args...)
}
} }
func (logger *Logger) Infoln(args ...interface{}) { func (logger *Logger) Infoln(args ...interface{}) {
if logger.Level >= InfoLevel {
NewEntry(logger).Infoln(args...) NewEntry(logger).Infoln(args...)
}
} }
func (logger *Logger) Println(args ...interface{}) { func (logger *Logger) Println(args ...interface{}) {
@ -141,21 +173,31 @@ func (logger *Logger) Println(args ...interface{}) {
} }
func (logger *Logger) Warnln(args ...interface{}) { func (logger *Logger) Warnln(args ...interface{}) {
if logger.Level >= WarnLevel {
NewEntry(logger).Warnln(args...) NewEntry(logger).Warnln(args...)
}
} }
func (logger *Logger) Warningln(args ...interface{}) { func (logger *Logger) Warningln(args ...interface{}) {
if logger.Level >= WarnLevel {
NewEntry(logger).Warnln(args...) NewEntry(logger).Warnln(args...)
}
} }
func (logger *Logger) Errorln(args ...interface{}) { func (logger *Logger) Errorln(args ...interface{}) {
if logger.Level >= ErrorLevel {
NewEntry(logger).Errorln(args...) NewEntry(logger).Errorln(args...)
}
} }
func (logger *Logger) Fatalln(args ...interface{}) { func (logger *Logger) Fatalln(args ...interface{}) {
if logger.Level >= FatalLevel {
NewEntry(logger).Fatalln(args...) NewEntry(logger).Fatalln(args...)
}
} }
func (logger *Logger) Panicln(args ...interface{}) { func (logger *Logger) Panicln(args ...interface{}) {
if logger.Level >= PanicLevel {
NewEntry(logger).Panicln(args...) NewEntry(logger).Panicln(args...)
}
} }

View File

@ -1,4 +1,3 @@
package logrus package logrus
import "syscall" import "syscall"

View File

@ -3,7 +3,6 @@ package logrus
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"regexp"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -21,7 +20,6 @@ const (
var ( var (
baseTimestamp time.Time baseTimestamp time.Time
isTerminal bool isTerminal bool
noQuoteNeeded *regexp.Regexp
) )
func init() { func init() {
@ -48,6 +46,9 @@ type TextFormatter struct {
// the time passed since beginning of execution. // the time passed since beginning of execution.
FullTimestamp bool FullTimestamp bool
// TimestampFormat to use for display when a full timestamp is printed
TimestampFormat string
// The fields are sorted by default for a consistent output. For applications // The fields are sorted by default for a consistent output. For applications
// that log extremely frequently and don't use the JSON formatter this may not // that log extremely frequently and don't use the JSON formatter this may not
// be desired. // be desired.
@ -70,11 +71,14 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
isColored := (f.ForceColors || isTerminal) && !f.DisableColors isColored := (f.ForceColors || isTerminal) && !f.DisableColors
if f.TimestampFormat == "" {
f.TimestampFormat = DefaultTimestampFormat
}
if isColored { if isColored {
f.printColored(b, entry, keys) f.printColored(b, entry, keys)
} else { } else {
if !f.DisableTimestamp { if !f.DisableTimestamp {
f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339)) f.appendKeyValue(b, "time", entry.Time.Format(f.TimestampFormat))
} }
f.appendKeyValue(b, "level", entry.Level.String()) f.appendKeyValue(b, "level", entry.Level.String())
f.appendKeyValue(b, "msg", entry.Message) f.appendKeyValue(b, "msg", entry.Message)
@ -105,7 +109,7 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
if !f.FullTimestamp { if !f.FullTimestamp {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
} else { } else {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(time.RFC3339), entry.Message) fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(f.TimestampFormat), entry.Message)
} }
for _, k := range keys { for _, k := range keys {
v := entry.Data[k] v := entry.Data[k]

View File

@ -3,8 +3,8 @@ package logrus
import ( import (
"bytes" "bytes"
"errors" "errors"
"testing" "testing"
"time"
) )
func TestQuoting(t *testing.T) { func TestQuoting(t *testing.T) {
@ -33,5 +33,29 @@ func TestQuoting(t *testing.T) {
checkQuoting(true, errors.New("invalid argument")) checkQuoting(true, errors.New("invalid argument"))
} }
func TestTimestampFormat(t *testing.T) {
checkTimeStr := func(format string) {
customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format}
customStr, _ := customFormatter.Format(WithField("test", "test"))
timeStart := bytes.Index(customStr, ([]byte)("time="))
timeEnd := bytes.Index(customStr, ([]byte)("level="))
timeStr := customStr[timeStart+5 : timeEnd-1]
if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' {
timeStr = timeStr[1 : len(timeStr)-1]
}
if format == "" {
format = time.RFC3339
}
_, e := time.Parse(format, (string)(timeStr))
if e != nil {
t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e)
}
}
checkTimeStr("2006-01-02T15:04:05.000000000Z07:00")
checkTimeStr("Mon Jan _2 15:04:05 2006")
checkTimeStr("")
}
// TODO add tests for sorting etc., this requires a parser for the text // TODO add tests for sorting etc., this requires a parser for the text
// formatter output. // formatter output.

View File

@ -6,7 +6,7 @@ import (
"runtime" "runtime"
) )
func (logger *Logger) Writer() (*io.PipeWriter) { func (logger *Logger) Writer() *io.PipeWriter {
reader, writer := io.Pipe() reader, writer := io.Pipe()
go logger.writerScanner(reader) go logger.writerScanner(reader)

View File

@ -60,7 +60,8 @@ type Capabilities interface {
Apply(kind CapType) error Apply(kind CapType) error
} }
// NewPid create new initialized Capabilities object for given pid. // NewPid create new initialized Capabilities object for given pid when it
// is nonzero, or for the current pid if pid is 0
func NewPid(pid int) (Capabilities, error) { func NewPid(pid int) (Capabilities, error) {
return newPid(pid) return newPid(pid)
} }

View File

@ -24,12 +24,46 @@ const (
linuxCapVer3 = 0x20080522 linuxCapVer3 = 0x20080522
) )
var capVers uint32 var (
capVers uint32
capLastCap Cap
)
func init() { func init() {
var hdr capHeader var hdr capHeader
capget(&hdr, nil) capget(&hdr, nil)
capVers = hdr.version capVers = hdr.version
if initLastCap() == nil {
CAP_LAST_CAP = capLastCap
if capLastCap > 31 {
capUpperMask = (uint32(1) << (uint(capLastCap) - 31)) - 1
} else {
capUpperMask = 0
}
}
}
func initLastCap() error {
if capLastCap != 0 {
return nil
}
f, err := os.Open("/proc/sys/kernel/cap_last_cap")
if err != nil {
return err
}
defer f.Close()
var b []byte = make([]byte, 11)
_, err = f.Read(b)
if err != nil {
return err
}
fmt.Sscanf(string(b), "%d", &capLastCap)
return nil
} }
func mkStringCap(c Capabilities, which CapType) (ret string) { func mkStringCap(c Capabilities, which CapType) (ret string) {
@ -351,7 +385,15 @@ func (c *capsV3) Load() (err error) {
return return
} }
f, err := os.Open(fmt.Sprintf("/proc/%d/status", c.hdr.pid)) var status_path string
if c.hdr.pid == 0 {
status_path = fmt.Sprintf("/proc/self/status")
} else {
status_path = fmt.Sprintf("/proc/%d/status", c.hdr.pid)
}
f, err := os.Open(status_path)
if err != nil { if err != nil {
return return
} }

View File

@ -112,32 +112,33 @@ func (c Cap) String() string {
return "wake_alarm" return "wake_alarm"
case CAP_BLOCK_SUSPEND: case CAP_BLOCK_SUSPEND:
return "block_suspend" return "block_suspend"
case CAP_AUDIT_READ:
return "audit_read"
} }
return "unknown" return "unknown"
} }
// POSIX-draft defined capabilities.
const ( const (
// POSIX-draft defined capabilities.
// In a system with the [_POSIX_CHOWN_RESTRICTED] option defined, this // In a system with the [_POSIX_CHOWN_RESTRICTED] option defined, this
// overrides the restriction of changing file ownership and group // overrides the restriction of changing file ownership and group
// ownership. // ownership.
CAP_CHOWN Cap = 0 CAP_CHOWN = Cap(0)
// Override all DAC access, including ACL execute access if // Override all DAC access, including ACL execute access if
// [_POSIX_ACL] is defined. Excluding DAC access covered by // [_POSIX_ACL] is defined. Excluding DAC access covered by
// CAP_LINUX_IMMUTABLE. // CAP_LINUX_IMMUTABLE.
CAP_DAC_OVERRIDE Cap = 1 CAP_DAC_OVERRIDE = Cap(1)
// Overrides all DAC restrictions regarding read and search on files // Overrides all DAC restrictions regarding read and search on files
// and directories, including ACL restrictions if [_POSIX_ACL] is // and directories, including ACL restrictions if [_POSIX_ACL] is
// defined. Excluding DAC access covered by CAP_LINUX_IMMUTABLE. // defined. Excluding DAC access covered by CAP_LINUX_IMMUTABLE.
CAP_DAC_READ_SEARCH Cap = 2 CAP_DAC_READ_SEARCH = Cap(2)
// Overrides all restrictions about allowed operations on files, where // Overrides all restrictions about allowed operations on files, where
// file owner ID must be equal to the user ID, except where CAP_FSETID // file owner ID must be equal to the user ID, except where CAP_FSETID
// is applicable. It doesn't override MAC and DAC restrictions. // is applicable. It doesn't override MAC and DAC restrictions.
CAP_FOWNER Cap = 3 CAP_FOWNER = Cap(3)
// Overrides the following restrictions that the effective user ID // Overrides the following restrictions that the effective user ID
// shall match the file owner ID when setting the S_ISUID and S_ISGID // shall match the file owner ID when setting the S_ISUID and S_ISGID
@ -145,21 +146,21 @@ const (
// supplementary group IDs) shall match the file owner ID when setting // supplementary group IDs) shall match the file owner ID when setting
// the S_ISGID bit on that file; that the S_ISUID and S_ISGID bits are // the S_ISGID bit on that file; that the S_ISUID and S_ISGID bits are
// cleared on successful return from chown(2) (not implemented). // cleared on successful return from chown(2) (not implemented).
CAP_FSETID Cap = 4 CAP_FSETID = Cap(4)
// Overrides the restriction that the real or effective user ID of a // Overrides the restriction that the real or effective user ID of a
// process sending a signal must match the real or effective user ID // process sending a signal must match the real or effective user ID
// of the process receiving the signal. // of the process receiving the signal.
CAP_KILL Cap = 5 CAP_KILL = Cap(5)
// Allows setgid(2) manipulation // Allows setgid(2) manipulation
// Allows setgroups(2) // Allows setgroups(2)
// Allows forged gids on socket credentials passing. // Allows forged gids on socket credentials passing.
CAP_SETGID Cap = 6 CAP_SETGID = Cap(6)
// Allows set*uid(2) manipulation (including fsuid). // Allows set*uid(2) manipulation (including fsuid).
// Allows forged pids on socket credentials passing. // Allows forged pids on socket credentials passing.
CAP_SETUID Cap = 7 CAP_SETUID = Cap(7)
// Linux-specific capabilities // Linux-specific capabilities
@ -171,17 +172,17 @@ const (
// to the current process' inheritable set // to the current process' inheritable set
// Allow taking bits out of capability bounding set // Allow taking bits out of capability bounding set
// Allow modification of the securebits for a process // Allow modification of the securebits for a process
CAP_SETPCAP Cap = 8 CAP_SETPCAP = Cap(8)
// Allow modification of S_IMMUTABLE and S_APPEND file attributes // Allow modification of S_IMMUTABLE and S_APPEND file attributes
CAP_LINUX_IMMUTABLE Cap = 9 CAP_LINUX_IMMUTABLE = Cap(9)
// Allows binding to TCP/UDP sockets below 1024 // Allows binding to TCP/UDP sockets below 1024
// Allows binding to ATM VCIs below 32 // Allows binding to ATM VCIs below 32
CAP_NET_BIND_SERVICE Cap = 10 CAP_NET_BIND_SERVICE = Cap(10)
// Allow broadcasting, listen to multicast // Allow broadcasting, listen to multicast
CAP_NET_BROADCAST Cap = 11 CAP_NET_BROADCAST = Cap(11)
// Allow interface configuration // Allow interface configuration
// Allow administration of IP firewall, masquerading and accounting // Allow administration of IP firewall, masquerading and accounting
@ -196,36 +197,36 @@ const (
// Allow multicasting // Allow multicasting
// Allow read/write of device-specific registers // Allow read/write of device-specific registers
// Allow activation of ATM control sockets // Allow activation of ATM control sockets
CAP_NET_ADMIN Cap = 12 CAP_NET_ADMIN = Cap(12)
// Allow use of RAW sockets // Allow use of RAW sockets
// Allow use of PACKET sockets // Allow use of PACKET sockets
// Allow binding to any address for transparent proxying (also via NET_ADMIN) // Allow binding to any address for transparent proxying (also via NET_ADMIN)
CAP_NET_RAW Cap = 13 CAP_NET_RAW = Cap(13)
// Allow locking of shared memory segments // Allow locking of shared memory segments
// Allow mlock and mlockall (which doesn't really have anything to do // Allow mlock and mlockall (which doesn't really have anything to do
// with IPC) // with IPC)
CAP_IPC_LOCK Cap = 14 CAP_IPC_LOCK = Cap(14)
// Override IPC ownership checks // Override IPC ownership checks
CAP_IPC_OWNER Cap = 15 CAP_IPC_OWNER = Cap(15)
// Insert and remove kernel modules - modify kernel without limit // Insert and remove kernel modules - modify kernel without limit
CAP_SYS_MODULE Cap = 16 CAP_SYS_MODULE = Cap(16)
// Allow ioperm/iopl access // Allow ioperm/iopl access
// Allow sending USB messages to any device via /proc/bus/usb // Allow sending USB messages to any device via /proc/bus/usb
CAP_SYS_RAWIO Cap = 17 CAP_SYS_RAWIO = Cap(17)
// Allow use of chroot() // Allow use of chroot()
CAP_SYS_CHROOT Cap = 18 CAP_SYS_CHROOT = Cap(18)
// Allow ptrace() of any process // Allow ptrace() of any process
CAP_SYS_PTRACE Cap = 19 CAP_SYS_PTRACE = Cap(19)
// Allow configuration of process accounting // Allow configuration of process accounting
CAP_SYS_PACCT Cap = 20 CAP_SYS_PACCT = Cap(20)
// Allow configuration of the secure attention key // Allow configuration of the secure attention key
// Allow administration of the random device // Allow administration of the random device
@ -263,10 +264,10 @@ const (
// arbitrary SCSI commands // arbitrary SCSI commands
// Allow setting encryption key on loopback filesystem // Allow setting encryption key on loopback filesystem
// Allow setting zone reclaim policy // Allow setting zone reclaim policy
CAP_SYS_ADMIN Cap = 21 CAP_SYS_ADMIN = Cap(21)
// Allow use of reboot() // Allow use of reboot()
CAP_SYS_BOOT Cap = 22 CAP_SYS_BOOT = Cap(22)
// Allow raising priority and setting priority on other (different // Allow raising priority and setting priority on other (different
// UID) processes // UID) processes
@ -274,7 +275,7 @@ const (
// processes and setting the scheduling algorithm used by another // processes and setting the scheduling algorithm used by another
// process. // process.
// Allow setting cpu affinity on other processes // Allow setting cpu affinity on other processes
CAP_SYS_NICE Cap = 23 CAP_SYS_NICE = Cap(23)
// Override resource limits. Set resource limits. // Override resource limits. Set resource limits.
// Override quota limits. // Override quota limits.
@ -287,33 +288,33 @@ const (
// Allow more than 64hz interrupts from the real-time clock // Allow more than 64hz interrupts from the real-time clock
// Override max number of consoles on console allocation // Override max number of consoles on console allocation
// Override max number of keymaps // Override max number of keymaps
CAP_SYS_RESOURCE Cap = 24 CAP_SYS_RESOURCE = Cap(24)
// Allow manipulation of system clock // Allow manipulation of system clock
// Allow irix_stime on mips // Allow irix_stime on mips
// Allow setting the real-time clock // Allow setting the real-time clock
CAP_SYS_TIME Cap = 25 CAP_SYS_TIME = Cap(25)
// Allow configuration of tty devices // Allow configuration of tty devices
// Allow vhangup() of tty // Allow vhangup() of tty
CAP_SYS_TTY_CONFIG Cap = 26 CAP_SYS_TTY_CONFIG = Cap(26)
// Allow the privileged aspects of mknod() // Allow the privileged aspects of mknod()
CAP_MKNOD Cap = 27 CAP_MKNOD = Cap(27)
// Allow taking of leases on files // Allow taking of leases on files
CAP_LEASE Cap = 28 CAP_LEASE = Cap(28)
CAP_AUDIT_WRITE Cap = 29 CAP_AUDIT_WRITE = Cap(29)
CAP_AUDIT_CONTROL Cap = 30 CAP_AUDIT_CONTROL = Cap(30)
CAP_SETFCAP Cap = 31 CAP_SETFCAP = Cap(31)
// Override MAC access. // Override MAC access.
// The base kernel enforces no MAC policy. // The base kernel enforces no MAC policy.
// An LSM may enforce a MAC policy, and if it does and it chooses // An LSM may enforce a MAC policy, and if it does and it chooses
// to implement capability based overrides of that policy, this is // to implement capability based overrides of that policy, this is
// the capability it should use to do so. // the capability it should use to do so.
CAP_MAC_OVERRIDE Cap = 32 CAP_MAC_OVERRIDE = Cap(32)
// Allow MAC configuration or state changes. // Allow MAC configuration or state changes.
// The base kernel requires no MAC configuration. // The base kernel requires no MAC configuration.
@ -321,18 +322,24 @@ const (
// to implement capability based checks on modifications to that // to implement capability based checks on modifications to that
// policy or the data required to maintain it, this is the // policy or the data required to maintain it, this is the
// capability it should use to do so. // capability it should use to do so.
CAP_MAC_ADMIN Cap = 33 CAP_MAC_ADMIN = Cap(33)
// Allow configuring the kernel's syslog (printk behaviour) // Allow configuring the kernel's syslog (printk behaviour)
CAP_SYSLOG Cap = 34 CAP_SYSLOG = Cap(34)
// Allow triggering something that will wake the system // Allow triggering something that will wake the system
CAP_WAKE_ALARM Cap = 35 CAP_WAKE_ALARM = Cap(35)
// Allow preventing system suspends // Allow preventing system suspends
CAP_BLOCK_SUSPEND Cap = 36 CAP_BLOCK_SUSPEND = Cap(36)
CAP_LAST_CAP = CAP_BLOCK_SUSPEND // Allow reading audit messages from the kernel
CAP_AUDIT_READ = Cap(37)
) )
const capUpperMask = (uint32(1) << (uint(CAP_LAST_CAP) - 31)) - 1 var (
// Highest valid capability of the running kernel.
CAP_LAST_CAP = Cap(63)
capUpperMask = ^uint32(0)
)