mirror of
https://github.com/jkl1337/duplicacy.git
synced 2026-01-02 19:54:54 -06:00
Basic support for BSD and Darwin style chflags (stat flags). Applies these flags at the end of file restore. Supports linux style ioctl_iflags(2) in a 2 step process. Flags that need to be applied prior to writes such as compress and especially no-COW are applied immediately upon file open. The flags format is backwards compatible. An attribute starting with a null byte is used to store flags in the entry attributes table. With an old version of duplicacy the restore of this attribute should silently fail (effectively be ignored). Fixes xattr restore to use O_NOFOLLOW so attributes are applied to symlink. TODO: Tests, possible option to switch off mutable/append prior to restore of existing file similar to rsync. Does not apply attributes or flags to the top most directory.
150 lines
4.2 KiB
Go
150 lines
4.2 KiB
Go
// Copyright (c) Acrosync LLC. All rights reserved.
|
|
// Free for personal use and commercial trial
|
|
// Commercial use requires per-user licenses available from https://duplicacy.com
|
|
|
|
package duplicacy
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
type symbolicLinkReparseBuffer struct {
|
|
SubstituteNameOffset uint16
|
|
SubstituteNameLength uint16
|
|
PrintNameOffset uint16
|
|
PrintNameLength uint16
|
|
Flags uint32
|
|
PathBuffer [1]uint16
|
|
}
|
|
|
|
type mountPointReparseBuffer struct {
|
|
SubstituteNameOffset uint16
|
|
SubstituteNameLength uint16
|
|
PrintNameOffset uint16
|
|
PrintNameLength uint16
|
|
PathBuffer [1]uint16
|
|
}
|
|
|
|
type reparseDataBuffer struct {
|
|
ReparseTag uint32
|
|
ReparseDataLength uint16
|
|
Reserved uint16
|
|
|
|
// GenericReparseBuffer
|
|
reparseBuffer byte
|
|
}
|
|
|
|
const (
|
|
FSCTL_GET_REPARSE_POINT = 0x900A8
|
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024
|
|
IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
|
|
IO_REPARSE_TAG_SYMLINK = 0xA000000C
|
|
IO_REPARSE_TAG_DEDUP = 0x80000013
|
|
SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1
|
|
|
|
FILE_READ_ATTRIBUTES = 0x0080
|
|
)
|
|
|
|
// We copied golang source code for Readlink but made a simple modification here: use FILE_READ_ATTRIBUTES instead of
|
|
// GENERIC_READ to read the symlink, because the latter would cause a Access Denied error on links such as
|
|
// C:\Documents and Settings
|
|
|
|
// Readlink returns the destination of the named symbolic link.
|
|
func Readlink(path string) (isRegular bool, s string, err error) {
|
|
fd, err := syscall.CreateFile(syscall.StringToUTF16Ptr(path), FILE_READ_ATTRIBUTES,
|
|
syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING,
|
|
syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
|
if err != nil {
|
|
return false, "", err
|
|
}
|
|
defer syscall.CloseHandle(fd)
|
|
|
|
rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
|
|
var bytesReturned uint32
|
|
err = syscall.DeviceIoControl(fd, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0],
|
|
uint32(len(rdbbuf)), &bytesReturned, nil)
|
|
if err != nil {
|
|
return false, "", err
|
|
}
|
|
|
|
rdb := (*reparseDataBuffer)(unsafe.Pointer(&rdbbuf[0]))
|
|
switch rdb.ReparseTag {
|
|
case IO_REPARSE_TAG_SYMLINK:
|
|
data := (*symbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer))
|
|
p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0]))
|
|
if data.PrintNameLength > 0 {
|
|
s = syscall.UTF16ToString(p[data.PrintNameOffset/2 : (data.PrintNameLength+data.PrintNameOffset)/2])
|
|
} else {
|
|
s = syscall.UTF16ToString(p[data.SubstituteNameOffset/2 : (data.SubstituteNameLength+data.SubstituteNameOffset)/2])
|
|
}
|
|
case IO_REPARSE_TAG_MOUNT_POINT:
|
|
data := (*mountPointReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer))
|
|
p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0]))
|
|
if data.PrintNameLength > 0 {
|
|
s = syscall.UTF16ToString(p[data.PrintNameOffset/2 : (data.PrintNameLength+data.PrintNameOffset)/2])
|
|
} else {
|
|
s = syscall.UTF16ToString(p[data.SubstituteNameOffset/2 : (data.SubstituteNameLength+data.SubstituteNameOffset)/2])
|
|
}
|
|
case IO_REPARSE_TAG_DEDUP:
|
|
return true, "", nil
|
|
default:
|
|
// the path is not a symlink or junction but another type of reparse
|
|
// point
|
|
return false, "", fmt.Errorf("Unhandled reparse point type %x", rdb.ReparseTag)
|
|
}
|
|
|
|
return false, s, nil
|
|
}
|
|
|
|
func GetOwner(entry *Entry, fileInfo *os.FileInfo) {
|
|
entry.UID = -1
|
|
entry.GID = -1
|
|
}
|
|
|
|
func SetOwner(fullPath string, entry *Entry, fileInfo *os.FileInfo) bool {
|
|
return true
|
|
}
|
|
|
|
func (entry *Entry) ReadAttributes(top string) {
|
|
}
|
|
|
|
func (entry *Entry) SetAttributesToFile(fullPath string) {
|
|
|
|
}
|
|
|
|
func joinPath(components ...string) string {
|
|
|
|
combinedPath := `\\?\` + filepath.Join(components...)
|
|
// If the path is on a samba drive we must use the UNC format
|
|
if strings.HasPrefix(combinedPath, `\\?\\\`) {
|
|
combinedPath = `\\?\UNC\` + combinedPath[6:]
|
|
}
|
|
return combinedPath
|
|
}
|
|
|
|
func SplitDir(fullPath string) (dir string, file string) {
|
|
i := strings.LastIndex(fullPath, "\\")
|
|
return fullPath[:i+1], fullPath[i+1:]
|
|
}
|
|
|
|
func (entry *Entry) ReadFileFlags(f *os.File) error {
|
|
return nil
|
|
}
|
|
|
|
func (entry *Entry) RestoreEarlyDirFlags(path string) error {
|
|
return nil
|
|
}
|
|
|
|
func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error {
|
|
return nil
|
|
}
|
|
|
|
func (entry *Entry) RestoreLateFileFlags(f *os.File) error {
|
|
return nil
|
|
}
|