Skip to content

Conversation

calmh
Copy link
Member

@calmh calmh commented Aug 13, 2024

🥳

@calmh
Copy link
Member Author

calmh commented Aug 13, 2024

Windows reparse point handling has changed, we need to check it out:

=== RUN   TestWalkTraverseDirJunct
    walkfs_test.go:85: Directory junction was not traversed
--- FAIL: TestWalkTraverseDirJunct (0.03s)
=== RUN   TestWalkInfiniteRecursion
    walkfs_test.go:132: Infinite recursion not detected correctly
--- FAIL: TestWalkInfiniteRecursion (0.04s)

Possibly, as simple as just removing our reparse handling entirely for 1.23+, my reading is that mount points are now just followed and don't look like symlinks any more.

On Windows, the mode bits reported by Lstat and Stat for reparse points changed. Mount points no longer have ModeSymlink set, and reparse points that are not symlinks, Unix sockets, or dedup files now always have ModeIrregular set.

@rasa
Copy link
Member

rasa commented Aug 15, 2024

@calmh On a Windows 11 Pro amd64 box (10.0.22631.4037), the tests pass:

>go version
go version go1.23.0 windows/amd64
>go test -v -run TestWalk
=== RUN   TestWalkTraverseDirJunct
--- PASS: TestWalkTraverseDirJunct (0.02s)
=== RUN   TestWalkInfiniteRecursion
--- PASS: TestWalkInfiniteRecursion (0.03s)
PASS
ok      github.com/syncthing/syncthing/lib/fs   0.820s

So perhaps these issues are specific to Windows Server 2022 10.0.20348?

@rasa
Copy link
Member

rasa commented Aug 17, 2024

I spun up a Windows 10 Pro amd64 VM (10.0.19045.2965), and the errors appeared. I'll try a Windows 11 VM now...

@rasa
Copy link
Member

rasa commented Aug 17, 2024

The tests pass on a Windows 11 VM Pro amd64 23H2 10.0.22631.2861.

@calmh
Copy link
Member Author

calmh commented Aug 17, 2024

Well that's worrying.

Fixes test failures:
=== RUN   TestWalkTraverseDirJunct
    walkfs_test.go:85: Directory junction was not traversed
--- FAIL: TestWalkTraverseDirJunct (0.03s)
=== RUN   TestWalkInfiniteRecursion
    walkfs_test.go:132: Infinite recursion not detected correctly
--- FAIL: TestWalkInfiniteRecursion (0.04s)
raised at
syncthing#9651 (comment)
See syncthing#9651 for details.
@rasa
Copy link
Member

rasa commented Aug 17, 2024

I committed a change that fixed the test failures on my Windows 10 VM. I haven't tested the change otherwise.

@calmh
Copy link
Member Author

calmh commented Aug 17, 2024

I don't like that fix. For one, if the symlink bit isn't set (which is indeed a documented change in Go 1.23), we shouldn't need to do the reparse handling, we should already traverse the directory as is. Additionally, this would mean we try to parse reparse points on every Lstat which would likely be a significant performance regression.

@bt90
Copy link
Contributor

bt90 commented Aug 17, 2024

@rasa can you break it down to a minimal reproducer? IMHO this is a go bug.

@rasa rasa self-assigned this Aug 17, 2024
@rasa
Copy link
Member

rasa commented Aug 17, 2024

@calmh Yes, I will replace that change with a proper one.

It appears we can replace all or most of our readReparseTag, with using Stat().ReparseTag instead, which was introduced in go1.11, as Reserved0, then renamed to ReparseTag in go1.21rc1, but not mentioned in the release notes.

I've assigned this to myself, and will test the code on both my VMs, as well as GitHub's windows-2022 and windows-2019 instances.

@bt90 I guess your right that it's a go bug. Go should return consistent results regardless of what version of Windows its run on.

@rasa
Copy link
Member

rasa commented Aug 17, 2024

A solution is forthcoming. Here's a preview:

click here func (f *BasicFilesystem) underlyingLstat(name string) (os.FileInfo, error) { var fi, err = os.Lstat(name)
// There are cases where files are tagged as symlink, but they end up being
// something else. Make sure we properly handle those types.
if err == nil {
	if f.junctionsAsDirs {
		var checkIfJunction bool
		// godebug("winsymlink") will return >0 if GODEBUG=winsymlink=0 is set
		if compare.Compare(runtime.Version(), "go1.23") >= 0 && godebug("winsymlink") == 0 {
			checkIfJunction = fi.Mode()&os.ModeIrregular != 0
		} else {
			checkIfJunction = fi.Mode()&os.ModeSymlink != 0
		}

		// NTFS directory junctions can be treated as ordinary directories,
		// see https://forum.syncthing.net/t/option-to-follow-directory-junctions-symbolic-links/14750
		if checkIfJunction {
			if reparseTag, reparseErr := readReparseTag(name); reparseErr == nil && isDirectoryJunction(reparseTag) {
				return &dirJunctFileInfo{fi}, nil
			}
		}
	}

	// Workaround for #9120 till golang properly handles deduplicated files by
	// considering them regular files.
	if fi.Mode()&os.ModeIrregular != 0 {
		if reparseTag, reparseErr := readReparseTag(name); reparseErr == nil && isDeduplicatedFile(reparseTag) {
			return &dedupFileInfo{fi}, nil
		}
	}
}

return fi, err

}

