mirror of
https://github.com/topjohnwu/libsu.git
synced 2024-11-23 12:09:42 +00:00
Cleanup ShellImpl
This commit is contained in:
parent
c5ddd26bf4
commit
bfff8e6189
@ -25,33 +25,41 @@ import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
class JobImpl extends Shell.Job implements Closeable {
|
||||
|
||||
protected List<String> out, err;
|
||||
private final List<ShellInputSource> sources;
|
||||
private final List<ShellInputSource> sources = new ArrayList<>();
|
||||
protected ShellImpl shell;
|
||||
private boolean stderrSet = false;
|
||||
|
||||
JobImpl() {
|
||||
sources = new ArrayList<>();
|
||||
}
|
||||
JobImpl() {}
|
||||
|
||||
JobImpl(ShellImpl s) {
|
||||
this();
|
||||
shell = s;
|
||||
}
|
||||
|
||||
private ResultImpl exec0() {
|
||||
ResultImpl result = new ResultImpl();
|
||||
boolean redirect = !stderrSet && shell.redirect;
|
||||
result.out = out;
|
||||
result.err = redirect ? out : err;
|
||||
Shell.Task task = shell.newTask(sources, result);
|
||||
if (redirect)
|
||||
err = out;
|
||||
|
||||
ResultImpl result = new ResultImpl();
|
||||
if (out != null && out == err && !Utils.isSynchronized(out)) {
|
||||
// Synchronize the list internally only if both lists are the same and are not
|
||||
// already synchronized by the user
|
||||
List<String> list = Collections.synchronizedList(out);
|
||||
result.out = list;
|
||||
result.err = list;
|
||||
} else {
|
||||
result.out = out;
|
||||
result.err = err;
|
||||
}
|
||||
try {
|
||||
shell.execTask(task);
|
||||
shell.execTask(new TaskImpl(sources, result));
|
||||
} catch (IOException e) {
|
||||
if (e instanceof ShellTerminatedException) {
|
||||
return ResultImpl.SHELL_ERR;
|
||||
@ -61,9 +69,9 @@ class JobImpl extends Shell.Job implements Closeable {
|
||||
}
|
||||
} finally {
|
||||
close();
|
||||
result.out = out;
|
||||
result.err = redirect ? null : err;
|
||||
}
|
||||
if (redirect)
|
||||
result.err = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -30,11 +30,7 @@ import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
@ -61,9 +57,6 @@ class ShellImpl extends Shell {
|
||||
private final NoCloseOutputStream STDIN;
|
||||
private final NoCloseInputStream STDOUT;
|
||||
private final NoCloseInputStream STDERR;
|
||||
private final StreamGobbler.OUT outGobbler;
|
||||
private final StreamGobbler.ERR errGobbler;
|
||||
private final byte[] endCmd;
|
||||
|
||||
private static class NoCloseInputStream extends FilterInputStream {
|
||||
|
||||
@ -109,12 +102,6 @@ class ShellImpl extends Shell {
|
||||
STDIN = new NoCloseOutputStream(process.getOutputStream());
|
||||
STDOUT = new NoCloseInputStream(process.getInputStream());
|
||||
STDERR = new NoCloseInputStream(process.getErrorStream());
|
||||
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
Utils.log(TAG, "UUID: " + uuid);
|
||||
outGobbler = new StreamGobbler.OUT(uuid);
|
||||
errGobbler = new StreamGobbler.ERR(uuid);
|
||||
endCmd = String.format("__RET=$?;echo %s;echo %s >&2;echo $__RET;unset __RET\n", uuid, uuid).getBytes(UTF_8);
|
||||
executor = new SerialExecutorService();
|
||||
|
||||
if (cmd.length >= 2 && TextUtils.equals(cmd[1], "--mount-master"))
|
||||
@ -248,46 +235,4 @@ class ShellImpl extends Shell {
|
||||
return new JobImpl(this);
|
||||
}
|
||||
|
||||
Task newTask(List<ShellInputSource> sources, ResultImpl res) {
|
||||
return new DefaultTask(sources, res);
|
||||
}
|
||||
|
||||
private class DefaultTask implements Task {
|
||||
|
||||
private final ResultImpl res;
|
||||
private final List<ShellInputSource> sources;
|
||||
|
||||
DefaultTask(List<ShellInputSource> s, ResultImpl r) {
|
||||
sources = s;
|
||||
res = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(@NonNull OutputStream stdin, @NonNull InputStream stdout, @NonNull InputStream stderr) throws IOException {
|
||||
Future<Integer> out;
|
||||
Future<Void> err;
|
||||
if (res.out != null && res.out == res.err &&
|
||||
!Utils.isSynchronized(res.out)) {
|
||||
// Synchronize the list internally only if both lists are the same and are not
|
||||
// already synchronized by the user
|
||||
List<String> list = Collections.synchronizedList(res.out);
|
||||
out = EXECUTOR.submit(outGobbler.set(stdout, list));
|
||||
err = EXECUTOR.submit(errGobbler.set(stderr, list));
|
||||
} else {
|
||||
out = EXECUTOR.submit(outGobbler.set(stdout, res.out));
|
||||
err = EXECUTOR.submit(errGobbler.set(stderr, res.err));
|
||||
}
|
||||
|
||||
for (ShellInputSource src : sources)
|
||||
src.serve(stdin);
|
||||
stdin.write(endCmd);
|
||||
stdin.flush();
|
||||
try {
|
||||
res.code = out.get();
|
||||
err.get();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
throw (InterruptedIOException) new InterruptedIOException().initCause(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,93 +17,79 @@
|
||||
package com.topjohnwu.superuser.internal;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static com.topjohnwu.superuser.internal.TaskImpl.END_UUID;
|
||||
import static com.topjohnwu.superuser.internal.TaskImpl.UUID_LEN;
|
||||
import static com.topjohnwu.superuser.internal.Utils.UTF_8;
|
||||
|
||||
abstract class StreamGobbler<T> implements Callable<T> {
|
||||
|
||||
private static final String TAG = "SHELLOUT";
|
||||
|
||||
private final String eos;
|
||||
private final int eosLength;
|
||||
protected final InputStream in;
|
||||
protected final List<String> list;
|
||||
|
||||
protected InputStream in;
|
||||
protected List<String> list;
|
||||
|
||||
StreamGobbler(String eos) {
|
||||
this.eos = eos;
|
||||
this.eosLength = this.eos.length();
|
||||
}
|
||||
|
||||
public Callable<T> set(InputStream in, List<String> list) {
|
||||
StreamGobbler(InputStream in, List<String> list) {
|
||||
this.in = in;
|
||||
this.list = list;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected boolean isEOS(String line) {
|
||||
if (line == null) {
|
||||
return true;
|
||||
private boolean outputAndCheck(String line) {
|
||||
if (line == null)
|
||||
return false;
|
||||
|
||||
int len = line.length();
|
||||
boolean end = line.startsWith(END_UUID, len - UUID_LEN);
|
||||
if (end) {
|
||||
if (len == UUID_LEN)
|
||||
return false;
|
||||
line = line.substring(0, len - UUID_LEN);
|
||||
}
|
||||
boolean eof = line.endsWith(eos);
|
||||
if (eof) {
|
||||
if (line.length() > eosLength) {
|
||||
line = line.substring(0, eosLength);
|
||||
} else {
|
||||
line = null;
|
||||
}
|
||||
}
|
||||
if (list != null && line != null) {
|
||||
if (list != null) {
|
||||
list.add(line);
|
||||
Utils.log(TAG, line);
|
||||
}
|
||||
return eof;
|
||||
return !end;
|
||||
}
|
||||
|
||||
protected String process(boolean res) throws IOException {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(in, UTF_8));
|
||||
String line;
|
||||
do {
|
||||
line = br.readLine();
|
||||
} while (outputAndCheck(line));
|
||||
return res ? br.readLine() : null;
|
||||
}
|
||||
|
||||
static class OUT extends StreamGobbler<Integer> {
|
||||
|
||||
private static final int NO_RESULT_CODE = 1;
|
||||
|
||||
OUT(String eos) {
|
||||
super(eos);
|
||||
}
|
||||
OUT(InputStream in, List<String> list) { super(in, list); }
|
||||
|
||||
@Override
|
||||
public Integer call() throws Exception {
|
||||
int code;
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(in, UTF_8))) {
|
||||
String line;
|
||||
do {
|
||||
line = br.readLine();
|
||||
} while (!isEOS(line));
|
||||
String resultCodeLine = br.readLine();
|
||||
code = resultCodeLine == null ? NO_RESULT_CODE : Integer.parseInt(resultCodeLine);
|
||||
String code = process(true);
|
||||
try {
|
||||
return code == null ? NO_RESULT_CODE : Integer.parseInt(code);
|
||||
} catch (NumberFormatException e) {
|
||||
return NO_RESULT_CODE;
|
||||
}
|
||||
in = null;
|
||||
list = null;
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
static class ERR extends StreamGobbler<Void> {
|
||||
ERR(String eos) {
|
||||
super(eos);
|
||||
}
|
||||
|
||||
ERR(InputStream in, List<String> list) { super(in, list); }
|
||||
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(in, UTF_8))) {
|
||||
String line;
|
||||
do {
|
||||
line = br.readLine();
|
||||
} while (!isEOS(line));
|
||||
}
|
||||
in = null;
|
||||
list = null;
|
||||
process(false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2021 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 androidx.annotation.NonNull;
|
||||
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static com.topjohnwu.superuser.Shell.EXECUTOR;
|
||||
import static com.topjohnwu.superuser.internal.Utils.UTF_8;
|
||||
|
||||
class TaskImpl implements Shell.Task {
|
||||
|
||||
static final String END_UUID;
|
||||
static final byte[] END_CMD;
|
||||
static final int UUID_LEN = 36;
|
||||
|
||||
static {
|
||||
END_UUID = UUID.randomUUID().toString();
|
||||
END_CMD = String
|
||||
.format("__RET=$?;echo %1$s;echo %1$s >&2;echo $__RET;unset __RET\n", END_UUID)
|
||||
.getBytes(UTF_8);
|
||||
// UUID_LEN = END_UUID.length();
|
||||
}
|
||||
|
||||
private final List<ShellInputSource> sources;
|
||||
private final ResultImpl res;
|
||||
|
||||
TaskImpl(List<ShellInputSource> sources, ResultImpl res) {
|
||||
this.sources = sources;
|
||||
this.res = res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(@NonNull OutputStream stdin,
|
||||
@NonNull InputStream stdout,
|
||||
@NonNull InputStream stderr) throws IOException {
|
||||
|
||||
Future<Integer> out = EXECUTOR.submit(new StreamGobbler.OUT(stdout, res.out));
|
||||
Future<Void> err = EXECUTOR.submit(new StreamGobbler.ERR(stderr, res.err));
|
||||
|
||||
for (ShellInputSource src : sources)
|
||||
src.serve(stdin);
|
||||
stdin.write(END_CMD);
|
||||
stdin.flush();
|
||||
|
||||
try {
|
||||
res.code = out.get();
|
||||
err.get();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
throw (InterruptedIOException) new InterruptedIOException().initCause(e);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user