question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unable to specify files inode

See original GitHub issue

This code https://github.com/dokan-dev/dokan-dotnet/blob/master/DokanNet/DokanOperationProxy.cs#L507 Causes the inode for two completly different files to be the same if they just have the same filename.

I’m running Python scripts that copy files around on my drive. And python’s os.path.samefile checks if the file’s inode and deviceID are different. Which they aren’t if the files happen to have the same name. In my case I am trying to copy a file to a different directory. So it indeed has the same name and thus the same inode. But they are still seperate files.

My proposed solution would be to provide the ability to set the inode in the FileInformation struct but keep it optional.

What could be done is have JUST GetFileInformation return a different FileInformation struct than everywhere else which contains the full path instead of just the filename. In my case I’m storing the FileInformation struct directly in my file node because I don’t need to generate a new one everytime.

Instead of

public NtStatus GetFileInformation(string filename, out FileInformation fileInfo, DokanFileInfo info)
{
    var node = GetNode(filename, info);
    if (node == null)
    {
        fileInfo = new FileInformation();
        return DokanResult.FileNotFound;
    }

    fileInfo = node.fileInfo;
    return DokanResult.Success;
}

I would need to

public NtStatus GetFileInformation(string filename, out FileInformation fileInfo, DokanFileInfo info)
{
    var node = GetNode(filename, info);
    if (node == null)
    {
        fileInfo = new FileInformation();
        return DokanResult.FileNotFound;
    }

    fileInfo = new FileInformation
    {
        FileName = filename,
        Attributes = node.fileInfo.Attributes,
        CreationTime = node.fileInfo.CreationTime,
        LastAccessTime = node.fileInfo.LastAccessTime,
        LastWriteTime = node.fileInfo.LastWriteTime,
        Length = node.fileInfo.Length
    };
    return DokanResult.Success;
}

But that would make GetFileInformation from now on always return a wrong FileName.

Which is btw what the Mirror sample is already doing.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Comments:21 (11 by maintainers)

github_iconTop GitHub Comments

1reaction
dedmencommented, Oct 10, 2019

For reference (as I already forgot most of the stuff I was looking at back then)

Hash is calculated and used here: https://github.com/dokan-dev/dokan-dotnet/blob/fa556dfc3631eb8feed374322f4285cf17194f88/DokanNet/DokanOperationProxy.cs#L547

This https://github.com/dokan-dev/dokan-dotnet/blob/fa556dfc3631eb8feed374322f4285cf17194f88/DokanNet/DokanOperationProxy.cs#L515 should really just return a struct mirroring the BY_HANDLE_FILE_INFORMATION contents in a C# format, we can basically keep most things but just add 64bit(?) number for fileIndex (inode) and dwNumberOfLinks

We need to communicate a way to the users to still let them generate hash from filename, to make sure new users won’t be confused? Might aswell just mention a //(uint)(fi.FileName?.GetHashCode() ?? 0) in comments in the new struct.

0reactions
kyanhacommented, Jan 23, 2020

I have my own reasons for wanting to specify the ID, for FILE_OPEN_BY_FILE_ID handling. I honestly think it’d be most appropriate to address this bug by implementing that.

That said… all filenames that come to the functions pointed to in DOKAN_OPERATIONS from dokan1.sys are (currently) passed the full pathname within the filesystem. There’s no “look for current working directory and append the filename to it” that the filesystem implementation needs to do, it’s all handled by the driver. Which is good, because the driver doesn’t pass current working directory information to the implementation. I don’t think the driver can actually even get the current working directory information of the process that called it.

To understand BY_HANDLE_FILE_INFORMATION and its use of nFileIndexLow and nFileIndexHigh, it’s important to realize that those came directly from the definition in the NT header files. Those fields are expressly defined and populated to support FILE_OPEN_BY_FILE_ID. The fact that you want to specify them is awesome, and I wholeheartedly salute it – I just want it done correctly.

