mirror of
https://github.com/syncthing/syncthing.git
synced 2024-11-16 02:18:44 -07:00
9553365d31
### Purpose Deduplicated files are apparently considered 'irregular' under the hood, this causes them to simply be ignored by Syncthing. This change is more of a workaround than a proper fix, as the fix should probably happen in the underlying libraries? - which may take some time. In the meanwhile, this change should make deduplicated files be treated as regular files and be indexed and synced as they should. ### Testing Create some volume where deduplication is turned on (see the relevant issue for details, including a proper description of how to reproduce it). Prior to this change, the deduplicated files were simply ignored (even by the indexer). After this change, the deduplicated files are being index and synced properly.
116 lines
3.5 KiB
Go
116 lines
3.5 KiB
Go
// Copyright (C) 2015 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/.
|
|
|
|
//go:build windows
|
|
// +build windows
|
|
|
|
package fs
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
const IO_REPARSE_TAG_DEDUP = 0x80000013
|
|
|
|
func readReparseTag(path string) (uint32, error) {
|
|
namep, err := syscall.UTF16PtrFromString(path)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("syscall.UTF16PtrFromString failed with: %s", err)
|
|
}
|
|
attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
|
|
h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("syscall.CreateFile failed with: %s", err)
|
|
}
|
|
defer syscall.CloseHandle(h)
|
|
|
|
//https://docs.microsoft.com/windows/win32/api/winbase/ns-winbase-file_attribute_tag_info
|
|
const fileAttributeTagInfo = 9
|
|
type FILE_ATTRIBUTE_TAG_INFO struct {
|
|
FileAttributes uint32
|
|
ReparseTag uint32
|
|
}
|
|
|
|
var ti FILE_ATTRIBUTE_TAG_INFO
|
|
err = windows.GetFileInformationByHandleEx(windows.Handle(h), fileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti)))
|
|
if err != nil {
|
|
if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER {
|
|
// It appears calling GetFileInformationByHandleEx with
|
|
// FILE_ATTRIBUTE_TAG_INFO fails on FAT file system with
|
|
// ERROR_INVALID_PARAMETER. Clear ti.ReparseTag in that
|
|
// instance to indicate no symlinks are possible.
|
|
ti.ReparseTag = 0
|
|
} else {
|
|
return 0, fmt.Errorf("windows.GetFileInformationByHandleEx failed with: %s", err)
|
|
}
|
|
}
|
|
|
|
return ti.ReparseTag, nil
|
|
}
|
|
|
|
func isDirectoryJunction(reparseTag uint32) bool {
|
|
return reparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT
|
|
}
|
|
|
|
func isDeduplicatedFile(reparseTag uint32) bool {
|
|
return reparseTag == IO_REPARSE_TAG_DEDUP
|
|
}
|
|
|
|
type dirJunctFileInfo struct {
|
|
os.FileInfo
|
|
}
|
|
|
|
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() ^ os.ModeSymlink | os.ModeDir | 0111
|
|
}
|
|
|
|
func (fi *dirJunctFileInfo) IsDir() bool {
|
|
return true
|
|
}
|
|
|
|
type dedupFileInfo struct {
|
|
os.FileInfo
|
|
}
|
|
|
|
func (fi *dedupFileInfo) Mode() os.FileMode {
|
|
// A deduplicated file should be treated as a regular file and not an
|
|
// irregular file.
|
|
return fi.FileInfo.Mode() &^ os.ModeIrregular
|
|
}
|
|
|
|
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 {
|
|
// NTFS directory junctions can be treated as ordinary directories,
|
|
// see https://forum.syncthing.net/t/option-to-follow-directory-junctions-symbolic-links/14750
|
|
if fi.Mode()&os.ModeSymlink != 0 && f.junctionsAsDirs {
|
|
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
|
|
}
|