| /* |
| * Copyright 2021 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 "VirtualMachine" |
| |
| #include <aidl/android/system/virtualizationservice/IVirtualMachine.h> |
| #include <android-base/scopeguard.h> |
| #include <android-base/strings.h> |
| #include <android/binder_auto_utils.h> |
| #include <android/binder_ibinder_jni.h> |
| #include <fcntl.h> |
| #include <jni.h> |
| #include <log/log.h> |
| #include <nativehelper/JNIHelp.h> |
| #include <nativehelper/JNIPlatformHelp.h> |
| #include <nativehelper/ScopedLocalRef.h> |
| #include <pty.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <termios.h> |
| #include <unistd.h> |
| |
| #include <binder_rpc_unstable.hpp> |
| #include <string> |
| #include <tuple> |
| |
| #include "common.h" |
| |
| namespace { |
| |
| void throwIOException(JNIEnv *env, const std::string &msg) { |
| jniThrowException(env, "java/io/IOException", msg.c_str()); |
| } |
| |
| } // namespace |
| |
| extern "C" JNIEXPORT jobject JNICALL |
| Java_android_system_virtualmachine_VirtualMachine_nativeConnectToVsockServer( |
| JNIEnv* env, [[maybe_unused]] jclass clazz, jobject vmBinder, jint port) { |
| using aidl::android::system::virtualizationservice::IVirtualMachine; |
| using ndk::ScopedFileDescriptor; |
| using ndk::SpAIBinder; |
| |
| auto vm = IVirtualMachine::fromBinder(SpAIBinder{AIBinder_fromJavaBinder(env, vmBinder)}); |
| |
| std::tuple args{env, vm.get(), port}; |
| using Args = decltype(args); |
| |
| auto requestFunc = [](void* param) { |
| auto [env, vm, port] = *static_cast<Args*>(param); |
| |
| ScopedFileDescriptor fd; |
| if (auto status = vm->connectVsock(port, &fd); !status.isOk()) { |
| env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"), |
| ("Failed to connect vsock: " + status.getDescription()).c_str()); |
| return -1; |
| } |
| |
| // take ownership |
| int ret = fd.get(); |
| *fd.getR() = -1; |
| |
| return ret; |
| }; |
| |
| RpcSessionHandle session; |
| // We need a thread pool to be able to support linkToDeath, or callbacks |
| // (b/268335700). These threads are currently created eagerly, so we don't |
| // want too many. The number 1 is chosen after some discussion, and to match |
| // the server-side default (mMaxThreads on RpcServer). |
| ARpcSession_setMaxIncomingThreads(session.get(), 1); |
| auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &args); |
| return AIBinder_toJavaBinder(env, client); |
| } |
| |
| extern "C" JNIEXPORT void JNICALL |
| Java_android_system_virtualmachine_VirtualMachine_nativeOpenPtyRawNonblock( |
| JNIEnv *env, [[maybe_unused]] jclass clazz, jobject resultCallback) { |
| int pm, ps; |
| // man openpty says: "Nobody knows how much space should be reserved for name." |
| // but on modern Linux the format of the pts name is always `/dev/pts/[0-9]+` |
| // Realistically speaking, a buffer of 32 bytes leaves us with 22 digits for the pts number, |
| // which should be more than enough. |
| // NOTE: bionic implements openpty() with internal name buffer of size 32, musl 20. |
| char name[32]; |
| if (openpty(&pm, &ps, name, nullptr, nullptr)) { |
| throwIOException(env, "openpty(): " + android::base::ErrnoNumberAsString(errno)); |
| return; |
| } |
| fcntl(pm, F_SETFD, FD_CLOEXEC); |
| fcntl(ps, F_SETFD, FD_CLOEXEC); |
| name[sizeof(name) - 1] = '\0'; |
| // Set world RW so adb shell can talk to the pts. |
| chmod(name, 0666); |
| |
| if (int flags = fcntl(pm, F_GETFL, 0); flags < 0) { |
| throwIOException(env, "fcntl(F_GETFL): " + android::base::ErrnoNumberAsString(errno)); |
| return; |
| } else if (fcntl(pm, F_SETFL, flags | O_NONBLOCK) < 0) { |
| throwIOException(env, "fcntl(F_SETFL): " + android::base::ErrnoNumberAsString(errno)); |
| return; |
| } |
| |
| android::base::ScopeGuard cleanup_handler([=] { |
| close(ps); |
| close(pm); |
| }); |
| |
| struct termios tio; |
| if (tcgetattr(pm, &tio)) { |
| throwIOException(env, "tcgetattr(): " + android::base::ErrnoNumberAsString(errno)); |
| return; |
| } |
| cfmakeraw(&tio); |
| if (tcsetattr(pm, TCSANOW, &tio)) { |
| throwIOException(env, "tcsetattr(): " + android::base::ErrnoNumberAsString(errno)); |
| return; |
| } |
| |
| jobject mfd = jniCreateFileDescriptor(env, pm); |
| if (mfd == nullptr) { |
| return; |
| } |
| jobject sfd = jniCreateFileDescriptor(env, ps); |
| if (sfd == nullptr) { |
| return; |
| } |
| size_t len = strlen(name); |
| ScopedLocalRef<jbyteArray> ptsName(env, env->NewByteArray(len)); |
| if (ptsName.get() != nullptr) { |
| env->SetByteArrayRegion(ptsName.get(), 0, len, (jbyte *)name); |
| } |
| ScopedLocalRef<jclass> callback_class(env, env->GetObjectClass(resultCallback)); |
| jmethodID mid = env->GetMethodID(callback_class.get(), "apply", |
| "(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;[B)V"); |
| if (mid == nullptr) { |
| return; |
| } |
| |
| env->CallVoidMethod(resultCallback, mid, mfd, sfd, ptsName.get()); |
| // FD ownership is transferred to the callback, reset the auto-close hander. |
| cleanup_handler.Disable(); |
| } |