This is decidedly not an answer, so please do not give me the bounty. It is also not a solution to the problem, for the simple reason that my C++, Win32, and LibraryLink skills are virtually nonexistent, and certainly not sufficient to write this robustly. Rather, my intent is simply to show that the answer given by Eric Towers contains some serious misconceptions about how Win32 and NT work, and is not a solution either.*
As I mentioned in my comments under his answer, the only reason why some (although not all) of Mathematica's file-handling functions fail to respect case-sensitivity is because they call the Win32 API function CreateFile
without supplying the flag FILE_FLAG_POSIX_SEMANTICS
. The Microsoft documentation for the flag is the following:
Access will occur according to POSIX rules. This includes allowing multiple files with names differing only in case, for file systems that support that naming. Use care when using this option, because files created with this flag may not be accessible by applications that are written for MS-DOS or 16-bit Windows.
(Note that it is not entirely clear whether there is any other semantic distinction apart from case-sensitivity. POSIX also does not normally lock files that are opened, whereas Windows does, which seems to me to fall under the category of semantics.)
Microsoft is absolutely correct that this capability (and creating and using case-distinguished files in general) should be approached with some care. Most Win32 applications do not supply this flag, even though there is nothing preventing them from doing so. This includes Explorer and many other utilities provided with Windows itself. These programs will thus not be able to distinguish between filenames that differ only in their case, and in fact they normally behave as if you had specified all of the applicable files. This can be a serious problem when the operation one wants to perform is e.g. deletion.
With that caveat, let us proceed to the program. For this I will use some Microsoft example code available on MSDN.
Let us note explicitly:
- This is Win32 code. Not POSIX. Not NT. (Most of the NT API is undocumented anyway.)
- The function called is
CreateFile
, part of the Win32 API. Not NtCreateFile
(which is case-sensitive by default, as NT is in general).
- It is a user-mode application, not a driver. It does not run in kernel mode.
First we create the files. I used Cygwin, consistent with the question:
Olek@core2 /mnt/c/Users/Olek/Desktop/fileprint
$ echo file > file
Olek@core2 /mnt/c/Users/Olek/Desktop/fileprint
$ echo File > File
Now compile the program (I used MinGW g++):
C:\Users\Olek\Desktop\fileprint>g++ -D__in= -o fileprint.exe fileprint.cpp
Try it out:
C:\Users\Olek\Desktop\fileprint>fileprint.exe File
Error code: 0
Number of bytes: 4
Data read from File (4 bytes):
file
What's that? It doesn't work? We must have forgotten to specify FILE_FLAG_POSIX_SEMANTICS
! Let's add it (on line 53):
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // normal file
becomes
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED | FILE_FLAG_POSIX_SEMANTICS,
After recompiling:
C:\Users\Olek\Desktop\fileprint>fileprint.exe File
Error code: 0
Number of bytes: 4
Data read from File (4 bytes):
File
Well, look at that! Apparently the presence or absence of FILE_FLAG_POSIX_SEMANTICS
really does make all the difference. Who'd have guessed?
But still, one can reasonably ask, how does this relate to Mathematica? The answer is that it is obviously possible to write the same C++/Win32 code as a DLL and load it into Mathematica using LibraryLink. With the aid of the (new-in-9) functions for defining stream methods, it would be relatively straightforward to manipulate files with arbitrarily-cased names as Mathematica streams. Relatively straightforward, that is, for someone who is more confident of their ability to write correct Win32 code than I am of mine.
Finally, the Microsoft example code is given below for reference, in case the page on which it currently exists is changed or moved in future:
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>
#define BUFFERSIZE 5
DWORD g_BytesTransferred = 0;
void DisplayError(LPTSTR lpszFunction);
VOID CALLBACK FileIOCompletionRoutine(
__in DWORD dwErrorCode,
__in DWORD dwNumberOfBytesTransfered,
__in LPOVERLAPPED lpOverlapped
);
VOID CALLBACK FileIOCompletionRoutine(
__in DWORD dwErrorCode,
__in DWORD dwNumberOfBytesTransfered,
__in LPOVERLAPPED lpOverlapped )
{
_tprintf(TEXT("Error code:\t%x\n"), dwErrorCode);
_tprintf(TEXT("Number of bytes:\t%x\n"), dwNumberOfBytesTransfered);
g_BytesTransferred = dwNumberOfBytesTransfered;
}
//
// Note: this simplified sample assumes the file to read is an ANSI text file
// only for the purposes of output to the screen. CreateFile and ReadFile
// do not use parameters to differentiate between text and binary file types.
//
void __cdecl _tmain(int argc, TCHAR *argv[])
{
HANDLE hFile;
DWORD dwBytesRead = 0;
char ReadBuffer[BUFFERSIZE] = {0};
OVERLAPPED ol = {0};
printf("\n");
if( argc != 2 )
{
printf("Usage Error: Incorrect number of arguments\n\n");
_tprintf(TEXT("Usage:\n\t%s <text_file_name>\n"), argv[0]);
return;
}
hFile = CreateFile(argv[1], // file to open
GENERIC_READ, // open for reading
FILE_SHARE_READ, // share for reading
NULL, // default security
OPEN_EXISTING, // existing file only
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // normal file
NULL); // no attr. template
if (hFile == INVALID_HANDLE_VALUE)
{
DisplayError(TEXT("CreateFile"));
_tprintf(TEXT("Terminal failure: unable to open file \"%s\" for read.\n"), argv[1]);
return;
}
// Read one character less than the buffer size to save room for
// the terminating NULL character.
if( FALSE == ReadFileEx(hFile, ReadBuffer, BUFFERSIZE-1, &ol, FileIOCompletionRoutine) )
{
DisplayError(TEXT("ReadFile"));
printf("Terminal failure: Unable to read from file.\n GetLastError=%08x\n", GetLastError());
CloseHandle(hFile);
return;
}
SleepEx(5000, TRUE);
dwBytesRead = g_BytesTransferred;
// This is the section of code that assumes the file is ANSI text.
// Modify this block for other data types if needed.
if (dwBytesRead > 0 && dwBytesRead <= BUFFERSIZE-1)
{
ReadBuffer[dwBytesRead]='\0'; // NULL character
_tprintf(TEXT("Data read from %s (%d bytes): \n"), argv[1], dwBytesRead);
printf("%s\n", ReadBuffer);
}
else if (dwBytesRead == 0)
{
_tprintf(TEXT("No data read from file %s\n"), argv[1]);
}
else
{
printf("\n ** Unexpected value for dwBytesRead ** \n");
}
// It is always good practice to close the open file handles even though
// the app will exit here and clean up open handles anyway.
CloseHandle(hFile);
}
void DisplayError(LPTSTR lpszFunction)
// Routine Description:
// Retrieve and output the system error message for the last-error code
{
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0,
NULL );
lpDisplayBuf =
(LPVOID)LocalAlloc( LMEM_ZEROINIT,
( lstrlen((LPCTSTR)lpMsgBuf)
+ lstrlen((LPCTSTR)lpszFunction)
+ 40) // account for format string
* sizeof(TCHAR) );
if (FAILED( StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with error code %d as follows:\n%s"),
lpszFunction,
dw,
lpMsgBuf)))
{
printf("FATAL ERROR: Unable to output error code.\n");
}
_tprintf(TEXT("ERROR: %s\n"), (LPCTSTR)lpDisplayBuf);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
}
* Eric's workaround of using the short file names was necessary because, in the case he describes, he had obviously handled the drag-and-drop operation incorrectly and did not retrieve the file names (which include the extensions) but rather the text displayed by Explorer, which is merely part of the user interface. Handling drag-and-drop input is not trivial in Win32, but even so, Microsoft cannot really be blamed if it is done wrongly, because all of the documentation and ample examples are provided. And why should we not rely on short file names? Because they don't necessarily even exist! Short file names are completely optional, and implemented as hard links, on NTFS volumes.
FileByteCount
andFileHash
do recognize the case sensitive names. $\endgroup$ – george2079 Nov 3 '15 at 19:40