Advanced Programming in the UNIX Environment: Second Edition [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Advanced Programming in the UNIX Environment: Second Edition [Electronic resources] - نسخه متنی

W. Richard Stevens; Stephen A. Rago

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید



19.3. Opening Pseudo-Terminal Devices


The way we open a pseudo-terminal device differs among platforms. The Single UNIX Specification includes several functions as XSI extensions in an attempt to unify the methods. These extensions are based on the functions originally provided to manage STREAMS-based pseudo terminals in System V Release 4.

The posix_openpt function is provided as a portable way to open an available pseudo-terminal master device.

#include <stdlib.h>
#include <fcntl.h>
int posix_openpt(int

oflag );

Returns: file descriptor of next available PTY master if OK, 1 on error

The

oflag argument is a bitmask that specifies how the master device is to be opened, similar to the same argument used with open(2). Not all open flags are supported, however. With posix_openpt, we can specify O_RDWR to open the master device for reading and writing, and we can specify O_NOCTTY to prevent the master device from becoming a controlling terminal for the caller. All other open flags result in unspecified behavior.

Before a slave pseudo-terminal device can be used, its permissions need to be set so that it is accessible to applications. The grantpt function does just this. It sets the user ID of the slave's device node to be the caller's real user ID and sets the node's group ID to an unspecified value, usually some group that has access to terminal devices. The permissions are set to allow read and write access to individual owners and write access to group owners (0620).

#include <stdlib.h>
int grantpt(int

filedes );
int unlockpt(int

filedes );

Both return: 0 on success, 1 on error

To change permission on the slave device node, grantpt might need to fork and exec a set-user-ID program (/usr/lib/pt_chmod on Solaris, for example). Thus, the behavior is unspecified if the caller is catching SIGCHLD.

The unlockpt function is used to grant access to the slave pseudo-terminal device, thereby allowing applications to open the device. By preventing others from opening the slave device, applications setting up the devices have an opportunity to initialize the slave and master devices properly before they can be used.

Note that in both grantpt and unlockpt, the file descriptor argument is the file descriptor associated with the master pseudo-terminal device.

The ptsname function is used to find the pathname of the slave pseudo-terminal device, given the file descriptor of the master. This allows applications to identify the slave independent of any particular conventions that might be followed by a given platform. Note that the name returned might be stored in static memory, so it can be overwritten on successive calls.

#include <stdlib.h>
char *ptsname(int

filedes );

Returns: pointer to name of PTY slave if OK, NULL on error

Figure 19.7 summarizes the pseudo-terminal functions in the Single UNIX Specification and indicates which functions are supported by the platforms discussed in this text.

Figure 19.7. XSI pseudo-terminal functions

Function

Description

XSI

FreeBSD 5.2.1

Linux 2.4.22

Mac OS X 10.3

Solaris 9

grantpt

Change permissions of slave PTY device.

posix_openpt

Open a master PTY device.

ptsname

Return name of slave PTY device.

unlockpt

Allow slave PTY device to be opened.

On FreeBSD, unlockpt does nothing; the O_NOCTTY flag is defined only for compatibility with applications that call posix_openpt. FreeBSD does not allocate a controlling terminal as a side effect of opening a terminal device, so the O_NOCTTY flag has no effect.

Even though the Single UNIX Specification has tried to improve portability in this area, implementations are still catching up, as illustrated by Figure 19.7. Thus, we provide two functions that handle all the details: ptym_open to open the next available PTY master device and ptys_open to open the corresponding slave device.

#include "apue.h"
int ptym_open(char *

pts_name , int

pts_namesz );

Returns: file descriptor of PTY master if OK, 1 on error

int ptys_open(char *

pts_name );

Returns: file descriptor of PTY slave if OK, 1 on error

Normally, we don't call these two functions directly; the function pty_fork (Section 19.4) calls them and also forks a child process.

The ptym_open function determines the next available PTY master and opens the device. The caller must allocate an array to hold the name of either the master or the slave; if the call succeeds, the name of the corresponding slave is returned through

pts_name . This name is then passed to ptys_open, which opens the slave device. The length of the buffer in bytes is passed in

pts_namesz so that the ptym_open function doesn't copy a string that is longer than the buffer.

The reason for providing two functions to open the two devices will become obvious when we show the pty_fork function. Normally, a process calls ptym_open to open the master and obtain the name of the slave. The process then forks, and the child calls ptys_open to open the slave after calling setsid to establish a new session. This is how the slave becomes the controlling terminal for the child.


19.3.1. STREAMS-Based Pseudo Terminals


The details of the STREAMS implementation of pseudo terminals under Solaris are covered in Appendix C of Sun Microsystems [2002]. The next available PTY master device is accessed through a STREAMS

clone device . A clone device is a special device that returns an unused device when it is opened. (STREAMS clone opens are discussed in detail in Rago [1993].)

The STREAMS-based PTY master clone device is /dev/ptmx. When we open it, the clone open routine automatically determines the first unused PTY master device and opens that unused device. (We'll see in the next section that, under BSD-based systems, we have to find the first unused PTY master ourselves.)


Figure 19.8. STREAMS-based pseudo-terminal open functions

#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#include <stropts.h>
int
ptym_open(char *pts_name, int pts_namesz)
{
char *ptr;
int fdm;
/*
* Return the name of the master device so that on failure
* the caller can print an error message. Null terminate
* to handle case where strlen("/dev/ptmx") > pts_namesz.
*/
strncpy(pts_name, "/dev/ptmx", pts_namesz);
pts_name[pts_namesz - 1] = '\0';
if ((fdm = open(pts_name, O_RDWR)) < 0)
return(-1);
if (grantpt(fdm) < 0) { /* grant access to slave */
close(fdm);
return(-2);
}
if (unlockpt(fdm) < 0) { /* clear slave's lock flag */
close(fdm);
return(-3);
}
if ((ptr = ptsname(fdm)) == NULL) { /* get slave's name */
close(fdm);
return(-4);
}
/*
* Return name of slave. Null terminate to handle
* case where strlen(ptr) > pts_namesz.
*/
strncpy(pts_name, ptr, pts_namesz);
pts_name[pts_namesz - 1] = '\0';
return(fdm); /* return fd of master */
}
int
ptys_open(char *pts_name)
{
int fds, setup;
/*
* The following open should allocate a controlling terminal.
*/
if ((fds = open(pts_name, O_RDWR)) < 0)
return(-5);
/*
* Check if stream is already set up by autopush facility.
*/
if ((setup = ioctl(fds, I_FIND, "ldterm")) < 0) {
close(fds);
return(-6);
}
if (setup == 0) {
if (ioctl(fds, I_PUSH, "ptem") < 0) {
close(fds);
return(-7);
}
if (ioctl(fds, I_PUSH, "ldterm") < 0) {
close(fds);
return(-8);
}
if (ioctl(fds, I_PUSH, "ttcompat") < 0) {
close(fds);
return(-9);
}
}
return(fds);
}

Figure 14.18), we push it onto the slave's stream.

The reason that we might not need to push these three modules is that they might be there already. The STREAMS system supports a facility known as

autopush , which allows an administrator to configure a list of modules to be pushed onto a stream whenever a particular device is opened (see Rago [1993] for more details). We use the I_FIND ioctl command to see whether ldterm is already on the stream. If so, we assume that the stream has been configured by the autopush mechanism and avoid pushing the modules a second time.

The result of calling ptym_open and ptys_open is two file descriptors open in the calling process: one for the master and one for the slave.


19.3.2. BSD-Based Pseudo Terminals


Under BSD-based systems and Linux-based systems, we provide our own versions of the XSI functions, which we can optionally include in our library, depending on which functions (if any) are provided by the underlying platform.

In our version of posix_openpt, we have to determine the first available PTY master device. To do this, we start at /dev/ptyp0 and keep trying until we successfully open a PTY master or until we run out of devices. We can get two different errors from open: EIO means that the device is already in use; ENOENT means that the device doesn't exist. In the latter case, we can terminate the search, as all pseudo terminals are in use. Once we are able to open a PTY master, say /dev/pty

MN , the name of the corresponding slave is /dev/tty

MN . On Linux, if the name of the PTY master is /dev/pty/m

XX , then the name of the corresponding PTY slave is /dev/pty/s

XX .

Section 19.4, we'll see how to allocate the controlling terminal under BSD-based systems.


Figure 19.9. Pseudo-terminal open functions for BSD and Linux

#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#ifndef _HAS_OPENPT
int
posix_openpt(int oflag)
{
int fdm;
char *ptr1, *ptr2;
char ptm_name[16];
strcpy(ptm_name, "/dev/ptyXY");
/* array index: 0123456789 (for references in following code) */
for (ptr1 = "pqrstuvwxyzPQRST"; *ptr1 != 0; ptr1++) {
ptm_name[8] = *ptr1;
for (ptr2 = "0123456789abcdef"; *ptr2 != 0; ptr2++) {
ptm_name[9] = *ptr2;
/*
* Try to open the master.
*/
if ((fdm = open(ptm_name, oflag)) < 0) {
if (errno == ENOENT) /* different from EIO */
return(-1); /* out of pty devices */
else
continue; /* try next pty device */
}
return(fdm); /* got it, return fd of master */
}
}
errno = EAGAIN;
return(-1); /* out of pty devices */
}
#endif
#ifndef _HAS_PTSNAME
char *
ptsname(int fdm)
{
static char pts_name[16];
char *ptm_name;
ptm_name = ttyname(fdm);
if (ptm_name == NULL)
return(NULL);
strncpy(pts_name, ptm_name, sizeof(pts_name));
pts_name[sizeof(pts_name) - 1] = '\0';
if (strncmp(pts_name, "/dev/pty/", 9) == 0)
pts_name[9] = 's'; /* change /dev/pty/mXX to /dev/pty/sXX */
else
pts_name[5] = 't'; /* change "pty" to "tty" */
return(pts_name);
}
#endif
#ifndef _HAS_GRANTPT
int
grantpt(int fdm)
{
struct group *grptr;
int gid;
char *pts_name;
pts_name = ptsname(fdm);
if ((grptr = getgrnam("tty")) != NULL)
gid = grptr->gr_gid;
else
gid = -1; /* group tty is not in the group file */
/*
* The following two calls won't work unless we're the superuser.
*/
if (chown(pts_name, getuid(), gid) < 0)
return(-1);
return(chmod(pts_name, S_IRUSR | S_IWUSR | S_IWGRP));
}
#endif
#ifndef _HAS_UNLOCKPT
int
unlockpt(int fdm)
{
return(0); /* nothing to do */
}
#endif
int
ptym_open(char *pts_name, int pts_namesz)
{
char *ptr;
int fdm;
/*
* Return the name of the master device so that on failure
* the caller can print an error message. Null terminate
* to handle case where string length > pts_namesz.
*/
strncpy(pts_name, "/dev/ptyXX", pts_namesz);
pts_name[pts_namesz - 1] = '\0';
if ((fdm = posix_openpt(O_RDWR)) < 0)
return(-1);
if (grantpt(fdm) < 0) { /* grant access to slave */
close(fdm);
return(-2);
}
if (unlockpt(fdm) < 0) { /* clear slave's lock flag */
close(fdm);
return(-3);
}
if ((ptr = ptsname(fdm)) == NULL) { /* get slave's name */
close(fdm);
return(-4);
}
/*
* Return name of slave. Null terminate to handle
* case where strlen(ptr) > pts_namesz.
*/
strncpy(pts_name, ptr, pts_namesz);
pts_name[pts_namesz - 1] = '\0';
return(fdm); /* return fd of master */
}
int
ptys_open(char *pts_name)
{
int fds;
if ((fds = open(pts_name, O_RDWR)) < 0)
return(-5);
return(fds);
}

Our version of posix_openpt tries 16 different groups of 16 PTY master devices: /dev/ptyp0 tHRough /dev/ptyTf. The actual number of PTY devices available depends on two factors: (a) the number configured into the kernel, and (b) the number of special device files that have been created in the /dev directory. The number available to any program is the lesser of (a) or (b).


19.3.3. Linux-Based Pseudo Terminals


Linux supports the BSD method for accessing pseudo terminals, so the same functions shown in Figure 19.9 will also work on Linux. However, Linux also supports a clone-style interface to pseudo terminals using /dev/ptmx (but this is not a STREAMS device). The clone interface requires extra steps to identify and unlock a slave device. The functions we can use to access these pseudo terminals on Linux are shown in Figure 19.10.


Figure 19.10. Pseudo-terminal open functions for Linux

#include "apue.h"
#include <fcntl.h>
#ifndef _HAS_OPENPT
int
posix_openpt(int oflag)
{
int fdm;
fdm = open("/dev/ptmx", oflag);
return(fdm);
}
#endif
#ifndef _HAS_PTSNAME
char *
ptsname(int fdm)
{
int sminor;
static char pts_name[16];
if (ioctl(fdm, TIOCGPTN, &sminor) < 0)
return(NULL);
snprintf(pts_name, sizeof(pts_name), "/dev/pts/%d", sminor);
return(pts_name);
}
#endif
#ifndef _HAS_GRANTPT
int
grantpt(int fdm)
{
char *pts_name;
pts_name = ptsname(fdm);
return(chmod(pts_name, S_IRUSR | S_IWUSR | S_IWGRP));
}
#endif
#ifndef _HAS_UNLOCKPT
int
unlockpt(int fdm)
{
int lock = 0;
return(ioctl(fdm, TIOCSPTLCK, &lock));
}
#endif
int
ptym_open(char *pts_name, int pts_namesz)
{
char *ptr;
int fdm;
/*
* Return the name of the master device so that on failure
* the caller can print an error message. Null terminate
* to handle case where string length > pts_namesz.
*/
strncpy(pts_name, "/dev/ptmx", pts_namesz);
pts_name[pts_namesz - 1] = '\0';
fdm = posix_openpt(O_RDWR);
if (fdm < 0)
return(-1);
if (grantpt(fdm) < 0) { /* grant access to slave */
close(fdm);
return(-2);
}
if (unlockpt(fdm) < 0) { /* clear slave's lock flag */
close(fdm);
return(-3);
}
if ((ptr = ptsname(fdm)) == NULL) { /* get slave's name */
close(fdm);
return(-4);
}
/*
* Return name of slave. Null terminate to handle case
* where strlen(ptr) > pts_namesz.
*/
strncpy(pts_name, ptr, pts_namesz);
pts_name[pts_namesz - 1] = '\0';
return(fdm); /* return fd of master */
}
int
ptys_open(char *pts_name)
{
int fds;
if ((fds = open(pts_name, O_RDWR)) < 0)
return(-5);
return(fds);
}

On Linux, the PTY slave device is already owned by group tty, so all we need to do in grantpt is ensure that the permissions are correct.


    / 369