| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 | 
							- // Copyright (C) 2017 The Syncthing Authors.
 
- //
 
- // This Source Code Form is subject to the terms of the Mozilla Public
 
- // License, v. 2.0. If a copy of the MPL was not distributed with this file,
 
- // You can obtain one at https://mozilla.org/MPL/2.0/.
 
- package fs
 
- import (
 
- 	"io/ioutil"
 
- 	"os"
 
- 	"path/filepath"
 
- 	"runtime"
 
- 	"sort"
 
- 	"strings"
 
- 	"testing"
 
- 	"time"
 
- 	"github.com/syncthing/syncthing/lib/rand"
 
- )
 
- func setup(t *testing.T) (*BasicFilesystem, string) {
 
- 	t.Helper()
 
- 	dir, err := ioutil.TempDir("", "")
 
- 	if err != nil {
 
- 		t.Fatal(err)
 
- 	}
 
- 	return newBasicFilesystem(dir), dir
 
- }
 
- func TestChmodFile(t *testing.T) {
 
- 	fs, dir := setup(t)
 
- 	path := filepath.Join(dir, "file")
 
- 	defer os.RemoveAll(dir)
 
- 	defer os.Chmod(path, 0666)
 
- 	fd, err := os.Create(path)
 
- 	if err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	fd.Close()
 
- 	if err := os.Chmod(path, 0666); err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	if stat, err := os.Stat(path); err != nil || stat.Mode()&os.ModePerm != 0666 {
 
- 		t.Errorf("wrong perm: %t %#o", err == nil, stat.Mode()&os.ModePerm)
 
- 	}
 
- 	if err := fs.Chmod("file", 0444); err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	if stat, err := os.Stat(path); err != nil || stat.Mode()&os.ModePerm != 0444 {
 
- 		t.Errorf("wrong perm: %t %#o", err == nil, stat.Mode()&os.ModePerm)
 
- 	}
 
- }
 
- func TestChownFile(t *testing.T) {
 
- 	if runtime.GOOS == "windows" {
 
- 		t.Skip("Not supported on Windows")
 
- 		return
 
- 	}
 
- 	if os.Getuid() != 0 {
 
- 		// We are not root. No expectation of being able to chown. Our tests
 
- 		// typically don't run with CAP_FOWNER.
 
- 		t.Skip("Test not possible")
 
- 		return
 
- 	}
 
- 	fs, dir := setup(t)
 
- 	path := filepath.Join(dir, "file")
 
- 	defer os.RemoveAll(dir)
 
- 	defer os.Chmod(path, 0666)
 
- 	fd, err := os.Create(path)
 
- 	if err != nil {
 
- 		t.Error("Unexpected error:", err)
 
- 	}
 
- 	fd.Close()
 
- 	_, err = fs.Lstat("file")
 
- 	if err != nil {
 
- 		t.Error("Unexpected error:", err)
 
- 	}
 
- 	newUID := 1000 + rand.Intn(30000)
 
- 	newGID := 1000 + rand.Intn(30000)
 
- 	if err := fs.Lchown("file", newUID, newGID); err != nil {
 
- 		t.Error("Unexpected error:", err)
 
- 	}
 
- 	info, err := fs.Lstat("file")
 
- 	if err != nil {
 
- 		t.Error("Unexpected error:", err)
 
- 	}
 
- 	if info.Owner() != newUID {
 
- 		t.Errorf("Incorrect owner, expected %d but got %d", newUID, info.Owner())
 
- 	}
 
- 	if info.Group() != newGID {
 
- 		t.Errorf("Incorrect group, expected %d but got %d", newGID, info.Group())
 
- 	}
 
- }
 
