mirror of
https://github.com/topjohnwu/libsu.git
synced 2024-10-07 01:03:22 +00:00
Cleanup API surface
This commit is contained in:
parent
3eaeea1403
commit
74d7d23e4b
@ -19,7 +19,6 @@ package com.topjohnwu.libsuexample;
|
||||
import static com.topjohnwu.libsuexample.MainActivity.TAG;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
@ -64,12 +63,11 @@ class AIDLService extends RootService {
|
||||
|
||||
@Override
|
||||
public IBinder getFS() {
|
||||
return fs;
|
||||
return FileSystemApi.getService();
|
||||
}
|
||||
}
|
||||
|
||||
private final String uuid = UUID.randomUUID().toString();
|
||||
private final Binder fs = FileSystemApi.createBinder();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
|
@ -76,7 +76,7 @@ public class MainActivity extends Activity implements Handler.Callback {
|
||||
|
||||
private AIDLConnection aidlConn;
|
||||
private AIDLConnection daemonConn;
|
||||
private FileSystemApi.Remote remoteFs;
|
||||
private FileSystemApi remoteFs;
|
||||
|
||||
class AIDLConnection implements ServiceConnection {
|
||||
|
||||
@ -101,7 +101,7 @@ public class MainActivity extends Activity implements Handler.Callback {
|
||||
consoleList.add("AIDL UID : " + ipc.getUid());
|
||||
consoleList.add("AIDL UUID: " + ipc.getUUID());
|
||||
if (!isDaemon)
|
||||
remoteFs = FileSystemApi.asRemote(ipc.getFS());
|
||||
remoteFs = FileSystemApi.getRemote(ipc.getFS());
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Remote error", e);
|
||||
}
|
||||
|
@ -27,31 +27,29 @@ import com.topjohnwu.superuser.internal.IOFactory;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.nio.ExtendedFile;
|
||||
import com.topjohnwu.superuser.nio.FileSystemApi;
|
||||
import com.topjohnwu.superuser.nio.RemoteFile;
|
||||
import com.topjohnwu.superuser.nio.RemoteFileChannel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.Random;
|
||||
|
||||
public class StressTest {
|
||||
|
||||
private static final Random r = new Random();
|
||||
|
||||
private static FileSystemApi.Remote fs;
|
||||
private static FileSystemApi fs;
|
||||
private static FileCallback callback;
|
||||
|
||||
interface FileCallback {
|
||||
void onFile(ExtendedFile file) throws Exception;
|
||||
}
|
||||
|
||||
public static void perform(FileSystemApi.Remote s) {
|
||||
public static void perform(FileSystemApi s) {
|
||||
fs = s;
|
||||
Shell.EXECUTOR.execute(() -> {
|
||||
try {
|
||||
//testShellIO();
|
||||
testShellIO();
|
||||
testRemoteIO();
|
||||
} catch (Exception e){
|
||||
Log.d(TAG, "", e);
|
||||
@ -94,15 +92,13 @@ public class StressTest {
|
||||
}
|
||||
|
||||
private static void testRemoteIO() throws Exception {
|
||||
RemoteFile root = new RemoteFile(fs, new File("/system/app"));
|
||||
ExtendedFile root = fs.getFile("/system/app");
|
||||
|
||||
// Stress test fifo IOStreams
|
||||
RemoteFile n = new RemoteFile(fs, new File("/dev/null"));
|
||||
RemoteFileChannel out = new RemoteFileChannel(n, MODE_WRITE_ONLY);
|
||||
FileChannel out = fs.openChannel("/dev/null", MODE_WRITE_ONLY);
|
||||
ByteBuffer buf = ByteBuffer.allocateDirect(512 * 1024);
|
||||
callback = file -> {
|
||||
Log.d(TAG, file.getPath());
|
||||
try (RemoteFileChannel src = new RemoteFileChannel((RemoteFile) file, MODE_READ_ONLY)) {
|
||||
try (FileChannel src = fs.openChannel(file, MODE_READ_ONLY)) {
|
||||
for (;;) {
|
||||
// Randomize read/write length
|
||||
int len = r.nextInt(buf.capacity());
|
||||
|
@ -25,6 +25,7 @@ import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.internal.FileImpl;
|
||||
import com.topjohnwu.superuser.internal.Utils;
|
||||
import com.topjohnwu.superuser.nio.ExtendedFile;
|
||||
import com.topjohnwu.superuser.nio.FileSystemApi;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
@ -62,22 +63,23 @@ public class SuFile extends FileImpl<SuFile> {
|
||||
private Shell mShell;
|
||||
|
||||
public static ExtendedFile open(String pathname) {
|
||||
return Utils.isMainShellRoot() ? new SuFile(pathname) : ExtendedFile.get(pathname);
|
||||
return Utils.isMainShellRoot() ?
|
||||
new SuFile(pathname) : FileSystemApi.getLocal().getFile(pathname);
|
||||
}
|
||||
|
||||
public static ExtendedFile open(String parent, String child) {
|
||||
return Utils.isMainShellRoot() ?
|
||||
new SuFile(parent, child) : ExtendedFile.get(parent, child);
|
||||
new SuFile(parent, child) : FileSystemApi.getLocal().getFile(parent, child);
|
||||
}
|
||||
|
||||
public static ExtendedFile open(File parent, String child) {
|
||||
return Utils.isMainShellRoot() ?
|
||||
new SuFile(parent, child) : ExtendedFile.get(parent.getPath(), child);
|
||||
return Utils.isMainShellRoot() ? new SuFile(parent, child) :
|
||||
FileSystemApi.getLocal().getFile(parent.getPath(), child);
|
||||
}
|
||||
|
||||
public static ExtendedFile open(URI uri) {
|
||||
return Utils.isMainShellRoot() ?
|
||||
new SuFile(uri) : ExtendedFile.get(new File(uri).getPath());
|
||||
new SuFile(uri) : FileSystemApi.getLocal().getFile(new File(uri).getPath());
|
||||
}
|
||||
|
||||
private static final Creator<SuFile> CREATOR = new Creator<SuFile>() {
|
||||
|
@ -35,7 +35,6 @@ import android.util.MutableLong;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RestrictTo;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
@ -45,8 +44,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||||
public class FileSystemImpl extends IFileSystemService.Stub {
|
||||
class FileSystemService extends IFileSystemService.Stub {
|
||||
|
||||
// This is only for testing purpose
|
||||
private static final boolean FORCE_NO_SPLICE = false;
|
@ -34,6 +34,7 @@ import android.os.Build;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Int64Ref;
|
||||
import android.system.Os;
|
||||
import android.util.ArraySet;
|
||||
import android.util.MutableLong;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
@ -41,6 +42,9 @@ import androidx.annotation.RequiresApi;
|
||||
import java.io.FileDescriptor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Set;
|
||||
|
||||
class FileUtils {
|
||||
|
||||
@ -48,6 +52,14 @@ class FileUtils {
|
||||
private static Method splice;
|
||||
private static Method sendfile;
|
||||
|
||||
static class Flag {
|
||||
boolean read;
|
||||
boolean write;
|
||||
boolean create;
|
||||
boolean truncate;
|
||||
boolean append;
|
||||
}
|
||||
|
||||
static int pfdModeToPosix(int mode) {
|
||||
int res;
|
||||
if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
|
||||
@ -71,6 +83,64 @@ class FileUtils {
|
||||
return res;
|
||||
}
|
||||
|
||||
@RequiresApi(api = 26)
|
||||
static Set<OpenOption> pfdModeToOptions(int mode) {
|
||||
Set<OpenOption> set = new ArraySet<>();
|
||||
if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
|
||||
set.add(StandardOpenOption.READ);
|
||||
set.add(StandardOpenOption.WRITE);
|
||||
} else if ((mode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) {
|
||||
set.add(StandardOpenOption.WRITE);
|
||||
} else if ((mode & MODE_READ_ONLY) == MODE_READ_ONLY) {
|
||||
set.add(StandardOpenOption.READ);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Bad mode: " + mode);
|
||||
}
|
||||
if ((mode & MODE_CREATE) == MODE_CREATE) {
|
||||
set.add(StandardOpenOption.CREATE);
|
||||
}
|
||||
if ((mode & MODE_TRUNCATE) == MODE_TRUNCATE) {
|
||||
set.add(StandardOpenOption.TRUNCATE_EXISTING);
|
||||
}
|
||||
if ((mode & MODE_APPEND) == MODE_APPEND) {
|
||||
set.add(StandardOpenOption.APPEND);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
static Flag pfdModeToFlag(int mode) {
|
||||
Flag f = new Flag();
|
||||
if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
|
||||
f.read = true;
|
||||
f.write = true;
|
||||
} else if ((mode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) {
|
||||
f.write = true;
|
||||
} else if ((mode & MODE_READ_ONLY) == MODE_READ_ONLY) {
|
||||
f.read = true;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Bad mode: " + mode);
|
||||
}
|
||||
if ((mode & MODE_CREATE) == MODE_CREATE) {
|
||||
f.create = true;
|
||||
}
|
||||
if ((mode & MODE_TRUNCATE) == MODE_TRUNCATE) {
|
||||
f.truncate = true;
|
||||
}
|
||||
if ((mode & MODE_APPEND) == MODE_APPEND) {
|
||||
f.append = true;
|
||||
}
|
||||
|
||||
// Validate flags
|
||||
if (f.append && f.read) {
|
||||
throw new IllegalArgumentException("READ + APPEND not allowed");
|
||||
}
|
||||
if (f.append && f.truncate) {
|
||||
throw new IllegalArgumentException("APPEND + TRUNCATE not allowed");
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
@RequiresApi(api = 28)
|
||||
static long splice(
|
||||
FileDescriptor fdIn, Int64Ref offIn,
|
||||
|
@ -21,10 +21,7 @@ import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
import android.system.StructStat;
|
||||
|
||||
import androidx.annotation.RestrictTo;
|
||||
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||||
public class LocalFile extends FileImpl<LocalFile> {
|
||||
class LocalFile extends FileImpl<LocalFile> {
|
||||
|
||||
private static final Creator<LocalFile> CREATOR = new Creator<LocalFile>() {
|
||||
|
||||
@ -44,11 +41,11 @@ public class LocalFile extends FileImpl<LocalFile> {
|
||||
}
|
||||
};
|
||||
|
||||
public LocalFile(String pathname) {
|
||||
LocalFile(String pathname) {
|
||||
super(pathname, CREATOR);
|
||||
}
|
||||
|
||||
public LocalFile(String parent, String child) {
|
||||
LocalFile(String parent, String child) {
|
||||
super(parent, child, CREATOR);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2022 John "topjohnwu" Wu
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.topjohnwu.superuser.internal;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RestrictTo;
|
||||
|
||||
import com.topjohnwu.superuser.nio.ExtendedFile;
|
||||
import com.topjohnwu.superuser.nio.FileSystemApi;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||||
public final class NIOFactory {
|
||||
|
||||
private NIOFactory() {}
|
||||
|
||||
public static FileSystemApi createLocal() {
|
||||
return new FileSystemApi() {
|
||||
@NonNull
|
||||
@Override
|
||||
public ExtendedFile getFile(@NonNull String pathname) {
|
||||
return new LocalFile(pathname);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ExtendedFile getFile(@Nullable String parent, @NonNull String child) {
|
||||
return new LocalFile(parent, child);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public FileChannel openChannel(@NonNull File file, int mode) throws IOException {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
return FileChannel.open(file.toPath(), FileUtils.pfdModeToOptions(mode));
|
||||
} else {
|
||||
FileUtils.Flag f = FileUtils.pfdModeToFlag(mode);
|
||||
if (f.write) {
|
||||
if (!f.create) {
|
||||
if (!file.exists()) {
|
||||
ErrnoException e = new ErrnoException("open", OsConstants.ENOENT);
|
||||
throw new FileNotFoundException(file + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (f.append) {
|
||||
return new FileOutputStream(file, true).getChannel();
|
||||
}
|
||||
if (!f.read && f.truncate) {
|
||||
return new FileOutputStream(file, false).getChannel();
|
||||
}
|
||||
|
||||
// Unfortunately, there is no way to create a write-only channel
|
||||
// without truncating. Forced to open rw RAF in all cases.
|
||||
FileChannel ch = new RandomAccessFile(file, "rw").getChannel();
|
||||
if (f.truncate) {
|
||||
ch.truncate(0);
|
||||
}
|
||||
return ch;
|
||||
} else {
|
||||
return new FileInputStream(file).getChannel();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static FileSystemApi createRemote(IBinder b) {
|
||||
return new FileSystemApi() {
|
||||
final IFileSystemService fs = IFileSystemService.Stub.asInterface(b);
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ExtendedFile getFile(@NonNull String pathname) {
|
||||
return new RemoteFile(fs, pathname);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ExtendedFile getFile(@Nullable String parent, @NonNull String child) {
|
||||
return new RemoteFile(fs, parent, child);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public FileChannel openChannel(@NonNull File file, int mode) throws IOException {
|
||||
return new RemoteFileChannel(fs, file, mode);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static FileSystemService createFsService() {
|
||||
return new FileSystemService();
|
||||
}
|
||||
}
|
@ -19,16 +19,13 @@ package com.topjohnwu.superuser.internal;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.RestrictTo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
||||
public class ParcelValues extends ArrayList<Object> implements Parcelable {
|
||||
class ParcelValues extends ArrayList<Object> implements Parcelable {
|
||||
|
||||
private static final ClassLoader cl = ParcelValues.class.getClassLoader();
|
||||
|
||||
public static final Creator<ParcelValues> CREATOR = new Creator<ParcelValues>() {
|
||||
static final Creator<ParcelValues> CREATOR = new Creator<ParcelValues>() {
|
||||
@Override
|
||||
public ParcelValues createFromParcel(Parcel in) {
|
||||
return new ParcelValues(in);
|
||||
@ -40,7 +37,7 @@ public class ParcelValues extends ArrayList<Object> implements Parcelable {
|
||||
}
|
||||
};
|
||||
|
||||
public ParcelValues() {}
|
||||
ParcelValues() {}
|
||||
|
||||
private ParcelValues(Parcel in) {
|
||||
int size = in.readInt();
|
||||
|
@ -14,26 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.topjohnwu.superuser.nio;
|
||||
package com.topjohnwu.superuser.internal;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.topjohnwu.superuser.internal.FileImpl;
|
||||
import com.topjohnwu.superuser.internal.IFileSystemService;
|
||||
import com.topjohnwu.superuser.internal.ParcelValues;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Represents a {@link File} instance on a remote process.
|
||||
*/
|
||||
public class RemoteFile extends FileImpl<RemoteFile> {
|
||||
class RemoteFile extends FileImpl<RemoteFile> {
|
||||
|
||||
final IFileSystemService fs;
|
||||
private final IFileSystemService fs;
|
||||
|
||||
private static final Creator<RemoteFile> CREATOR = new Creator<RemoteFile>() {
|
||||
|
||||
@ -49,22 +42,17 @@ public class RemoteFile extends FileImpl<RemoteFile> {
|
||||
|
||||
@Override
|
||||
public RemoteFile createChild(RemoteFile parent, String name) {
|
||||
return new RemoteFile(parent.fs, new File(parent, name).getAbsolutePath());
|
||||
return new RemoteFile(parent.fs, parent.getPath(), name);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new file instance on a remote process.
|
||||
* @param remote the remote process's filesystem API
|
||||
* @param file the file path
|
||||
*/
|
||||
public RemoteFile(FileSystemApi.Remote remote, File file) {
|
||||
super(file.getAbsolutePath(), CREATOR);
|
||||
fs = remote.fs;
|
||||
RemoteFile(IFileSystemService f, String path) {
|
||||
super(path, CREATOR);
|
||||
fs = f;
|
||||
}
|
||||
|
||||
private RemoteFile(IFileSystemService f, String path) {
|
||||
super(path, CREATOR);
|
||||
RemoteFile(IFileSystemService f, String parent, String child) {
|
||||
super(parent, child, CREATOR);
|
||||
fs = f;
|
||||
}
|
||||
|
||||
@ -129,9 +117,6 @@ public class RemoteFile extends FileImpl<RemoteFile> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isBlock() {
|
||||
try {
|
||||
@ -141,9 +126,6 @@ public class RemoteFile extends FileImpl<RemoteFile> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isCharacter() {
|
||||
try {
|
||||
@ -153,9 +135,6 @@ public class RemoteFile extends FileImpl<RemoteFile> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean isSymlink() {
|
||||
try {
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.topjohnwu.superuser.nio;
|
||||
package com.topjohnwu.superuser.internal;
|
||||
|
||||
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
|
||||
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
|
||||
@ -28,9 +28,6 @@ import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import com.topjohnwu.superuser.internal.IFileSystemService;
|
||||
import com.topjohnwu.superuser.internal.ParcelValues;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
@ -44,7 +41,7 @@ import java.nio.channels.NonWritableChannelException;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
public class RemoteFileChannel extends FileChannel {
|
||||
class RemoteFileChannel extends FileChannel {
|
||||
|
||||
private static final int PIPE_CAPACITY = 16 * 4096;
|
||||
|
||||
@ -57,8 +54,8 @@ public class RemoteFileChannel extends FileChannel {
|
||||
private final int handle;
|
||||
|
||||
@SuppressWarnings("OctalInteger")
|
||||
public RemoteFileChannel(RemoteFile file, int mode) throws IOException {
|
||||
fs = file.fs;
|
||||
RemoteFileChannel(IFileSystemService fs, File file, int mode) throws IOException {
|
||||
this.fs = fs;
|
||||
this.mode = mode;
|
||||
try {
|
||||
// We use a FIFO created on the client side instead of opening a pipe and
|
||||
@ -68,7 +65,7 @@ public class RemoteFileChannel extends FileChannel {
|
||||
Os.mkfifo(fifo.getPath(), 0644);
|
||||
|
||||
// Open the file on the remote process
|
||||
handle = checkAndGet(fs.open(file.getPath(), mode, fifo.getPath()));
|
||||
handle = checkAndGet(fs.open(file.getAbsolutePath(), mode, fifo.getPath()));
|
||||
|
||||
// Since we do not have the machinery to interrupt native pthreads, we
|
||||
// have to make sure none of our I/O can block in all operations.
|
@ -19,8 +19,6 @@ package com.topjohnwu.superuser.nio;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.topjohnwu.superuser.internal.LocalFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FilenameFilter;
|
||||
@ -29,22 +27,6 @@ import java.net.URI;
|
||||
|
||||
public abstract class ExtendedFile extends File {
|
||||
|
||||
/**
|
||||
* @see File#File(String)
|
||||
*/
|
||||
@NonNull
|
||||
public static ExtendedFile get(@NonNull String pathname) {
|
||||
return new LocalFile(pathname);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see File#File(String, String)
|
||||
*/
|
||||
@NonNull
|
||||
public static ExtendedFile get(@Nullable String parent, @NonNull String child) {
|
||||
return new LocalFile(parent, child);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
@ -17,61 +17,111 @@
|
||||
package com.topjohnwu.superuser.nio;
|
||||
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.topjohnwu.superuser.internal.FileSystemImpl;
|
||||
import com.topjohnwu.superuser.internal.IFileSystemService;
|
||||
import com.topjohnwu.superuser.internal.NIOFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
/**
|
||||
* Expose filesystem APIs over Binder.
|
||||
*
|
||||
* In the remote process, create a new {@link Binder} object that exposes filesystem APIs by
|
||||
* calling {@link #createBinder()}. You can then pass this {@link Binder} object in many different
|
||||
* ways, such as adding it to an Intent, sending it with {@link Message}es, returning it in the
|
||||
* {@code onBind()} of bind / root services, or returning it in an AIDL interface.
|
||||
*
|
||||
* In the client process, create a {@link Remote} instance by passing the remote {@link IBinder}
|
||||
* proxy you received to {@link #asRemote(IBinder)}. This {@link Remote} instance can then be used
|
||||
* to construct remote I/O classes, such as {@link RemoteFile}
|
||||
* Access file system APIs.
|
||||
*/
|
||||
public final class FileSystemApi {
|
||||
public abstract class FileSystemApi {
|
||||
|
||||
// The actual reason why this class exists is because we do not want to expose
|
||||
// IFileSystemService in any API surface to keep it an internal implementation detail.
|
||||
private static Binder fsService;
|
||||
|
||||
private FileSystemApi() {}
|
||||
private static final FileSystemApi LOCAL = NIOFactory.createLocal();
|
||||
|
||||
/**
|
||||
* Create a new {@link Binder} instance that exposes filesystem APIs.
|
||||
* An example use case is to return this value in {@code onBind()} of (root) services.
|
||||
* Get the service that exposes file system APIs over Binder IPC.
|
||||
* <p>
|
||||
* Sending the {@link Binder} obtained from this method to a client process enables
|
||||
* the calling process to perform file system operations on behalf of the client.
|
||||
* This allows a client process to access files normally denied by its permissions.
|
||||
* <p>
|
||||
* You can pass this {@link Binder} object in many different ways, such as returning it in the
|
||||
* {@code onBind()} method of (root) services, passing it around with a {@link Bundle},
|
||||
* or returning it in an AIDL interface method. The receiving end will get an {@link IBinder},
|
||||
* which should be passed to {@link #getRemote(IBinder)} for usage.
|
||||
*/
|
||||
@NonNull
|
||||
public static Binder createBinder() {
|
||||
return new FileSystemImpl();
|
||||
public synchronized static Binder getService() {
|
||||
if (fsService == null)
|
||||
fsService = NIOFactory.createFsService();
|
||||
return fsService;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static FileSystemApi getLocal() {
|
||||
return LOCAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Remote} instance that exposes filesystem APIs of a remote process.
|
||||
* @param service an instance or a remote proxy of the return value of {@link #createBinder()}
|
||||
* @return this return value is for constructing {@link RemoteFile}
|
||||
* Create a {@link FileSystemApi} to access file system APIs of a remote process.
|
||||
* @param binder a remote proxy of the {@link Binder} obtained from {@link #getService()}
|
||||
*/
|
||||
@NonNull
|
||||
public static Remote asRemote(@NonNull IBinder service) {
|
||||
return new Remote(service);
|
||||
public static FileSystemApi getRemote(@NonNull IBinder binder) {
|
||||
return NIOFactory.createRemote(binder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the filesystem API of a remote process.
|
||||
* @see #asRemote(IBinder)
|
||||
* @see File#File(String)
|
||||
*/
|
||||
public static class Remote {
|
||||
final IFileSystemService fs;
|
||||
Remote(IBinder b) {
|
||||
fs = IFileSystemService.Stub.asInterface(b);
|
||||
}
|
||||
@NonNull
|
||||
public abstract ExtendedFile getFile(@NonNull String pathname);
|
||||
|
||||
/**
|
||||
* @see File#File(String, String)
|
||||
*/
|
||||
@NonNull
|
||||
public abstract ExtendedFile getFile(@Nullable String parent, @NonNull String child);
|
||||
|
||||
/**
|
||||
* @see File#File(File, String)
|
||||
*/
|
||||
@NonNull
|
||||
public final ExtendedFile getFile(@Nullable File parent, @NonNull String child) {
|
||||
return getFile(parent == null ? null : parent.getPath(), child);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see File#File(URI)
|
||||
*/
|
||||
@NonNull
|
||||
public final ExtendedFile getFile(@NonNull URI uri) {
|
||||
return getFile(new File(uri).getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a file channel to access the file.
|
||||
* @param pathname the file to be opened.
|
||||
* @param mode same {@code mode} argument in {@link ParcelFileDescriptor#open(File, int)}
|
||||
* @return a new FileChannel pointing to the given file.
|
||||
* @throws IOException if the given file can not be opened with the requested mode.
|
||||
* @see ParcelFileDescriptor#open(File, int)
|
||||
*/
|
||||
@NonNull
|
||||
public final FileChannel openChannel(@NonNull String pathname, int mode) throws IOException {
|
||||
return openChannel(new File(pathname), mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a file channel to access the file.
|
||||
* @param file the file to be opened.
|
||||
* @param mode same {@code mode} argument in {@link ParcelFileDescriptor#open(File, int)}
|
||||
* @return a new FileChannel pointing to the given file.
|
||||
* @throws IOException if the given file can not be opened with the requested mode.
|
||||
* @see ParcelFileDescriptor#open(File, int)
|
||||
*/
|
||||
@NonNull
|
||||
public abstract FileChannel openChannel(@NonNull File file, int mode) throws IOException;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user