Skip to content

Commit

Permalink
Updated docs and added debug cpp defs.
Browse files Browse the repository at this point in the history
  • Loading branch information
emptymonkey committed Jun 21, 2013
1 parent 2d4ec30 commit 105ffbe
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 26 deletions.
49 changes: 25 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
# ctty #

_ctty_ is a controlling tty discovery tool (and library).
_ctty_ is a [controlling tty](http://en.wikipedia.org/wiki/POSIX_terminal_interface#Controlling_terminals_and_process_groups) discovery tool (and library).

**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 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 driver](http://lxr.linux.no/#linux+v3.9.5/drivers/tty) section of the [Linux kernel](https://www.kernel.org/).
In [Unix](http://en.wikipedia.org/wiki/Unix) and [Linux](http://en.wikipedia.org/wiki/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](http://en.wikipedia.org/wiki/Teleprinter) and leverages the [tty driver](http://lxr.linux.no/#linux+v3.9.5/drivers/tty) section of the [Linux kernel](https://www.kernel.org/).

**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.
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 is a session?**

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).
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 to facilitate the resource sharing of a tty. The [man page](http://en.wikipedia.org/wiki/Man_page) for [credentials](http://linux.die.net/man/7/credentials) is an excellent resource on this topic.

**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. 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.
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. There is no single authoritative point on the topic and a stable system requires the cooperation of all the players involved.

**This sounds totally crazy? How did it end up this way?**

Back in late 1960s, computers were finally fast enough to interact with users in real time. Coincidentally, the [old teletype terminals](http://en.wikipedia.org/wiki/Teleprinter) were broadly used in throughout the telecommunications industry. The engineers of the day, being appropriately [lazy](http://threevirtues.com/), simply re-purposed this existing technology to fit their needs. This was the birth of the command line.
Back in late 1960s, computers were finally fast enough to interact with users in real time. Coincidentally, the [old teletype terminals](http://en.wikipedia.org/wiki/Teleprinter) were broadly used throughout the telecommunications industry. The engineers of the day, being appropriately [lazy](http://threevirtues.com/), simply re-purposed this existing technology to fit their needs. This was the birth of the command line.

Make sure you read [The TTY demystified](http://www.linusakesson.net/programming/tty/) by [Linus Åkesson](http://www.linusakesson.net/). His page is the most enlightening for this topic anywhere on the internet. Many thanks to Linus for putting it together!

Expand All @@ -35,34 +35,35 @@ Traditionally, there is no easy way to see this information programmatically. (T

## _ctty_ Usage ##

usage: ctty [-v] [TTY_NAME]
-v verbose reporting format

To see the session information for a particular tty:
```
empty@monkey:~$ ctty /dev/pts/3
/dev/pts/3:empty:3099:3099:3099:0,1,2,255
/dev/pts/3:empty:3099:3158:3158:0,1,2
/dev/pts/3:empty:3099:3158:3170:1,2
/dev/pts/3:empty:3099:3176:3176:15,16,17,18,19
/dev/pts/3:empty:3099:3184:3184:0,1,2,5,6,7
```

empty@monkey:~$ ctty /dev/pts/3
/dev/pts/3:empty:3099:3099:3099:0,1,2,255
/dev/pts/3:empty:3099:3158:3158:0,1,2
/dev/pts/3:empty:3099:3158:3170:1,2
/dev/pts/3:empty:3099:3176:3176:15,16,17,18,19
/dev/pts/3:empty:3099:3184:3184:0,1,2,5,6,7

The format is:

TTY_NAME:USER:SID:PGID:PID:FD1,FD2,...,FDn

The fields are:

* TTY_NAME: tty name
* USER: user name (or uid if no match in /etc/passwd)
* SID: session ID
* PGID: process group ID
* PID: process ID
* FDs: file descriptors *which are open to this controlling tty.*
* TTY_NAME: tty name
* USER: user name (or uid if no match in /etc/passwd)
* SID: session ID
* PGID: process group ID
* PID: process ID
* FDs: file descriptors *which this process has open to the controlling tty.*

Notes:
Note:

* Only the file descriptors that are pointing to the processes controlling tty are listed.
* If you run _ctty_ without any arguments, it will attempt to return the results for all ttys. (This will probably fail for most ttys unless you are root.)
* The -v switch will give a different output format that is a bit easier to read, though much longer and not fit for scripting.
* Running _ctty_ without any arguments will attempt to return the results for all ttys.
* The -v switch will give a different output format that is a bit easier to read, though much longer and not fit for scripting.

## _libctty_ Usage ##

Expand Down
5 changes: 3 additions & 2 deletions ctty.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ void ctty_print_session(struct sid_node *session_list, int verbose);


void usage(){
fprintf(stderr, "usage: %s [-c] [TTY_NAME]\n", program_invocation_short_name);
fprintf(stderr, "usage: %s [-v] [TTY_NAME]\n", program_invocation_short_name);
fprintf(stderr, "\t-v\tverbose reporting format\n");
exit(-1);
}
Expand Down Expand Up @@ -85,8 +85,9 @@ int main(int argc, char **argv){
"/dev/pts/[0-9]*", 0, NULL, (unsigned long) &pglob);
}
for(i = 0; i < (int) pglob.gl_pathc; i++){
errno = 0;
if(((session_tmp = ctty_get_session(pglob.gl_pathv[i])) == NULL) && (errno)){
fprintf(stderr, "ctty_get_session(%s)", pglob.gl_pathv[i]);
fprintf(stderr, "ctty_get_session(%s): %s\n", pglob.gl_pathv[i], strerror(errno));
}else if(session_tmp){

if(!session_head){
Expand Down
44 changes: 44 additions & 0 deletions libctty.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ char *ctty_get_name(int pid){


if((retval = ctty_stat_parse(pid, &stat_info)) == -1){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_name(): ctty_stat_parse(%d, %lx): %s\n", program_invocation_short_name, \
pid, (unsigned long) &stat_info, \
strerror(errno));
#endif
goto CLEAN_UP;
}

Expand All @@ -67,9 +69,11 @@ char *ctty_get_name(int pid){
}

if(!(dev_dir = opendir(path))){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_name(): opendir(%s): %s\n", program_invocation_short_name, \
path, \
strerror(errno));
#endif
return(NULL);
}

Expand All @@ -87,17 +91,21 @@ char *ctty_get_name(int pid){
}

if(stat(scratch, &dev_info)){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_name(): stat(%s, %lx): %s\n", program_invocation_short_name, \
scratch, (unsigned long) &dev_info, \
strerror(errno));
#endif
goto CLEAN_UP;
}

if(stat_info.tty_nr == (int) dev_info.st_rdev){
if((name = (char *) malloc(strlen(scratch) + 1)) == NULL){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_name(): malloc(%d): %s\n", program_invocation_short_name, \
(int) strlen(scratch) + 1, \
strerror(errno));
#endif
goto CLEAN_UP;
}
memset(name, 0, strlen(scratch) + 1);
Expand Down Expand Up @@ -145,9 +153,11 @@ struct sid_node *ctty_get_session(char *tty_name){
*
*/
if((retval = stat(tty_name, &stat_buf)) == -1){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_session(): stat(%s, %lx): %s\n", program_invocation_short_name, \
tty_name, (unsigned long) &stat_buf, \
strerror(errno));
#endif
return(NULL);
}

Expand All @@ -159,35 +169,43 @@ struct sid_node *ctty_get_session(char *tty_name){
* We are only interested in processes for which tty_name is a controlling tty.
*/
if((retval = glob("/proc/[0-9]*/stat", 0, NULL, &pglob))){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_session(): glob(%s, %d, %p, %lx): retval == %d\n", program_invocation_short_name, \
"/proc/[0-9]*/stat", 0, NULL, (unsigned long) &pglob, \
retval);
#endif
return(NULL);
}

for(i = 0; i < (int) pglob.gl_pathc; i++){
if((retval = stat(pglob.gl_pathv[i], &stat_buf)) == -1){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_session(): stat(%s, %lx): %s\n", program_invocation_short_name, \
pglob.gl_pathv[i], (unsigned long) &stat_buf, \
strerror(errno));
#endif
globfree(&pglob);
return(NULL);
}

if(ctty_uid == stat_buf.st_uid){

if((pid = (int) strtol((pglob.gl_pathv[i]) + 6, NULL, 10)) == 0){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_session(): strtol(%lx, %p, %d): %s\n", program_invocation_short_name, \
(unsigned long) (pglob.gl_pathv[i]) + 6, NULL, 10, \
strerror(errno));
#endif
globfree(&pglob);
return(NULL);
}

if((retval = ctty_stat_parse(pid, &tmp_stat_info)) == -1){
#ifdef DEBUG
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));
#endif
globfree(&pglob);
return(NULL);
}
Expand All @@ -199,9 +217,11 @@ struct sid_node *ctty_get_session(char *tty_name){
*
*/
if((new_pid_ptr = (struct pid_node *) malloc(sizeof(struct pid_node))) == NULL){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_session(): malloc(%d): %s\n", program_invocation_short_name, \
(int) sizeof(struct pid_node), \
strerror(errno));
#endif
globfree(&pglob);
return(NULL);
}
Expand All @@ -212,9 +232,11 @@ struct sid_node *ctty_get_session(char *tty_name){
new_pid_ptr->sid = tmp_stat_info.session;

if((new_pid_ptr->fd_count = ctty_get_fds(new_pid_ptr->pid, tty_name, &new_pid_ptr->fds)) == -1){
#ifdef DEBUG
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));
#endif
globfree(&pglob);
clean_pids(new_pid_ptr);
clean_pids(head_pid_ptr);
Expand Down Expand Up @@ -291,9 +313,11 @@ struct sid_node *ctty_get_session(char *tty_name){
*
*/
if((tmp_sid_ptr = (struct sid_node *) malloc(sizeof(struct sid_node))) == NULL){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_session(): malloc(%d): %s\n", program_invocation_short_name, \
(int) sizeof(struct sid_node), \
strerror(errno));
#endif
clean_pids(head_pid_ptr);
return(NULL);
}
Expand All @@ -304,9 +328,11 @@ struct sid_node *ctty_get_session(char *tty_name){

retval = strlen(tty_name);
if((tmp_sid_ptr->ctty = (char *) malloc(retval + 1)) == NULL){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_session(): malloc(%d): %s\n", program_invocation_short_name, \
retval + 1, \
strerror(errno));
#endif
clean_pids(head_pid_ptr);
ctty_free_session(tmp_sid_ptr);
return(NULL);
Expand All @@ -330,9 +356,11 @@ struct sid_node *ctty_get_session(char *tty_name){
while(head_pid_ptr){

if((new_pgid_ptr = (struct pgid_node *) malloc(sizeof(struct pgid_node))) == NULL){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_session(): malloc(%d): %s\n", program_invocation_short_name, \
(int) sizeof(struct pgid_node), \
strerror(errno));
#endif
clean_pids(head_pid_ptr);
ctty_free_session(tmp_sid_ptr);
return(NULL);
Expand Down Expand Up @@ -471,26 +499,32 @@ int ctty_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){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_stat_parse(): open(%s, %d): %s\n", program_invocation_short_name, \
scratch, O_RDONLY, \
strerror(errno));
#endif
return(-1);
}

if((read(stat_fd, scratch, sizeof(scratch))) < 1){
#ifdef DEBUG
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));
#endif
return(-1);
}
close(stat_fd);

stat_info->pid = strtol(scratch, NULL, 10);

if((parse_ptr = strrchr(scratch, ')')) == NULL){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_stat_parse(): strrchr(%lx, %d): %s\n", program_invocation_short_name, \
(unsigned long) scratch, ')', \
strerror(errno));
#endif
return(-1);
}

Expand Down Expand Up @@ -528,16 +562,20 @@ int ctty_get_fds(int pid, char *tty, int **fds){

memset(path, 0, sizeof(path));
if(snprintf(path, sizeof(path), "/proc/%d/fd/", pid) < 0){
#ifdef DEBUG
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));
#endif
return(-1);
}

if(!(proc_pid_fd = opendir(path))){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_fds(): opendir(%s): %s\n", program_invocation_short_name, \
path, \
strerror(errno));
#endif
return(-1);
}

Expand All @@ -551,18 +589,22 @@ int ctty_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){
#ifdef DEBUG
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));
#endif
count = -1;
goto CLEAN_UP;
}

memset(path, 0, sizeof(path));
if(readlink(scratch, path, sizeof(path) - 1) == -1){
#ifdef DEBUG
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));
#endif
count = -1;
goto CLEAN_UP;
}
Expand All @@ -578,9 +620,11 @@ int ctty_get_fds(int pid, char *tty, int **fds){
if(!i){
rewinddir(proc_pid_fd);
if(((*fds = (int *) malloc(count * sizeof(int))) == 0) && count){
#ifdef DEBUG
fprintf(stderr, "%s: ctty_get_fds(): malloc(%d): %s\n", program_invocation_short_name, \
count * (int) sizeof(int), \
strerror(errno));
#endif
count = -1;
goto CLEAN_UP;
}
Expand Down
1 change: 1 addition & 0 deletions libctty.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#undef DEBUG

#define _GNU_SOURCE

Expand Down

0 comments on commit 105ffbe

Please sign in to comment.