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:
parent
8e3a8980a8
commit
f3ebad8002
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user