github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/archive/archive_linux_test.go (about) 1 package archive // import "github.com/demonoid81/moby/pkg/archive" 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "syscall" 10 "testing" 11 12 "github.com/demonoid81/moby/pkg/reexec" 13 "github.com/demonoid81/moby/pkg/system" 14 "github.com/moby/sys/mount" 15 rsystem "github.com/opencontainers/runc/libcontainer/system" 16 "github.com/pkg/errors" 17 "golang.org/x/sys/unix" 18 "gotest.tools/v3/assert" 19 "gotest.tools/v3/skip" 20 ) 21 22 // setupOverlayTestDir creates files in a directory with overlay whiteouts 23 // Tree layout 24 // . 25 // ├── d1 # opaque, 0700 26 // │ └── f1 # empty file, 0600 27 // ├── d2 # opaque, 0750 28 // │ └── f1 # empty file, 0660 29 // └── d3 # 0700 30 // └── f1 # whiteout, 0644 31 func setupOverlayTestDir(t *testing.T, src string) { 32 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 33 skip.If(t, rsystem.RunningInUserNS(), "skipping test that requires initial userns (trusted.overlay.opaque xattr cannot be set in userns, even with Ubuntu kernel)") 34 // Create opaque directory containing single file and permission 0700 35 err := os.Mkdir(filepath.Join(src, "d1"), 0700) 36 assert.NilError(t, err) 37 38 err = system.Lsetxattr(filepath.Join(src, "d1"), "trusted.overlay.opaque", []byte("y"), 0) 39 assert.NilError(t, err) 40 41 err = ioutil.WriteFile(filepath.Join(src, "d1", "f1"), []byte{}, 0600) 42 assert.NilError(t, err) 43 44 // Create another opaque directory containing single file but with permission 0750 45 err = os.Mkdir(filepath.Join(src, "d2"), 0750) 46 assert.NilError(t, err) 47 48 err = system.Lsetxattr(filepath.Join(src, "d2"), "trusted.overlay.opaque", []byte("y"), 0) 49 assert.NilError(t, err) 50 51 err = ioutil.WriteFile(filepath.Join(src, "d2", "f1"), []byte{}, 0660) 52 assert.NilError(t, err) 53 54 // Create regular directory with deleted file 55 err = os.Mkdir(filepath.Join(src, "d3"), 0700) 56 assert.NilError(t, err) 57 58 err = system.Mknod(filepath.Join(src, "d3", "f1"), unix.S_IFCHR, 0) 59 assert.NilError(t, err) 60 } 61 62 func checkOpaqueness(t *testing.T, path string, opaque string) { 63 xattrOpaque, err := system.Lgetxattr(path, "trusted.overlay.opaque") 64 assert.NilError(t, err) 65 66 if string(xattrOpaque) != opaque { 67 t.Fatalf("Unexpected opaque value: %q, expected %q", string(xattrOpaque), opaque) 68 } 69 70 } 71 72 func checkOverlayWhiteout(t *testing.T, path string) { 73 stat, err := os.Stat(path) 74 assert.NilError(t, err) 75 76 statT, ok := stat.Sys().(*syscall.Stat_t) 77 if !ok { 78 t.Fatalf("Unexpected type: %t, expected *syscall.Stat_t", stat.Sys()) 79 } 80 if statT.Rdev != 0 { 81 t.Fatalf("Non-zero device number for whiteout") 82 } 83 } 84 85 func checkFileMode(t *testing.T, path string, perm os.FileMode) { 86 stat, err := os.Stat(path) 87 assert.NilError(t, err) 88 89 if stat.Mode() != perm { 90 t.Fatalf("Unexpected file mode for %s: %o, expected %o", path, stat.Mode(), perm) 91 } 92 } 93 94 func TestOverlayTarUntar(t *testing.T) { 95 oldmask, err := system.Umask(0) 96 assert.NilError(t, err) 97 defer system.Umask(oldmask) 98 99 src, err := ioutil.TempDir("", "docker-test-overlay-tar-src") 100 assert.NilError(t, err) 101 defer os.RemoveAll(src) 102 103 setupOverlayTestDir(t, src) 104 105 dst, err := ioutil.TempDir("", "docker-test-overlay-tar-dst") 106 assert.NilError(t, err) 107 defer os.RemoveAll(dst) 108 109 options := &TarOptions{ 110 Compression: Uncompressed, 111 WhiteoutFormat: OverlayWhiteoutFormat, 112 } 113 archive, err := TarWithOptions(src, options) 114 assert.NilError(t, err) 115 defer archive.Close() 116 117 err = Untar(archive, dst, options) 118 assert.NilError(t, err) 119 120 checkFileMode(t, filepath.Join(dst, "d1"), 0700|os.ModeDir) 121 checkFileMode(t, filepath.Join(dst, "d2"), 0750|os.ModeDir) 122 checkFileMode(t, filepath.Join(dst, "d3"), 0700|os.ModeDir) 123 checkFileMode(t, filepath.Join(dst, "d1", "f1"), 0600) 124 checkFileMode(t, filepath.Join(dst, "d2", "f1"), 0660) 125 checkFileMode(t, filepath.Join(dst, "d3", "f1"), os.ModeCharDevice|os.ModeDevice) 126 127 checkOpaqueness(t, filepath.Join(dst, "d1"), "y") 128 checkOpaqueness(t, filepath.Join(dst, "d2"), "y") 129 checkOpaqueness(t, filepath.Join(dst, "d3"), "") 130 checkOverlayWhiteout(t, filepath.Join(dst, "d3", "f1")) 131 } 132 133 func TestOverlayTarAUFSUntar(t *testing.T) { 134 oldmask, err := system.Umask(0) 135 assert.NilError(t, err) 136 defer system.Umask(oldmask) 137 138 src, err := ioutil.TempDir("", "docker-test-overlay-tar-src") 139 assert.NilError(t, err) 140 defer os.RemoveAll(src) 141 142 setupOverlayTestDir(t, src) 143 144 dst, err := ioutil.TempDir("", "docker-test-overlay-tar-dst") 145 assert.NilError(t, err) 146 defer os.RemoveAll(dst) 147 148 archive, err := TarWithOptions(src, &TarOptions{ 149 Compression: Uncompressed, 150 WhiteoutFormat: OverlayWhiteoutFormat, 151 }) 152 assert.NilError(t, err) 153 defer archive.Close() 154 155 err = Untar(archive, dst, &TarOptions{ 156 Compression: Uncompressed, 157 WhiteoutFormat: AUFSWhiteoutFormat, 158 }) 159 assert.NilError(t, err) 160 161 checkFileMode(t, filepath.Join(dst, "d1"), 0700|os.ModeDir) 162 checkFileMode(t, filepath.Join(dst, "d1", WhiteoutOpaqueDir), 0700) 163 checkFileMode(t, filepath.Join(dst, "d2"), 0750|os.ModeDir) 164 checkFileMode(t, filepath.Join(dst, "d2", WhiteoutOpaqueDir), 0750) 165 checkFileMode(t, filepath.Join(dst, "d3"), 0700|os.ModeDir) 166 checkFileMode(t, filepath.Join(dst, "d1", "f1"), 0600) 167 checkFileMode(t, filepath.Join(dst, "d2", "f1"), 0660) 168 checkFileMode(t, filepath.Join(dst, "d3", WhiteoutPrefix+"f1"), 0600) 169 } 170 171 func unshareCmd(cmd *exec.Cmd) { 172 cmd.SysProcAttr = &syscall.SysProcAttr{ 173 Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS, 174 UidMappings: []syscall.SysProcIDMap{ 175 { 176 ContainerID: 0, 177 HostID: os.Geteuid(), 178 Size: 1, 179 }, 180 }, 181 GidMappings: []syscall.SysProcIDMap{ 182 { 183 ContainerID: 0, 184 HostID: os.Getegid(), 185 Size: 1, 186 }, 187 }, 188 } 189 } 190 191 const ( 192 reexecSupportsUserNSOverlay = "docker-test-supports-userns-overlay" 193 reexecMknodChar0 = "docker-test-userns-mknod-char0" 194 reexecSetOpaque = "docker-test-userns-set-opaque" 195 ) 196 197 func supportsOverlay(dir string) error { 198 lower := filepath.Join(dir, "l") 199 upper := filepath.Join(dir, "u") 200 work := filepath.Join(dir, "w") 201 merged := filepath.Join(dir, "m") 202 for _, s := range []string{lower, upper, work, merged} { 203 if err := os.MkdirAll(s, 0700); err != nil { 204 return err 205 } 206 } 207 mOpts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work) 208 if err := mount.Mount("overlay", merged, "overlay", mOpts); err != nil { 209 return err 210 } 211 if err := mount.Unmount(merged); err != nil { 212 return err 213 } 214 return nil 215 } 216 217 // supportsUserNSOverlay returns nil error if overlay is supported in userns. 218 // Only Ubuntu and a few distros support overlay in userns (by patching the kernel). 219 // https://lists.ubuntu.com/archives/kernel-team/2014-February/038091.html 220 // As of kernel 4.19, the patch is not merged to the upstream. 221 func supportsUserNSOverlay() error { 222 tmp, err := ioutil.TempDir("", "docker-test-supports-userns-overlay") 223 if err != nil { 224 return err 225 } 226 defer os.RemoveAll(tmp) 227 cmd := reexec.Command(reexecSupportsUserNSOverlay, tmp) 228 unshareCmd(cmd) 229 out, err := cmd.CombinedOutput() 230 if err != nil { 231 return errors.Wrapf(err, "output: %q", string(out)) 232 } 233 return nil 234 } 235 236 // isOpaque returns nil error if the dir has trusted.overlay.opaque=y. 237 // isOpaque needs to be called in the initial userns. 238 func isOpaque(dir string) error { 239 xattrOpaque, err := system.Lgetxattr(dir, "trusted.overlay.opaque") 240 if err != nil { 241 return errors.Wrapf(err, "failed to read opaque flag of %s", dir) 242 } 243 if string(xattrOpaque) != "y" { 244 return errors.Errorf("expected \"y\", got %q", string(xattrOpaque)) 245 } 246 return nil 247 } 248 249 func TestReexecUserNSOverlayWhiteoutConverter(t *testing.T) { 250 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 251 skip.If(t, rsystem.RunningInUserNS(), "skipping test that requires initial userns") 252 if err := supportsUserNSOverlay(); err != nil { 253 t.Skipf("skipping test that requires kernel support for overlay-in-userns: %v", err) 254 } 255 tmp, err := ioutil.TempDir("", "docker-test-userns-overlay") 256 assert.NilError(t, err) 257 defer os.RemoveAll(tmp) 258 259 char0 := filepath.Join(tmp, "char0") 260 cmd := reexec.Command(reexecMknodChar0, char0) 261 unshareCmd(cmd) 262 out, err := cmd.CombinedOutput() 263 assert.NilError(t, err, string(out)) 264 assert.NilError(t, isChar0(char0)) 265 266 opaqueDir := filepath.Join(tmp, "opaquedir") 267 err = os.MkdirAll(opaqueDir, 0755) 268 assert.NilError(t, err, string(out)) 269 cmd = reexec.Command(reexecSetOpaque, opaqueDir) 270 unshareCmd(cmd) 271 out, err = cmd.CombinedOutput() 272 assert.NilError(t, err, string(out)) 273 assert.NilError(t, isOpaque(opaqueDir)) 274 } 275 276 func init() { 277 reexec.Register(reexecSupportsUserNSOverlay, func() { 278 if err := supportsOverlay(os.Args[1]); err != nil { 279 panic(err) 280 } 281 }) 282 reexec.Register(reexecMknodChar0, func() { 283 if err := mknodChar0Overlay(os.Args[1]); err != nil { 284 panic(err) 285 } 286 }) 287 reexec.Register(reexecSetOpaque, func() { 288 if err := replaceDirWithOverlayOpaque(os.Args[1]); err != nil { 289 panic(err) 290 } 291 }) 292 if reexec.Init() { 293 os.Exit(0) 294 } 295 }