1
0
mirror of https://github.com/termux/termux-app synced 2024-06-17 14:47:08 +00:00

Added: Methods sendFD and readFD with sendFDNative and readFDNative to send and receive file descriptors over UNIX sockets as ancillary data.

This commit is contained in:
tareksander 2022-04-25 16:27:11 +02:00
parent 8e3a8980a8
commit f3ebad8002
2 changed files with 175 additions and 0 deletions

View File

@ -5,6 +5,7 @@
#include <sstream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <android/log.h>
@ -601,3 +602,123 @@ Java_com_termux_shared_net_socket_local_LocalSocketManager_getPeerCredNative(JNI
// Return success since PeerCred was filled successfully
return getJniResult(env, logTitle);
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_termux_shared_net_socket_local_LocalSocketManager_readFDNative(JNIEnv *env, jclass clazz,
jstring logTitle,
jint fd, jbyteArray dataArray,
jintArray readFDArray) {
if (fd < 0) {
return getJniResult(env, logTitle, -1, "readFDNative(): Invalid fd \"" + to_string(fd) + "\" passed");
}
jint* readFD = env->GetIntArrayElements(readFDArray, nullptr);
if (checkJniException(env)) return NULL;
if (readFD == NULL) {
return getJniResult(env, logTitle, -1, "readFDNative(): readFD passed is null");
}
if (env->GetArrayLength(readFDArray) < 1) {
return getJniResult(env, logTitle, -1, "readFDNative(): readFD length is < 1");
}
if (checkJniException(env)) return NULL;
readFD[0] = -1; // set to an invalid value to signify if an fd was received or not
jbyte* data = env->GetByteArrayElements(dataArray, nullptr);
if (checkJniException(env)) return NULL;
if (data == nullptr) {
return getJniResult(env, logTitle, -1, "readFDNative(): data passed is null");
}
if (env->GetArrayLength(dataArray) < 1) {
return getJniResult(env, logTitle, -1, "readFDNative(): data length is < 1");
}
if (checkJniException(env)) return NULL;
// enough size for exactly one control message with one fd, so the excess fds are automatically closed
constexpr int CONTROLLEN = CMSG_SPACE(sizeof(int));
union {
cmsghdr _; // for alignment
char controlBuffer[CONTROLLEN];
} controlBufferUnion;
memset(&controlBufferUnion, 0, CONTROLLEN); // clear the buffer to be sure
iovec buffer{data, 1};
msghdr receiveHeader{NULL, 0, &buffer, 1, controlBufferUnion.controlBuffer, sizeof(controlBufferUnion.controlBuffer), 0};
int ret = recvmsg(fd, &receiveHeader, 0);
if (ret == -1) {
int errnoBackup = errno;
env->ReleaseByteArrayElements(dataArray, data, 0);
if (checkJniException(env)) return NULL;
env->ReleaseIntArrayElements(readFDArray, readFD, 0);
if (checkJniException(env)) return NULL;
return getJniResult(env, logTitle, -1, errnoBackup, "readNative(): Failed to read on fd " + to_string(fd));
}
for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&receiveHeader); cmsg != NULL; cmsg = CMSG_NXTHDR(&receiveHeader, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
int recfd;
memcpy(&recfd, CMSG_DATA(cmsg), sizeof(recfd));
readFD[0] = recfd;
break;
}
}
env->ReleaseByteArrayElements(dataArray, data, 0);
if (checkJniException(env)) return NULL;
env->ReleaseIntArrayElements(readFDArray, readFD, 0);
if (checkJniException(env)) return NULL;
// Return success and bytes read in JniResult.intData field
return getJniResult(env, logTitle, ret);
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_termux_shared_net_socket_local_LocalSocketManager_sendFDNative(JNIEnv *env, jclass clazz,
jstring logTitle,
jint fd, jbyte data,
jint sendFD) {
if (fd < 0) {
return getJniResult(env, logTitle, -1, "sendFDNative(): Invalid fd \"" + to_string(fd) + "\" passed");
}
if (sendFD < 0) {
return getJniResult(env, logTitle, -1, "sendFDNative(): Invalid sendFD \"" + to_string(fd) + "\" passed");
}
const int sendFDInt = sendFD; // in case a platform has an int that isn't the same size as jint
// enough size for exactly one control message with one fd
constexpr int CONTROLLEN = CMSG_SPACE(sizeof(int));
union {
cmsghdr _; // for alignment
char controlBuffer[CONTROLLEN];
} controlBufferUnion;
memset(&controlBufferUnion, 0, CONTROLLEN); // clear the buffer to be sure
iovec buffer{&data, 1};
msghdr sendHeader{nullptr, 0, &buffer, 1, controlBufferUnion.controlBuffer, sizeof(controlBufferUnion.controlBuffer), 0};
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&sendHeader);
if (cmsg == NULL) {
return getJniResult(env, logTitle, -1, "sendFDNative(): CMSG_FIRSTHDR returned NULL");
}
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(sendFDInt));
memcpy(CMSG_DATA(cmsg), &sendFDInt, sizeof(sendFDInt));
int ret = sendmsg(fd, &sendHeader, MSG_NOSIGNAL);
if (ret == -1) {
int errnoBackup = errno;
return getJniResult(env, logTitle, -1, errnoBackup, "sendFDNative(): Failed to send on fd " + to_string(fd));
}
// Return success
return getJniResult(env, logTitle);
}

