Implement seekability for file descriptors (#6)

* Implement seekability for file descriptors
This commit is contained in:
Ruslan Tkhakokhov 2017-07-06 12:50:13 -07:00 committed by Xu Tan
parent ef48c1647e
commit 228ca3f9d7
17 changed files with 377 additions and 23 deletions

View file

@ -1,12 +1,12 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
compileSdkVersion 26
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.google.android.sambadocumentsprovider"
minSdkVersion 21
targetSdkVersion 25
targetSdkVersion 26
versionCode 102
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

View file

@ -527,6 +527,46 @@ Java_com_google_android_sambadocumentsprovider_nativefacade_NativeSambaFacade_op
return fd;
}
jobject Java_com_google_android_sambadocumentsprovider_nativefacade_SambaFile_fstat(
JNIEnv *env,
jobject instance,
jlong pointer,
jint fd) {
SambaClient::SambaClient *client =
reinterpret_cast<SambaClient::SambaClient*>(pointer);
jobject stat = NULL;
struct stat st;
int result = client->Fstat(fd, &st);
if (result < 0) {
throw_new_errno_exception(env, "stat", -result);
}
return create_structstat(env, st);
}
jlong Java_com_google_android_sambadocumentsprovider_nativefacade_SambaFile_seek(
JNIEnv *env,
jobject instance,
jlong pointer,
jint fd,
jlong offset,
jint whence) {
SambaClient::SambaClient *client =
reinterpret_cast<SambaClient::SambaClient*>(pointer);
jlong result = client->SeekFile(fd, offset, whence);
if (result < 0) {
throw_new_errno_exception(env, "seek", static_cast<int>(-result));
}
return result;
}
jlong Java_com_google_android_sambadocumentsprovider_nativefacade_SambaFile_read(
JNIEnv *env,
jobject instance,

View file

@ -68,6 +68,14 @@ JNIEXPORT jlong JNICALL
Java_com_google_android_sambadocumentsprovider_nativefacade_SambaFile_read(
JNIEnv *env, jobject instance, jlong pointer, jint fd, jobject buffer, jint maxlen);
JNIEXPORT jlong JNICALL
Java_com_google_android_sambadocumentsprovider_nativefacade_SambaFile_seek(
JNIEnv *env, jobject instance, jlong pointer, jint fd, jlong offset, jint whence);
JNIEXPORT jobject JNICALL
Java_com_google_android_sambadocumentsprovider_nativefacade_SambaFile_fstat(
JNIEnv *env, jobject instance, jlong pointer, jint fd);
JNIEXPORT jlong JNICALL
Java_com_google_android_sambadocumentsprovider_nativefacade_SambaFile_write(
JNIEnv *env, jobject instance, jlong pointer, jint fd, jobject buffer, jint length);

View file

@ -160,6 +160,19 @@ SambaClient::ReadDir(
return 0;
}
int
SambaClient::Fstat(const int fd, struct stat * const st) {
LOGD(TAG, "Getting stat for %x.", fd);
int result = smbc_fstat(fd, st);
if (result < 0) {
int err = errno;
LOGE(TAG, "Failed to obtain stat for %x. Errno: %x.", fd, err);
return -err;
}
LOGV(TAG, "Got stat for %x.", fd);
return 0;
}
int
SambaClient::Stat(const char *url, struct stat * const st) {
LOGD(TAG, "Getting stat for %s.", url);
@ -251,6 +264,20 @@ int SambaClient::OpenFile(const char *url, const int flag, const mode_t mode) {
return fd;
}
off_t
SambaClient::SeekFile(const int fd, const off_t offset, const int whence) {
LOGV(TAG, "Set offset to %x for file with fd %x", offset, fd);
off_t result = smbc_lseek(fd, offset, whence);
if (result < 0) {
int err = errno;
LOGE(TAG, "Failed to seek in file %x. Errno: %x", fd, err);
return -err;
}
return result;
}
ssize_t
SambaClient::ReadFile(const int fd, void *buffer, const size_t maxlen) {
LOGV(TAG, "Reading max %lu bytes from file with fd %x", maxlen, fd);

View file

@ -18,10 +18,11 @@
#ifndef SAMBADOCUMENTSPROVIDER_SAMBAPROVIDER_H
#define SAMBADOCUMENTSPROVIDER_SAMBAPROVIDER_H
#include "samba_includes/libsmbclient.h"
#include "base/Callback.h"
#include "jni_helper/JniHelper.h"
#include "samba_includes/libsmbclient.h"
#include <sys/types.h>
#include <vector>
namespace SambaClient {
@ -38,6 +39,8 @@ class SambaClient {
int Stat(const char *url, struct stat *st);
int Fstat(const int fd, struct stat * const st);
int CreateFile(const char *url);
int Mkdir(const char *url);
@ -54,6 +57,8 @@ class SambaClient {
ssize_t WriteFile(const int fd, void *buffer, const size_t length);
off_t SeekFile(const int fd, const off_t offset, const int whence);
int CloseFile(const int fd);
private:
::SMBCCTX *sambaContext = NULL;

View file

@ -24,17 +24,18 @@ import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import com.google.android.sambadocumentsprovider.SambaConfiguration.OnConfigurationChangedListener;
import com.google.android.sambadocumentsprovider.cache.DocumentCache;
import com.google.android.sambadocumentsprovider.nativefacade.CredentialCache;
import com.google.android.sambadocumentsprovider.nativefacade.SambaMessageLooper;
import com.google.android.sambadocumentsprovider.nativefacade.SmbClient;
import com.google.android.sambadocumentsprovider.nativefacade.SmbFacade;
public class SambaProviderApplication extends Application {
private final DocumentCache mCache = new DocumentCache();
private final TaskManager mTaskManager = new TaskManager();
private final SmbClient mSambaClient;
private final SmbFacade mSambaClient;
private final CredentialCache mCredentialCache;
private SambaConfiguration mSambaConf;
@ -94,7 +95,7 @@ public class SambaProviderApplication extends Application {
}
}
public static SmbClient getSambaClient(Context context) {
public static SmbFacade getSambaClient(Context context) {
return getApplication(context).mSambaClient;
}

View file

@ -17,6 +17,9 @@
package com.google.android.sambadocumentsprovider.nativefacade;
import android.os.ParcelFileDescriptor;
import android.os.ProxyFileDescriptorCallback;
import android.os.storage.StorageManager;
import android.system.ErrnoException;
import android.system.StructStat;
import com.google.android.sambadocumentsprovider.BuildConfig;

View file

@ -18,17 +18,25 @@
package com.google.android.sambadocumentsprovider.nativefacade;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import android.support.annotation.IntDef;
import android.system.StructStat;
import com.google.android.sambadocumentsprovider.base.DirectoryEntry;
import com.google.android.sambadocumentsprovider.provider.ByteBufferPool;
import com.google.android.sambadocumentsprovider.provider.SambaProxyFileCallback;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.util.List;
class SambaFacadeClient extends BaseClient implements SmbClient {
class SambaFacadeClient extends BaseClient implements SmbFacade {
@IntDef({ RESET, READ_DIR, STAT, MKDIR, RENAME, UNLINK, RMDIR, OPEN_FILE })
@Retention(RetentionPolicy.SOURCE)
@ -134,14 +142,37 @@ class SambaFacadeClient extends BaseClient implements SmbClient {
@Override
public SmbFile openFile(String uri, String mode) throws IOException {
return new SambaFileClient(mHandler.getLooper(), openFileRaw(uri, mode));
}
@Override
public ParcelFileDescriptor openProxyFile(
String uri,
String mode,
StorageManager storageManager,
ByteBufferPool bufferPool,
CancellationSignal signal) throws IOException {
SambaFile file = openFileRaw(uri, mode);
return storageManager.openProxyFileDescriptor(
ParcelFileDescriptor.parseMode(mode),
new SambaProxyFileCallback(file, bufferPool, signal),
mHandler);
}
private SambaFile openFileRaw(String uri, String mode) throws IOException {
try (final MessageValues<SambaFile> messageValues = MessageValues.obtain()) {
final Message msg = obtainMessage(OPEN_FILE, messageValues, uri);
msg.peekData().putString(MODE, mode);
enqueue(msg);
return new SambaFileClient(mHandler.getLooper(), messageValues.getObj());
enqueue(obtainMessageForOpenFile(uri, mode, messageValues));
return messageValues.getObj();
}
}
private Message obtainMessageForOpenFile(String uri, String mode,
MessageValues<SambaFile> messageValues) {
final Message msg = obtainMessage(OPEN_FILE, messageValues, uri);
msg.peekData().putString(MODE, mode);
return msg;
}
private static class SambaServiceHandler extends BaseHandler {
private final SmbClient mClientImpl;

View file

@ -18,6 +18,9 @@
package com.google.android.sambadocumentsprovider.nativefacade;
import android.system.ErrnoException;
import android.system.StructStat;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
@ -46,6 +49,23 @@ class SambaFile implements SmbFile {
}
}
public long seek(long offset) throws IOException {
try {
return seek(mNativeHandler, mNativeFd, offset, 0);
} catch (ErrnoException e) {
throw new IOException("Failed to move to offset in file. Fd: " + mNativeFd, e);
}
}
@Override
public StructStat fstat() throws IOException {
try {
return fstat(mNativeHandler, mNativeFd);
} catch (ErrnoException e) {
throw new IOException("Failed to get stat of " + mNativeFd, e);
}
}
@Override
public void close() throws IOException {
try {
@ -63,5 +83,10 @@ class SambaFile implements SmbFile {
private native int write(long handler, int fd, ByteBuffer buffer, int length)
throws ErrnoException;
private native long seek(long handler, int fd, long offset, int whence)
throws ErrnoException;
private native StructStat fstat(long handler, int fd) throws ErrnoException;
private native void close(long handler, int fd) throws ErrnoException;
}

View file

@ -17,9 +17,12 @@
package com.google.android.sambadocumentsprovider.nativefacade;
import android.os.Bundle;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.IntDef;
import android.system.StructStat;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -33,6 +36,10 @@ class SambaFileClient extends BaseClient implements SmbFile {
private static final int READ = 1;
private static final int WRITE = 2;
private static final int CLOSE = 3;
private static final int SEEK = 4;
private static final int FSTAT = 5;
private static final String OFFSET = "offset";
SambaFileClient(Looper looper, SmbFile smbFileImpl) {
mHandler = new SambaFileHandler(looper, smbFileImpl);
@ -59,6 +66,30 @@ class SambaFileClient extends BaseClient implements SmbFile {
}
}
@Override
public long seek(long offset) throws IOException {
try (final MessageValues messageValues = MessageValues.obtain()) {
final Message msg = mHandler.obtainMessage(SEEK, messageValues);
Bundle data = new Bundle();
data.putLong(OFFSET, offset);
msg.setData(data);
enqueue(msg);
return msg.peekData().getLong(OFFSET);
}
}
@Override
public StructStat fstat() throws IOException {
try (final MessageValues<StructStat> messageValues = MessageValues.obtain()) {
final Message msg = mHandler.obtainMessage(FSTAT, messageValues);
enqueue(msg);
return messageValues.getObj();
}
}
@Override
public void close() throws IOException {
try (final MessageValues messageValues = MessageValues.obtain()) {
@ -81,22 +112,28 @@ class SambaFileClient extends BaseClient implements SmbFile {
@Override
@SuppressWarnings("unchecked")
public void processMessage(Message msg) {
final MessageValues<ByteBuffer> messageValues = (MessageValues<ByteBuffer>) msg.obj;
final MessageValues messageValues = (MessageValues) msg.obj;
try {
final ByteBuffer buffer = messageValues.getObj();
switch (msg.what) {
case READ:
messageValues.setInt(mSmbFileImpl.read(buffer));
final ByteBuffer readBuffer = (ByteBuffer) messageValues.getObj();
messageValues.setInt(mSmbFileImpl.read(readBuffer));
break;
case WRITE: {
final ByteBuffer writeBuffer = (ByteBuffer) messageValues.getObj();
final int length = msg.arg1;
messageValues.setInt(mSmbFileImpl.write(buffer, length));
messageValues.setInt(mSmbFileImpl.write(writeBuffer, length));
break;
}
case CLOSE:
mSmbFileImpl.close();
break;
case SEEK:
long offset = mSmbFileImpl.seek(msg.peekData().getLong(OFFSET));
msg.peekData().putLong(OFFSET, offset);
case FSTAT:
messageValues.setObj(mSmbFileImpl.fstat());
break;
default:
throw new UnsupportedOperationException("Unknown operation " + msg.what);
}

View file

@ -18,6 +18,7 @@
package com.google.android.sambadocumentsprovider.nativefacade;
import android.os.Looper;
import java.util.concurrent.CountDownLatch;
public class SambaMessageLooper {
@ -41,7 +42,7 @@ public class SambaMessageLooper {
init();
}
public SmbClient getClient() {
public SmbFacade getClient() {
return mServiceClient;
}
@ -55,6 +56,7 @@ public class SambaMessageLooper {
mLatch.await();
mCredentialCacheClient = new CredentialCacheClient(mLooper, mCredentialCacheImpl);
mServiceClient = new SambaFacadeClient(mLooper, mClientImpl);
} catch(InterruptedException e) {
// Should never happen

View file

@ -0,0 +1,36 @@
/*
* Copyright 2017 Google Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.google.android.sambadocumentsprovider.nativefacade;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import com.google.android.sambadocumentsprovider.provider.ByteBufferPool;
import java.io.IOException;
import java.nio.ByteBuffer;
public interface SmbFacade extends SmbClient {
ParcelFileDescriptor openProxyFile(
String uri,
String mode,
StorageManager storageManager,
ByteBufferPool bufferPool,
CancellationSignal signal) throws IOException;
}

View file

@ -17,6 +17,8 @@
package com.google.android.sambadocumentsprovider.nativefacade;
import android.system.StructStat;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
@ -25,5 +27,7 @@ public interface SmbFile extends Closeable {
int read(ByteBuffer buffer) throws IOException;
int write(ByteBuffer buffer, int length) throws IOException;
long seek(long offset) throws IOException;
StructStat fstat() throws IOException;
}

View file

@ -20,12 +20,12 @@ package com.google.android.sambadocumentsprovider.provider;
import android.support.v4.util.Pools;
import java.nio.ByteBuffer;
class ByteBufferPool {
public class ByteBufferPool {
private static final int BUFFER_CAPACITY = 128 * 1024;
private final Pools.Pool<ByteBuffer> mBufferPool = new Pools.SynchronizedPool<>(16);
ByteBuffer obtainBuffer() {
public ByteBuffer obtainBuffer() {
ByteBuffer buffer = mBufferPool.acquire();
if (buffer == null) {
@ -35,7 +35,7 @@ class ByteBufferPool {
return buffer;
}
void recycleBuffer(ByteBuffer buffer) {
public void recycleBuffer(ByteBuffer buffer) {
buffer.clear();
mBufferPool.release(buffer);
}

View file

@ -27,9 +27,11 @@ import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
@ -53,6 +55,9 @@ import com.google.android.sambadocumentsprovider.base.OnTaskFinishedCallback;
import com.google.android.sambadocumentsprovider.document.LoadDocumentTask;
import com.google.android.sambadocumentsprovider.document.LoadStatTask;
import com.google.android.sambadocumentsprovider.nativefacade.SmbClient;
import com.google.android.sambadocumentsprovider.nativefacade.SmbFacade;
import com.google.android.sambadocumentsprovider.nativefacade.SmbFile;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collection;
@ -131,10 +136,11 @@ public class SambaDocumentsProvider extends DocumentsProvider {
};
private ShareManager mShareManager;
private SmbClient mClient;
private SmbFacade mClient;
private ByteBufferPool mBufferPool;
private DocumentCache mCache;
private TaskManager mTaskManager;
private StorageManager mStorageManager;
@Override
public boolean onCreate() {
@ -145,6 +151,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
mBufferPool = new ByteBufferPool();
mShareManager = SambaProviderApplication.getServerManager(context);
mShareManager.addListener(mShareChangeListener);
mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
return mClient != null;
}
@ -547,12 +554,23 @@ public class SambaDocumentsProvider extends DocumentsProvider {
public ParcelFileDescriptor openDocument(String documentId, String mode,
CancellationSignal cancellationSignal) throws FileNotFoundException {
Log.d(TAG, "Opening document " + documentId + " with mode " + mode);
try {
if (!"r".equals(mode) && !"w".equals(mode)) {
throw new UnsupportedOperationException("Mode " + mode + " is not supported");
}
final String uri = toUriString(documentId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return mClient.openProxyFile(
uri,
mode,
mStorageManager,
mBufferPool,
cancellationSignal);
}
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe();
switch (mode) {
case "r": {
@ -560,7 +578,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
uri, mClient, pipe[1], mBufferPool, cancellationSignal);
mTaskManager.runIoTask(task);
}
return pipe[0];
return pipe[0];
case "w": {
final WriteFileTask task = new WriteFileTask(uri, mClient, pipe[0], mBufferPool,
cancellationSignal, mWriteFinishedCallback);

View file

@ -0,0 +1,117 @@
/*
* Copyright 2017 Google Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.google.android.sambadocumentsprovider.provider;
import android.os.CancellationSignal;
import android.os.ProxyFileDescriptorCallback;
import android.system.ErrnoException;
import android.system.StructStat;
import android.util.Log;
import com.google.android.sambadocumentsprovider.nativefacade.SmbFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
public class SambaProxyFileCallback extends ProxyFileDescriptorCallback {
private static final String TAG = "SambaProxyFileCallback";
private final SmbFile mFile;
private final ByteBufferPool mBufferPool;
private final ByteBuffer mBuffer;
private final CancellationSignal mSignal;
public SambaProxyFileCallback(SmbFile file, ByteBufferPool bufferPool, CancellationSignal signal) {
mFile = file;
mBufferPool = bufferPool;
mBuffer = mBufferPool.obtainBuffer();
mSignal = signal;
}
@Override
public long onGetSize() throws ErrnoException {
StructStat stat = null;
try {
stat = mFile.fstat();
return stat.st_size;
} catch (IOException e) {
Log.e(TAG, "Failed to get size for file");
}
return 0;
}
@Override
public int onRead(long offset, int size, byte[] data) throws ErrnoException {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
mFile.seek(offset);
int readSize;
int total = 0;
while ((mSignal == null || !mSignal.isCanceled())
&& (readSize = mFile.read(mBuffer)) > 0) {
mBuffer.get(data, total, Math.min(size - total, readSize));
mBuffer.clear();
total += Math.min(size - total, readSize);
if (total >= size) {
break;
}
}
return Math.min(size, total);
} catch (IOException e) {
e.printStackTrace();
}
return 0;
}
@Override
public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
int written = 0;
try {
mFile.seek(offset);
while ((mSignal == null || !mSignal.isCanceled())
&& written < size) {
int willWrite = Math.min(size - written, mBuffer.capacity());
mBuffer.put(data, written, willWrite);
int res = mFile.write(mBuffer, willWrite);
written += res;
mBuffer.clear();
}
} catch (IOException e) {
Log.e(TAG, "Failed to write file.", e);
}
return written;
}
@Override
public void onRelease() {
mBufferPool.recycleBuffer(mBuffer);
try {
mFile.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close file");
}
}
}

View file

@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'
classpath 'com.android.tools.build:gradle:2.3.3'
}
}