- func TestChmodDir(t *testing.T) {
 
- 	fs, dir := setup(t)
 
- 	path := filepath.Join(dir, "dir")
 
- 	defer os.RemoveAll(dir)
 
- 	mode := os.FileMode(0755)
 
- 	if runtime.GOOS == "windows" {
 
- 		mode = os.FileMode(0777)
 
- 	}
 
- 	defer os.Chmod(path, mode)
 
- 	if err := os.Mkdir(path, mode); err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	if stat, err := os.Stat(path); err != nil || stat.Mode()&os.ModePerm != mode {
 
- 		t.Errorf("wrong perm: %t %#o", err == nil, stat.Mode()&os.ModePerm)
 
- 	}
 
- 	if err := fs.Chmod("dir", 0555); err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	if stat, err := os.Stat(path); err != nil || stat.Mode()&os.ModePerm != 0555 {
 
- 		t.Errorf("wrong perm: %t %#o", err == nil, stat.Mode()&os.ModePerm)
 
- 	}
 
- }
 
- func TestChtimes(t *testing.T) {
 
- 	fs, dir := setup(t)
 
- 	path := filepath.Join(dir, "file")
 
- 	defer os.RemoveAll(dir)
 
- 	fd, err := os.Create(path)
 
- 	if err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	fd.Close()
 
- 	mtime := time.Now().Add(-time.Hour)
 
- 	fs.Chtimes("file", mtime, mtime)
 
- 	stat, err := os.Stat(path)
 
- 	if err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	diff := stat.ModTime().Sub(mtime)
 
- 	if diff > 3*time.Second || diff < -3*time.Second {
 
- 		t.Errorf("%s != %s", stat.Mode(), mtime)
 
- 	}
 
- }
 
- func TestCreate(t *testing.T) {
 
- 	fs, dir := setup(t)
 
- 	path := filepath.Join(dir, "file")
 
- 	defer os.RemoveAll(dir)
 
- 	if _, err := os.Stat(path); err == nil {
 
- 		t.Errorf("exists?")
 
- 	}
 
- 	fd, err := fs.Create("file")
 
- 	if err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	fd.Close()
 
- 	if _, err := os.Stat(path); err != nil {
 
- 		t.Error(err)
 
- 	}
 
- }
 
- func TestCreateSymlink(t *testing.T) {
 
- 	if runtime.GOOS == "windows" {
 
- 		t.Skip("windows not supported")
 
- 	}
 
- 	fs, dir := setup(t)
 
- 	path := filepath.Join(dir, "file")
 
- 	defer os.RemoveAll(dir)
 
- 	if err := fs.CreateSymlink("blah", "file"); err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	if target, err := os.Readlink(path); err != nil || target != "blah" {
 
- 		t.Error("target", target, "err", err)
 
- 	}
 
- 	if err := os.Remove(path); err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	if err := fs.CreateSymlink(filepath.Join("..", "blah"), "file"); err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	if target, err := os.Readlink(path); err != nil || target != filepath.Join("..", "blah") {
 
- 		t.Error("target", target, "err", err)
 
- 	}
 
- }
 
- func TestDirNames(t *testing.T) {
 
- 	fs, dir := setup(t)
 
- 	defer os.RemoveAll(dir)
 
- 	// Case differences
 
- 	testCases := []string{
 
- 		"a",
 
- 		"bC",
 
- 	}
 
- 	sort.Strings(testCases)
 
- 	for _, sub := range testCases {
 
- 		if err := os.Mkdir(filepath.Join(dir, sub), 0777); err != nil {
 
- 			t.Error(err)
 
- 		}
 
- 	}
 
- 	if dirs, err := fs.DirNames("."); err != nil || len(dirs) != len(testCases) {
 
- 		t.Errorf("%s %s %s", err, dirs, testCases)
 
- 	} else {
 
- 		sort.Strings(dirs)
 
- 		for i := range dirs {
 
- 			if dirs[i] != testCases[i] {
 
- 				t.Errorf("%s != %s", dirs[i], testCases[i])
 
- 			}
 
- 		}
 
- 	}
 
- }
 
