Skip to content

Commit 4c6a519

Browse files
dougvjagnostic-apollo
authored andcommitted
Fixed: Fix usb fd not being sent due to refactoring in 3c1a6be
This is a minor refactor of the WithAncillaryFd ResultReturner that achieves two goals: 1. The usb fd is no longer closed before the message is sent over the socket. This resolves #643 2. We queue the fd to be sent (using setFileDescriptorsForSend) and then write `@` as data and then flush the output message in a single operation so that fd is actually sent. Writing `@` is required because as per docs "The file descriptors will be sent with the next write of normal data, and will be delivered in a single ancillary message.". The `@` character is not special, it is just the chosen character expected as the message by the native `termux-api` command when a fd is sent. - https://developer.android.com/reference/android/net/LocalSocket#setFileDescriptorsForSend(java.io.FileDescriptor[]) - https://github.com/termux/termux-api-package/blob/e62bdadea3f26b60430bb85248f300fee68ecdcc/termux-api.c#L358 Explanation for why this got broken in 3c1a6be by @agnostic-apollo at #644 (comment): Previously, in `UsbApi`, the fd was only temporarily stored in `WithAncillaryFd`, but not actually sent or passed to `LocalSocket`, and then a `@` was written, possibly in attempts to send it, even though like I said, it wasn't passed to `LocalSocket` with `setFileDescriptorsForSend()` yet, so it was a pointless write, but worked due to autoflush on `close()` (check below). After this, when `writeResult()` returned, then `fd` was passed to `LocalSocket`, but still not sent, since no data was written after it. - https://github.com/termux/termux-api/blob/3bea194249586a7dcb143e66b46c1694cb6ca21a/app/src/main/java/com/termux/api/apis/UsbAPI.java#L69-L70 - https://github.com/termux/termux-api/blob/3c1a6be86ff0768fa8be029267fbe96dd7fbfb7f/app/src/main/java/com/termux/api/util/ResultReturner.java#L173-L181 Previously, before 3c1a6be, the `PrintWriter` in `ResultReturner` was using java `try-with-resources`, which automatically closes when `try` gets out of scope. Additionally, when `new PrintWriter(outputSocket.getOutputStream()` is called, it uses a `BufferedWriter` internally, which when closed, automatically flushes. What this would do is actually send the socket that was previously passed to `LocalSocket`. Moreover, `PrintWriter` was also closed before `fd` was closed since `try-with-resources` finished before, but after the commit, it was closed afterwards, causing the issue. - https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:libcore/ojluni/src/main/java/java/io/PrintWriter.java;l=164 - https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:libcore/ojluni/src/main/java/java/io/BufferedWriter.java;l=268 - https://cs.android.com/android/platform/superproject/main/+/main:libcore/ojluni/src/main/java/java/io/Writer.java;l=412 Closes #643
1 parent a9abc96 commit 4c6a519

File tree

2 files changed

+50
-18
lines changed

2 files changed

+50
-18
lines changed

app/src/main/java/com/termux/api/apis/UsbAPI.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ public void writeResult(PrintWriter out) {
6666
if (result < 0) {
6767
out.append("Failed to open device\n");
6868
} else {
69-
this.setFd(result);
70-
out.append("@"); // has to be non-empty
69+
this.sendFd(out, result);
7170
}
7271
} else out.append("No permission\n");
7372
}

app/src/main/java/com/termux/api/util/ResultReturner.java

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,53 @@ public final void setInput(InputStream inputStream) throws Exception {
103103
}
104104

