Cleanup ShellImpl

This commit is contained in:
topjohnwu 2021-01-31 06:19:21 -08:00
parent c5ddd26bf4
commit bfff8e6189
4 changed files with 133 additions and 117 deletions

View File

@ -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;
}

View File

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

View File

@ -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;
}
}

View File

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