Skip to content

Commit

Permalink
feat: Support Linux, Android and BSD
Browse files Browse the repository at this point in the history
!fix: Swift 6 compile error by removing/redefining Stream methods
  • Loading branch information
amosavian committed Oct 14, 2024
1 parent f1ea3b0 commit 1b64d4b
Show file tree
Hide file tree
Showing 14 changed files with 743 additions and 227 deletions.
124 changes: 43 additions & 81 deletions AMSMB2/AMSMB2.swift

Large diffs are not rendered by default.

52 changes: 10 additions & 42 deletions AMSMB2/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
import Foundation
import SMB2

#if !canImport(Darwin)
let USEC_PER_SEC = 1_000_000
let NSEC_PER_SEC = 1_000_000_000
#endif

extension Optional {
func unwrap() throws -> Wrapped {
guard let self = self else {
Expand Down Expand Up @@ -133,8 +138,8 @@ extension Dictionary where Key == URLResourceKey {
}
}

extension Array where Element == [URLResourceKey: Any] {
func sortedByPath(_ comparison: ComparisonResult) -> [[URLResourceKey: Any]] {
extension Array where Element == [URLResourceKey: any Sendable] {
func sortedByPath(_ comparison: ComparisonResult) -> [[URLResourceKey: any Sendable]] {
sorted {
guard let firstPath = $0.path, let secPath = $1.path else {
return false
Expand Down Expand Up @@ -243,45 +248,6 @@ extension String {
}
}

extension Stream {
func withOpenStream(_ handler: () throws -> Void) rethrows {
let shouldCloseStream = streamStatus == .notOpen
if streamStatus == .notOpen {
open()
}
defer {
if shouldCloseStream {
close()
}
}
try handler()
}
}

extension InputStream {
func readData(maxLength length: Int) throws -> Data {
var buffer = [UInt8](repeating: 0, count: length)
let result = read(&buffer, maxLength: buffer.count)
if result < 0 {
throw streamError ?? POSIXError(.EIO, description: "Unknown stream error.")
} else {
return Data(buffer.prefix(result))
}
}
}

extension OutputStream {
func write<DataType: DataProtocol>(_ data: DataType) throws -> Int {
var buffer = Array(data)
let result = write(&buffer, maxLength: buffer.count)
if result < 0 {
throw streamError ?? POSIXError(.EIO, description: "Unknown stream error.")
} else {
return result
}
}
}

func asyncHandler(_ continuation: CheckedContinuation<Void, any Error>) -> @Sendable (_ error: (any Error)?) -> Void {
{ error in
if let error = error {
Expand All @@ -292,7 +258,9 @@ func asyncHandler(_ continuation: CheckedContinuation<Void, any Error>) -> @Send
}
}

func asyncHandler<T>(_ continuation: CheckedContinuation<T, any Error>) -> @Sendable (Result<T, any Error>) -> Void {
func asyncHandler<T>(_ continuation: CheckedContinuation<T, any Error>)
-> @Sendable (Result<T, any Error>) -> Void where T: Sendable
{
{ result in
continuation.resume(with: result)
}
Expand Down
100 changes: 48 additions & 52 deletions AMSMB2/FileHandle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,37 @@ import SMB2

typealias smb2fh = OpaquePointer

#if os(Linux) || os(Android) || os(OpenBSD)
let O_SYMLINK: Int32 = O_NOFOLLOW
#endif

final class SMB2FileHandle {
struct SeekWhence: RawRepresentable {
struct SeekWhence: RawRepresentable, Sendable {
var rawValue: Int32

static let set = SeekWhence(rawValue: SEEK_SET)
static let current = SeekWhence(rawValue: SEEK_CUR)
static let end = SeekWhence(rawValue: SEEK_END)
}

struct LockOperation: OptionSet, Sendable {
var rawValue: Int32

static let shared = LockOperation(rawValue: LOCK_SH)
static let exclusive = LockOperation(rawValue: LOCK_EX)
static let unlock = LockOperation(rawValue: LOCK_UN)
static let nonBlocking = LockOperation(rawValue: LOCK_NB)

var smb2Flag: UInt32 {
var result: UInt32 = 0
if contains(.shared) { result |= 0x0000_0001 }
if contains(.exclusive) { result |= 0x0000_0002 }
if contains(.unlock) { result |= 0x0000_0004 }
if contains(.nonBlocking) { result |= 0x0000_0010 }
return result
}
}

struct Attributes: OptionSet, Sendable {
var rawValue: UInt32

Expand Down Expand Up @@ -49,54 +71,6 @@ final class SMB2FileHandle {
static let noScrubData = Self(rawValue: SMB2_FILE_ATTRIBUTE_NO_SCRUB_DATA)
}

struct ChangeNotifyFilter: OptionSet {
var rawValue: UInt32

init(rawValue: UInt32) {
self.rawValue = rawValue
}

init(rawValue: Int32) {
self.rawValue = .init(bitPattern: rawValue)
}

// The client is notified if a file-name changes.
static let fileName: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_FILE_NAME)

// The client is notified if a directory name changes.
static let directoryName: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_DIR_NAME)

// The client is notified if a file's attributes change.
static let attributes: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_ATTRIBUTES)

// The client is notified if a file's size changes.
static let size: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_SIZE)

// The client is notified if the last write time of a file changes.
static let lastWriteDate: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_LAST_WRITE)

// The client is notified if the last access time of a file changes.
static let lastAccessDate: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_LAST_ACCESS)

// The client is notified if the creation time of a file changes.
static let creationDate: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_CREATION)

// The client is notified if a file's extended attributes (EAs) change.
static let extendedAttributes: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_EA)

// The client is notified of a file's access control list (ACL) settings change.
static let security: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_SECURITY)

// The client is notified if a named stream is added to a file.
static let streamName: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_STREAM_NAME)

// The client is notified if the size of a named stream is changed.
static let streamSize: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_STREAM_SIZE)

// The client is notified if a named stream is modified.
static let streamWrite: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_STREAM_WRITE)
}

private var context: SMB2Context
private var handle: smb2fh?

Expand Down Expand Up @@ -397,14 +371,36 @@ final class SMB2FileHandle {
}
}

