Repository navigation
Expand file tree
/
Copy pathTermExec.java
More file actions
145 lines (122 loc) · 4.82 KB
/
Copy pathTermExec.java
File metadata and controls
145 lines (122 loc) · 4.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package jackpal.androidterm;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
/**
* Utility methods for creating and managing a subprocess. This class differs from
* {@link java.lang.ProcessBuilder} in that a pty is used to communicate with the subprocess.
* <p>
* Pseudo-terminals are a powerful Unix feature, that allows programs to interact with other programs
* they start in slightly more human-like way. For example, a pty owner can send ^C (aka SIGINT)
* to attached shell, even if said shell runs under a different user ID.
*/
public class TermExec {
// Warning: bump the library revision, when an incompatible change happens
static {
System.loadLibrary("termexec");
}
public static final String SERVICE_ACTION_V1 = "jackpal.androidterm.action.START_TERM.v1";
private static Field descriptorField;
private final List<String> command;
private final Map<String, String> environment;
public TermExec(@NonNull String... command) {
this(new ArrayList<>(Arrays.asList(command)));
}
public TermExec(@NonNull List<String> command) {
this.command = command;
this.environment = new Hashtable<>(System.getenv());
}
public
@NonNull
List<String> command() {
return command;
}
public
@NonNull
Map<String, String> environment() {
return environment;
}
public
@NonNull
TermExec command(@NonNull String... command) {
return command(new ArrayList<>(Arrays.asList(command)));
}
public
@NonNull
TermExec command(List<String> command) {
command.clear();
command.addAll(command);
return this;
}
/**
* Start the process and attach it to the pty, corresponding to given file descriptor.
* You have to obtain this file descriptor yourself by calling
* {@link android.os.ParcelFileDescriptor#open} on special terminal multiplexer
* device (located at /dev/ptmx).
* <p>
* Callers are responsible for closing the descriptor.
*
* @return the PID of the started process.
*/
public int start(@NonNull ParcelFileDescriptor ptmxFd) throws IOException {
if (Looper.getMainLooper() == Looper.myLooper())
throw new IllegalStateException("This method must not be called from the main thread!");
if (command.size() == 0)
throw new IllegalStateException("Empty command!");
final String cmd = command.remove(0);
final String[] cmdArray = command.toArray(new String[command.size()]);
final String[] envArray = new String[environment.size()];
int i = 0;
for (Map.Entry<String, String> entry : environment.entrySet()) {
envArray[i++] = entry.getKey() + "=" + entry.getValue();
}
return createSubprocess(ptmxFd, cmd, cmdArray, envArray);
}
/**
* Causes the calling thread to wait for the process associated with the
* receiver to finish executing.
*
* @return The exit value of the Process being waited on
*/
public static native int waitFor(int processId);
/**
* Send signal via the "kill" system call. Android {@link android.os.Process#sendSignal} does not
* allow negative numbers (denoting process groups) to be used.
*/
public static native void sendSignal(int processId, int signal);
public static int createSubprocess(ParcelFileDescriptor masterFd, String cmd, String[] args, String[] envVars) throws IOException {
final int integerFd;
if (Build.VERSION.SDK_INT >= 12)
integerFd = FdHelperHoneycomb.getFd(masterFd);
else {
try {
if (descriptorField == null) {
descriptorField = FileDescriptor.class.getDeclaredField("descriptor");
descriptorField.setAccessible(true);
}
integerFd = descriptorField.getInt(masterFd.getFileDescriptor());
} catch (Exception e) {
throw new IOException("Unable to obtain file descriptor on this OS version: " + e.getMessage());
}
}
return createSubprocessInternal(cmd, args, envVars, integerFd);
}
private static native int createSubprocessInternal(String cmd, String[] args, String[] envVars, int masterFd);
}
// prevents runtime errors on old API versions with ruthless verifier
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
class FdHelperHoneycomb {
static int getFd(ParcelFileDescriptor descriptor) {
return descriptor.getFd();
}
}