Repository: syncthing/syncthing (language: go)

Issue:
Junctions as dirs doesn't work anymore
### What happened?

I used to be able to sync the contents of NTFS junction directories. I recently noticed that it doesn't seem to work anymore.

I tried on 2 different machines, one with admin/systemwide install, the other with user install. On the systemwide install I added SyncthingServiceAcct to the perms on the sync'ed folder. The _Junctions as dirs_ option is set. 

Relevant logs below.

Here are some screenshots:
![syncthing_main](https://github.com/user-attachments/assets/9f85bbd5-fcfe-4494-a186-c336cecf9eda)
![files](https://github.com/user-attachments/assets/b4e08b5f-5321-4fba-95df-5c453097abb1)
![options](https://github.com/user-attachments/assets/71523094-7113-4494-bc43-eae9b4ab9331)
![perms](https://github.com/user-attachments/assets/94928024-32a6-4fb2-a863-ff559e66db9a)



### Syncthing version

v1.28.0

### Platform & operating system

Windows 10 Pro x64 

### Browser version

Brave Browser 1.71.114

### Relevant log output

2024-10-18 22:21:00 casefs.go:255 basic c:\aaa\junction Stat . {0xc00013c070} <nil>
2024-10-18 22:21:00 casefs.go:255 basic c:\aaa\junction Stat .stfolder {0xc00013c230} <nil>
2024-10-18 22:21:00 casefs.go:479 basic c:\aaa\junction DirNames . [.stfolder file junctiontotarget] <nil>
2024-10-18 22:21:00 logfs.go:173 basic C:\ProgramData\Syncthing\index-v0.14.0.db Usage . {702322937856 999533756416} <nil>
2024-10-18 22:21:00 log 40 StateChanged map[duration:182.8508201 folder:dayss-x9hqe from:idle to:scan-waiting]
2024-10-18 22:21:00 walker/dayss-x9hqe@0xc0000e76b0 Walk [] Matcher/[]@0xc0002422d0
2024-10-18 22:21:00 log 41 StateChanged map[duration:0.8508201 folder:dayss-x9hqe from:scan-waiting to:scanning]
2024-10-18 22:21:00 walk: path=.
2024-10-18 22:21:00 ancestorDirList: Contains 'junction'
2024-10-18 22:21:00 ancestorDirList: Push 'junction'
2024-10-18 22:21:00 walk: path=.stfolder
2024-10-18 22:21:00 walker/dayss-x9hqe@0xc0000e76b0 ignored (internal): .stfolder
2024-10-18 22:21:00 walk: path=file
2024-10-18 22:21:00 walker/dayss-x9hqe@0xc0000e76b0 checking: File{Name:"file", Sequence:0, Permissions:0644, ModTime:2024-10-18 21:11:45.2540096 +0200 CEST, Version:{[{62S6MDD 1729282860}]}, VersionHash:, Length:7, Deleted:false, Invalid:false, LocalFlags:0x0, NoPermissions:false, BlockSize:131072, NumBlocks:0, BlocksHash:, Platform:{<nil> <nil> <nil> <nil> <nil> <nil>}, InodeChangeTime:1970-01-01 01:00:00 +0100 CET}
2024-10-18 22:21:00 walker/dayss-x9hqe@0xc0000e76b0 unchanged: File{Name:"file", Sequence:1, Permissions:0644, ModTime:2024-10-18 21:11:45.2540096 +0200 CEST, Version:{[{62S6MDD 1729282860}]}, VersionHash:, Length:7, Deleted:false, Invalid:false, LocalFlags:0x0, NoPermissions:false, BlockSize:131072, NumBlocks:1, BlocksHash:d3535c46046caa8f282f78819d9f8616010eacd1d507e952c833a67ada965799, Platform:{<nil> <nil> <nil> <nil> <nil> <nil>}, InodeChangeTime:1970-01-01 01:00:00 +0100 CET}
2024-10-18 22:21:00 walk: path=junctiontotarget
2024-10-18 22:21:00 ancestorDirList: Pop 'junction'
2024-10-18 22:21:00 log 42 StateChanged map[duration:0.8525193 folder:dayss-x9hqe from:scanning to:idle]



Discussion / hints:
I can confirm that something seems to have broken regarding the setting. In my test environment using just the basic Syncthing binary (so no permission issues that could be caused by the Windows installer, etc.), it doesn't seem to be able to detect the junction content at all.

I'm going to try investigating which specific version and possibly commit (or maybe a Go upgrade) is responsible for the regression.
8dc826b23 / #9651 is probably a good starting point
I can confirm that after compiling Syncthing v1.28.0 with Go v1.22, junctions work again, so the culprit is in Go v1.23.
> I can confirm that after compiling Syncthing v1.28.0 with Go v1.22, junctions work again, so the culprit is in Go v1.23.

@tomasz1986 The unit tests succeed, so it appears the test may be flawed too. I can look into fixing this, but don't want to duplicate effort if someone has already taken this on.
My capabilities are very limited, so please go on if you know how to approach the problem 🙂.
The unit tests still succeed with go 1.23, so they will need to be fixed too. I'm testing on Windows 11, so this might be a Windows 10 only bug. I can spin up the same Windows 10 VM I used when I tested
https://github.com/syncthing/syncthing/pull/9651/commits/c29112d2e1b4066d47214ef0917112d8a78cccbe

@hardtopnet Can you run
```
ver
```
on the command line, and tell us what it outputs?
Certainly.
All three PC's are running `Microsoft Windows [version 10.0.19045.5011]` 
Cheers
Here's what we're seeing on junctions:
```
go 1.23:
stat: Mode()=drwxr-xr-x (0o020000000755)
lstat:Mode()=?rw-r--r-- (    0o02000644)
go 1.22.6:
stat: Mode()=drwxr-xr-x (0o020000000755)
lstat:Mode()=Lrw-r--r-- ( 0o01000000644)
```
Gotta drill into this a bit more....
I can confirm NTFS junction are also broken under Windows 11 24H2 as well.
I juste submit [another bug](https://github.com/syncthing/syncthing/issues/9784#issue-2612685182) where my folders doesn't synchronise properly since the v1.28.0.
I don't use junction but I think it can be related to GO 1.23 update too.
I also seem to have this issue. It's not working on Windows 10 and Windows 11 for me.

Fix patch:
diff --git a/lib/fs/basicfs_lstat_windows.go b/lib/fs/basicfs_lstat_windows.go
index de2f6328074..1868880d103 100644
--- a/lib/fs/basicfs_lstat_windows.go
+++ b/lib/fs/basicfs_lstat_windows.go
@@ -67,7 +67,7 @@ type dirJunctFileInfo struct {
 func (fi *dirJunctFileInfo) Mode() os.FileMode {
 	// Simulate a directory and not a symlink; also set the execute
 	// bits so the directory can be traversed Unix-side.
-	return fi.FileInfo.Mode() ^ junctionPointModeMask | os.ModeDir | 0111
+	return fi.FileInfo.Mode()&^junctionPointModeMask | os.ModeDir | 0111
 }
 
 func (fi *dirJunctFileInfo) IsDir() bool {
@@ -77,10 +77,13 @@ func (fi *dirJunctFileInfo) IsDir() bool {
 var junctionPointModeMask os.FileMode
 
 func init() {
+	// Per https://tip.golang.org/doc/go1.23#minor_library_changes
+	// In go1.22 (and go1.23 when GODEBUG=winsymlink=0) Windows' directory
+	// junctions (aka "mount points") always have ModeSymlink set.
 	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.
+		// In go1.23 Windows' directory junctions always have ModeIrregular set
+		// (unless GODEBUG=winsymlink=0).
 		junctionPointModeMask |= os.ModeIrregular
 	}
 }


Test patch: