how to mount a network share in Swift

I've tried multiple variations of the below but nothing seem to work. The output is always

Error: sharePath blabla Not Valid


For some reason NetFSMountURLSync is not working in Swift 4.


import Cocoa

import NetFS

@NSApplicationMain

class AppDelegate: NSObject, NSApplicationDelegate {



var fileSystemProtocol: String = "smb"

var logServer: String = "smb://myservername"

var logShareName: String = "/Scripts"

var userName: String = ""

var password: String = ""



func applicationDidFinishLaunching(_ aNotification: Notification) {

mountShare(serverAddress: logServer, shareName: logShareName, userName: userName, password: password)

}


func applicationWillTerminate(_ aNotification: Notification) {

}


func mountShare( serverAddress: String, shareName: String, userName: String, password: String) {

let fm = FileManager.default

let mountPoint = "/Volumes/".appending(shareName)

var isDir : ObjCBool = false

if fm.fileExists(atPath: mountPoint, isDirectory:&isDir) {

if isDir.boolValue {

unmount(mountPoint, 0)

print("unmount \(mountPoint)")

}

}

let sharePath = NSURL(string: "\(serverAddress)\(shareName)")!

let mounted: Int32 = NetFSMountURLSync(sharePath, nil, nil, nil, nil, nil, nil)

if mounted > 0 {

print("Error: sharePath: \(sharePath) Not Valid")

} else {

print("Mounted: \(sharePath)")

}

}

}

Unless it's one of the multiple variations you've already tried, instead of:

  let mountPoint = "/Volumes/".appending(shareName)


Try:

  let mountPoint = "/Volumes/".stringByAppendingString(shareName)

a. In your example code, this line:


        let mountPoint = "/Volumes/".appending(shareName)// shareName is "/Scripts"


is going to produce "/Volumes//Scripts", which doesn't look like anything you'd want.


b. After getting an error from the mount:


        let mounted: Int32 = NetFSMountURLSync(sharePath, nil, nil, nil, nil, nil, nil)


why on earth would you not log the returned OSStatus code? That is presumably going to tell you what failed.

What is the context of this app? Is it running in the sandbox? If so, I doubt that you would have access to /Volumes.

Is this just a demo or proof-of-concept app? You would never, ever want to call NetFSMountURLSync. Call NetFSMountURLAsync instead.

Speaking of asyncronous operations, you should assume that unmount will take a non-trivial time to complete and will fail a significant part of the time.


This particular function is extremely delicate.

Sorry, I am new to Xcode and Swift. OSStatus returns 0. Interstingly, after I downloaded Xcode 8.3 and compiled, it ran just fine and mounted the share. So not sure why it's not running correctly under Xcode 9. I will take a closer look at NetFSMountURLAsync.

>> OSStatus returns 0.


According to the header file for this API (Command-click on "NetFSMountURLSync" in your source code; if a menu pops up, choose "Jump to Definition"; then scroll up a bit to read the comments):


* If the return value is zero the mount has succeeded.
*
* A positive non-zero return value represents an errno value
* (see /usr/include/sys/errno.h).  For instance, a missing mountpoint
* error will be returned as ENOENT (2).
*
* A negative non-zero return value represents an OSStatus error.


So your "mount > 0" test seems to be wrong.

Oops, I mean, either there was no error, so your claim that it always prints "Error: sharePath blabla Not Valid" is wrong, or there was an error, so your claim that the result was 0 is wrong.


Either way, the "mounted > 0" ought to be "mounted != 0", right?

Just a word to the wise. If you ask for help on an internet forum, and someone asks you really, really specific questions, it is nice to at least try to answer them.


At this point, all I can say is that you are trying to execute a method that is particularly noteable for its complexity and deep hooks into different parts of the operating system. You tried the demo, sample-code version and it returned a zero result. Then you tried it again under some different configuration that at least consists of a new development toolchain. Chances are, there were more changes than just upgrading Xcode. I'm guessing you did an OS upgrade too. Does this function even work at all in the new OS? I can tell you from experience that this function has a history of breaking with OS updates. Can you even perform this mount manually in the Finder and from the command line? That is an important plot point. But, of couse, I'm just guessing, because guesses are all I have.

