github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/osutil/syncdir_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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_test
    21  
    22  import (
    23  	"io/ioutil"
    24  	"os"
    25  	"path"
    26  	"path/filepath"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/osutil"
    31  	"github.com/snapcore/snapd/testutil"
    32  )
    33  
    34  type EnsureDirStateSuite struct {
    35  	dir  string
    36  	glob string
    37  }
    38  
    39  var _ = Suite(&EnsureDirStateSuite{glob: "*.snap"})
    40  
    41  func (s *EnsureDirStateSuite) SetUpTest(c *C) {
    42  	s.dir = c.MkDir()
    43  }
    44  
    45  func (s *EnsureDirStateSuite) TestVerifiesExpectedFiles(c *C) {
    46  	name := filepath.Join(s.dir, "expected.snap")
    47  	err := ioutil.WriteFile(name, []byte("expected"), 0600)
    48  	c.Assert(err, IsNil)
    49  	changed, removed, err := osutil.EnsureDirState(s.dir, s.glob, map[string]osutil.FileState{
    50  		"expected.snap": &osutil.MemoryFileState{Content: []byte("expected"), Mode: 0600},
    51  	})
    52  	c.Assert(err, IsNil)
    53  	// Report says that nothing has changed
    54  	c.Assert(changed, HasLen, 0)
    55  	c.Assert(removed, HasLen, 0)
    56  	// The content is correct
    57  	c.Assert(path.Join(s.dir, "expected.snap"), testutil.FileEquals, "expected")
    58  	// The permissions are correct
    59  	stat, err := os.Stat(name)
    60  	c.Assert(err, IsNil)
    61  	c.Assert(stat.Mode().Perm(), Equals, os.FileMode(0600))
    62  }
    63  
    64  func (s *EnsureDirStateSuite) TestTwoPatterns(c *C) {
    65  	name1 := filepath.Join(s.dir, "expected.snap")
    66  	err := ioutil.WriteFile(name1, []byte("expected-1"), 0600)
    67  	c.Assert(err, IsNil)
    68  
    69  	name2 := filepath.Join(s.dir, "expected.snap-update-ns")
    70  	err = ioutil.WriteFile(name2, []byte("expected-2"), 0600)
    71  	c.Assert(err, IsNil)
    72  
    73  	changed, removed, err := osutil.EnsureDirStateGlobs(s.dir, []string{"*.snap", "*.snap-update-ns"}, map[string]osutil.FileState{
    74  		"expected.snap":           &osutil.MemoryFileState{Content: []byte("expected-1"), Mode: 0600},
    75  		"expected.snap-update-ns": &osutil.MemoryFileState{Content: []byte("expected-2"), Mode: 0600},
    76  	})
    77  	c.Assert(err, IsNil)
    78  	// Report says that nothing has changed
    79  	c.Assert(changed, HasLen, 0)
    80  	c.Assert(removed, HasLen, 0)
    81  	// The content is correct
    82  	c.Assert(name1, testutil.FileEquals, "expected-1")
    83  	c.Assert(name2, testutil.FileEquals, "expected-2")
    84  	// The permissions are correct
    85  	stat, err := os.Stat(name1)
    86  	c.Assert(err, IsNil)
    87  	c.Assert(stat.Mode().Perm(), Equals, os.FileMode(0600))
    88  	stat, err = os.Stat(name2)
    89  	c.Assert(err, IsNil)
    90  	c.Assert(stat.Mode().Perm(), Equals, os.FileMode(0600))
    91  }
    92  
    93  func (s *EnsureDirStateSuite) TestMultipleMatches(c *C) {
    94  	name := filepath.Join(s.dir, "foo")
    95  	err := ioutil.WriteFile(name, []byte("content"), 0600)
    96  	c.Assert(err, IsNil)
    97  	// When a file is matched by multiple globs it removed correctly.
    98  	changed, removed, err := osutil.EnsureDirStateGlobs(s.dir, []string{"foo", "f*"}, nil)
    99  	c.Assert(err, IsNil)
   100  	c.Assert(changed, HasLen, 0)
   101  	c.Assert(removed, DeepEquals, []string{"foo"})
   102  }
   103  
   104  func (s *EnsureDirStateSuite) TestCreatesMissingFiles(c *C) {
   105  	name := filepath.Join(s.dir, "missing.snap")
   106  	changed, removed, err := osutil.EnsureDirState(s.dir, s.glob, map[string]osutil.FileState{
   107  		"missing.snap": &osutil.MemoryFileState{Content: []byte(`content`), Mode: 0600},
   108  	})
   109  	c.Assert(err, IsNil)
   110  	// Created file is reported
   111  	c.Assert(changed, DeepEquals, []string{"missing.snap"})
   112  	c.Assert(removed, HasLen, 0)
   113  	// The content is correct
   114  	c.Assert(name, testutil.FileEquals, "content")
   115  	// The permissions are correct
   116  	stat, err := os.Stat(name)
   117  	c.Assert(err, IsNil)
   118  	c.Assert(stat.Mode().Perm(), Equals, os.FileMode(0600))
   119  }
   120  
   121  func (s *EnsureDirStateSuite) TestRemovesUnexpectedFiless(c *C) {
   122  	name := filepath.Join(s.dir, "evil.snap")
   123  	err := ioutil.WriteFile(name, []byte(`evil text`), 0600)
   124  	c.Assert(err, IsNil)
   125  	changed, removed, err := osutil.EnsureDirState(s.dir, s.glob, map[string]osutil.FileState{})
   126  	c.Assert(err, IsNil)
   127  	// Removed file is reported
   128  	c.Assert(changed, HasLen, 0)
   129  	c.Assert(removed, DeepEquals, []string{"evil.snap"})
   130  	// The file is removed
   131  	_, err = os.Stat(name)
   132  	c.Assert(os.IsNotExist(err), Equals, true)
   133  }
   134  
   135  func (s *EnsureDirStateSuite) TestIgnoresUnrelatedFiles(c *C) {
   136  	name := filepath.Join(s.dir, "unrelated")
   137  	err := ioutil.WriteFile(name, []byte(`text`), 0600)
   138  	c.Assert(err, IsNil)
   139  	changed, removed, err := osutil.EnsureDirState(s.dir, s.glob, map[string]osutil.FileState{})
   140  	c.Assert(err, IsNil)
   141  	// Report says that nothing has changed
   142  	c.Assert(changed, HasLen, 0)
   143  	c.Assert(removed, HasLen, 0)
   144  	// The file is still there
   145  	_, err = os.Stat(name)
   146  	c.Assert(err, IsNil)
   147  }
   148  
   149  func (s *EnsureDirStateSuite) TestCorrectsFilesWithDifferentSize(c *C) {
   150  	name := filepath.Join(s.dir, "differing.snap")
   151  	err := ioutil.WriteFile(name, []byte(``), 0600)
   152  	c.Assert(err, IsNil)
   153  	changed, removed, err := osutil.EnsureDirState(s.dir, s.glob, map[string]osutil.FileState{
   154  		"differing.snap": &osutil.MemoryFileState{Content: []byte(`Hello World`), Mode: 0600},
   155  	})
   156  	c.Assert(err, IsNil)
   157  	// changed file is reported
   158  	c.Assert(changed, DeepEquals, []string{"differing.snap"})
   159  	c.Assert(removed, HasLen, 0)
   160  	// The content is changed
   161  	c.Assert(name, testutil.FileEquals, "Hello World")
   162  	// The permissions are what we expect
   163  	stat, err := os.Stat(name)
   164  	c.Assert(err, IsNil)
   165  	c.Assert(stat.Mode().Perm(), Equals, os.FileMode(0600))
   166  }
   167  
   168  func (s *EnsureDirStateSuite) TestCorrectsFilesWithSameSize(c *C) {
   169  	name := filepath.Join(s.dir, "differing.snap")
   170  	err := ioutil.WriteFile(name, []byte("evil"), 0600)
   171  	c.Assert(err, IsNil)
   172  	changed, removed, err := osutil.EnsureDirState(s.dir, s.glob, map[string]osutil.FileState{
   173  		"differing.snap": &osutil.MemoryFileState{Content: []byte("good"), Mode: 0600},
   174  	})
   175  	c.Assert(err, IsNil)
   176  	// changed file is reported
   177  	c.Assert(changed, DeepEquals, []string{"differing.snap"})
   178  	c.Assert(removed, HasLen, 0)
   179  	// The content is changed
   180  	c.Assert(name, testutil.FileEquals, "good")
   181  	// The permissions are what we expect
   182  	stat, err := os.Stat(name)
   183  	c.Assert(err, IsNil)
   184  	c.Assert(stat.Mode().Perm(), Equals, os.FileMode(0600))
   185  }
   186  
   187  func (s *EnsureDirStateSuite) TestFixesFilesWithBadPermissions(c *C) {
   188  	name := filepath.Join(s.dir, "sensitive.snap")
   189  	// NOTE: the existing file is currently wide-open for everyone"
   190  	err := ioutil.WriteFile(name, []byte("password"), 0666)
   191  	c.Assert(err, IsNil)
   192  	changed, removed, err := osutil.EnsureDirState(s.dir, s.glob, map[string]osutil.FileState{
   193  		// NOTE: we want the file to be private
   194  		"sensitive.snap": &osutil.MemoryFileState{Content: []byte("password"), Mode: 0600},
   195  	})
   196  	c.Assert(err, IsNil)
   197  	// changed file is reported
   198  	c.Assert(changed, DeepEquals, []string{"sensitive.snap"})
   199  	c.Assert(removed, HasLen, 0)
   200  	// The content is still the same
   201  	c.Assert(name, testutil.FileEquals, "password")
   202  	// The permissions are changed
   203  	stat, err := os.Stat(name)
   204  	c.Assert(err, IsNil)
   205  	c.Assert(stat.Mode().Perm(), Equals, os.FileMode(0600))
   206  }
   207  
   208  func (s *EnsureDirStateSuite) TestReportsAbnormalFileLocation(c *C) {
   209  	_, _, err := osutil.EnsureDirState(s.dir, s.glob, map[string]osutil.FileState{"subdir/file.snap": &osutil.MemoryFileState{}})
   210  	c.Assert(err, ErrorMatches, `internal error: EnsureDirState got filename "subdir/file.snap" which has a path component`)
   211  }
   212  
   213  func (s *EnsureDirStateSuite) TestReportsAbnormalFileName(c *C) {
   214  	_, _, err := osutil.EnsureDirState(s.dir, s.glob, map[string]osutil.FileState{"without-namespace": &osutil.MemoryFileState{}})
   215  	c.Assert(err, ErrorMatches, `internal error: EnsureDirState got filename "without-namespace" which doesn't match the glob pattern "\*\.snap"`)
   216  }
   217  
   218  func (s *EnsureDirStateSuite) TestReportsAbnormalPatterns(c *C) {
   219  	_, _, err := osutil.EnsureDirState(s.dir, "[", nil)
   220  	c.Assert(err, ErrorMatches, `internal error: EnsureDirState got invalid pattern "\[": syntax error in pattern`)
   221  }
   222  
   223  func (s *EnsureDirStateSuite) TestRemovesAllManagedFilesOnError(c *C) {
   224  	// Create a "prior.snap" file
   225  	prior := filepath.Join(s.dir, "prior.snap")
   226  	err := ioutil.WriteFile(prior, []byte("data"), 0600)
   227  	c.Assert(err, IsNil)
   228  	// Create a "clash.snap" directory to simulate failure
   229  	clash := filepath.Join(s.dir, "clash.snap")
   230  	err = os.Mkdir(clash, 0000)
   231  	c.Assert(err, IsNil)
   232  	// Try to ensure directory state
   233  	changed, removed, err := osutil.EnsureDirState(s.dir, s.glob, map[string]osutil.FileState{
   234  		"prior.snap": &osutil.MemoryFileState{Content: []byte("data"), Mode: 0600},
   235  		"clash.snap": &osutil.MemoryFileState{Content: []byte("data"), Mode: 0600},
   236  	})
   237  	c.Assert(changed, HasLen, 0)
   238  	c.Assert(removed, DeepEquals, []string{"clash.snap", "prior.snap"})
   239  	c.Assert(err, ErrorMatches, "open .*/clash.snap: permission denied")
   240  	// The clashing file is removed
   241  	_, err = os.Stat(clash)
   242  	c.Assert(os.IsNotExist(err), Equals, true)
   243  }