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 #1008

Closed
wants to merge 2 commits into from
Closed
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
251 changes: 134 additions & 117 deletions src/freenet/support/io/Fallocate.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,121 +19,138 @@
*/
public final class Fallocate {

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

private static final int FALLOC_FL_KEEP_SIZE = 0x01;

private final int fd;
private int mode;
private long offset;
private final long final_filesize;
private final FileChannel channel;

private Fallocate(FileChannel channel, int fd, long final_filesize) {
this.fd = fd;
this.final_filesize = final_filesize;
this.channel = channel;
}

public static Fallocate forChannel(FileChannel channel, long final_filesize) {
return new Fallocate(channel, getDescriptor(channel), final_filesize);
}

public static Fallocate forChannel(FileChannel channel, FileDescriptor fd, long final_filesize) {
return new Fallocate(channel, getDescriptor(fd), final_filesize);
}

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

public Fallocate keepSize() {
requireLinux("fallocate keep size");
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);
} else {
isUnsupported = true;
}

if (isUnsupported || errno != 0) {
Logger.normal(this, "fallocate() failed; using legacy method; errno="+errno);
legacyFill(channel, final_filesize, offset);
}
}

private static class FallocateHolder {
static {
Native.register(FallocateHolder.class, Platform.C_LIBRARY_NAME);
}

private static native int fallocate(int fd, int mode, long offset, long length);
}

private static class FallocateHolderPOSIX {
static {
Native.register(FallocateHolderPOSIX.class, Platform.C_LIBRARY_NAME);
}

private static native int posix_fallocate(int fd, long offset, long length);
}

private static int getDescriptor(FileChannel channel) {
try {
// sun.nio.ch.FileChannelImpl declares private final java.io.FileDescriptor fd
final Field field = channel.getClass().getDeclaredField("fd");
field.setAccessible(true);
return getDescriptor((FileDescriptor) field.get(channel));
} catch (final Exception e) {
throw new UnsupportedOperationException("unsupported FileChannel implementation", e);
}
}

private static int getDescriptor(FileDescriptor descriptor) {
try {
// Oracle java.io.FileDescriptor declares private int fd
final Field field = descriptor.getClass().getDeclaredField(IS_ANDROID ? "descriptor" : "fd");
field.setAccessible(true);
return (int) field.get(descriptor);
} catch (final Exception e) {
throw new UnsupportedOperationException("unsupported FileDescriptor implementation", e);
}
}

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();
}
}
}
private static final boolean IS_LINUX = Platform.isLinux();
private static final boolean IS_POSIX = !Platform.isWindows() && !Platform.isMac() && !Platform.isOpenBSD();
private static final boolean IS_ANDROID = Platform.isAndroid();

private static final int FALLOC_FL_KEEP_SIZE = 0x01;

private final int fd;
private int mode;
private long offset;
private final long final_filesize;
private final FileChannel channel;

private Fallocate(FileChannel channel, int fd, long final_filesize) {
this.fd = fd;
this.final_filesize = final_filesize;
this.channel = channel;
}

public static Fallocate forChannel(FileChannel channel, long final_filesize) {
try {
return new Fallocate(channel, getDescriptor(channel), final_filesize);
} catch (final UnsupportedOperationException exception) {
return new Fallocate(channel, 0, final_filesize);
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be good to have a comment here that explains why 0 is the right file descriptor.

}
}

public static Fallocate forChannel(FileChannel channel, FileDescriptor fd, long final_filesize) {
try {
return new Fallocate(channel, getDescriptor(fd), final_filesize);
} catch (final UnsupportedOperationException exception) {
return new Fallocate(channel, 0, final_filesize);
}
}

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

public Fallocate keepSize() throws UnsupportedOperationException {
requireLinux("fallocate keep size");
mode |= FALLOC_FL_KEEP_SIZE;
return this;
}

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

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

public void execute() throws IOException {
int errno = 0;
boolean isUnsupported = false;
if (fd != 0) {
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 {
if (Platform.isWindows()) {
// On Windows, just write a byte at the end.
channel.write(ByteBuffer.allocate(1), final_filesize - 1);
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this safe?

} else {
isUnsupported = true;
}
}

if (isUnsupported || errno != 0) {
Logger.normal(this, "fallocate() failed; using legacy method; errno=" + errno);
legacyFill(channel, final_filesize, offset);
}
}

private static class FallocateHolder {
static {
Native.register(FallocateHolder.class, Platform.C_LIBRARY_NAME);
}

private static native int fallocate(int fd, int mode, long offset, long length);
}

private static class FallocateHolderPOSIX {
static {
Native.register(FallocateHolderPOSIX.class, Platform.C_LIBRARY_NAME);
}

private static native int posix_fallocate(int fd, long offset, long length);
}

private static int getDescriptor(FileChannel channel) throws UnsupportedOperationException {
try {
// sun.nio.ch.FileChannelImpl declares private final java.io.FileDescriptor fd
final Field field = channel.getClass().getDeclaredField("fd");
field.setAccessible(true);
return getDescriptor((FileDescriptor) field.get(channel));
} catch (final Exception e) {
throw new UnsupportedOperationException("unsupported FileChannel implementation", e);
}
}

private static int getDescriptor(FileDescriptor descriptor) throws UnsupportedOperationException {
try {
// Oracle java.io.FileDescriptor declares private int fd
final Field field = descriptor.getClass().getDeclaredField(IS_ANDROID ? "descriptor" : "fd");
field.setAccessible(true);
return (int) field.get(descriptor);
} catch (final Exception e) {
throw new UnsupportedOperationException("unsupported FileDescriptor implementation", e);
}
}

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();
}
}
}
}