Freebsd – Which virtual terminal is a given X process running on

freebsdx11xorg

When X starts, it searches for the lowest unused VT, and attaches to it. My problem is that when there are several running X processes, I need to be able to identify which one is the currently active one.

This is a *BSD question, because on linux it's easy: X sets its controlling terminal to be ttyN, or, on very old distributions, it's specified on the commandline as vtN. So, I'm running a service and I see that the currently active VT is tty7, and there are two X servers running, it's easy to tell which one corresponds to the current terminal. (This is a reasonable case: perhaps the user used GNOME/KDE's 'switch user' functionality or ran two servers using startx.) An example application that might want to follow the active X server is x11vnc (which is forked from the software I'm developing).

On FreeBSD though, the controlling terminal does not tell you anything. When X is started from ttyv1, that remains the controlling terminal.

Update

I've done due diligence and read the X code. After some hunting around, it's now clearer to me what's going on.

In lnx_init.c, the X server does setsid to make a fresh session for itself, then opens an fd to ttyN straight after to do a VT_ACTIVATE ioctl on it. Pretty standard; opening the fd to a terminal with no controlling process from a process with no controlling terminal associates the two, and the server keeps the fd open, so it's guaranteed that the terminal will remaining the controlling terminal for the X server.

Now, in bsd_init.c, opening the fd to the tty to be used as the framebuffer doesn't make it a controlling terminal (and in fact, with no setsid, BSD Xserver started from xinit on ttyv2 will keep ttyv2 as its ctty!).

Question further updated and cleaned up on 2012-04-09.

Best Answer

There is a more general way. On all platforms with virtual terminals, including linux and BSD, Xserver retains an open fd to the terminal it's running on. On linux, it remains a good solution to check for the controlling terminal of the X process to distinguish between multiple X processes (use the seventh field of /proc/<..>/stat). More generally though, look through the list of open fds of the X process, and it only needs some simple filtering to get out the terminal the Xserver is running on. (Unfortunately, getting the list of open fds is again platform-dependant...) For sysctl platforms like BSD, the code will look similar to this, plus some error handling:

int ttyByOpenFds(int curPid) {
    int ctl[4] = { CTL_KERN, KERN_PROC, KERN_PROC_FILEDESC, curPid };
    size_t sizeGuess = 50*sizeof(kinfo_file);
    char* buf = malloc(sizeGuess);
    int rv = sysctl(ctl, 4, buf, &sizeGuess, 0, 0);
    if (rv < 0 && errno == ESRCH) return 0;
    else if (rv < 0 && errno == ENOMEM) { /* try again */ }
    else if (rv < 0) throw SystemException("unexpected error getting args", errno);

    char* position = buf;
    while (position < buf + sizeGuess) {
      kinfo_file* kfp = reinterpret_cast<kinfo_file*>(position);
      position += kfp->kf_structsize;
      if (kfp->kf_type != KF_TYPE_VNODE) continue;
      if (kfp->kf_vnode_type != KF_VTYPE_VCHR) continue;
      if (kfp->kf_fd < 0) continue;
      char* name = devname(kfp->kf_un.kf_file.kf_file_rdev, S_IFCHR);
      if (!name) continue;
      unsigned int ttynum = 0;
      if (sscanf(name, "ttyv%u", &ttynum) != 1) continue;
      if (ttynum < 8 && kfp->kf_fd <= 2) continue; // stderr going to a console
      return ttynum;
    }
    return 0;
}
Related Question