diff --git a/core/spring-boot/src/main/java/org/springframework/boot/system/ApplicationPid.java b/core/spring-boot/src/main/java/org/springframework/boot/system/ApplicationPid.java index 0e71c292b37..7543d4f9f74 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/system/ApplicationPid.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/system/ApplicationPid.java @@ -18,9 +18,11 @@ package org.springframework.boot.system; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.nio.file.attribute.PosixFilePermission; import java.util.Set; @@ -106,31 +108,31 @@ public class ApplicationPid { */ public void write(File file) throws IOException { Assert.state(this.pid != null, "No PID available"); - createParentDirectory(file); - if (file.exists()) { - assertCanOverwrite(file); - } - try (FileWriter writer = new FileWriter(file)) { - writer.append(String.valueOf(this.pid)); + Path path = file.toPath(); + createParentDirectory(path); + if (Files.exists(path, LinkOption.NOFOLLOW_LINKS)) { + assertCanOverwrite(path); } + Files.writeString(path, this.pid.toString(), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE, + LinkOption.NOFOLLOW_LINKS); } - private void createParentDirectory(File file) { - File parent = file.getParentFile(); + private void createParentDirectory(Path path) throws IOException { + Path parent = path.getParent(); if (parent != null) { - parent.mkdirs(); + Files.createDirectories(parent); } } - private void assertCanOverwrite(File file) throws IOException { - if (!file.canWrite() || !canWritePosixFile(file)) { - throw new FileNotFoundException(file + " (permission denied)"); + private void assertCanOverwrite(Path file) throws IOException { + if (!Files.isWritable(file) || !canWritePosixFile(file)) { + throw new FileNotFoundException(file.toString() + " (permission denied)"); } } - private boolean canWritePosixFile(File file) throws IOException { + private boolean canWritePosixFile(Path file) throws IOException { try { - Set permissions = Files.getPosixFilePermissions(file.toPath()); + Set permissions = Files.getPosixFilePermissions(file, LinkOption.NOFOLLOW_LINKS); for (PosixFilePermission permission : WRITE_PERMISSIONS) { if (permissions.contains(permission)) { return true; diff --git a/core/spring-boot/src/test/java/org/springframework/boot/system/ApplicationPidTests.java b/core/spring-boot/src/test/java/org/springframework/boot/system/ApplicationPidTests.java index 3f97831cd62..9da1d3e90f9 100644 --- a/core/spring-boot/src/test/java/org/springframework/boot/system/ApplicationPidTests.java +++ b/core/spring-boot/src/test/java/org/springframework/boot/system/ApplicationPidTests.java @@ -17,11 +17,18 @@ package org.springframework.boot.system; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIOException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.contentOf; @@ -71,6 +78,40 @@ class ApplicationPidTests { assertThat(contentOf(file)).isEqualTo("123"); } + @Test + void overwriteExistingPid() throws Exception { + File file = new File(this.tempDir, "pid"); + new ApplicationPid(123L).write(file); + assertThat(contentOf(file)).isEqualTo("123"); + new ApplicationPid(456L).write(file); + assertThat(contentOf(file)).isEqualTo("456"); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + void whenSymlinkToNonExistentTargetExistsAtPidFileLocationWriteThrows() throws IOException { + File link = new File(this.tempDir, "pid"); + File target = new File(this.tempDir, "target"); + Files.createSymbolicLink(link.toPath(), target.toPath()); + ApplicationPid pid = new ApplicationPid(123L); + assertThatIOException().isThrownBy(() -> pid.write(link)); + assertThat(Files.isSymbolicLink(link.toPath())).isTrue(); + assertThat(target).doesNotExist(); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + void whenSymlinkToTargetExistsAtPidFileLocationWriteThrows() throws IOException { + File link = new File(this.tempDir, "pid"); + Path target = new File(this.tempDir, "target").toPath(); + Files.write(target, "target".getBytes(), StandardOpenOption.CREATE_NEW); + Files.createSymbolicLink(link.toPath(), target); + ApplicationPid pid = new ApplicationPid(123L); + assertThatIOException().isThrownBy(() -> pid.write(link)); + assertThat(Files.isSymbolicLink(link.toPath())).isTrue(); + assertThat(target).hasContent("target"); + } + @Test void toLong() { ApplicationPid pid = new ApplicationPid(123L);