105105
public static abstract class WithAncillaryFd implements ResultWriter {
106-
private int fd = -1;
106+
private LocalSocket outputSocket = null;
107+
private final ParcelFileDescriptor[] pfds = { null };
107108

108-
public final void setFd(int newFd) {
109-
fd = newFd;
109+
public final void setOutputSocketForFds(LocalSocket outputSocket) {
110+
this.outputSocket = outputSocket;
110111
}
111112

112-
public final int getFd() {
113-
return fd;
113+
public final void sendFd(PrintWriter out, int fd) {
114+
// If fd already sent, then error out as we only support sending one currently.
115+
if (this.pfds[0] != null) {
116+
Logger.logStackTraceWithMessage(LOG_TAG, "File descriptor already sent", new Exception());
117+
return;
118+
}
119+
120+
this.pfds[0] = ParcelFileDescriptor.adoptFd(fd);
121+
FileDescriptor[] fds = { pfds[0].getFileDescriptor() };
122+
123+
// Set fd to be sent
124+
outputSocket.setFileDescriptorsForSend(fds);
125+
126+
// As per the docs:
127+
// > The file descriptors will be sent with the next write of normal data, and will be
128+
// delivered in a single ancillary message.
129+
// - https://developer.android.com/reference/android/net/LocalSocket#setFileDescriptorsForSend(java.io.FileDescriptor[])
130+
// So we write the `@` character. It is not special, it is just the chosen character
131+
// expected as the message by the native `termux-api` command when a fd is sent.
132+
// - https://github.com/termux/termux-api-package/blob/e62bdadea3f26b60430bb85248f300fee68ecdcc/termux-api.c#L358
133+
out.print("@");
134+
135+
// Actually send the by fd by flushing the data previously written (`@`) as PrintWriter is buffered.
136+
out.flush();
137+
138+
// Clear existing fd after it has been sent, otherwise it will get sent for every data write,
139+
// even though we are currently not writing anything else. Android will not clear it automatically.
140+
// - https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/net/LocalSocketImpl.java;l=523?q=setFileDescriptorsForSend
141+
// - https://cs.android.com/android/_/android/platform/frameworks/base/+/refs/tags/android-14.0.0_r1:core/jni/android_net_LocalSocketImpl.cpp;l=194
142+
outputSocket.setFileDescriptorsForSend(null);
143+
}
144+
145+
public final void cleanupFds() {
146+
if (this.pfds[0] != null) {
147+
try {
148+
this.pfds[0].close();
149+
} catch (IOException e) {
150+
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to close file descriptor", e);
151+
}
152+
}
114153
}
115154
}
116155

@@ -152,7 +191,6 @@ public static void returnData(Object context, final Intent intent, final ResultW
152191
PrintWriter writer = null;
153192
LocalSocket outputSocket = null;
154193
try {
155-
final ParcelFileDescriptor[] pfds = { null };
156194
outputSocket = new LocalSocket();
157195
String outputSocketAdress = intent.getStringExtra(SOCKET_OUTPUT_EXTRA);
158196
if (outputSocketAdress == null || outputSocketAdress.isEmpty())
@@ -162,6 +200,9 @@ public static void returnData(Object context, final Intent intent, final ResultW
162200
writer = new PrintWriter(outputSocket.getOutputStream());
163201

164202
if (resultWriter != null) {
203+
if(resultWriter instanceof WithAncillaryFd) {
204+
((WithAncillaryFd) resultWriter).setOutputSocketForFds(outputSocket);
205+
}
165206
if (resultWriter instanceof BinaryOutput) {
166207
BinaryOutput bout = (BinaryOutput) resultWriter;
167208
bout.setOutput(outputSocket.getOutputStream());
@@ -178,19 +219,11 @@ public static void returnData(Object context, final Intent intent, final ResultW
178219
} else {
179220
resultWriter.writeResult(writer);
180221
}
181-
if(resultWriter instanceof WithAncillaryFd) {
182-
int fd = ((WithAncillaryFd) resultWriter).getFd();
183-
if (fd >= 0) {
184-
pfds[0] = ParcelFileDescriptor.adoptFd(fd);
185-
FileDescriptor[] fds = { pfds[0].getFileDescriptor() };
186-
outputSocket.setFileDescriptorsForSend(fds);
187-
}
222+
if (resultWriter instanceof WithAncillaryFd) {
223+
((WithAncillaryFd) resultWriter).cleanupFds();
188224
}
189225
}
190226

191-
if(pfds[0] != null) {
192-
pfds[0].close();
193-
}
194227

195228
if (asyncResult != null && receiver.isOrderedBroadcast()) {
196229
asyncResult.setResultCode(0);

0 commit comments

Comments
 (0)