0

I want to know why the two process id's match when the the getpid() in the fork() to my knowledge is supposed to be a different process than the one produced by popen().

I was informed that my code only works because of what I interpret might be a bug with Ubuntu based distro's such as Xubuntu, Lubuntu, and KDE neon, (which are the distro's I've tested so far). You can easily compile and test the code from here: https://github.com/time-killer-games/XTransientFor Ignore the x64 binary available at that link if you don't trust it. Arch, RedHat, etc. testers are especially welcome to give feedback if this happens to not work for them.

Here's a much more minimal approach to demonstrating the issue:

// USAGE: xprocesstest [command]

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>

#include <unistd.h>

#include <thread>
#include <chrono>

#include <iostream>
#include <string>

using std::string;

static inline Window XGetActiveWindow(Display *display) {
  unsigned long window;
  unsigned char *prop;

  Atom actual_type, filter_atom;
  int actual_format, status;
  unsigned long nitems, bytes_after;

  int screen = XDefaultScreen(display);
  window = RootWindow(display, screen);

  filter_atom = XInternAtom(display, "_NET_ACTIVE_WINDOW", True);
  status = XGetWindowProperty(display, window, filter_atom, 0, 1000, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);

  unsigned long long_property = prop[0] + (prop[1] << 8) + (prop[2] << 16) + (prop[3] << 24);
  XFree(prop);

  return (Window)long_property;
}

static inline pid_t XGetActiveProcessId(Display *display) {
  unsigned long window = XGetActiveWindow(display);
  unsigned char *prop;

  Atom actual_type, filter_atom;
  int actual_format, status;
  unsigned long nitems, bytes_after;

  filter_atom = XInternAtom(display, "_NET_WM_PID", True);
  status = XGetWindowProperty(display, window, filter_atom, 0, 1000, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);

  unsigned long long_property = prop[0] + (prop[1] << 8) + (prop[2] << 16) + (prop[3] << 24);
  XFree(prop);

  return (pid_t)(long_property - 1);
}

int main(int argc, const char **argv) {
  if (argc == 2) {
    char *buffer = NULL;
    size_t buffer_size = 0;
    string str_buffer;

    FILE *file = popen(argv[1], "r");

    if (fork() == 0) {
      Display *display = XOpenDisplay(NULL);
      Window window;

      unsigned i = 0;
      while (i < 10) {
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        if (XGetActiveProcessId(display) == getpid()) {
          window = XGetActiveWindow(display);
          break;
        }
        i++;
      }

      if (window == XGetActiveWindow(display)) 
      std::cout << "process id's match!" << std::endl;
      else std::cout << "process id's don't match!" << std::endl;

      XCloseDisplay(display);
      exit(0);
    }

    while (getline(&buffer, &buffer_size, file) != -1)
      str_buffer += buffer;

    std::cout << str_buffer;
    free(buffer);
    pclose(file);
  }
}

Compile with:

cd "${0%/*}"
g++ -c -std=c++17 "xprocesstest.cpp" -fPIC -m64
g++ "xprocesstest.o" -o "xprocesstest" -fPIC -lX11

Run with:

cd "${0%/*}"
./xprocesstest "kdialog --getopenfilename"

