| /* | |
| * Copyright (C) 2014 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. | |
| */ | |
| #include <pty.h> | |
| #include <gtest/gtest.h> | |
| #include <pthread.h> | |
| #include <sys/ioctl.h> | |
| #include <atomic> | |
| #include <android-base/file.h> | |
| #include "utils.h" | |
| TEST(pty, openpty) { | |
| int pty, tty; | |
| char name[32]; | |
| struct winsize w = { 123, 456, 9999, 999 }; | |
| ASSERT_EQ(0, openpty(&pty, &tty, name, nullptr, &w)); | |
| ASSERT_NE(-1, pty); | |
| ASSERT_NE(-1, tty); | |
| ASSERT_NE(pty, tty); | |
| char tty_name[32]; | |
| ASSERT_EQ(0, ttyname_r(tty, tty_name, sizeof(tty_name))); | |
| ASSERT_STREQ(tty_name, name); | |
| struct winsize w_actual; | |
| ASSERT_EQ(0, ioctl(tty, TIOCGWINSZ, &w_actual)); | |
| ASSERT_EQ(w_actual.ws_row, w.ws_row); | |
| ASSERT_EQ(w_actual.ws_col, w.ws_col); | |
| ASSERT_EQ(w_actual.ws_xpixel, w.ws_xpixel); | |
| ASSERT_EQ(w_actual.ws_ypixel, w.ws_ypixel); | |
| close(pty); | |
| close(tty); | |
| } | |
| TEST(pty, forkpty) { | |
| pid_t sid = getsid(0); | |
| int pty; | |
| pid_t pid = forkpty(&pty, nullptr, nullptr, nullptr); | |
| ASSERT_NE(-1, pid); | |
| if (pid == 0) { | |
| // We're the child. | |
| ASSERT_NE(sid, getsid(0)); | |
| _exit(0); | |
| } | |
| ASSERT_EQ(sid, getsid(0)); | |
| AssertChildExited(pid, 0); | |
| close(pty); | |
| } | |
| struct PtyReader_28979140_Arg { | |
| int main_cpu_id; | |
| int fd; | |
| uint32_t data_count; | |
| bool finished; | |
| std::atomic<bool> matched; | |
| }; | |
| static void PtyReader_28979140(PtyReader_28979140_Arg* arg) { | |
| arg->finished = false; | |
| cpu_set_t cpus; | |
| ASSERT_EQ(0, sched_getaffinity(0, sizeof(cpu_set_t), &cpus)); | |
| CPU_CLR(arg->main_cpu_id, &cpus); | |
| ASSERT_EQ(0, sched_setaffinity(0, sizeof(cpu_set_t), &cpus)); | |
| uint32_t counter = 0; | |
| while (counter <= arg->data_count) { | |
| char buf[4096]; // Use big buffer to read to hit the bug more easily. | |
| size_t to_read = std::min(sizeof(buf), (arg->data_count + 1 - counter) * sizeof(uint32_t)); | |
| ASSERT_TRUE(android::base::ReadFully(arg->fd, buf, to_read)); | |
| size_t num_of_value = to_read / sizeof(uint32_t); | |
| uint32_t* p = reinterpret_cast<uint32_t*>(buf); | |
| while (num_of_value-- > 0) { | |
| if (*p++ != counter++) { | |
| arg->matched = false; | |
| } | |
| } | |
| } | |
| close(arg->fd); | |
| arg->finished = true; | |
| } | |
| TEST(pty, bug_28979140) { | |
| // This test is to test a kernel bug, which uses a lock free ring-buffer to | |
| // pass data through a raw pty, but missing necessary memory barriers. | |
| cpu_set_t cpus; | |
| ASSERT_EQ(0, sched_getaffinity(0, sizeof(cpu_set_t), &cpus)); | |
| if (CPU_COUNT(&cpus) < 2) { | |
| GTEST_SKIP() << "This bug only happens on multiprocessors"; | |
| } | |
| constexpr uint32_t TEST_DATA_COUNT = 2000000; | |
| // 1. Open raw pty. | |
| int pty; | |
| int tty; | |
| ASSERT_EQ(0, openpty(&pty, &tty, nullptr, nullptr, nullptr)); | |
| termios tattr; | |
| ASSERT_EQ(0, tcgetattr(tty, &tattr)); | |
| cfmakeraw(&tattr); | |
| ASSERT_EQ(0, tcsetattr(tty, TCSADRAIN, &tattr)); | |
| // 2. Make two threads running on different cpus: | |
| // pty thread uses first available cpu, and tty thread uses other cpus. | |
| PtyReader_28979140_Arg arg; | |
| arg.main_cpu_id = -1; | |
| for (int i = 0; i < CPU_SETSIZE; i++) { | |
| if (CPU_ISSET(i, &cpus)) { | |
| arg.main_cpu_id = i; | |
| break; | |
| } | |
| } | |
| ASSERT_GE(arg.main_cpu_id, 0); | |
| // 3. Create thread for tty reader. | |
| pthread_t thread; | |
| arg.fd = tty; | |
| arg.data_count = TEST_DATA_COUNT; | |
| arg.matched = true; | |
| ASSERT_EQ(0, pthread_create(&thread, nullptr, [](void* arg)->void* { | |
| PtyReader_28979140(static_cast<PtyReader_28979140_Arg*>(arg)); | |
| return nullptr; | |
| }, &arg)); | |
| CPU_ZERO(&cpus); | |
| CPU_SET(arg.main_cpu_id, &cpus); | |
| ASSERT_EQ(0, sched_setaffinity(0, sizeof(cpu_set_t), &cpus)); | |
| // 4. Send data to tty reader. | |
| // Send a bunch of data at a time, so it is easier to catch the bug that some data isn't seen | |
| // by the reader thread on another cpu. | |
| uint32_t counter_buf[100]; | |
| uint32_t counter = 0; | |
| while (counter <= TEST_DATA_COUNT) { | |
| for (size_t i = 0; i < sizeof(counter_buf) / sizeof(counter_buf[0]); ++i) { | |
| counter_buf[i] = counter++; | |
| } | |
| ASSERT_TRUE(android::base::WriteFully(pty, &counter_buf, sizeof(counter_buf))); | |
| ASSERT_TRUE(arg.matched) << "failed at count = " << counter; | |
| } | |
| ASSERT_EQ(0, pthread_join(thread, nullptr)); | |
| ASSERT_TRUE(arg.finished); | |
| ASSERT_TRUE(arg.matched); | |
| close(pty); | |
| } |