Getting standard macOS directories

Created On:

macOS applications are supposed to store certain kinds of data in specific folders. From Apple’s File System Programming Guide the guide says:

Put data cache files in the Library/Caches/ directory.

Put app-created support files in the Library/Application Support/ directory.

It’s possible to hardcode these paths in an application but macOS provides APIs to programmatically discover these directories. Using these APIs can ensure an application is storing data in the “right place” no matter what.

For non Objective-C/Swift applications there are two APIs available from macOS. This post will show how to use them with Rust, but it’s applicable for any application linking against the macOS SDK.

NSSearchPathForDirectoriesInDomains

The NSSearchPathForDirectoriesInDomains is located in the Foundation framework. Typically Foundation contains Objective-C APIs but this function is unique. It’s declared in Foundation.framework/Versions/C/Headers/NSPathUtilities.h The declaration is:

FOUNDATION_EXPORT NSArray<NSString *> *NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);

Copied from NSPathUtilities.h

It’s a C function, but it returns Objective-C objects. Normally this would be very difficult to use without the Objective-C runtime, but macOS APIs have “toll free bridging” 1 between select Core Foundation and Foundation objects including NSArray and NSString. This means an application can simply cast the return type to a CFArray of CFString instead. So long as the caller deallocates the returned array, there should be no issues from this approach.

It’s important to note that the NSSearchPathDirectory and NSSearchPathDomainMask arguments are enums using NSUInteger.

typedef NS_ENUM(NSUInteger, NSSearchPathDirectory) {
    NSApplicationDirectory = 1,             // supported applications (Applications)
    NSDemoApplicationDirectory,             // unsupported applications, demonstration versions (Demos)
    NSDeveloperApplicationDirectory,        // developer applications (Developer/Applications). DEPRECATED - there is no one single Developer directory.
    NSAdminApplicationDirectory,            // system and network administration applications (Administration)
    NSLibraryDirectory,                     // various documentation, support, and configuration files, resources (Library)
    NSDeveloperDirectory,                   // developer resources (Developer) DEPRECATED - there is no one single Developer directory.
    NSUserDirectory,                        // user home directories (Users)
    NSDocumentationDirectory,               // documentation (Documentation)
    NSDocumentDirectory,                    // documents (Documents)
    NSCoreServiceDirectory,                 // location of CoreServices directory (System/Library/CoreServices)
    NSAutosavedInformationDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 11,   // location of autosaved documents (Documents/Autosaved)
    NSDesktopDirectory = 12,                // location of user's desktop
    NSCachesDirectory = 13,                 // location of discardable cache files (Library/Caches)
    NSApplicationSupportDirectory = 14,     // location of application support files (plug-ins, etc) (Library/Application Support)
    NSDownloadsDirectory API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 15,              // location of the user's "Downloads" directory
    NSInputMethodsDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 16,           // input methods (Library/Input Methods)
    NSMoviesDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 17,                 // location of user's Movies directory (~/Movies)
    NSMusicDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 18,                  // location of user's Music directory (~/Music)
    NSPicturesDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 19,               // location of user's Pictures directory (~/Pictures)
    NSPrinterDescriptionDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 20,     // location of system's PPDs directory (Library/Printers/PPDs)
    NSSharedPublicDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 21,           // location of user's Public sharing directory (~/Public)
    NSPreferencePanesDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 22,        // location of the PreferencePanes directory for use with System Preferences (Library/PreferencePanes)
    NSApplicationScriptsDirectory API_AVAILABLE(macos(10.8)) API_UNAVAILABLE(ios, watchos, tvos) = 23,      // location of the user scripts folder for the calling application (~/Library/Application Scripts/code-signing-id)
    NSItemReplacementDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 99,      // For use with NSFileManager's URLForDirectory:inDomain:appropriateForURL:create:error:
    NSAllApplicationsDirectory = 100,       // all directories where applications can occur
    NSAllLibrariesDirectory = 101,          // all directories where resources can occur
    NSTrashDirectory API_AVAILABLE(macos(10.8), ios(11.0)) API_UNAVAILABLE(watchos, tvos) = 102             // location of Trash directory

};

