/* * 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 #include "jni.h" #include "utils/Log.h" #include "utils/misc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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(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 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, "", "(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; } };