1//===-- PseudoTerminal.cpp --------------------------------------*- C++ -*-===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10//  Created by Greg Clayton on 1/8/08.
11//
12//===----------------------------------------------------------------------===//
13
14#include "PseudoTerminal.h"
15#include <stdlib.h>
16#include <sys/ioctl.h>
17#include <unistd.h>
18
19//----------------------------------------------------------------------
20// PseudoTerminal constructor
21//----------------------------------------------------------------------
22PseudoTerminal::PseudoTerminal() :
23    m_master_fd(invalid_fd),
24    m_slave_fd(invalid_fd)
25{
26}
27
28//----------------------------------------------------------------------
29// Destructor
30// The master and slave file descriptors will get closed if they are
31// valid. Call the ReleaseMasterFD()/ReleaseSlaveFD() member functions
32// to release any file descriptors that are needed beyond the lifespan
33// of this object.
34//----------------------------------------------------------------------
35PseudoTerminal::~PseudoTerminal()
36{
37    CloseMaster();
38    CloseSlave();
39}
40
41//----------------------------------------------------------------------
42// Close the master file descriptor if it is valid.
43//----------------------------------------------------------------------
44void
45PseudoTerminal::CloseMaster()
46{
47    if (m_master_fd > 0)
48    {
49        ::close (m_master_fd);
50        m_master_fd = invalid_fd;
51    }
52}
53
54//----------------------------------------------------------------------
55// Close the slave file descriptor if it is valid.
56//----------------------------------------------------------------------
57void
58PseudoTerminal::CloseSlave()
59{
60    if (m_slave_fd > 0)
61    {
62        ::close (m_slave_fd);
63        m_slave_fd = invalid_fd;
64    }
65}
66
67//----------------------------------------------------------------------
68// Open the first available pseudo terminal with OFLAG as the
69// permissions. The file descriptor is store in the m_master_fd member
70// variable and can be accessed via the MasterFD() or ReleaseMasterFD()
71// accessors.
72//
73// Suggested value for oflag is O_RDWR|O_NOCTTY
74//
75// RETURNS:
76//  Zero when successful, non-zero indicating an error occurred.
77//----------------------------------------------------------------------
78PseudoTerminal::Error
79PseudoTerminal::OpenFirstAvailableMaster(int oflag)
80{
81    // Open the master side of a pseudo terminal
82    m_master_fd = ::posix_openpt (oflag);
83    if (m_master_fd < 0)
84    {
85        return err_posix_openpt_failed;
86    }
87
88    // Grant access to the slave pseudo terminal
89    if (::grantpt (m_master_fd) < 0)
90    {
91        CloseMaster();
92        return err_grantpt_failed;
93    }
94
95    // Clear the lock flag on the slave pseudo terminal
96    if (::unlockpt (m_master_fd) < 0)
97    {
98        CloseMaster();
99        return err_unlockpt_failed;
100    }
101
102    return success;
103}
104
105//----------------------------------------------------------------------
106// Open the slave pseudo terminal for the current master pseudo
107// terminal. A master pseudo terminal should already be valid prior to
108// calling this function (see PseudoTerminal::OpenFirstAvailableMaster()).
109// The file descriptor is stored in the m_slave_fd member variable and
110// can be accessed via the SlaveFD() or ReleaseSlaveFD() accessors.
111//
112// RETURNS:
113//  Zero when successful, non-zero indicating an error occurred.
114//----------------------------------------------------------------------
115PseudoTerminal::Error
116PseudoTerminal::OpenSlave(int oflag)
117{
118    CloseSlave();
119
120    // Open the master side of a pseudo terminal
121    const char *slave_name = SlaveName();
122
123    if (slave_name == NULL)
124        return err_ptsname_failed;
125
126    m_slave_fd = ::open (slave_name, oflag);
127
128    if (m_slave_fd < 0)
129        return err_open_slave_failed;
130
131    return success;
132}
133
134
135
136//----------------------------------------------------------------------
137// Get the name of the slave pseudo terminal. A master pseudo terminal
138// should already be valid prior to calling this function (see
139// PseudoTerminal::OpenFirstAvailableMaster()).
140//
141// RETURNS:
142//  NULL if no valid master pseudo terminal or if ptsname() fails.
143//  The name of the slave pseudo terminal as a NULL terminated C string
144//  that comes from static memory, so a copy of the string should be
145//  made as subsequent calls can change this value.
146//----------------------------------------------------------------------
147const char*
148PseudoTerminal::SlaveName() const
149{
150    if (m_master_fd < 0)
151        return NULL;
152    return ::ptsname (m_master_fd);
153}
154
155
156//----------------------------------------------------------------------
157// Fork a child process that and have its stdio routed to a pseudo
158// terminal.
159//
160// In the parent process when a valid pid is returned, the master file
161// descriptor can be used as a read/write access to stdio of the
162// child process.
163//
164// In the child process the stdin/stdout/stderr will already be routed
165// to the slave pseudo terminal and the master file descriptor will be
166// closed as it is no longer needed by the child process.
167//
168// This class will close the file descriptors for the master/slave
169// when the destructor is called, so be sure to call ReleaseMasterFD()
170// or ReleaseSlaveFD() if any file descriptors are going to be used
171// past the lifespan of this object.
172//
173// RETURNS:
174//  in the parent process: the pid of the child, or -1 if fork fails
175//  in the child process: zero
176//----------------------------------------------------------------------
177
178pid_t
179PseudoTerminal::Fork(PseudoTerminal::Error& error)
180{
181    pid_t pid = invalid_pid;
182    error = OpenFirstAvailableMaster (O_RDWR|O_NOCTTY);
183
184    if (error == 0)
185    {
186        // Successfully opened our master pseudo terminal
187
188        pid = ::fork ();
189        if (pid < 0)
190        {
191            // Fork failed
192            error = err_fork_failed;
193        }
194        else if (pid == 0)
195        {
196            // Child Process
197            ::setsid();
198
199            error = OpenSlave (O_RDWR);
200            if (error == 0)
201            {
202                // Successfully opened slave
203                // We are done with the master in the child process so lets close it
204                CloseMaster ();
205
206#if defined (TIOCSCTTY)
207                // Acquire the controlling terminal
208                if (::ioctl (m_slave_fd, TIOCSCTTY, (char *)0) < 0)
209                    error = err_failed_to_acquire_controlling_terminal;
210#endif
211                // Duplicate all stdio file descriptors to the slave pseudo terminal
212                if (::dup2 (m_slave_fd, STDIN_FILENO) != STDIN_FILENO)
213                    error = error ? error : err_dup2_failed_on_stdin;
214                if (::dup2 (m_slave_fd, STDOUT_FILENO) != STDOUT_FILENO)
215                    error = error ? error : err_dup2_failed_on_stdout;
216                if (::dup2 (m_slave_fd, STDERR_FILENO) != STDERR_FILENO)
217                    error = error ? error : err_dup2_failed_on_stderr;
218            }
219        }
220        else
221        {
222            // Parent Process
223            // Do nothing and let the pid get returned!
224        }
225    }
226    return pid;
227}
228