Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,17 @@ public void putFile(final String uuid, final String filename, final long size, f
Files.createDirectory(uuidPath);
}
final Path file = filePath(uuidPath, filename);
Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
// Write to a temporary file in the same directory and then atomically move it into place. A plain
// Files.copy(REPLACE_EXISTING) deletes the target before recreating it, which exposes a window where a
// concurrent read sees the file missing or partially written and fails with a 500. ATOMIC_MOVE (a rename)
// ensures readers always observe either the complete old file or the complete new one.
final Path tempFile = Files.createTempFile(uuidPath, filename + ".", ".tmp");
try {
Files.copy(in, tempFile, StandardCopyOption.REPLACE_EXISTING);
Files.move(tempFile, file, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
} finally {
Files.deleteIfExists(tempFile);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package nl.aerius.fileserver.local;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand Down Expand Up @@ -82,6 +83,16 @@ void testPutFileOverwrite() throws IOException {
assertEquals(overwriteContent, Files.readString(expectedFile.toPath()), "Content of file should be as expected.");
}

@Test
void testPutFileLeavesNoTempFile() throws IOException {
// The atomic-write path stages the content in a temp file before moving it into place; ensure that
// temp file is always cleaned up so it can't leak into directory listings or repeated overwrites.
service.putFile(UUID_CODE, FILENAME, 10, null, new ByteArrayInputStream(CONTENT.getBytes()));
service.putFile(UUID_CODE, FILENAME, 0, null, new ByteArrayInputStream(CONTENT.getBytes()));
final String[] storedFiles = expectedFile.getParentFile().list();
assertArrayEquals(new String[] {FILENAME}, storedFiles, "Only the target file should remain, no temp residue.");
}

@Test
void testGetFile() throws IOException {
writeTempFile();
Expand Down
Loading