/*
|
* Copyright (C) 2006 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
#define LOG_TAG "LocalSocketImpl"
|
|
#include <nativehelper/JNIHelp.h>
|
#include "jni.h"
|
#include "utils/Log.h"
|
#include "utils/misc.h"
|
|
#include <stdio.h>
|
#include <string.h>
|
#include <sys/types.h>
|
#include <sys/socket.h>
|
#include <sys/un.h>
|
#include <arpa/inet.h>
|
#include <netinet/in.h>
|
#include <stdlib.h>
|
#include <errno.h>
|
#include <unistd.h>
|
#include <sys/ioctl.h>
|
|
#include <android-base/cmsg.h>
|
#include <android-base/macros.h>
|
#include <cutils/sockets.h>
|
#include <netinet/tcp.h>
|
#include <nativehelper/ScopedUtfChars.h>
|
|
using android::base::ReceiveFileDescriptorVector;
|
using android::base::SendFileDescriptorVector;
|
|
namespace android {
|
|
static jfieldID field_inboundFileDescriptors;
|
static jfieldID field_outboundFileDescriptors;
|
static jclass class_Credentials;
|
static jclass class_FileDescriptor;
|
static jmethodID method_CredentialsInit;
|
|
/* private native void connectLocal(FileDescriptor fd,
|
* String name, int namespace) throws IOException
|
*/
|
static void
|
socket_connect_local(JNIEnv *env, jobject object,
|
jobject fileDescriptor, jstring name, jint namespaceId)
|
{
|
int ret;
|
int fd;
|
|
if (name == NULL) {
|
jniThrowNullPointerException(env, NULL);
|
return;
|
}
|
|
fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
|
|
if (env->ExceptionCheck()) {
|
return;
|
}
|
|
ScopedUtfChars nameUtf8(env, name);
|
|
ret = socket_local_client_connect(
|
fd,
|
nameUtf8.c_str(),
|
namespaceId,
|
SOCK_STREAM);
|
|
if (ret < 0) {
|
jniThrowIOException(env, errno);
|
return;
|
}
|
}
|
|
#define DEFAULT_BACKLOG 4
|
|
/* private native void bindLocal(FileDescriptor fd, String name, namespace)
|
* throws IOException;
|
*/
|
|
static void
|
socket_bind_local (JNIEnv *env, jobject object, jobject fileDescriptor,
|
jstring name, jint namespaceId)
|
{
|
int ret;
|
int fd;
|
|
if (name == NULL) {
|
jniThrowNullPointerException(env, NULL);
|
return;
|
}
|
|
fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
|
|
if (env->ExceptionCheck()) {
|
return;
|
}
|
|
ScopedUtfChars nameUtf8(env, name);
|
|
ret = socket_local_server_bind(fd, nameUtf8.c_str(), namespaceId);
|
|
if (ret < 0) {
|
jniThrowIOException(env, errno);
|
return;
|
}
|
}
|
|
/**
|
* Reads data from a socket into buf, processing any ancillary data
|
* and adding it to thisJ.
|
*
|
* Returns the length of normal data read, or -1 if an exception has
|
* been thrown in this function.
|
*/
|
static ssize_t socket_read_all(JNIEnv *env, jobject thisJ, int fd,
|
void *buffer, size_t len)
|
{
|
ssize_t ret;
|
std::vector<android::base::unique_fd> received_fds;
|
|
ret = ReceiveFileDescriptorVector(fd, buffer, len, 64, &received_fds);
|
|
if (ret < 0) {
|
if (errno == EPIPE) {
|
// Treat this as an end of stream
|
return 0;
|
}
|
|
jniThrowIOException(env, errno);
|
return -1;
|
}
|
|
if (received_fds.size() > 0) {
|
jobjectArray fdArray = env->NewObjectArray(received_fds.size(), class_FileDescriptor, NULL);
|
|
if (fdArray == NULL) {
|
// NewObjectArray has thrown.
|
return -1;
|
}
|
|
for (size_t i = 0; i < received_fds.size(); i++) {
|
jobject fdObject = jniCreateFileDescriptor(env, received_fds[i].get());
|
|
if (env->ExceptionCheck()) {
|
return -1;
|
}
|
|
env->SetObjectArrayElement(fdArray, i, fdObject);
|
|
if (env->ExceptionCheck()) {
|
return -1;
|
}
|
}
|
|
for (auto &fd : received_fds) {
|
// The fds are stored in java.io.FileDescriptors now.
|
static_cast<void>(fd.release());
|
}
|
|
env->SetObjectField(thisJ, field_inboundFileDescriptors, fdArray);
|
}
|
|
return ret;
|
}
|
|
/**
|
* Writes all the data in the specified buffer to the specified socket.
|
*
|
* Returns 0 on success or -1 if an exception was thrown.
|
*/
|
static int socket_write_all(JNIEnv *env, jobject object, int fd,
|
void *buf, size_t len)
|
{
|
struct msghdr msg;
|
unsigned char *buffer = (unsigned char *)buf;
|
memset(&msg, 0, sizeof(msg));
|
|
jobjectArray outboundFds
|
= (jobjectArray)env->GetObjectField(
|
object, field_outboundFileDescriptors);
|
|
if (env->ExceptionCheck()) {
|
return -1;
|
}
|
|
int countFds = outboundFds == NULL ? 0 : env->GetArrayLength(outboundFds);
|
std::vector<int> fds;
|
|
// Add any pending outbound file descriptors to the message
|
if (outboundFds != NULL) {
|
if (env->ExceptionCheck()) {
|
return -1;
|
}
|
|
for (int i = 0; i < countFds; i++) {
|
jobject fdObject = env->GetObjectArrayElement(outboundFds, i);
|
if (env->ExceptionCheck()) {
|
return -1;
|
}
|
|
fds.push_back(jniGetFDFromFileDescriptor(env, fdObject));
|
if (env->ExceptionCheck()) {
|
return -1;
|
}
|
}
|
}
|
|
ssize_t rc = SendFileDescriptorVector(fd, buffer, len, fds);
|
|
while (rc != len) {
|
if (rc == -1) {
|
jniThrowIOException(env, errno);
|
return -1;
|
}
|
|
buffer += rc;
|
len -= rc;
|
|
rc = send(fd, buffer, len, MSG_NOSIGNAL);
|
}
|
|
return 0;
|
}
|
|
static jint socket_read (JNIEnv *env, jobject object, jobject fileDescriptor)
|
{
|
int fd;
|
int err;
|
|
if (fileDescriptor == NULL) {
|
jniThrowNullPointerException(env, NULL);
|
return (jint)-1;
|
}
|
|
fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
|
|
if (env->ExceptionCheck()) {
|
return (jint)0;
|
}
|
|
unsigned char buf;
|
|
err = socket_read_all(env, object, fd, &buf, 1);
|
|
if (err < 0) {
|
jniThrowIOException(env, errno);
|
return (jint)0;
|
}
|
|
if (err == 0) {
|
// end of file
|
return (jint)-1;
|
}
|
|
return (jint)buf;
|
}
|
|
static jint socket_readba (JNIEnv *env, jobject object,
|
jbyteArray buffer, jint off, jint len, jobject fileDescriptor)
|
{
|
int fd;
|
jbyte* byteBuffer;
|
int ret;
|
|
if (fileDescriptor == NULL || buffer == NULL) {
|
jniThrowNullPointerException(env, NULL);
|
return (jint)-1;
|
}
|
|
if (off < 0 || len < 0 || (off + len) > env->GetArrayLength(buffer)) {
|
jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
|
return (jint)-1;
|
}
|
|
if (len == 0) {
|
// because socket_read_all returns 0 on EOF
|
return 0;
|
}
|
|
fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
|
|
if (env->ExceptionCheck()) {
|
return (jint)-1;
|
}
|
|
byteBuffer = env->GetByteArrayElements(buffer, NULL);
|
|
if (NULL == byteBuffer) {
|
// an exception will have been thrown
|
return (jint)-1;
|
}
|
|
ret = socket_read_all(env, object,
|
fd, byteBuffer + off, len);
|
|
// A return of -1 above means an exception is pending
|
|
env->ReleaseByteArrayElements(buffer, byteBuffer, 0);
|
|
return (jint) ((ret == 0) ? -1 : ret);
|
}
|
|
static void socket_write (JNIEnv *env, jobject object,
|
jint b, jobject fileDescriptor)
|
{
|
int fd;
|
int err;
|
|
if (fileDescriptor == NULL) {
|
jniThrowNullPointerException(env, NULL);
|
return;
|
}
|
|
fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
|
|
if (env->ExceptionCheck()) {
|
return;
|
}
|
|
err = socket_write_all(env, object, fd, &b, 1);
|
UNUSED(err);
|
// A return of -1 above means an exception is pending
|
}
|
|
static void socket_writeba (JNIEnv *env, jobject object,
|
jbyteArray buffer, jint off, jint len, jobject fileDescriptor)
|
{
|
int fd;
|
int err;
|
jbyte* byteBuffer;
|
|
if (fileDescriptor == NULL || buffer == NULL) {
|
jniThrowNullPointerException(env, NULL);
|
return;
|
}
|
|
if (off < 0 || len < 0 || (off + len) > env->GetArrayLength(buffer)) {
|
jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
|
return;
|
}
|
|
fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
|
|
if (env->ExceptionCheck()) {
|
return;
|
}
|
|
byteBuffer = env->GetByteArrayElements(buffer,NULL);
|
|
if (NULL == byteBuffer) {
|
// an exception will have been thrown
|
return;
|
}
|
|
err = socket_write_all(env, object, fd,
|
byteBuffer + off, len);
|
UNUSED(err);
|
// A return of -1 above means an exception is pending
|
|
env->ReleaseByteArrayElements(buffer, byteBuffer, JNI_ABORT);
|
}
|
|
static jobject socket_get_peer_credentials(JNIEnv *env,
|
jobject object, jobject fileDescriptor)
|
{
|
int err;
|
int fd;
|
|
if (fileDescriptor == NULL) {
|
jniThrowNullPointerException(env, NULL);
|
return NULL;
|
}
|
|
fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
|
|
if (env->ExceptionCheck()) {
|
return NULL;
|
}
|
|
struct ucred creds;
|
|
memset(&creds, 0, sizeof(creds));
|
socklen_t szCreds = sizeof(creds);
|
|
err = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);
|
|
if (err < 0) {
|
jniThrowIOException(env, errno);
|
return NULL;
|
}
|
|
if (szCreds == 0) {
|
return NULL;
|
}
|
|
return env->NewObject(class_Credentials, method_CredentialsInit,
|
creds.pid, creds.uid, creds.gid);
|
}
|
|
/*
|
* JNI registration.
|
*/
|
static const JNINativeMethod gMethods[] = {
|
/* name, signature, funcPtr */
|
{"connectLocal", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
|
(void*)socket_connect_local},
|
{"bindLocal", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V", (void*)socket_bind_local},
|
{"read_native", "(Ljava/io/FileDescriptor;)I", (void*) socket_read},
|
{"readba_native", "([BIILjava/io/FileDescriptor;)I", (void*) socket_readba},
|
{"writeba_native", "([BIILjava/io/FileDescriptor;)V", (void*) socket_writeba},
|
{"write_native", "(ILjava/io/FileDescriptor;)V", (void*) socket_write},
|
{"getPeerCredentials_native",
|
"(Ljava/io/FileDescriptor;)Landroid/net/Credentials;",
|
(void*) socket_get_peer_credentials}
|
};
|
|
int register_android_net_LocalSocketImpl(JNIEnv *env)
|
{
|
jclass clazz;
|
|
clazz = env->FindClass("android/net/LocalSocketImpl");
|
|
if (clazz == NULL) {
|
goto error;
|
}
|
|
field_inboundFileDescriptors = env->GetFieldID(clazz,
|
"inboundFileDescriptors", "[Ljava/io/FileDescriptor;");
|
|
if (field_inboundFileDescriptors == NULL) {
|
goto error;
|
}
|
|
field_outboundFileDescriptors = env->GetFieldID(clazz,
|
"outboundFileDescriptors", "[Ljava/io/FileDescriptor;");
|
|
if (field_outboundFileDescriptors == NULL) {
|
goto error;
|
}
|
|
class_Credentials = env->FindClass("android/net/Credentials");
|
|
if (class_Credentials == NULL) {
|
goto error;
|
}
|
|
class_Credentials = (jclass)env->NewGlobalRef(class_Credentials);
|
|
class_FileDescriptor = env->FindClass("java/io/FileDescriptor");
|
|
if (class_FileDescriptor == NULL) {
|
goto error;
|
}
|
|
class_FileDescriptor = (jclass)env->NewGlobalRef(class_FileDescriptor);
|
|
method_CredentialsInit
|
= env->GetMethodID(class_Credentials, "<init>", "(III)V");
|
|
if (method_CredentialsInit == NULL) {
|
goto error;
|
}
|
|
return jniRegisterNativeMethods(env,
|
"android/net/LocalSocketImpl", gMethods, NELEM(gMethods));
|
|
error:
|
ALOGE("Error registering android.net.LocalSocketImpl");
|
return -1;
|
}
|
|
};
|