]> git.saurik.com Git - wxWidgets.git/commitdiff
Respect wxFileName::DontFollowLink() in wxFileSystemWatcher.
authorVadim Zeitlin <vadim@wxwidgets.org>
Wed, 24 Oct 2012 18:21:31 +0000 (18:21 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Wed, 24 Oct 2012 18:21:31 +0000 (18:21 +0000)
Watch the link itself and not its target if DontFollowLink() had been called.

Closes #14543.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@72751 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

interface/wx/fswatcher.h
samples/fswatcher/fswatcher.cpp
src/common/fswatchercmn.cpp
tests/fswatcher/fswatchertest.cpp

index 271fb7021694a571589aa90e127fdf16c2b205cd..5041acc37bfd326a3cb9b355950b0b050fef4129 100644 (file)
@@ -60,6 +60,10 @@ public:
         to this directory itself or its immediate children will generate the
         events. Use AddTree() to monitor the directory recursively.
 
+        Note that on platforms that use symbolic links, you should consider the
+        possibility that @a path is a symlink. To watch the symlink itself and
+        not its target you may call wxFileName::DontFollowLink() on @a path.
+
         @param path
             The name of the path to watch.
         @param events
@@ -68,29 +72,38 @@ public:
     virtual bool Add(const wxFileName& path, int events = wxFSW_EVENT_ALL);
 
     /**
-        This is the same as Add(), but recursively adds every file/directory in
-        the tree rooted at @a path.
+        This is the same as Add(), but also recursively adds every
+        file/directory in the tree rooted at @a path.
 
         Additionally a file mask can be specified to include only files
         matching that particular mask.
 
-        This method is implemented efficiently under MSW but shouldn't be used
-        for the directories with a lot of children (such as e.g. the root
-        directory) under the other platforms as it calls Add() there for each
-        subdirectory potentially creating a lot of watches and taking a long
-        time to execute.
+        This method is implemented efficiently on MSW, but should be used with
+        care on other platforms for directories with lots of children (e.g. the
+        root directory) as it calls Add() for each subdirectory, potentially
+        creating a lot of watches and taking a long time to execute.
+
+        Note that on platforms that use symbolic links, you will probably want
+        to have called wxFileName::DontFollowLink on @a path. This is especially
+        important if the symlink targets may themselves be watched.
      */
     virtual bool AddTree(const wxFileName& path, int events = wxFSW_EVENT_ALL,
                          const wxString& filter = wxEmptyString);
 
     /**
         Removes @a path from the list of watched paths.
+
+        See the comment in Add() about symbolic links. @a path should treat
+        symbolic links in the same way as in the original Add() call.
      */
     virtual bool Remove(const wxFileName& path);
 
     /**
-        Same as Remove(), but also removes every file/directory belonging to
-        the tree rooted at @a path.
+        This is the same as Remove(), but also removes every file/directory
+        belonging to the tree rooted at @a path.
+
+        See the comment in AddTree() about symbolic links. @a path should treat
+        symbolic links in the same way as in the original AddTree() call.
      */
     virtual bool RemoveTree(const wxFileName& path);
 
index 25e7e2cc5cae2908e7b5e76bc0dd50c52d7e8d67..7d3d0815a3dc9371b8f0a382ea8128a8275ea62c 100644 (file)
@@ -46,11 +46,13 @@ private:
     void OnClear(wxCommandEvent& WXUNUSED(event)) { m_evtConsole->Clear(); }
     void OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(true); }
     void OnWatch(wxCommandEvent& event);
+    void OnFollowLinks(wxCommandEvent& event);
     void OnAbout(wxCommandEvent& event);
 
     void OnAdd(wxCommandEvent& event);
     void OnAddTree(wxCommandEvent& event);
     void OnRemove(wxCommandEvent& event);
+    void OnRemoveUpdateUI(wxUpdateUIEvent& event);
 
     void OnFileSystemEvent(wxFileSystemWatcherEvent& event);
     void LogEvent(const wxFileSystemWatcherEvent& event);
@@ -58,6 +60,7 @@ private:
     wxTextCtrl *m_evtConsole;         // events console
     wxListView *m_filesList;          // list of watched paths
     wxFileSystemWatcher* m_watcher;   // file system watcher
+    bool m_followLinks;               // should symlinks be dereferenced
 
     const static wxString LOG_FORMAT; // how to format events
 };
@@ -135,7 +138,7 @@ IMPLEMENT_APP(MyApp)
 // frame constructor
 MyFrame::MyFrame(const wxString& title)
     : wxFrame(NULL, wxID_ANY, title),
