Skip to content

Commit

Permalink
More documentation. Some code cleanup / function renames.
Browse files Browse the repository at this point in the history
  • Loading branch information
emptymonkey committed Jun 12, 2013
1 parent d38ba98 commit fd0c407
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 66 deletions.
59 changes: 26 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,48 @@
# ctty #

_ctty_ is a controlling [tty](http://www.linusakesson.net/programming/tty/) discovery tool (and library).
_ctty_ is a controlling tty discovery tool (and library).

**What's a tty?**
**What is a tty?**

In Unix and Linux, users can issue commands to the operating system through a [command line](http://en.wikipedia.org/wiki/Command_line). In the modern era, a command line is implemented by a [shell](http://en.wikipedia.org/wiki/Shell_%28computing%29) that the user interacts with through a [pseudo-terminal](http://linux.die.net/man/7/pty). The pseudo-terminal itself is a type of tty and leverages the [tty subsystem](http://lxr.linux.no/#linux+v3.9.5/drivers/tty) of the [Linux kernel](https://www.kernel.org/).
In Unix and Linux, users can issue commands to the operating system through a [command line](http://en.wikipedia.org/wiki/Command_line). In the modern era, a command line is implemented as a [shell](http://en.wikipedia.org/wiki/Shell_%28computing%29) attached to a [pseudo-terminal](http://linux.die.net/man/7/pty). The pseudo-terminal itself is a type of tty and leverages the [tty subsystem](http://lxr.linux.no/#linux+v3.9.5/drivers/tty) of the [Linux kernel](https://www.kernel.org/).

**What's a _controlling_ tty?**
**What is a _controlling_ tty?**

A controlling tty is a tty that has a special relationship with a [process session](http://www.win.tue.nl/~aeb/linux/lk/lk-10.html). When a tty is controlling for a session, it will send the session leader, and other members of that session, [signals](http://en.wikipedia.org/wiki/Unix_signal) to help control the user experience.

**What's a session?**
Processes are grouped into process groups. Process groups are grouped together into sessions. A session is that grouping of processes that cooperatively conspire to share a controlling tty. A very good reference on this topic is the [man page](http://en.wikipedia.org/wiki/Man_page) for [credentials](http://linux.die.net/man/7/credentials).
**What is a session?**

**What part of the operating system keeps track of all this?**

The tty driver in the kernel will know what session id a tty is controlling for. Also, any process knows which tty is controlling for it. Honestly, it seems to be a bit of a conspiracy between the two, as neither is really authoritative on the subject. All in all, it seems to work pretty well.

**How can you tell what the controlling tty is for any given process?**
Processes are grouped into process groups for [job control](http://en.wikipedia.org/wiki/Job_control_%28Unix%29). Process groups themselves are grouped together into a session for resource sharing of a tty. A very good reference on this topic is the [man page](http://en.wikipedia.org/wiki/Man_page) for [credentials](http://linux.die.net/man/7/credentials).

The [stat](http://linux.die.net/man/5/proc) file contains that information.

**How can you tell which process session is controlled by a given tty?**
**What part of the operating system keeps track of all this?**

Traditionally, there is no easy way to see this information. I wrote _ctty_ to fill this gap. It does the needed detective work, and reports it back to the user.
The tty driver in the kernel will know what session ID a tty is controlling for. Likewise, every process in a session will know which tty is controlling for it. Honestly, it seems to be a bit of a conspiracy between the driver and all of the processes in the session. No single piece acts authoritatively, and this relationship requires that all the participating members play well together. In the end, it seems to work out pretty well.

**This is all pretty crazy? How did it end up this way?**

***Short Version***

Pretend you are a computer engineer in the 1960s. Computers are big and slow. Users are used to interacting with them by feeding them [punch cards](http://en.wikipedia.org/wiki/Punch_cards). Around this time, however, the computers finally get fast enough to interact with users in real time. What interface would you invent to allow multiple users to talk to a machine all at the same time??
Pretend you are a computer engineer in the 1960s. Computers are big and slow. Users are used to interacting with them by feeding them [punch cards](http://en.wikipedia.org/wiki/Punch_cards). Around this time, however, the computers finally get fast enough to interact with users in real time. What interface would you invent to allow multiple users to talk to the machine all at the same time??

If you're any good, I'll tell you how you'd do it... [You'd be lazy!](http://c2.com/cgi/wiki?LazinessImpatienceHubris)

Back in that era, the old [teletype terminals](http://en.wikipedia.org/wiki/Teleprinter) were broadly used for communication. The engineers of the day simply re-purposed this technology to fit their needs. This was the birth of the command line.
Back in that era, the [old teletype terminals](http://en.wikipedia.org/wiki/Teleprinter) were broadly used in the communications industry. The engineers of the day simply re-purposed this technology to fit their needs. This was the birth of the command line.

***Long Version***

Go read [The TTY demystified](http://www.linusakesson.net/programming/tty/) by [Linus Åkesson](http://www.linusakesson.net/)! On this topic, his page is the most enlightening on the internet. Many thanks to Linus for putting this together!
Go read [The TTY demystified](http://www.linusakesson.net/programming/tty/) by [Linus Åkesson](http://www.linusakesson.net/). On this topic, his page is the most enlightening on the internet. Many thanks to Linus for putting this together!

**And there's a _ctty_ library too?**
**How can you tell what the controlling tty is for any given process?**

I needed code to do ctty discovery for [another project](https://github.com/emptymonkey/shelljack). The library was the main code-base. The _ctty_ tool was simply my debug driver code. I then decided it may be useful enough to be promoted to "tool".
The [stat](http://linux.die.net/man/5/proc) file contains that information.

**How can you tell which session is controlled by any given tty?**

Traditionally, there is no easy way to see this information programmatically. (The "[ps j](http://linux.die.net/man/1/ps)" command can help you perform this discovery manually.) I wrote _ctty_ to fill this gap. It does the needed detective work, and reports back to the user. The library gives you a C interface to this functionality.

**In addition to the _ctty_ tool, there's a library too?**

I needed code to do ctty discovery for [another project](https://github.com/emptymonkey/shelljack). The _ctty_ tool was simply my debug test code. I then decided it may be useful enough to be promoted to "tool".

## _ctty_ Usage ##

Expand Down Expand Up @@ -81,12 +82,8 @@ To see the session information for all tty's:

## _libctty_ Usage ##

This is best documented inside the source code. However, as a quick overview, _libctty_ provides the following interfaces:
This is best documented inside the source code. However, as a quick overview, _libctty.h_ defines the following interfaces:
```
/************************************************************************
* Functions for working with controlling ttys:
************************************************************************/
/* ctty_get_name() is used to discover the controlling tty for a process. */
char *ctty_get_name(int pid);
Expand All @@ -96,15 +93,11 @@ struct sid_node *ctty_get_session(char *tty_name);
/* ctty_free_session() is used to release the session data structure. */
void ctty_free_session(struct sid_node *session);
/************************************************************************
* Functions for working with any ttys:
************************************************************************/
/* tty_stat_parse() will parse the processes stat file in /proc. */
int tty_stat_parse(int pid, struct proc_stat *stat_info);
/* ctty_stat_parse() will pull ctty and session related info from the processes stat file. */
int ctty_stat_parse(int pid, struct proc_stat *stat_info);
/* tty_get_fds() returns the list of file descriptors open to the tty you're interested in. */
int tty_get_fds(int pid, char *tty, int **fds);
/* ctty_get_fds() returns the list of file descriptors open to the tty you're interested in. */
int ctty_get_fds(int pid, char *tty, int **fds);
```

## Installation ##
Expand Down
38 changes: 19 additions & 19 deletions libctty.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ char *ctty_get_name(int pid){
struct proc_stat stat_info;


if((retval = tty_stat_parse(pid, &stat_info)) == -1){
fprintf(stderr, "%s: ctty_get_name(): tty_stat_parse(%d, %lx): %s\n", program_invocation_short_name, \
if((retval = ctty_stat_parse(pid, &stat_info)) == -1){
fprintf(stderr, "%s: ctty_get_name(): ctty_stat_parse(%d, %lx): %s\n", program_invocation_short_name, \
pid, (unsigned long) &stat_info, \
strerror(errno));
goto CLEAN_UP;
Expand Down Expand Up @@ -184,8 +184,8 @@ struct sid_node *ctty_get_session(char *tty_name){
return(NULL);
}

if((retval = tty_stat_parse(pid, &tmp_stat_info)) == -1){
fprintf(stderr, "%s: ctty_get_session(): tty_stat_parse(%d, %lx): %s\n", program_invocation_short_name, \
if((retval = ctty_stat_parse(pid, &tmp_stat_info)) == -1){
fprintf(stderr, "%s: ctty_get_session(): ctty_stat_parse(%d, %lx): %s\n", program_invocation_short_name, \
pid, (unsigned long) &tmp_stat_info, \
strerror(errno));
globfree(&pglob);
Expand All @@ -211,8 +211,8 @@ struct sid_node *ctty_get_session(char *tty_name){
new_pid_ptr->pgid = tmp_stat_info.pgrp;
new_pid_ptr->sid = tmp_stat_info.session;

if((new_pid_ptr->fd_count = tty_get_fds(new_pid_ptr->pid, tty_name, &new_pid_ptr->fds)) == -1){
fprintf(stderr, "%s: ctty_get_session(): tty_get_fds(%d, %s, %lx): %s\n", program_invocation_short_name, \
if((new_pid_ptr->fd_count = ctty_get_fds(new_pid_ptr->pid, tty_name, &new_pid_ptr->fds)) == -1){
fprintf(stderr, "%s: ctty_get_session(): ctty_get_fds(%d, %s, %lx): %s\n", program_invocation_short_name, \
new_pid_ptr->pid, tty_name, (unsigned long) &new_pid_ptr->fds, \
strerror(errno));
globfree(&pglob);
Expand Down Expand Up @@ -451,7 +451,7 @@ struct sid_node *ctty_get_session(char *tty_name){

/************************************************************************
*
* tty_stat_parse()
* ctty_stat_parse()
*
* Inputs:
* The name of the /proc/PID/stat file you want to parse.
Expand All @@ -461,7 +461,7 @@ struct sid_node *ctty_get_session(char *tty_name){
* An error code. (Hopefully zero, if all is well.)
*
************************************************************************/
int tty_stat_parse(int pid, struct proc_stat *stat_info){
int ctty_stat_parse(int pid, struct proc_stat *stat_info){
int stat_fd;

char scratch[BUFF_LEN];
Expand All @@ -471,14 +471,14 @@ int tty_stat_parse(int pid, struct proc_stat *stat_info){
snprintf(scratch, BUFF_LEN, "/proc/%d/stat", pid);

if((stat_fd = open(scratch, O_RDONLY)) == -1){
fprintf(stderr, "%s: tty_stat_parse(): open(%s, %d): %s\n", program_invocation_short_name, \
fprintf(stderr, "%s: ctty_stat_parse(): open(%s, %d): %s\n", program_invocation_short_name, \
scratch, O_RDONLY, \
strerror(errno));
return(-1);
}

if((read(stat_fd, scratch, sizeof(scratch))) < 1){
fprintf(stderr, "%s: tty_stat_parse(): read(%d, %lx, %d): %s\n", program_invocation_short_name, \
fprintf(stderr, "%s: ctty_stat_parse(): read(%d, %lx, %d): %s\n", program_invocation_short_name, \
stat_fd, (unsigned long) scratch, (int) sizeof(scratch), \
strerror(errno));
return(-1);
Expand All @@ -488,7 +488,7 @@ int tty_stat_parse(int pid, struct proc_stat *stat_info){
stat_info->pid = strtol(scratch, NULL, 10);

if((parse_ptr = strrchr(scratch, ')')) == NULL){
fprintf(stderr, "%s: tty_stat_parse(): strrchr(%lx, %d): %s\n", program_invocation_short_name, \
fprintf(stderr, "%s: ctty_stat_parse(): strrchr(%lx, %d): %s\n", program_invocation_short_name, \
(unsigned long) scratch, ')', \
strerror(errno));
return(-1);
Expand All @@ -499,15 +499,15 @@ int tty_stat_parse(int pid, struct proc_stat *stat_info){
stat_info->ppid = strtol(parse_ptr, &parse_ptr, 10);
stat_info->pgrp = strtol(parse_ptr, &parse_ptr, 10);
stat_info->session = strtol(parse_ptr, &parse_ptr, 10);
stat_info->tty_nr= strtol(parse_ptr, NULL, 10);
stat_info->tty_nr = strtol(parse_ptr, NULL, 10);

return(0);
}


/************************************************************************
*
* tty_get_fds()
* ctty_get_fds()
*
* Inputs:
* The pid of the process we are interested in.
Expand All @@ -519,7 +519,7 @@ int tty_stat_parse(int pid, struct proc_stat *stat_info){
* The total count of file descriptors being returned in the array.
*
************************************************************************/
int tty_get_fds(int pid, char *tty, int **fds){
int ctty_get_fds(int pid, char *tty, int **fds){
char path[MAX_PATH_LEN + 1];
char scratch[MAX_PATH_LEN + 1];
DIR *proc_pid_fd;
Expand All @@ -528,14 +528,14 @@ int tty_get_fds(int pid, char *tty, int **fds){

memset(path, 0, sizeof(path));
if(snprintf(path, sizeof(path), "/proc/%d/fd/", pid) < 0){
fprintf(stderr, "%s: tty_get_fds(): snprintf(%lx, %d, %s, %d): %s\n", program_invocation_short_name, \
fprintf(stderr, "%s: ctty_get_fds(): snprintf(%lx, %d, %s, %d): %s\n", program_invocation_short_name, \
(unsigned long) path, (int) sizeof(path), "/proc/%%d/fd/", pid, \
strerror(errno));
return(-1);
}

if(!(proc_pid_fd = opendir(path))){
fprintf(stderr, "%s: tty_get_fds(): opendir(%s): %s\n", program_invocation_short_name, \
fprintf(stderr, "%s: ctty_get_fds(): opendir(%s): %s\n", program_invocation_short_name, \
path, \
strerror(errno));
return(-1);
Expand All @@ -551,7 +551,7 @@ int tty_get_fds(int pid, char *tty, int **fds){

memset(scratch, 0, sizeof(scratch));
if(snprintf(scratch, sizeof(scratch), "/proc/%d/fd/%s", pid, dir_entry->d_name) < 0){
fprintf(stderr, "%s: tty_get_fds(): snprintf(%lx, %d, %s, %d, %s): %s\n", program_invocation_short_name, \
fprintf(stderr, "%s: ctty_get_fds(): snprintf(%lx, %d, %s, %d, %s): %s\n", program_invocation_short_name, \
(unsigned long) scratch, (int) sizeof(scratch), "/proc/%%d/fd/", pid, dir_entry->d_name, \
strerror(errno));
count = -1;
Expand All @@ -560,7 +560,7 @@ int tty_get_fds(int pid, char *tty, int **fds){

memset(path, 0, sizeof(path));
if(readlink(scratch, path, sizeof(path) - 1) == -1){
fprintf(stderr, "%s: tty_get_fds(): readlink(%lx, %s, %d): %s\n", program_invocation_short_name, \
fprintf(stderr, "%s: ctty_get_fds(): readlink(%lx, %s, %d): %s\n", program_invocation_short_name, \
(unsigned long) scratch, path, (int) sizeof(path) - 1, \
strerror(errno));
count = -1;
Expand All @@ -578,7 +578,7 @@ int tty_get_fds(int pid, char *tty, int **fds){
if(!i){
rewinddir(proc_pid_fd);
if(((*fds = (int *) malloc(count * sizeof(int))) == 0) && count){
fprintf(stderr, "%s: tty_get_fds(): malloc(%d): %s\n", program_invocation_short_name, \
fprintf(stderr, "%s: ctty_get_fds(): malloc(%d): %s\n", program_invocation_short_name, \
count * (int) sizeof(int), \
strerror(errno));
count = -1;
Expand Down
18 changes: 4 additions & 14 deletions libctty.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,6 @@ struct proc_stat{
};


/************************************************************************
* Functions for working with controlling ttys:
************************************************************************/

/* ctty_get_name() is used to discover the controlling tty for a process. */
char *ctty_get_name(int pid);

Expand All @@ -95,14 +91,8 @@ struct sid_node *ctty_get_session(char *tty_name);
/* ctty_free_session() is used to release the session data structure. */
void ctty_free_session(struct sid_node *session);

/* ctty_stat_parse() will pull ctty and session related info from the processes stat file. */
int ctty_stat_parse(int pid, struct proc_stat *stat_info);

/************************************************************************
* Functions for working with any ttys:
************************************************************************/

/* tty_stat_parse() will parse the processes stat file in /proc. */
int tty_stat_parse(int pid, struct proc_stat *stat_info);

/* tty_get_fds() returns the list of file descriptors open to the tty you're interested in. */
int tty_get_fds(int pid, char *tty, int **fds);

/* ctty_get_fds() returns the list of file descriptors open to the tty you're interested in. */
int ctty_get_fds(int pid, char *tty, int **fds);

0 comments on commit fd0c407

Please sign in to comment.