github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/integration/utils_test.go (about) 1 package integration 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "regexp" 10 "runtime" 11 "strconv" 12 "strings" 13 "syscall" 14 "testing" 15 "time" 16 17 "github.com/opencontainers/runc/libcontainer" 18 "github.com/opencontainers/runc/libcontainer/configs" 19 ) 20 21 var busyboxTar string 22 23 // init makes sure the container images are downloaded, 24 // and initializes busyboxTar. If images can't be downloaded, 25 // we are unable to run any tests, so panic. 26 func init() { 27 // Figure out path to get-images.sh. Note it won't work 28 // in case the compiled test binary is moved elsewhere. 29 _, ex, _, _ := runtime.Caller(0) 30 getImages, err := filepath.Abs(filepath.Join(filepath.Dir(ex), "..", "..", "tests", "integration", "get-images.sh")) 31 if err != nil { 32 panic(err) 33 } 34 // Call it to make sure images are downloaded, and to get the paths. 35 out, err := exec.Command(getImages).CombinedOutput() 36 if err != nil { 37 panic(fmt.Errorf("getImages error %w (output: %s)", err, out)) 38 } 39 // Extract the value of BUSYBOX_IMAGE. 40 found := regexp.MustCompile(`(?m)^BUSYBOX_IMAGE=(.*)$`).FindSubmatchIndex(out) 41 if len(found) < 4 { 42 panic(fmt.Errorf("unable to find BUSYBOX_IMAGE=<value> in %q", out)) 43 } 44 busyboxTar = string(out[found[2]:found[3]]) 45 // Finally, check the file is present 46 if _, err := os.Stat(busyboxTar); err != nil { 47 panic(err) 48 } 49 } 50 51 func ptrInt(v int) *int { 52 return &v 53 } 54 55 func newStdBuffers() *stdBuffers { 56 return &stdBuffers{ 57 Stdin: bytes.NewBuffer(nil), 58 Stdout: bytes.NewBuffer(nil), 59 Stderr: bytes.NewBuffer(nil), 60 } 61 } 62 63 type stdBuffers struct { 64 Stdin *bytes.Buffer 65 Stdout *bytes.Buffer 66 Stderr *bytes.Buffer 67 } 68 69 func (b *stdBuffers) String() string { 70 s := []string{} 71 if b.Stderr != nil { 72 s = append(s, b.Stderr.String()) 73 } 74 if b.Stdout != nil { 75 s = append(s, b.Stdout.String()) 76 } 77 return strings.Join(s, "|") 78 } 79 80 // ok fails the test if an err is not nil. 81 func ok(t testing.TB, err error) { 82 t.Helper() 83 if err != nil { 84 t.Fatalf("unexpected error: %v", err) 85 } 86 } 87 88 func waitProcess(p *libcontainer.Process, t *testing.T) { 89 t.Helper() 90 status, err := p.Wait() 91 if err != nil { 92 t.Fatalf("unexpected error: %v", err) 93 } 94 95 if !status.Success() { 96 t.Fatalf("unexpected status: %v", status) 97 } 98 } 99 100 // newRootfs creates a new tmp directory and copies the busybox root 101 // filesystem to it. 102 func newRootfs(t *testing.T) string { 103 t.Helper() 104 dir := t.TempDir() 105 if err := copyBusybox(dir); err != nil { 106 t.Fatal(err) 107 } 108 109 // Make sure others can read+exec, so all tests (inside userns too) can 110 // read the rootfs. 111 if err := traversePath(dir); err != nil { 112 t.Fatalf("Error making newRootfs path traversable by others: %v", err) 113 } 114 115 return dir 116 } 117 118 // traversePath gives read+execute permissions to others for all elements in tPath below 119 // os.TempDir() and errors out if elements above it don't have read+exec permissions for others. 120 // tPath MUST be a descendant of os.TempDir(). The path returned by testing.TempDir() usually is. 121 func traversePath(tPath string) error { 122 // Check the assumption that the argument is under os.TempDir(). 123 tempBase := os.TempDir() 124 if !strings.HasPrefix(tPath, tempBase) { 125 return fmt.Errorf("traversePath: %q is not a descendant of %q", tPath, tempBase) 126 } 127 128 var path string 129 for _, p := range strings.SplitAfter(tPath, "/") { 130 path = path + p 131 stats, err := os.Stat(path) 132 if err != nil { 133 return err 134 } 135 136 perm := stats.Mode().Perm() 137 138 if perm&0o5 == 0o5 { 139 continue 140 } 141 142 if strings.HasPrefix(tempBase, path) { 143 return fmt.Errorf("traversePath: directory %q MUST have read+exec permissions for others", path) 144 } 145 146 if err := os.Chmod(path, perm|0o5); err != nil { 147 return err 148 } 149 } 150 151 return nil 152 } 153 154 func remove(dir string) { 155 _ = os.RemoveAll(dir) 156 } 157 158 // copyBusybox copies the rootfs for a busybox container created for the test image 159 // into the new directory for the specific test 160 func copyBusybox(dest string) error { 161 out, err := exec.Command("sh", "-c", fmt.Sprintf("tar --exclude './dev/*' -C %q -xf %q", dest, busyboxTar)).CombinedOutput() 162 if err != nil { 163 return fmt.Errorf("untar error %w: %q", err, out) 164 } 165 return nil 166 } 167 168 func newContainer(t *testing.T, config *configs.Config) (*libcontainer.Container, error) { 169 name := strings.ReplaceAll(t.Name(), "/", "_") + strconv.FormatInt(-int64(time.Now().Nanosecond()), 35) 170 root := t.TempDir() 171 172 return libcontainer.Create(root, name, config) 173 } 174 175 // runContainer runs the container with the specific config and arguments 176 // 177 // buffers are returned containing the STDOUT and STDERR output for the run 178 // along with the exit code and any go error 179 func runContainer(t *testing.T, config *configs.Config, args ...string) (buffers *stdBuffers, exitCode int, err error) { 180 container, err := newContainer(t, config) 181 if err != nil { 182 return nil, -1, err 183 } 184 defer destroyContainer(container) 185 buffers = newStdBuffers() 186 process := &libcontainer.Process{ 187 Cwd: "/", 188 Args: args, 189 Env: standardEnvironment, 190 Stdin: buffers.Stdin, 191 Stdout: buffers.Stdout, 192 Stderr: buffers.Stderr, 193 Init: true, 194 } 195 196 err = container.Run(process) 197 if err != nil { 198 return buffers, -1, err 199 } 200 ps, err := process.Wait() 201 if err != nil { 202 return buffers, -1, err 203 } 204 status := ps.Sys().(syscall.WaitStatus) 205 if status.Exited() { 206 exitCode = status.ExitStatus() 207 } else if status.Signaled() { 208 exitCode = -int(status.Signal()) 209 } else { 210 return buffers, -1, err 211 } 212 return 213 } 214 215 // runContainerOk is a wrapper for runContainer, simplifying its use for cases 216 // when the run is expected to succeed and return exit code of 0. 217 func runContainerOk(t *testing.T, config *configs.Config, args ...string) *stdBuffers { 218 buffers, exitCode, err := runContainer(t, config, args...) 219 220 t.Helper() 221 if err != nil { 222 t.Fatalf("%s: %s", buffers, err) 223 } 224 if exitCode != 0 { 225 t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) 226 } 227 228 return buffers 229 } 230 231 func destroyContainer(container *libcontainer.Container) { 232 _ = container.Destroy() 233 }