How to resolve/compare Firmlinks?

In the WWDC discussion on Firmlinks, it says they are "Not expected to be noticed by a user or an application".
I don't see how creating multiple aliases to a folder, and providing no way for developer to resolve them to for the purposes of comparison, is expected *not* to cause serious headaches.
Consider lower level calls like LSGetApplicationForInfo, which may not return a "firmlinked" response vs. an NSOpenPanel result, which does seem to return a "firmlinked" value.
I've prepared a simple method here for testing, so you can see the challenge. Stripping off the firmlinked prefix is a possibility too, but it's clunky.

+ (NSURL *)exampleNotWorking
{
    NSURL *defaultUrl = [NSURL fileURLWithPath:@"/Applications/Notes.app"];
    NSURL *defaultUrltest1 = [defaultUrl URLByResolvingSymlinksInPath];
    NSURL *defaultUrltest2 = [defaultUrl URLByStandardizingPath];

    NSLog(@"Default application set to: %@", defaultUrl.path);
    NSLog(@"Attempts to standardize path: %@, %@", defaultUrltest1.path, defaultUrltest2.path);

    NSOpenPanel *panel = [NSOpenPanel openPanel];
    [panel setTitle:NSLocalizedString(@"Choose Application", nil)];
    [panel setPrompt:NSLocalizedString(@"Select", nil)];

    [panel setAllowedFileTypes:@[ @"app" ]];
    [panel setDirectoryURL:defaultUrl];
    [panel setCanChooseFiles:YES];
    [panel setCanChooseDirectories:NO];
    [panel setAllowsMultipleSelection:NO];

    if ([panel runModal] == NSModalResponseOK)
    {
        NSURL *result = [[panel URLs] firstObject];

        NSURL *resultTest1 = [result URLByResolvingSymlinksInPath];
        NSURL *resultTest2 = [result URLByStandardizingPath];

        NSLog(@"Selection from NSOpenPanel: %@", result.path);
        NSLog(@"Attempts to standardize path: %@, %@", resultTest1.path, resultTest2.path);

        // how to compare result with defaultUrl??
        // even test1, test2 don't behave properly (they remove /private, but not /System... etc.)

        return result;
    }

    return nil;
}

Replies

Hello dxdc,


I think you can use fcntl() with the F_GETPATH and F_GETPATH_NOFIRMLINK options. I wrote a test app that compares 3 calls on 3 different paths.


Path: /Users/crdaviso

fcntl(F_GETPATH): /Users/crdaviso

fcntl(F_GETPATH_NOFIRMLINK): /System/Volumes/Data/Users/crdaviso

realpath(): /Users/crdaviso


Path: /System/Volumes/Data/Users/crdaviso

fcntl(F_GETPATH): /Users/crdaviso

fcntl(F_GETPATH_NOFIRMLINK): /System/Volumes/Data/Users/crdaviso

realpath(): /System/Volumes/Data/Users/crdaviso


Path: .

fcntl(F_GETPATH): /Users/crdaviso

fcntl(F_GETPATH_NOFIRMLINK): /System/Volumes/Data/Users/crdaviso

realpath(): /Users/crdaviso

I know that in Finder, /Applications seems to be a union of /Applications and /System/Applications, but I don't think it's a firmlink, exactly. In the Terminal:


$ ls /Applications/Notes.app

ls: /Applications/Notes.app: No such file or directory

Wow, thanks! I didn't see this @crdaviso. That's very interesting. Is F_GETPATH_NOFIRMLINK documented somewhere??
You're proposing some version of this? Do you know if it works in a sandboxed environment?
https://stackoverflow.com/questions/18341086/dont-lose-file-in-objective-c

Yes, I've noticed this too... not sure exactly what to make of it yet.

Hello @dxdc. F_GETPATH_NOFIRMLINK only seems to be documented in `man fcntl`.


I'm not sure I see the connection to the stackoverflow question you pasted, but it's true that fcntl only works on open files.


So, to compare two paths, you would have to open them (possibly with some lightweight mode like O_EVTONLY), run fcntl to find the full, prefixed path, and then close the files. Of course, this wouldn't work on really long (> MAXPATHLEN bytes) paths.