If you really, really need access to a path that Apple doesn’t want you to access while sandboxed (i.e. everywhere except what user selected, or a few paths like Documents, Music, Downloads) — you need to add a temporary file exception entitlement.
While these are intended as a stop-gap measure and are not intended for long-term use, they may help you solve your short-term problem.
How to use
After adding entitlements to your app (by marking that checkbox in Xcode), and after turning on Sandbox (again, by marking another checkbox in Xcode), you can see that a new plist-formatted file has appeared in your project with a single entry, com.apple.security.app-sandbox
set to true.
Now add a new entry com.apple.security.temporary-exception.files.home-relative-path.read-only
(or any of the other combinations of home-relative-path
, absolute-path
, read-only
and read-write
). Its type needs to be Array, and its contents need to be Strings.
I only used a single read-only, home-relative path. It needs to be formatted as follows: /Library/Somewhere/Some Data/
Gotchas
First of all… as mentioned, it’s not a String value, it’s an Array value containing strings.
Second… with NSFileManager, you are getting actual values for the first level of contents (e.g. folders), but when accessing subfolders you’re getting nil
returnvalue and the error set to The file “2011-07-28” couldn’t be opened because you don’t have permission to view it.
? Heh. See that slash on the end? It NEEDS to be there. It absolutely, 100% needs to be there, or else you’re getting the aforementioned permission denied error.
Third… you may be wondering, “How the hell am I going to get the user folder? NSHomeDirectory()
is returning a path inside the sandbox container, and so do all other methods!”
Sure, if you stick to Cocoa. Apple has wrapped everything nicely, and I actually commend them on thoroughness. Even getpwent()
returns incorrect values – I got /var/virusmail
as the home folder.
There’s one thing that does return the username, however: NSUserName()
. Don’t be easily tempted to construct the path by simply prepending /Users/
. On my external drive, I tend to keep “recent cats” and “future cats”, in order to try everything out, but avoid breaking my workflow. However, it’s worthless unless I bring over the home folder, so on that installation, my home folder is not /Users/ivucica
but /Volumes/Macintosh HD/Users/ivucica
. Be careful, and use this solution.
#include#include NSString * IVHomeDirectory() { const struct passwd * passwd = getpwnam([NSUserName() UTF8String]); if(!passwd) return nil; // bail out cowardly const char *homeDir_c = getpwnam([NSUserName() UTF8String])->pw_dir; NSString *homeDir = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:homeDir_c length:strlen(homeDir_c)]; return homeDir; } // simple drop-in replacement for NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); NSArray * IVLibraryDirectory() { NSArray * libraryDirectories = [NSArray arrayWithObject: [IVHomeDirectory() stringByAppendingPathComponent:@"Library"]]; return libraryDirectories; }
Above solution is inspired by this answer on StackOverflow.
If this helped you, leave me a comment here, and perhaps upvote those comments I made on StackOverflow. Everyone deserves encouragement now and then, right? 🙂
–
via blog.vucica.net
to get the user path in a sandbox, this seems to work too:
struct passwd *pw = getpwuid(getuid());
return [NSString stringWithUTF8String:pw->pw_dir];
You will probably still want to use stringWithFileSystemRepresentation:, and you will probably still want to error-check getpwuid() before accessing pw_dir, but yeah — this is nicer than basing it off of NSUserName().
Thanks!