func changeNotify(watchTree: Bool, filter: ChangeNotifyFilter) throws {
func flock(_ op: LockOperation) throws {
let handle = try handle.unwrap()
try context.async_await_pdu { context, dataPtr in
var element = smb2_lock_element(
offset: 0,
length: 0,
flags: op.smb2Flag,
reserved: 0
)
return withUnsafeMutablePointer(to: &element) { element in
var request = smb2_lock_request(
lock_count: 1,
lock_sequence_number: 0,
lock_sequence_index: 0,
file_id: smb2_get_file_id(handle).pointee,
locks: element
)
return smb2_cmd_lock_async(context, &request, SMB2Context.generic_handler, dataPtr)
}
}
}

func changeNotify(for type: SMB2FileChangeType) throws {
let handle = try handle.unwrap()
try context.async_await_pdu { context, cbPtr in
var request = smb2_change_notify_request(
flags: UInt16(watchTree ? SMB2_CHANGE_NOTIFY_WATCH_TREE : 0),
flags: UInt16(!type.intersection([.recursive]).isEmpty ? SMB2_CHANGE_NOTIFY_WATCH_TREE : 0),
output_buffer_length: 0,
file_id: smb2_get_file_id(handle).pointee,
completion_filter: filter.rawValue
completion_filter: type.rawValue & 0x00ff_ffff
)
return smb2_cmd_change_notify_async(context, &request, SMB2Context.generic_handler, cbPtr)
}
Expand Down
65 changes: 65 additions & 0 deletions AMSMB2/FileMonitoring.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// FileMonitoring.swift
// AMSMB2
//
// Created by Amir Abbas on 10/14/24.
// Copyright © 2024 Mousavian. Distributed under MIT license.
// All rights reserved.
//

import SMB2

/// Change notifcation filter.
struct SMB2FileChangeType: OptionSet, Sendable {
public var rawValue: UInt32

public init(rawValue: UInt32) {
self.rawValue = rawValue
}

@_disfavoredOverload
public init(rawValue: Int32) {
self.rawValue = .init(bitPattern: rawValue)
}

/// The client is notified if a file-name changes.
public static let fileName: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_FILE_NAME)

/// The client is notified if a directory name changes.
public static let directoryName: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_DIR_NAME)

/// The client is notified if a file's attributes change.
public static let attributes: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_ATTRIBUTES)

