| /* |
| * 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; |
| } |
| |
| }; |