How to get the parent(s) of a PID in Haskell?

Viewed 85

Haskell provides

-- getProcessID calls getppid to obtain the ProcessID for the parent of the current process.
getParentProcessID :: IO ProcessID

but given an external pid, is there an idiomatic way to obtain the parent(s) of this pid, perhaps returning a list of them?

1 Answers

Yes, it is. I'm basing my answer on Michael Sloan's custom xmonad setup (which you can find here). The original code is safer, but I tried to simplify it at the cost of said safety. With that out of the way, let's start: the getParentProcessID you mentioned uses a primitive call, so it won't really help us. We'd need to work with what the operating system (in this case, Linux) provides us with.

I'll take PID 54742 as my working example. If I execute cat /proc/54742/stat, I'll be greeted with a bunch of numbers:

54742 (xmobar) S 1448 54742 54742 0 -1 1077936128 5426 2478991 0 0 20286 11071 4413 922 20 0 45 0 261768 1102823616512 11314 18446744073709551615 4194304 26203544 140728670498336 0 0 0 0 0 547334 0 0 0 17 11 0 0 3 0 0 26210368 28767304 33419264 140728670505038 140728670505049 140728670505049 140728670506968 0

it happens that the fourth entry is the PID of the parent process, in our case: 1448. With that, we can write a function that fetches that entry for us

import           Control.Exception
import           System.Posix.Types


getParentPid :: ProcessID -> IO (Maybe ProcessID)
getParentPid pid = do
  let fp = "/proc/" ++ show (toInteger pid) ++ "/stat"
  econtents <- try $ readFile fp :: IO (Either SomeException String)
  case econtents of
    Right xs ->
      case lines xs of
        [ws] -> case words ws of
          (_:_:_:ppid:_) -> return . Just . read $ ppid
    _ -> return Nothing

phew, that was some work, but nothing complicated: generate the name of the file, read it, get the fourth entry, cast it to a ProcessID, and wrap it up in Just. If anything fails, return Nothing.

With that, we can go ahead and implement our getParentsPids function, which keeps reading PIDs until it hits Nothing:

getParentsPids :: ProcessID  -> IO [ProcessID]
getParentsPids pid = do
  ppid <- getParentPid pid
  case ppid of
    Just ppid' -> do
      ps <- getParentsPids ppid'
      return (ppid':ps)
    Nothing -> return []

And we're pretty much done. A little driver main:

main :: IO ()
main = do
  pid <- read <$> getLine
  getParentsPids pid >>= print . show

which prints [1448,1,0] when I give it 54742. Seems right!

Bonus: we can make the code of getPraentPid a bit prettier by using ViewPatterns:

{-# LANGUAGE ViewPatterns #-}

import           Control.Exception
import           System.Posix.Types


getParentPid :: ProcessID -> IO (Maybe ProcessID)
getParentPid pid = do
  let fp = "/proc/" ++ show (toInteger pid) ++ "/stat"
  econtents <- try $ readFile fp :: IO (Either SomeException String)
  case econtents of
    Right (lines -> [words -> (_:_:_: (read -> ppid):_)]) -> return (Just ppid)
    _ -> return Nothing

Bonus #2: I would also rewrite getParentsPids more compactly with LambdaCase:

getParentsPids :: ProcessID -> IO [ProcessID]
getParentsPids pid = getParentPid pid >>= \case
  Just ppid -> (ppid :) <$> getParentsPids ppid
  Nothing   -> return []
Related