...

Source file src/github.com/Microsoft/go-winio/backuptar/tar_test.go

Documentation: github.com/Microsoft/go-winio/backuptar

     1  //go:build windows
     2  // +build windows
     3  
     4  package backuptar
     5  
     6  import (
     7  	"archive/tar"
     8  	"bytes"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"reflect"
    13  	"testing"
    14  
    15  	"github.com/Microsoft/go-winio"
    16  	"golang.org/x/sys/windows"
    17  )
    18  
    19  func ensurePresent(t *testing.T, m map[string]string, keys ...string) {
    20  	for _, k := range keys {
    21  		if _, ok := m[k]; !ok {
    22  			t.Error(k, "not present in tar header")
    23  		}
    24  	}
    25  }
    26  
    27  func setSparse(t *testing.T, f *os.File) {
    28  	if err := windows.DeviceIoControl(windows.Handle(f.Fd()), windows.FSCTL_SET_SPARSE, nil, 0, nil, 0, nil, nil); err != nil {
    29  		t.Fatal(err)
    30  	}
    31  }
    32  
    33  // compareReaders validates that two readers contain the exact same data.
    34  func compareReaders(t *testing.T, rActual io.Reader, rExpected io.Reader) {
    35  	const size = 8 * 1024
    36  	var bufExpected, bufActual [size]byte
    37  	var readCount int64
    38  	// Loop, first reading from rExpected, then reading the same amount from rActual.
    39  	// For each set of reads, compare the bytes to make sure they are identical.
    40  	// When we run out of data in rExpected, exit the loop.
    41  	for {
    42  		// Do a read from rExpected and see how many bytes we get.
    43  		nExpected, err := rExpected.Read(bufExpected[:])
    44  		if err == io.EOF && nExpected == 0 {
    45  			break
    46  		} else if err != nil && err != io.EOF {
    47  			t.Fatalf("Failed reading from rExpected at %d: %s", readCount, err)
    48  		}
    49  		// Do a ReadFull from rActual for the same number of bytes we got from rExpected.
    50  		if nActual, err := io.ReadFull(rActual, bufActual[:nExpected]); err != nil {
    51  			t.Fatalf("Only read %d bytes out of %d from rActual at %d: %s", nActual, nExpected, readCount, err)
    52  		}
    53  		readCount += int64(nExpected)
    54  		for i, bExpected := range bufExpected[:nExpected] {
    55  			if bExpected != bufActual[i] {
    56  				t.Fatalf("Mismatched bytes at %d. got 0x%x, expected 0x%x", i, bufActual[i], bExpected)
    57  			}
    58  		}
    59  	}
    60  	// Now we just need to make sure there isn't any further data in rActual.
    61  	var b [1]byte
    62  	if n, err := rActual.Read(b[:]); n != 0 || err != io.EOF {
    63  		t.Fatalf("rActual didn't return EOF at expected end. Read %d bytes with error %s", n, err)
    64  	}
    65  }
    66  
    67  func TestRoundTrip(t *testing.T) {
    68  	// Each test case is a name mapped to a function which must create a file and return its path.
    69  	// The test then round-trips that file through backuptar, and validates the output matches the input.
    70  	//
    71  	//nolint:gosec // G306: Expect WriteFile permissions to be 0600 or less
    72  	for name, setup := range map[string]func(*testing.T) string{
    73  		"normalFile": func(t *testing.T) string {
    74  			path := filepath.Join(t.TempDir(), "foo.txt")
    75  			if err := os.WriteFile(path, []byte("testing 1 2 3\n"), 0644); err != nil {
    76  				t.Fatal(err)
    77  			}
    78  			return path
    79  		},
    80  		"normalFileEmpty": func(t *testing.T) string {
    81  			path := filepath.Join(t.TempDir(), "foo.txt")
    82  			f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
    83  			if err != nil {
    84  				t.Fatal(err)
    85  			}
    86  			defer f.Close()
    87  			return path
    88  		},
    89  		"sparseFileEmpty": func(t *testing.T) string {
    90  			path := filepath.Join(t.TempDir(), "foo.txt")
    91  			f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
    92  			if err != nil {
    93  				t.Fatal(err)
    94  			}
    95  			defer f.Close()
    96  			setSparse(t, f)
    97  			return path
    98  		},
    99  		"sparseFileWithNoAllocatedRanges": func(t *testing.T) string {
   100  			path := filepath.Join(t.TempDir(), "foo.txt")
   101  			f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
   102  			if err != nil {
   103  				t.Fatal(err)
   104  			}
   105  			defer f.Close()
   106  			setSparse(t, f)
   107  			// Set file size without writing data to produce a file with size > 0
   108  			// but no allocated ranges.
   109  			if err := f.Truncate(1000000); err != nil {
   110  				t.Fatal(err)
   111  			}
   112  			return path
   113  		},
   114  		"sparseFileWithOneAllocatedRange": func(t *testing.T) string {
   115  			path := filepath.Join(t.TempDir(), "foo.txt")
   116  			f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
   117  			if err != nil {
   118  				t.Fatal(err)
   119  			}
   120  			defer f.Close()
   121  			setSparse(t, f)
   122  			if _, err := f.WriteString("test sparse data"); err != nil {
   123  				t.Fatal(err)
   124  			}
   125  			return path
   126  		},
   127  		"sparseFileWithMultipleAllocatedRanges": func(t *testing.T) string {
   128  			path := filepath.Join(t.TempDir(), "foo.txt")
   129  			f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
   130  			if err != nil {
   131  				t.Fatal(err)
   132  			}
   133  			defer f.Close()
   134  			setSparse(t, f)
   135  			if _, err = f.Write([]byte("testing 1 2 3\n")); err != nil {
   136  				t.Fatal(err)
   137  			}
   138  			// The documentation talks about FSCTL_SET_ZERO_DATA, but seeking also
   139  			// seems to create a hole.
   140  			if _, err = f.Seek(1000000, 0); err != nil {
   141  				t.Fatal(err)
   142  			}
   143  			if _, err = f.Write([]byte("more data later\n")); err != nil {
   144  				t.Fatal(err)
   145  			}
   146  			return path
   147  		},
   148  	} {
   149  		t.Run(name, func(t *testing.T) {
   150  			path := setup(t)
   151  			f, err := os.Open(path)
   152  			if err != nil {
   153  				t.Fatal(err)
   154  			}
   155  			defer f.Close()
   156  
   157  			fi, err := f.Stat()
   158  			if err != nil {
   159  				t.Fatal(err)
   160  			}
   161  			bi, err := winio.GetFileBasicInfo(f)
   162  			if err != nil {
   163  				t.Fatal(err)
   164  			}
   165  
   166  			br := winio.NewBackupFileReader(f, true)
   167  			defer br.Close()
   168  			var buf bytes.Buffer
   169  			tw := tar.NewWriter(&buf)
   170  			err = WriteTarFileFromBackupStream(tw, br, f.Name(), fi.Size(), bi)
   171  			if err != nil {
   172  				t.Fatal(err)
   173  			}
   174  			tr := tar.NewReader(&buf)
   175  			hdr, err := tr.Next()
   176  			if err != nil {
   177  				t.Fatal(err)
   178  			}
   179  
   180  			name, size, bi2, err := FileInfoFromHeader(hdr)
   181  			if err != nil {
   182  				t.Fatal(err)
   183  			}
   184  			if name != filepath.ToSlash(f.Name()) {
   185  				t.Errorf("got name %s, expected %s", name, filepath.ToSlash(f.Name()))
   186  			}
   187  			if size != fi.Size() {
   188  				t.Errorf("got size %d, expected %d", size, fi.Size())
   189  			}
   190  			if !reflect.DeepEqual(*bi2, *bi) {
   191  				t.Errorf("got %#v, expected %#v", *bi2, *bi)
   192  			}
   193  			ensurePresent(t, hdr.PAXRecords, "MSWINDOWS.fileattr", "MSWINDOWS.rawsd")
   194  			// Reset file position so we can compare file contents.
   195  			// The file contents of the actual file should match what we get from the tar.
   196  			if _, err := f.Seek(0, 0); err != nil {
   197  				t.Fatal(err)
   198  			}
   199  			compareReaders(t, tr, f)
   200  		})
   201  	}
   202  }
   203  
   204  func TestZeroReader(t *testing.T) {
   205  	const size = 512
   206  	var b [size]byte
   207  	var bExpected [size]byte
   208  	var r zeroReader
   209  	n, err := r.Read(b[:])
   210  	if err != nil {
   211  		t.Fatalf("Unexpected read error: %s", err)
   212  	}
   213  	if n != size {
   214  		t.Errorf("Wrong read size. got %d, expected %d", n, size)
   215  	}
   216  	for i := range b {
   217  		if b[i] != bExpected[i] {
   218  			t.Errorf("Wrong content at index %d. got %d, expected %d", i, b[i], bExpected[i])
   219  		}
   220  	}
   221  }
   222  

View as plain text