@@ -2,6 +2,7 @@ package cache
2
2
3
3
import (
4
4
"context"
5
+ "fmt"
5
6
"io/fs"
6
7
"os"
7
8
"path/filepath"
@@ -132,11 +133,24 @@ func (s *Scanner) watchAllDirectories() error {
132
133
return filepath .SkipDir
133
134
}
134
135
135
- if err := s .watcher .Add (path ); err != nil {
136
- logger .Error ("Failed to watch directory:" , path , err )
136
+ // Resolve symlinks to get the actual directory path to watch
137
+ actualPath := path
138
+ if d .Type ()& os .ModeSymlink != 0 {
139
+ // This is a symlink, resolve it to get the target path
140
+ if resolvedPath , err := filepath .EvalSymlinks (path ); err == nil {
141
+ actualPath = resolvedPath
142
+ logger .Debug ("Resolved symlink for watching:" , path , "->" , actualPath )
143
+ } else {
144
+ logger .Debug ("Failed to resolve symlink, skipping:" , path , err )
145
+ return filepath .SkipDir
146
+ }
147
+ }
148
+
149
+ if err := s .watcher .Add (actualPath ); err != nil {
150
+ logger .Error ("Failed to watch directory:" , actualPath , err )
137
151
return err
138
152
}
139
- // logger.Debug("Watching directory:", path )
153
+ logger .Debug ("Watching directory:" , actualPath )
140
154
}
141
155
return nil
142
156
})
@@ -216,12 +230,28 @@ func (s *Scanner) handleFileEvent(event fsnotify.Event) {
216
230
return
217
231
}
218
232
219
- fi , err := os .Stat (event .Name )
233
+ // Use Lstat to get symlink info without following it
234
+ fi , err := os .Lstat (event .Name )
220
235
if err != nil {
221
236
return
222
237
}
223
238
224
- if fi .IsDir () {
239
+ // If it's a symlink, we need to check what it points to
240
+ var targetIsDir bool
241
+ if fi .Mode ()& os .ModeSymlink != 0 {
242
+ // For symlinks, check the target
243
+ targetFi , err := os .Stat (event .Name )
244
+ if err != nil {
245
+ logger .Debug ("Symlink target not accessible:" , event .Name , err )
246
+ return
247
+ }
248
+ targetIsDir = targetFi .IsDir ()
249
+ logger .Debug ("Symlink changed:" , event .Name , "-> target is dir:" , targetIsDir )
250
+ } else {
251
+ targetIsDir = fi .IsDir ()
252
+ }
253
+
254
+ if targetIsDir {
225
255
logger .Debug ("Directory changed:" , event .Name )
226
256
} else {
227
257
logger .Debug ("File changed:" , event .Name )
@@ -252,10 +282,24 @@ func (s *Scanner) scanSingleFile(filePath string) error {
252
282
return nil
253
283
}
254
284
255
- // Skip symlinks to avoid potential issues
285
+ // Handle symlinks carefully
256
286
if fileInfo .Mode ()& os .ModeSymlink != 0 {
257
- logger .Debugf ("Skipping symlink: %s" , filePath )
258
- return nil
287
+ // Check what the symlink points to
288
+ targetInfo , err := os .Stat (filePath )
289
+ if err != nil {
290
+ logger .Debugf ("Skipping symlink with inaccessible target: %s (%v)" , filePath , err )
291
+ return nil
292
+ }
293
+
294
+ // Skip symlinks to directories
295
+ if targetInfo .IsDir () {
296
+ logger .Debugf ("Skipping symlink to directory: %s" , filePath )
297
+ return nil
298
+ }
299
+
300
+ // Process symlinks to files, but use the target's info for size check
301
+ fileInfo = targetInfo
302
+ logger .Debugf ("Processing symlink to file: %s" , filePath )
259
303
}
260
304
261
305
// Skip non-regular files (devices, pipes, sockets, etc.)
@@ -326,7 +370,22 @@ func (s *Scanner) ScanAllConfigs() error {
326
370
return filepath .SkipDir
327
371
}
328
372
329
- // Only process regular files
373
+ // Handle symlinks to directories specially
374
+ if d .Type ()& os .ModeSymlink != 0 {
375
+ if targetInfo , err := os .Stat (path ); err == nil && targetInfo .IsDir () {
376
+ // This is a symlink to a directory, we should traverse its contents
377
+ // but not process the symlink itself as a file
378
+ logger .Debug ("Found symlink to directory, will traverse contents:" , path )
379
+
380
+ // Manually scan the symlink target directory since WalkDir doesn't follow symlinks
381
+ if err := s .scanSymlinkDirectory (path ); err != nil {
382
+ logger .Error ("Failed to scan symlink directory:" , path , err )
383
+ }
384
+ return nil
385
+ }
386
+ }
387
+
388
+ // Only process regular files (not directories, not symlinks to directories)
330
389
if ! d .IsDir () {
331
390
if err := s .scanSingleFile (path ); err != nil {
332
391
logger .Error ("Failed to scan config:" , path , err )
@@ -337,6 +396,45 @@ func (s *Scanner) ScanAllConfigs() error {
337
396
})
338
397
}
339
398
399
+ // scanSymlinkDirectory recursively scans a symlink directory and its contents
400
+ func (s * Scanner ) scanSymlinkDirectory (symlinkPath string ) error {
401
+ // Resolve the symlink to get the actual target path
402
+ targetPath , err := filepath .EvalSymlinks (symlinkPath )
403
+ if err != nil {
404
+ return fmt .Errorf ("failed to resolve symlink %s: %w" , symlinkPath , err )
405
+ }
406
+
407
+ logger .Debug ("Scanning symlink directory contents:" , symlinkPath , "->" , targetPath )
408
+
409
+ // Use WalkDir on the resolved target path
410
+ return filepath .WalkDir (targetPath , func (path string , d fs.DirEntry , err error ) error {
411
+ if err != nil {
412
+ return err
413
+ }
414
+
415
+ // Skip excluded directories
416
+ if d .IsDir () && shouldSkipPath (path ) {
417
+ return filepath .SkipDir
418
+ }
419
+
420
+ // Only process regular files (not directories, not symlinks to directories)
421
+ if ! d .IsDir () {
422
+ // Handle symlinks to directories (skip them)
423
+ if d .Type ()& os .ModeSymlink != 0 {
424
+ if targetInfo , err := os .Stat (path ); err == nil && targetInfo .IsDir () {
425
+ logger .Debug ("Skipping symlink to directory in symlink scan:" , path )
426
+ return nil
427
+ }
428
+ }
429
+
430
+ if err := s .scanSingleFile (path ); err != nil {
431
+ logger .Error ("Failed to scan config in symlink directory:" , path , err )
432
+ }
433
+ }
434
+ return nil
435
+ })
436
+ }
437
+
340
438
// Shutdown cleans up scanner resources
341
439
func (s * Scanner ) Shutdown () {
342
440
if s .watcher != nil {
0 commit comments