diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/FileWatcher.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/FileWatcher.java index eecad97b3b4..5ffcdbc8335 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/FileWatcher.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/FileWatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,7 +86,11 @@ class FileWatcher implements Closeable { this.thread = new WatcherThread(); this.thread.start(); } - this.thread.register(new Registration(paths, action)); + Set actualPaths = new HashSet<>(); + for (Path path : paths) { + actualPaths.add(resolveSymlinkIfNecessary(path)); + } + this.thread.register(new Registration(actualPaths, action)); } catch (IOException ex) { throw new UncheckedIOException("Failed to register paths for watching: " + paths, ex); @@ -94,6 +98,14 @@ class FileWatcher implements Closeable { } } + private static Path resolveSymlinkIfNecessary(Path path) throws IOException { + if (Files.isSymbolicLink(path)) { + Path target = Files.readSymbolicLink(path); + return resolveSymlinkIfNecessary(target); + } + return path; + } + @Override public void close() throws IOException { synchronized (this.lock) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/FileWatcherTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/FileWatcherTests.java index 5b3e18e3f79..498b31cb899 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/FileWatcherTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/FileWatcherTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -94,6 +94,32 @@ class FileWatcherTests { callback.expectChanges(); } + @Test + void shouldFollowSymlink(@TempDir Path tempDir) throws Exception { + Path realFile = tempDir.resolve("realFile.txt"); + Path symLink = tempDir.resolve("symlink.txt"); + Files.createFile(realFile); + Files.createSymbolicLink(symLink, realFile); + WaitingCallback callback = new WaitingCallback(); + this.fileWatcher.watch(Set.of(symLink), callback); + Files.writeString(realFile, "Some content"); + callback.expectChanges(); + } + + @Test + void shouldFollowSymlinkRecursively(@TempDir Path tempDir) throws Exception { + Path realFile = tempDir.resolve("realFile.txt"); + Path symLink = tempDir.resolve("symlink.txt"); + Path symLink2 = tempDir.resolve("symlink2.txt"); + Files.createFile(realFile); + Files.createSymbolicLink(symLink, symLink2); + Files.createSymbolicLink(symLink2, realFile); + WaitingCallback callback = new WaitingCallback(); + this.fileWatcher.watch(Set.of(symLink), callback); + Files.writeString(realFile, "Some content"); + callback.expectChanges(); + } + @Test void shouldIgnoreNotWatchedFiles(@TempDir Path tempDir) throws Exception { Path watchedFile = tempDir.resolve("watched.txt");