1
0
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:
amarcu5
2018-04-29 04:11:10 +01:00
parent 23b98a3034
commit 714a45c34a

View File

@@ -16,31 +16,39 @@ import (
"errors"
"time"
"syscall"
"strconv"
)
var snapshotPath 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")
if err != nil {
return 0, 0, 0, err;
len := len(ca)
ba := make([]byte, len)
for i, v := range ca {
ba[i] = byte(v)
if ba[i] == 0 {
len = i
break
}
}
versionSplit := strings.Split(versionString, ".")
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;
return string(ba[:len])
}
// 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) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutInSeconds) * time.Second)
@@ -88,19 +96,31 @@ func CreateShadowCopy(top string, shadowCopy bool, timeoutInSeconds int) (shadow
return top
}
major, _, _, err := GetKernelVersion()
// Check repository filesystem is APFS
stat := syscall.Statfs_t{}
err := syscall.Statfs(top, &stat)
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
}
if major < 17 {
LOG_WARN("VSS_INIT", "VSS requires macOS 10.13 High Sierra or higher")
// Check path is local as tmutil snapshots will not support APFS formatted external drives
deviceIdLocal, err := GetPathDeviceId("/")
if err != nil {
LOG_ERROR("VSS_INIT", "Unable to get device ID of path: /")
return top
}
if top == "/Volumes" || strings.HasPrefix(top, "/Volumes/") {
LOG_ERROR("VSS_PATH", "Invalid repository path: %s", top)
deviceIdRepository, err := GetPathDeviceId(top)
if err != nil {
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
}
@@ -108,29 +128,32 @@ func CreateShadowCopy(top string, shadowCopy bool, timeoutInSeconds int) (shadow
timeoutInSeconds = 60
}
// Create mount point
snapshotPath, err = ioutil.TempDir("/tmp/", "snp_")
if err != nil {
LOG_ERROR("VSS_CREATE", "Failed to create temporary mount directory")
return top
}
// Use tmutil to create snapshot
tmutilOutput, err := CommandWithTimeout(timeoutInSeconds, "tmutil", "snapshot")
if err != nil {
LOG_ERROR("VSS_CREATE", "Error while calling tmutil: " + err.Error())
LOG_ERROR("VSS_CREATE", "Error while calling tmutil: ", err)
return top
}
colonPos := strings.IndexByte(tmutilOutput, ':')
if colonPos < 0 {
LOG_ERROR("VSS_CREATE", "Snapshot creation failed: " + tmutilOutput)
LOG_ERROR("VSS_CREATE", "Snapshot creation failed: ", tmutilOutput)
return top
}
snapshotDate = strings.TrimSpace(tmutilOutput[colonPos+1:])
// Mount snapshot as readonly and hide from GUI i.e. Finder
_, err = CommandWithTimeout(timeoutInSeconds,
"mount", "-t", "apfs", "-o", "nobrowse,-r,-s=com.apple.TimeMachine." + snapshotDate, "/", snapshotPath)
if err != nil {
LOG_ERROR("VSS_CREATE", "Error while mounting snapshot: " + err.Error())
LOG_ERROR("VSS_CREATE", "Error while mounting snapshot: ", err)
return top
}