-      m_watcher(NULL)
+      m_watcher(NULL), m_followLinks(false)
 {
     SetIcon(wxICON(sample));
 
@@ -145,6 +148,7 @@ MyFrame::MyFrame(const wxString& title)
         MENU_ID_QUIT = wxID_EXIT,
         MENU_ID_CLEAR = wxID_CLEAR,
         MENU_ID_WATCH = 101,
+        MENU_ID_DEREFERENCE,
 
         BTN_ID_ADD = 200,
         BTN_ID_ADD_TREE,
@@ -166,6 +170,18 @@ MyFrame::MyFrame(const wxString& title)
     // started by default, because file system watcher is started by default
     it->Check(true);
 
+#if defined(__UNIX__)
+    // Let the user decide whether to dereference symlinks. If he makes the
+    // wrong choice, asserts will occur if the symlink target is also watched
+    it = menuMon->AppendCheckItem(MENU_ID_DEREFERENCE,
+                                  "&Follow symlinks\tCtrl-F",
+                                  _("If checked, dereference symlinks")
+                                 );
+    it->Check(false);
+    Connect(MENU_ID_DEREFERENCE, wxEVT_COMMAND_MENU_SELECTED,
+            wxCommandEventHandler(MyFrame::OnFollowLinks));
+#endif // __UNIX__
+
     // the "About" item should be in the help menu
     wxMenu *menuHelp = new wxMenu;
     menuHelp->Append(wxID_ABOUT, "&About\tF1", "Show about dialog");
@@ -263,6 +279,8 @@ MyFrame::MyFrame(const wxString& title)
             wxCommandEventHandler(MyFrame::OnAddTree));
     Connect(BTN_ID_REMOVE, wxEVT_COMMAND_BUTTON_CLICKED,
             wxCommandEventHandler(MyFrame::OnRemove));
+    Connect(BTN_ID_REMOVE, wxEVT_UPDATE_UI,
+            wxUpdateUIEventHandler(MyFrame::OnRemoveUpdateUI));
 
     // and show itself (the frames, unlike simple controls, are not shown when
     // created initially)
@@ -324,6 +342,11 @@ void MyFrame::OnWatch(wxCommandEvent& event)
     }
 }
 
+void MyFrame::OnFollowLinks(wxCommandEvent& event)
+{
+    m_followLinks = event.IsChecked();
+}
+
 void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event))
 {
     AddEntry(wxFSWPath_Dir);
@@ -353,15 +376,23 @@ void MyFrame::AddEntry(wxFSWPathType type, wxString filename)
 
     wxString prefix;
     bool ok = false;
+
+    // This will tell wxFileSystemWatcher whether to dereference symlinks
+    wxFileName fn = wxFileName::DirName(filename);
+    if (!m_followLinks)
+    {
+        fn.DontFollowLink();
+    }
+
     switch ( type )
     {
         case wxFSWPath_Dir:
-            ok = m_watcher->Add(wxFileName::DirName(filename));
+            ok = m_watcher->Add(fn);
             prefix = "Dir:  ";
             break;
 
         case wxFSWPath_Tree:
-            ok = m_watcher->AddTree(wxFileName::DirName(filename));
+            ok = m_watcher->AddTree(fn);
             prefix = "Tree: ";
             break;
 
@@ -390,15 +421,23 @@ void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))
         return;
 
     bool ret;
-    wxString path;
+    wxString path = m_filesList->GetItemText(idx).Mid(6);
+
+    // This will tell wxFileSystemWatcher whether to dereference symlinks
+    wxFileName fn = wxFileName::DirName(path);
+    if (!m_followLinks)
+    {
+        fn.DontFollowLink();
+    }
+
     // TODO we know it is a dir, but it doesn't have to be
-    if (m_filesList->GetItemText(idx).StartsWith("Dir:  ", &path))
+    if (m_filesList->GetItemText(idx).StartsWith("Dir:  "))
     {
-        ret = m_watcher->Remove(wxFileName::DirName(path));
+        ret = m_watcher->Remove(fn);
     }
-    else if (m_filesList->GetItemText(idx).StartsWith("Tree: ", &path))
+    else if (m_filesList->GetItemText(idx).StartsWith("Tree: "))
     {
-        ret = m_watcher->RemoveTree(wxFileName::DirName(path));
+        ret = m_watcher->RemoveTree(fn);
     }
     else
     {
@@ -415,6 +454,11 @@ void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))
     }
 }
 
