What does the /private prefix on an iOS file path indicate?
Asked Answered
M

6

37

I have a bug when my app runs on the iPhone but not when it runs on the simulator. I was using the length of the home directory path to extract the relative path of a file in /Documents. Unfortunately this doesn't always work correctly on the iPhone because the prefix "/private" is being added to the home path. However, with or without the prefix, the same file is referenced ok. The following code demonstrates this inconsistency. What is the purpose of "/private" and when is it supplied by iOS?

- (IBAction)testHomepath:(id)sender {
    NSFileManager *fmgr = [NSFileManager defaultManager];
    NSString  *homePath = [NSString stringWithFormat:@"%@/Documents",NSHomeDirectory()];
    NSString  *dirPath  = [homePath stringByAppendingPathComponent:@"TempDir"];
    NSURL     *dirURL   = [NSURL fileURLWithPath:dirPath];
    NSString  *filePath = [dirPath  stringByAppendingPathComponent:@"test.jpg"];
    [fmgr createDirectoryAtPath:dirPath withIntermediateDirectories:NO attributes:nil error:nil];
    [fmgr createFileAtPath:filePath contents:nil attributes:nil];
    NSArray *keys  = [[NSArray alloc] initWithObjects:NSURLNameKey,nil];
    NSArray *files = [fmgr contentsOfDirectoryAtURL:dirURL includingPropertiesForKeys:keys options:0 error:nil];
    NSURL *f1 = (files.count>0)? [files objectAtIndex:0] : 0;
    NSURL *f2 = (files.count>1)? [files objectAtIndex:1] : 0;
    bool   b0 = [fmgr fileExistsAtPath:filePath];
    bool   b1 = [fmgr fileExistsAtPath:f1.path];
    bool   b2 = [fmgr fileExistsAtPath:f2.path];

    NSLog(@"File exists=%d at path:%@",b0,filePath);
    NSLog(@"File exists=%d at path:%@",b1,f1.path);
    NSLog(@"File exists=%d at path:%@",b2,f2.path);
}

The following is written to the log when running on the iPhone. I manually spaced the output to show the difference between lines 1 and 2.

2013-02-20 16:31:26.615 Test1[4059:907] File exists=1 at path:        /var/mobile/Applications/558B5D82-ACEB-457D-8A70-E6E00DB3A484/Documents/TempDir/test.jpg
2013-02-20 16:31:26.622 Test1[4059:907] File exists=1 at path:/private/var/mobile/Applications/558B5D82-ACEB-457D-8A70-E6E00DB3A484/Documents/TempDir/test.jpg
2013-02-20 16:31:26.628 Test1[4059:907] File exists=0 at path:(null)

The following is written to the log when running on the simulator (no "/private"):

2013-02-20 16:50:38.730 Test1[7224:c07] File exists=1 at path:/Users/kenm/Library/Application Support/iPhone Simulator/6.1/Applications/C6FDE177-958C-4BF5-8770-A4D3FBD281F1/Documents/TempDir/test.jpg
2013-02-20 16:50:38.732 Test1[7224:c07] File exists=1 at path:/Users/kenm/Library/Application Support/iPhone Simulator/6.1/Applications/C6FDE177-958C-4BF5-8770-A4D3FBD281F1/Documents/TempDir/.DS_Store
2013-02-20 16:50:38.733 Test1[7224:c07] File exists=1 at path:/Users/kenm/Library/Application Support/iPhone Simulator/6.1/Applications/C6FDE177-958C-4BF5-8770-A4D3FBD281F1/Documents/TempDir/test.jpg
Milligram answered 21/2, 2013 at 0:57 Comment(6)
The worst thing you can do is make ANY assumptions about what the path to your app's Documents directory is or will be. Expecting a specific length for that path is even worse. Just determine the Documents path and remove that from the full path to get your relative path.Arrowroot
@maddy, I wasn't assuming a specific length, just that the path to /Documents wouldn't change, which is violated by IOS adding /private as Kevin Ballard pointed out below is just a symlink. I'm coming from Windows where I've never seen this happen. Now, I'm finding the substring of /NSHomeDirectory()/Documents in any path IOS gives me and calling the path string after that the relative path. Do you see any problem with this or know of a better way to get the relative path?Milligram
Your question states: I was using the length of the home directory path. You should always be working with paths that are relative to the Documents directory. You must never persist the full path. If you only have relative directories, there is nothing to process.Arrowroot
@maddy, I am only persisting the relative path, but NSFileManager contentsOfDirectoryAtURL returns full paths. I need to remove the stuff up to /Documents to get the relative path that I can persist.Milligram
Use NSFileManager contentsOfDirectoryAtPath:error: instead. The list of returned paths are relative to the path you get the content of.Arrowroot
Thanks for all your feedback, @maddy. That's a useful difference between DirectoryAtPath and DirectoryAtURL. I thought I needed the cached PropertiesForKeys, but maybe not.Milligram
L
33

I tried this from the debugger and discovered that URLByResolvingSymlinksInPath "fixes" the /private/ addition.

(lldb) p (NSURL *)[NSURL fileURLWithPath:@"/private/var" isDirectory:YES]
(NSURL *) $1 = 0x1fd9fc20 @"file://localhost/private/var/"
(lldb) po [$1 URLByResolvingSymlinksInPath]
$2 = 0x1fda0190 file://localhost/var/

