How do I ask for permission (from C) to scan a user's Documents Folder on Catalina

Hello all,


I have a large application written in C that needs to be able to scan the contents of the user's Documents (and other) folder(s). I'm aware that this permision mechanism has changed in Catalina, but I'm totally unable to get it to work.


The underlying symptom is that opendir fails and errno is EPERM.


Note, that I CAN read and write files in ~/Documents, and I'm able to do that without ever having prompted the user.

Note also, that the bash "find" command, when called via Terminal, CAN scan the directory, AFTER it has prompted the user, (and I guess it uses a similar opendir/readdir mechanism),


I've tried a number of things:


NSDocumentsFolderUsageDescription

  • I've added the following to Info.plist

<key>NSDocumentsFolderUsageDescription</key>

<string>application needs Documents access</string>

to make sure that the bundle is properly notarized.


None of these things allow opendir to succeed (and none them result in a dialog containging "<app> Would like to access files in your Documents folder").


Is there an API I should be calling to provoke the dialog and to grant the requred permissions?


Many Thanks,

John.

Replies

I have a large application written in C

I’d like to start by clarifying the above. It’s not possible to write an macOS app in C. You will need at least some Objective-C (or Swift) for the outer shell. So I suspect that either:

  • Your app has a C core with an Objective-C outer shell

  • You’re not talking about a GUI app, but instead using the term “application” in a more general sense

So, is this a GUI app? Or something else, like a command-line tool, that’s not a GUI app but is an application in the general sense?

In terms of GUI apps, there should be nothing to stop you from enumerating the Documents directory using C APIs. I tried this here in my office today and it worked just fine. Here’s what I did:

  1. Using Xcode 11.2 on macOS 10.15, I created a new app from the macOS > App template, selecting Objective-C for the language and Storyboard for the user interface.

  2. In the Signing & Capabilities editor, I removed the App Sandbox slice. I’m presuming that your app is not sandboxed. If it is, that’s a whole different kettle of fish.

  3. I added an

    NSDocumentsFolderUsageDescription
    entry to the
    Info.plist
    .
  4. I added the following method to the

    ViewController
    class:
    - (IBAction)testAction:(id)sender {
        #pragma unused(sender)
        NSURL * docDirURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:NULL];
        assert(docDirURL != nil);
        DIR * dir = opendir(docDirURL.fileSystemRepresentation);
        do {
            struct dirent * ent = readdir(dir);
            if (ent == NULL) {
                break;
            }
            NSLog(@"%s\n", ent->d_name);
        } while (1);
        int err = closedir(dir);
        assert(err == 0);
    }

    .

  5. In the storyboard, I added a button and wired it up to that method.

  6. I ran the app and clicked my button. The system put up the expected approval alert and, once I approved the access, the app printed a listing of the Documents directory.

Share and Enjoy

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

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

Hello Eskimo, Thanks for responding.


As you suspected, the configuration is more complicated than I implied in my first post - perhaps I oversimplified.


The "failing" process is a tty application (called "dyalog") that can run in a number of ways, including as an interactive session in a tty/Terminal window, or as a TCPIP server, communicating with a parent GUI Application (called "Dyalog 18.0")


When running interactively inside Termimal, and "dyalog" calls opendir, Terminal pops up with "Teminal eould like to access files in your Documents folder"). I can click "OK" and the call succeeds.


The GUI application (Dyalog 18.0) is developed using electron and packaged using electron-packager. I've been able to notarize the .pkg using the process described in the link in my first message


When the GUI application ("Dyalog 18.0") is launched it:

  • starts the TCP server using the “net” module of electron
  • spawns a "dyalog" process (via a bash shell) using the “child_process” module in electron. The "dyalog" process then connects back to the GUI app via TCP/IP.

  • So, there is a deeper process hierarchy: (There's supposed to be an image here, but it appears to have disappered from the post).



    I've drag-dropped ALL of the processes between "Dyalog 18.0" and "dyalog" (inclusive) to "Full Disk Access" in "Security & Privacy" but I still can't get opendir to succeed in the "dyalog" process.


    There doesn't appear to be a way (as a user) to add Applications to the "Files and Folders" section of "Security & Privacy", (the + button is permanently inactive).


    I've added NSDocumentsFolderUsageDescription to the info.plist for "Dyalog 18.0" (is it OK to do that AFTER installation, or does it need to be in the .pkg'd version of info.plist?), and that hasn't helped.



    Many Thanks.

    John.

    Oi vey!

    There’s two potential gotchas here:

    • macOS has a complex subsystem to track process responsibility, that is, the user-visible process that’s responsible for various background actions.

    • There’s also complex logic to determine whether a process is allowed to display UI.

    It’s likely that something being done by these various Electron modules is confusing one or both of these subsystems. It’s hard to offer any concrete advice on that topic because I’ve no idea what they’re doing behind the scenes. My best suggestion is to start with your known broken state and remove complexity until things start working, and then investigate what’s going at that inflexion point.

    Share and Enjoy

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

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

    Thanks for the process. It run for me.

    Is it possible to deny dialogs and return with EACCESS error immediately?

    Is it possible to deny dialogs and return with EACCESS error immediately?

    Not explicitly, although it can happen if the system gets confused about process responsibility.

    Note You’ll get EPERM not EACCESS. In this context, you get EACCESS if the request fails due to file system permissions (rwx and ACLs) and EPERM if the request fails due to mandatory access control (App Sandbox and TCC).

    Share and Enjoy

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

    WWDC runs Mon, 22 Jun through to Fri, 26 Jun. During that time all of DTS will be busy with conference duties.
    I will provide a little more details:

    I have primary UI-thread that displays disk-data and a background thread that runs opendir()/readdir() to populate the data. When background thread tries to access |~/Downloads, ~/Documents, ../Calendar, etc| opendir blocks background thread from doing its stuff. Rather than displaying dialog I would like function to return with error.

    Rather than displaying dialog I would like function to return with error.

    Fair enough. Alas, there’s no way to request that. My recommendation is that you file an enhancement request explaining your requirements. Please post your bug number, just for the record.

    Share and Enjoy

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