- func TestNames(t *testing.T) {
 
- 	// Tests that all names are without the root directory.
 
- 	fs, dir := setup(t)
 
- 	defer os.RemoveAll(dir)
 
- 	expected := "file"
 
- 	fd, err := fs.Create(expected)
 
- 	if err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	defer fd.Close()
 
- 	if fd.Name() != expected {
 
- 		t.Errorf("incorrect %s != %s", fd.Name(), expected)
 
- 	}
 
- 	if stat, err := fd.Stat(); err != nil || stat.Name() != expected {
 
- 		t.Errorf("incorrect %s != %s (%v)", stat.Name(), expected, err)
 
- 	}
 
- 	if err := fs.Mkdir("dir", 0777); err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	expected = filepath.Join("dir", "file")
 
- 	fd, err = fs.Create(expected)
 
- 	if err != nil {
 
- 		t.Error(err)
 
- 	}
 
- 	defer fd.Close()
 
- 	if fd.Name() != expected {
 
- 		t.Errorf("incorrect %s != %s", fd.Name(), expected)
 
- 	}
 
- 	// os.fd.Stat() returns just base, so do we.
 
- 	if stat, err := fd.Stat(); err != nil || stat.Name() != filepath.Base(expected) {
 
- 		t.Errorf("incorrect %s != %s (%v)", stat.Name(), filepath.Base(expected), err)
 
- 	}
 
- }
 
- func TestGlob(t *testing.T) {
 
- 	// Tests that all names are without the root directory.
 
- 	fs, dir := setup(t)
 
- 	defer os.RemoveAll(dir)
 
- 	for _, dirToCreate := range []string{
 
- 		filepath.Join("a", "test", "b"),
 
- 		filepath.Join("a", "best", "b"),
 
- 		filepath.Join("a", "best", "c"),
 
- 	} {
 
- 		if err := fs.MkdirAll(dirToCreate, 0777); err != nil {
 
- 			t.Error(err)
 
- 		}
 
- 	}
 
- 	testCases := []struct {
 
- 		pattern string
 
- 		matches []string
 
- 	}{
 
- 		{
 
- 			filepath.Join("a", "?est", "?"),
 
- 			[]string{
 
- 				filepath.Join("a", "test", "b"),
 
- 				filepath.Join("a", "best", "b"),
 
- 				filepath.Join("a", "best", "c"),
 
- 			},
 
- 		},
 
- 		{
 
- 			filepath.Join("a", "?est", "b"),
 
- 			[]string{
 
- 				filepath.Join("a", "test", "b"),
 
- 				filepath.Join("a", "best", "b"),
 
- 			},
 
- 		},
 
- 		{
 
- 			filepath.Join("a", "best", "?"),
 
- 			[]string{
 
- 				filepath.Join("a", "best", "b"),
 
- 				filepath.Join("a", "best", "c"),
 
- 			},
 
- 		},
 
- 	}
 
- 	for _, testCase := range testCases {
 
- 		results, err := fs.Glob(testCase.pattern)
 
- 		sort.Strings(results)
 
- 		sort.Strings(testCase.matches)
 
- 		if err != nil {
 
- 			t.Error(err)
 
- 		}
 
- 		if len(results) != len(testCase.matches) {
 
- 			t.Errorf("result count mismatch")
 
- 		}
 
- 		for i := range testCase.matches {
 
- 			if results[i] != testCase.matches[i] {
 
- 				t.Errorf("%s != %s", results[i], testCase.matches[i])
 
- 			}
 
- 		}
 
- 	}
 
- }
 
- func TestUsage(t *testing.T) {
 
- 	fs, dir := setup(t)
 
- 	defer os.RemoveAll(dir)
 
- 	usage, err := fs.Usage(".")
 
- 	if err != nil {
 
- 		if runtime.GOOS == "netbsd" || runtime.GOOS == "openbsd" || runtime.GOOS == "solaris" {
 
- 			t.Skip()
 
- 		}
 
- 		t.Errorf("Unexpected error: %s", err)
 
- 	}
 
- 	if usage.Free < 1 {
 
- 		t.Error("Disk is full?", usage.Free)
 
- 	}
 
- }
 