/// The client is notified if a file's size changes.
public static let size: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_SIZE)

/// The client is notified if the last write time of a file changes.
public static let write: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_LAST_WRITE)

/// The client is notified if the last access time of a file changes.
public static let access: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_LAST_ACCESS)

/// The client is notified if the creation time of a file changes.
public static let create: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_CREATION)

/// The client is notified if a file's extended attributes (EAs) change.
public static let extendedAttributes: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_EA)

/// The client is notified of a file's access control list (ACL) settings change.
static let security: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_SECURITY)

/// The client is notified if a named stream is added to a file.
static let streamName: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_STREAM_NAME)

/// The client is notified if the size of a named stream is changed.
static let streamSize: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_STREAM_SIZE)

/// The client is notified if a named stream is modified.
static let streamWrite: Self = .init(rawValue: SMB2_CHANGE_NOTIIFY_FILE_NOTIFY_CHANGE_STREAM_WRITE)

public static let recursive: Self = .init(rawValue: 0x8000_0000)

/// The client is notified if last write time, creation time or size of a file modified.
public static let contentModify: Self = [.create, .write, .size]
}
12 changes: 6 additions & 6 deletions AMSMB2/Fsctl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ extension IOCtl {

init(data: Data) throws {
guard data.count >= 24 else {
throw POSIXError(.ENODATA)
throw POSIXError(.ENODATA, userInfo: [:])
}
self.resumeKey = data.prefix(24)
}
Expand Down Expand Up @@ -176,12 +176,12 @@ extension IOCtl {

init(data: Data) throws {
guard data.count >= Self.headerLength else {
throw POSIXError(.EINVAL)
throw POSIXError(.EINVAL, userInfo: [:])
}
self.reparseTag = data.scanValue(offset: 0, as: UInt32.self) ?? SMB2_REPARSE_TAG_SYMLINK

let count = try data.scanInt(offset: 4, as: UInt16.self).unwrap()
guard count + 8 == data.count else { throw POSIXError(.EINVAL) }
guard count + 8 == data.count else { throw POSIXError(.EINVAL, userInfo: [:]) }
self.buffer = data.dropFirst(Int(Self.headerLength))
}
}
Expand All @@ -204,10 +204,10 @@ extension IOCtl {

init(data: Data) throws {
guard data.scanValue(offset: 0, as: UInt32.self) == reparseTag else {
throw POSIXError(.EINVAL)
throw POSIXError(.EINVAL, userInfo: [:])
}
let count = try data.scanInt(offset: 4, as: UInt16.self).unwrap()
guard count + 8 == data.count else { throw POSIXError(.EINVAL) }
guard count + 8 == data.count else { throw POSIXError(.EINVAL, userInfo: [:]) }

let substituteOffset = try data.scanInt(offset: 8, as: UInt16.self).unwrap()
let substituteLen = try data.scanInt(offset: 10, as: UInt16.self).unwrap()
Expand Down Expand Up @@ -291,7 +291,7 @@ extension IOCtl {

init(data: Data) throws {
guard data.scanValue(offset: 0, as: UInt32.self) == reparseTag else {
throw POSIXError(.EINVAL)
throw POSIXError(.EINVAL, userInfo: [:])
}

let substituteOffset = try data.scanInt(offset: 8, as: UInt16.self).unwrap()
Expand Down
4 changes: 2 additions & 2 deletions AMSMB2/MSRPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ enum MSRPC {

offset += 12
if offset + nameActualCount * 2 > data.count {
throw POSIXError(.EBADRPC)
throw POSIXError(.EINVAL, userInfo: [:])
}

// Getting utf16le data, omitting nul char
Expand All @@ -81,7 +81,7 @@ enum MSRPC {

offset += 12
if offset + commentActualCount * 2 > data.count {
throw POSIXError(.EBADRPC)
throw POSIXError(.EINVAL, userInfo: [:])
}

// Getting utf16le data, omitting nul char
Expand Down
Loading

0 comments on commit 1b64d4b

Please sign in to comment.