Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Native Development

Reply
New Contributor
Mordak
Posts: 4
Registered: ‎08-26-2012
My Device: Playbook 16GB

How to get controlling tty in fork() child?

Hello,

 

I am trying to get a controlling tty in my Playbook application, but am unable to do so. After getting a pty/tty pair via openpty(), my application forks, and the child attempts to set the tty slave as its controlling tty, then exec sh (this is classic Terminal / Shell behaviour). openpty() works fine, but the child process is unable to set the tty slave as its controlling tty. I have tried:

 

1. Using login_tty() to login and set controlling tty on the slave. login_tty() returns 'Operation not permitted'.

 

2. Using tcsetsid() on the slave tty to set it as the controlling tty, which returns 'Operation not permitted'.

 

3. Using setuid(), setgid(), setsid() and then ioctl() to set the slave fd as the controlling tty. setsid returns 'Operation not permitted', as does ioctl().

 

4. Opening /dev/tty directly, unsetting it as the controlling tty, then setting the slave tty as the controlling tty. Opening /dev/tty fails, even though ctermid() identifies it as the controlling tty for the child process, and then ioctl() fails with 'Operation not permitted'.

 

Is this possible? It would be nice if shell applications could have full job control, but without a controlling tty hooked up to the parent process, sh will complain.

 

I have attached a simple main.c that illustrates the different methods I have tried. If someone knows how to do this and can point me in the right direction I would be much appreciative.

 

/*
 * main.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ioctl.h>
#include <unix.h>
#include <errno.h>
#include <termios.h>

 int main(int argc, char **argv) {

	int pty_ret;
	int master_fd;
	int child_fd;
	int child_pid;
	int fd;
	int uid = getuid();
	int gid = getgid();
	char ttyname[L_ctermid];
	char cttyname[L_ctermid];

	int METHOD = 1;

	pty_ret = openpty(&master_fd, &child_fd, ttyname, NULL, NULL);
	if (pty_ret != 0){
		fprintf(stderr, "openpty returned: %s\n", strerror(errno));
		close(master_fd);
		close(child_fd);
		return -1;
	} else {
		fprintf(stderr, "openpty returned name: %s\n", ttyname);
	}

	child_pid = fork();

	if (child_pid == 0) {
		/* Child */
		fprintf(stderr, "fork returned in child\n");
		ctermid(cttyname);
		fprintf(stderr, "controlling tty is: %s\n", cttyname);

		/* Method 1: Use login_tty */
		if(METHOD == 1){
			if(login_tty(child_fd) == -1) {
				fprintf(stderr, "login_tty returned: %s\n", strerror(errno));
			}
		}

		/* Method 2: Use tcsetsid on the slave fd */
		if(METHOD == 2){
			/* Clear old controlling tty */
			tcsetsid(-1,getpid());
			/* And set new one */
			if(tcsetsid(child_fd, getpid())){
				fprintf(stderr, "tcsetsid returned: %s\n", strerror(errno));
			}
		}

		/* Method 3: Try setting uid/gid/sid, then ioctl */
		if (METHOD == 3){
			if(setuid(uid)<0) {fprintf(stderr, "ERROR (setuid)\n");}
			if(setgid(gid)<0) {fprintf(stderr, "ERROR (setgid)\n");}
			if(setsid()<0) {fprintf(stderr, "ERROR (setsid)\n");}
			if(ioctl(child_fd, TIOCSCTTY, NULL)) {
				fprintf(stderr, "ERROR! (ioctl): %s\n", strerror(errno));
			}
		}

		/* Method 4: Try opening / closing the tty's directly */
		if (METHOD == 4){
			/* Disconnect from old controlling tty (usually /dev/tty) */
			fd = open(cttyname, O_RDWR | O_NOCTTY);
			if(fd >= 0){
				(void)ioctl(fd, TIOCNOTTY, NULL);
				close(fd);
			} else {
				fprintf(stderr, "Failed to open %s\n", cttyname);
			}

			/* Set slave as controlling tty */
			if(ioctl(child_fd, TIOCSCTTY, NULL)) {
				fprintf(stderr, "ERROR! (ioctl): %s\n", strerror(errno));
			}
		}

		/* And now dup the std in/out/err to the tty */
		dup2(child_fd, STDIN_FILENO);
		dup2(child_fd, STDOUT_FILENO);
		dup2(child_fd, STDERR_FILENO);

		/* And exec the shell */
		execl("/bin/sh", "sh", (char*)0);
	}

	/* Parent */
	if (child_pid == -1){
		fprintf(stderr, "fork returned: %s\n", strerror(errno));
		return -1;
	}
	sleep(5);
	close(child_fd);
	close(master_fd);
	return 0;
 }

 

Developer
BGmot
Posts: 1,068
Registered: ‎11-24-2011
My Device: PlayBook

