-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
executable file
·152 lines (132 loc) · 4.04 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package main
/* Created by Meng Chen
* Build a container from scratch
* Expect to build a container which could run all bash commands inside
* E-mail: [email protected]
*/
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"syscall"
)
//first time
func run() {
//The second argument and the successive args are the targer command needed to run
fmt.Printf("Runnning command: %v as %d\n", os.Args[2:], os.Getpid())
//Program 1
//Golang package exec runs external commands. (Like exec in C?)
//os/exec package do not invoke any system shell or something like pipe and so on.
//it just like 'exec' in C
//Type cmd and func Command returns the Cmd struct to execute the named program
//with given arguments. (In this case, os.Args[2] is the program name)
// cmd := exec.Command(os.Args[2], os.Args[3:]...) // use '...' to split args.
//Program 2
args := os.Args
args[0] = "/proc/self/exe" //The file always links to the currenly executable
args[1] = "child"
cmd := exec.Command(args[0], args[1:]...)
//NEWUST isolates system identifier like hostname...
//NEWPID isolates process id number
//
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,
Unshareflags: syscall.CLONE_NEWNS,
}
//golang os/exec wrapped os.StartProcess to make it easier to remap stdin and stdout.
//eg. we can remap easily like
//cmd.Stdin = strings.NewReader("input example")
//but we don't need to remap in this situation
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
//just run
err := cmd.Run()
if err != nil {
panic(fmt.Sprintf("Running error: %v\n", err))
}
}
//second time, actually run the expected command
func child() {
fmt.Printf("Running %v as %d\n", os.Args[2:], os.Getpid())
cgroup()
// fmt.Println("Check executable path", cmd.Path)
//set hostname in every command we executed
err := syscall.Sethostname([]byte("tiny-container"))
if err != nil {
panic(fmt.Sprintf("Sethostname: %v\n", err))
}
//chroot
err = syscall.Chroot("/home/meng/projects/tiny-container/ubuntu")
if err != nil {
panic(fmt.Sprintf("Chroot: %v\n", err))
}
//chdir, change to new root directory
err = syscall.Chdir("/")
if err != nil {
panic(fmt.Sprintf("Chdir: %v\n", err))
}
// syscall.Setenv("PATH", os.Getenv("PATH"))
// fmt.Println(os.Getenv("PATH"))
err = syscall.Mount("proc", "proc", "proc", 0, "")
if err != nil {
panic(fmt.Sprintf("mount: %v\n", err))
}
cmd := exec.Command(os.Args[2], os.Args[3:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
//Ubuntu's executables file paths are different from alpine
//So after chroot and chdir, we need to use LookPath to
//get the program path and make cmd.Path equals progPath
// progPath, err := exec.LookPath(os.Args[2])
// cmd.Path = progPath
err = cmd.Run()
if err != nil {
panic(fmt.Sprintf("running: %v\n", err))
}
err = syscall.Unmount("/proc", 0)
if err != nil {
panic(fmt.Sprintf("Umount: %v\n", err))
}
}
func main() {
switch os.Args[1] {
//assume "run" is the first argument for start.
case "run":
run()
case "child":
child()
default:
//if the first argument is not "run", then trigger a panic
panic("BAD COMMAND!!! (the first argument must be \"run\")")
}
fmt.Println("Golang program exited.")
}
func cgroup() {
cgroups := "/sys/fs/cgroup"
pids := filepath.Join(cgroups, "pids")
err := os.Mkdir(filepath.Join(pids, "tiny"), 0755)
if err != nil && !os.IsExist(err) {
panic(fmt.Sprintf("Mkdir: %v\n", err))
}
// Inside 'tiny' control group, there can only be 20 processes.
err = ioutil.WriteFile(filepath.Join(pids, "tiny/pids.max"), []byte("20"), 0700)
if err != nil {
panic(err)
}
//
err = ioutil.WriteFile(filepath.Join(pids, "tiny/notify_on_release"), []byte("1"), 0700)
if err != nil {
panic(err)
}
// This line add the present process into 'tiny' control group
// so it subject the same limits.
err = ioutil.WriteFile(filepath.Join(pids, "tiny/cgroup.procs"), []byte(strconv.Itoa(os.Getpid())), 0700)
if err != nil {
panic(err)
}
}