1package houdini
2
3import (
4	"fmt"
5	"os"
6	"os/exec"
7	"path/filepath"
8	"strings"
9	"syscall"
10
11	"code.cloudfoundry.org/garden"
12)
13
14func (container *container) setup() error {
15	if container.hasRootfs {
16		for _, dir := range []string{"/proc", "/dev", "/sys"} {
17			dest := filepath.Join(container.workDir, dir)
18
19			err := os.MkdirAll(dest, 0755)
20			if err != nil {
21				return fmt.Errorf("failed to create target for bind mount: %s", err)
22			}
23
24			err = syscall.Mount(dir, dest, "none", syscall.MS_BIND|syscall.MS_RDONLY, "")
25			if err != nil {
26				return err
27			}
28		}
29
30		for _, file := range []string{"/etc/resolv.conf", "/etc/hosts"} {
31			dest := filepath.Join(container.workDir, file)
32
33			f, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, 0644)
34			if err != nil {
35				return fmt.Errorf("failed to create target for bind mount: %s", err)
36			}
37
38			err = f.Close()
39			if err != nil {
40				return err
41			}
42
43			err = syscall.Mount(file, dest, "none", syscall.MS_BIND|syscall.MS_RDONLY, "")
44			if err != nil {
45				return err
46			}
47		}
48	}
49
50	for _, bm := range container.spec.BindMounts {
51		dest := filepath.Join(container.workDir, bm.DstPath)
52
53		err := os.MkdirAll(dest, 0755)
54		if err != nil {
55			return fmt.Errorf("failed to create target for bind mount: %s", err)
56		}
57
58		flags := uintptr(syscall.MS_BIND)
59		if bm.Mode == garden.BindMountModeRO {
60			flags |= syscall.MS_RDONLY
61		}
62
63		err = syscall.Mount(bm.SrcPath, dest, "none", flags, "")
64		if err != nil {
65			return err
66		}
67	}
68
69	return nil
70}
71
72const defaultRootPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
73// const defaultPath = "/usr/local/bin:/usr/bin:/bin"
74
75func (container *container) path() string {
76	var path string
77	for _, env := range container.env {
78		segs := strings.SplitN(env, "=", 2)
79		if len(segs) < 2 {
80			continue
81		}
82
83		if segs[0] == "PATH" {
84			path = segs[1]
85		}
86	}
87
88	if !container.hasRootfs {
89		if path == "" {
90			path = os.Getenv("PATH")
91		}
92
93		return path
94	}
95
96	if path == "" {
97		// assume running as root for now, since Houdini doesn't currently support
98		// running as a user
99		path = defaultRootPath
100	}
101
102	var scopedPath string
103	for _, dir := range filepath.SplitList(path) {
104		if scopedPath != "" {
105			scopedPath += string(filepath.ListSeparator)
106		}
107
108		scopedPath += container.workDir + dir
109	}
110
111	return scopedPath
112}
113
114func (container *container) cmd(spec garden.ProcessSpec) (*exec.Cmd, error) {
115	var cmd *exec.Cmd
116
117	if container.hasRootfs {
118		path := spec.Path
119
120		if !strings.Contains(path, "/") {
121			// find executable within container's $PATH
122
123			absPath, err := lookPath(path, container.path())
124			if err != nil {
125				return nil, garden.ExecutableNotFoundError{
126					Message: err.Error(),
127				}
128			}
129
130			// correct path so that it's absolute from the rootfs
131			path = strings.TrimPrefix(absPath, container.workDir)
132		}
133
134		cmd = exec.Command(path, spec.Args...)
135
136		if spec.Dir != "" {
137			cmd.Dir = spec.Dir
138		} else {
139			cmd.Dir = "/"
140		}
141
142		cmd.SysProcAttr = &syscall.SysProcAttr{
143			Chroot: container.workDir,
144		}
145	} else {
146		cmd = exec.Command(spec.Path, spec.Args...)
147		cmd.Dir = filepath.Join(container.workDir, spec.Dir)
148	}
149
150	cmd.Env = append(os.Environ(), append(container.env, spec.Env...)...)
151
152	return cmd, nil
153}
154
155func findExecutable(file string) error {
156	d, err := os.Stat(file)
157	if err != nil {
158		return err
159	}
160	if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
161		return nil
162	}
163	return os.ErrPermission
164}
165
166// based on exec.LookPath from stdlib
167func lookPath(file string, path string) (string, error) {
168	if strings.Contains(file, "/") {
169		err := findExecutable(file)
170		if err == nil {
171			return file, nil
172		}
173		return "", &exec.Error{Name: file, Err: err}
174	}
175
176	for _, dir := range filepath.SplitList(path) {
177		if dir == "" {
178			// Unix shell semantics: path element "" means "."
179			dir = "."
180		}
181		path := filepath.Join(dir, file)
182		if err := findExecutable(path); err == nil {
183			return path, nil
184		}
185	}
186
187	return "", &exec.Error{Name: file, Err: exec.ErrNotFound}
188}
189