Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I find the correct case of a filename?

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

like image 641
Roland Rabien Avatar asked Dec 16 '08 00:12

Roland Rabien


People also ask

Are file names case-sensitive?

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.

How do I get the path of a file name?

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);

Is git case-sensitive for file names?

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.


1 Answers

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.
like image 69
TALlama Avatar answered Nov 03 '22 20:11

TALlama