]> 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.
 
         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
         @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);
 
     /**
     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.
 
 
         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.
      */
     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);
 
     /**
      */
     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);
 
      */
     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 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 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);
 
     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
     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
 };
 
     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),
 // frame constructor
 MyFrame::MyFrame(const wxString& title)
     : wxFrame(NULL, wxID_ANY, title),
-      m_watcher(NULL)
+      m_watcher(NULL), m_followLinks(false)
 {
     SetIcon(wxICON(sample));
 
 {
     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_QUIT = wxID_EXIT,
         MENU_ID_CLEAR = wxID_CLEAR,
         MENU_ID_WATCH = 101,
+        MENU_ID_DEREFERENCE,
 
         BTN_ID_ADD = 200,
         BTN_ID_ADD_TREE,
 
         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);
 
     // 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");
     // 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));
             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)
 
     // 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);
 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;
 
     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:
     switch ( type )
     {
         case wxFSWPath_Dir:
-            ok = m_watcher->Add(wxFileName::DirName(filename));
+            ok = m_watcher->Add(fn);
             prefix = "Dir:  ";
             break;
 
         case wxFSWPath_Tree:
             prefix = "Dir:  ";
             break;
 
         case wxFSWPath_Tree:
-            ok = m_watcher->AddTree(wxFileName::DirName(filename));
+            ok = m_watcher->AddTree(fn);
             prefix = "Tree: ";
             break;
 
             prefix = "Tree: ";
             break;
 
@@ -390,15 +421,23 @@ void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))
         return;
 
     bool ret;
         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
     // 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
     {
     }
     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
 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());
     };
 
     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);
     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);
 
     // 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());
 #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);
     RemoveTraverser traverser(this, filespec);
-    dir.Traverse(traverser, filespec);
+    dir.Traverse(traverser, filespec, flags);
 
     // As in AddTree() above, handle the path itself explicitly.
     Remove(path);
 
     // 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) {}
 
     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());
         {
             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
 
             // 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);
                 }
                     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();
 
             // 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();
 
             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());
             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)
         }
 
         void WatchTreeWithFilespec(const wxFileName& dir)