wxFileExists/wxDir::GetAllFiles difference; does this inconsistency is filesystem relative or not?

Do you have a typical platform dependent issue you're battling with ? Ask it here. Make sure you mention your platform, compiler, and wxWidgets version.
Post Reply
User avatar
eranon
Can't get richer than this
Can't get richer than this
Posts: 867
Joined: Sun May 13, 2012 11:42 pm
Location: France
Contact:

wxFileExists/wxDir::GetAllFiles difference; does this inconsistency is filesystem relative or not?

Post by eranon »

Hello,

Searching the reason why of an issue in my app's code, I find an inconsistency between too wxWidgets functions: wxFileExists() and wxDir::GetAllFiles().

Say I have the file "Test.txt" on disk. If I check for its existence passing "test.txt" to my two functions, wxFileExists says YES, it exists, while GetAllFiles doesn't found it (of course, this latter function is more dedicated to files spec, but this one can target a single named file too).

I observed this from my dev station in OS X 10.9 Mavericks with files on an Apple HFS+ partition. From what I know, HFS+ is case-insensitive (but case-preserving when we're not talking about comparison) by default, but can be formated as case-sensitive. Mine is case-insensitive (seen using "diskutil info /").

So, if the filesystem's sensitivity has to be considered, in my current specific context, wxFileExists is OK, while wxDir::GetAllFiles() is not! I have no time right now to do some tests on different OSs and filesystems...

What's the behavior of these two functions in your own contexts?
Do you think it's a bug or a feature?

--
EDIT: In fact, in my mind, it would be better to be filesystem-independent in a cross-platform spirit... So, wxFileExists() shouldn't consider that "test.txt" matches "Test.txt"; and should always return false whatever the underlying context.
[Ind. dev. - wxWidgets 3.0/3.1 under "Win 7 64-bit, TDM64-GCC" + "OS X 10.9, LLVM Clang"]
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxFileExists/wxDir::GetAllFiles difference; does this inconsistency is filesystem relative or not?

Post by doublemax »

I tested under Windows and the two functions return a consistent result - as they should.

As both functions use the underlying OS functions, i suspect OSX to be at fault here. Can you trace through both functions and check where exactly the problem lies?

As a quick workaround, you could write your own replacement for wxFileExists using wxDir::GetFirst() (which GetAllFiles() uses internally). Then you should get a consistent result at least.
Use the source, Luke!
User avatar
eranon
Can't get richer than this
Can't get richer than this
Posts: 867
Joined: Sun May 13, 2012 11:42 pm
Location: France
Contact:

Re: wxFileExists/wxDir::GetAllFiles difference; does this inconsistency is filesystem relative or not?

Post by eranon »

Hm, OK, so it shouldn't found it... Sounds effectivelly more consistent to me too!

Well, so I only traced the faulty wxFileExists (since wxDir::GetAllFiles is right).

Searching for "test.txt" while there's only "Test.txt" on disk.

Code: Select all

wxFileExists("test.txt"):
=> bool wxFileExists (const wxString& filename){return wxFileName::FileExists(filename);}
=> bool wxFileName::FileExists(const wxString &filePath){return wxFileSystemObjectExists(filePath, wxFILE_EXISTS_REGULAR);}
=>
wxFileSystemObjectExists(const wxString& path, int flags)
{   // Reduced to useful code (removed all non-Mac parts and the ones about dirs, symlinks, etc)
    const bool acceptFile = (flags & wxFILE_EXISTS_REGULAR) != 0;
    const bool acceptDir  = (flags & wxFILE_EXISTS_DIR)  != 0;
    wxString strPath(path);
    wxStructStat st;
    if ( !StatAny(st, strPath, flags) )
        return false;
    if ( S_ISREG(st.st_mode) )
        return acceptFile;     // It reaches this line! 
}
The S_ISREG macro being defined like this:

Code: Select all

#define	S_ISREG(m)	(((m) & S_IFMT) == S_IFREG)	/* regular file */
And OK for the workaround; thanks, doublemax.

--
EDIT: If it matter, the concerned app is built with LLVM Clang in OS X 10.9 against wxWidgets 3.1.0 and SDK 10.9 (as minimum supported OS X).
[Ind. dev. - wxWidgets 3.0/3.1 under "Win 7 64-bit, TDM64-GCC" + "OS X 10.9, LLVM Clang"]
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxFileExists/wxDir::GetAllFiles difference; does this inconsistency is filesystem relative or not?

Post by doublemax »

OK, so it shouldn't found it... Sounds effectivelly more consistent to me too!
No. Both functions find the file under Windows.
Use the source, Luke!
User avatar
eranon
Can't get richer than this
Can't get richer than this
Posts: 867
Joined: Sun May 13, 2012 11:42 pm
Location: France
Contact:

Re: wxFileExists/wxDir::GetAllFiles difference; does this inconsistency is filesystem relative or not?

Post by eranon »

Oh! so, wxFileExists gives the same result as your Windows one on my Mac platform. Then, if it's the right behavior, I have no reason to write my own wxFileExists replacement (unless I want a ::CaseSensitiveFileExists()).

About wxDir::GetAllFiles, from what I see, it goes through wxDir::Traverse -> wxDir::GetFirst/GetNext -> wxDirData::Read... More difficult to quickly trace (especially on Mac where debugging is quite special because of lack of support around; CodeLite requires OS X 10.10+, CodeBlocks doesn't know lldb and Clang binaries are not gdb compliants, so I use an alternate front-end which sometime succeeds and sometime freezes and hangs in the middle) and locate where if fails when it encounters "Test.txt" while searching for "test.txt".

Well, I'm wondering if the files related functions are all case-insensitive... Using the same example I taken, what will happen if I want to delete "Test.txt" while there's "test.txt" on disk? Does the deletion functions will succeed?

Something like (not tested, I'm writing it on fly):

Code: Select all

#ifdef __WXMSW__
wxString strFile("c:/Test.txt");
#endif
#ifdef __WXOSX__
wxString strFile("/Test.txt");
#endif

if (wxFileExists(strFile)
   wxRemoveFile(strFile);   // Does wxRemoveFile will succeed to delete "test.txt"?
And, in this case (sorry for eventual typo, not tested again):

Code: Select all

wxFileName fn(strFile);
wxString strPath = fn.GetPath();
wxString strSpec = fn.GetFullName();
wxArrayString arFiles;
int nFiles = wxDir::GetAllFiles(strPath, &arFiles, strSpec, wxDIR_FILES);   // From what I saw, it doesn't see "test.txt" on disk,
for (int idx = 0 ; idx < nFiles ; idx++){wxRemoveFile(arFiles.Item(idx));}   // then "test.txt" will never be deleted.
[Ind. dev. - wxWidgets 3.0/3.1 under "Win 7 64-bit, TDM64-GCC" + "OS X 10.9, LLVM Clang"]
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxFileExists/wxDir::GetAllFiles difference; does this inconsistency is filesystem relative or not?

Post by doublemax »

Well, I'm wondering if the files related functions are all case-insensitive... Using the same example I taken, what will happen if I want to delete "Test.txt" while there's "test.txt" on disk? Does the deletion functions will succeed?
That should depend only on whether the underlying file system is case sensitive or not. Under Windows/NTFS attempting to delete "Test.txt" should also delete "test.txt". AFAIK all file systems under Linux are case sensitive, so the file should not get deleted there.

OSX/HFS should behave the same as under Windows.

I think you should write a small test program to confirm the wrong behavior under OSX and open a bug report.
Use the source, Luke!
User avatar
eranon
Can't get richer than this
Can't get richer than this
Posts: 867
Joined: Sun May 13, 2012 11:42 pm
Location: France
Contact:

Re: wxFileExists/wxDir::GetAllFiles difference; does this inconsistency is filesystem relative or not?

Post by eranon »

Well, wxRemoveFile is well case-insensitive too. So, the issue is only with wxDir::GetAllFiles() and underlying functions which behaves case-sensitivelly in OS X 10.8 and 10.9 (I'll check with 10.10 and 10.12 too). This both with wxWidgets 3.0 and 3.1.

Test code, replacing the About-box code in minimal sample coming with wxWidgets:

Code: Select all

#include <wx/filename.h>
#include <wx/stdpaths.h>
#include <wx/dir.h>

void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
{
    wxFileName fnExe(wxStandardPaths::Get().GetExecutablePath());
    wxString strExePath(fnExe.GetPath());
    wxString strFile(strExePath.Append("/../../../Test.txt"));
  
// Way #1: both functions are case-insensitive => it succeeds to delete "test.txt" even when searching for "Test.txt"
//    if (wxFileExists(strFile))
//        wxRemoveFile(strFile);   // Does wxRemoveFile will succeed to delete "test.txt"?

// Way #2: since wxDir::GetAllFiles shows a case-sensitive behavior, "test.txt" is not found (then not deleted)
    wxFileName fn(strFile);
    wxString strPath = fn.GetPath();
    wxString strSpec = fn.GetFullName();
    wxArrayString arFiles;
    int nFiles = wxDir::GetAllFiles(strPath, &arFiles, strSpec, wxDIR_FILES);   
    wxMessageBox(wxString::Format("Searching for: %s\n\nNumber of files found: %i", strFile, nFiles));
    
    for (int idx = 0 ; idx < nFiles ; idx++){
        wxString strCurr = arFiles.Item(idx);
        wxMessageBox(wxString::Format("Current file to delete (%i/%i): %s", idx+1, nFiles, strCurr));
        wxRemoveFile(strCurr);}
}
[Ind. dev. - wxWidgets 3.0/3.1 under "Win 7 64-bit, TDM64-GCC" + "OS X 10.9, LLVM Clang"]
Post Reply