FileManager.default.contentsOfDirectory fails with SMB

Hi,

I am trying to list the contents of a directory selected by the user using the following code:

Code Block
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.folder])
documentPicker.delegate = self
self.present(documentPicker, animated: true)


Code Block
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        guard let folderUrl = urls.first else {return}
let startAccessing = folderUrl.startAccessingSecurityScopedResource()
        guard startAccessing else {
print("ERROR")
            return
        }
        defer {
            if startAccessing {
                folderUrl.stopAccessingSecurityScopedResource()
            }
        }
        var error : NSError? = nil
        NSFileCoordinator().coordinate(readingItemAt: folderUrl, error: &error) { (url) in
            let startAccessing = url.startAccessingSecurityScopedResource()
            guard startAccessing else {
                print("ERROR 2")
                return
            }
            defer {
                if startAccessing {
folderUrl.stopAccessingSecurityScopedResource()
                }
            }
            
            do {
                let files = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: [.nameKey])
                print(files)
            } catch let filesError {
                print(filesError)
            }
        }
    }


This works for iCloud and for local files. It also, curiously, works on empty folders on the SMB server (though, obviously, it returns an empty array).

However, for SMB folders with contents I get the following cryptic error message.

Code Block
Error Domain=NSCocoaErrorDomain Code=256 "The file “downloads” couldn’t be opened." UserInfo={NSURL=file:///private/var/mobile/Library/LiveFiles/com.apple.filesystems.smbclientd/Gabgugfiles/downloads, NSFilePath=/private/var/mobile/Library/LiveFiles/com.apple.filesystems.smbclientd/Gabgugfiles/downloads, NSUnderlyingError=0x28010b0c0 {Error Domain=NSPOSIXErrorDomain Code=10006 "Unknown error: 10006"}}


In addition, modifying the code slightly so the user picks an image instead works just fine too. I can load the image from the supplied URL.

I have read the documentation here and watched the WWDC 2019 presentation, What's New in File Management and Quicklook, where this was introduced but this hasn't turned up any answers.

My app is set up with Supports Document Browser. Is there something I'm doing wrong? The way this is described what I'm trying to do, get a list of files and act on each of them, is exactly what this is intended for yet it's not working.



Some updates. I have tried this with a share from my Mac and my Windows PC and it works, it's only not working with my Synology DiskStation.

I have full access to this, I can browse and do whatever I like using the Files app. I can open and work with files on it within my app's code, all I can't do is get FileManager to list the contents of a user selected folder.
I recommend that you file a bug about this. The fact that this only fails on specific servers suggests something wonky’s going on at the SMB level, and that’s not something you have any control over.

Please post your bug number, just for the record.

Beyond that, two things:
  • Try passing nil to the includingPropertiesForKeys. That gets you the default behaviour and it’s possible that your current non-default behaviour is provoking the bug.

  • Try using a lower-level directory iteration API. For example, does readdir work (see its man page)?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Hi, thanks for getting back to me.

Bug report filed here, number FB8902970.

I have tried passing nil to includingPropertiesForKeys, in fact that's how I had it initially before, as a guess, I tried narrowing the information I was asking for.

I am unclear as to how to use lower level APIs for accessing the file system on iOS. I would have thought that I'd be prevented from doing this by the app's sandbox. (Unfortunately, Googling this reveals that Google is keen to suggest that MacOS and iOS are the same thing)

number FB8902970.

Thanks.

I would have thought that I'd be prevented from doing this by the
app's sandbox.

No. All the FileManager calls you’re currently making are implemented in terms of lower-level BSD calls. There’s no reason you can’t call those yourself.

The only exception here is {start,}AccessingSecurityScopedResource calls, but you can do that and then make BSD calls in between.

Here’s a snippet you get you started:

Code Block
let dir = opendir("/Users/quinn")!
defer {
closedir(dir)
}
while let ent = readdir(dir) {
print(String(cString: &ent.pointee.d_name.0))
}


A real app would need some error checking (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"

A real app would need some error checking (-:

… although I’m not actually suggesting this as a workaround, at least not for the moment. Rather, I think it’d be an interesting diagnostic test.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
This is what I tried:

Code Block
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        guard let folderUrl = urls.first else {return}
        let startAccessing = folderUrl.startAccessingSecurityScopedResource()
        defer { if startAccessing {folderUrl.stopAccessingSecurityScopedResource()}}
        var error : NSError? = nil
        NSFileCoordinator().coordinate(readingItemAt: folderUrl, error: &error) { (url) in
            let rep = (url as NSURL).fileSystemRepresentation
            guard let dir = opendir(rep) else {return}
            defer { closedir(dir) }
            while let ent = readdir(dir) {
                print(String(cString: &ent.pointee.d_name.0))
            }
        }
    }


No difference. This works for the folders that worked with the higher level APIs (other SMB servers, iCloud, local files) but the while loop never starts with my DiskStation. It does, however, get past the opendir line with what appears to be a valid handle.

Oh, and no, I wouldn't use this as an actual solution either.

Thanks.
So what error do you get back?

btw Errors in BSD APIs are weird. See the readdir man page for details.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
The system errno is set to -10006. Creating an Errno object with this code gives the following description:

Code Block
The operation couldn’t be completed. (POSIX error 10006 - Unknown error: 10006)


The same code shows up with the higher level APIs.
To be clear, this is 10006 not -10006.

Getting that error back in the Posix error domain is weird. Posix errors, by definition, run from 1 up to ELAST (106) and this is far out of that range. My best guess is that this is coming from automount, which internally uses 10006 to indicate ‘server fault’. However, that’s little more than a guess.

Regardless, you can’t go any lower in the stack here, so it’s unlikely there’s an API-level workaround. The only thing you can do is file a bug (which you’ve done, FB8902970) so that iOS Engineering to investigate.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"

I'm having same trouble. I can't scan the contents of folder at QNAP NAS connected with SMB.

import SwiftUI

struct ContentView: View {
	@State private var showPicker = false
	@State private var files: [URL] = []

    var body: some View {

        VStack {
			Button("Open Folder...") { showPicker.toggle() }
				.font(.largeTitle)
				.padding(.bottom)

			if files.isEmpty {
				Text("EMPTY")
					.font(.largeTitle)
					.foregroundColor(.secondary)
					.frame(maxHeight: .infinity)
			} else {
				ScrollView {
					VStack {
						ForEach(files, id: \.self) { url in
							Text(url.lastPathComponent)
								.frame(maxWidth: .infinity, alignment: .leading)
						}
					}
					.padding(.leading)
				}
				.frame(maxHeight: .infinity, alignment: .topLeading)
			}
        }
        .padding()
		.fileImporter(isPresented: $showPicker, allowedContentTypes: [.folder]) { result in
			switch result {
			case .success(let url):
				scanFolder(url)
			case .failure(let error):
				print("[ERROR] \(error)")
			}
		}
   }

	private func scanFolder(_ folderURL: URL) {
		let secure = folderURL.startAccessingSecurityScopedResource()

		do {
			let contents = try FileManager.default.contentsOfDirectory(atPath: folderURL.path)
			files = contents.map { folderURL.appending(path: $0) }
		} catch {
			print("[ERROR] contentsOfDirectory \(error)")
		}

		if secure {
			folderURL.stopAccessingSecurityScopedResource()
		}
	}
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Also, Apple's Folder.app can't copy the folder in NAS to iCloud Drive. When I copy the folder in NAS and paste it in iCloud/On device, the empty folder is created. Of corse I have permission for accessing NAS. I can copy files from NAS to iCloud.

FileManager.default.contentsOfDirectory fails with SMB
 
 
Q