typedef NS_OPTIONS(NSUInteger, NSSearchPathDomainMask) {
    NSUserDomainMask = 1,       // user's home directory --- place to install user's personal items (~)
    NSLocalDomainMask = 2,      // local to the current machine --- place to install items available to everyone on this machine (/Library)
    NSNetworkDomainMask = 4,    // publically available location in the local area network --- place to install items available on the network (/Network)
    NSSystemDomainMask = 8,     // provided by Apple, unmodifiable (/System)
    NSAllDomainsMask = 0x0ffff  // all domains: all of the above and future items
};

Copied from NSPathUtilities.h

Using the core-foundation crate a program to print out the Application Support Directory is below.

use core_foundation::base::TCFType;
use core_foundation::array::{CFArrayRef, CFArray};
use core_foundation::string::{CFString};

fn main() {
    let results = unsafe {NSSearchPathForDirectoriesInDomains(14, 1, 1) };
    let results = unsafe {CFArray::<CFString>::wrap_under_get_rule(results)};

    results.iter().for_each(|x| println!("{}", *x));
}

#[link(name = "Foundation", kind = "framework")]
extern "C" {
    pub fn NSSearchPathForDirectoriesInDomains(directory: u64, domain_mask: u64, expand_tilde: i8) -> CFArrayRef;
}

Using the NSSearchPathForDirectoriesInDomains API

The above program prints out the following on my machine:

/Users/zmanji/Library/Application Support

sysdir

The second option is the sysdir API, it appears to be less documented and less used, but it’s located at /usr/include/sysdir.h and comes with a manpage sysdir(3).

The declaration is:

typedef unsigned int sysdir_search_path_enumeration_state;

__BEGIN_DECLS

extern sysdir_search_path_enumeration_state
sysdir_start_search_path_enumeration(sysdir_search_path_directory_t dir, sysdir_search_path_domain_mask_t domainMask) __API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

extern sysdir_search_path_enumeration_state
sysdir_get_next_search_path_enumeration(sysdir_search_path_enumeration_state state, char *path) __API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

__END_DECLS

Copied from sysdir.h

sysdir_search_path_directory_t and sysdir_search_path_domain_mask_t are enums identical to the NSSearchPathForDirectoriesInDomains API:

// Available OSX 10.12, iOS 10.0, WatchOS 3.0 and TVOS 10.0. Not all enum identifiers return a useful path on all platforms.
OS_ENUM(sysdir_search_path_directory, unsigned int,
    SYSDIR_DIRECTORY_APPLICATION            = 1,    // supported applications (Applications)
    SYSDIR_DIRECTORY_DEMO_APPLICATION       = 2,    // unsupported applications, demonstration versions (Applications/Demos)
    SYSDIR_DIRECTORY_DEVELOPER_APPLICATION  = 3,    // developer applications (Developer/Applications) Soft deprecated as of __MAC_10_5 - there is no one single Developer directory
    SYSDIR_DIRECTORY_ADMIN_APPLICATION      = 4,    // system and network administration applications (Applications/Utilities)
    SYSDIR_DIRECTORY_LIBRARY                = 5,    // various user-visible documentation, support, and configuration files, resources (Library)
    SYSDIR_DIRECTORY_DEVELOPER              = 6,    // developer resources (Developer) Soft deprecated as of __MAC_10_5 - there is no one single Developer directory
    SYSDIR_DIRECTORY_USER                   = 7,    // user home directories (Users)
    SYSDIR_DIRECTORY_DOCUMENTATION          = 8,    // documentation (Library/Documentation)
    SYSDIR_DIRECTORY_DOCUMENT               = 9,    // documents (Documents)
    SYSDIR_DIRECTORY_CORESERVICE            = 10,   // location of core services (Library/CoreServices)
    SYSDIR_DIRECTORY_AUTOSAVED_INFORMATION  = 11,   // location of user's directory for use with autosaving (Library/Autosave Information)
    SYSDIR_DIRECTORY_DESKTOP                = 12,   // location of user's Desktop (Desktop)
    SYSDIR_DIRECTORY_CACHES                 = 13,   // location of discardable cache files (Library/Caches)
    SYSDIR_DIRECTORY_APPLICATION_SUPPORT    = 14,   // location of application support files (plug-ins, etc) (Library/Application Support)
    SYSDIR_DIRECTORY_DOWNLOADS              = 15,   // location of user's Downloads directory (Downloads)
    SYSDIR_DIRECTORY_INPUT_METHODS          = 16,   // input methods (Library/Input Methods)
    SYSDIR_DIRECTORY_MOVIES                 = 17,   // location of user's Movies directory (Movies)
    SYSDIR_DIRECTORY_MUSIC                  = 18,   // location of user's Music directory (Music)
    SYSDIR_DIRECTORY_PICTURES               = 19,   // location of user's Pictures directory (Pictures)
    SYSDIR_DIRECTORY_PRINTER_DESCRIPTION    = 20,   // location of system's PPDs directory (Library/Printers/PPDs)
    SYSDIR_DIRECTORY_SHARED_PUBLIC          = 21,   // location of user's Public sharing directory (Public)
    SYSDIR_DIRECTORY_PREFERENCE_PANES       = 22,   // location of the PreferencePanes directory for use with System Preferences (Library/PreferencePanes)
    SYSDIR_DIRECTORY_ALL_APPLICATIONS       = 100,  // all directories where applications can occur (Applications, Applications/Utilities, Developer/Applications, ...)
    SYSDIR_DIRECTORY_ALL_LIBRARIES          = 101,  // all directories where resources can occur (Library, Developer)
);

