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 []