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.