1// +build linux
2
3package main
4
5import (
6	"encoding/json"
7	"fmt"
8	"io/ioutil"
9	"os"
10	"runtime"
11
12	"github.com/codegangsta/cli"
13	"github.com/opencontainers/runc/libcontainer/configs"
14	"github.com/opencontainers/runtime-spec/specs-go"
15)
16
17var specCommand = cli.Command{
18	Name:      "spec",
19	Usage:     "create a new specification file",
20	ArgsUsage: "",
21	Description: `The spec command creates the new specification file named "` + specConfig + `" for
22the bundle.
23
24The spec generated is just a starter file. Editing of the spec is required to
25achieve desired results. For example, the newly generated spec includes an args
26parameter that is initially set to call the "sh" command when the container is
27started. Calling "sh" may work for an ubuntu container or busybox, but will not
28work for containers that do not include the "sh" program.
29
30EXAMPLE:
31  To run docker's hello-world container one needs to set the args parameter
32in the spec to call hello. This can be done using the sed command or a text
33editor. The following commands create a bundle for hello-world, change the
34default args parameter in the spec from "sh" to "/hello", then run the hello
35command in a new hello-world container named container1:
36
37    mkdir hello
38    cd hello
39    docker pull hello-world
40    docker export $(docker create hello-world) > hello-world.tar
41    mkdir rootfs
42    tar -C rootfs -xf hello-world.tar
43    runc spec
44    sed -i 's;"sh";"/hello";' ` + specConfig + `
45    runc start container1
46
47In the start command above, "container1" is the name for the instance of the
48container that you are starting. The name you provide for the container instance
49must be unique on your host.
50
51When starting a container through runc, runc needs root privilege. If not
52already running as root, you can use sudo to give runc root privilege. For
53example: "sudo runc start container1" will give runc root privilege to start the
54container on your host.`,
55	Flags: []cli.Flag{
56		cli.StringFlag{
57			Name:  "bundle, b",
58			Value: "",
59			Usage: "path to the root of the bundle directory",
60		},
61	},
62	Action: func(context *cli.Context) {
63		spec := specs.Spec{
64			Version: specs.Version,
65			Platform: specs.Platform{
66				OS:   runtime.GOOS,
67				Arch: runtime.GOARCH,
68			},
69			Root: specs.Root{
70				Path:     "rootfs",
71				Readonly: true,
72			},
73			Process: specs.Process{
74				Terminal: true,
75				User:     specs.User{},
76				Args: []string{
77					"sh",
78				},
79				Env: []string{
80					"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
81					"TERM=xterm",
82				},
83				Cwd:             "/",
84				NoNewPrivileges: true,
85				Capabilities: []string{
86					"CAP_AUDIT_WRITE",
87					"CAP_KILL",
88					"CAP_NET_BIND_SERVICE",
89				},
90				Rlimits: []specs.Rlimit{
91					{
92						Type: "RLIMIT_NOFILE",
93						Hard: uint64(1024),
94						Soft: uint64(1024),
95					},
96				},
97			},
98			Hostname: "runc",
99			Mounts: []specs.Mount{
100				{
101					Destination: "/proc",
102					Type:        "proc",
103					Source:      "proc",
104					Options:     nil,
105				},
106				{
107					Destination: "/dev",
108					Type:        "tmpfs",
109					Source:      "tmpfs",
110					Options:     []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
111				},
112				{
113					Destination: "/dev/pts",
114					Type:        "devpts",
115					Source:      "devpts",
116					Options:     []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
117				},
118				{
119					Destination: "/dev/shm",
120					Type:        "tmpfs",
121					Source:      "shm",
122					Options:     []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
123				},
124				{
125					Destination: "/dev/mqueue",
126					Type:        "mqueue",
127					Source:      "mqueue",
128					Options:     []string{"nosuid", "noexec", "nodev"},
129				},
130				{
131					Destination: "/sys",
132					Type:        "sysfs",
133					Source:      "sysfs",
134					Options:     []string{"nosuid", "noexec", "nodev", "ro"},
135				},
136				{
137					Destination: "/sys/fs/cgroup",
138					Type:        "cgroup",
139					Source:      "cgroup",
140					Options:     []string{"nosuid", "noexec", "nodev", "relatime", "ro"},
141				},
142			},
143			Linux: specs.Linux{
144				MaskedPaths: []string{
145					"/proc/kcore",
146					"/proc/latency_stats",
147					"/proc/timer_stats",
148					"/proc/sched_debug",
149				},
150				ReadonlyPaths: []string{
151					"/proc/asound",
152					"/proc/bus",
153					"/proc/fs",
154					"/proc/irq",
155					"/proc/sys",
156					"/proc/sysrq-trigger",
157				},
158				Resources: &specs.Resources{
159					Devices: []specs.DeviceCgroup{
160						{
161							Allow:  false,
162							Access: sPtr("rwm"),
163						},
164					},
165				},
166				Namespaces: []specs.Namespace{
167					{
168						Type: "pid",
169					},
170					{
171						Type: "network",
172					},
173					{
174						Type: "ipc",
175					},
176					{
177						Type: "uts",
178					},
179					{
180						Type: "mount",
181					},
182				},
183			},
184		}
185
186		checkNoFile := func(name string) error {
187			_, err := os.Stat(name)
188			if err == nil {
189				return fmt.Errorf("File %s exists. Remove it first", name)
190			}
191			if !os.IsNotExist(err) {
192				return err
193			}
194			return nil
195		}
196		bundle := context.String("bundle")
197		if bundle != "" {
198			if err := os.Chdir(bundle); err != nil {
199				fatal(err)
200			}
201		}
202		if err := checkNoFile(specConfig); err != nil {
203			fatal(err)
204		}
205		data, err := json.MarshalIndent(&spec, "", "\t")
206		if err != nil {
207			fatal(err)
208		}
209		if err := ioutil.WriteFile(specConfig, data, 0666); err != nil {
210			fatal(err)
211		}
212	},
213}
214
215func sPtr(s string) *string      { return &s }
216func rPtr(r rune) *rune          { return &r }
217func iPtr(i int64) *int64        { return &i }
218func u32Ptr(i int64) *uint32     { u := uint32(i); return &u }
219func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
220
221// loadSpec loads the specification from the provided path.
222// If the path is empty then the default path will be "config.json"
223func loadSpec(cPath string) (spec *specs.Spec, err error) {
224	cf, err := os.Open(cPath)
225	if err != nil {
226		if os.IsNotExist(err) {
227			return nil, fmt.Errorf("JSON specification file %s not found", cPath)
228		}
229		return nil, err
230	}
231	defer cf.Close()
232
233	if err = json.NewDecoder(cf).Decode(&spec); err != nil {
234		return nil, err
235	}
236	return spec, validateProcessSpec(&spec.Process)
237}
238
239func createLibContainerRlimit(rlimit specs.Rlimit) (configs.Rlimit, error) {
240	rl, err := strToRlimit(rlimit.Type)
241	if err != nil {
242		return configs.Rlimit{}, err
243	}
244	return configs.Rlimit{
245		Type: rl,
246		Hard: uint64(rlimit.Hard),
247		Soft: uint64(rlimit.Soft),
248	}, nil
249}
250