If FILE_OPEN_BY_FILE_ID is handled, the file ID is passed to the kernel in raw form (64 raw bits, with no base64 encoding, no padding, no NUL-termination) as FileName. Trying to interpret it as a string will either lead to incorrect behavior (since the first four bytes of a 32 bit value stuffed into a 64-bit space matches the UTF-32 representation of U+0000, thus leading to a string that’s empty) or a bluescreen (since a 64-bit number with no 0x00 octets would have no guarantee that it would be followed by U+0000 in any reasonable space, and the kernel driver would need to interpret it as a string to determine what filename to pass up to user mode) – see https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntcreatefile for more information. But, since the kernel driver would also receive the FILE_OPEN_BY_FILE_ID flag, it would be easy enough for the driver to break the value out into the nFileIndexLow and nFileIndexHigh fields, and pass an empty string in FileName. (This would be the differentiation that would allow a user to know that it’s being requested to open a file by ID instead of by name – if the string is @"" and not even @"\", it’s not referring to a normal name.) [note: the kernel doesn’t allow FILE_OPEN_BY_FILE_ID unless the filesystem is mounted with the FILE_SUPPORTS_OPEN_BY_FILE_ID deviceCharacteristic. This would need to be added to the DokanOptions enum, and its underlying handlers, to let the implementation tell dokany that it won’t panic if it receives String.Empty in CreateFile.]

As an implementation note on your patch, long? FileIndex actually won’t work to make it nullable, because long is a value type. Because of the underlying semantics of how memory addresses are interpreted, If ‘null’ were passed there, it would simply be passed the value of 0L, with no way to differentiate whether it’s intended to be null or a literal 0L.

I’d also suggest making that parameter ulong instead of long, since it’s not actually interpreted as an numeric value, and the underlying nFileIndexLow and nFileIndexHigh are both defined as uint (which would have the potential of making fully half of the nFileIndexHigh values appear to be negative numbers – as well as potentially opening up sign-extension issues).

I understand that you probably derived the name FileIndex as a name from BY_HANDLE_FILE_INFORMATION.cs, but I’d suggest renaming it to FileID", since FILE_ID is what the MSDN documentation calls it. (The names nFileIndexLow and nFileIndexHigh are artifacts of how NTFS implements it – it’s the index of the file into the Master File Table. However, in dokany implementations, they’re not always going to be true “indexes”. I’m working on an implementation that would be able to directly expose underlying object IDs for file streams stored in PostgreSQL, which doesn’t relate to indexing into an MFT. [As a side note, Postgres object IDs are all 32 bits long, which would also lead to me being impacted by a 0x00000000 nFileIndexHigh if it were interpreted as a string, which is why I’m concerned about correctly handling it.])

Also, the name that NtCreateFile receives when asked to open by FILE_ID is of either of the forms [8RawBytesOfFileID] or \??\C:\[8RawBytesOfFileID]. I don’t have a means of testing right now, but I would guess that if the Dokan filesystem is mounted on a path, it would be the path to the mountpoint prepended with \??\. (I don’t have any idea what it would be in a UNC environment, since the IDs provided by \Device\HardDiskVolume1\[16RawBytesOfObjectID] name syntax are 16-byte GUIDs, and UNC paths are accessed via \Device\MUP\hostname\sharename\.)

Read more comments on GitHub >

github_iconTop Results From Across the Web

why can't we store file names directly in inodes
1 Answer 1 · a file may have multiple names, a.k.a. hard link · to support long file name, say at least 255...
Read more >
How Do I Fix File Creation Failures Due to Inode Exhaustion?
When you create a file or directory, you get the error message "No space left on device", "Cannot create directory", or "Couldn't create ......
Read more >
Quickly find which file(s) belongs to a specific inode number
For an ext4 filesystem, you can use debugfs as in the following example: $ sudo debugfs -R 'ncheck 393094' /dev/sda2 2>/dev/null Inode ...
Read more >
Unable to create folder/file on root in spite of free space
1 Answer 1 ... You have no inodes free. You will need to delete files to free up inodes if you would like...
Read more >
Inodes and the Linux filesystem | Enable Sysadmin
Every time you run the ls command and see the output—files listed, permissions, account ownership, etc.—understand that the data about the files ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found