Merge branch '3.5.x' into 4.0.x

Closes gh-50185
This commit is contained in:
Andy Wilkinson
2026-04-23 08:55:02 +01:00
2 changed files with 58 additions and 15 deletions
@@ -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<PosixFilePermission> permissions = Files.getPosixFilePermissions(file.toPath());
Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(file, LinkOption.NOFOLLOW_LINKS);
for (PosixFilePermission permission : WRITE_PERMISSIONS) {
if (permissions.contains(permission)) {
return true;
@@ -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);