// Available OSX 10.12, iOS 10.0, WatchOS 3.0 and TVOS 10.0. Not all enum identifiers are useful on all platforms.
OS_OPTIONS(sysdir_search_path_domain_mask, unsigned int,
    SYSDIR_DOMAIN_MASK_USER                 = ( 1UL << 0 ), // user's home directory --- place to install user's personal items (~)
    SYSDIR_DOMAIN_MASK_LOCAL                = ( 1UL << 1 ), // local to the current machine --- place to install items available to everyone on this machine
    SYSDIR_DOMAIN_MASK_NETWORK              = ( 1UL << 2 ), // publically available location in the local area network --- place to install items available on the network (/Network)
    SYSDIR_DOMAIN_MASK_SYSTEM               = ( 1UL << 3 ), // provided by Apple
    SYSDIR_DOMAIN_MASK_ALL                  = 0x0ffff,      // all domains: all of the above and more, future items
);

Copied from sysdir.h

Using this API via FFI is awkward compared to the NSSearchPathForDirectoriesInDomains API because the application has to manually iterate over the results.

use std::os::raw::c_char;

fn main() {
    let mut result = unsafe { sysdir_start_search_path_enumeration(14, 1) };

    loop {
        let mut v = vec![0; 1024];
        let path = v.as_mut_ptr() as *mut c_char;
        result = unsafe { sysdir_get_next_search_path_enumeration(result, path) };
        if result == 0 {
            break;
        }
        let s: String = String::from_utf8(v).unwrap();
        println!("{}", s);
    }
}

extern "C" {
    pub fn sysdir_start_search_path_enumeration(directory: u32, domain_mask: u32) -> u32;
    pub fn sysdir_get_next_search_path_enumeration(state: u32, path: *mut c_char) -> u32;
}

Using the sysdir API in Rust

This approach does not require any external crates or linking against any frameworks. The sysdir API is located in libSystem and is available to any application in macOS. The above program prints out the following on my machine:

~/Library/Application Support

It’s important to note that this API will always return ~ to represent the user’s home directory which might make the return value hard to use with other APIs.

Conclusion

There are two APIs available to get standard application directories on macOS for non Objective-C/Swift applications. sysdir is available in libSystem but does not expand ~ in user specific paths. Alternatively, NSSearchPathForDirectoriesInDomains is available in Foundation.framework and returns a CFArray and can expand ~ in user specific paths.


  1. See this Apple document for all of the details.↩︎