Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable support for preserving SMB info on Linux. #1723

Merged
merged 8 commits into from
Dec 22, 2022
12 changes: 7 additions & 5 deletions cmd/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -928,10 +928,11 @@ func validateForceIfReadOnly(toForce bool, fromTo common.FromTo) error {

func areBothLocationsSMBAware(fromTo common.FromTo) bool {
// preserverSMBInfo will be true by default for SMB-aware locations unless specified false.
// 1. Upload (Windows -> Azure File)
// 2. Download (Azure File -> Windows)
// 1. Upload (Windows/Linux -> Azure File)
// 2. Download (Azure File -> Windows/Linux)
// 3. S2S (Azure File -> Azure File)
if runtime.GOOS == "windows" && (fromTo == common.EFromTo.LocalFile() || fromTo == common.EFromTo.FileLocal()) {
if (runtime.GOOS == "windows" || runtime.GOOS == "linux") &&
(fromTo == common.EFromTo.LocalFile() || fromTo == common.EFromTo.FileLocal()) {
return true
} else if fromTo == common.EFromTo.FileFile() {
return true
Expand All @@ -956,8 +957,9 @@ func validatePreserveSMBPropertyOption(toPreserve bool, fromTo common.FromTo, ov
return fmt.Errorf("%s is set but the job is not between %s-aware resources", flagName, common.IffString(flagName == PreservePermissionsFlag, "permission", "SMB"))
}

if toPreserve && (fromTo.IsUpload() || fromTo.IsDownload()) && runtime.GOOS != "windows" {
return fmt.Errorf("%s is set but persistence for up/downloads is a Windows-only feature", flagName)
if toPreserve && (fromTo.IsUpload() || fromTo.IsDownload()) &&
runtime.GOOS != "windows" && runtime.GOOS != "linux" {
return fmt.Errorf("%s is set but persistence for up/downloads is supported only in Windows and Linux", flagName)
}

return nil
Expand Down
133 changes: 133 additions & 0 deletions common/writeThoughFile_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,143 @@
package common

import (
"encoding/binary"
"fmt"
"os"
"syscall"
"time"

"github.com/pkg/xattr"
"golang.org/x/sys/unix"
)

// Extended Attribute (xattr) keys for fetching various information from Linux cifs client.
const (
CIFS_XATTR_CREATETIME = "user.cifs.creationtime" // File creation time.
CIFS_XATTR_ATTRIB = "user.cifs.dosattrib" // FileAttributes.
CIFS_XATTR_CIFS_ACL = "system.cifs_acl" // DACL only.
CIFS_XATTR_CIFS_NTSD = "system.cifs_ntsd" // Owner, Group, DACL.
CIFS_XATTR_CIFS_NTSD_FULL = "system.cifs_ntsd_full" // Owner, Group, DACL, SACL.
)

// 100-nanosecond intervals from Windows Epoch (January 1, 1601) to Unix Epoch (January 1, 1970).
const (
TICKS_FROM_WINDOWS_EPOCH_TO_UNIX_EPOCH = 116444736000000000
)

// windows.Filetime.
type Filetime struct {
LowDateTime uint32
HighDateTime uint32
}

// windows.ByHandleFileInformation
type ByHandleFileInformation struct {
FileAttributes uint32
CreationTime Filetime
LastAccessTime Filetime
LastWriteTime Filetime
VolumeSerialNumber uint32
FileSizeHigh uint32
FileSizeLow uint32
NumberOfLinks uint32
FileIndexHigh uint32
FileIndexLow uint32
}

// Nanoseconds converts Filetime (as ticks since Windows Epoch) to nanoseconds since Unix Epoch (January 1, 1970).
func (ft *Filetime) Nanoseconds() int64 {
// 100-nanosecond intervals (ticks) since Windows Epoch (January 1, 1601).
nsec := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime)

// 100-nanosecond intervals since Unix Epoch (January 1, 1970).
nsec -= TICKS_FROM_WINDOWS_EPOCH_TO_UNIX_EPOCH

// nanoseconds since Unix Epoch.
return nsec * 100
}

// Convert nanoseconds since Unix Epoch (January 1, 1970) to Filetime since Windows Epoch (January 1, 1601).
func NsecToFiletime(nsec int64) Filetime {
// 100-nanosecond intervals since Unix Epoch (January 1, 1970).
nsec /= 100

// 100-nanosecond intervals since Windows Epoch (January 1, 1601).
nsec += TICKS_FROM_WINDOWS_EPOCH_TO_UNIX_EPOCH

return Filetime{LowDateTime: uint32(nsec & 0xFFFFFFFF), HighDateTime: uint32(nsec >> 32)}
}

// WindowsTicksToUnixNano converts ticks (100-ns intervals) since Windows Epoch to nanoseconds since Unix Epoch.
func WindowsTicksToUnixNano(ticks int64) int64 {
// 100-nanosecond intervals since Unix Epoch (January 1, 1970).
ticks -= TICKS_FROM_WINDOWS_EPOCH_TO_UNIX_EPOCH

// nanoseconds since Unix Epoch (January 1, 1970).
return ticks * 100
}

// UnixNanoToWindowsTicks converts nanoseconds since Unix Epoch to ticks since Windows Epoch.
func UnixNanoToWindowsTicks(nsec int64) int64 {
// 100-nanosecond intervals since Unix Epoch (January 1, 1970).
nsec /= 100

// 100-nanosecond intervals since Windows Epoch (January 1, 1601).
nsec += TICKS_FROM_WINDOWS_EPOCH_TO_UNIX_EPOCH

return nsec
}

// StatxTimestampToFiletime converts the unix StatxTimestamp (sec, nsec) to the Windows' Filetime.
// Note that StatxTimestamp is from Unix Epoch while Filetime holds time from Windows Epoch.
func StatxTimestampToFiletime(ts unix.StatxTimestamp) Filetime {
return NsecToFiletime(ts.Sec*int64(time.Second) + int64(ts.Nsec))
}

func GetFileInformation(path string) (ByHandleFileInformation, error) {
var stx unix.Statx_t

// We want all attributes including Btime (aka creation time).
// For consistency with Windows implementation we pass flags==0 which causes it to follow symlinks.
err := unix.Statx(unix.AT_FDCWD, path, 0 /* flags */, unix.STATX_ALL, &stx)
if err == unix.ENOSYS || err == unix.EPERM {
panic(fmt.Errorf("statx syscall is not available: %v", err))
} else if err != nil {
return ByHandleFileInformation{}, fmt.Errorf("statx(%s) failed: %v", path, err)
}

// For getting FileAttributes we need to query the CIFS_XATTR_ATTRIB extended attribute.
// Note: This doesn't necessarily cause a new QUERY_PATH_INFO call to the SMB server, instead
// the value cached in the inode (likely as a result of the above Statx call) will be
// returned.
xattrbuf, err := xattr.Get(path, CIFS_XATTR_ATTRIB)
if err != nil {
return ByHandleFileInformation{},
fmt.Errorf("xattr.Get(%s, %s) failed: %v", path, CIFS_XATTR_ATTRIB, err)
}

var info ByHandleFileInformation

info.FileAttributes = binary.LittleEndian.Uint32(xattrbuf)

info.CreationTime = StatxTimestampToFiletime(stx.Btime)
info.LastAccessTime = StatxTimestampToFiletime(stx.Atime)
info.LastWriteTime = StatxTimestampToFiletime(stx.Mtime)

// TODO: Do we need this?
info.VolumeSerialNumber = 0

info.FileSizeHigh = uint32(stx.Size >> 32)
info.FileSizeLow = uint32(stx.Size & 0xFFFFFFFF)

info.NumberOfLinks = stx.Nlink

info.FileIndexHigh = uint32(stx.Ino >> 32)
info.FileIndexLow = uint32(stx.Ino & 0xFFFFFFFF)

return info, nil
}

func CreateFileOfSizeWithWriteThroughOption(destinationPath string, fileSize int64, writeThrough bool, t FolderCreationTracker, forceIfReadOnly bool) (*os.File, error) {
// forceIfReadOnly is not used on this OS

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ require (
github.com/mattn/go-ieproxy v0.0.3
github.com/minio/minio-go v6.0.14+incompatible
github.com/pkg/errors v0.9.1
github.com/pkg/xattr v0.4.6
github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/spf13/cobra v1.4.0
github.com/wastore/keychain v0.0.0-20180920053336-f2c902a3d807
github.com/wastore/keyctl v0.3.1
Expand Down Expand Up @@ -44,7 +46,6 @@ require (
github.com/kr/pretty v0.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.3.0 // indirect
Expand Down
12 changes: 3 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,9 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
cloud.google.com/go/storage v1.21.0 h1:HwnT2u2D309SFDHQII6m18HlrCi3jAXhUMTLOWXYH14=
cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-pipeline-go v0.2.4-0.20220420205509-9c760f3e9499 h1:eVXzrNOutCSxn7gYn2Tb2alO/D41vX6EyDoRhByS4zc=
github.com/Azure/azure-pipeline-go v0.2.4-0.20220420205509-9c760f3e9499/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-pipeline-go v0.2.4-0.20220425205405-09e6f201e1e4 h1:hDJImUzpTAeIw/UasFUUDB/+UsZm5Q/6x2/jKKvEUiw=
github.com/Azure/azure-pipeline-go v0.2.4-0.20220425205405-09e6f201e1e4/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-storage-blob-go v0.13.1-0.20220307213743-78b465951faf h1:81jHLpY81IPdZqBzsnudRpQM1E9xk+ZzBhhJm7BEvcY=
github.com/Azure/azure-storage-blob-go v0.13.1-0.20220307213743-78b465951faf/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
github.com/Azure/azure-storage-blob-go v0.13.1-0.20220418210520-914dace75d43 h1:/yh9OPVjemL4n8CaXc+GpFTvSlotRFj2HXJIgLo2gG8=
github.com/Azure/azure-storage-blob-go v0.13.1-0.20220418210520-914dace75d43/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
github.com/Azure/azure-storage-blob-go v0.13.1-0.20220418220008-28ac0a48144e h1:uGef/l7KHdWy6XTwhnEB4IhJEisPLe0TDfLVthiVL04=
github.com/Azure/azure-storage-blob-go v0.13.1-0.20220418220008-28ac0a48144e/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
github.com/Azure/azure-storage-file-go v0.6.1-0.20201111053559-3c1754dc00a5 h1:aHEvBM4oXIWSTOVdL55nCYXO0Cl7ie3Ui5xMQhLVez8=
Expand Down Expand Up @@ -247,6 +238,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/xattr v0.4.6 h1:0vqthLIMxQKA9VscyMcxjvAUGvyfzlk009vwLE8OZJg=
github.com/pkg/xattr v0.4.6/go.mod h1:sBD3RAqlr8Q+RC3FutZcikpT8nyDrIEEBw2J744gVWs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
Expand Down Expand Up @@ -437,6 +430,7 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down