From 26ca3790b26b3153090462fbb9264e5fdda75153 Mon Sep 17 00:00:00 2001 From: Tomasz Maciejewski Date: Fri, 20 Dec 2024 20:39:11 +0100 Subject: [PATCH 1/2] Add support for symlinks in FileWatcher This commit allows using symlinks for SSL certificate hot reloading. See gh-43586 --- .../boot/autoconfigure/ssl/FileWatcher.java | 13 ++++++++++++- .../boot/autoconfigure/ssl/FileWatcherTests.java | 12 ++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) 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..3e590a55947 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 @@ -86,7 +86,7 @@ class FileWatcher implements Closeable { this.thread = new WatcherThread(); this.thread.start(); } - this.thread.register(new Registration(paths, action)); + this.thread.register(new Registration(resolveSymlinks(paths), action)); } catch (IOException ex) { throw new UncheckedIOException("Failed to register paths for watching: " + paths, ex); @@ -94,6 +94,17 @@ class FileWatcher implements Closeable { } } + private Set resolveSymlinks(Set paths) throws IOException { + Set result = new HashSet<>(); + for (Path path : paths) { + result.add(path); + if (Files.isSymbolicLink(path)) { + result.add(Files.readSymbolicLink(path)); + } + } + return result; + } + @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..f0a2cc8b167 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 @@ -94,6 +94,18 @@ 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 shouldIgnoreNotWatchedFiles(@TempDir Path tempDir) throws Exception { Path watchedFile = tempDir.resolve("watched.txt"); From 916705538e64357ba6aa5564175caa1ec1def332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 24 Dec 2024 10:20:56 +0100 Subject: [PATCH 2/2] Polish "Add support for symlinks in FileWatcher" See gh-43586 --- .../boot/autoconfigure/ssl/FileWatcher.java | 21 ++++++++++--------- .../autoconfigure/ssl/FileWatcherTests.java | 16 +++++++++++++- 2 files changed, 26 insertions(+), 11 deletions(-) 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 3e590a55947..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(resolveSymlinks(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,15 +98,12 @@ class FileWatcher implements Closeable { } } - private Set resolveSymlinks(Set paths) throws IOException { - Set result = new HashSet<>(); - for (Path path : paths) { - result.add(path); - if (Files.isSymbolicLink(path)) { - result.add(Files.readSymbolicLink(path)); - } + private static Path resolveSymlinkIfNecessary(Path path) throws IOException { + if (Files.isSymbolicLink(path)) { + Path target = Files.readSymbolicLink(path); + return resolveSymlinkIfNecessary(target); } - return result; + return path; } @Override 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 f0a2cc8b167..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. @@ -106,6 +106,20 @@ class FileWatcherTests { 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");