func godebug(godebug string) uint64 {
myMetric := "/godebug/non-default-behavior/" + godebug + ":events"

// Create a sample for the metric.
sample := make([]metrics.Sample, 1)
sample[0].Name = myMetric

// Sample the metric.
metrics.Read(sample)

// Check if the metric is actually supported.
// If it's not, the resulting value will always have
// kind KindBad.
if sample[0].Value.Kind() == metrics.KindBad {
	return 0
}
// Handle the result.
// It's OK to assume a particular Kind for a metric;
// they're guaranteed not to change.
return sample[0].Value.Uint64()

}

rasa added a commit to rasa/syncthing that referenced this pull request Aug 18, 2024
rasa added a commit to rasa/syncthing that referenced this pull request Aug 18, 2024
@rasa
Copy link
Member

rasa commented Aug 18, 2024

The below code works in go1.21, go1.22, and go1.23, on windows 10, 11, windows-latest (windows-2022) and windoes-2019, with or without GODEBUG=winsymlink=0 set:

func (f *BasicFilesystem) underlyingLstat(name string) (os.FileInfo, error) {
	fi, err := os.Lstat(name)

	if err == nil && f.junctionsAsDirs {
		v := reflect.Indirect(reflect.ValueOf(fi))
		y := v.FieldByName("ReparseTag")
		if y.Interface() == uint32(windows.IO_REPARSE_TAG_MOUNT_POINT) {
			return &dirJunctFileInfo{fi}, nil
		}
	}

	return fi, err
}

Is it acceptable to use reflect to access non-public members of a FileInfo? It sure makes things faster and simpler.

@rasa
Copy link
Member

rasa commented Aug 18, 2024

Another solution that would work for at least two years would be to add

//go:debug winsymlink=0

to the existing file. This would force the pre-go1.23 functionality.

@calmh
Copy link
Member Author

calmh commented Aug 18, 2024

I would prefer we understand what's going on and do the minimal fix. We shouldn't need to change our reparse parsing stuff, only when to trigger it. In principle it seems to me this should be just adding a "if go1.22" to our existing symlink-to-reparse-point check and in go1.23+ do nothing at all as it should just look like a normal directory.

rasa added 2 commits August 18, 2024 07:55
This fixes the test failures:
=== RUN   TestWalkTraverseDirJunct
    walkfs_test.go:85: Directory junction was not traversed
--- FAIL: TestWalkTraverseDirJunct (0.03s)
=== RUN   TestWalkInfiniteRecursion
    walkfs_test.go:132: Infinite recursion not detected correctly
--- FAIL: TestWalkInfiniteRecursion (0.04s)
documented in syncthing#9651.

This also undoes syncthing#9168 by not checking if ModeIrregular files are dedups as
golang/go@f26fa05
removed the ModeIrregular bit from dedup files.
Comment on lines -105 to -111
// Workaround for #9120 till golang properly handles deduplicated files by
// considering them regular files.
if fi.Mode()&os.ModeIrregular != 0 {
if reparseTag, reparseErr := readReparseTag(name); reparseErr == nil && isDeduplicatedFile(reparseTag) {
return &dedupFileInfo{fi}, nil
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this fixed in 1.22 and earlier? I did a search but I don't find the fix for this documented

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was fixed in 1.21 in Nov 2023 in
golang/go@f26fa05

Comment on lines +80 to +85
junctionPointModeMask = os.ModeSymlink
if version.Compare(runtime.Version(), "go1.23") >= 0 {
// if GODEBUG=winsymlink=0, then we are in g1.22 mode so let's check for
// os.ModeSymlink as well, as it can't hurt.
junctionPointModeMask |= os.ModeIrregular
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@calmh calmh merged commit 8dc826b into syncthing:main Aug 19, 2024
21 checks passed
calmh added a commit to calmh/syncthing that referenced this pull request Aug 19, 2024
* main: (46 commits)
  build: use Go 1.23, require minimum 1.22 (syncthing#9651)
  gui, man, authors: Update docs, translations, and contributors
  lib/fs: Put the caseFS as the outermost layer (syncthing#9648)
  gui: Add Irish (ga) translation template (syncthing#9646)
  gui, man, authors: Update docs, translations, and contributors
  lib/syncthing: Add wrapper for access to model (syncthing#9627)
  cli: Remove `go-shlex` dependency (syncthing#9644)
  lib/sha256: Remove it (syncthing#9643)
  build: Update dependencies (syncthing#9640)
  Chmod -x non-executable files (fixes syncthing#9629) (syncthing#9630)
  gui, man, authors: Update docs, translations, and contributors
  all: minimal set of changes for iOS app (syncthing#9619)
  gui, man, authors: Update docs, translations, and contributors
  gui, man, authors: Update docs, translations, and contributors
  etc: Remove restart on suspend systemd service (ref syncthing#8448) (syncthing#9611)
  gui, man, authors: Update docs, translations, and contributors
  lib/fs: Add missing locks to fakeFile methods (fixes syncthing#9499) (syncthing#9603)
  lib/api: Increase test request timeout (fixes syncthing#9455) (syncthing#9602)
  gui, man, authors: Update docs, translations, and contributors
  lib/ignore: Remove unused patterns in cache (syncthing#9601)
  ...
calmh added a commit to calmh/syncthing that referenced this pull request Aug 20, 2024
…-smaller

* upstream/main:
  build: use Go 1.23, require minimum 1.22 (syncthing#9651)
  gui, man, authors: Update docs, translations, and contributors
@calmh calmh added this to the v1.27.13 milestone Sep 11, 2024
@calmh calmh deleted the jb/go123 branch February 12, 2025 09:51
@calmh calmh added the build Issues caused by or requiring changes to the build system (scripts or Docker image) label May 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
build Issues caused by or requiring changes to the build system (scripts or Docker image)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants