LCOV - code coverage report
Current view: top level - src/tty - forkpty.cc (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 103 109 94.5 %
Date: 2022-06-15 20:16:21 Functions: 17 21 81.0 %
Branches: 34 58 58.6 %

           Branch data     Line data    Source code
       1                 :            : #ifdef RUN_TESTS
       2                 :            : # include <gtest/gtest.h>
       3                 :            : extern "C" {
       4                 :            : # include <gcov.h>
       5                 :            : }
       6                 :            : # include <utmp.h>
       7                 :            : #endif
       8                 :            : /** @file tty/forkpty.cc
       9                 :            :  * @brief Physical frontend to the underlying process (shell).
      10                 :            :  */
      11                 :            : 
      12                 :            : #include <cstdlib>
      13                 :            : #include <cerrno>
      14                 :            : 
      15                 :            : #include <unistd.h>
      16                 :            : #include <sys/wait.h>
      17                 :            : #include <sys/fcntl.h>
      18                 :            : #include <sys/ioctl.h>
      19                 :            : 
      20                 :            : #ifdef __APPLE__
      21                 :            : # include <util.h>
      22                 :            : #else
      23                 :            : # include <pty.h>
      24                 :            : #endif
      25                 :            : 
      26                 :            : #include "forkpty.hh"
      27                 :            : 
      28                 :          4 : bool ForkPTY::Active() const
      29                 :            : {
      30                 :          4 :     return fd >= 0;
      31                 :            : }
      32                 :            : 
      33                 :          3 : int ForkPTY::getfd() const
      34                 :            : {
      35                 :          3 :     return fd;
      36                 :            : }
      37                 :            : 
      38                 :          4 : ForkPTY::~ForkPTY()
      39                 :            : {
      40         [ -  + ]:          4 :     if(Active())
      41                 :            :     {
      42                 :          0 :         Close();
      43                 :            :     }
      44                 :          4 : }
      45                 :            : 
      46                 :          4 : void ForkPTY::Open(std::size_t width, std::size_t height)
      47                 :            : {
      48                 :          4 :     struct winsize ws = {};
      49                 :          4 :     ws.ws_col = width;
      50                 :          4 :     ws.ws_row = height;
      51                 :            :   #if 0
      52                 :            :     pid = -1;
      53                 :            :   #elif defined RUN_TESTS_no
      54                 :            :     // gcov duplicates every counter on fork. Because of that, implement forkpty
      55                 :            :     // manually, and do NOT call fork() (which gets aliased to __gcov_fork)
      56                 :            :     int fd2 = -1;
      57                 :            :     openpty(&fd, &fd2, nullptr, nullptr, &ws);
      58                 :            :     pid = syscall(__NR_fork);
      59                 :            :     if(pid)
      60                 :            :         close(fd2);
      61                 :            :     else
      62                 :            :     {
      63                 :            :         __gcov_reset();
      64                 :            :         close(fd);
      65                 :            :         login_tty(fd2);
      66                 :            :     }
      67                 :            :   #else
      68                 :          4 :     pid = forkpty(&fd, nullptr, nullptr, &ws);
      69                 :            :   #endif
      70         [ -  + ]:          4 :     if(!pid)
      71                 :            :     {
      72                 :          0 :         static char termstr[] = "TERM=xterm";
      73                 :          0 :         putenv(termstr);
      74                 :            :   #ifdef RUN_TESTS_no
      75                 :            :         __gcov_dump();
      76                 :            :   #endif
      77                 :          0 :         execl(std::getenv("SHELL"), std::getenv("SHELL"), "-l", "-i", nullptr); // TODO: check return values
      78                 :            :         // Note: getenv() is in C++ standard, but putenv() is not.
      79                 :          0 :         _exit(0);
      80                 :            :     }
      81                 :            : 
      82                 :            :     // Change the virtual terminal handle (file descriptor)
      83                 :            :     // into non-blocking mode.
      84                 :          4 :     fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
      85                 :          4 : }
      86                 :            : 
      87                 :          4 : void ForkPTY::Close()
      88                 :            : {
      89                 :          4 :     Kill(SIGTERM);
      90                 :          4 :     close(fd);
      91                 :          4 :     waitpid(pid, nullptr, 0);
      92                 :          4 :     fd = -1;
      93                 :          4 : }
      94                 :            : 
      95                 :          4 : int ForkPTY::Send(std::string_view buffer)
      96                 :            : {
      97                 :          4 :     return write(fd, buffer.data(), buffer.size());
      98                 :            : }
      99                 :            : 
     100                 :        115 : std::pair<std::string,int> ForkPTY::Recv()
     101                 :            : {
     102                 :            :     //char buffer[16]; usleep(400);
     103                 :        115 :     char buffer[512];
     104         [ +  - ]:        115 :     std::pair<std::string,int> result;
     105         [ +  - ]:        115 :     result.second = read(fd, buffer, sizeof(buffer));
     106         [ +  + ]:        115 :     if(result.second > 0)
     107         [ +  - ]:          7 :         result.first.assign(buffer, buffer+result.second);
     108                 :        115 :     return result;
     109                 :          0 : }
     110                 :            : 
     111                 :          5 : void ForkPTY::Kill(int signal)
     112                 :            : {
     113         [ +  - ]:          5 :     if(pid >= 0) kill(pid, signal);
     114                 :          5 : }
     115                 :            : 
     116                 :          1 : void ForkPTY::Resize(unsigned xsize, unsigned ysize)
     117                 :            : {
     118                 :          1 :     struct winsize ws = {};
     119                 :          1 :     ws.ws_col = xsize;
     120                 :          1 :     ws.ws_row = ysize;
     121                 :          1 :     ioctl(fd, TIOCSWINSZ, &ws);
     122                 :          1 : }
     123                 :            : 
     124                 :            : #ifdef RUN_TESTS
     125                 :            : // For some strange reason, every call here seems to be treated as a branch by gcov.
     126                 :            : // This seriously skews the statistics.
     127                 :            : 
     128                 :            : #include <chrono>
     129                 :          3 : TEST(forkpty, opening_a_shell_works) // Test that opening a shell works
     130                 :            : {
     131                 :          1 :     ForkPTY pty(80,25);
     132                 :          1 :     pty.Active();
     133                 :          1 :     EXPECT_NE(pty.getfd(), -1);
     134         [ +  - ]:          1 :     pty.Close();
     135                 :          1 :     pty.Active();
     136                 :          1 : }
     137                 :          3 : TEST(forkpty, killing_a_shell_works) // Test that killing a shell works
     138                 :            : {
     139                 :          1 :     ForkPTY pty(80,25);
     140                 :            :     // Disable non-blocking mode
     141   [ +  -  +  - ]:          1 :     fcntl(pty.getfd(), F_SETFL, fcntl(pty.getfd(), F_GETFL) & ~O_NONBLOCK);
     142                 :            : 
     143                 :          2 :     EXPECT_NE(pty.Recv().second, -1);
     144                 :          1 :     pty.Kill(SIGHUP);
     145                 :          1 :     pty.Active();
     146                 :          2 :     EXPECT_EQ(pty.Recv().second, -1);
     147         [ +  - ]:          1 :     pty.Close();
     148                 :          1 :     pty.Active();
     149                 :          1 : }
     150                 :            : // Test that running commands in shell works
     151                 :            : // This test depends on the command `stat` existing.
     152                 :          3 : TEST(forkpty, running_a_command_in_shell_works)
     153                 :            : {
     154                 :          1 :     ForkPTY pty(80,25);
     155                 :         58 :     auto t = []{return std::chrono::system_clock::now(); };
     156                 :            :     // Wait until we receive something
     157                 :          1 :     auto start = t();
     158         [ +  - ]:          1 :     pty.Send("\r");
     159                 :          1 :     std::string in;
     160         [ +  + ]:          3 :     while(in.empty())
     161                 :            :     {
     162         [ +  - ]:          2 :         auto k = pty.Recv();
     163         [ +  - ]:          4 :         in += k.first;
     164                 :          2 :         ASSERT_LE(std::chrono::duration<double>(t()-start).count(), 2);
     165                 :          2 :         usleep(10000);
     166                 :          2 :     }
     167                 :            :     // Send a command
     168         [ +  - ]:          1 :     pty.Send("stat /tmp\r");
     169                 :            :     // Wait until we receive something
     170                 :         55 :     for(;;)
     171                 :            :     {
     172         [ +  - ]:         55 :         auto k = pty.Recv();
     173         [ +  - ]:        110 :         in += k.first;
     174                 :         55 :         ASSERT_LE(std::chrono::duration<double>(t()-start).count(), 2);
     175                 :         55 :         usleep(10000);
     176         [ +  + ]:         55 :         if(in.find("Device:") != in.npos) break;
     177                 :         54 :     }
     178         [ +  - ]:          1 :     pty.Close();
     179                 :          1 : }
     180                 :            : // Test that the shell reacts to resizes.
     181                 :          3 : TEST(forkpty, resizing_works)
     182                 :            : {
     183                 :          1 :     ForkPTY pty(80,25);
     184                 :         57 :     auto t = []{return std::chrono::system_clock::now(); };
     185                 :            :     // Wait until we receive something
     186                 :          1 :     auto start = t();
     187         [ +  - ]:          1 :     pty.Send("\r");
     188                 :          1 :     pty.Active();
     189                 :          1 :     pty.Resize(40,30);
     190                 :          1 :     std::string in;
     191         [ +  + ]:          2 :     while(in.empty())
     192                 :            :     {
     193         [ +  - ]:          1 :         auto k = pty.Recv();
     194         [ +  - ]:          2 :         in += k.first;
     195                 :          1 :         ASSERT_LE(std::chrono::duration<double>(t()-start).count(), 2);
     196                 :          1 :         usleep(10000);
     197                 :          1 :     }
     198                 :            :     // Send a command
     199         [ +  - ]:          1 :     pty.Send("echo $LINES $COLUMNS\r");
     200                 :            :     // Wait until we receive something
     201                 :         55 :     for(;;)
     202                 :            :     {
     203         [ +  - ]:         55 :         auto k = pty.Recv();
     204         [ +  - ]:        110 :         in += k.first;
     205                 :         55 :         ASSERT_LE(std::chrono::duration<double>(t()-start).count(), 2);
     206                 :         55 :         usleep(10000);
     207         [ +  + ]:         55 :         if(in.find("30 40") != in.npos) break;
     208                 :         54 :     }
     209         [ +  - ]:          1 :     pty.Close();
     210                 :          1 :     pty.Active();
     211                 :          1 : }
     212                 :            : #endif

Generated by: LCOV version 1.16