Execute a Command in Another Terminal via /Dev/Pts

Execute a command in another terminal via /dev/pts

I completely get what you are asking. You can achieve this by writing and executing a small piece of code in C yourself. This should give you some idea.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>

void print_help(char *prog_name) {
printf("Usage: %s [-n] DEVNAME COMMAND\n", prog_name);
printf("Usage: '-n' is an optional argument if you want to push a new line at the end of the text\n");
printf("Usage: Will require 'sudo' to run if the executable is not setuid root\n");
exit(1);
}

int main (int argc, char *argv[]) {
char *cmd, *nl = "\n";
int i, fd;
int devno, commandno, newline;
int mem_len;
devno = 1; commandno = 2; newline = 0;
if (argc < 3) {
print_help(argv[0]);
}
if (argc > 3 && argv[1][0] == '-' && argv[1][1] == 'n') {
devno = 2; commandno = 3; newline=1;
} else if (argc > 3 && argv[1][0] == '-' && argv[1][1] != 'n') {
printf("Invalid Option\n");
print_help(argv[0]);
}
fd = open(argv[devno],O_RDWR);
if(fd == -1) {
perror("open DEVICE");
exit(1);
}
mem_len = 0;
for (i = commandno; i < argc; i++) {
mem_len += strlen(argv[i]) + 2;
if (i > commandno) {
cmd = (char *)realloc((void *)cmd, mem_len);
} else { // i == commandno
cmd = (char *)malloc(mem_len);
}

strcat(cmd, argv[i]);
strcat(cmd, " ");
}
if (newline == 0)
usleep(225000);
for (i = 0; cmd[i]; i++)
ioctl (fd, TIOCSTI, cmd+i);
if (newline == 1)
ioctl (fd, TIOCSTI, nl);
close(fd);
free((void *)cmd);
exit (0);
}

Compile and execute it with sudo permissions. For example, if you want to execute a command on /dev/pts/3, then simply do a sudo ./a.out -n /dev/pts/3 whoami, runs a whoami on /dev/pts/3.

This code was completely taken from this page.

Executing string sent from one terminal in another in Linux pseudo-terminal

No; terminals don't execute commands. They're just channels for data.

You can sort of run a command and attach it to another terminal like this, though:

ls </dev/pts/2 >/dev/pts/2 2>/dev/pts/2

It won't behave exactly like you ran it from that terminal, though, as it won't have that device set as its controlling terminal. It's reasonably close, though.

why can't I write to the standard input of my terminal device from another terminal

You can do it by doing this commands, (from /dev/pts/1 or another tty):

exec 1>/dev/pts/0

to deactivate

exec 1>/dev/pts/1 #or your actually original tty address.

Basically you are supplanting the tty stdin.

Edited for more details.

"exec" in this case starts a new bash and you can feed this with a new set of environment variables that normally you can not change on the fly. For more details please do "man exec".

"1>/dev/pts/0" here we are saying, "whatever I type on this new bash, write it to this another one, and indeed it will do it, but all the stdout will be displayed at the original tty.

Good luck learning linux, I hope you enjoy it.

Open terminal, run command, return to SAME terminal later and execute another command

In order to write commands to a terminal from another program or terminal you must use a system input-output control system call (ioctl). (This may not always be the case but is is the solution I have found). I will also be presenting a solution in Python but I have cited other resources including a method in c below.

First, you need the process identifier (PID) of the terminal instance you wish to send commands to for it to execute. This can be determined in a few ways but the easiest way I found was via the following command:

ps -A | grep bash --color=always

This will output a list of open terminals and their PIDs and pts numbers. The easiest way I find to know which is the one you want is to open a terminal via your program, run the aforementioned command and the recently opened terminal will be the last on the list. I'm sure you can get more fancy with it if you need to be certain but that isn't the point of this question. You will see something like this, where the pts/# is what you're after

108514 pts/2    00:00:00 bash

Next use the following code and simply save it to a .py file of your choice, (credit for this code goes to the answer in the first link below, the Python one). Note that the example below is hard coded to send the "ls" command. Again, either change the hard coded command or make it not hard coded depending on your own preference and use case.

import fcntl
import sys
import termios

with open(sys.argv[1], 'w') as fd:
for c in "ls\n":
fcntl.ioctl(fd, termios.TIOCSTI, c)

Then, simply call the new function and pass it the following path based on the pts number found previously like so:

python <your_fcn_name_here).py /dev/pts/#

Worked fine for me on Ubuntu 14.04. I'll be trying it on CentOS soon. Didn't have to install any python libraries to do it.

Other Resources

This question has been posed differently here:

  • In Python: https://stackoverflow.com/a/29615101/7590133
  • In C: https://stackoverflow.com/a/7370822/7590133

For more good information regarding IOCTLs:

  • IOCTL Linux device driver

How can I start a bash on a specific /dev/ttyX (or /dev/pts/X) device?

fork() a child process. Open the serial device or pseudotty and dup2() it to fd's 0, 1 and 2. execv() your preferred shell.

Race condition when reading from /dev/pts/x

Actually, bash sets the terminal in non canonical mode when reading from the terminal and when it gets an end of line, it sets back the terminal in canonical mode to run the command line.

The same experience can be done with two terminals:

  • Terminal#1 (/dev/pts/6): launch strace /bin/bash
  • Terminal#2: launch strace cat /dev/pts/6

The bash shell on terminal#1 deactivates the canonical mode and calls pselect() to wait for the input:

$ strace /bin/bash
[...]
ioctl(0, TCGETS, {B38400 opost isig -icanon -echo ...}) = 0
[...]
pselect6(1, [0], NULL, NULL, NULL, {[], 8}

On terminal#2, the cat command merely calls a blocking read() to get chars from the terminal:

$ strace cat /dev/pts/6
[...]
openat(AT_FDCWD, "/dev/pts/6", O_RDONLY) = 3
[...]
read(3,

So, both bash and cat are reading concurrently on the terminal. When we type characters in terminal#1, pselect() returns to indicate that a character is available and then bash calls a blocking read() to get the char. But the concurrent read() from cat stills the characters between the calls to pselect() and read() by bash. Sometimes, bash is able to get a character before cat.

Here is an example where pselect() returns because a character is available (I typed "Y") and a following read() is called to get it on terminal#1:

pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = 1 (in [0])
read(0,

But read() from cat on the other terminal succeeded to get the character before the read() of bash:

write(1, "Y", 1Y)                        = 1
read(3,

Sometimes, bash is able to get the typed character before cat. Generally, when it is blocked on its read() call (that is to say it missed the character detected by pselect() but it will be able to get one of the subsequent typed characters before one of the calls to read() by cat)...

Side note

When we launch stty -a under bash, the display shows that the terminal is in canonical mode:

$ stty -a
[...]
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc

This is because of the fact that bash reactivates the canonical mode before launching the stty command.



Related Topics



Leave a reply



Submit