+void MyFrame::OnRemoveUpdateUI(wxUpdateUIEvent& event)
+{
+    event.Enable(m_filesList->GetFirstSelected() != wxNOT_FOUND);
+}
+
 void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent& event)
 {
     // TODO remove when code is rock-solid
index 89cb09e4a8c8f41bd4c33619143d78c50ce2382a..9891bb7f3c3ce854a2d7fdeffb0598ad1f5cebff 100644 (file)
@@ -198,8 +198,14 @@ bool wxFileSystemWatcherBase::AddTree(const wxFileName& path, int events,
     };
 
     wxDir dir(path.GetFullPath());
+    // Prevent asserts or infinite loops in trees containing symlinks
+    int flags = wxDIR_DEFAULT; // TODO: we ignore files, so why use wxDIR_FILES?
+    if ( !path.ShouldFollowLink() )
+    {
+        flags |= wxDIR_NO_FOLLOW;
+    }
     AddTraverser traverser(this, events, filespec);
-    dir.Traverse(traverser, filespec);
+    dir.Traverse(traverser, filespec, flags);
 
     // Add the path itself explicitly as Traverse() doesn't return it.
     AddAny(path.GetPathWithSep(), events, wxFSWPath_Tree, filespec);
@@ -260,8 +266,17 @@ bool wxFileSystemWatcherBase::RemoveTree(const wxFileName& path)
 #endif // __WINDOWS__
 
     wxDir dir(path.GetFullPath());
+    // AddTree() might have used the wxDIR_NO_FOLLOW to prevent asserts or
+    // infinite loops in trees containing symlinks. We need to do the same
+    // or we'll try to remove unwatched items. Let's hope the caller used
+    // the same ShouldFollowLink() setting as in AddTree()...
+    int flags = wxDIR_DEFAULT;  // See the TODO in AddTree()
+    if ( !path.ShouldFollowLink() )
+    {
+        flags |= wxDIR_NO_FOLLOW;
+    }
     RemoveTraverser traverser(this, filespec);
-    dir.Traverse(traverser, filespec);
+    dir.Traverse(traverser, filespec, flags);
 
     // As in AddTree() above, handle the path itself explicitly.
     Remove(path);
index 3768c349bdc3a5626af295e77dc83bf3b73b5949..c77ceea5a72c0d7a27adb70ca9cdc5b151da015e 100644 (file)
@@ -650,12 +650,13 @@ void FileSystemWatcherTestCase::TestTrees()
     public:
         TreeTester() : subdirs(5), files(3) {}
 
-        void GrowTree(wxFileName dir)
+        void GrowTree(wxFileName dir, bool withSymlinks)
         {
             CPPUNIT_ASSERT(dir.Mkdir());
             // Now add a subdir with an easy name to remember in WatchTree()
             dir.AppendDir("child");
             CPPUNIT_ASSERT(dir.Mkdir());
+            wxFileName child(dir);  // Create a copy to which to symlink
 
             // Create a branch of 5 numbered subdirs, each containing 3
             // numbered files
@@ -672,6 +673,18 @@ void FileSystemWatcherTestCase::TestTrees()
                     wxFile(prefix + wxString::Format("file%u", f+1) + ext[f],
                            wxFile::write);
                 }
+#if defined(__UNIX__)
+                if ( withSymlinks )
+                {
+                    // Create a symlink to a files, and another to 'child'
+                    CPPUNIT_ASSERT_EQUAL(0,
+                        symlink(wxString(prefix + "file1").c_str(),
+                        wxString(prefix + "file.lnk").c_str()));
+                    CPPUNIT_ASSERT_EQUAL(0,
+                        symlink(child.GetFullPath().c_str(),
+                        wxString(prefix + "dir.lnk").c_str()));
+                }
+#endif // __UNIX__
             }
         }
 
@@ -720,7 +733,7 @@ void FileSystemWatcherTestCase::TestTrees()
             // Store the initial count; there may already be some watches
             const int initial = m_watcher->GetWatchedPathsCount();
 
-            GrowTree(dir);
+            GrowTree(dir, false /* no symlinks */);
 
             m_watcher->AddTree(dir);
             const int plustree = m_watcher->GetWatchedPathsCount();
@@ -750,6 +763,22 @@ void FileSystemWatcherTestCase::TestTrees()
             CPPUNIT_ASSERT(initial < m_watcher->GetWatchedPathsCount());
             m_watcher->RemoveTree(dir);
             CPPUNIT_ASSERT_EQUAL(initial, m_watcher->GetWatchedPathsCount());
+#if defined(__UNIX__)
+            // Finally, test a tree containing internal symlinks
+            RmDir(dir);
+            GrowTree(dir, true /* test symlinks */);
+
+            // Without the DontFollowLink() call AddTree() would now assert
+            // (and without the assert, it would infinitely loop)
+            wxFileName fn = dir;
+            fn.DontFollowLink();
+            CPPUNIT_ASSERT(m_watcher->AddTree(fn));
+            CPPUNIT_ASSERT(m_watcher->RemoveTree(fn));
+
+            // Regrow the tree without symlinks, ready for the next test
+            RmDir(dir);
+            GrowTree(dir, false);
+#endif // __UNIX__
         }
 
         void WatchTreeWithFilespec(const wxFileName& dir)