- func TestRooted(t *testing.T) {
 
- 	type testcase struct {
 
- 		root   string
 
- 		rel    string
 
- 		joined string
 
- 		ok     bool
 
- 	}
 
- 	cases := []testcase{
 
- 		// Valid cases
 
- 		{"foo", "bar", "foo/bar", true},
 
- 		{"foo", "/bar", "foo/bar", true},
 
- 		{"foo/", "bar", "foo/bar", true},
 
- 		{"foo/", "/bar", "foo/bar", true},
 
- 		{"baz/foo", "bar", "baz/foo/bar", true},
 
- 		{"baz/foo", "/bar", "baz/foo/bar", true},
 
- 		{"baz/foo/", "bar", "baz/foo/bar", true},
 
- 		{"baz/foo/", "/bar", "baz/foo/bar", true},
 
- 		{"foo", "bar/baz", "foo/bar/baz", true},
 
- 		{"foo", "/bar/baz", "foo/bar/baz", true},
 
- 		{"foo/", "bar/baz", "foo/bar/baz", true},
 
- 		{"foo/", "/bar/baz", "foo/bar/baz", true},
 
- 		{"baz/foo", "bar/baz", "baz/foo/bar/baz", true},
 
- 		{"baz/foo", "/bar/baz", "baz/foo/bar/baz", true},
 
- 		{"baz/foo/", "bar/baz", "baz/foo/bar/baz", true},
 
- 		{"baz/foo/", "/bar/baz", "baz/foo/bar/baz", true},
 
- 		// Not escape attempts, but oddly formatted relative paths.
 
- 		{"foo", "", "foo", true},
 
- 		{"foo", "/", "foo", true},
 
- 		{"foo", "/..", "foo", true},
 
- 		{"foo", "./bar", "foo/bar", true},
 
- 		{"foo/", "", "foo", true},
 
- 		{"foo/", "/", "foo", true},
 
- 		{"foo/", "/..", "foo", true},
 
- 		{"foo/", "./bar", "foo/bar", true},
 
- 		{"baz/foo", "./bar", "baz/foo/bar", true},
 
- 		{"foo", "./bar/baz", "foo/bar/baz", true},
 
- 		{"baz/foo", "./bar/baz", "baz/foo/bar/baz", true},
 
- 		{"baz/foo", "bar/../baz", "baz/foo/baz", true},
 
- 		{"baz/foo", "/bar/../baz", "baz/foo/baz", true},
 
- 		{"baz/foo", "./bar/../baz", "baz/foo/baz", true},
 
- 		// Results in an allowed path, but does it by probing. Disallowed.
 
- 		{"foo", "../foo", "", false},
 
- 		{"foo", "../foo/bar", "", false},
 
- 		{"baz/foo", "../foo/bar", "", false},
 
- 		{"baz/foo", "../../baz/foo/bar", "", false},
 
- 		{"baz/foo", "bar/../../foo/bar", "", false},
 
- 		{"baz/foo", "bar/../../../baz/foo/bar", "", false},
 
- 		// Escape attempts.
 
- 		{"foo", "..", "", false},
 
- 		{"foo", "../", "", false},
 
- 		{"foo", "../bar", "", false},
 
- 		{"foo", "../foobar", "", false},
 
- 		{"foo/", "../bar", "", false},
 
- 		{"foo/", "../foobar", "", false},
 
- 		{"baz/foo", "../bar", "", false},
 
- 		{"baz/foo", "../foobar", "", false},
 
- 		{"baz/foo/", "../bar", "", false},
 
- 		{"baz/foo/", "../foobar", "", false},
 
- 		{"baz/foo/", "bar/../../quux/baz", "", false},
 
- 		// Empty root is a misconfiguration.
 
- 		{"", "/foo", "", false},
 
- 		{"", "foo", "", false},
 
- 		{"", ".", "", false},
 
- 		{"", "..", "", false},
 
- 		{"", "/", "", false},
 
- 		{"", "", "", false},
 
- 		// Root=/ is valid, and things should be verified as usual.
 
- 		{"/", "foo", "/foo", true},
 
- 		{"/", "/foo", "/foo", true},
 
- 		{"/", "../foo", "", false},
 
- 		{"/", "..", "", false},
 
- 		{"/", "/", "/", true},
 
- 		{"/", "", "/", true},
 
- 		// special case for filesystems to be able to MkdirAll('.') for example
 
- 		{"/", ".", "/", true},
 
- 	}
 
- 	if runtime.GOOS == "windows" {
 
- 		extraCases := []testcase{
 
- 			{`c:\`, `foo`, `c:\foo`, true},
 
- 			{`\\?\c:\`, `foo`, `\\?\c:\foo`, true},
 
- 			{`c:\`, `\foo`, `c:\foo`, true},
 
- 			{`\\?\c:\`, `\foo`, `\\?\c:\foo`, true},
 
- 			{`c:\`, `\\foo`, ``, false},
 
- 			{`c:\`, ``, `c:\`, true},
 
- 			{`c:\`, `\`, `c:\`, true},
 
- 			{`\\?\c:\`, `\\foo`, ``, false},
 
- 			{`\\?\c:\`, ``, `\\?\c:\`, true},
 
- 			{`\\?\c:\`, `\`, `\\?\c:\`, true},
 
- 			{`\\?\c:\test`, `.`, `\\?\c:\test`, true},
 
- 			{`c:\test`, `.`, `c:\test`, true},
 
- 			{`\\?\c:\test`, `/`, `\\?\c:\test`, true},
 
- 			{`c:\test`, ``, `c:\test`, true},
 
- 			// makes no sense, but will be treated simply as a bad filename
 
- 			{`c:\foo`, `d:\bar`, `c:\foo\d:\bar`, true},
 
- 			// special case for filesystems to be able to MkdirAll('.') for example
 
- 			{`c:\`, `.`, `c:\`, true},
 
- 			{`\\?\c:\`, `.`, `\\?\c:\`, true},
 
- 		}
 
- 		for _, tc := range cases {
 
- 			// Add case where root is backslashed, rel is forward slashed
 
- 			extraCases = append(extraCases, testcase{
 
- 				root:   filepath.FromSlash(tc.root),
 
- 				rel:    tc.rel,
 
- 				joined: tc.joined,
 
- 				ok:     tc.ok,
 
- 			})
 
- 			// and the opposite
 
- 			extraCases = append(extraCases, testcase{
 
- 				root:   tc.root,
 
- 				rel:    filepath.FromSlash(tc.rel),
 
- 				joined: tc.joined,
 
- 				ok:     tc.ok,
 
- 			})
 
- 			// and both backslashed
 
- 			extraCases = append(extraCases, testcase{
 
- 				root:   filepath.FromSlash(tc.root),
 
- 				rel:    filepath.FromSlash(tc.rel),
 
- 				joined: tc.joined,
 
- 				ok:     tc.ok,
 
- 			})
 
- 		}
 
- 		cases = append(cases, extraCases...)
 
- 	}
 
- 	for _, tc := range cases {
 
- 		fs := BasicFilesystem{root: tc.root}
 
- 		res, err := fs.rooted(tc.rel)
 
- 		if tc.ok {
 
- 			if err != nil {
 
- 				t.Errorf("Unexpected error for rooted(%q, %q): %v", tc.root, tc.rel, err)
 
- 				continue
 
- 			}
 
- 			exp := filepath.FromSlash(tc.joined)
 
- 			if res != exp {
 
- 				t.Errorf("Unexpected result for rooted(%q, %q): %q != expected %q", tc.root, tc.rel, res, exp)
 
- 			}
 
- 		} else if err == nil {
 
- 			t.Errorf("Unexpected pass for rooted(%q, %q) => %q", tc.root, tc.rel, res)
 
- 			continue
 
- 		}
 
- 	}
 
- }
 
- func TestNewBasicFilesystem(t *testing.T) {
 
- 	if runtime.GOOS == "windows" {
 
- 		t.Skip("non-windows root paths")
 
- 	}
 
- 	currentDir, err := filepath.Abs(".")
 
- 	if err != nil {
 
- 		t.Fatal(err)
 
- 	}
 
- 	testCases := []struct {
 
- 		input        string
 
- 		expectedRoot string
 
- 		expectedURI  string
 
- 	}{
 
- 		{"/foo/bar/baz", "/foo/bar/baz", "/foo/bar/baz"},
 
- 		{"/foo/bar/baz/", "/foo/bar/baz", "/foo/bar/baz"},
 
- 		{"", currentDir, currentDir},
 
- 		{".", currentDir, currentDir},
 
- 		{"/", "/", "/"},
 
- 	}
 
- 	for _, testCase := range testCases {
 
- 		fs := newBasicFilesystem(testCase.input)
 
- 		if fs.root != testCase.expectedRoot {
 
- 			t.Errorf("root %q != %q", fs.root, testCase.expectedRoot)
 
- 		}
 
- 		if fs.URI() != testCase.expectedURI {
 
- 			t.Errorf("uri %q != %q", fs.URI(), testCase.expectedURI)
 
- 		}
 
- 	}
 
- 	fs := newBasicFilesystem("relative/path")
 
- 	if fs.root == "relative/path" || !strings.HasPrefix(fs.root, string(PathSeparator)) {
 
- 		t.Errorf(`newBasicFilesystem("relative/path").root == %q, expected absolutification`, fs.root)
 
- 	}
 
- }
 
- func TestRel(t *testing.T) {
 
- 	testCases := []struct {
 
- 		root        string
 
- 		abs         string
 
- 		expectedRel string
 
- 	}{
 
- 		{"/", "/", ""},
 
- 		{"/", "/test", "test"},
 
- 		{"/", "/Test", "Test"},
 
- 		{"/Test", "/Test/test", "test"},
 
- 	}
 
- 	if runtime.GOOS == "windows" {
 
- 		for i := range testCases {
 
- 			testCases[i].root = filepath.FromSlash(testCases[i].root)
 
- 			testCases[i].abs = filepath.FromSlash(testCases[i].abs)
 
- 			testCases[i].expectedRel = filepath.FromSlash(testCases[i].expectedRel)
 
- 		}
 
- 	}
 
- 	for _, tc := range testCases {
 
- 		if res := rel(tc.abs, tc.root); res != tc.expectedRel {
 
- 			t.Errorf(`rel("%v", "%v") == "%v", expected "%v"`, tc.abs, tc.root, res, tc.expectedRel)
 
- 		}
 
- 	}
 
- }
 
- func TestBasicWalkSkipSymlink(t *testing.T) {
 
- 	_, dir := setup(t)
 
- 	defer os.RemoveAll(dir)
 
- 	testWalkSkipSymlink(t, FilesystemTypeBasic, dir)
 
- }
 
- func TestWalkTraverseDirJunct(t *testing.T) {
 
- 	_, dir := setup(t)
 
- 	defer os.RemoveAll(dir)
 
- 	testWalkTraverseDirJunct(t, FilesystemTypeBasic, dir)
 
- }
 
- func TestWalkInfiniteRecursion(t *testing.T) {
 
- 	_, dir := setup(t)
 
- 	defer os.RemoveAll(dir)
 
- 	testWalkInfiniteRecursion(t, FilesystemTypeBasic, dir)
 
- }
 
 
  |