github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/layer/tar_generate_test.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016-2020 SUSE LLC 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package layer 19 20 import ( 21 "archive/tar" 22 "bytes" 23 "fmt" 24 "io" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "strings" 29 "testing" 30 "time" 31 ) 32 33 func TestTarGenerateAddFileNormal(t *testing.T) { 34 reader, writer := io.Pipe() 35 36 dir, err := ioutil.TempDir("", "umoci-TestTarGenerateAddFileNormal") 37 if err != nil { 38 t.Fatal(err) 39 } 40 defer os.RemoveAll(dir) 41 42 file := "file" 43 data := []byte("this is a normal file") 44 path := filepath.Join(dir, file) 45 46 expectedHdr := &tar.Header{ 47 Name: file, 48 Mode: 0644, 49 ModTime: time.Unix(123, 0), 50 AccessTime: time.Unix(888, 0), 51 Uid: os.Getuid(), 52 Gid: os.Getgid(), 53 Typeflag: tar.TypeReg, 54 Size: int64(len(data)), 55 } 56 57 te := NewTarExtractor(UnpackOptions{}) 58 if err := ioutil.WriteFile(path, data, 0777); err != nil { 59 t.Fatalf("unexpected error creating file to add: %s", err) 60 } 61 if err := te.applyMetadata(path, expectedHdr); err != nil { 62 t.Fatalf("apply metadata: %s", err) 63 } 64 65 tg := newTarGenerator(writer, MapOptions{}) 66 tr := tar.NewReader(reader) 67 68 // Create all of the tar entries in a goroutine so we can parse the tar 69 // entries as they're generated (io.Pipe pipes are unbuffered). 70 go func() { 71 if err := tg.AddFile(file, path); err != nil { 72 t.Errorf("AddFile: %s: unexpected error: %s", path, err) 73 } 74 if err := tg.tw.Close(); err != nil { 75 t.Errorf("tw.Close: unexpected error: %s", err) 76 } 77 if err := writer.Close(); err != nil { 78 t.Errorf("writer.Close: unexpected error: %s", err) 79 } 80 }() 81 82 hdr, err := tr.Next() 83 if err != nil { 84 t.Fatalf("reading tar archive: %s", err) 85 } 86 87 if hdr.Typeflag != expectedHdr.Typeflag { 88 t.Errorf("hdr.Typeflag changed: expected %d, got %d", expectedHdr.Typeflag, hdr.Typeflag) 89 } 90 if hdr.Name != expectedHdr.Name { 91 t.Errorf("hdr.Name changed: expected %s, got %s", expectedHdr.Name, hdr.Name) 92 } 93 if hdr.Uid != expectedHdr.Uid { 94 t.Errorf("hdr.Uid changed: expected %d, got %d", expectedHdr.Uid, hdr.Uid) 95 } 96 if hdr.Gid != expectedHdr.Gid { 97 t.Errorf("hdr.Gid changed: expected %d, got %d", expectedHdr.Gid, hdr.Gid) 98 } 99 if hdr.Size != expectedHdr.Size { 100 t.Errorf("hdr.Size changed: expected %d, got %d", expectedHdr.Size, hdr.Size) 101 } 102 if !hdr.ModTime.Equal(expectedHdr.ModTime) { 103 t.Errorf("hdr.ModTime changed: expected %s, got %s", expectedHdr.ModTime, hdr.ModTime) 104 } 105 // This test will always fail because of a Golang bug: https://github.com/golang/go/issues/17876. 106 // We will skip this test for now. 107 if !hdr.AccessTime.Equal(expectedHdr.AccessTime) { 108 if hdr.AccessTime.IsZero() { 109 t.Logf("hdr.AccessTime doesn't match (it is zero) -- this is a Golang bug") 110 } else { 111 t.Errorf("hdr.AccessTime changed: expected %s, got %s", expectedHdr.AccessTime, hdr.AccessTime) 112 } 113 } 114 115 gotBytes, err := ioutil.ReadAll(tr) 116 if err != nil { 117 t.Errorf("read all: unexpected error: %s", err) 118 } 119 if !bytes.Equal(gotBytes, data) { 120 t.Errorf("unexpected data read from tar.Reader: expected %v, got %v", data, gotBytes) 121 } 122 123 if _, err := tr.Next(); err != io.EOF { 124 t.Errorf("expected only one entry, err=%s", err) 125 } 126 } 127 128 func TestTarGenerateAddFileDirectory(t *testing.T) { 129 reader, writer := io.Pipe() 130 131 dir, err := ioutil.TempDir("", "umoci-TestTarGenerateAddFileDirectory") 132 if err != nil { 133 t.Fatal(err) 134 } 135 defer os.RemoveAll(dir) 136 137 file := "directory/" 138 path := filepath.Join(dir, file) 139 140 expectedHdr := &tar.Header{ 141 Name: file, 142 Mode: 0644, 143 ModTime: time.Unix(123, 0), 144 AccessTime: time.Unix(888, 0), 145 Uid: os.Getuid(), 146 Gid: os.Getgid(), 147 Typeflag: tar.TypeDir, 148 Size: 0, 149 } 150 151 te := NewTarExtractor(UnpackOptions{}) 152 if err := os.Mkdir(path, 0777); err != nil { 153 t.Fatalf("unexpected error creating file to add: %s", err) 154 } 155 if err := te.applyMetadata(path, expectedHdr); err != nil { 156 t.Fatalf("apply metadata: %s", err) 157 } 158 159 tg := newTarGenerator(writer, MapOptions{}) 160 tr := tar.NewReader(reader) 161 162 // Create all of the tar entries in a goroutine so we can parse the tar 163 // entries as they're generated (io.Pipe pipes are unbuffered). 164 go func() { 165 if err := tg.AddFile(file, path); err != nil { 166 t.Errorf("AddFile: %s: unexpected error: %s", path, err) 167 } 168 if err := tg.tw.Close(); err != nil { 169 t.Errorf("tw.Close: unexpected error: %s", err) 170 } 171 if err := writer.Close(); err != nil { 172 t.Errorf("writer.Close: unexpected error: %s", err) 173 } 174 }() 175 176 hdr, err := tr.Next() 177 if err != nil { 178 t.Fatalf("reading tar archive: %s", err) 179 } 180 181 if hdr.Typeflag != expectedHdr.Typeflag { 182 t.Errorf("hdr.Typeflag changed: expected %d, got %d", expectedHdr.Typeflag, hdr.Typeflag) 183 } 184 if hdr.Name != expectedHdr.Name { 185 t.Errorf("hdr.Name changed: expected %s, got %s", expectedHdr.Name, hdr.Name) 186 } 187 if hdr.Uid != expectedHdr.Uid { 188 t.Errorf("hdr.Uid changed: expected %d, got %d", expectedHdr.Uid, hdr.Uid) 189 } 190 if hdr.Gid != expectedHdr.Gid { 191 t.Errorf("hdr.Gid changed: expected %d, got %d", expectedHdr.Gid, hdr.Gid) 192 } 193 if hdr.Size != expectedHdr.Size { 194 t.Errorf("hdr.Size changed: expected %d, got %d", expectedHdr.Size, hdr.Size) 195 } 196 if !hdr.ModTime.Equal(expectedHdr.ModTime) { 197 t.Errorf("hdr.ModTime changed: expected %s, got %s", expectedHdr.ModTime, hdr.ModTime) 198 } 199 // This test will always fail because of a Golang bug: https://github.com/golang/go/issues/17876. 200 // We will skip this test for now. 201 if !hdr.AccessTime.Equal(expectedHdr.AccessTime) { 202 if hdr.AccessTime.IsZero() { 203 t.Logf("hdr.AccessTime doesn't match (it is zero) -- this is a Golang bug") 204 } else { 205 t.Errorf("hdr.AccessTime changed: expected %s, got %s", expectedHdr.AccessTime, hdr.AccessTime) 206 } 207 } 208 209 if _, err := tr.Next(); err != io.EOF { 210 t.Errorf("expected only one entry, err=%s", err) 211 } 212 } 213 214 func TestTarGenerateAddFileSymlink(t *testing.T) { 215 reader, writer := io.Pipe() 216 217 dir, err := ioutil.TempDir("", "umoci-TestTarGenerateAddFileSymlink") 218 if err != nil { 219 t.Fatal(err) 220 } 221 defer os.RemoveAll(dir) 222 223 file := "link" 224 linkname := "/test" 225 path := filepath.Join(dir, file) 226 227 expectedHdr := &tar.Header{ 228 Name: file, 229 Linkname: linkname, 230 ModTime: time.Unix(123, 0), 231 AccessTime: time.Unix(888, 0), 232 Uid: os.Getuid(), 233 Gid: os.Getgid(), 234 Typeflag: tar.TypeSymlink, 235 Size: 0, 236 } 237 238 te := NewTarExtractor(UnpackOptions{}) 239 if err := os.Symlink(linkname, path); err != nil { 240 t.Fatalf("unexpected error creating file to add: %s", err) 241 } 242 if err := te.applyMetadata(path, expectedHdr); err != nil { 243 t.Fatalf("apply metadata: %s", err) 244 } 245 246 tg := newTarGenerator(writer, MapOptions{}) 247 tr := tar.NewReader(reader) 248 249 // Create all of the tar entries in a goroutine so we can parse the tar 250 // entries as they're generated (io.Pipe pipes are unbuffered). 251 go func() { 252 if err := tg.AddFile(file, path); err != nil { 253 t.Errorf("AddFile: %s: unexpected error: %s", path, err) 254 } 255 if err := tg.tw.Close(); err != nil { 256 t.Errorf("tw.Close: unexpected error: %s", err) 257 } 258 if err := writer.Close(); err != nil { 259 t.Errorf("writer.Close: unexpected error: %s", err) 260 } 261 }() 262 263 hdr, err := tr.Next() 264 if err != nil { 265 t.Fatalf("reading tar archive: %s", err) 266 } 267 268 if hdr.Typeflag != expectedHdr.Typeflag { 269 t.Errorf("hdr.Typeflag changed: expected %d, got %d", expectedHdr.Typeflag, hdr.Typeflag) 270 } 271 if hdr.Name != expectedHdr.Name { 272 t.Errorf("hdr.Name changed: expected %s, got %s", expectedHdr.Name, hdr.Name) 273 } 274 if hdr.Linkname != expectedHdr.Linkname { 275 t.Errorf("hdr.Name changed: expected %s, got %s", expectedHdr.Name, hdr.Name) 276 } 277 if hdr.Uid != expectedHdr.Uid { 278 t.Errorf("hdr.Uid changed: expected %d, got %d", expectedHdr.Uid, hdr.Uid) 279 } 280 if hdr.Gid != expectedHdr.Gid { 281 t.Errorf("hdr.Gid changed: expected %d, got %d", expectedHdr.Gid, hdr.Gid) 282 } 283 if hdr.Size != expectedHdr.Size { 284 t.Errorf("hdr.Size changed: expected %d, got %d", expectedHdr.Size, hdr.Size) 285 } 286 if !hdr.ModTime.Equal(expectedHdr.ModTime) { 287 t.Errorf("hdr.ModTime changed: expected %s, got %s", expectedHdr.ModTime, hdr.ModTime) 288 } 289 // This test will always fail because of a Golang bug: https://github.com/golang/go/issues/17876. 290 // We will skip this test for now. 291 if !hdr.AccessTime.Equal(expectedHdr.AccessTime) { 292 if hdr.AccessTime.IsZero() { 293 t.Logf("hdr.AccessTime doesn't match (it is zero) -- this is a Golang bug") 294 } else { 295 t.Errorf("hdr.AccessTime changed: expected %s, got %s", expectedHdr.AccessTime, hdr.AccessTime) 296 } 297 } 298 299 if _, err := tr.Next(); err != io.EOF { 300 t.Errorf("expected only one entry, err=%s", err) 301 } 302 } 303 304 func parseWhiteout(path string) (string, error) { 305 path = filepath.Clean(path) 306 dir, file := filepath.Split(path) 307 if !strings.HasPrefix(file, whPrefix) { 308 return "", fmt.Errorf("not a whiteout path: %s", path) 309 } 310 return filepath.Join(dir, strings.TrimPrefix(file, whPrefix)), nil 311 } 312 313 func TestTarGenerateAddWhiteout(t *testing.T) { 314 reader, writer := io.Pipe() 315 316 dir, err := ioutil.TempDir("", "umoci-TestTarGenerateAddWhiteout") 317 if err != nil { 318 t.Fatal(err) 319 } 320 defer os.RemoveAll(dir) 321 322 // Paths we want to generate whiteouts for. 323 paths := []string{ 324 "root", 325 "dir/file", 326 "dir/", 327 "dir/.", 328 } 329 330 tg := newTarGenerator(writer, MapOptions{}) 331 tr := tar.NewReader(reader) 332 333 // Create all of the whiteout entries in a goroutine so we can parse the 334 // tar entries as they're generated (io.Pipe pipes are unbuffered). 335 go func() { 336 for _, path := range paths { 337 if err := tg.AddWhiteout(path); err != nil { 338 t.Errorf("AddWhitout: %s: unexpected error: %s", path, err) 339 } 340 } 341 if err := tg.tw.Close(); err != nil { 342 t.Errorf("tw.Close: unexpected error: %s", err) 343 } 344 if err := writer.Close(); err != nil { 345 t.Errorf("writer.Close: unexpected error: %s", err) 346 } 347 }() 348 349 idx := 0 350 for { 351 hdr, err := tr.Next() 352 if err == io.EOF { 353 break 354 } 355 if err != nil { 356 t.Fatalf("reading tar archive: %s", err) 357 } 358 359 if idx >= len(paths) { 360 t.Fatal("got more whiteout entries than AddWhiteout calls!") 361 } 362 363 parsed, err := parseWhiteout(hdr.Name) 364 if err != nil { 365 t.Errorf("getting whiteout for %s: %s", paths[idx], err) 366 } 367 368 cleanPath := filepath.Clean(paths[idx]) 369 if parsed != cleanPath { 370 t.Errorf("whiteout entry %d is out of order: expected %s, got %s", idx, cleanPath, parsed) 371 } 372 373 idx++ 374 } 375 376 if idx != len(paths) { 377 t.Errorf("not all paths had a whiteout entry generated (only read %d, expected %d)!", idx, len(paths)) 378 } 379 }