View File

@ -198,6 +198,34 @@ public class LocalSocketManager {
return new JniResult(message, t);
}
}
/**
* Attempts to read one byte from file descriptor {@code fd} into the data buffer and
* a received file descriptor into the file descriptor buffer {@code readFD}.
* On success, the number of bytes read is returned (zero indicates end of file).
* It is not an error if bytes read is smaller than the number of bytes requested; this may happen
* for example because fewer bytes are actually available right now (maybe because we were close
* to end-of-file, or because we are reading from a pipe), or because read() was interrupted by
* a signal. On error, the {@link JniResult#errno} and {@link JniResult#errmsg} will be set.
* If no file descriptor was attached to the data byte, {@code readFD[0]} is -1.
*
* @param serverTitle The server title used for logging and errors.
* @param fd The socket fd.
* @param data The data buffer to read bytes into. Has to have a length of at least one, but only the first byte is used.
* @param readFD The file descriptor buffer to read file descriptors into. Has to have a length of at least one, but only the first int is used.
* @return Returns the {@link JniResult}. If reading was successful, then {@link JniResult#retval}
* will be 0 and {@link JniResult#intData} will contain the bytes read.
*/
@Nullable
public static JniResult readFD(@NonNull String serverTitle, int fd, @NonNull byte[] data, @NonNull int[] readFD) {
try {
return readFDNative(serverTitle, fd, data, readFD);
} catch (Throwable t) {
String message = "Exception in readFDNative()";
Logger.logStackTraceWithMessage(LOG_TAG, message, t);
return new JniResult(message, t);
}
}
/**
* Attempts to send data buffer to the file descriptor. On error, the {@link JniResult#errno} and
@ -222,6 +250,28 @@ public class LocalSocketManager {
return new JniResult(message, t);
}
}
/**
* Attempts to send a byte and the passed file descriptor {@code sendFD} to the file descriptor {@code fd}. On error, the {@link JniResult#errno} and
* {@link JniResult#errmsg} will be set.
*
* @param serverTitle The server title used for logging and errors.
* @param fd The socket fd.
* @param data The byte to send.
* @param sendFD The file descriptor to send.
* @return Returns the {@link JniResult}. If sending was successful, then {@link JniResult#retval}
* will be 0.
*/
@Nullable
public static JniResult sendFD(@NonNull String serverTitle, int fd, byte data, int sendFD) {
try {
return sendFDNative(serverTitle, fd, data, sendFD);
} catch (Throwable t) {
String message = "Exception in sendFDNative()";
Logger.logStackTraceWithMessage(LOG_TAG, message, t);
return new JniResult(message, t);
}
}
/**
* Gets the number of bytes available to read on the socket.
@ -436,8 +486,12 @@ public class LocalSocketManager {
@Nullable private static native JniResult acceptNative(@NonNull String serverTitle, int fd);
@Nullable private static native JniResult readNative(@NonNull String serverTitle, int fd, @NonNull byte[] data, long deadline);
@Nullable private static native JniResult readFDNative(@NonNull String serverTitle, int socketFD, @NonNull byte[] data, @NonNull int[] readFD);
@Nullable private static native JniResult sendNative(@NonNull String serverTitle, int fd, @NonNull byte[] data, long deadline);
@Nullable private static native JniResult sendFDNative(@NonNull String serverTitle, int socketFD, byte data, int sendFD);
@Nullable private static native JniResult availableNative(@NonNull String serverTitle, int fd);