gopkg.in/docker/docker.v23@v23.0.11/pkg/chrootarchive/archive_test.go (about) 1 package chrootarchive // import "github.com/docker/docker/pkg/chrootarchive" 2 3 import ( 4 "bytes" 5 "fmt" 6 "hash/crc32" 7 "io" 8 "os" 9 "path/filepath" 10 "runtime" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/docker/docker/pkg/archive" 16 "github.com/docker/docker/pkg/idtools" 17 "github.com/docker/docker/pkg/reexec" 18 "github.com/docker/docker/pkg/system" 19 "gotest.tools/v3/skip" 20 ) 21 22 func init() { 23 reexec.Init() 24 } 25 26 var chrootArchiver = NewArchiver(idtools.IdentityMapping{}) 27 28 func TarUntar(src, dst string) error { 29 return chrootArchiver.TarUntar(src, dst) 30 } 31 32 func CopyFileWithTar(src, dst string) (err error) { 33 return chrootArchiver.CopyFileWithTar(src, dst) 34 } 35 36 func UntarPath(src, dst string) error { 37 return chrootArchiver.UntarPath(src, dst) 38 } 39 40 func CopyWithTar(src, dst string) error { 41 return chrootArchiver.CopyWithTar(src, dst) 42 } 43 44 func TestChrootTarUntar(t *testing.T) { 45 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 46 tmpdir, err := os.MkdirTemp("", "docker-TestChrootTarUntar") 47 if err != nil { 48 t.Fatal(err) 49 } 50 defer os.RemoveAll(tmpdir) 51 src := filepath.Join(tmpdir, "src") 52 if err := system.MkdirAll(src, 0700); err != nil { 53 t.Fatal(err) 54 } 55 if err := os.WriteFile(filepath.Join(src, "toto"), []byte("hello toto"), 0644); err != nil { 56 t.Fatal(err) 57 } 58 if err := os.WriteFile(filepath.Join(src, "lolo"), []byte("hello lolo"), 0644); err != nil { 59 t.Fatal(err) 60 } 61 stream, err := archive.Tar(src, archive.Uncompressed) 62 if err != nil { 63 t.Fatal(err) 64 } 65 dest := filepath.Join(tmpdir, "src") 66 if err := system.MkdirAll(dest, 0700); err != nil { 67 t.Fatal(err) 68 } 69 if err := Untar(stream, dest, &archive.TarOptions{ExcludePatterns: []string{"lolo"}}); err != nil { 70 t.Fatal(err) 71 } 72 } 73 74 // gh#10426: Verify the fix for having a huge excludes list (like on `docker load` with large # of 75 // local images) 76 func TestChrootUntarWithHugeExcludesList(t *testing.T) { 77 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 78 tmpdir, err := os.MkdirTemp("", "docker-TestChrootUntarHugeExcludes") 79 if err != nil { 80 t.Fatal(err) 81 } 82 defer os.RemoveAll(tmpdir) 83 src := filepath.Join(tmpdir, "src") 84 if err := system.MkdirAll(src, 0700); err != nil { 85 t.Fatal(err) 86 } 87 if err := os.WriteFile(filepath.Join(src, "toto"), []byte("hello toto"), 0644); err != nil { 88 t.Fatal(err) 89 } 90 stream, err := archive.Tar(src, archive.Uncompressed) 91 if err != nil { 92 t.Fatal(err) 93 } 94 dest := filepath.Join(tmpdir, "dest") 95 if err := system.MkdirAll(dest, 0700); err != nil { 96 t.Fatal(err) 97 } 98 options := &archive.TarOptions{} 99 // 65534 entries of 64-byte strings ~= 4MB of environment space which should overflow 100 // on most systems when passed via environment or command line arguments 101 excludes := make([]string, 65534) 102 var i rune 103 for i = 0; i < 65534; i++ { 104 excludes[i] = strings.Repeat(string(i), 64) 105 } 106 options.ExcludePatterns = excludes 107 if err := Untar(stream, dest, options); err != nil { 108 t.Fatal(err) 109 } 110 } 111 112 func TestChrootUntarEmptyArchive(t *testing.T) { 113 tmpdir, err := os.MkdirTemp("", "docker-TestChrootUntarEmptyArchive") 114 if err != nil { 115 t.Fatal(err) 116 } 117 defer os.RemoveAll(tmpdir) 118 if err := Untar(nil, tmpdir, nil); err == nil { 119 t.Fatal("expected error on empty archive") 120 } 121 } 122 123 func prepareSourceDirectory(numberOfFiles int, targetPath string, makeSymLinks bool) (int, error) { 124 fileData := []byte("fooo") 125 for n := 0; n < numberOfFiles; n++ { 126 fileName := fmt.Sprintf("file-%d", n) 127 if err := os.WriteFile(filepath.Join(targetPath, fileName), fileData, 0700); err != nil { 128 return 0, err 129 } 130 if makeSymLinks { 131 if err := os.Symlink(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil { 132 return 0, err 133 } 134 } 135 } 136 totalSize := numberOfFiles * len(fileData) 137 return totalSize, nil 138 } 139 140 func getHash(filename string) (uint32, error) { 141 stream, err := os.ReadFile(filename) 142 if err != nil { 143 return 0, err 144 } 145 hash := crc32.NewIEEE() 146 hash.Write(stream) 147 return hash.Sum32(), nil 148 } 149 150 func compareDirectories(src string, dest string) error { 151 changes, err := archive.ChangesDirs(dest, src) 152 if err != nil { 153 return err 154 } 155 if len(changes) > 0 { 156 return fmt.Errorf("Unexpected differences after untar: %v", changes) 157 } 158 return nil 159 } 160 161 func compareFiles(src string, dest string) error { 162 srcHash, err := getHash(src) 163 if err != nil { 164 return err 165 } 166 destHash, err := getHash(dest) 167 if err != nil { 168 return err 169 } 170 if srcHash != destHash { 171 return fmt.Errorf("%s is different from %s", src, dest) 172 } 173 return nil 174 } 175 176 func TestChrootTarUntarWithSymlink(t *testing.T) { 177 skip.If(t, runtime.GOOS == "windows", "FIXME: figure out why this is failing") 178 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 179 tmpdir, err := os.MkdirTemp("", "docker-TestChrootTarUntarWithSymlink") 180 if err != nil { 181 t.Fatal(err) 182 } 183 defer os.RemoveAll(tmpdir) 184 src := filepath.Join(tmpdir, "src") 185 if err := system.MkdirAll(src, 0700); err != nil { 186 t.Fatal(err) 187 } 188 if _, err := prepareSourceDirectory(10, src, false); err != nil { 189 t.Fatal(err) 190 } 191 dest := filepath.Join(tmpdir, "dest") 192 if err := TarUntar(src, dest); err != nil { 193 t.Fatal(err) 194 } 195 if err := compareDirectories(src, dest); err != nil { 196 t.Fatal(err) 197 } 198 } 199 200 func TestChrootCopyWithTar(t *testing.T) { 201 skip.If(t, runtime.GOOS == "windows", "FIXME: figure out why this is failing") 202 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 203 tmpdir, err := os.MkdirTemp("", "docker-TestChrootCopyWithTar") 204 if err != nil { 205 t.Fatal(err) 206 } 207 defer os.RemoveAll(tmpdir) 208 src := filepath.Join(tmpdir, "src") 209 if err := system.MkdirAll(src, 0700); err != nil { 210 t.Fatal(err) 211 } 212 if _, err := prepareSourceDirectory(10, src, true); err != nil { 213 t.Fatal(err) 214 } 215 216 // Copy directory 217 dest := filepath.Join(tmpdir, "dest") 218 if err := CopyWithTar(src, dest); err != nil { 219 t.Fatal(err) 220 } 221 if err := compareDirectories(src, dest); err != nil { 222 t.Fatal(err) 223 } 224 225 // Copy file 226 srcfile := filepath.Join(src, "file-1") 227 dest = filepath.Join(tmpdir, "destFile") 228 destfile := filepath.Join(dest, "file-1") 229 if err := CopyWithTar(srcfile, destfile); err != nil { 230 t.Fatal(err) 231 } 232 if err := compareFiles(srcfile, destfile); err != nil { 233 t.Fatal(err) 234 } 235 236 // Copy symbolic link 237 srcLinkfile := filepath.Join(src, "file-1-link") 238 dest = filepath.Join(tmpdir, "destSymlink") 239 destLinkfile := filepath.Join(dest, "file-1-link") 240 if err := CopyWithTar(srcLinkfile, destLinkfile); err != nil { 241 t.Fatal(err) 242 } 243 if err := compareFiles(srcLinkfile, destLinkfile); err != nil { 244 t.Fatal(err) 245 } 246 } 247 248 func TestChrootCopyFileWithTar(t *testing.T) { 249 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 250 tmpdir, err := os.MkdirTemp("", "docker-TestChrootCopyFileWithTar") 251 if err != nil { 252 t.Fatal(err) 253 } 254 defer os.RemoveAll(tmpdir) 255 src := filepath.Join(tmpdir, "src") 256 if err := system.MkdirAll(src, 0700); err != nil { 257 t.Fatal(err) 258 } 259 if _, err := prepareSourceDirectory(10, src, true); err != nil { 260 t.Fatal(err) 261 } 262 263 // Copy directory 264 dest := filepath.Join(tmpdir, "dest") 265 if err := CopyFileWithTar(src, dest); err == nil { 266 t.Fatal("Expected error on copying directory") 267 } 268 269 // Copy file 270 srcfile := filepath.Join(src, "file-1") 271 dest = filepath.Join(tmpdir, "destFile") 272 destfile := filepath.Join(dest, "file-1") 273 if err := CopyFileWithTar(srcfile, destfile); err != nil { 274 t.Fatal(err) 275 } 276 if err := compareFiles(srcfile, destfile); err != nil { 277 t.Fatal(err) 278 } 279 280 // Copy symbolic link 281 srcLinkfile := filepath.Join(src, "file-1-link") 282 dest = filepath.Join(tmpdir, "destSymlink") 283 destLinkfile := filepath.Join(dest, "file-1-link") 284 if err := CopyFileWithTar(srcLinkfile, destLinkfile); err != nil { 285 t.Fatal(err) 286 } 287 if err := compareFiles(srcLinkfile, destLinkfile); err != nil { 288 t.Fatal(err) 289 } 290 } 291 292 func TestChrootUntarPath(t *testing.T) { 293 skip.If(t, runtime.GOOS == "windows", "FIXME: figure out why this is failing") 294 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 295 tmpdir, err := os.MkdirTemp("", "docker-TestChrootUntarPath") 296 if err != nil { 297 t.Fatal(err) 298 } 299 defer os.RemoveAll(tmpdir) 300 src := filepath.Join(tmpdir, "src") 301 if err := system.MkdirAll(src, 0700); err != nil { 302 t.Fatal(err) 303 } 304 if _, err := prepareSourceDirectory(10, src, false); err != nil { 305 t.Fatal(err) 306 } 307 dest := filepath.Join(tmpdir, "dest") 308 // Untar a directory 309 if err := UntarPath(src, dest); err == nil { 310 t.Fatal("Expected error on untaring a directory") 311 } 312 313 // Untar a tar file 314 stream, err := archive.Tar(src, archive.Uncompressed) 315 if err != nil { 316 t.Fatal(err) 317 } 318 buf := new(bytes.Buffer) 319 buf.ReadFrom(stream) 320 tarfile := filepath.Join(tmpdir, "src.tar") 321 if err := os.WriteFile(tarfile, buf.Bytes(), 0644); err != nil { 322 t.Fatal(err) 323 } 324 if err := UntarPath(tarfile, dest); err != nil { 325 t.Fatal(err) 326 } 327 if err := compareDirectories(src, dest); err != nil { 328 t.Fatal(err) 329 } 330 } 331 332 type slowEmptyTarReader struct { 333 size int 334 offset int 335 chunkSize int 336 } 337 338 // Read is a slow reader of an empty tar (like the output of "tar c --files-from /dev/null") 339 func (s *slowEmptyTarReader) Read(p []byte) (int, error) { 340 time.Sleep(100 * time.Millisecond) 341 count := s.chunkSize 342 if len(p) < s.chunkSize { 343 count = len(p) 344 } 345 for i := 0; i < count; i++ { 346 p[i] = 0 347 } 348 s.offset += count 349 if s.offset > s.size { 350 return count, io.EOF 351 } 352 return count, nil 353 } 354 355 func TestChrootUntarEmptyArchiveFromSlowReader(t *testing.T) { 356 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 357 tmpdir, err := os.MkdirTemp("", "docker-TestChrootUntarEmptyArchiveFromSlowReader") 358 if err != nil { 359 t.Fatal(err) 360 } 361 defer os.RemoveAll(tmpdir) 362 dest := filepath.Join(tmpdir, "dest") 363 if err := system.MkdirAll(dest, 0700); err != nil { 364 t.Fatal(err) 365 } 366 stream := &slowEmptyTarReader{size: 10240, chunkSize: 1024} 367 if err := Untar(stream, dest, nil); err != nil { 368 t.Fatal(err) 369 } 370 } 371 372 func TestChrootApplyEmptyArchiveFromSlowReader(t *testing.T) { 373 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 374 tmpdir, err := os.MkdirTemp("", "docker-TestChrootApplyEmptyArchiveFromSlowReader") 375 if err != nil { 376 t.Fatal(err) 377 } 378 defer os.RemoveAll(tmpdir) 379 dest := filepath.Join(tmpdir, "dest") 380 if err := system.MkdirAll(dest, 0700); err != nil { 381 t.Fatal(err) 382 } 383 stream := &slowEmptyTarReader{size: 10240, chunkSize: 1024} 384 if _, err := ApplyLayer(dest, stream); err != nil { 385 t.Fatal(err) 386 } 387 } 388 389 func TestChrootApplyDotDotFile(t *testing.T) { 390 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 391 tmpdir, err := os.MkdirTemp("", "docker-TestChrootApplyDotDotFile") 392 if err != nil { 393 t.Fatal(err) 394 } 395 defer os.RemoveAll(tmpdir) 396 src := filepath.Join(tmpdir, "src") 397 if err := system.MkdirAll(src, 0700); err != nil { 398 t.Fatal(err) 399 } 400 if err := os.WriteFile(filepath.Join(src, "..gitme"), []byte(""), 0644); err != nil { 401 t.Fatal(err) 402 } 403 stream, err := archive.Tar(src, archive.Uncompressed) 404 if err != nil { 405 t.Fatal(err) 406 } 407 dest := filepath.Join(tmpdir, "dest") 408 if err := system.MkdirAll(dest, 0700); err != nil { 409 t.Fatal(err) 410 } 411 if _, err := ApplyLayer(dest, stream); err != nil { 412 t.Fatal(err) 413 } 414 }