From 42ebf0eca543eb74c6069d705d0ed0924c5c6132 Mon Sep 17 00:00:00 2001
From: Seth Hillbrand <seth@kipro-pcb.com>
Date: Thu, 9 May 2024 15:28:38 -0700
Subject: [PATCH] Limit FSWatcher

Library watches only need a single directory or immediate children.  The
project watcher should have a sensible limit to the total number of
files it tries to track.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/15717
---
 common/advanced_config.cpp  |  7 +++++++
 eeschema/sch_base_frame.cpp |  2 +-
 include/advanced_config.h   | 10 ++++++++++
 kicad/project_tree_pane.cpp |  7 ++++++-
 pcbnew/pcb_base_frame.cpp   |  2 +-
 5 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/common/advanced_config.cpp b/common/advanced_config.cpp
index 7bab16d597..78659f9822 100644
--- a/common/advanced_config.cpp
+++ b/common/advanced_config.cpp
@@ -111,6 +111,7 @@ static const wxChar TriangulateSimplificationLevel[] = wxT( "TriangulateSimplifi
 static const wxChar TriangulateMinimumArea[] = wxT( "TriangulateMinimumArea" );
 static const wxChar EnableCacheFriendlyFracture[] = wxT( "EnableCacheFriendlyFracture" );
 static const wxChar EnableAPILogging[] = wxT( "EnableAPILogging" );
+static const wxChar MaxFileSystemWatchers[] = wxT( "MaxFileSystemWatchers" );
 } // namespace KEYS
 
 
@@ -265,6 +266,8 @@ ADVANCED_CFG::ADVANCED_CFG()
 
     m_EnableCacheFriendlyFracture = true;
 
+    m_MaxFilesystemWatchers = 16384;
+
     loadFromConfigFile();
 }
 
@@ -487,6 +490,10 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
                                                 &m_EnableCacheFriendlyFracture,
                                                 m_EnableCacheFriendlyFracture ) );
 
+    configParams.push_back( new PARAM_CFG_INT( true, AC_KEYS::MaxFileSystemWatchers,
+                                                  &m_MaxFilesystemWatchers, m_MaxFilesystemWatchers,
+                                                  0, 2147483647 ) );
+
     // Special case for trace mask setting...we just grab them and set them immediately
     // Because we even use wxLogTrace inside of advanced config
     wxString traceMasks;
diff --git a/eeschema/sch_base_frame.cpp b/eeschema/sch_base_frame.cpp
index e81aacc003..67aceaddd7 100644
--- a/eeschema/sch_base_frame.cpp
+++ b/eeschema/sch_base_frame.cpp
@@ -688,7 +688,7 @@ void SCH_BASE_FRAME::setSymWatcher( const LIB_ID* aID )
     fn.AssignDir( m_watcherFileName.GetPath() );
     fn.DontFollowLink();
 
-    m_watcher->AddTree( fn );
+    m_watcher->Add( fn );
 }
 
 
diff --git a/include/advanced_config.h b/include/advanced_config.h
index bb7d05a4e4..978ca53527 100644
--- a/include/advanced_config.h
+++ b/include/advanced_config.h
@@ -580,6 +580,16 @@ public:
      * Log IPC API requests and responses
      */
     bool m_EnableAPILogging;
+
+    /**
+     * Maximum number of filesystem watchers to use.
+     *
+     * Setting name: "MaxFilesystemWatchers"
+     * Valid values: 0 to 2147483647
+     * Default value: 16384
+     */
+    int m_MaxFilesystemWatchers;
+
 ///@}
 
 private:
diff --git a/kicad/project_tree_pane.cpp b/kicad/project_tree_pane.cpp
index 6fc673cf39..6d28a0a6ff 100644
--- a/kicad/project_tree_pane.cpp
+++ b/kicad/project_tree_pane.cpp
@@ -1435,8 +1435,9 @@ void PROJECT_TREE_PANE::FileWatcherReset()
     std::stack < wxTreeItemId > subdirs_id;
 
     wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
+    int total_watch_count = 0;
 
-    while( true )
+    while( total_watch_count < ADVANCED_CFG::GetCfg().m_MaxFilesystemWatchers )
     {
         if( !kid.IsOk() )
         {
@@ -1468,6 +1469,7 @@ void PROJECT_TREE_PANE::FileWatcherReset()
             {
                 fn.AssignDir( path );
                 m_watcher->Add( fn );
+                total_watch_count++;
 
                 // if kid is a subdir, push in list to explore it later
                 if( itemData->IsPopulated() && m_TreeProject->GetChildrenCount( kid ) )
@@ -1479,6 +1481,9 @@ void PROJECT_TREE_PANE::FileWatcherReset()
     }
 #endif
 
+    if( total_watch_count >= ADVANCED_CFG::GetCfg().m_MaxFilesystemWatchers )
+        wxLogTrace( tracePathsAndFiles, "%s: too many directories to watch\n", __func__ );
+
 #if defined(DEBUG) && 1
     wxArrayString paths;
     m_watcher->GetWatchedPaths( &paths );
diff --git a/pcbnew/pcb_base_frame.cpp b/pcbnew/pcb_base_frame.cpp
index d5fbfde55f..b6930dce96 100644
--- a/pcbnew/pcb_base_frame.cpp
+++ b/pcbnew/pcb_base_frame.cpp
@@ -1185,7 +1185,7 @@ void PCB_BASE_FRAME::setFPWatcher( FOOTPRINT* aFootprint )
 
     wxLogTrace( "KICAD_LIB_WATCH", "Add watch: %s", fn.GetPath() );
 
-    m_watcher->AddTree( fn );
+    m_watcher->Add( fn );
 }