diff --git a/Duplicati/Library/Backend/CIFS/CIFS.cs b/Duplicati/Library/Backend/CIFS/CIFS.cs
index 53ec3264e..04603ec74 100644
--- a/Duplicati/Library/Backend/CIFS/CIFS.cs
+++ b/Duplicati/Library/Backend/CIFS/CIFS.cs
@@ -19,6 +19,8 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
+#nullable enable
+
using System;
using Duplicati.Library.Interface;
using System.Collections.Generic;
@@ -32,7 +34,7 @@ using SMBLibrary;
using Duplicati.Library.SourceProvider;
using System.Runtime.CompilerServices;
using Duplicati.Library.Common.IO;
-using System.Runtime.CompilerServices;
+using Duplicati.Library.Utility.Options;
namespace Duplicati.Library.Backend;
@@ -69,12 +71,17 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
///
/// Cache of parsed connection parameters
///
- private SMBConnectionParameters _connectionParameters;
+ private readonly SMBConnectionParameters _connectionParameters;
+
+ ///
+ /// Timeout options to be used in the backend
+ ///
+ private readonly TimeoutOptionsHelper.Timeouts _timeouts;
///
/// Shared connection between all methods to avoid re-authentication
///
- private SMBShareConnection _sharedConnection;
+ private SMBShareConnection? _sharedConnection;
///
/// Read buffer size for SMB operations (will be capped automatically by SMB negotiated values)
@@ -96,16 +103,6 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
///
private const string AUTH_DOMAIN_OPTION = "auth-domain";
- ///
- /// Username for authentication
- ///
- private const string AUTH_USERNAME_OPTION = "auth-username";
-
- ///
- /// Password for authentication
- ///
- private const string AUTH_PASSWORD_OPTION = "auth-password";
-
///
/// Defines the default transport to be used in CIFS connection
///
@@ -125,6 +122,9 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
///
public CIFSBackend()
{
+ _DnsName = null!;
+ _connectionParameters = null!;
+ _timeouts = null!;
}
///
@@ -132,7 +132,7 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
///
/// URL in Duplicati Uri format
/// options to be used in the backend
- public CIFSBackend(string url, Dictionary options)
+ public CIFSBackend(string url, Dictionary options)
{
if (string.IsNullOrEmpty(url))
throw new ArgumentNullException(nameof(url));
@@ -146,37 +146,39 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
var input = uri.Path.TrimEnd('/');
var slashIndex = input.IndexOf('/'); // Find first slash to separate server and share if present.
- options.TryGetValue(AUTH_USERNAME_OPTION, out string authUsername);
- options.TryGetValue(AUTH_PASSWORD_OPTION, out string authPassword);
- options.TryGetValue(AUTH_DOMAIN_OPTION, out string authDomain);
- options.TryGetValue(TRANSPORT_OPTION, out string transport);
+ var auth = AuthOptionsHelper.Parse(options, uri);
+ var authDomain = options.GetValueOrDefault(AUTH_DOMAIN_OPTION);
+ var transport = options.GetValueOrDefault(TRANSPORT_OPTION);
int? readBufferSize = null, writeBufferSize = null;
- options.TryGetValue(READ_BUFFER_SIZE_OPTION, out string readBufferSizeConfig);
- if (!string.IsNullOrWhiteSpace(readBufferSizeConfig)) readBufferSize = Int32.TryParse(readBufferSizeConfig, out int value) ? value : null;
+ var readBufferSizeConfig = options.GetValueOrDefault(READ_BUFFER_SIZE_OPTION);
+ if (!string.IsNullOrWhiteSpace(readBufferSizeConfig))
+ readBufferSize = int.TryParse(readBufferSizeConfig, out int value) ? value : null;
- options.TryGetValue(WRITE_BUFFER_SIZE_OPTION, out string writeBufferSizeConfig);
- if (!string.IsNullOrWhiteSpace(writeBufferSizeConfig)) writeBufferSize = Int32.TryParse(readBufferSizeConfig, out int value) ? value : null;
+ var writeBufferSizeConfig = options.GetValueOrDefault(WRITE_BUFFER_SIZE_OPTION);
+ if (!string.IsNullOrWhiteSpace(writeBufferSizeConfig))
+ writeBufferSize = int.TryParse(readBufferSizeConfig, out int value) ? value : null;
// Normalize to 10KB minimum buffers size
readBufferSize = readBufferSize < 1024 * 10 ? null : readBufferSize;
writeBufferSize = writeBufferSize < 1024 * 10 ? null : writeBufferSize;
- SMBTransportType transportType = _transportMap.TryGetValue(
+ var transportType = _transportMap.TryGetValue(
string.IsNullOrEmpty(transport) ? DEFAULT_TRANSPORT : transport.ToLower(),
out SMBTransportType type)
? type
: throw new UserInformationException($"Transport must be one of: {string.Join(", ", _transportMap.Keys)}", "CIFSConfig");
+ _timeouts = TimeoutOptionsHelper.Parse(options);
_connectionParameters = new SMBConnectionParameters(
uri.Host,
transportType,
slashIndex >= 0 ? input[..slashIndex] : input,
slashIndex >= 0 ? input[(slashIndex + 1)..] : "",
authDomain,
- authUsername,
- authPassword,
+ auth.Username,
+ auth.Password,
readBufferSize,
writeBufferSize
);
@@ -186,14 +188,14 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
/// Implementation of interface property to return supported command parameters
///
public IList SupportedCommands =>
- new List([
- new CommandLineArgument(AUTH_PASSWORD_OPTION, CommandLineArgument.ArgumentType.Password, Strings.CIFSBackend.DescriptionAuthPasswordShort, Strings.CIFSBackend.DescriptionAuthPasswordLong),
- new CommandLineArgument(AUTH_USERNAME_OPTION, CommandLineArgument.ArgumentType.String, Strings.CIFSBackend.DescriptionAuthUsernameShort, Strings.CIFSBackend.DescriptionAuthUsernameLong),
+ [
+ .. AuthOptionsHelper.GetOptions(),
new CommandLineArgument(AUTH_DOMAIN_OPTION, CommandLineArgument.ArgumentType.String, Strings.CIFSBackend.DescriptionAuthDomainShort, Strings.CIFSBackend.DescriptionAuthDomainLong),
new CommandLineArgument(TRANSPORT_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.Options.TransportShort, Strings.Options.TransportLong, DEFAULT_TRANSPORT, null, _transportMap.Keys.ToArray()),
new CommandLineArgument(READ_BUFFER_SIZE_OPTION, CommandLineArgument.ArgumentType.String, Strings.Options.DescriptionReadBufferSizeShort, Strings.Options.DescriptionReadBufferSizeLong),
- new CommandLineArgument(WRITE_BUFFER_SIZE_OPTION, CommandLineArgument.ArgumentType.String, Strings.Options.DescriptionWriteBufferSizeShort, Strings.Options.DescriptionWriteBufferSizeLong)
- ]);
+ new CommandLineArgument(WRITE_BUFFER_SIZE_OPTION, CommandLineArgument.ArgumentType.String, Strings.Options.DescriptionWriteBufferSizeShort, Strings.Options.DescriptionWriteBufferSizeLong),
+ .. TimeoutOptionsHelper.GetOptions()
+ ];
///
/// Implementation of interface method for listing remote folder contents
@@ -201,7 +203,8 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
/// List of IFileEntry with directory listing result
public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
- foreach (var v in await GetConnection().ListAsync(_connectionParameters.Path, cancellationToken).ConfigureAwait(false))
+ var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false);
+ foreach (var v in await con.ListAsync(_connectionParameters.Path, cancellationToken).ConfigureAwait(false))
yield return v;
}
@@ -230,7 +233,8 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
/// Exceptions arising from either code execution
public async Task PutAsync(string remotename, Stream input, CancellationToken cancellationToken)
{
- await GetConnection().PutAsync(remotename, input, cancellationToken).ConfigureAwait(false);
+ var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false);
+ await con.PutAsync(remotename, input, cancellationToken).ConfigureAwait(false);
}
///
@@ -259,7 +263,8 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
/// Exceptions arising from either code execution or FileMissingException
public async Task GetAsync(string remotename, Stream output, CancellationToken cancellationToken)
{
- await GetConnection().GetAsync(remotename, output, cancellationToken).ConfigureAwait(false);
+ var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false);
+ await con.GetAsync(remotename, output, cancellationToken).ConfigureAwait(false);
}
///
@@ -272,7 +277,8 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
/// Exceptions arising from either code execution or business logic errors
public async Task DeleteAsync(string remotename, CancellationToken cancellationToken)
{
- await GetConnection().DeleteAsync(remotename, cancellationToken).ConfigureAwait(false);
+ var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false);
+ await con.DeleteAsync(remotename, cancellationToken).ConfigureAwait(false);
}
///
@@ -291,7 +297,8 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
public async Task TestAsync(CancellationToken cancellationToken)
{
// This will throw an exception if the folder is missing
- await GetConnection().ListAsync(_connectionParameters.Path, cancellationToken).ConfigureAwait(false);
+ var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false);
+ await con.ListAsync(_connectionParameters.Path, cancellationToken).ConfigureAwait(false);
}
///
@@ -308,7 +315,8 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
if (pathParts == null || pathParts.Length == 0)
return;
- await GetConnection().CreateFolderAsync(_connectionParameters.Path, cancellationToken)
+ var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false);
+ await con.CreateFolderAsync(_connectionParameters.Path ?? "", cancellationToken)
.ConfigureAwait(false);
}
@@ -316,7 +324,8 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
/// Gets or creates a shared SMB connection
///
/// An SMB connection that can be used for file operations
- private SMBShareConnection GetConnection() => _sharedConnection ??= new SMBShareConnection(_connectionParameters);
+ private async Task GetConnectionAsync(CancellationToken cancellationToken)
+ => _sharedConnection ??= await SMBShareConnection.CreateAsync(_connectionParameters, _timeouts, cancellationToken).ConfigureAwait(false);
@@ -337,18 +346,19 @@ public class CIFSBackend : IStreamingBackend, IFolderEnabledBackend
}
///
- public async IAsyncEnumerable ListAsync(string path, [EnumeratorCancellation] CancellationToken cancellationToken)
+ public async IAsyncEnumerable ListAsync(string? path, [EnumeratorCancellation] CancellationToken cancellationToken)
{
var sourcePath = _connectionParameters.Path;
if (!string.IsNullOrWhiteSpace(sourcePath))
sourcePath = Util.AppendDirSeparator(sourcePath, "/");
- foreach (var v in await GetConnection().ListAsync(sourcePath + BackendSourceFileEntry.NormalizePathTo(path, '/'), cancellationToken).ConfigureAwait(false))
+ var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false);
+ foreach (var v in await con.ListAsync(sourcePath + BackendSourceFileEntry.NormalizePathTo(path, '/'), cancellationToken).ConfigureAwait(false))
if (v.Name != "." && v.Name != "..")
yield return v;
}
///
- public Task GetEntryAsync(string path, CancellationToken cancellationToken)
- => Task.FromResult(null);
+ public Task GetEntryAsync(string path, CancellationToken cancellationToken)
+ => Task.FromResult(null);
}
\ No newline at end of file
diff --git a/Duplicati/Library/Backend/CIFS/Model/SMBConnectionParameters.cs b/Duplicati/Library/Backend/CIFS/Model/SMBConnectionParameters.cs
index e2f687625..8fe1770b9 100644
--- a/Duplicati/Library/Backend/CIFS/Model/SMBConnectionParameters.cs
+++ b/Duplicati/Library/Backend/CIFS/Model/SMBConnectionParameters.cs
@@ -26,84 +26,22 @@ namespace Duplicati.Library.Backend.CIFS.Model;
///
/// Connection parameters for establishing an SMB connection.
///
-public record SMBConnectionParameters
-{
- ///
- /// The name or IP address of the SMB server
- ///
- public string ServerName { get; init; }
-
- ///
- /// The transport protocol type used for SMB communication
- ///
- public SMBTransportType TransportType { get; init; }
-
- ///
- /// The name of the network share to connect to
- ///
- public string ShareName { get; init; }
-
- ///
- /// The path within the share to access
- ///
- public string Path { get; init; }
-
- ///
- /// The authentication domain name
- ///
- public string AuthDomain { get; init; }
-
- ///
- /// The username for authentication
- ///
- public string AuthUser { get; init; }
-
- ///
- /// The password for authentication
- ///
- public string AuthPassword { get; init; }
-
- ///
- /// Write buffer size for SMB operations (will be capped automatically by SMB negotiated values)
- ///
- public int? WriteBufferSize { get; init; }
-
- ///
- /// Read buffer size for SMB operations (will be capped automatically by SMB negotiated values)
- ///
- public int? ReadBufferSize { get; init; }
-
- ///
- /// Creates a new instance of SMB connection parameters
- ///
- /// The name or IP address of the SMB server
- /// The transport protocol type used for SMB communication
- /// The name of the network share to connect to
- /// The path within the share to access
- /// The authentication domain name
- /// The username for authentication
- /// The password for authentication
- /// Read buffer size for SMB operations (will be capped automatically by SMB negotiated values)
- /// Write buffer size for SMB operations (will be capped automatically by SMB negotiated values)
- public SMBConnectionParameters(
- string serverName,
- SMBTransportType transportType,
- string shareName,
- string path,
- string authDomain,
- string authUser,
- string authPassword,
- int? readBufferSize,
- int? writeBufferSize)
- {
- ServerName = serverName;
- TransportType = transportType;
- ShareName = shareName;
- Path = path;
- AuthDomain = authDomain;
- AuthUser = authUser;
- AuthPassword = authPassword;
- ReadBufferSize = readBufferSize;
- WriteBufferSize = writeBufferSize;
- }
-}
\ No newline at end of file
+/// The name or IP address of the SMB server
+/// The transport protocol type used for SMB communication
+/// The name of the network share to connect to
+/// The path within the share to access
+/// The authentication domain name
+/// The username for authentication
+/// The password for authentication
+/// Read buffer size for SMB operations (will be capped automatically by SMB negotiated values)
+/// Write buffer size for SMB operations (will be capped automatically by SMB negotiated values)
+public sealed record SMBConnectionParameters(
+ string ServerName,
+ SMBTransportType TransportType,
+ string ShareName,
+ string Path,
+ string AuthDomain,
+ string AuthUser,
+ string AuthPassword,
+ int? ReadBufferSize,
+ int? WriteBufferSize);
\ No newline at end of file
diff --git a/Duplicati/Library/Backend/CIFS/SMBShareConnection.cs b/Duplicati/Library/Backend/CIFS/SMBShareConnection.cs
index 47e23b742..0226811cd 100644
--- a/Duplicati/Library/Backend/CIFS/SMBShareConnection.cs
+++ b/Duplicati/Library/Backend/CIFS/SMBShareConnection.cs
@@ -31,6 +31,8 @@ using Duplicati.Library.Backend.CIFS.Model;
using Duplicati.Library.Common.IO;
using Duplicati.Library.Interface;
using Duplicati.Library.Localization.Short;
+using Duplicati.Library.Utility;
+using Duplicati.Library.Utility.Options;
using SMBLibrary;
using SMBLibrary.Client;
using FileAttributes = SMBLibrary.FileAttributes;
@@ -46,7 +48,7 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable
///
/// SMBConnection client
///
- private readonly SMB2Client _smb2Client = new();
+ private readonly SMB2Client _smb2Client;
///
/// Shared fileStore object.
@@ -58,11 +60,19 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable
///
private readonly SMBConnectionParameters _connectionParameters;
+ ///
+ /// The timeouts to use for operations.
+ ///
+ private readonly TimeoutOptionsHelper.Timeouts _timeouts;
+
///
/// The semaphore to ensure that only one operation is performed at a time.
///
private readonly SemaphoreSlim _semaphore = new(1, 1);
+ ///
+ /// Flag to indicate if the object has been disposed.
+ ///
private bool _disposed;
///
@@ -71,23 +81,51 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable
/// It throws specific exceptions for connection and authentication failures.
///
/// Connection Parameters
- /// Exception to be displayed to user
- public SMBShareConnection(SMBConnectionParameters connectionParameters)
+ /// Timeouts to use for operations
+ private SMBShareConnection(SMB2Client client, ISMBFileStore fileStore, SMBConnectionParameters connectionParameters, TimeoutOptionsHelper.Timeouts timeouts)
{
_connectionParameters = connectionParameters;
+ _timeouts = timeouts;
+ _smbFileStore = fileStore;
+ _smb2Client = client;
+ }
- if (!_smb2Client.Connect(connectionParameters.ServerName, connectionParameters.TransportType))
+ ///
+ /// Creates a new SMBShareConnection object asynchronously.
+ ///
+ /// The connection parameters
+ /// The timeouts to use for operations
+ /// The cancellation token
+ /// The SMBShareConnection object
+ /// Exception to be displayed to user
+ public static async Task CreateAsync(SMBConnectionParameters connectionParameters, TimeoutOptionsHelper.Timeouts timeouts, CancellationToken cancellationToken)
+ {
+ var client = new SMB2Client();
+ var connected = await Utility.Utility.WithTimeout(timeouts.ShortTimeout, cancellationToken, _ =>
+ client.Connect(connectionParameters.ServerName, connectionParameters.TransportType)
+ ).ConfigureAwait(false);
+
+ if (!connected)
throw new UserInformationException($"{LC.L("Failed to connect to server")} {connectionParameters.ServerName}", "ConnectionError");
- var status = _smb2Client.Login(connectionParameters.AuthDomain ?? "", connectionParameters.AuthUser ?? "", connectionParameters.AuthPassword ?? "");
+ var status = await Utility.Utility.WithTimeout(timeouts.ShortTimeout, cancellationToken, _ =>
+ client.Login(connectionParameters.AuthDomain ?? "", connectionParameters.AuthUser ?? "", connectionParameters.AuthPassword ?? "")
+ ).ConfigureAwait(false);
if (status != NTStatus.STATUS_SUCCESS)
throw new UserInformationException($"{LC.L("Failed to authenticate to server")} {connectionParameters.ServerName} with status {status}", "ConnectionError");
- _smbFileStore = _smb2Client.TreeConnect(connectionParameters.ShareName, out status);
+ (var res, status) = await Utility.Utility.WithTimeout(timeouts.ShortTimeout, cancellationToken, _ =>
+ {
+ var res = client.TreeConnect(connectionParameters.ShareName, out status);
+ return (res, status);
+ }
+ ).ConfigureAwait(false);
- if (status != NTStatus.STATUS_SUCCESS)
+ if (status != NTStatus.STATUS_SUCCESS || res == null)
throw new UserInformationException($"{LC.L("Failed to connect to share")} {connectionParameters.ShareName} with status {status}", "ConnectionError");
+
+ return new SMBShareConnection(client, res, connectionParameters, timeouts);
}
///
@@ -101,33 +139,33 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable
await _semaphore.WaitAsync(cancellationToken);
try
{
- NTStatus status;
- object fileHandle;
- FileStatus fileStatus;
- status = _smbFileStore.CreateFile(out fileHandle, out fileStatus, NormalizeSlashes(Path.Combine(_connectionParameters.Path, fileName)),
- AccessMask.GENERIC_WRITE | AccessMask.DELETE | AccessMask.SYNCHRONIZE,
- FileAttributes.Normal,
- ShareAccess.None,
- CreateDisposition.FILE_OPEN,
- CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT,
- null);
-
- if (status == NTStatus.STATUS_OBJECT_NAME_NOT_FOUND)
- throw new FileMissingException();
-
- if (status == NTStatus.STATUS_SUCCESS)
+ await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, _ =>
{
- var fileDispositionInformation = new FileDispositionInformation
+ var status = _smbFileStore.CreateFile(out var fileHandle, out var fileStatus, NormalizeSlashes(Path.Combine(_connectionParameters.Path, fileName)),
+ AccessMask.GENERIC_WRITE | AccessMask.DELETE | AccessMask.SYNCHRONIZE,
+ FileAttributes.Normal,
+ ShareAccess.None,
+ CreateDisposition.FILE_OPEN,
+ CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT,
+ null);
+
+ if (status == NTStatus.STATUS_OBJECT_NAME_NOT_FOUND)
+ throw new FileMissingException();
+
+ if (status == NTStatus.STATUS_SUCCESS)
{
- DeletePending = true
- };
- status = _smbFileStore.SetFileInformation(fileHandle, fileDispositionInformation);
- if (status != NTStatus.STATUS_SUCCESS)
- throw new UserInformationException($"{LC.L("Failed to delete file on DeleteAsync")} with status {status}", "DeleteFileError");
- status = _smbFileStore.CloseFile(fileHandle);
- if (status != NTStatus.STATUS_SUCCESS)
- throw new UserInformationException($"{LC.L("Failed to close file on DeleteAsync")} with status {status}", "CloseFileError");
- }
+ var fileDispositionInformation = new FileDispositionInformation
+ {
+ DeletePending = true
+ };
+ status = _smbFileStore.SetFileInformation(fileHandle, fileDispositionInformation);
+ if (status != NTStatus.STATUS_SUCCESS)
+ throw new UserInformationException($"{LC.L("Failed to delete file on DeleteAsync")} with status {status}", "DeleteFileError");
+ status = _smbFileStore.CloseFile(fileHandle);
+ if (status != NTStatus.STATUS_SUCCESS)
+ throw new UserInformationException($"{LC.L("Failed to close file on DeleteAsync")} with status {status}", "CloseFileError");
+ }
+ }).ConfigureAwait(false);
}
finally
{
@@ -147,10 +185,10 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable
try
{
// Normalize path separators to forward slashes and trim any trailing separators
- string linuxNormalizedPath = path.Replace('/', '\\').TrimEnd('\\');
- string currentPath = "";
+ var linuxNormalizedPath = path.Replace('/', '\\').TrimEnd('\\');
+ var currentPath = "";
- foreach (string part in linuxNormalizedPath.Split('\\', StringSplitOptions.RemoveEmptyEntries))
+ foreach (var part in linuxNormalizedPath.Split('\\', StringSplitOptions.RemoveEmptyEntries))
{
if (string.IsNullOrWhiteSpace(part) || part == ".")
continue;
@@ -159,20 +197,23 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable
object? fileHandle = null;
try
{
- NTStatus status = _smbFileStore.CreateFile(
- out fileHandle,
- out FileStatus fileStatus,
- currentPath,
- AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE,
- FileAttributes.Normal,
- ShareAccess.None,
- CreateDisposition.FILE_CREATE,
- CreateOptions.FILE_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT,
- null
- );
- if (status != NTStatus.STATUS_SUCCESS &&
- status != NTStatus.STATUS_OBJECT_NAME_COLLISION) // Ignore if directory already exists
- throw new UserInformationException($"{LC.L("Failed to create directory")} {currentPath} with status{status}", "CreateDirectoryError");
+ await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, _ =>
+ {
+ var status = _smbFileStore.CreateFile(
+ out fileHandle,
+ out var fileStatus,
+ currentPath,
+ AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE,
+ FileAttributes.Normal,
+ ShareAccess.None,
+ CreateDisposition.FILE_CREATE,
+ CreateOptions.FILE_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT,
+ null
+ );
+ if (status != NTStatus.STATUS_SUCCESS &&
+ status != NTStatus.STATUS_OBJECT_NAME_COLLISION) // Ignore if directory already exists
+ throw new UserInformationException($"{LC.L("Failed to create directory")} {currentPath} with status{status}", "CreateDirectoryError");
+ }).ConfigureAwait(false);
}
finally
{
@@ -207,32 +248,37 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable
{
try
{
- var status = _smbFileStore.CreateFile(
- out directoryHandle,
- out fileStatus,
- NormalizeSlashes(path),
- AccessMask.GENERIC_READ,
- FileAttributes.Directory,
- ShareAccess.Read | ShareAccess.Write,
- CreateDisposition.FILE_OPEN,
- CreateOptions.FILE_DIRECTORY_FILE,
- null);
+ var fileList = await Utility.Utility.WithTimeout(_timeouts.ListTimeout, cancellationToken, _ =>
+ {
+ var status = _smbFileStore.CreateFile(
+ out directoryHandle,
+ out fileStatus,
+ NormalizeSlashes(path),
+ AccessMask.GENERIC_READ,
+ FileAttributes.Directory,
+ ShareAccess.Read | ShareAccess.Write,
+ CreateDisposition.FILE_OPEN,
+ CreateOptions.FILE_DIRECTORY_FILE,
+ null);
- if (status != NTStatus.STATUS_SUCCESS && fileStatus != FileStatus.FILE_OPENED)
- if (status == NTStatus.STATUS_OBJECT_PATH_NOT_FOUND || status == NTStatus.STATUS_OBJECT_NAME_NOT_FOUND)
- throw new FolderMissingException();
- else
- throw new UserInformationException($"{LC.L("Failed to open directory")} {NormalizeSlashes(path)} with status {status}", "DirectoryOpenError");
+ if (status != NTStatus.STATUS_SUCCESS && fileStatus != FileStatus.FILE_OPENED)
+ if (status == NTStatus.STATUS_OBJECT_PATH_NOT_FOUND || status == NTStatus.STATUS_OBJECT_NAME_NOT_FOUND)
+ throw new FolderMissingException();
+ else
+ throw new UserInformationException($"{LC.L("Failed to open directory")} {NormalizeSlashes(path)} with status {status}", "DirectoryOpenError");
- List fileList;
- status = _smbFileStore.QueryDirectory(
- out fileList,
- directoryHandle,
- "*",
- FileInformationClass.FileDirectoryInformation);
+ List fileList;
+ status = _smbFileStore.QueryDirectory(
+ out fileList,
+ directoryHandle,
+ "*",
+ FileInformationClass.FileDirectoryInformation);
- if (status != NTStatus.STATUS_NO_MORE_FILES)
- throw new UserInformationException($"{LC.L("Failed to query directory contents")} with status {status}", "DirectoryQueryError");
+ if (status != NTStatus.STATUS_NO_MORE_FILES)
+ throw new UserInformationException($"{LC.L("Failed to query directory contents")} with status {status}", "DirectoryQueryError");
+
+ return fileList;
+ }).ConfigureAwait(false);
return
[
@@ -279,18 +325,22 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable
try
{
- object? fileHandle;
- FileStatus fileStatus;
- NTStatus status = _smbFileStore.CreateFile(out fileHandle, out fileStatus,
+ (var status, var fileStatus, var fileHandle) = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, _ =>
+ {
+ var status = _smbFileStore.CreateFile(out var fileHandle, out var fileStatus,
NormalizeSlashes(Path.Combine(_connectionParameters.Path, filename)),
AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.Read,
CreateDisposition.FILE_OPEN,
CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null);
+ return (status, fileStatus, fileHandle);
+ }
+ ).ConfigureAwait(false);
if (status == NTStatus.STATUS_SUCCESS || fileStatus != FileStatus.FILE_DOES_NOT_EXIST)
{
byte[] data;
long bytesRead = 0;
+ using var timeoutStream = destinationStream.ObserveWriteTimeout(_timeouts.ReadWriteTimeout, false);
while (!cancellationToken.IsCancellationRequested)
{
// Use the provided read buffer size if set, otherwise use the protocol negotiated maximum. Never exceed the negotiated maximum.
@@ -303,10 +353,10 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable
break;
bytesRead += data.Length;
- await destinationStream.WriteAsync(data, 0, data.Length, cancellationToken);
+ await timeoutStream.WriteAsync(data, 0, data.Length, cancellationToken);
}
- await destinationStream.FlushAsync(cancellationToken);
+ await timeoutStream.FlushAsync(cancellationToken);
if (fileHandle != null)
{
@@ -321,7 +371,7 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable
throw new FolderMissingException();
else
throw new UserInformationException(
- $"{LC.L("Failed to open file with error")} {filename} with status {status.ToString()}",
+ $"{LC.L("Failed to open file with error")} {filename} with status {status}",
"FileOpenError");
}
finally
@@ -343,25 +393,29 @@ public class SMBShareConnection : IDisposable, IAsyncDisposable
await _semaphore.WaitAsync(cancellationToken);
try
{
- object fileHandle;
- FileStatus fileStatus;
- NTStatus status = _smbFileStore.CreateFile(out fileHandle, out fileStatus,
- NormalizeSlashes(Path.Combine(_connectionParameters.Path, filename)),
- AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE,
- FileAttributes.Normal, ShareAccess.None,
- CreateDisposition.FILE_SUPERSEDE,
- CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT,
- null);
+ (var status, var fileStatus, var fileHandle) = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, _ =>
+ {
+ var status = _smbFileStore.CreateFile(out var fileHandle, out var fileStatus,
+ NormalizeSlashes(Path.Combine(_connectionParameters.Path, filename)),
+ AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE,
+ FileAttributes.Normal, ShareAccess.None,
+ CreateDisposition.FILE_SUPERSEDE,
+ CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT,
+ null);
+ return (status, fileStatus, fileHandle);
+ }).ConfigureAwait(false);
+
if (status == NTStatus.STATUS_SUCCESS)
{
// Use the provided write buffer size if set, otherwise use the protocol negotiated maximum. Never exceed the negotiated maximum.
- byte[] buffer = new byte[Math.Min(_connectionParameters.WriteBufferSize ?? (int)_smb2Client.MaxWriteSize, _smb2Client.MaxWriteSize)];
+ var buffer = new byte[Math.Min(_connectionParameters.WriteBufferSize ?? (int)_smb2Client.MaxWriteSize, _smb2Client.MaxWriteSize)];
int bytesRead;
int numberOfBytesWritten;
int offset = 0;
- while (!cancellationToken.IsCancellationRequested && sourceStream.Position < sourceStream.Length)
+ using var timeoutStream = sourceStream.ObserveReadTimeout(_timeouts.ReadWriteTimeout, false);
+ while (!cancellationToken.IsCancellationRequested && timeoutStream.Position < timeoutStream.Length)
{
- bytesRead = await sourceStream.ReadAsync(buffer, cancellationToken);
+ bytesRead = await timeoutStream.ReadAsync(buffer, cancellationToken);
if (bytesRead == 0)
break;
status = _smbFileStore.WriteFile(out numberOfBytesWritten, fileHandle, offset, buffer.Take(bytesRead).ToArray());
diff --git a/Duplicati/Library/Backend/CIFS/Strings.cs b/Duplicati/Library/Backend/CIFS/Strings.cs
index 53699aa9a..2457dcf9d 100644
--- a/Duplicati/Library/Backend/CIFS/Strings.cs
+++ b/Duplicati/Library/Backend/CIFS/Strings.cs
@@ -26,10 +26,6 @@ namespace Duplicati.Library.Backend.Strings
internal static class CIFSBackend
{
- public static string DescriptionAuthPasswordLong => LC.L(@"The password used to connect to the server. This may also be supplied as the environment variable ""AUTH_PASSWORD"".");
- public static string DescriptionAuthPasswordShort => LC.L(@"Supply the password used to connect to the server");
- public static string DescriptionAuthUsernameLong => LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME"".");
- public static string DescriptionAuthUsernameShort => LC.L(@"Supply the username used to connect to the server");
public static string DescriptionAuthDomainLong => LC.L(@"The domain used to connect to the server. This may also be supplied as the environment variable ""AUTH_DOMAIN"".");
public static string DescriptionAuthDomainShort => LC.L(@"Supply the domain used to connect to the server");
public static string Description => LC.L(@"This backend can read and write data to CIFS/SMB destinations. Allowed format is ""cifs://server/share"".");
diff --git a/Duplicati/Library/SourceProvider/Builtin/BackendSourceFileEntry.cs b/Duplicati/Library/SourceProvider/Builtin/BackendSourceFileEntry.cs
index b9e134014..da1a31ef0 100644
--- a/Duplicati/Library/SourceProvider/Builtin/BackendSourceFileEntry.cs
+++ b/Duplicati/Library/SourceProvider/Builtin/BackendSourceFileEntry.cs
@@ -19,6 +19,7 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
+using System.Diagnostics.CodeAnalysis;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using Duplicati.Library.Common.IO;
@@ -322,7 +323,8 @@ public class BackendSourceFileEntry(BackendSourceProvider parent, string path, b
///
/// The path to normalize
/// The normalized path
- public static string NormalizePathTo(string path, char separator)
+ [return: NotNullIfNotNull("path")]
+ public static string? NormalizePathTo(string? path, char separator)
{
if (string.IsNullOrEmpty(path))
return path;