summaryrefslogtreecommitdiff
path: root/dht/Presence/UTmp.hs
diff options
context:
space:
mode:
Diffstat (limited to 'dht/Presence/UTmp.hs')
-rw-r--r--dht/Presence/UTmp.hs259
1 files changed, 259 insertions, 0 deletions
diff --git a/dht/Presence/UTmp.hs b/dht/Presence/UTmp.hs
new file mode 100644
index 00000000..fcfe529a
--- /dev/null
+++ b/dht/Presence/UTmp.hs
@@ -0,0 +1,259 @@
1{-# LANGUAGE TemplateHaskell #-}
2{-# LANGUAGE RankNTypes #-}
3module UTmp
4 ( users
5 , users2
6 , utmp_file
7 , UserName
8 , Tty
9 , ProcessID
10 , UtmpRecord(..)
11 , UT_Type(..)
12 ) where
13
14import qualified Data.ByteString as S
15import qualified Data.ByteString.Char8 as C
16import qualified Data.ByteString.Lazy.Char8 as L
17import Data.BitSyntax
18import Data.Functor.Identity
19import Data.Maybe
20import Data.String
21import System.Posix.Process
22import System.Posix.Signals
23import System.Posix.Types
24import System.Posix.User
25import Control.Monad
26import Data.Word
27import Data.Int
28import Control.Monad.Error.Class
29import System.IO.Error
30import qualified Paths
31import Data.Text ( Text )
32import Unsafe.Coerce ( unsafeCoerce )
33import Network.Socket ( SockAddr(..) )
34import qualified Data.Text.Encoding as Text
35import SockAddr ()
36
37
38utmp_file :: IsString s => s
39utmp_file = fromString $ Paths.utmp -- "/var/run/utmp"
40
41utmp_bs :: IO C.ByteString
42utmp_bs = S.readFile utmp_file
43
44decode_utmp_bytestring ::
45 C.ByteString
46 -> (Word32,
47 Word32,
48 C.ByteString,
49 C.ByteString,
50 C.ByteString,
51 C.ByteString,
52 Word16,
53 Word16,
54 Word32,
55 C.ByteString,
56 Word32,
57 Word32,
58 Word32,
59 Word32)
60decode_utmp_bytestring =
61 runIdentity
62 . $(bitSyn [ UnsignedLE 4 -- type
63 , UnsignedLE 4 -- pid
64 , Fixed 32 -- tty
65 , Fixed 4 -- inittab id
66 , Fixed 32 -- username
67 , Fixed 256 -- remote host
68 , UnsignedLE 2 -- termination status
69 , UnsignedLE 2 -- exit status (int)
70 , UnsignedLE 4 -- session id (int)
71 , Fixed 8 -- time entry was made
72 , Unsigned 4 -- remote addr v6 addr[0]
73 , Unsigned 4 -- remote addr v6 addr[1]
74 , Unsigned 4 -- remote addr v6 addr[2]
75 , Unsigned 4 -- remote addr v6 addr[3]
76 , Skip 20 -- reserved
77 ])
78
79utmp_size :: Int
80utmp_size = 384 -- 768
81
82
83utmp_records :: C.ByteString -> [C.ByteString]
84utmp_records bs | S.length bs >= utmp_size
85 = u:utmp_records us
86 where
87 (u,us) = S.splitAt utmp_size bs
88
89utmp_records bs = [bs]
90
91utmp ::
92 IO
93 [(Word32,
94 Word32,
95 C.ByteString,
96 C.ByteString,
97 C.ByteString,
98 C.ByteString,
99 Word16,
100 Word16,
101 Word32,
102 C.ByteString,
103 Word32,
104 Word32,
105 Word32,
106 Word32)]
107utmp = fmap (map decode_utmp_bytestring . utmp_records) utmp_bs
108
109toStr :: C.ByteString -> [Char]
110toStr = takeWhile (/='\0') . C.unpack
111
112interp_utmp_record ::
113 forall t t1 t2 t3 t4 t5 t6 t7 t8 a.
114 Integral a =>
115 (a,
116 Word32,
117 C.ByteString,
118 t,
119 C.ByteString,
120 C.ByteString,
121 t1,
122 t2,
123 t3,
124 t4,
125 t5,
126 t6,
127 t7,
128 t8)
129 -> (UT_Type, [Char], [Char], CPid, [Char])
130interp_utmp_record (typ,pid,tty,inittab,user,hostv4,term,exit,session,time
131 ,addr0,addr1,addr2,addr3) =
132 ( (toEnum . fromIntegral) typ :: UT_Type
133 , toStr user, toStr tty, processId pid, toStr hostv4 )
134 where
135 processId = CPid . coerceToSigned
136
137coerceToSigned :: Word32 -> Int32
138coerceToSigned = unsafeCoerce
139
140
141data UT_Type
142 = EMPTY -- No valid user accounting information. */
143
144 | RUN_LVL -- The system's runlevel. */
145 | BOOT_TIME -- Time of system boot. */
146 | NEW_TIME -- Time after system clock changed. */
147 | OLD_TIME -- Time when system clock changed. */
148
149 | INIT_PROCESS -- Process spawned by the init process. */
150 | LOGIN_PROCESS -- Session leader of a logged in user. */
151 | USER_PROCESS -- Normal process. */
152 | DEAD_PROCESS -- Terminated process. */
153
154 | ACCOUNTING
155
156 deriving (Enum,Show,Eq,Ord,Read)
157
158processAlive :: ProcessID -> IO Bool
159processAlive pid = do
160 catchError (do { signalProcess nullSignal pid ; return True })
161 $ \e -> do { return (not ( isDoesNotExistError e)); }
162
163type UserName = L.ByteString
164type Tty = L.ByteString
165
166users :: IO [(UserName, Tty, ProcessID)]
167users = utmp_users `catchIOError` \_ -> do
168 -- If we can't read utmp file, then return a list with only the current
169 -- user.
170 uname <- getLoginName
171 pid <- getProcessID -- TODO: XXX: Does this make sense as a fallback?
172 return [(L.pack uname,L.empty,pid)]
173 where
174 utmp_users = fmap (map only3) $ do
175 us <- utmp
176 let us' = map interp_utmp_record us
177 us'' = mapMaybe user_proc us'
178 user_proc (USER_PROCESS, u,tty,pid, hostv4)
179 = Just (L.pack u,L.pack tty,pid,hostv4)
180 user_proc _ = Nothing
181 onThrd f (_,_,pid,_) = f pid
182 us3 <- filterM (onThrd processAlive) us''
183 return us3
184
185only3 :: forall t t1 t2 t3. (t1, t2, t3, t) -> (t1, t2, t3)
186only3 (a,b,c,_) = (a,b,c)
187
188data UtmpRecord = UtmpRecord
189 { utmpType :: UT_Type
190 , utmpUser :: Text
191 , utmpTty :: Text
192 , utmpPid :: CPid
193 , utmpHost :: Text
194 , utmpSession :: Int32
195 , utmpRemoteAddr :: Maybe SockAddr
196 }
197 deriving ( Show, Eq, Ord )
198
199toText :: C.ByteString -> Text
200toText bs = Text.decodeUtf8 $ C.takeWhile (/='\0') bs
201
202interp_utmp_record2 ::
203 forall t t1 t2 t3 a.
204 Integral a =>
205 (a,
206 Word32,
207 C.ByteString,
208 t,
209 C.ByteString,
210 C.ByteString,
211 t1,
212 t2,
213 Word32,
214 t3,
215 Word32,
216 Word32,
217 Word32,
218 Word32)
219 -> UtmpRecord
220interp_utmp_record2 (typ,pid,tty,inittab,user,hostv4
221 ,term,exit,session,time,addr0,addr1,addr2,addr3) =
222 UtmpRecord
223 { utmpType = toEnum (fromIntegral typ) :: UT_Type
224 , utmpUser = toText user
225 , utmpTty = toText tty
226 , utmpPid = processId pid
227 , utmpHost = toText hostv4
228 , utmpSession = coerceToSigned session
229 , utmpRemoteAddr =
230 if all (==0) [addr1,addr2,addr3]
231 then do guard (addr0/=0)
232 Just $ SockAddrInet6 0 0 (0,0,0xFFFF,addr0) 0
233 else Just $ SockAddrInet6 0 0 (addr0,addr1,addr2,addr3) 0
234 }
235 where
236 processId = CPid . coerceToSigned
237
238users2 :: IO [UtmpRecord]
239users2 = do
240 us <- utmp
241 let us' = map interp_utmp_record2 us
242 us3 <- filterM (processAlive . utmpPid) us'
243 return us3
244
245{-
246 - This is how the w command reports idle time:
247/* stat the device file to get an idle time */
248static time_t idletime(const char *restrict const tty)
249{
250 struct stat sbuf;
251 if (stat(tty, &sbuf) != 0)
252 return 0;
253 return time(NULL) - sbuf.st_atime;
254}
255 - THis might be useful fo rimplementing
256 - xep-0012 Last Activity
257 - iq get {jabber:iq:last}query
258 -
259 -}