This is on the Mac:
If I have two filenames /foo/foo and /foo/FOO they may refer to the same file or the may be different files depending on the file system. How do I figure out if they are both pointing to the same file? And if they are, how do I get the correct representation of the filename?
My problem is caused by links. A link might point to /foo/FOO but the actual directory is named /foo/foo.
Is there any function that will follow a link and give me the the full path of the linked file? [NSFileManager pathContentOfSymbolicLinkAtPath] gives relative paths that might be in the incorrect case.
Ultimately what I'm try to do is cache info for files. But if I have two different paths for the same file, my cache can get out of sync.
Thanks
File names: Traditionally, Unix-like operating systems treat file names case-sensitively while Microsoft Windows is case-insensitive but, for most file systems, case-preserving.
To extract filename from the file, we use “GetFileName()” method of “Path” class. This method is used to get the file name and extension of the specified path string. The returned value is null if the file path is null. Syntax: public static string GetFileName (string path);
The Windows and macOS file systems are case-insensitive (but case-preserving) by default. Most Linux filesystems are case-sensitive. Git was built originally to be the Linux kernel's version control system, so unsurprisingly, it's case-sensitive.
There's really a couple of different parts to your question. By my reading, you want:
1 a way to tell if two different paths are the same on-disk file
2 a canonical name for the file on disk, with the proper casing
There's a third issue that gets mixed in, as well, having to do with Display Names, because in OS X a file can localize its name and appear differently for different locales. So let's add
3 a way to get the display name, because we might want to cache things depending on how the user sees the file system, not how the file system appears in the terminal.
We can solve 1 with the FSRef trick pointed out by @boaz-stuller. Or here's some code that does it using higher-level Cocoa calls, which saves us a little bit of memory juggling (since we can let the NSAutoreleasePool
do it for us):
long getInode(NSString* path) {
NSFileManager* fm = [NSFileManager defaultManager];
NSError* error;
NSDictionary* info = [fm attributesOfItemAtPath:path error:&error];
NSNumber* inode = [info objectForKey:NSFileSystemFileNumber];
return [inode longValue];
}
But to solve 2, we've got to use FSRefs to find out the canonical casing of the file:
NSString* getActualPath(NSString* path) {
FSRef ref;
OSStatus sts;
UInt8* actualPath;
//first get an FSRef for the path
sts = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
if (sts) return [NSString stringWithFormat:@"Error #%d making ref.", sts];
//then get a path from the FSRef
actualPath = malloc(sizeof(UInt8)*MAX_PATH_LENGTH);
sts = FSRefMakePath(&ref, actualPath, MAX_PATH_LENGTH);
if (sts) return [NSString stringWithFormat:@"Error #%d making path.", sts];
return [NSString stringWithUTF8String:(const char*)actualPath];
}
That's not bad at all, but we're still happy when we can solve 3 with Cocoa methods:
NSString* getDisplayPath(NSString* path) {
NSFileManager* fm = [NSFileManager defaultManager];
NSString* mine = [fm displayNameAtPath:path];
NSString* parentPath = [path stringByDeletingLastPathComponent];
NSString* parents = [@"/" isEqualToString:parentPath]
? @""
: getDisplayPath(parentPath);
return [NSString stringWithFormat:@"%@/%@", parents, mine];
}
Finally, we can add a bit of driver code and tie this all together into a CoreFoundation command line tool (I had to add the AppKit framework to get this to compile).
NSString* fileInfoString(NSString* path) {
long inode = getInode(path);
return [NSString stringWithFormat:
@"\t%@ [inode #%d]\n\t\tis actually %@\n\t\tand displays as %@",
path,
inode,
getActualPath(path),
getDisplayPath(path)];
}
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
if (argc < 2) {
NSLog(@"Usage: %s <path1> [<path2>]", argv[0]);
return -1;
}
NSString* path1 = [NSString stringWithCString:argv[1]];
NSString* path2 = argc > 2
? [NSString stringWithCString:argv[1]]
: [path1 uppercaseString];
long inode1 = getInode(path1);
long inode2 = getInode(path2);
NSString* prefix = [NSString stringWithFormat:
@"Comparing Files:\n%@\n%@",
fileInfoString(path1),
fileInfoString(path2)];
int retval = 0;
if (inode1 == inode2) {
NSLog(@"%@\nSame file.", prefix);
} else {
NSLog(@"%@\nDifferent files.", prefix);
retval = 1;
}
[pool drain];
return retval;
}
Now, we can put it all together and run it:
$ checkpath /users/tal 2008-12-15 23:59:10.605 checkpath[22375:10b] Comparing Files: /users/tal [inode #1061692] is actually /Users/tal and displays as /Users/tal /USERS/TAL [inode #1061692] is actually /Users/tal and displays as /Users/tal Same file.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With