diff --git a/app/src/main/java/com/termux/api/apis/UsbAPI.java b/app/src/main/java/com/termux/api/apis/UsbAPI.java index 667ba179..e44f793e 100644 --- a/app/src/main/java/com/termux/api/apis/UsbAPI.java +++ b/app/src/main/java/com/termux/api/apis/UsbAPI.java @@ -66,8 +66,7 @@ public void writeResult(PrintWriter out) { if (result < 0) { out.append("Failed to open device\n"); } else { - this.setFd(result); - out.append("@"); // has to be non-empty + this.sendFd(out, result); } } else out.append("No permission\n"); } diff --git a/app/src/main/java/com/termux/api/util/ResultReturner.java b/app/src/main/java/com/termux/api/util/ResultReturner.java index b96e8f3b..6a207e1b 100644 --- a/app/src/main/java/com/termux/api/util/ResultReturner.java +++ b/app/src/main/java/com/termux/api/util/ResultReturner.java @@ -103,14 +103,53 @@ public final void setInput(InputStream inputStream) throws Exception { } public static abstract class WithAncillaryFd implements ResultWriter { - private int fd = -1; + private LocalSocket outputSocket = null; + private final ParcelFileDescriptor[] pfds = { null }; - public final void setFd(int newFd) { - fd = newFd; + public final void setOutputSocketForFds(LocalSocket outputSocket) { + this.outputSocket = outputSocket; } - public final int getFd() { - return fd; + public final void sendFd(PrintWriter out, int fd) { + // If fd already sent, then error out as we only support sending one currently. + if (this.pfds[0] != null) { + Logger.logStackTraceWithMessage(LOG_TAG, "File descriptor already sent", new Exception()); + return; + } + + this.pfds[0] = ParcelFileDescriptor.adoptFd(fd); + FileDescriptor[] fds = { pfds[0].getFileDescriptor() }; + + // Set fd to be sent + outputSocket.setFileDescriptorsForSend(fds); + + // As per the docs: + // > The file descriptors will be sent with the next write of normal data, and will be + // delivered in a single ancillary message. + // - https://developer.android.com/reference/android/net/LocalSocket#setFileDescriptorsForSend(java.io.FileDescriptor[]) + // So we write the `@` character. It 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://github.com/termux/termux-api-package/blob/e62bdadea3f26b60430bb85248f300fee68ecdcc/termux-api.c#L358 + out.print("@"); + + // Actually send the by fd by flushing the data previously written (`@`) as PrintWriter is buffered. + out.flush(); + + // Clear existing fd after it has been sent, otherwise it will get sent for every data write, + // even though we are currently not writing anything else. Android will not clear it automatically. + // - https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/net/LocalSocketImpl.java;l=523?q=setFileDescriptorsForSend + // - https://cs.android.com/android/_/android/platform/frameworks/base/+/refs/tags/android-14.0.0_r1:core/jni/android_net_LocalSocketImpl.cpp;l=194 + outputSocket.setFileDescriptorsForSend(null); + } + + public final void cleanupFds() { + if (this.pfds[0] != null) { + try { + this.pfds[0].close(); + } catch (IOException e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to close file descriptor", e); + } + } } } @@ -152,7 +191,6 @@ public static void returnData(Object context, final Intent intent, final ResultW PrintWriter writer = null; LocalSocket outputSocket = null; try { - final ParcelFileDescriptor[] pfds = { null }; outputSocket = new LocalSocket(); String outputSocketAdress = intent.getStringExtra(SOCKET_OUTPUT_EXTRA); if (outputSocketAdress == null || outputSocketAdress.isEmpty()) @@ -162,6 +200,9 @@ public static void returnData(Object context, final Intent intent, final ResultW writer = new PrintWriter(outputSocket.getOutputStream()); if (resultWriter != null) { + if(resultWriter instanceof WithAncillaryFd) { + ((WithAncillaryFd) resultWriter).setOutputSocketForFds(outputSocket); + } if (resultWriter instanceof BinaryOutput) { BinaryOutput bout = (BinaryOutput) resultWriter; bout.setOutput(outputSocket.getOutputStream()); @@ -178,19 +219,11 @@ public static void returnData(Object context, final Intent intent, final ResultW } else { resultWriter.writeResult(writer); } - if(resultWriter instanceof WithAncillaryFd) { - int fd = ((WithAncillaryFd) resultWriter).getFd(); - if (fd >= 0) { - pfds[0] = ParcelFileDescriptor.adoptFd(fd); - FileDescriptor[] fds = { pfds[0].getFileDescriptor() }; - outputSocket.setFileDescriptorsForSend(fds); - } + if (resultWriter instanceof WithAncillaryFd) { + ((WithAncillaryFd) resultWriter).cleanupFds(); } } - if(pfds[0] != null) { - pfds[0].close(); - } if (asyncResult != null && receiver.isOrderedBroadcast()) { asyncResult.setResultCode(0);