Skip to content

Commit 7e61e09

Browse files
committed
Fix IOUtils#fsync on Windows fsyncing directories (#43008)
Fsyncing directories on Windows is not possible. We always suppressed this by allowing that an AccessDeniedException is thrown when attemping to open the directory for reading. Yet, this suppression also allowed other IOExceptions to be suppressed, and that was a bug (e.g., the directory not existing, or a filesystem error and reasons that we might get an access denied there, like genuine permissions issues). This leniency was previously removed yet it exposed that we were suppressing this case on Windows. Rather than relying on exceptions for flow control and continuing to suppress there, we simply return early if attempting to fsync a directory on Windows (we will not put this burden on the caller).
1 parent 27b39bd commit 7e61e09

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

libs/core/src/main/java/org/elasticsearch/core/internal/io/IOUtils.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.nio.file.FileVisitResult;
2525
import java.nio.file.FileVisitor;
2626
import java.nio.file.Files;
27+
import java.nio.file.NoSuchFileException;
2728
import java.nio.file.Path;
2829
import java.nio.file.StandardOpenOption;
2930
import java.nio.file.attribute.BasicFileAttributes;
@@ -249,6 +250,7 @@ public FileVisitResult visitFileFailed(final Path file, final IOException exc) t
249250
}
250251

251252
// TODO: replace with constants class if needed (cf. org.apache.lucene.util.Constants)
253+
private static final boolean WINDOWS = System.getProperty("os.name").startsWith("Windows");
252254
private static final boolean LINUX = System.getProperty("os.name").startsWith("Linux");
253255
private static final boolean MAC_OS_X = System.getProperty("os.name").startsWith("Mac OS X");
254256

@@ -263,6 +265,14 @@ public FileVisitResult visitFileFailed(final Path file, final IOException exc) t
263265
* systems and operating systems allow to fsync on a directory)
264266
*/
265267
public static void fsync(final Path fileToSync, final boolean isDir) throws IOException {
268+
if (isDir && WINDOWS) {
269+
// opening a directory on Windows fails, directories can not be fsynced there
270+
if (Files.exists(fileToSync) == false) {
271+
// yet do not suppress trying to fsync directories that do not exist
272+
throw new NoSuchFileException(fileToSync.toString());
273+
}
274+
return;
275+
}
266276
try (FileChannel file = FileChannel.open(fileToSync, isDir ? StandardOpenOption.READ : StandardOpenOption.WRITE)) {
267277
try {
268278
file.force(true);

libs/core/src/test/java/org/elasticsearch/core/internal/io/IOUtilsTests.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.apache.lucene.mockfile.FilterFileSystemProvider;
2121
import org.apache.lucene.mockfile.FilterPath;
22+
import org.apache.lucene.util.Constants;
2223
import org.elasticsearch.common.CheckedConsumer;
2324
import org.elasticsearch.common.io.PathUtils;
2425
import org.elasticsearch.test.ESTestCase;
@@ -27,14 +28,20 @@
2728
import java.io.IOException;
2829
import java.io.OutputStream;
2930
import java.net.URI;
31+
import java.nio.channels.FileChannel;
3032
import java.nio.charset.StandardCharsets;
3133
import java.nio.file.AccessDeniedException;
3234
import java.nio.file.FileSystem;
3335
import java.nio.file.Files;
36+
import java.nio.file.NoSuchFileException;
37+
import java.nio.file.OpenOption;
3438
import java.nio.file.Path;
39+
import java.nio.file.attribute.FileAttribute;
3540
import java.util.ArrayList;
3641
import java.util.Arrays;
3742
import java.util.List;
43+
import java.util.Objects;
44+
import java.util.Set;
3845
import java.util.function.Function;
3946

4047
import static org.hamcrest.Matchers.arrayWithSize;
@@ -214,6 +221,43 @@ public void testFsyncDirectory() throws Exception {
214221
// no exception
215222
}
216223

224+
private static final class AccessDeniedWhileOpeningDirectoryFileSystem extends FilterFileSystemProvider {
225+
226+
AccessDeniedWhileOpeningDirectoryFileSystem(final FileSystem delegate) {
227+
super("access_denied://", Objects.requireNonNull(delegate));
228+
}
229+
230+
@Override
231+
public FileChannel newFileChannel(
232+
final Path path,
233+
final Set<? extends OpenOption> options,
234+
final FileAttribute<?>... attrs) throws IOException {
235+
if (Files.isDirectory(path)) {
236+
throw new AccessDeniedException(path.toString());
237+
}
238+
return delegate.newFileChannel(path, options, attrs);
239+
}
240+
241+
}
242+
243+
public void testFsyncAccessDeniedOpeningDirectory() throws Exception {
244+
final Path path = createTempDir().toRealPath();
245+
final FileSystem fs = new AccessDeniedWhileOpeningDirectoryFileSystem(path.getFileSystem()).getFileSystem(URI.create("file:///"));
246+
final Path wrapped = new FilterPath(path, fs);
247+
if (Constants.WINDOWS) {
248+
// no exception, we early return and do not even try to open the directory
249+
IOUtils.fsync(wrapped, true);
250+
} else {
251+
expectThrows(AccessDeniedException.class, () -> IOUtils.fsync(wrapped, true));
252+
}
253+
}
254+
255+
public void testFsyncNonExistentDirectory() throws Exception {
256+
final Path dir = FilterPath.unwrap(createTempDir()).toRealPath();
257+
final Path nonExistentDir = dir.resolve("non-existent");
258+
expectThrows(NoSuchFileException.class, () -> IOUtils.fsync(nonExistentDir, true));
259+
}
260+
217261
public void testFsyncFile() throws IOException {
218262
final Path path = createTempDir().toRealPath();
219263
final Path subPath = path.resolve(randomAlphaOfLength(8));

0 commit comments

Comments
 (0)