Re: How to get controlling tty in fork() child?

It would be interesting to see the output of all these attempts...

Thanks.

New Contributor
Mordak
Posts: 4
Registered: ‎08-26-2012
My Device: Playbook 16GB

Re: How to get controlling tty in fork() child?

No problem. The output from each of the four code paths is:
Method 1

openpty returned name: /dev/ttyp0
fork returned in child
controlling tty is: /dev/tty
login_tty returned: Operation not permitted
Method 2

openpty returned name: /dev/ttyp0
fork returned in child
controlling tty is: /dev/tty
tcsetsid returned: Operation not permitted
Method 3

openpty returned name: /dev/ttyp0
fork returned in child
controlling tty is: /dev/tty
ERROR (setsid)
ERROR! (ioctl): Operation not permitted
Method 4

openpty returned name: /dev/ttyp0
fork returned in child
controlling tty is: /dev/tty
Failed to open /dev/tty
ERROR! (ioctl): Operation not permitted

You should be able to just copy / paste the code into an empty project template and compile it if you wanted to run it yourself.

If we can figure out a solution, then I should be able to send you a diff for BGShell, which is how I got started on this. :-)

 

BlackBerry Development Advisor
smcveigh
Posts: 668
Registered: ‎11-29-2011
My Device: developer

Re: How to get controlling tty in fork() child?

regarding method 3:

From the setsid() docs, this is a restricted function:

"In order to create a new session, your process must have the PROCMGR_AID_PGRP ability enabled. For more information, see procmgr_ability() ."

 

After perusing the docs for the functions in your other methods, they tend to reference "creates a new session", which would point towards the same restriction.

 

You can try requesting the appropriate procmgr_ability(), but it's conceivable that this may only be doable by root.

 

Cheers,

Sean

New Contributor
Mordak
Posts: 4
Registered: ‎08-26-2012
My Device: Playbook 16GB

Re: How to get controlling tty in fork() child?

Thank you for replying! It is nice to have someone take a look at my problem.

 

I had messed around with procmgr_ability() before, but didn't get anywhere. I figured that since the documentation says this is enabled by default, that it wasn't the problem.

 

Anyway, when I modify Method 3 to include the call to procmgr_ability():

 

/* Method 3: Try setting uid/gid/sid, then ioctl */
  if (METHOD == 3){
    if(setuid(uid)<0) {fprintf(stderr, "ERROR (setuid)\n");}
    if(setgid(gid)<0) {fprintf(stderr, "ERROR (setgid)\n");}
    switch (procmgr_ability(getpid(), PROCMGR_ADN_NONROOT|PROCMGR_AOP_ALLOW|PROCMGR_AID_PGRP, PROCMGR_AID_EOL)){
      case EOK: fprintf(stderr, "procmgr_ability success\n"); break;
      case EINVAL: fprintf(stderr, "procmgr_ability returned EINVAL\n");break;
      case EPERM: fprintf(stderr, "procmgr_ability returned EPERM\n");break;
      default: fprintf(stderr, "procmgr_ability returned something else?\n");break;
    }
    if(setsid()<0) {fprintf(stderr, "ERROR (setsid)\n");}
    if(ioctl(child_fd, TIOCSCTTY, NULL)) {
      fprintf(stderr, "ERROR! (ioctl): %s\n", strerror(errno));
    }
  }

 

Then I get this output:

 

openpty returned name: /dev/ttyp0
fork returned in child
controlling tty is: /dev/tty
procmgr_ability returned EPERM
ERROR (setsid)
ERROR! (ioctl): Operation not permitted

 

Indicating that I do not have permission to modify that attribute. I did try it with:

 

int pgpid = setpgrp();
fprintf(stderr, "setpgrp returned %d, mypid is %d\n", pgpid, getpid());

 

instead of:

 

if(setsid()<0) {fprintf(stderr, "ERROR (setsid)\n");}

 

and got:

 

openpty returned name: /dev/ttyp0
fork returned in child
controlling tty is: /dev/tty
procmgr_ability returned EPERM
setpgrp returned 125431964, mypid is 125460550
ERROR! (ioctl): Operation not permitted

 

Indicating that the process group id was not set to the process id, and so my program cannot become a session leader.

 

So, from this I can conclude that I need to be root in order to get a controlling tty? If this is the case, is this a bug or a feature? It would be too bad if this was simply impossible, because with a decent shell, and some command line tools, the playbook could replace my macbook for 90% of the stuff I use it for, especially on the road.

 

Developer
BGmot
Posts: 1,068
Registered: ‎11-24-2011
My Device: PlayBook

Re: How to get controlling tty in fork() child?

I guess the problem is that you can't steal 'session lead' from parent process if you are not root -(

Did not have time to play myself...