summaryrefslogtreecommitdiff
path: root/examples/consolation.hs
diff options
context:
space:
mode:
authorJoe Crayne <joe@jerkface.net>2018-09-08 08:15:21 -0400
committerJoe Crayne <joe@jerkface.net>2018-10-03 07:00:40 -0400
commit9c02c2816c826d8f40cfaa4409175c88cfc1ea12 (patch)
tree69d2a079d19b7affa9e2b5f41552ed2b35c68d7d /examples/consolation.hs
parentfbf9890a6bcd4e6212b5947f908bc34f233b279d (diff)
Move example utilities into examples directory.
Diffstat (limited to 'examples/consolation.hs')
-rw-r--r--examples/consolation.hs186
1 files changed, 186 insertions, 0 deletions
diff --git a/examples/consolation.hs b/examples/consolation.hs
new file mode 100644
index 00000000..0c576dfc
--- /dev/null
+++ b/examples/consolation.hs
@@ -0,0 +1,186 @@
1{-# LANGUAGE OverloadedStrings #-}
2module Main where
3
4import Control.Monad
5import Control.Applicative
6import Control.Concurrent
7import Control.Concurrent.STM
8import Data.Monoid
9import Data.Char
10import System.INotify ( initINotify, EventVariety(Modify), addWatch )
11import Data.Word ( Word8 )
12import Data.Text ( Text )
13import Data.Map ( Map )
14import Data.List ( foldl' )
15import qualified Data.Map as Map
16import qualified Data.Traversable as Traversable
17import qualified Data.Text as Text
18import qualified Data.Text.IO as Text
19import qualified Network.BSD as BSD
20
21import WaitForSignal ( waitForTermSignal )
22import UTmp ( users2, utmp_file, UtmpRecord(..), UT_Type(..) )
23import FGConsole ( monitorTTY )
24
25data ConsoleState = ConsoleState
26 { csActiveTTY :: TVar Word8
27 , csUtmp :: TVar (Map Text (TVar (Maybe UtmpRecord)))
28 }
29
30newConsoleState = atomically $
31 ConsoleState <$> newTVar 0 <*> newTVar Map.empty
32
33
34onLogin cs start = \e -> do
35 us <- UTmp.users2
36 let (m,cruft) =
37 foldl' (\(m,cruft) x ->
38 case utmpType x of
39 USER_PROCESS
40 -> (Map.insert (utmpTty x) x m,cruft)
41 DEAD_PROCESS | utmpPid x /= 0
42 -> (m,Map.insert (utmpTty x) x cruft)
43 _ -> (m,cruft))
44 (Map.empty,Map.empty)
45 us
46 forM_ (Map.elems cruft) $ \c -> do
47 putStrLn $ "cruft " ++ show (utmpTty c, utmpPid c,utmpHost c, utmpRemoteAddr c)
48 newborn <- atomically $ do
49 old <- readTVar (csUtmp cs) -- swapTVar (csUtmp cs) m
50 newborn <- flip Traversable.mapM (m Map.\\ old)
51 $ newTVar . Just
52 updated <- let upd v u = writeTVar v $ Just u
53 in Traversable.sequence $ Map.intersectionWith upd old m
54 let dead = old Map.\\ m
55 Traversable.mapM (flip writeTVar Nothing) dead
56 writeTVar (csUtmp cs) $ (old `Map.union` newborn) Map.\\ dead
57 return newborn
58 let getActive = do
59 tty <- readTVar $ csActiveTTY cs
60 utmp <- readTVar $ csUtmp cs
61 flip (maybe $ return (tty,Nothing))
62 (Map.lookup ("tty"<>tshow tty) utmp)
63 $ \tuvar -> do
64 tu <- readTVar tuvar
65 return (tty,tu)
66
67 forM_ (Map.elems newborn) $
68 forkIO . start getActive
69 -- forM_ (Map.elems dead ) $ putStrLn . ("gone: "++) . show
70
71onTTY outvar cs vtnum = do
72 logit outvar $ "switch: " <> tshow vtnum
73 atomically $ writeTVar (csActiveTTY cs) vtnum
74
75retryWhen var pred = do
76 value <- var
77 if pred value then retry
78 else return value
79
80tshow x = Text.pack . show $ x
81
82resource :: UtmpRecord -> Text
83resource u =
84 case utmpTty u of
85 s | Text.take 3 s == "tty" -> s
86 s | Text.take 4 s == "pts/" -> "pty" <> Text.drop 4 s <> ":" <> utmpHost u
87 s -> escapeR s <> ":" <> utmpHost u
88 where
89 escapeR s = s
90
91textHostName = fmap Text.pack BSD.getHostName
92
93ujid u = do
94 h <- textHostName
95 return $ utmpUser u <> "@" <> h <> "/" <> resource u
96
97newCon :: (Text -> IO ()) -> STM (Word8,Maybe UtmpRecord) -> TVar (Maybe UtmpRecord) -> IO ()
98newCon log activeTTY utmp = do
99 ((tty,tu),u) <- atomically $
100 liftM2 (,) activeTTY
101 (readTVar utmp)
102 flip (maybe $ return ()) u $ \u -> do
103 jid <- ujid u
104 log $ status (resource u) tty tu <> " " <> jid <> " pid=" <> tshow (utmpPid u)
105 <> (if istty (resource u)
106 then " host=" <> tshow (utmpHost u)
107 else "")
108 <> " session=" <> tshow (utmpSession u)
109 <> " addr=" <> tshow (utmpRemoteAddr u)
110 loop tty tu (Just u)
111 where
112 bstatus r ttynum mtu
113 = r == ttystr
114 || match mtu
115 where ttystr = "tty" <> tshow ttynum
116 searchstr mtu = maybe ttystr utmpHost $ do
117 tu <- mtu
118 guard (not $ Text.null $ utmpHost tu)
119 return tu
120 match mtu = searchstr mtu `Text.isInfixOf` Text.dropWhile (/=':') r
121 status r ttynum tu =
122 if bstatus r ttynum tu
123 then "Available"
124 else "Away "
125
126 istty r = fst3 == "tty" && Text.all isDigit rst
127 where
128 (fst3,rst) = Text.splitAt 3 r
129
130 loop tty tu u = do
131 what <- atomically $ foldr1 orElse
132 [ do (tty',tu') <- retryWhen activeTTY
133 (\ttyu -> bstatus r tty tu == uncurry (bstatus r) ttyu)
134 return $ ttyChanged tty' tu'
135 , do u' <- retryWhen (readTVar utmp) (==u)
136 return $ utmpChanged u'
137 ]
138 what
139 where
140 r = maybe "" resource u
141
142 ttyChanged tty' tu' = do
143 jid <- maybe (return "") ujid u
144 log $ status r tty' tu' <> " " <> jid
145 loop tty' tu' u
146
147 utmpChanged u' = maybe dead changed u'
148 where
149 changed u' = do
150 jid0 <- maybe (return "") ujid u
151 jid <- ujid u'
152 log $ "changed: " <> jid0 <> " --> " <> jid
153 loop tty tu (Just u')
154 dead = do
155 jid <- maybe (return "") ujid u
156 log $ "Offline " <> jid
157
158logit outvar s = do
159 atomically $ takeTMVar outvar
160 Text.putStrLn s
161 atomically $ putTMVar outvar ()
162
163
164main = do
165 outvar <- atomically $ newTMVar ()
166
167 cs <- newConsoleState
168 inotify <- initINotify
169
170 -- get active tty
171 mtty <- monitorTTY (onTTY outvar cs)
172 atomically $ retryWhen (readTVar $ csActiveTTY cs) (==0)
173
174 -- read utmp
175 onLogin cs (newCon $ logit outvar) Modify
176
177 -- monitor utmp
178 wd <- addWatch
179 inotify
180 [Modify] -- [CloseWrite,Open,Close,Access,Modify,Move]
181 utmp_file
182 (onLogin cs (newCon $ logit outvar))
183
184 waitForTermSignal
185
186 putStrLn "goodbye."