mirror of
https://github.com/gilbertchen/duplicacy
synced 2025-12-06 00:03:38 +00:00
Improved macOS VSS checks
VSS support now determined by: 1) Checking that repository filesystem is APFS rather than by inspecting macOS version 2) Checking that repository resides on local device (as external APFS formatted drives are not supported) rather than crudely by path name
This commit is contained in:
@@ -16,33 +16,41 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
"syscall"
|
"syscall"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var snapshotPath string
|
var snapshotPath string
|
||||||
var snapshotDate string
|
var snapshotDate string
|
||||||
|
|
||||||
func GetKernelVersion() (major int, minor int, component int, err error) {
|
// Converts char array to string
|
||||||
|
func CharsToString(ca []int8) string {
|
||||||
|
|
||||||
versionString, err := syscall.Sysctl("kern.osrelease")
|
len := len(ca)
|
||||||
if err != nil {
|
ba := make([]byte, len)
|
||||||
return 0, 0, 0, err;
|
|
||||||
|
for i, v := range ca {
|
||||||
|
ba[i] = byte(v)
|
||||||
|
if ba[i] == 0 {
|
||||||
|
len = i
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
versionSplit := strings.Split(versionString, ".")
|
return string(ba[:len])
|
||||||
if len(versionSplit) != 3 {
|
|
||||||
return 0, 0, 0, errors.New("Sysctl returned invalid kernel version string")
|
|
||||||
}
|
|
||||||
|
|
||||||
major, _ = strconv.Atoi(versionSplit[0])
|
|
||||||
minor, _ = strconv.Atoi(versionSplit[1])
|
|
||||||
component, _ = strconv.Atoi(versionSplit[2])
|
|
||||||
|
|
||||||
return major, minor, component, nil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get ID of device containing path
|
||||||
|
func GetPathDeviceId(path string) (deviceId int32, err error) {
|
||||||
|
stat := syscall.Stat_t{}
|
||||||
|
err = syscall.Stat(path, &stat)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return stat.Dev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executes shell command with timeout and returns stdout
|
||||||
func CommandWithTimeout(timeoutInSeconds int, name string, arg ...string) (output string, err error) {
|
func CommandWithTimeout(timeoutInSeconds int, name string, arg ...string) (output string, err error) {
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutInSeconds) * time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutInSeconds) * time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -88,19 +96,31 @@ func CreateShadowCopy(top string, shadowCopy bool, timeoutInSeconds int) (shadow
|
|||||||
return top
|
return top
|
||||||
}
|
}
|
||||||
|
|
||||||
major, _, _, err := GetKernelVersion()
|
// Check repository filesystem is APFS
|
||||||
|
stat := syscall.Statfs_t{}
|
||||||
|
err := syscall.Statfs(top, &stat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LOG_ERROR("VSS_INIT", "Failed to get kernel version: " + err.Error())
|
LOG_ERROR("VSS_INIT", "Unable to determine filesystem of repository path")
|
||||||
|
return top
|
||||||
|
}
|
||||||
|
if CharsToString(stat.Fstypename[:]) != "apfs" {
|
||||||
|
LOG_WARN("VSS_INIT", "VSS requires APFS filesystem")
|
||||||
return top
|
return top
|
||||||
}
|
}
|
||||||
|
|
||||||
if major < 17 {
|
// Check path is local as tmutil snapshots will not support APFS formatted external drives
|
||||||
LOG_WARN("VSS_INIT", "VSS requires macOS 10.13 High Sierra or higher")
|
deviceIdLocal, err := GetPathDeviceId("/")
|
||||||
|
if err != nil {
|
||||||
|
LOG_ERROR("VSS_INIT", "Unable to get device ID of path: /")
|
||||||
return top
|
return top
|
||||||
}
|
}
|
||||||
|
deviceIdRepository, err := GetPathDeviceId(top)
|
||||||
if top == "/Volumes" || strings.HasPrefix(top, "/Volumes/") {
|
if err != nil {
|
||||||
LOG_ERROR("VSS_PATH", "Invalid repository path: %s", top)
|
LOG_ERROR("VSS_INIT", "Unable to get device ID of path: ", top)
|
||||||
|
return top
|
||||||
|
}
|
||||||
|
if deviceIdLocal != deviceIdRepository {
|
||||||
|
LOG_WARN("VSS_PATH", "VSS not supported for non-local repository path: ", top)
|
||||||
return top
|
return top
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,29 +128,32 @@ func CreateShadowCopy(top string, shadowCopy bool, timeoutInSeconds int) (shadow
|
|||||||
timeoutInSeconds = 60
|
timeoutInSeconds = 60
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create mount point
|
||||||
snapshotPath, err = ioutil.TempDir("/tmp/", "snp_")
|
snapshotPath, err = ioutil.TempDir("/tmp/", "snp_")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LOG_ERROR("VSS_CREATE", "Failed to create temporary mount directory")
|
LOG_ERROR("VSS_CREATE", "Failed to create temporary mount directory")
|
||||||
return top
|
return top
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use tmutil to create snapshot
|
||||||
tmutilOutput, err := CommandWithTimeout(timeoutInSeconds, "tmutil", "snapshot")
|
tmutilOutput, err := CommandWithTimeout(timeoutInSeconds, "tmutil", "snapshot")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LOG_ERROR("VSS_CREATE", "Error while calling tmutil: " + err.Error())
|
LOG_ERROR("VSS_CREATE", "Error while calling tmutil: ", err)
|
||||||
return top
|
return top
|
||||||
}
|
}
|
||||||
|
|
||||||
colonPos := strings.IndexByte(tmutilOutput, ':')
|
colonPos := strings.IndexByte(tmutilOutput, ':')
|
||||||
if colonPos < 0 {
|
if colonPos < 0 {
|
||||||
LOG_ERROR("VSS_CREATE", "Snapshot creation failed: " + tmutilOutput)
|
LOG_ERROR("VSS_CREATE", "Snapshot creation failed: ", tmutilOutput)
|
||||||
return top
|
return top
|
||||||
}
|
}
|
||||||
snapshotDate = strings.TrimSpace(tmutilOutput[colonPos+1:])
|
snapshotDate = strings.TrimSpace(tmutilOutput[colonPos+1:])
|
||||||
|
|
||||||
|
// Mount snapshot as readonly and hide from GUI i.e. Finder
|
||||||
_, err = CommandWithTimeout(timeoutInSeconds,
|
_, err = CommandWithTimeout(timeoutInSeconds,
|
||||||
"mount", "-t", "apfs", "-o", "nobrowse,-r,-s=com.apple.TimeMachine." + snapshotDate, "/", snapshotPath)
|
"mount", "-t", "apfs", "-o", "nobrowse,-r,-s=com.apple.TimeMachine." + snapshotDate, "/", snapshotPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
LOG_ERROR("VSS_CREATE", "Error while mounting snapshot: " + err.Error())
|
LOG_ERROR("VSS_CREATE", "Error while mounting snapshot: ", err)
|
||||||
return top
|
return top
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user