(lldb) p (NSURL *)[NSURL fileURLWithPath:@"/var" isDirectory:YES]
(NSURL *) $7 = 0x1fd9fee0 @"file://localhost/var/"
(lldb) po [$7 URLByResolvingSymlinksInPath]
$8 = 0x1fda2f50 file://localhost/var/

as you can see, file://localhost/var is what we really want here.

Because of this, it seemed obvious that /private/var is a symlink to /var. However, @Kevin-Ballard points out that is not true. I confirmed that he is correct, and /var is the symlink to /private/var (sigh)

(lldb) p (NSDictionary *)[[NSFileManager defaultManager] attributesOfItemAtPath:@"/var" error:nil]
(NSDictionary *) $3 = 0x1fda11b0 13 key/value pairs
(lldb) po $3
$3 = 0x1fda11b0 {
    ...
    NSFileType = NSFileTypeSymbolicLink;
}

(lldb) p (NSDictionary *)[[NSFileManager defaultManager]   attributesOfItemAtPath:@"/private/var" error:nil]
(NSDictionary *) $5 = 0x1fda4820 14 key/value pairs
(lldb) po $5
$5 = 0x1fda4820 {
    ...
    NSFileType = NSFileTypeDirectory;
}

So URLByResolvingSymlinksInPath is doing something funny here, but now we know. For this particular problem, URLByResolvingSymlinksInPath still sounds like a good solution that works for both the simulator and the device and should continue to work in the future if something changes.

Latashalatashia answered 15/4, 2013 at 23:39 Comment(4)
/var is actually a symlink to /private/var on iOS too. It's possible that -URLByResolvingSymlinksInPath is somehow special-casing this to provide a more canonical path.Bollinger
Solid discussion; I ran into an issue loading files within a PDF viewer and it all revolved around the simlink. Once I came across this explanation the solution jumped out and the problem was solved.Razo
Apple documents that URLByResolvingSymlinksInPath removes the leading /private. developer.apple.com/library/ios/documentation/Cocoa/Reference/…Succentor
salved all path issues for meMalda
R
7

In Swift 3, URL has the standardizedFileUrl property, which will remove any symlinks and resolve relative parts within the path like ./.

As of writing the documentation is pretty useless but it looks like it is equivialent to NSURL's standardized property.

Reverence answered 26/1, 2017 at 16:28 Comment(1)
NOTE: URL has 2 similar properties - standardized doesn't work but standardizedFileUrl works!Circumstantial
B
5

/var is just a symlink to /private/var. So the first path is the logical path that you tried to access. The second is that same path with symlinks expanded.

Bollinger answered 21/2, 2013 at 1:2 Comment(4)
Thanks for the hint, but it seems to be the opposite. See my answer.Latashalatashia
@Skotch: It's actually not. See my comment on your answer.Bollinger
/var has been a symlink to /private/var for a long time on Mac OS X. It was that way before iOS existed.Putter
contentsOfDirectory returns /private/var/... and by using resolvingSymlinksInPath() it returns /var/...Ferbam
L
4

To actually answer your question:

I believe /private was a prefix added when they released OS X (I don't think it was there in NeXTStep, but it's been decades). It seems to exist to house etc, var, and tmp (and, oddly, tftpboot; I didn't know my PBG4 could do that), perhaps so users don't wonder what this silly folder called etc is and try to delete it.

On device, Apple decided to store user data in /private/var/mobile (the username is "mobile"). I'm not sure why they didn't pick /Users/mobile or just /mobile, but it has no more significance than /var/mobile would on a "normal" Unix.

On the simulator, your user account can't write to /var (for good reason). User data is stored somewhere in ~/Library/Application Support/iPhone Simulator. At one point, they started using different directories for different simulator versions.

Library answered 21/2, 2013 at 2:12 Comment(0)
M
0
  1. In my case while using FileManager.default.fileExists to check whether the file already exists but i was using .absoluteString/"()". instead of .path for passing URL as String.
  2. When i printed the paths of the files in Documents Directory using

let fileURLs = try fileManager.contentsOfDirectory(at:

it printed as /private/var

let DocumentDirURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)

it printed as /var

  1. This difference in path made me confused.
  2. As explained Above its just symlink. ie both the paths are same.

:)

Mullock answered 18/7, 2018 at 10:4 Comment(0)
C
0

A function (Swift 5) to remove an file from cache. I had a lot of problems and the use of the URL solved the /private issue.

class func removeFileFromCache( filePattern: String ) -> Bool {
    // find filename with pattern
     
    do {
        let cachePath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        let filePathUrl = URL( fileURLWithPath: filePattern )
        let originalFilename = filePathUrl.lastPathComponent
        let newFilePath = cachePath.appendingPathComponent( originalFilename ).path
       
        try FileManager.default.removeItem(atPath: newFilePath )
        print("file \( filePattern ) succesfull removed.",classname:URL(fileURLWithPath: #file).lastPathComponent )
        
    } catch let error as NSError {
        print("Error \(error)",classname:URL(fileURLWithPath: #file).lastPathComponent )
        return false // all is not well
    }
    
    return true
}
Counterinsurgency answered 21/6, 2020 at 17:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.