Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix unhandled UnsupportedOperationException in Fallocate #1010

Open
wants to merge 3 commits into
base: next
Choose a base branch
from
Open
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
68 changes: 39 additions & 29 deletions src/freenet/support/io/Fallocate.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.nio.channels.FileChannel;

import freenet.support.Logger;
import freenet.support.math.MersenneTwister;

/**
* Provides access to operating system-specific {@code fallocate} and
Expand All @@ -20,6 +19,7 @@
public final class Fallocate {

private static final boolean IS_LINUX = Platform.isLinux();
private static final boolean IS_WINDOWS = Platform.isWindows();
private static final boolean IS_POSIX = !Platform.isWindows() && !Platform.isMac() && !Platform.isOpenBSD();
private static final boolean IS_ANDROID = Platform.isAndroid();

Expand All @@ -45,42 +45,49 @@ public static Fallocate forChannel(FileChannel channel, FileDescriptor fd, long
return new Fallocate(channel, getDescriptor(fd), final_filesize);
}

/**
* @throws IllegalArgumentException
*/
public Fallocate fromOffset(long offset) {
if(offset < 0 || offset > final_filesize) throw new IllegalArgumentException();
this.offset = offset;
return this;
}

/**
* This method only works for Linux, do not use it.
* @throws UnsupportedOperationException
*/
@Deprecated
public Fallocate keepSize() {
requireLinux("fallocate keep size");
if (!IS_LINUX) {
throw new UnsupportedOperationException("fallocate keep size is not supported on this file system");
}
mode |= FALLOC_FL_KEEP_SIZE;
return this;
}

private void requireLinux(String feature) {
if (!IS_LINUX) {
throwUnsupported(feature);
}
}

private void throwUnsupported(String feature) {
throw new UnsupportedOperationException(feature + " is not supported on this file system");
}

public void execute() throws IOException {
int errno = 0;
boolean isUnsupported = false;
if (IS_LINUX) {
final int result = FallocateHolder.fallocate(fd, mode, offset, final_filesize-offset);
errno = result == 0 ? 0 : Native.getLastError();
} else if (IS_POSIX) {
errno = FallocateHolderPOSIX.posix_fallocate(fd, offset, final_filesize-offset);
if (fd > 2) {
if (IS_LINUX) {
final int result = FallocateHolder.fallocate(fd, mode, offset, final_filesize-offset);
errno = result == 0 ? 0 : Native.getLastError();
} else if (IS_POSIX) {
errno = FallocateHolderPOSIX.posix_fallocate(fd, offset, final_filesize-offset);
} else {
isUnsupported = true;
}
} else {
isUnsupported = true;
}

if (isUnsupported || errno != 0) {
Logger.normal(this, "fallocate() failed; using legacy method; errno="+errno);
if (errno != 0) {
// OS supports fallocate() but it failed. Do not log if the OS does not support fallocate().
Logger.normal(this, "fallocate() failed; using legacy method; errno=" + errno);
}
legacyFill(channel, final_filesize, offset);
}
}
Expand Down Expand Up @@ -108,7 +115,8 @@ private static int getDescriptor(FileChannel channel) {
field.setAccessible(true);
return getDescriptor((FileDescriptor) field.get(channel));
} catch (final Exception e) {
throw new UnsupportedOperationException("unsupported FileChannel implementation", e);
// File descriptor is not supported: fd is null and unavailable, return 0
return 0;
}
}

Expand All @@ -119,20 +127,22 @@ private static int getDescriptor(FileDescriptor descriptor) {
field.setAccessible(true);
return (int) field.get(descriptor);
} catch (final Exception e) {
throw new UnsupportedOperationException("unsupported FileDescriptor implementation", e);
// File descriptor is not supported: fd is null and unavailable, return 0
return 0;
}
}

private static void legacyFill(FileChannel fc, long newLength, long offset) throws IOException {
MersenneTwister mt = new MersenneTwister();
byte[] b = new byte[4096];
ByteBuffer bb = ByteBuffer.wrap(b);
while (offset < newLength) {
bb.rewind();
mt.nextBytes(b);
offset += fc.write(bb, offset);
if (offset % (1024 * 1024 * 1024L) == 0) {
mt = new MersenneTwister();
if (IS_WINDOWS) {
// Windows do not create sparse files by default, so just write a byte at the end.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think we should assume that the user has whatever setting at the default value. Either verify that sparse files are disabled (which you would probably have to do on every access because it can change at any time), or don’t assume anything and always create files with the method that will allocate all of the space.

Copy link
Contributor Author

@torusrxxx torusrxxx Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a setting for the user, only the application can change this setting as far as I know (https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_set_sparse). The user can change an existing file into a sparse file via fsutill (https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/fsutil-sparse), but not for new files. (Edited: don't link to AI translated crap)

fc.write(ByteBuffer.allocate(1), newLength - 1);
} else {
// fill fc with zeros
byte[] b = new byte[4096];
ByteBuffer bb = ByteBuffer.wrap(b);
while (offset < newLength) {
bb.rewind();
offset += fc.write(bb, offset);
}
}
}
Expand Down