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 }