mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
![Solomon Hykes](/assets/img/avatar_default.png)
plugin. The merge is inert, in other words the devmapper code is not called and the primary aufs backend is untouched.
367 lines
8.6 KiB
Go
367 lines
8.6 KiB
Go
package docker
|
|
|
|
/*
|
|
#include <sys/ioctl.h>
|
|
#include <linux/fs.h>
|
|
#include <errno.h>
|
|
|
|
// See linux.git/fs/btrfs/ioctl.h
|
|
#define BTRFS_IOCTL_MAGIC 0x94
|
|
#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int)
|
|
|
|
int
|
|
btrfs_reflink(int fd_out, int fd_in)
|
|
{
|
|
int res;
|
|
res = ioctl(fd_out, BTRFS_IOC_CLONE, fd_in);
|
|
if (res < 0)
|
|
return errno;
|
|
return 0;
|
|
}
|
|
|
|
*/
|
|
import "C"
|
|
import (
|
|
"fmt"
|
|
"github.com/dotcloud/docker/namesgenerator"
|
|
"github.com/dotcloud/docker/utils"
|
|
"strconv"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields
|
|
// If OpenStdin is set, then it differs
|
|
func CompareConfig(a, b *Config) bool {
|
|
if a == nil || b == nil ||
|
|
a.OpenStdin || b.OpenStdin {
|
|
return false
|
|
}
|
|
if a.AttachStdout != b.AttachStdout ||
|
|
a.AttachStderr != b.AttachStderr ||
|
|
a.User != b.User ||
|
|
a.Memory != b.Memory ||
|
|
a.MemorySwap != b.MemorySwap ||
|
|
a.CpuShares != b.CpuShares ||
|
|
a.OpenStdin != b.OpenStdin ||
|
|
a.Tty != b.Tty ||
|
|
a.VolumesFrom != b.VolumesFrom {
|
|
return false
|
|
}
|
|
if len(a.Cmd) != len(b.Cmd) ||
|
|
len(a.Dns) != len(b.Dns) ||
|
|
len(a.Env) != len(b.Env) ||
|
|
len(a.PortSpecs) != len(b.PortSpecs) ||
|
|
len(a.ExposedPorts) != len(b.ExposedPorts) ||
|
|
len(a.Entrypoint) != len(b.Entrypoint) ||
|
|
len(a.Volumes) != len(b.Volumes) {
|
|
return false
|
|
}
|
|
|
|
for i := 0; i < len(a.Cmd); i++ {
|
|
if a.Cmd[i] != b.Cmd[i] {
|
|
return false
|
|
}
|
|
}
|
|
for i := 0; i < len(a.Dns); i++ {
|
|
if a.Dns[i] != b.Dns[i] {
|
|
return false
|
|
}
|
|
}
|
|
for i := 0; i < len(a.Env); i++ {
|
|
if a.Env[i] != b.Env[i] {
|
|
return false
|
|
}
|
|
}
|
|
for i := 0; i < len(a.PortSpecs); i++ {
|
|
if a.PortSpecs[i] != b.PortSpecs[i] {
|
|
return false
|
|
}
|
|
}
|
|
for k := range a.ExposedPorts {
|
|
if _, exists := b.ExposedPorts[k]; !exists {
|
|
return false
|
|
}
|
|
}
|
|
for i := 0; i < len(a.Entrypoint); i++ {
|
|
if a.Entrypoint[i] != b.Entrypoint[i] {
|
|
return false
|
|
}
|
|
}
|
|
for key := range a.Volumes {
|
|
if _, exists := b.Volumes[key]; !exists {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func MergeConfig(userConf, imageConf *Config) error {
|
|
if userConf.User == "" {
|
|
userConf.User = imageConf.User
|
|
}
|
|
if userConf.Memory == 0 {
|
|
userConf.Memory = imageConf.Memory
|
|
}
|
|
if userConf.MemorySwap == 0 {
|
|
userConf.MemorySwap = imageConf.MemorySwap
|
|
}
|
|
if userConf.CpuShares == 0 {
|
|
userConf.CpuShares = imageConf.CpuShares
|
|
}
|
|
if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 {
|
|
userConf.ExposedPorts = imageConf.ExposedPorts
|
|
}
|
|
|
|
if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 {
|
|
if userConf.ExposedPorts == nil {
|
|
userConf.ExposedPorts = make(map[Port]struct{})
|
|
}
|
|
ports, _, err := parsePortSpecs(userConf.PortSpecs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for port := range ports {
|
|
if _, exists := userConf.ExposedPorts[port]; !exists {
|
|
userConf.ExposedPorts[port] = struct{}{}
|
|
}
|
|
}
|
|
userConf.PortSpecs = nil
|
|
}
|
|
if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 {
|
|
utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", "))
|
|
if userConf.ExposedPorts == nil {
|
|
userConf.ExposedPorts = make(map[Port]struct{})
|
|
}
|
|
|
|
ports, _, err := parsePortSpecs(imageConf.PortSpecs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for port := range ports {
|
|
if _, exists := userConf.ExposedPorts[port]; !exists {
|
|
userConf.ExposedPorts[port] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
if !userConf.Tty {
|
|
userConf.Tty = imageConf.Tty
|
|
}
|
|
if !userConf.OpenStdin {
|
|
userConf.OpenStdin = imageConf.OpenStdin
|
|
}
|
|
if !userConf.StdinOnce {
|
|
userConf.StdinOnce = imageConf.StdinOnce
|
|
}
|
|
if userConf.Env == nil || len(userConf.Env) == 0 {
|
|
userConf.Env = imageConf.Env
|
|
} else {
|
|
for _, imageEnv := range imageConf.Env {
|
|
found := false
|
|
imageEnvKey := strings.Split(imageEnv, "=")[0]
|
|
for _, userEnv := range userConf.Env {
|
|
userEnvKey := strings.Split(userEnv, "=")[0]
|
|
if imageEnvKey == userEnvKey {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
userConf.Env = append(userConf.Env, imageEnv)
|
|
}
|
|
}
|
|
}
|
|
if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
|
|
userConf.Cmd = imageConf.Cmd
|
|
}
|
|
if userConf.Dns == nil || len(userConf.Dns) == 0 {
|
|
userConf.Dns = imageConf.Dns
|
|
} else {
|
|
//duplicates aren't an issue here
|
|
userConf.Dns = append(userConf.Dns, imageConf.Dns...)
|
|
}
|
|
if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 {
|
|
userConf.Entrypoint = imageConf.Entrypoint
|
|
}
|
|
if userConf.WorkingDir == "" {
|
|
userConf.WorkingDir = imageConf.WorkingDir
|
|
}
|
|
if userConf.VolumesFrom == "" {
|
|
userConf.VolumesFrom = imageConf.VolumesFrom
|
|
}
|
|
if userConf.Volumes == nil || len(userConf.Volumes) == 0 {
|
|
userConf.Volumes = imageConf.Volumes
|
|
} else {
|
|
for k, v := range imageConf.Volumes {
|
|
userConf.Volumes[k] = v
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseLxcConfOpts(opts utils.ListOpts) ([]KeyValuePair, error) {
|
|
out := make([]KeyValuePair, len(opts))
|
|
for i, o := range opts {
|
|
k, v, err := parseLxcOpt(o)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out[i] = KeyValuePair{Key: k, Value: v}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func parseLxcOpt(opt string) (string, string, error) {
|
|
parts := strings.SplitN(opt, "=", 2)
|
|
if len(parts) != 2 {
|
|
return "", "", fmt.Errorf("Unable to parse lxc conf option: %s", opt)
|
|
}
|
|
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
|
|
}
|
|
|
|
// We will receive port specs in the format of ip:public:private/proto and these need to be
|
|
// parsed in the internal types
|
|
func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) {
|
|
exposedPorts := make(map[Port]struct{}, len(ports))
|
|
bindings := make(map[Port][]PortBinding)
|
|
|
|
for _, rawPort := range ports {
|
|
proto := "tcp"
|
|
if i := strings.LastIndex(rawPort, "/"); i != -1 {
|
|
proto = rawPort[i+1:]
|
|
rawPort = rawPort[:i]
|
|
}
|
|
if !strings.Contains(rawPort, ":") {
|
|
rawPort = fmt.Sprintf("::%s", rawPort)
|
|
} else if len(strings.Split(rawPort, ":")) == 2 {
|
|
rawPort = fmt.Sprintf(":%s", rawPort)
|
|
}
|
|
|
|
parts, err := utils.PartParser("ip:hostPort:containerPort", rawPort)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
containerPort := parts["containerPort"]
|
|
rawIp := parts["ip"]
|
|
hostPort := parts["hostPort"]
|
|
|
|
if containerPort == "" {
|
|
return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
|
|
}
|
|
|
|
port := NewPort(proto, containerPort)
|
|
if _, exists := exposedPorts[port]; !exists {
|
|
exposedPorts[port] = struct{}{}
|
|
}
|
|
|
|
binding := PortBinding{
|
|
HostIp: rawIp,
|
|
HostPort: hostPort,
|
|
}
|
|
bslice, exists := bindings[port]
|
|
if !exists {
|
|
bslice = []PortBinding{}
|
|
}
|
|
bindings[port] = append(bslice, binding)
|
|
}
|
|
return exposedPorts, bindings, nil
|
|
}
|
|
|
|
// Splits a port in the format of port/proto
|
|
func splitProtoPort(rawPort string) (string, string) {
|
|
parts := strings.Split(rawPort, "/")
|
|
l := len(parts)
|
|
if l == 0 {
|
|
return "", ""
|
|
}
|
|
if l == 1 {
|
|
return "tcp", rawPort
|
|
}
|
|
return parts[0], parts[1]
|
|
}
|
|
|
|
func parsePort(rawPort string) (int, error) {
|
|
port, err := strconv.ParseUint(rawPort, 10, 16)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return int(port), nil
|
|
}
|
|
|
|
func migratePortMappings(config *Config, hostConfig *HostConfig) error {
|
|
if config.PortSpecs != nil {
|
|
ports, bindings, err := parsePortSpecs(config.PortSpecs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
config.PortSpecs = nil
|
|
if len(bindings) > 0 {
|
|
if hostConfig == nil {
|
|
hostConfig = &HostConfig{}
|
|
}
|
|
hostConfig.PortBindings = bindings
|
|
}
|
|
|
|
if config.ExposedPorts == nil {
|
|
config.ExposedPorts = make(map[Port]struct{}, len(ports))
|
|
}
|
|
for k, v := range ports {
|
|
config.ExposedPorts[k] = v
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func RootIsShared() bool {
|
|
if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil {
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
cols := strings.Split(line, " ")
|
|
if len(cols) >= 6 && cols[4] == "/" {
|
|
return strings.HasPrefix(cols[6], "shared")
|
|
}
|
|
}
|
|
}
|
|
|
|
// No idea, probably safe to assume so
|
|
return true
|
|
}
|
|
|
|
func BtrfsReflink(fd_out, fd_in uintptr) error {
|
|
res := C.btrfs_reflink(C.int(fd_out), C.int(fd_in))
|
|
if res != 0 {
|
|
return syscall.Errno(res)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Links come in the format of
|
|
// name:alias
|
|
func parseLink(rawLink string) (map[string]string, error) {
|
|
return utils.PartParser("name:alias", rawLink)
|
|
}
|
|
|
|
type checker struct {
|
|
runtime *Runtime
|
|
}
|
|
|
|
func (c *checker) Exists(name string) bool {
|
|
return c.runtime.containerGraph.Exists("/" + name)
|
|
}
|
|
|
|
// Generate a random and unique name
|
|
func generateRandomName(runtime *Runtime) (string, error) {
|
|
return namesgenerator.GenerateRandomName(&checker{runtime})
|
|
}
|
|
|
|
func CopyFile(dstFile, srcFile *os.File) error {
|
|
err := BtrfsReflink(dstFile.Fd(), srcFile.Fd())
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
// Fall back to normal copy
|
|
_, err = io.Copy(dstFile, srcFile)
|
|
return err
|
|
}
|