Lots of folks have posted lots of good suggestions here, but I wanted to add a few other things:

  • NetFSMountURLSync
    takes a URL. I strongly recommend against constructing URLs by hand, but rather you should construct them using
    URLComponents
    . For example:
    var uc = URLComponents()
    uc.scheme = "smb"
    uc.host = "fluffy.local"
    uc.path = "/Fluffy Storage"
    let url = uc.url!
    print(url)  // -> smb://fluffy.local/Fluffy%20Storage

    This takes care of a bunch of fiddly details, most notably percent encoding.

  • Once you have a

    URL
    you can pass it to
    NetFSMountURLSync
    (which takes a
    CFURL!
    ) by converting it to an
    NSURL
    .
    let result = NetFSMountURLSync(url as NSURL, …)

    .

  • Unless you’re doing something weird, it is much better to let NetFS choose the mount point (by passing

    nil
    to the
    mountpath
    parameter) rather than trying to construct your own mount point. Doing the latter is an exercise in much complexity.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Unless you’re doing something weird, it is much better to let NetFS choose the mount point (by passing

nil
to the
mountpath
parameter) rather than trying to construct your own mount point. Doing the latter is an exercise in much complexity.



Unfortunately this isn't the case in a Sandboxed app - a nil mount point will cause the mount to fail. Is there a way around this?

Calling that function from the sandbox is really weird. It might still work since I used it, but you have to jump through some hoops to make it useable.

a nil mount point will cause the mount to fail. Is there a way around this?

Have you tried passing in the path of a directory within your app’s container?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Yes - this is how we mount the shares at the moment, but it's a pretty terrible user experience if they expect it to be mounted in /Volumes.


I find it strange that Sandboxed apps cannot mount into /Volumes. NetFS already handles the creation of the mount point in /Volumes for a non-sandboxed app (abstracting having to sudo up and create a mount point). Why should a sandboxed app be any different?


At the moment it's actually less secure, as a Sandboxed app by default can access any file within a mounted share inside its container, without requesting access from the user. Surely it would be best to stick to the standards macOS has upheld for years, and simply mount into /Volumes.

Yes - this is how we mount the shares at the moment …

Cool. It’s good to know that I’m not totally off in the weeds.

I find it strange that Sandboxed apps cannot mount into /Volumes.

I tend to agree with you. My recommendation is that you make your case with the App Sandbox team by filing an enhancement request explaining your use case.

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Quinn!


Bug number here: 45795638

Unfortunately Apple will not be resolving this problem, so I guess it will remain to be an issue.

Mapping to an SMB file share is something I'm working on now. I understand from other threads that instead of specifying a mount path of "/Volumes/mountdir", which for some reason is not allowed, you can use "/../Volumes/mountdir" which is both not the same (it is allowed) and the same..(it does what you wanted to do originally)

Mapping to an SMB file share is something I'm working on now. I understand from other threads that instead of specifying a mount path of "/Volumes/mountdir", which for some reason is not allowed, you can use "/../Volumes/mountdir" which is both not the same (it is allowed) and the same…(it does what you wanted to do originally).

Don't do this. The issue here isn't actually whether or not you can specify a mount point in "/Volumes/", it's race conditions. Volume names aren't unique, so if two volumes with the same name attempt to mount at the same time, one mount will "win" (mounting at the expected point) and the other will "lose", failing the mount.

At that point, you'd then need to attempt the mount again with a new mount point like:

"/Volumes/mountdir 1"

...but it's entirely possible that this will ALSO fail, forcing you to repeat that entire process again.

...And, of course, it's also possible that a volume is ALREADY mounted at "mountdir", so you really should check for its existence before you use that mount point.

...And, of course, it's also possible that "mountdir" is unmounting after your check above, but before your mount, so now you end up mounting in "/Volumes/mountdir 1" even though "/Volumes/mountdir" no longer exists.

Now, you could deal with all of those issues... or you could just pass in "NULL" as the mountpath, let the system deal with all the issues above, and then use whatever path it actually ends up mounting at, which the API helpfully returns in the "mountpoints" argument.

That leads to my final point which is that, in my experience, code that makes ANY assumptions about the mount point name in "/Volumes/" is VERY likely to be broken. As designed, the system itself should not (and generally doesn’t) make ANY assumptions about the relationship between these two things:

  1. "The Volume” -> meaning "the logical storage object users interact with".

  2. "The mount point” -> meaning "the file system path the system happened to attach #1 at".

macOS has a longstanding convention that where we use the volume name (from #1) as the initial mountpoint name, then append a number as needed to generate a unique path if/when a collision occurs.

However, the critical word there is "convention". NOTHING actually requires the system to work this way and, in fact, iOS does NOT work this way. It simply generates a UUID for each new mount and uses that instead. Aside from the confusion it would create for command line users, there's no reason macOS couldn't work exactly the same way. I really do mean that quite literally—if you mount the volume "foo" at "/Volumes/bar", the Finder will show the volume’s name as "foo" because that's what the volume’s name actually is. It does not care what path the volume mounted out and never has.

That's the basic approach all apps should be using. That is, the volume path should be treated as an opaque value and APIs like "NSURLVolumeNameKey", "NSURLLocalizedNameKey", displayName(atPath:), and componentsToDisplay(forPath:) should be used to retrieve user presentation names for those objects.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

how to mount a network share in Swift
 
 
Q