Skip to content
RuiNelsonPublic

About

Access SMB shares with libsmb2 using Swift

Topics

Resources

Stars

9 stars

Watchers

0 watching

Forks

Repository files navigation

SwiftSMB

License

iOS | macOS | visionOS | tvOS Linux Android

Swift Package Manager Release CI Linux Android

SwiftSMB Banner

SwiftSMB is a Swift Package Manager library for talking to SMB2 and SMB3 file shares from Swift. It wraps the proven libsmb2 client library in a Swift-first API with typed configuration, friendly path handling, file and directory handles, share discovery, metadata operations, and convenience helpers for common upload and download workflows.

Use it when your app or service needs to browse Windows, Samba, NAS, or other SMB-compatible shares without shelling out to system tools.

Cookbook

The examples below list shares and transfer local files through an SMB share. Every operation that talks to the server is async, so call them from an asynchronous context with try await:

import Foundation
import SwiftSMB

let server = SMB.Server(host: "RASPBERRYPI.local") // IP or hostname
let credentials = SMB.Credentials(user: "Anna", password: "1987")

Listing shares

listShares(server:credentials:...) connects to the server, asks it for its disk shares, and disconnects before returning:

let shares = try await SMB.listShares(
    server: server,
    credentials: credentials
)

for share in shares {
    print(share.name)
}

By default, hidden shares are filtered out. Pass includeHidden: true if you need to inspect them too.

Connecting to a share

Use one of the returned share names to open a connection:

let connection = try await SMB.connect(
    server: server,
    credentials: credentials,
    share: "Documents"
)
defer { try? await connection.disconnect() }

let serverID: UUID = try await connection.serverGUID

Operations on a connection run one at a time, in the order they were requested. Open separate connections to run operations in parallel.

Set a command timeout when connecting if you want libsmb2 to abort operations that take too long:

let connection = try await SMB.connect(
    server: server,
    credentials: credentials,
    share: "Documents",
    configuration: SMB.Configuration(timeout: 30)
)

Establishing the connection always has a deadline: the configured timeout when it is positive, otherwise 30 seconds.

You can also change the timeout for subsequent operations on an existing connection:

try await connection.setTimeout(10)
try await connection.setTimeout(0) // Disable command timeouts

Encryption has three explicit policies: .automatic advertises support and uses encryption when required by the server or share, .disabled suppresses encryption, and .required fails unless encryption is negotiated:

let configuration = SMB.Configuration(encryption: .required)

Listing a directory

listDirectory(at:) returns an array with the entries in a directory:

let entries = try await connection.listDirectory(at: "Anna/Inbox")

In this library, just like libsmb2 uses forward slash for separating directories. You don't need to add "/" to indicate the root of the file share.

uploadFile

uploadFile(local:remote:...) copies a local file to the connected share. It can create missing parent directories and, by default, stages the upload through a temporary remote file before renaming it into place:

let localURL = URL(fileURLWithPath: "/Users/Anna/Desktop/report.pdf")

try await connection.uploadFile(
    local: localURL,
    remote: "Anna/Inbox/report.pdf"
) { completed, total, lastBlockSpeed, averageSpeedSinceTheStartOfTheTransfer in
    let speed = 0.5 * lastBlockSpeed + 0.5 * averageSpeedSinceTheStartOfTheTransfer
    print("Uploaded \(completed) of \(total) bytes at \(round(speed/1000.0)) kB/s")
    return true
}

Return false from the progress closure to cancel the upload. Cancelling the task that runs the upload also stops it, and the call then throws CancellationError.

downloadFile

downloadFile(remote:local:...) copies a file from the connected share to local storage. The download is written to a temporary local file first and then moved into place after the transfer succeeds:

let localURL = URL(fileURLWithPath: "/Users/alice/Downloads/report.pdf")

try await connection.downloadFile(
    remote: "Anna/Inbox/report.pdf",
    local: localURL
) { completed, total, latestSpeed, averageSpeed in
    print("Downloaded \(completed) of \(total) bytes")
    return true
}

Return false from the progress closure to cancel the download. Cancelling the task that runs the download also stops it, and the call then throws CancellationError.

More advanced cookbooks

Longer examples belong in docs/:

Adding this package to your Project

Swift Package Manager (Package.swift)

Add SwiftSMB to your package dependencies:

dependencies: [
    .package(url: "https://github.com/RuiNelson/SwiftSMB.git", from: "3.0.0"),
]

Then add SwiftSMB to the target that uses it:

.target(
    name: "YourTarget",
    dependencies: [
        .product(name: "SwiftSMB", package: "SwiftSMB"),
    ]
)

SwiftSMB currently declares support for macOS 10.15.4+, iOS 13.4+, macCatalyst 13.4+, tvOS 13.4+, visionOS 1+, and watchOS 6.2+. It also builds for Linux and Android (aarch64, API 28+). The package uses Swift tools version 6.2.

Xcode Project

  1. Open your project in Xcode.
  2. Choose File -> Add Package Dependencies....
  3. Enter https://github.com/RuiNelson/SwiftSMB.git.
  4. Choose the version rule you want to use.
  5. Add the SwiftSMB product to your app or framework target.
  6. Import the library where you need it:
import SwiftSMB

Building

Command Line

Just enter:

swift build

Some tests are integration tests and expect the Docker-based Samba test server to be running:

docker ps --filter ancestor=swiftsmb-test-server
source TestServer/up.sh
swift test

Licensing

SwiftSMB is distributed under the Apache License, version 2.0. A copy of the license is included in LICENSE.

The bundled libsmb2 sources keep their own license, the GNU Lesser General Public License, version 2.1. If you distribute an app or product that links with SwiftSMB/libsmb2, make sure you preserve license notices, provide access to the LGPL-covered libsmb2 source, and allow users to replace or relink the LGPL-covered library as required by that license.

To help with that last requirement, Package.swift declares libsmb2 as a dynamic library product (.library(name: "libsmb2", type: .dynamic, ...)), so the LGPL-covered code is linked dynamically and ships as a separate, replaceable binary instead of being statically absorbed into your app.

This section is only a project summary, not legal advice. Review the Apache 2.0 and LGPL v2.1 terms for your distribution model.

About

Access SMB shares with libsmb2 using Swift

Topics

Resources

Stars

9 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages