~xdavidwu/saf-cephfs

f63d8f26c763a360923ba2a576210381f699698b — Pinghao Wu a month ago a45a404
dedup libcephfs-jni error translation
M src/main/java/org/safcephfs/CephFSDocumentsProvider.java => src/main/java/org/safcephfs/CephFSDocumentsProvider.java +61 -119
@@ 28,6 28,7 @@ import android.util.Log;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Vector;


@@ 114,72 115,7 @@ public class CephFSDocumentsProvider extends DocumentsProvider {
		lthread.handler.sendMessage(msg);
	}

	// Wrapper to make IOException from JNI catchable
	private interface CephOperation<T> {
		T execute() throws IOException;
	}

	private void throwOrAddErrorExtra(String error, Cursor cursor) {
		if (cursor != null) {
			Bundle extra = new Bundle();
			extra.putString(DocumentsContract.EXTRA_ERROR, error);
			cursor.setExtras(extra);
		} else {
			toast(error);
			throw new IllegalStateException(error);
		}
	}

	private <T> T doCephOperation(CephOperation<T> op) {
		return doCephOperation(op, null);
	}

	private <T> T doCephOperation(CephOperation<T> op, Cursor cursor) {
		if (cm == null) {
			try {
				cm = setupCeph();
			} catch (IOException e) {
				throwOrAddErrorExtra("Unable to mount root: " + e.getMessage(),
					cursor);
				return null;
			}
		}
		int r = retries;
		while (r-- != 0) {
			try {
				return op.execute();
			} catch (IOException e) {
				if (e.getMessage().equals("Cannot send after transport endpoint shutdown")) {
					if (r != 0) {
						Log.e(APP_NAME, "Mount died, " + r + "attempts remaining, retrying");
						cm.unmount();
						try {
							new CephOperation<Void>() {
								@Override
								public Void execute() throws IOException {
									cm.mount(path);
									return null;
								}
							}.execute();
						} catch (IOException e2) {
							toast("Unable to remount root: " + e2);
						}
					} else {
						throwOrAddErrorExtra("Operation failed: " +
							e.getMessage(), cursor);
						return null;
					}
				} else {
					throwOrAddErrorExtra("Operation failed: " + e.getMessage(),
						cursor);
					return null;
				}
			}
		}
		return null;
	}

	private CephMount setupCeph() throws IOException {
	private CephFSOperations.Operation<CephMount> setupMount = () -> {
		SharedPreferences settings = PreferenceManager
			.getDefaultSharedPreferences(getContext());
		mon = settings.getString("mon", "");


@@ 199,6 135,24 @@ public class CephFSDocumentsProvider extends DocumentsProvider {
		}
		newMount.mount(path); // IOException if fails
		return newMount;
	};

	// TODO refactor to a executor, and port to CephFSProxyFileDescriptorCallback
	private <T> CephFSOperations.Operation<T> withLazyRetriedMount(
			CephFSOperations.Operation<T> op) {
		return () -> {
			if (cm == null) {
				cm = setupMount.execute();
			}
			return CephFSOperations.retryOnESHUTDOWN(
				() -> {
					cm.unmount();
					Log.e(APP_NAME, "Mount died, retrying");
					cm = setupMount.execute();
					return null;
				},
				op).execute();
		};
	}

	@Override


@@ 221,21 175,16 @@ public class CephFSDocumentsProvider extends DocumentsProvider {
		String filename = parentDocumentId.substring(parentDocumentId.indexOf("/") + 1)
				+ "/" + displayName;
		if (mimeType.equals(Document.MIME_TYPE_DIR)) {
			doCephOperation(() -> {
			CephFSOperations.translateToUnchecked(withLazyRetriedMount(() -> {
				cm.mkdir(filename, 0700);
				return null;
			});
			}));
		} else {
			doCephOperation(() -> {
				try {
					int fd = cm.open(filename, CephMount.O_WRONLY | CephMount.O_CREAT | CephMount.O_EXCL, 0700);
					cm.close(fd);
					return null;
				} catch (FileNotFoundException e) {
					Log.e(APP_NAME, "Create " + filename + " not found");
					throw new FileNotFoundException(parentDocumentId + " not found");
				}
			});
			CephFSOperations.translateToUnchecked(withLazyRetriedMount(() -> {
				int fd = cm.open(filename, CephMount.O_WRONLY | CephMount.O_CREAT | CephMount.O_EXCL, 0700);
				cm.close(fd);
				return null;
			}));
		}
		return parentDocumentId + "/" + displayName;
	}


@@ 267,21 216,17 @@ public class CephFSDocumentsProvider extends DocumentsProvider {
		default:
			throw new UnsupportedOperationException("Mode " + mode + " not implemented");
		}

		String filename = documentId.substring(documentId.indexOf("/") + 1);
		int fd = doCephOperation(() -> {
			try {
				return cm.open(filename, flag, 0);
			} catch (FileNotFoundException e) {
				Log.e(APP_NAME, "Open " + documentId + " not found");
				throw new FileNotFoundException(documentId + "not found");
			}
		});
		int fd = CephFSOperations.translateToUnchecked(withLazyRetriedMount(() -> {
			return cm.open(filename, flag, 0);
		}));

		try {
			return sm.openProxyFileDescriptor(fdmode,
					new CephFSProxyFileDescriptorCallback(cm, fd),
					ioHandler);
			return sm.openProxyFileDescriptor(
				fdmode, new CephFSProxyFileDescriptorCallback(cm, fd), ioHandler);
		} catch (IOException e) {
			throw new IllegalStateException("openProxyFileDescriptor: " + e.toString());
			throw new UncheckedIOException(e);
		}
	}



@@ 290,7 235,7 @@ public class CephFSDocumentsProvider extends DocumentsProvider {
		try {
			md5 = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			throw new IllegalStateException();
			throw new IllegalStateException(e);
		}
		md5.update(("./" + name).getBytes());
		byte[] digest = md5.digest();


@@ 315,16 260,16 @@ public class CephFSDocumentsProvider extends DocumentsProvider {
	private void lstatBuildDocumentRow(String dir, String displayName,
			String documentId, String[] thumbnails, MatrixCursor result)
			throws FileNotFoundException {
		// TODO consider EXTRA_ERROR?
		CephStat cs = new CephStat();
		doCephOperation(() -> {
		CephFSOperations.translateToUnchecked(withLazyRetriedMount(() -> {
			try {
				cm.lstat(dir + displayName, cs);
				return null;
			} catch (FileNotFoundException|CephNotDirectoryException e) {
				Log.e(APP_NAME, "lstat: " + dir + displayName + " not found");
				throw new FileNotFoundException(documentId + " not found");
			} catch (CephNotDirectoryException e) {
				throw new FileNotFoundException(e.getMessage());
			}
		});
		}));
		MatrixCursor.RowBuilder row = result.newRow();
		row.add(Document.COLUMN_DOCUMENT_ID, documentId);
		row.add(Document.COLUMN_DISPLAY_NAME, displayName);


@@ 332,15 277,15 @@ public class CephFSDocumentsProvider extends DocumentsProvider {
		row.add(Document.COLUMN_LAST_MODIFIED, cs.m_time);

		if (cs.isSymlink()) {
			doCephOperation(() -> {
			CephFSOperations.translateToUnchecked(withLazyRetriedMount(() -> {
				try {
					cm.stat(dir + displayName, cs);
					return null;
				} catch (FileNotFoundException|CephNotDirectoryException e) {
					Log.e(APP_NAME, "stat: " + dir + displayName + " not found");
					Log.e(APP_NAME, "stat: " + dir + displayName + " not found", e);
					return null;
				}
			});
			}));
		}
		String mimeType = getMime(cs.mode, displayName);
		row.add(Document.COLUMN_MIME_TYPE, mimeType);


@@ 375,14 320,14 @@ public class CephFSDocumentsProvider extends DocumentsProvider {
				}
			} else {
				String thubmailPath = dir + ".sh_thumbnails/normal/" + getXDGThumbnailFile(displayName);
				thumbnailFound = doCephOperation(() -> {
				thumbnailFound = CephFSOperations.translateToUnchecked(withLazyRetriedMount(() -> {
					try {
						cm.stat(thubmailPath, cs);
						return true;
					} catch (FileNotFoundException|CephNotDirectoryException e) {
						return false;
					}
				});
				}));
			}

			if (thumbnailFound) {


@@ 398,24 343,25 @@ public class CephFSDocumentsProvider extends DocumentsProvider {
		MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOC_PROJECTION);
		Log.v(APP_NAME, "queryChildDocuments " + parentDocumentId);
		String filename = parentDocumentId.substring(parentDocumentId.indexOf("/") + 1);
		String[] res = doCephOperation(() -> {
			try {
				return cm.listdir(filename);
			} catch (FileNotFoundException e) {
				Log.e(APP_NAME, "queryChildDocuments " + parentDocumentId + " not found");
				throw new FileNotFoundException(parentDocumentId + " not found");
			}
		}, result);
		String[] res = CephFSOperations.translateToCursorExtra(withLazyRetriedMount(() -> {
			return cm.listdir(filename);
		}), result);
		if (res == null) {
			return result;
		}
		String[] thumbnails = doCephOperation(() -> {

		// TODO make this not fatal instead?
		String[] thumbnails = CephFSOperations.translateToCursorExtra(withLazyRetriedMount(() -> {
			try {
				return cm.listdir(filename + "/.sh_thumbnails/normal");
			} catch (FileNotFoundException e) {
				return new String[0];
			}
		}, result);
		}), result);
		if (res == null) {
			return result;
		}

		for (String entry : res) {
			lstatBuildDocumentRow(filename + "/", entry,
					parentDocumentId + "/" + entry, thumbnails, result);


@@ 476,14 422,10 @@ public class CephFSDocumentsProvider extends DocumentsProvider {
			cm = null;
		}
		CephStatVFS csvfs = new CephStatVFS();
		doCephOperation(() -> {
			try {
				cm.statfs("/", csvfs);
				return null;
			} catch (FileNotFoundException e) {
				throw new FileNotFoundException("/ not found");
			}
		});
		CephFSOperations.translateToUnchecked(withLazyRetriedMount(() -> {
			cm.statfs("/", csvfs);
			return null;
		}));
		MatrixCursor.RowBuilder row = result.newRow();
		row.add(Root.COLUMN_ROOT_ID, id + "@" + mon + ":" + path);
		row.add(Root.COLUMN_DOCUMENT_ID, "root/");

A src/main/java/org/safcephfs/CephFSOperations.java => src/main/java/org/safcephfs/CephFSOperations.java +151 -0
@@ 0,0 1,151 @@
package org.safcephfs;

import android.database.Cursor;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.system.ErrnoException;
import android.system.OsConstants;

import java.io.IOException;
import java.io.UncheckedIOException;

public class CephFSOperations {
	protected interface Operation<T> {
		T execute() throws IOException;
	}

	/*
	 * libcephfs_jni throws IOException with message from strerror()
	 * Bionic strerror:
	 * https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/private/bionic_errdefs.h
	 * sed 's|__BIONIC_ERRDEF(\([^,]*\), \([^)]*\).*|case \2 -> OsConstants.\1;|'
	 * and delete where there are no matching OsConstants
	 */
	private static int cephIOEToOsConstants(IOException e) {
		return switch (e.getMessage()) {
		case "Operation not permitted" -> OsConstants.EPERM;
		case "No such file or directory" -> OsConstants.ENOENT;
		case "No such process" -> OsConstants.ESRCH;
		case "Interrupted system call" -> OsConstants.EINTR;
		case "I/O error" -> OsConstants.EIO;
		case "No such device or address" -> OsConstants.ENXIO;
		case "Argument list too long" -> OsConstants.E2BIG;
		case "Exec format error" -> OsConstants.ENOEXEC;
		case "Bad file descriptor" -> OsConstants.EBADF;
		case "No child processes" -> OsConstants.ECHILD;
		case "Try again" -> OsConstants.EAGAIN;
		case "Out of memory" -> OsConstants.ENOMEM;
		case "Permission denied" -> OsConstants.EACCES;
		case "Bad address" -> OsConstants.EFAULT;
		case "Device or resource busy" -> OsConstants.EBUSY;
		case "File exists" -> OsConstants.EEXIST;
		case "Cross-device link" -> OsConstants.EXDEV;
		case "No such device" -> OsConstants.ENODEV;
		case "Not a directory" -> OsConstants.ENOTDIR;
		case "Is a directory" -> OsConstants.EISDIR;
		case "Invalid argument" -> OsConstants.EINVAL;
		case "File table overflow" -> OsConstants.ENFILE;
		case "Too many open files" -> OsConstants.EMFILE;
		case "Inappropriate ioctl for device" -> OsConstants.ENOTTY;
		case "Text file busy" -> OsConstants.ETXTBSY;
		case "File too large" -> OsConstants.EFBIG;
		case "No space left on device" -> OsConstants.ENOSPC;
		case "Illegal seek" -> OsConstants.ESPIPE;
		case "Read-only file system" -> OsConstants.EROFS;
		case "Too many links" -> OsConstants.EMLINK;
		case "Broken pipe" -> OsConstants.EPIPE;
		case "Math argument out of domain of func" -> OsConstants.EDOM;
		case "Math result not representable" -> OsConstants.ERANGE;
		case "Resource deadlock would occur" -> OsConstants.EDEADLK;
		case "File name too long" -> OsConstants.ENAMETOOLONG;
		case "No record locks available" -> OsConstants.ENOLCK;
		case "Function not implemented" -> OsConstants.ENOSYS;
		case "Directory not empty" -> OsConstants.ENOTEMPTY;
		case "Too many symbolic links encountered" -> OsConstants.ELOOP;
		case "No message of desired type" -> OsConstants.ENOMSG;
		case "Identifier removed" -> OsConstants.EIDRM;
		case "Device not a stream" -> OsConstants.ENOSTR;
		case "No data available" -> OsConstants.ENODATA;
		case "Timer expired" -> OsConstants.ETIME;
		case "Out of streams resources" -> OsConstants.ENOSR;
		case "Machine is not on the network" -> OsConstants.ENONET;
		case "Link has been severed" -> OsConstants.ENOLINK;
		case "Protocol error" -> OsConstants.EPROTO;
		case "Multihop attempted" -> OsConstants.EMULTIHOP;
		case "Not a data message" -> OsConstants.EBADMSG;
		case "Value too large for defined data type" -> OsConstants.EOVERFLOW;
		case "Illegal byte sequence" -> OsConstants.EILSEQ;
		case "Socket operation on non-socket" -> OsConstants.ENOTSOCK;
		case "Destination address required" -> OsConstants.EDESTADDRREQ;
		case "Message too long" -> OsConstants.EMSGSIZE;
		case "Protocol wrong type for socket" -> OsConstants.EPROTOTYPE;
		case "Protocol not available" -> OsConstants.ENOPROTOOPT;
		case "Protocol not supported" -> OsConstants.EPROTONOSUPPORT;
		case "Operation not supported on transport endpoint" -> OsConstants.EOPNOTSUPP;
		case "Address family not supported by protocol" -> OsConstants.EAFNOSUPPORT;
		case "Address already in use" -> OsConstants.EADDRINUSE;
		case "Cannot assign requested address" -> OsConstants.EADDRNOTAVAIL;
		case "Network is down" -> OsConstants.ENETDOWN;
		case "Network is unreachable" -> OsConstants.ENETUNREACH;
		case "Network dropped connection because of reset" -> OsConstants.ENETRESET;
		case "Software caused connection abort" -> OsConstants.ECONNABORTED;
		case "Connection reset by peer" -> OsConstants.ECONNRESET;
		case "No buffer space available" -> OsConstants.ENOBUFS;
		case "Transport endpoint is already connected" -> OsConstants.EISCONN;
		case "Transport endpoint is not connected" -> OsConstants.ENOTCONN;
		case "Connection timed out" -> OsConstants.ETIMEDOUT;
		case "Connection refused" -> OsConstants.ECONNREFUSED;
		case "No route to host" -> OsConstants.EHOSTUNREACH;
		case "Operation already in progress" -> OsConstants.EALREADY;
		case "Operation now in progress" -> OsConstants.EINPROGRESS;
		case "Stale NFS file handle" -> OsConstants.ESTALE;
		case "Quota exceeded" -> OsConstants.EDQUOT;
		case "Operation Canceled" -> OsConstants.ECANCELED;
		default -> OsConstants.EIO;
		};
	}

	protected static <T> T translateToErrnoException(
			String functionName, Operation<T> op) throws ErrnoException {
		try {
			return op.execute();
		} catch (IOException e) {
			throw new ErrnoException(functionName, cephIOEToOsConstants(e));
		}
	}

	protected static <T> T translateToCursorExtra(Operation<T> op, Cursor c) {
		try {
			return op.execute();
		} catch (IOException e) {
			var extra = new Bundle();
			extra.putString(DocumentsContract.EXTRA_ERROR, e.getMessage());
			c.setExtras(extra);
			return null;
		}
	}

	protected static <T> T translateToUnchecked(Operation<T> op) {
		try {
			return op.execute();
		} catch (IOException e) {
			throw new UncheckedIOException(e);
		}
	}

	protected static <T> Operation<T> retryOnESHUTDOWN(
			Operation<Object> setup, Operation<T> op) {
		return () -> {
			try {
				return op.execute();
			} catch (IOException e) {
				if (e.getMessage().equals("Cannot send after transport endpoint shutdown")) {
					setup.execute();
					return op.execute();
				} else {
					throw e;
				}
			}
		};
	}
}

M src/main/java/org/safcephfs/CephFSProxyFileDescriptorCallback.java => src/main/java/org/safcephfs/CephFSProxyFileDescriptorCallback.java +4 -186
@@ 13,188 13,6 @@ public class CephFSProxyFileDescriptorCallback extends ProxyFileDescriptorCallba
	private CephMount cm;
	private int fd;

	/*
	 * libcephfs_jni throws IOException with message from strerror()
	 * Bionic strerror:
	 * https://android.googlesource.com/platform/bionic/+/refs/heads/master/libc/bionic/strerror.cpp
	 * lazy solution: copy error strings line,
	 * `sed -E 's/    \[([A-Z0-9]+)\] = ([^,]+),/\t\tcase \2:\n\t\t\treturn OsConstants.\1;/'`,
	 * and delete where there are no matching OsConstants
	 */
	private static int cephIOEToOsConstants(IOException e) {
		switch (e.getMessage()) {
		case "Operation not permitted":
			return OsConstants.EPERM;
		case "No such file or directory":
			return OsConstants.ENOENT;
		case "No such process":
			return OsConstants.ESRCH;
		case "Interrupted system call":
			return OsConstants.EINTR;
		case "I/O error":
			return OsConstants.EIO;
		case "No such device or address":
			return OsConstants.ENXIO;
		case "Argument list too long":
			return OsConstants.E2BIG;
		case "Exec format error":
			return OsConstants.ENOEXEC;
		case "Bad file descriptor":
			return OsConstants.EBADF;
		case "No child processes":
			return OsConstants.ECHILD;
		case "Try again":
			return OsConstants.EAGAIN;
		case "Out of memory":
			return OsConstants.ENOMEM;
		case "Permission denied":
			return OsConstants.EACCES;
		case "Bad address":
			return OsConstants.EFAULT;
		case "Device or resource busy":
			return OsConstants.EBUSY;
		case "File exists":
			return OsConstants.EEXIST;
		case "Cross-device link":
			return OsConstants.EXDEV;
		case "No such device":
			return OsConstants.ENODEV;
		case "Not a directory":
			return OsConstants.ENOTDIR;
		case "Is a directory":
			return OsConstants.EISDIR;
		case "Invalid argument":
			return OsConstants.EINVAL;
		case "File table overflow":
			return OsConstants.ENFILE;
		case "Too many open files":
			return OsConstants.EMFILE;
		case "Inappropriate ioctl for device":
			return OsConstants.ENOTTY;
		case "Text file busy":
			return OsConstants.ETXTBSY;
		case "File too large":
			return OsConstants.EFBIG;
		case "No space left on device":
			return OsConstants.ENOSPC;
		case "Illegal seek":
			return OsConstants.ESPIPE;
		case "Read-only file system":
			return OsConstants.EROFS;
		case "Too many links":
			return OsConstants.EMLINK;
		case "Broken pipe":
			return OsConstants.EPIPE;
		case "Math argument out of domain of func":
			return OsConstants.EDOM;
		case "Math result not representable":
			return OsConstants.ERANGE;
		case "Resource deadlock would occur":
			return OsConstants.EDEADLK;
		case "File name too long":
			return OsConstants.ENAMETOOLONG;
		case "No record locks available":
			return OsConstants.ENOLCK;
		case "Function not implemented":
			return OsConstants.ENOSYS;
		case "Directory not empty":
			return OsConstants.ENOTEMPTY;
		case "Too many symbolic links encountered":
			return OsConstants.ELOOP;
		case "No message of desired type":
			return OsConstants.ENOMSG;
		case "Identifier removed":
			return OsConstants.EIDRM;
		case "Device not a stream":
			return OsConstants.ENOSTR;
		case "No data available":
			return OsConstants.ENODATA;
		case "Timer expired":
			return OsConstants.ETIME;
		case "Out of streams resources":
			return OsConstants.ENOSR;
		case "Link has been severed":
			return OsConstants.ENOLINK;
		case "Protocol error":
			return OsConstants.EPROTO;
		case "Multihop attempted":
			return OsConstants.EMULTIHOP;
		case "Not a data message":
			return OsConstants.EBADMSG;
		case "Value too large for defined data type":
			return OsConstants.EOVERFLOW;
		case "Illegal byte sequence":
			return OsConstants.EILSEQ;
		case "Socket operation on non-socket":
			return OsConstants.ENOTSOCK;
		case "Destination address required":
			return OsConstants.EDESTADDRREQ;
		case "Message too long":
			return OsConstants.EMSGSIZE;
		case "Protocol wrong type for socket":
			return OsConstants.EPROTOTYPE;
		case "Protocol not available":
			return OsConstants.ENOPROTOOPT;
		case "Protocol not supported":
			return OsConstants.EPROTONOSUPPORT;
		case "Operation not supported on transport endpoint":
			return OsConstants.EOPNOTSUPP;
		case "Address family not supported by protocol":
			return OsConstants.EAFNOSUPPORT;
		case "Address already in use":
			return OsConstants.EADDRINUSE;
		case "Cannot assign requested address":
			return OsConstants.EADDRNOTAVAIL;
		case "Network is down":
			return OsConstants.ENETDOWN;
		case "Network is unreachable":
			return OsConstants.ENETUNREACH;
		case "Network dropped connection because of reset":
			return OsConstants.ENETRESET;
		case "Software caused connection abort":
			return OsConstants.ECONNABORTED;
		case "Connection reset by peer":
			return OsConstants.ECONNRESET;
		case "No buffer space available":
			return OsConstants.ENOBUFS;
		case "Transport endpoint is already connected":
			return OsConstants.EISCONN;
		case "Transport endpoint is not connected":
			return OsConstants.ENOTCONN;
		case "Connection timed out":
			return OsConstants.ETIMEDOUT;
		case "Connection refused":
			return OsConstants.ECONNREFUSED;
		case "No route to host":
			return OsConstants.EHOSTUNREACH;
		case "Operation already in progress":
			return OsConstants.EALREADY;
		case "Operation now in progress":
			return OsConstants.EINPROGRESS;
		case "Stale NFS file handle":
			return OsConstants.ESTALE;
		case "Quota exceeded":
			return OsConstants.EDQUOT;
		case "Operation Canceled":
			return OsConstants.ECANCELED;
		default:
			return OsConstants.EIO;
		}
	}

	private interface CephFDOperation<T> {
		T execute() throws IOException;
	}

	private <T> T doCephFDOperation(String funcName, CephFDOperation<T> op)
			throws ErrnoException {
		try {
			return op.execute();
		} catch (IOException e) {
			throw new ErrnoException(funcName, cephIOEToOsConstants(e));
		}
	}

	public CephFSProxyFileDescriptorCallback(CephMount cm, int fd) {
		this.cm = cm;
		this.fd = fd;


@@ 202,7 20,7 @@ public class CephFSProxyFileDescriptorCallback extends ProxyFileDescriptorCallba

	@Override
	public void onFsync() throws ErrnoException {
		doCephFDOperation("fsync", () -> {
		CephFSOperations.translateToErrnoException("fsync", () -> {
			cm.fsync(fd, false);
			return null;
		});


@@ 211,7 29,7 @@ public class CephFSProxyFileDescriptorCallback extends ProxyFileDescriptorCallba
	@Override
	public long onGetSize() throws ErrnoException {
		CephStat cs = new CephStat();
		doCephFDOperation("fstat", () -> {
		CephFSOperations.translateToErrnoException("fstat", () -> {
			cm.fstat(fd, cs);
			return null;
		});


@@ 221,7 39,7 @@ public class CephFSProxyFileDescriptorCallback extends ProxyFileDescriptorCallba
	@Override
	public int onRead(long offset, int size, byte[] data)
		throws ErrnoException {
		return doCephFDOperation("read", () -> {
		return CephFSOperations.translateToErrnoException("read", () -> {
			return cm.read(fd, data, size, offset);
		}).intValue();
	}


@@ 234,7 52,7 @@ public class CephFSProxyFileDescriptorCallback extends ProxyFileDescriptorCallba
	@Override
	public int onWrite(long offset, int size, byte[] data)
		throws ErrnoException {
		return doCephFDOperation("write", () -> {
		return CephFSOperations.translateToErrnoException("write", () -> {
			return cm.write(fd, data, size, offset);
		}).intValue();
	}