You can replace the command in the quotes with any executable which sets the _NET_WM_PID atom.

  • 4
    Is this really a [mcve]? It's not possible to remove half of the code to reproduce it? Xubuntu, Lubuntu and Ubuntu are the same distros with different desktop environments. – Thomas Sablik Nov 09 '19 at 23:55
  • True, I'll take care of it, sorry about that. –  Nov 09 '19 at 23:57
  • Although it's worth noting different desktop environments can mean different default window managers and packages and I'm not sure if that makes any difference which is why I posted that. –  Nov 10 '19 at 00:13
  • I wasn't clear and concise enough, I'll update the question again. –  Nov 10 '19 at 00:30
  • it appears someone has deleted their comment which seemed to be helpful to me at a first glance. –  Nov 10 '19 at 00:39
  • Let us know when you think you've given us a minimal complete example. – Beta Nov 10 '19 at 00:46
  • I have it now. My apologies. This really is the smallest that it can get that I know of. Thank you! –  Nov 10 '19 at 00:47
  • @rustyx then why is it printing that the two process id's are equal? Does this mean this is normal behavior for a if (fork() == 0) { getpid() } and popen process to be identical? –  Nov 10 '19 at 00:58
  • @SamuelJosephVenable There seems to still be something wrong with the currect code. `window` is never initialized. I assume you simply want to test `XGetActiveProcessId(display) == getpid()` after the fork? – walnut Nov 10 '19 at 00:59
  • @uneven_mark that is correct. I was in a rush to get my code corrected to avoid downvotes. but generally, if the two process id's are equal, then window will be initialized, otherwise it will be zero, thus making the window comparison true when not zero if the process id comparison is true. –  Nov 10 '19 at 01:01
  • 1
    I think the problem is as described [Is it possible to get active window & executable names in X11/Xlib?](https://stackoverflow.com/questions/2391669/is-it-possible-to-get-active-window-executable-names-in-x11-xlib). I had no issues building on either openSUSE or Archlinux with `g++ -Wall -Wextra -pedantic -std=gnu++11 -Ofast -lX11 -o bin/xprocesstest xprocesstest.cpp` and ran successfully with both `kdialog` and `zenity`. In both cases I get `"process id's don't match!"` which according to the link, isn't unexpected. – David C. Rankin Nov 10 '19 at 02:12
  • 4
    Are you aware that due to your `- 1` you're actually checking that the two processes have *sequential* pids? This is not surprising behavior on Linux. Differences between distros would be down to whether `sh` optimizes out the extra fork it uses to run the command – that other guy Nov 10 '19 at 04:42
  • @thatotherguy if you would like to explain that as an answer, I'll mark it as the accepted one, and upvote it, because even though I explain stuff in my answer that gives explanation, I never would've found out why they were returning equal if you hadn't reminded me I subtracted one. –  Nov 10 '19 at 19:35

1 Answers1

1

@thatotherguy explains the answer in his comment:

"Are you aware that due to your - 1 you're actually checking that the two processes have sequential pids? This is not surprising behavior on Linux. Differences between distros would be down to whether sh optimizes out the extra fork it uses to run the command"

static inline pid_t XGetActiveProcessId(Display *display) {
  unsigned long window = XGetActiveWindow(display);
  unsigned char *prop;

  Atom actual_type, filter_atom;
  int actual_format, status;
  unsigned long nitems, bytes_after;

  filter_atom = XInternAtom(display, "_NET_WM_PID", True);
  status = XGetWindowProperty(display, window, filter_atom, 0, 1000, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);

  unsigned long long_property = prop[0] + (prop[1] << 8) + (prop[2] << 16) + (prop[3] << 24);
  XFree(prop);

  return (pid_t)(long_property - 1);
}

That - 1 in the process id return I initially added because I thought at the time it was returning the wrong process id, because I thought when I wrote that the fork() was supposed to have the same process id as the popen, which later on I discovered wasn't the case. I subtracted one, thus making two different otherwise correct process id's, incorrectly equal.

Here's the correct way to do what I intended to do, in my original code, which lead to me asking this question; I wanted to know how to detect if a fork and popen child processes stem from a common parent process (while removing the subtraction of one from the return of the GetActiveProcessId() function):

#include <proc/readproc.h>
#include <cstring>

static inline pid_t GetParentPidFromPid(pid_t pid) {
  proc_t proc_info; pid_t ppid;
  memset(&proc_info, 0, sizeof(proc_info));
  PROCTAB *pt_ptr = openproc(PROC_FILLSTATUS | PROC_PID, &pid);
  if(readproc(pt_ptr, &proc_info) != 0) { 
    ppid = proc_info.ppid;
    string cmd = proc_info.cmd;
    if (cmd == "sh")
      ppid = GetParentPidFromPid(ppid);
  } else ppid = 0;
  closeproc(pt_ptr);
  return ppid;
}

Using the above helper function, while replacing the while loop, in the original code, with this, allows me to do what I was after:

  while (i < 10) {
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    if (GetParentPidFromPid(XGetActiveProcessId(display)) == GetParentPidFromPid(getpid()) ||
      GetParentPidFromPid(GetParentPidFromPid(XGetActiveProcessId(display))) == GetParentPidFromPid(getppid())) {
      window = XGetActiveWindow(display);
      break;
    }
    i++;
  }

As @thatotherguy also pointed out, some distros will return a different parent process because the sh cmd will use run directly. To solve this, I did an or check in the if statement for seeing whether the parent or "grandparent" process id's returned equal, while attempting to skip any parent processes with the sh cmd value.

The helper function needs the -lprocps linker flag and libprocps-dev package installed if you are on a Debian based system. Package name will be different on other distros.