@@ -296,44 +296,76 @@ func (s *Scanner) watchForChanges() {
296
296
297
297
// scanSingleFile scans a single file and executes all registered callbacks
298
298
func (s * Scanner ) scanSingleFile (filePath string ) error {
299
- // Set scanning state to true
300
- s .scanMutex .Lock ()
301
- wasScanning := s .scanning
302
- s .scanning = true
303
- if ! wasScanning {
304
- // Only broadcast if status changed from not scanning to scanning
305
- s .statusChan <- true
299
+ return s .scanSingleFileWithDepth (filePath , make (map [string ]bool ), 0 )
300
+ }
301
+
302
+ // scanSingleFileWithDepth scans a single file with recursion protection
303
+ func (s * Scanner ) scanSingleFileWithDepth (filePath string , visited map [string ]bool , depth int ) error {
304
+ // Maximum recursion depth to prevent infinite recursion
305
+ const maxDepth = 10
306
+
307
+ if depth > maxDepth {
308
+ logger .Warn ("Maximum recursion depth reached for file:" , filePath )
309
+ return nil
306
310
}
307
- s .scanMutex .Unlock ()
308
311
309
- // Ensure we reset scanning state when done
310
- defer func () {
312
+ // Resolve the absolute path to handle symlinks properly
313
+ absPath , err := filepath .Abs (filePath )
314
+ if err != nil {
315
+ logger .Error ("Failed to resolve absolute path for:" , filePath , err )
316
+ return err
317
+ }
318
+
319
+ // Check for circular includes
320
+ if visited [absPath ] {
321
+ // Circular include detected, skip this file
322
+ return nil
323
+ }
324
+
325
+ // Mark this file as visited
326
+ visited [absPath ] = true
327
+
328
+ // Set scanning state to true only for the root call (depth 0)
329
+ var wasScanning bool
330
+ if depth == 0 {
311
331
s .scanMutex .Lock ()
312
- s .scanning = false
313
- // Broadcast the completion
314
- s .statusChan <- false
332
+ wasScanning = s .scanning
333
+ s .scanning = true
334
+ if ! wasScanning {
335
+ // Only broadcast if status changed from not scanning to scanning
336
+ s .statusChan <- true
337
+ }
315
338
s .scanMutex .Unlock ()
316
- }()
339
+
340
+ // Ensure we reset scanning state when done (only for root call)
341
+ defer func () {
342
+ s .scanMutex .Lock ()
343
+ s .scanning = false
344
+ // Broadcast the completion
345
+ s .statusChan <- false
346
+ s .scanMutex .Unlock ()
347
+ }()
348
+ }
317
349
318
350
// Open the file
319
- file , err := os .Open (filePath )
351
+ file , err := os .Open (absPath )
320
352
if err != nil {
321
353
return err
322
354
}
323
355
defer file .Close ()
324
356
325
357
// Read the entire file content
326
- content , err := os .ReadFile (filePath )
358
+ content , err := os .ReadFile (absPath )
327
359
if err != nil {
328
360
return err
329
361
}
330
362
331
363
// Execute all registered callbacks
332
364
scanCallbacksMutex .RLock ()
333
365
for _ , callback := range scanCallbacks {
334
- err := callback (filePath , content )
366
+ err := callback (absPath , content )
335
367
if err != nil {
336
- logger .Error ("Callback error for file" , filePath , ":" , err )
368
+ logger .Error ("Callback error for file" , absPath , ":" , err )
337
369
}
338
370
}
339
371
scanCallbacksMutex .RUnlock ()
@@ -363,7 +395,7 @@ func (s *Scanner) scanSingleFile(filePath string) error {
363
395
for _ , matchedFile := range matchedFiles {
364
396
fileInfo , err := os .Stat (matchedFile )
365
397
if err == nil && ! fileInfo .IsDir () {
366
- err = s .scanSingleFile (matchedFile )
398
+ err = s .scanSingleFileWithDepth (matchedFile , visited , depth + 1 )
367
399
if err != nil {
368
400
logger .Error ("Failed to scan included file:" , matchedFile , err )
369
401
}
@@ -379,7 +411,7 @@ func (s *Scanner) scanSingleFile(filePath string) error {
379
411
380
412
fileInfo , err := os .Stat (includePath )
381
413
if err == nil && ! fileInfo .IsDir () {
382
- err = s .scanSingleFile (includePath )
414
+ err = s .scanSingleFileWithDepth (includePath , visited , depth + 1 )
383
415
if err != nil {
384
416
logger .Error ("Failed to scan included file:" , includePath , err )
385
417
}
0 commit comments