Beispiel #1
0
func TestWalkSymlink(t *testing.T) {
	if !symlinks.Supported {
		t.Skip("skipping unsupported symlink test")
		return
	}

	// Create a folder with a symlink in it

	os.RemoveAll("_symlinks")
	defer os.RemoveAll("_symlinks")

	os.Mkdir("_symlinks", 0755)
	symlinks.Create("_symlinks/link", "destination", symlinks.TargetUnknown)

	// Scan it

	fchan, err := Walk(Config{
		Dir:       "_symlinks",
		BlockSize: 128 * 1024,
	})

	if err != nil {
		t.Fatal(err)
	}

	var files []protocol.FileInfo
	for f := range fchan {
		files = append(files, f)
	}

	// Verify that we got one symlink and with the correct block contents

	if len(files) != 1 {
		t.Errorf("expected 1 symlink, not %d", len(files))
	}
	if len(files[0].Blocks) != 1 {
		t.Errorf("expected 1 block, not %d", len(files[0].Blocks))
	}

	if files[0].Blocks[0].Size != int32(len("destination")) {
		t.Errorf("expected block length %d, not %d", len("destination"), files[0].Blocks[0].Size)
	}

	// echo -n "destination" | openssl dgst -sha256
	hash := "b5c755aaab1038b3d5627bbde7f47ca80c5f5c0481c6d33f04139d07aa1530e7"
	if fmt.Sprintf("%x", files[0].Blocks[0].Hash) != hash {
		t.Errorf("incorrect hash")
	}
}
func TestTraversesSymlink(t *testing.T) {
	if !symlinks.Supported {
		t.Skip("pointless test")
		return
	}

	os.RemoveAll("testdata")
	defer os.RemoveAll("testdata")
	os.MkdirAll("testdata/a/b/c", 0755)
	symlinks.Create("testdata/a/l", "b", symlinks.TargetDirectory)

	// a/l -> b, so a/l/c should resolve by normal stat
	info, err := osutil.Lstat("testdata/a/l/c")
	if err != nil {
		t.Fatal("unexpected error", err)
	}
	if !info.IsDir() {
		t.Fatal("error in setup, a/l/c should be a directory")
	}

	cases := []struct {
		name      string
		traverses bool
	}{
		// Exist
		{".", false},
		{"a", false},
		{"a/b", false},
		{"a/b/c", false},
		// Don't exist
		{"x", false},
		{"a/x", false},
		{"a/b/x", false},
		{"a/x/c", false},
		// Symlink or behind symlink
		{"a/l", true},
		{"a/l/c", true},
		// Non-existing behind a symlink
		{"a/l/x", true},
	}

	for _, tc := range cases {
		if res := osutil.TraversesSymlink("testdata", tc.name); tc.traverses == (res == nil) {
			t.Errorf("TraversesSymlink(%q) = %v, should be %v", tc.name, res, tc.traverses)
		}
	}
}
Beispiel #3
0
func TestWalkSymlink(t *testing.T) {
	if !symlinks.Supported {
		t.Skip("skipping unsupported symlink test")
		return
	}

	// Create a folder with a symlink in it

	os.RemoveAll("_symlinks")
	defer os.RemoveAll("_symlinks")

	os.Mkdir("_symlinks", 0755)
	symlinks.Create("_symlinks/link", "destination", symlinks.TargetUnknown)

	// Scan it

	fchan, err := Walk(Config{
		Dir:       "_symlinks",
		BlockSize: 128 * 1024,
	})

	if err != nil {
		t.Fatal(err)
	}

	var files []protocol.FileInfo
	for f := range fchan {
		files = append(files, f)
	}

	// Verify that we got one symlink and with the correct attributes

	if len(files) != 1 {
		t.Errorf("expected 1 symlink, not %d", len(files))
	}
	if len(files[0].Blocks) != 0 {
		t.Errorf("expected zero blocks for symlink, not %d", len(files[0].Blocks))
	}
	if files[0].SymlinkTarget != "destination" {
		t.Errorf("expected symlink to have target destination, not %q", files[0].SymlinkTarget)
	}
}
Beispiel #4
0
// handleSymlink creates or updates the given symlink
func (f *rwFolder) handleSymlink(file protocol.FileInfo) {
	// Used in the defer closure below, updated by the function body. Take
	// care not declare another err.
	var err error

	events.Default.Log(events.ItemStarted, map[string]string{
		"folder": f.folderID,
		"item":   file.Name,
		"type":   "symlink",
		"action": "update",
	})

	defer func() {
		events.Default.Log(events.ItemFinished, map[string]interface{}{
			"folder": f.folderID,
			"item":   file.Name,
			"error":  events.Error(err),
			"type":   "symlink",
			"action": "update",
		})
	}()

	realName, err := rootedJoinedPath(f.dir, file.Name)
	if err != nil {
		f.newError(file.Name, err)
		return
	}

	if shouldDebug() {
		curFile, _ := f.model.CurrentFolderFile(f.folderID, file.Name)
		l.Debugf("need symlink\n\t%v\n\t%v", file, curFile)
	}

	if len(file.SymlinkTarget) == 0 {
		// Index entry from a Syncthing predating the support for including
		// the link target in the index entry. We log this as an error.
		err = errors.New("incompatible symlink entry; rescan with newer Syncthing on source")
		l.Infof("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
		f.newError(file.Name, err)
		return
	}

	if _, err = f.mtimeFS.Lstat(realName); err == nil {
		// There is already something under that name. Remove it to replace
		// with the symlink. This also handles the "change symlink type"
		// path.
		err = osutil.InWritableDir(os.Remove, realName)
		if err != nil {
			l.Infof("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
			f.newError(file.Name, err)
			return
		}
	}

	tt := symlinks.TargetFile
	if file.IsDirectory() {
		tt = symlinks.TargetDirectory
	}

	// We declare a function that acts on only the path name, so
	// we can pass it to InWritableDir.
	createLink := func(path string) error {
		return symlinks.Create(path, file.SymlinkTarget, tt)
	}

	if err = osutil.InWritableDir(createLink, realName); err == nil {
		f.dbUpdates <- dbUpdateJob{file, dbUpdateHandleSymlink}
	} else {
		l.Infof("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
		f.newError(file.Name, err)
	}
}
Beispiel #5
0
func (p *rwFolder) performFinish(state *sharedPullerState) error {
	// Set the correct permission bits on the new file
	if !p.ignorePermissions(state.file) {
		if err := os.Chmod(state.tempName, os.FileMode(state.file.Flags&0777)); err != nil {
			return err
		}
	}

	// Set the correct timestamp on the new file
	t := time.Unix(state.file.Modified, 0)
	if err := os.Chtimes(state.tempName, t, t); err != nil {
		// Try using virtual mtimes instead
		info, err := os.Stat(state.tempName)
		if err != nil {
			return err
		}
		p.virtualMtimeRepo.UpdateMtime(state.file.Name, info.ModTime(), t)
	}

	if stat, err := osutil.Lstat(state.realName); err == nil {
		// There is an old file or directory already in place. We need to
		// handle that.

		switch {
		case stat.IsDir() || stat.Mode()&os.ModeSymlink != 0:
			// It's a directory or a symlink. These are not versioned or
			// archived for conflicts, only removed (which of course fails for
			// non-empty directories).

			// TODO: This is the place where we want to remove temporary files
			// and future hard ignores before attempting a directory delete.
			// Should share code with p.deletDir().

			if err = osutil.InWritableDir(osutil.Remove, state.realName); err != nil {
				return err
			}

		case p.inConflict(state.version, state.file.Version):
			// The new file has been changed in conflict with the existing one. We
			// should file it away as a conflict instead of just removing or
			// archiving. Also merge with the version vector we had, to indicate
			// we have resolved the conflict.

			state.file.Version = state.file.Version.Merge(state.version)
			if err = osutil.InWritableDir(moveForConflict, state.realName); err != nil {
				return err
			}

		case p.versioner != nil:
			// If we should use versioning, let the versioner archive the old
			// file before we replace it. Archiving a non-existent file is not
			// an error.

			if err = p.versioner.Archive(state.realName); err != nil {
				return err
			}
		}
	}

	// Replace the original content with the new one
	if err := osutil.Rename(state.tempName, state.realName); err != nil {
		return err
	}

	// If it's a symlink, the target of the symlink is inside the file.
	if state.file.IsSymlink() {
		content, err := ioutil.ReadFile(state.realName)
		if err != nil {
			return err
		}

		// Remove the file, and replace it with a symlink.
		err = osutil.InWritableDir(func(path string) error {
			os.Remove(path)
			return symlinks.Create(path, string(content), state.file.Flags)
		}, state.realName)
		if err != nil {
			return err
		}
	}

	// Record the updated file in the index
	p.dbUpdates <- dbUpdateJob{state.file, dbUpdateHandleFile}
	return nil
}
Beispiel #6
0
func TestIgnores(t *testing.T) {
	// Clean and start a syncthing instance

	log.Println("Cleaning...")
	err := removeAll("s1", "h1/index*")
	if err != nil {
		t.Fatal(err)
	}

	p := startInstance(t, 1)
	defer checkedStop(t, p)

	// Create eight empty files and directories

	dirs := []string{"d1", "d2", "d3", "d4", "d11", "d12", "d13", "d14"}
	files := []string{"f1", "f2", "f3", "f4", "f11", "f12", "f13", "f14", "d1/f1.TXT"}
	all := append(files, dirs...)

	for _, dir := range dirs {
		err := os.Mkdir(filepath.Join("s1", dir), 0755)
		if err != nil {
			t.Fatal(err)
		}
	}

	for _, file := range files {
		fd, err := os.Create(filepath.Join("s1", file))
		if err != nil {
			t.Fatal(err)
		}
		fd.Close()
	}

	var syms []string
	if symlinksSupported() {
		syms = []string{"s1", "s2", "s3", "s4", "s11", "s12", "s13", "s14"}
		for _, sym := range syms {
			p := filepath.Join("s1", sym)
			symlinks.Create(p, p, 0)
		}
		all = append(all, syms...)
	}

	// Rescan and verify that we see them all

	if err := p.Rescan("default"); err != nil {
		t.Fatal(err)
	}

	m, err := p.Model("default")
	if err != nil {
		t.Fatal(err)
	}
	expected := len(all) // nothing is ignored
	if m.LocalFiles != expected {
		t.Fatalf("Incorrect number of files after initial scan, %d != %d", m.LocalFiles, expected)
	}

	// Add some of them to an ignore file

	fd, err := os.Create("s1/.stignore")
	if err != nil {
		t.Fatal(err)
	}
	_, err = fd.WriteString("f1*\nf2\nd1*\nd2\ns1*\ns2\n(?i)*.txt") // [fds][34] only non-ignored items
	if err != nil {
		t.Fatal(err)
	}
	err = fd.Close()
	if err != nil {
		t.Fatal(err)
	}

	// Rescan and verify that we see them

	if err := p.Rescan("default"); err != nil {
		t.Fatal(err)
	}

	m, err = p.Model("default")
	if err != nil {
		t.Fatal(err)
	}
	expected = len(all) * 2 / 8 // two out of eight items of each type should remain
	if m.LocalFiles != expected {
		t.Fatalf("Incorrect number of files after first ignore, %d != %d", m.LocalFiles, expected)
	}

	// Change the pattern to include some of the files and dirs previously ignored

	fd, err = os.Create("s1/.stignore")
	if err != nil {
		t.Fatal(err)
	}
	_, err = fd.WriteString("f2\nd2\ns2\n")
	if err != nil {
		t.Fatal(err)
	}
	err = fd.Close()
	if err != nil {
		t.Fatal(err)
	}

	// Rescan and verify that we see them

	if err := p.Rescan("default"); err != nil {
		t.Fatal(err)
	}

	m, err = p.Model("default")
	if err != nil {
		t.Fatal(err)
	}
	expected = len(all)*7/8 + 1 // seven out of eight items of each type should remain, plus the foo.TXT
	if m.LocalFiles != expected {
		t.Fatalf("Incorrect number of files after second ignore, %d != %d", m.LocalFiles, expected)
	}
}
Beispiel #7
0
func testSymlinks(t *testing.T) {
	log.Println("Cleaning...")
	err := removeAll("s1", "s2", "h1/index*", "h2/index*")
	if err != nil {
		t.Fatal(err)
	}

	log.Println("Generating files...")
	err = generateFiles("s1", 100, 20, "../LICENSE")
	if err != nil {
		t.Fatal(err)
	}

	// A file that we will replace with a symlink later

	fd, err := os.Create("s1/fileToReplace")
	if err != nil {
		t.Fatal(err)
	}
	fd.Close()

	// A directory that we will replace with a symlink later

	err = os.Mkdir("s1/dirToReplace", 0755)
	if err != nil {
		t.Fatal(err)
	}

	// A file and a symlink to that file

	fd, err = os.Create("s1/file")
	if err != nil {
		t.Fatal(err)
	}
	fd.Close()
	err = symlinks.Create("s1/fileLink", "file", 0)
	if err != nil {
		log.Fatal(err)
	}

	// A directory and a symlink to that directory

	err = os.Mkdir("s1/dir", 0755)
	if err != nil {
		t.Fatal(err)
	}
	err = symlinks.Create("s1/dirLink", "dir", 0)
	if err != nil {
		log.Fatal(err)
	}

	// A link to something in the repo that does not exist

	err = symlinks.Create("s1/noneLink", "does/not/exist", 0)
	if err != nil {
		log.Fatal(err)
	}

	// A link we will replace with a file later

	err = symlinks.Create("s1/repFileLink", "does/not/exist", 0)
	if err != nil {
		log.Fatal(err)
	}

	// A link we will replace with a directory later

	err = symlinks.Create("s1/repDirLink", "does/not/exist", 0)
	if err != nil {
		log.Fatal(err)
	}

	// A link we will remove later

	err = symlinks.Create("s1/removeLink", "does/not/exist", 0)
	if err != nil {
		log.Fatal(err)
	}

	// Verify that the files and symlinks sync to the other side

	sender := startInstance(t, 1)
	defer checkedStop(t, sender)

	receiver := startInstance(t, 2)
	defer checkedStop(t, receiver)

	log.Println("Syncing...")
	rc.AwaitSync("default", sender, receiver)

	log.Println("Comparing directories...")
	err = compareDirectories("s1", "s2")
	if err != nil {
		t.Fatal(err)
	}

	log.Println("Making some changes...")

	// Remove one symlink

	err = os.Remove("s1/fileLink")
	if err != nil {
		log.Fatal(err)
	}

	// Change the target of another

	err = os.Remove("s1/dirLink")
	if err != nil {
		log.Fatal(err)
	}
	err = symlinks.Create("s1/dirLink", "file", 0)
	if err != nil {
		log.Fatal(err)
	}

	// Replace one with a file

	err = os.Remove("s1/repFileLink")
	if err != nil {
		log.Fatal(err)
	}

	fd, err = os.Create("s1/repFileLink")
	if err != nil {
		log.Fatal(err)
	}
	fd.Close()

	// Replace one with a directory

	err = os.Remove("s1/repDirLink")
	if err != nil {
		log.Fatal(err)
	}

	err = os.Mkdir("s1/repDirLink", 0755)
	if err != nil {
		log.Fatal(err)
	}

	// Replace a file with a symlink

	err = os.Remove("s1/fileToReplace")
	if err != nil {
		log.Fatal(err)
	}
	err = symlinks.Create("s1/fileToReplace", "somewhere/non/existent", 0)
	if err != nil {
		log.Fatal(err)
	}

	// Replace a directory with a symlink

	err = os.RemoveAll("s1/dirToReplace")
	if err != nil {
		log.Fatal(err)
	}
	err = symlinks.Create("s1/dirToReplace", "somewhere/non/existent", 0)
	if err != nil {
		log.Fatal(err)
	}

	// Remove a broken symlink

	err = os.Remove("s1/removeLink")
	if err != nil {
		log.Fatal(err)
	}

	// Sync these changes and recheck

	log.Println("Syncing...")

	if err := sender.Rescan("default"); err != nil {
		t.Fatal(err)
	}

	rc.AwaitSync("default", sender, receiver)

	log.Println("Comparing directories...")
	err = compareDirectories("s1", "s2")
	if err != nil {
		t.Fatal(err)
	}
}
Beispiel #8
0
func (p *rwFolder) performFinish(state *sharedPullerState) error {
	// Set the correct permission bits on the new file
	if !p.ignorePermissions(state.file) {
		if err := os.Chmod(state.tempName, os.FileMode(state.file.Flags&0777)); err != nil {
			return err
		}
	}

	// Set the correct timestamp on the new file
	t := time.Unix(state.file.Modified, 0)
	if err := os.Chtimes(state.tempName, t, t); err != nil {
		// Try using virtual mtimes instead
		info, err := os.Stat(state.tempName)
		if err != nil {
			return err
		}
		p.virtualMtimeRepo.UpdateMtime(state.file.Name, info.ModTime(), t)
	}

	var err error
	if p.inConflict(state.version, state.file.Version) {
		// The new file has been changed in conflict with the existing one. We
		// should file it away as a conflict instead of just removing or
		// archiving. Also merge with the version vector we had, to indicate
		// we have resolved the conflict.
		state.file.Version = state.file.Version.Merge(state.version)
		err = osutil.InWritableDir(moveForConflict, state.realName)
	} else if p.versioner != nil {
		// If we should use versioning, let the versioner archive the old
		// file before we replace it. Archiving a non-existent file is not
		// an error.
		err = p.versioner.Archive(state.realName)
	} else {
		err = nil
	}
	if err != nil {
		return err
	}

	// If the target path is a symlink or a directory, we cannot copy
	// over it, hence remove it before proceeding.
	stat, err := osutil.Lstat(state.realName)
	if err == nil && (stat.IsDir() || stat.Mode()&os.ModeSymlink != 0) {
		osutil.InWritableDir(osutil.Remove, state.realName)
	}
	// Replace the original content with the new one
	if err = osutil.Rename(state.tempName, state.realName); err != nil {
		return err
	}

	// If it's a symlink, the target of the symlink is inside the file.
	if state.file.IsSymlink() {
		content, err := ioutil.ReadFile(state.realName)
		if err != nil {
			return err
		}

		// Remove the file, and replace it with a symlink.
		err = osutil.InWritableDir(func(path string) error {
			os.Remove(path)
			return symlinks.Create(path, string(content), state.file.Flags)
		}, state.realName)
		if err != nil {
			return err
		}
	}

	// Record the updated file in the index
	p.dbUpdates <- dbUpdateJob{state.file, dbUpdateHandleFile}
	return nil
}