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  }