github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/osutil/cp_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package osutil
    21  
    22  import (
    23  	"errors"
    24  	"io/ioutil"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"strings"
    29  	"syscall"
    30  	"time"
    31  
    32  	. "gopkg.in/check.v1"
    33  
    34  	"github.com/snapcore/snapd/testutil"
    35  )
    36  
    37  type cpSuite struct {
    38  	dir  string
    39  	f1   string
    40  	f2   string
    41  	data []byte
    42  	log  []string
    43  	errs []error
    44  	idx  int
    45  }
    46  
    47  var _ = Suite(&cpSuite{})
    48  
    49  func (s *cpSuite) mockCopyFile(fin, fout fileish, fi os.FileInfo) error {
    50  	return s.µ("copyfile")
    51  }
    52  
    53  func (s *cpSuite) mockOpenFile(name string, flag int, perm os.FileMode) (fileish, error) {
    54  	return &mockfile{s}, s.µ("open")
    55  }
    56  
    57  func (s *cpSuite) µ(msg string) (err error) {
    58  	s.log = append(s.log, msg)
    59  	if len(s.errs) > 0 {
    60  		err = s.errs[0]
    61  		if len(s.errs) > 1 {
    62  			s.errs = s.errs[1:]
    63  		}
    64  	}
    65  
    66  	return err
    67  }
    68  
    69  func (s *cpSuite) SetUpTest(c *C) {
    70  	s.errs = nil
    71  	s.log = nil
    72  	s.dir = c.MkDir()
    73  	s.f1 = filepath.Join(s.dir, "f1")
    74  	s.f2 = filepath.Join(s.dir, "f2")
    75  	s.data = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    76  	c.Assert(ioutil.WriteFile(s.f1, s.data, 0644), IsNil)
    77  }
    78  
    79  func (s *cpSuite) mock() {
    80  	copyfile = s.mockCopyFile
    81  	openfile = s.mockOpenFile
    82  }
    83  
    84  func (s *cpSuite) TearDownTest(c *C) {
    85  	copyfile = doCopyFile
    86  	openfile = doOpenFile
    87  }
    88  
    89  func (s *cpSuite) TestCp(c *C) {
    90  	c.Check(CopyFile(s.f1, s.f2, CopyFlagDefault), IsNil)
    91  	c.Check(s.f2, testutil.FileEquals, s.data)
    92  }
    93  
    94  func (s *cpSuite) TestCpNoOverwrite(c *C) {
    95  	_, err := os.Create(s.f2)
    96  	c.Assert(err, IsNil)
    97  	c.Check(CopyFile(s.f1, s.f2, CopyFlagDefault), NotNil)
    98  }
    99  
   100  func (s *cpSuite) TestCpOverwrite(c *C) {
   101  	_, err := os.Create(s.f2)
   102  	c.Assert(err, IsNil)
   103  	c.Check(CopyFile(s.f1, s.f2, CopyFlagOverwrite), IsNil)
   104  	c.Check(s.f2, testutil.FileEquals, s.data)
   105  }
   106  
   107  func (s *cpSuite) TestCpOverwriteTruncates(c *C) {
   108  	c.Assert(ioutil.WriteFile(s.f2, []byte("xxxxxxxxxxxxxxxx"), 0644), IsNil)
   109  	c.Check(CopyFile(s.f1, s.f2, CopyFlagOverwrite), IsNil)
   110  	c.Check(s.f2, testutil.FileEquals, s.data)
   111  }
   112  
   113  func (s *cpSuite) TestCpSync(c *C) {
   114  	s.mock()
   115  	c.Check(CopyFile(s.f1, s.f2, CopyFlagDefault), IsNil)
   116  	c.Check(strings.Join(s.log, ":"), Not(Matches), `.*:sync(:.*)?`)
   117  
   118  	s.log = nil
   119  	c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), IsNil)
   120  	c.Check(strings.Join(s.log, ":"), Matches, `(.*:)?sync(:.*)?`)
   121  }
   122  
   123  func (s *cpSuite) TestCpCantOpen(c *C) {
   124  	s.mock()
   125  	s.errs = []error{errors.New("xyzzy"), nil}
   126  
   127  	c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `unable to open \S+/f1: xyzzy`)
   128  }
   129  
   130  func (s *cpSuite) TestCpCantStat(c *C) {
   131  	s.mock()
   132  	s.errs = []error{nil, errors.New("xyzzy"), nil}
   133  
   134  	c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `unable to stat \S+/f1: xyzzy`)
   135  }
   136  
   137  func (s *cpSuite) TestCpCantCreate(c *C) {
   138  	s.mock()
   139  	s.errs = []error{nil, nil, errors.New("xyzzy"), nil}
   140  
   141  	c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `unable to create \S+/f2: xyzzy`)
   142  }
   143  
   144  func (s *cpSuite) TestCpCantCopy(c *C) {
   145  	s.mock()
   146  	s.errs = []error{nil, nil, nil, errors.New("xyzzy"), nil}
   147  
   148  	c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `unable to copy \S+/f1 to \S+/f2: xyzzy`)
   149  }
   150  
   151  func (s *cpSuite) TestCpCantSync(c *C) {
   152  	s.mock()
   153  	s.errs = []error{nil, nil, nil, nil, errors.New("xyzzy"), nil}
   154  
   155  	c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `unable to sync \S+/f2: xyzzy`)
   156  }
   157  
   158  func (s *cpSuite) TestCpCantStop2(c *C) {
   159  	s.mock()
   160  	s.errs = []error{nil, nil, nil, nil, nil, errors.New("xyzzy"), nil}
   161  
   162  	c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `when closing \S+/f2: xyzzy`)
   163  }
   164  
   165  func (s *cpSuite) TestCpCantStop1(c *C) {
   166  	s.mock()
   167  	s.errs = []error{nil, nil, nil, nil, nil, nil, errors.New("xyzzy"), nil}
   168  
   169  	c.Check(CopyFile(s.f1, s.f2, CopyFlagSync), ErrorMatches, `when closing \S+/f1: xyzzy`)
   170  }
   171  
   172  type mockfile struct {
   173  	s *cpSuite
   174  }
   175  
   176  var mockst = mockstat{}
   177  
   178  func (f *mockfile) Close() error               { return f.s.µ("close") }
   179  func (f *mockfile) Sync() error                { return f.s.µ("sync") }
   180  func (f *mockfile) Fd() uintptr                { f.s.µ("fd"); return 42 }
   181  func (f *mockfile) Read([]byte) (int, error)   { return 0, f.s.µ("read") }
   182  func (f *mockfile) Write([]byte) (int, error)  { return 0, f.s.µ("write") }
   183  func (f *mockfile) Stat() (os.FileInfo, error) { return mockst, f.s.µ("stat") }
   184  
   185  type mockstat struct{}
   186  
   187  func (mockstat) Name() string       { return "mockstat" }
   188  func (mockstat) Size() int64        { return 42 }
   189  func (mockstat) Mode() os.FileMode  { return 0644 }
   190  func (mockstat) ModTime() time.Time { return time.Now() }
   191  func (mockstat) IsDir() bool        { return false }
   192  func (mockstat) Sys() interface{}   { return nil }
   193  
   194  func (s *cpSuite) TestCopySpecialFileSimple(c *C) {
   195  	sync := testutil.MockCommand(c, "sync", "")
   196  	defer sync.Restore()
   197  
   198  	src := filepath.Join(c.MkDir(), "fifo")
   199  	err := syscall.Mkfifo(src, 0644)
   200  	c.Assert(err, IsNil)
   201  	dir := c.MkDir()
   202  	dst := filepath.Join(dir, "copied-fifo")
   203  
   204  	err = CopySpecialFile(src, dst)
   205  	c.Assert(err, IsNil)
   206  
   207  	st, err := os.Stat(dst)
   208  	c.Assert(err, IsNil)
   209  	c.Check((st.Mode() & os.ModeNamedPipe), Equals, os.ModeNamedPipe)
   210  	c.Check(sync.Calls(), DeepEquals, [][]string{{"sync", dir}})
   211  }
   212  
   213  func (s *cpSuite) TestCopySpecialFileErrors(c *C) {
   214  	err := CopySpecialFile("no-such-file", "no-such-target")
   215  	c.Assert(err, ErrorMatches, "failed to copy device node:.*cp:.*stat.*no-such-file.*")
   216  }
   217  
   218  func (s *cpSuite) TestCopyPreserveAll(c *C) {
   219  	src := filepath.Join(c.MkDir(), "meep")
   220  	dst := filepath.Join(c.MkDir(), "copied-meep")
   221  
   222  	err := ioutil.WriteFile(src, []byte(nil), 0644)
   223  	c.Assert(err, IsNil)
   224  
   225  	// Give the file a different mtime to ensure CopyFlagPreserveAll
   226  	// really works.
   227  	//
   228  	// You wonder why "touch" is used? And want to me about
   229  	// syscall.Utime()? Well, syscall not implemented on armhf
   230  	// Aha, syscall.Utimes() then? No, not implemented on arm64
   231  	// Really, this is a just a test, touch is good enough!
   232  	err = exec.Command("touch", src, "-d", "2007-08-23 08:21:42").Run()
   233  	c.Assert(err, IsNil)
   234  
   235  	err = CopyFile(src, dst, CopyFlagPreserveAll)
   236  	c.Assert(err, IsNil)
   237  
   238  	// ensure that the mtime got preserved
   239  	st1, err := os.Stat(src)
   240  	c.Assert(err, IsNil)
   241  	st2, err := os.Stat(dst)
   242  	c.Assert(err, IsNil)
   243  	c.Assert(st1.ModTime(), Equals, st2.ModTime())
   244  }
   245  
   246  func (s *cpSuite) TestCopyPreserveAllSync(c *C) {
   247  	dir := c.MkDir()
   248  	mocked := testutil.MockCommand(c, "cp", "").Also("sync", "")
   249  	defer mocked.Restore()
   250  
   251  	src := filepath.Join(dir, "meep")
   252  	dst := filepath.Join(dir, "copied-meep")
   253  
   254  	err := ioutil.WriteFile(src, []byte(nil), 0644)
   255  	c.Assert(err, IsNil)
   256  
   257  	err = CopyFile(src, dst, CopyFlagPreserveAll|CopyFlagSync)
   258  	c.Assert(err, IsNil)
   259  
   260  	c.Check(mocked.Calls(), DeepEquals, [][]string{
   261  		{"cp", "-av", src, dst},
   262  		{"sync"},
   263  	})
   264  }
   265  
   266  func (s *cpSuite) TestCopyPreserveAllSyncCpFailure(c *C) {
   267  	dir := c.MkDir()
   268  	mocked := testutil.MockCommand(c, "cp", "echo OUCH: cp failed.;exit 42").Also("sync", "")
   269  	defer mocked.Restore()
   270  
   271  	src := filepath.Join(dir, "meep")
   272  	dst := filepath.Join(dir, "copied-meep")
   273  
   274  	err := ioutil.WriteFile(src, []byte(nil), 0644)
   275  	c.Assert(err, IsNil)
   276  
   277  	err = CopyFile(src, dst, CopyFlagPreserveAll|CopyFlagSync)
   278  	c.Assert(err, ErrorMatches, `failed to copy all: "OUCH: cp failed." \(42\)`)
   279  	c.Check(mocked.Calls(), DeepEquals, [][]string{
   280  		{"cp", "-av", src, dst},
   281  	})
   282  }
   283  
   284  func (s *cpSuite) TestCopyPreserveAllSyncSyncFailure(c *C) {
   285  	dir := c.MkDir()
   286  	mocked := testutil.MockCommand(c, "cp", "").Also("sync", "echo OUCH: sync failed.;exit 42")
   287  	defer mocked.Restore()
   288  
   289  	src := filepath.Join(dir, "meep")
   290  	dst := filepath.Join(dir, "copied-meep")
   291  
   292  	err := ioutil.WriteFile(src, []byte(nil), 0644)
   293  	c.Assert(err, IsNil)
   294  
   295  	err = CopyFile(src, dst, CopyFlagPreserveAll|CopyFlagSync)
   296  	c.Assert(err, ErrorMatches, `failed to sync: "OUCH: sync failed." \(42\)`)
   297  
   298  	c.Check(mocked.Calls(), DeepEquals, [][]string{
   299  		{"cp", "-av", src, dst},
   300  		{"sync"},
   301  	})
   302  }
   303  
   304  func (s *cpSuite) TestAtomicWriteFileCopySimple(c *C) {
   305  	err := AtomicWriteFileCopy(s.f2, s.f1, 0)
   306  	c.Assert(err, IsNil)
   307  	c.Assert(s.f2, testutil.FileEquals, s.data)
   308  
   309  }
   310  
   311  func (s *cpSuite) TestAtomicWriteFileCopyOverwrites(c *C) {
   312  	err := ioutil.WriteFile(s.f2, []byte("this is f2 content"), 0644)
   313  	c.Assert(err, IsNil)
   314  
   315  	err = AtomicWriteFileCopy(s.f2, s.f1, 0)
   316  	c.Assert(err, IsNil)
   317  	c.Assert(s.f2, testutil.FileEquals, s.data)
   318  }
   319  
   320  func (s *cpSuite) TestAtomicWriteFileCopySymlinks(c *C) {
   321  	f2Symlink := filepath.Join(s.dir, "f2-symlink")
   322  	err := os.Symlink(s.f2, f2Symlink)
   323  	c.Assert(err, IsNil)
   324  
   325  	f2SymlinkNoFollow := filepath.Join(s.dir, "f2-symlink-no-follow")
   326  	err = os.Symlink(s.f2, f2SymlinkNoFollow)
   327  	c.Assert(err, IsNil)
   328  
   329  	// follows symlink, dst is f2
   330  	err = AtomicWriteFileCopy(f2Symlink, s.f1, AtomicWriteFollow)
   331  	c.Assert(err, IsNil)
   332  	c.Check(IsSymlink(f2Symlink), Equals, true, Commentf("%q is not a symlink", f2Symlink))
   333  	c.Check(s.f2, testutil.FileEquals, s.data)
   334  	c.Check(f2SymlinkNoFollow, testutil.FileEquals, s.data)
   335  
   336  	// when not following, copy overwrites the symlink
   337  	err = AtomicWriteFileCopy(f2SymlinkNoFollow, s.f1, 0)
   338  	c.Assert(err, IsNil)
   339  	c.Check(IsSymlink(f2SymlinkNoFollow), Equals, false, Commentf("%q is not a file", f2SymlinkNoFollow))
   340  	c.Check(f2SymlinkNoFollow, testutil.FileEquals, s.data)
   341  }
   342  
   343  func (s *cpSuite) TestAtomicWriteFileCopyErrReal(c *C) {
   344  	err := AtomicWriteFileCopy(s.f2, filepath.Join(s.dir, "random-file"), 0)
   345  	c.Assert(err, ErrorMatches, "unable to open source file .*/random-file: open .* no such file or directory")
   346  
   347  	dir := c.MkDir()
   348  
   349  	err = AtomicWriteFileCopy(filepath.Join(dir, "random-dir", "f3"), s.f1, 0)
   350  	c.Assert(err, ErrorMatches, `cannot create atomic file: open .*/random-dir/f3\.[a-zA-Z0-9]+~: no such file or directory`)
   351  
   352  	err = os.MkdirAll(filepath.Join(dir, "read-only"), 0000)
   353  	c.Assert(err, IsNil)
   354  	err = AtomicWriteFileCopy(filepath.Join(dir, "read-only", "f3"), s.f1, 0)
   355  	c.Assert(err, ErrorMatches, `cannot create atomic file: open .*/read-only/f3\.[a-zA-Z0-9]+~: permission denied`)
   356  }
   357  
   358  func (s *cpSuite) TestAtomicWriteFileCopyErrMockedCopy(c *C) {
   359  	s.mock()
   360  	s.errs = []error{
   361  		nil, // openFile
   362  		nil, // src.Stat()
   363  		errors.New("copy fail"),
   364  	}
   365  
   366  	err := AtomicWriteFileCopy(s.f2, s.f1, 0)
   367  	c.Assert(err, ErrorMatches, `unable to copy .*/f1 to .*/f2\.[a-zA-Z0-9]+~: copy fail`)
   368  	entries, err := filepath.Glob(filepath.Join(s.dir, "*"))
   369  	c.Assert(err, IsNil)
   370  	c.Assert(entries, DeepEquals, []string{
   371  		filepath.Join(s.dir, "f1"),
   372  	})
   373  }