diff --git a/Duplicati/Library/Main/Options.cs b/Duplicati/Library/Main/Options.cs
index 3bb038d54..8628a5320 100644
--- a/Duplicati/Library/Main/Options.cs
+++ b/Duplicati/Library/Main/Options.cs
@@ -109,6 +109,13 @@ namespace Duplicati.Library.Main
///
private const long DEFAULT_BACKUP_TEST_SAMPLES = 1;
+ ///
+ /// The default snapshot policy
+ ///
+ private static readonly OptimizationStrategy DEFAULT_SNAPSHOT_POLICY = PermissionHelper.HasSeBackupPrivilege()
+ ? OptimizationStrategy.Auto
+ : OptimizationStrategy.Off;
+
///
/// The default number of compressor instances
///
@@ -372,7 +379,7 @@ namespace Duplicati.Library.Main
new CommandLineArgument("upload-unchanged-backups", CommandLineArgument.ArgumentType.Boolean, Strings.Options.UploadUnchangedBackupsShort, Strings.Options.UploadUnchangedBackupsLong, "false"),
- new CommandLineArgument("snapshot-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.SnapshotpolicyShort, Strings.Options.SnapshotpolicyLong, "off", null, Enum.GetNames(typeof(OptimizationStrategy))),
+ new CommandLineArgument("snapshot-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.SnapshotpolicyShort, Strings.Options.SnapshotpolicyLong, DEFAULT_SNAPSHOT_POLICY.ToString(), null, Enum.GetNames(typeof(OptimizationStrategy))),
new CommandLineArgument("snapshot-provider", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.SnapshotproviderShort, Strings.Options.SnapshotproviderLong, OperatingSystem.IsWindows() ? Snapshots.SnapshotProvider.AlphaVSS.ToString() : Snapshots.SnapshotProvider.LVM.ToString(), null, (OperatingSystem.IsWindows() ? [Snapshots.SnapshotProvider.AlphaVSS, Snapshots.SnapshotProvider.Wmic] : new [] { Snapshots.SnapshotProvider.LVM }).Select(x => x.ToString()).ToArray()),
new CommandLineArgument("vss-exclude-writers", CommandLineArgument.ArgumentType.String, Strings.Options.VssexcludewritersShort, Strings.Options.VssexcludewritersLong, "{e8132975-6f93-4464-a53e-1050253ae220}"),
new CommandLineArgument("vss-use-mapping", CommandLineArgument.ArgumentType.Boolean, Strings.Options.VssusemappingShort, Strings.Options.VssusemappingLong, "false"),
@@ -894,7 +901,7 @@ namespace Duplicati.Library.Main
///
/// Gets the snapshot strategy to use
///
- public OptimizationStrategy SnapShotStrategy => GetEnum("snapshot-policy", OptimizationStrategy.Off);
+ public OptimizationStrategy SnapShotStrategy => GetEnum("snapshot-policy", DEFAULT_SNAPSHOT_POLICY);
///
/// Gets the snapshot strategy to use
diff --git a/Duplicati/Library/Utility/PermissionHelper.cs b/Duplicati/Library/Utility/PermissionHelper.cs
new file mode 100644
index 000000000..e27a6bb4d
--- /dev/null
+++ b/Duplicati/Library/Utility/PermissionHelper.cs
@@ -0,0 +1,356 @@
+// Copyright (C) 2025, The Duplicati Team
+// https://duplicati.com, hello@duplicati.com
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+#nullable enable
+
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Security.Principal;
+
+namespace Duplicati.Library.Utility;
+
+///
+/// Collection of helper methods to check for permissions.
+///
+public static class PermissionHelper
+{
+ ///
+ /// Checks if the current process has the Backup Privilege.
+ ///
+ /// True if has Backup Privilege, false otherwise.
+ public static bool HasSeBackupPrivilege()
+ {
+ if (OperatingSystem.IsWindows())
+ try
+ {
+ return PrivilegeChecker.TryEnablePrivilege("SeBackupPrivilege");
+ }
+ catch (DllNotFoundException)
+ {
+ // If the function is not available, we assume the privilege is not present.
+ }
+
+ return false;
+ }
+
+ ///
+ /// Check if the current process is running as an administrator, LocalSystem or root.
+ ///
+ /// True if running as administrator, LocalSystem or root, false otherwise.
+ public static bool IsRunningAsAdministratorOrLocalSystem()
+ {
+ if (OperatingSystem.IsWindows())
+ return IsRunningAsAdministrator() || IsRunningAsLocalSystem();
+ if (OperatingSystem.IsLinux())
+ return IsRunningAsRoot();
+
+ return false;
+ }
+
+ ///
+ /// Check if the current process is running as an administrator.
+ ///
+ /// True if running as administrator, false otherwise.
+ [SupportedOSPlatform("windows")]
+ private static bool IsRunningAsAdministrator()
+ {
+ using var identity = WindowsIdentity.GetCurrent();
+ var principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ }
+
+ ///
+ /// Check if the current process is running as LocalSystem.
+ ///
+ /// True if running as LocalSystem, false otherwise.
+ [SupportedOSPlatform("windows")]
+ private static bool IsRunningAsLocalSystem()
+ {
+ using (var identity = WindowsIdentity.GetCurrent())
+ return identity.User?.Value == "S-1-5-18"; // SID for LocalSystem
+ }
+
+ ///
+ /// Gets the effective user ID of the calling process.
+ ///
+ /// The effective user ID of the calling process.
+ [SupportedOSPlatform("linux")]
+ [DllImport("libc")]
+ public static extern uint geteuid();
+
+ ///
+ /// Check if the current process is running as root (user ID 0).
+ ///
+ /// True if running as root, false otherwise.
+ [SupportedOSPlatform("linux")]
+ private static bool IsRunningAsRoot()
+ {
+ return geteuid() == 0;
+ }
+
+ ///
+ /// Helper class for checking Windows privileges.
+ ///
+ private class PrivilegeChecker
+ {
+ ///
+ /// Constant for adjusting token privileges
+ ///
+ const int TOKEN_ADJUST_PRIVILEGES = 0x0020;
+ ///
+ /// Constants for token access rights.
+ ///
+ const int TOKEN_QUERY = 0x0008;
+ ///
+ /// Constant for privilege enabled attribute.
+ ///
+ const int SE_PRIVILEGE_ENABLED = 0x00000002;
+
+ ///
+ /// Structure that contains a locally unique identifier (LUID).
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ struct LUID
+ {
+ public uint LowPart;
+ public int HighPart;
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ return obj is LUID other &&
+ LowPart == other.LowPart &&
+ HighPart == other.HighPart;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(LowPart, HighPart);
+ }
+ }
+
+ ///
+ /// Structure that contains a locally unique identifier (LUID) and its attributes.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ struct LUID_AND_ATTRIBUTES
+ {
+ ///
+ /// The locally unique identifier (LUID) for the privilege.
+ ///
+ public LUID Luid;
+ ///
+ /// The attributes for the privilege.
+ ///
+ public int Attributes;
+ }
+
+ ///
+ /// Structure that contains the privileges associated with a token.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ struct TOKEN_PRIVILEGES
+ {
+ ///
+ /// The number of privileges in the Privileges array.
+ ///
+ public int PrivilegeCount;
+ ///
+ /// The privileges associated with the token.
+ ///
+ /// This field is actually an array of ANY size — we handle this dynamically
+ public LUID_AND_ATTRIBUTES Privileges;
+ }
+
+ ///
+ /// Opens the access token associated with a process.
+ ///
+ /// The handle of the process whose access token is to be opened.
+ /// The access rights to the token.
+ /// A pointer to a variable that receives the handle to the opened access token.
+ /// true if the function succeeds; otherwise, false.
+ [DllImport("advapi32.dll", SetLastError = true)]
+ static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, out IntPtr TokenHandle);
+
+ ///
+ /// Retrieves a handle to the access token associated with the current process.
+ /// ///
+ /// A handle to the access token associated with the current process.
+ [DllImport("kernel32.dll")]
+ static extern IntPtr GetCurrentProcess();
+
+ ///
+ /// Retrieves a specified type of information about an access token.
+ ///
+ /// A handle to the access token from which information is being retrieved.
+ /// The type of information to be retrieved.
+ /// A pointer to a buffer that receives the requested information.
+ /// The size of the buffer pointed to by the TokenInformation parameter.
+ /// A pointer to a variable that receives the size of the data returned in the TokenInformation buffer.
+ /// true if the function succeeds; otherwise, false.
+ [DllImport("advapi32.dll", SetLastError = true)]
+ static extern bool GetTokenInformation(
+ IntPtr TokenHandle,
+ int TokenInformationClass,
+ IntPtr TokenInformation,
+ int TokenInformationLength,
+ out int ReturnLength
+ );
+
+ ///
+ /// Retrieves the locally unique identifier (LUID) for a specified privilege name.
+ ///
+ /// The name of the system on which the privilege is being looked up. This parameter can be null.
+ /// The name of the privilege to look up. This parameter cannot be null.
+ /// A pointer to a LUID structure that receives the LUID for the specified privilege.
+ /// true if the function succeeds; otherwise, false.
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ static extern bool LookupPrivilegeValue(string? lpSystemName, string lpName, out LUID lpLuid);
+
+ ///
+ /// Adjusts the privileges of a specified access token.
+ ///
+ /// A handle to the access token whose privileges are to be adjusted.
+ /// Specifies whether all privileges are to be disabled.
+ /// A pointer to a TOKEN_PRIVILEGES structure that specifies the new privileges.
+ /// The size of the buffer pointed to by the PreviousState parameter.
+ /// A pointer to a buffer that receives the previous state of the privileges.
+ /// A pointer to a variable that receives the size of the data returned in the PreviousState buffer.
+ /// true if the function succeeds; otherwise, false.
+ [DllImport("advapi32.dll", SetLastError = true)]
+ static extern bool AdjustTokenPrivileges(
+ IntPtr TokenHandle,
+ bool DisableAllPrivileges,
+ ref TOKEN_PRIVILEGES NewState,
+ int BufferLength,
+ IntPtr PreviousState,
+ IntPtr ReturnLength
+ );
+
+ ///
+ /// Retrieves the calling thread's last-error code value.
+ ///
+ /// The last-error code value.
+ [DllImport("kernel32.dll")]
+ static extern uint GetLastError();
+
+ ///
+ /// Flag tracking if backup privilege is enabled
+ ///
+ private static bool? _isPrivilegeEnabled;
+
+ ///
+ /// Tries to enable a specific privilege in the current process token.
+ ///
+ /// The name of the privilege (e.g., "SeBackupPrivilege").
+ ///
+ /// True if the privilege is present and was successfully enabled.
+ /// False if the privilege is not held or could not be enabled.
+ ///
+ public static bool TryEnablePrivilege(string privilegeName)
+ {
+ if (_isPrivilegeEnabled.HasValue)
+ return _isPrivilegeEnabled.Value;
+
+ try
+ {
+ _isPrivilegeEnabled = false;
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out IntPtr tokenHandle))
+ return false;
+
+ if (!LookupPrivilegeValue(null, privilegeName, out LUID luid))
+ return false;
+
+ var tp = new TOKEN_PRIVILEGES
+ {
+ PrivilegeCount = 1,
+ Privileges = new LUID_AND_ATTRIBUTES()
+ {
+ Luid = luid,
+ Attributes = SE_PRIVILEGE_ENABLED
+ }
+ };
+
+ if (!AdjustTokenPrivileges(tokenHandle, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero))
+ return false;
+
+ _isPrivilegeEnabled = (GetLastError() == 0); // If not zero, then the privilege wasn't assigned (e.g., ERROR_NOT_ALL_ASSIGNED)
+ return _isPrivilegeEnabled.Value;
+ }
+ catch (Exception)
+ {
+ // Ignore this error
+ }
+
+ return _isPrivilegeEnabled ?? false;
+ }
+
+ ///
+ /// Determines whether the current process token has the specified Windows privilege enabled.
+ ///
+ ///
+ /// The name of the privilege to check (e.g., "SeBackupPrivilege", "SeRestorePrivilege").
+ ///
+ ///
+ /// true if the privilege is present and enabled in the current process token; otherwise, false.
+ ///
+ ///
+ /// This function does not attempt to enable the privilege, it only verifies whether it is enabled.
+ /// Requires that the calling process has permission to query its own token.
+ ///
+ public static bool IsPrivilegeEnabled(string privilegeName)
+ {
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out IntPtr tokenHandle))
+ return false;
+
+ if (!LookupPrivilegeValue(null, privilegeName, out LUID targetLuid))
+ return false;
+
+ // First call to get required buffer size
+ GetTokenInformation(tokenHandle, 3, IntPtr.Zero, 0, out int bufferSize); // TokenPrivileges = 3
+ IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
+
+ try
+ {
+ if (!GetTokenInformation(tokenHandle, 3, buffer, bufferSize, out _))
+ return false;
+
+ int count = Marshal.ReadInt32(buffer);
+ IntPtr luidPtr = buffer + sizeof(int);
+ int structSize = Marshal.SizeOf();
+
+ for (int i = 0; i < count; i++)
+ {
+ LUID_AND_ATTRIBUTES laa = Marshal.PtrToStructure(luidPtr + i * structSize);
+ if (laa.Luid.Equals(targetLuid))
+ return (laa.Attributes & SE_PRIVILEGE_ENABLED) != 0;
+ }
+ return false;
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(buffer);
+ }
+ }
+ }
+}
\ No newline at end of file