Add a new wxFileName function to resolve symlinks to absolute paths

This commit is contained in:
Ian McInerney
2021-04-01 18:40:21 +01:00
parent 152a2079b4
commit 53bd1391f4
5 changed files with 135 additions and 2 deletions

View File

@@ -381,7 +381,10 @@ enum wxPosixPermissions
#define wxCRT_Access access
#define wxCRT_Chmod chmod
#define wxCRT_Readlink readlink
#define wxHAS_NATIVE_LSTAT
#define wxHAS_NATIVE_READLINK
#endif // platforms
// if the platform doesn't have symlinks, define wxCRT_Lstat to be the same as
@@ -406,6 +409,11 @@ inline int wxChmod(const wxString& path, mode_t mode)
inline int wxOpen(const wxString& path, int flags, mode_t mode)
{ return wxCRT_Open(path.fn_str(), flags, mode); }
#if defined(wxHAS_NATIVE_READLINK)
inline int wxReadlink(const wxString& path, char* buf, int size)
{ return wxCRT_Readlink(path.fn_str(), buf, size); }
#endif
inline int wxStat(const wxString& path, wxStructStat *buf)
{ return wxCRT_Stat(path.fn_str(), buf); }
inline int wxLstat(const wxString& path, wxStructStat *buf)

View File

@@ -389,6 +389,9 @@ public:
return !m_dontFollowLinks;
}
// Resolve a wxFileName object representing a link to its target
wxFileName ResolveLink();
#if defined(__WIN32__) && wxUSE_OLE
// if the path is a shortcut, return the target and optionally,
// the arguments

View File

@@ -1138,6 +1138,24 @@ public:
*/
bool ReplaceHomeDir(wxPathFormat format = wxPATH_NATIVE);
/**
Find the absolute path of the file/directory that is pointed to by this
path.
If this path isn't a symlink, then this function will return the current
path. If the path does not exist on disk, An empty wxFileName instance
will be returned.
@note This is only supported on Unix-like platforms (e.g. wxGTK, wxOSX),
on other platforms (e.g. wxMSW) this function just returns the
current path.
@since 3.1.5
@return The absolute path that the current symlink path points to.
*/
wxFileName ResolveLink();
/**
Deletes the specified directory from the file system.

View File

@@ -1668,6 +1668,56 @@ bool wxFileName::GetShortcutTarget(const wxString& shortcutPath,
#endif // __WIN32__
// ----------------------------------------------------------------------------
// Resolve links
// ----------------------------------------------------------------------------
wxFileName wxFileName::ResolveLink()
{
wxString link = GetFullPath();
wxFileName linkTarget( *this );
// Only resolve links on platforms with readlink (e.g. Unix-like platforms)
#if defined(wxHAS_NATIVE_READLINK)
wxStructStat st;
// This means the link itself doesn't exist, so return an empty filename
if ( !StatAny(st, link, wxFILE_EXISTS_NO_FOLLOW) )
{
linkTarget.Clear();
return linkTarget;
}
// If it isn't an actual link, bail out just and return the link as the result
if ( !S_ISLNK(st.st_mode) )
return linkTarget;
// Dynamically compute the buffer size from the stat call, but fallback if it isn't valid
int bufSize = 4096;
if( st.st_size != 0 )
bufSize = st.st_size + 1;
char buf[bufSize];
int result = wxReadlink(link, buf, bufSize - 1);
if ( result != -1 )
{
buf[result] = '\0'; // readlink() doesn't NULL-terminate the buffer
linkTarget.Assign( wxString(buf, wxConvLibc) );
// Ensure the resulting path is absolute since readlink can return paths relative to the link
if ( !linkTarget.IsAbsolute() )
linkTarget.MakeAbsolute(GetPath());
}
else
{
// This means the lookup failed for some reason
linkTarget.Clear();
}
#endif
return linkTarget;
}
// ----------------------------------------------------------------------------
// absolute/relative paths

View File

@@ -842,25 +842,71 @@ void FileNameTestCase::TestSymlinks()
wxFileName targetfn(wxFileName::CreateTempFileName(tempdir));
CPPUNIT_ASSERT(targetfn.FileExists());
// Resolving a non-symlink will just return the same thing
CPPUNIT_ASSERT_EQUAL_MESSAGE
(
"Non-symlink didn't resolve to the same file",
targetfn, targetfn.ResolveLink()
);
// Create a symlink to that file
wxFileName linktofile(tempdir, "linktofile");
CPPUNIT_ASSERT_EQUAL(0, symlink(targetfn.GetFullPath().c_str(),
linktofile.GetFullPath().c_str()));
linktofile.GetFullPath().c_str()));
// Test resolving the filename to the symlink
CPPUNIT_ASSERT_EQUAL_MESSAGE
(
"Failed to resolve symlink to file",
targetfn, linktofile.ResolveLink()
);
// Create a relative symlink to that file
wxFileName relativelinktofile(tempdir, "relativelinktofile");
wxString relativeFileName = "./" + targetfn.GetFullName();
CPPUNIT_ASSERT_EQUAL(0, symlink(relativeFileName.c_str(),
relativelinktofile.GetFullPath().c_str()));
CPPUNIT_ASSERT_EQUAL_MESSAGE
(
"Failed to resolve relative symlink to absolute file path",
targetfn, relativelinktofile.ResolveLink()
);
// ... and another to the temporary directory
const wxString linktodirName(tempdir + "/linktodir");
wxFileName linktodirfn(linktodirName);
wxFileName linktodir(wxFileName::DirName(linktodirName));
CPPUNIT_ASSERT_EQUAL(0, symlink(tmpfn.GetFullPath().c_str(),
linktodirName.c_str()));
linktodirName.c_str()));
CPPUNIT_ASSERT_EQUAL_MESSAGE
(
"Failed to resolve symlink to directory",
tmpfn, linktodirfn.ResolveLink()
);
// And symlinks to both of those symlinks
wxFileName linktofilelnk(tempdir, "linktofilelnk");
CPPUNIT_ASSERT_EQUAL(0, symlink(linktofile.GetFullPath().c_str(),
linktofilelnk.GetFullPath().c_str()));
CPPUNIT_ASSERT_EQUAL_MESSAGE
(
"Failed to resolve symlink to file symlink",
targetfn, linktofilelnk.ResolveLink()
);
wxFileName linktodirlnk(tempdir, "linktodirlnk");
CPPUNIT_ASSERT_EQUAL(0, symlink(linktodir.GetFullPath().c_str(),
linktodirlnk.GetFullPath().c_str()));
CPPUNIT_ASSERT_EQUAL_MESSAGE
(
"Failed to resolve symlink to directory symlink",
tmpfn, linktodirlnk.ResolveLink()
);
// Run the tests twice: once in the default symlink following mode and the
// second time without following symlinks.
bool deref = true;
@@ -969,6 +1015,14 @@ void FileNameTestCase::TestSymlinks()
// Finally test Exists() after removing the file.
CPPUNIT_ASSERT(wxRemoveFile(targetfn.GetFullPath()));
// Resolving a file that doesn't exist returns empty
CPPUNIT_ASSERT_EQUAL_MESSAGE
(
"Non-existant file didn't resolve correctly",
wxFileName(), targetfn.ResolveLink()
);
// This should succeed, as the symlink still exists and
// the default wxFILE_EXISTS_ANY implies wxFILE_EXISTS_NO_FOLLOW
CPPUNIT_ASSERT(wxFileName(tempdir, "linktofile").Exists());