-- | Init a device manager
initDeviceManager :: Handle -> Handle -> Sys DeviceManager
initDeviceManager sysfs devfs = do
   
   -- open Netlink socket and then duplicate the kernel event channel so that
   -- events start accumulating until we launch the handling thread
   bch <- newKernelEventReader
   ch <- atomically $ dupTChan bch

   -- create empty device manager
   root      <- deviceTreeCreate Nothing Nothing Map.empty
   devNum    <- newTVarIO 0          -- device node counter
   subIndex' <- newTVarIO Map.empty
   tree'     <- newTVarIO root
   sadd      <- newBroadcastTChanIO
   let dm = DeviceManager
               { dmDevices        = tree'
               , dmSubsystems     = subIndex'
               , dmEvents         = bch
               , dmSysFS          = sysfs
               , dmDevFS          = devfs
               , dmDevNum         = devNum
               , dmOnSubsystemAdd = sadd
               }

   -- we enumerate devices from sysfs. Directory listing is non-atomic so
   -- directories may appear or be removed while we do the traversal. Hence we
   -- shouldn't fail on error, just skip the erroneous directories.
   --
   -- After the traversal, kernel events potentially received during the
   -- traversal are used to create/remove nodes. We have to be liberal in their
   -- interpretation: e.g., a remove event could be received for a directory we
   -- haven't been able to read, etc.
   let

      withDevDir hdl path f = withOpenAt hdl path flags BitSet.empty f
      flags = BitSet.fromList [ HandleDirectory
                              , HandleNonBlocking
                              , HandleDontFollowSymLinks
                              ]

      -- read a sysfs device directory and try to create a DeviceTree
      -- recursively from it. The current directory is already opened and the
      -- handle is passed (alongside the name and fullname).
      -- Return Nothing if it fails for any reason.
      readSysfsDir :: Text -> Handle -> FlowT '[] Sys ()
      readSysfsDir path hdl = do
         
         unless (Text.null path) $
            lift (deviceAdd dm path Nothing)

         -- list directories (sub-devices) that are *not* symlinks
         dirs <- (listDirectory hdl
                    -- filter to keep only directories (sysfs fills the type field)
                    ||> filter (\entry -> entryType entry == TypeDirectory)
                    -- only keep the directory name
                    ||> fmap entryName
                 -- return an empty directory list on error
                 ) `catchAllE` (\_ -> success [])

         -- recursively try to create a tree for each sub-dir
         forM_ dirs $ \dir -> do
            let path' = Text.concat [path, Text.pack "/", Text.pack dir]
            withDevDir hdl dir (readSysfsDir path')
               `catchAllE` (\_ -> success ())

         return ()


   -- list devices in /devices
   withDevDir sysfs "devices" (readSysfsDir Text.empty)
      |> evalCatchFlowT (\err -> sysError (textFormat ("Cannot read /devices in sysfs: " % shown) err))
      |> void

   -- launch handling thread
   sysFork "Kernel sysfs event handler" $ eventThread ch dm

   return dm

-- | Thread handling incoming kernel events
initDeviceManager :: Handle -> Handle -> Sys DeviceManager
initDeviceManager sysfs devfs = do
   
   -- open Netlink socket and then duplicate the kernel event channel so that
   -- events start accumulating until we launch the handling thread
   bch <- newKernelEventReader
   ch <- atomically $ dupTChan bch

   -- create empty device manager
   root      <- deviceTreeCreate Nothing Nothing Map.empty
   devNum    <- newTVarIO 0          -- device node counter
   subIndex' <- newTVarIO Map.empty
   tree'     <- newTVarIO root
   sadd      <- newBroadcastTChanIO
   let dm = DeviceManager
               { dmDevices        = tree'
               , dmSubsystems     = subIndex'
               , dmEvents         = bch
               , dmSysFS          = sysfs
               , dmDevFS          = devfs
               , dmDevNum         = devNum
               , dmOnSubsystemAdd = sadd
               }

   -- we enumerate devices from sysfs. Directory listing is non-atomic so
   -- directories may appear or be removed while we do the traversal. Hence we
   -- shouldn't fail on error, just skip the erroneous directories.
   --
   -- After the traversal, kernel events potentially received during the
   -- traversal are used to create/remove nodes. We have to be liberal in their
   -- interpretation: e.g., a remove event could be received for a directory we
   -- haven't been able to read, etc.
   let

      withDevDir hdl path f = withOpenAt hdl path flags BitSet.empty f
      flags = BitSet.fromList [ HandleDirectory
                              , HandleNonBlocking
                              , HandleDontFollowSymLinks
                              ]

      -- read a sysfs device directory and try to create a DeviceTree
      -- recursively from it. The current directory is already opened and the
      -- handle is passed (alongside the name and fullname).
      -- Return Nothing if it fails for any reason.
      readSysfsDir :: Text -> Handle -> FlowT '[] Sys ()
      readSysfsDir path hdl = do
         
         unless (Text.null path) $
            lift (deviceAdd dm path Nothing)

         -- list directories (sub-devices) that are *not* symlinks
         dirs <- (listDirectory hdl
                    -- filter to keep only directories (sysfs fills the type field)
                    ||> filter (\entry -> entryType entry == TypeDirectory)
                    -- only keep the directory name
                    ||> fmap entryName
                 -- return an empty directory list on error
                 ) `catchAllE` (\_ -> success [])

         -- recursively try to create a tree for each sub-dir
         forM_ dirs $ \dir -> do
            let path' = Text.concat [path, Text.pack "/", Text.pack dir]
            withDevDir hdl dir (readSysfsDir path')
               `catchAllE` (\_ -> success ())

         return ()


   -- list devices in /devices
   withDevDir sysfs "devices" (readSysfsDir Text.empty)
      |> evalCatchFlowT (\err -> sysError (textFormat ("Cannot read /devices in sysfs: " % shown) err))
      |> void

   -- launch handling thread
   sysFork "Kernel sysfs event handler" $ eventThread ch dm

   return dm

-- | Thread handling incoming kernel events
initDeviceManager sysfs devfs = do
   
   -- open Netlink socket and then duplicate the kernel event channel so that
   -- events start accumulating until we launch the handling thread
   bch <- newKernelEventReader
   ch <- atomically $ dupTChan bch

   -- create empty device manager
   root      <- deviceTreeCreate Nothing Nothing Map.empty
   devNum    <- newTVarIO 0          -- device node counter
   subIndex' <- newTVarIO Map.empty
   tree'     <- newTVarIO root
   sadd      <- newBroadcastTChanIO
   let dm = DeviceManager
               { dmDevices        = tree'
               , dmSubsystems     = subIndex'
               , dmEvents         = bch
               , dmSysFS          = sysfs
               , dmDevFS          = devfs
               , dmDevNum         = devNum
               , dmOnSubsystemAdd = sadd
               }

   -- we enumerate devices from sysfs. Directory listing is non-atomic so
   -- directories may appear or be removed while we do the traversal. Hence we
   -- shouldn't fail on error, just skip the erroneous directories.
   --
   -- After the traversal, kernel events potentially received during the
   -- traversal are used to create/remove nodes. We have to be liberal in their
   -- interpretation: e.g., a remove event could be received for a directory we
   -- haven't been able to read, etc.
   let

      withDevDir hdl path f = withOpenAt hdl path flags BitSet.empty f
      flags = BitSet.fromList [ HandleDirectory
                              , HandleNonBlocking
                              , HandleDontFollowSymLinks
                              ]

      -- read a sysfs device directory and try to create a DeviceTree
      -- recursively from it. The current directory is already opened and the
      -- handle is passed (alongside the name and fullname).
      -- Return Nothing if it fails for any reason.
      readSysfsDir :: Text -> Handle -> FlowT '[] Sys ()
      readSysfsDir path hdl = do
         
         unless (Text.null path) $
            lift (deviceAdd dm path Nothing)

         -- list directories (sub-devices) that are *not* symlinks
         dirs <- (listDirectory hdl
                    -- filter to keep only directories (sysfs fills the type field)
                    ||> filter (\entry -> entryType entry == TypeDirectory)
                    -- only keep the directory name
                    ||> fmap entryName
                 -- return an empty directory list on error
                 ) `catchAllE` (\_ -> success [])

         -- recursively try to create a tree for each sub-dir
         forM_ dirs $ \dir -> do
            let path' = Text.concat [path, Text.pack "/", Text.pack dir]
            withDevDir hdl dir (readSysfsDir path')
               `catchAllE` (\_ -> success ())

         return ()


   -- list devices in /devices
   withDevDir sysfs "devices" (readSysfsDir Text.empty)
      |> evalCatchFlowT (\err -> sysError (textFormat ("Cannot read /devices in sysfs: " % shown) err))
      |> void

   -- launch handling thread
   sysFork "Kernel sysfs event handler" $ eventThread ch dm

   return dm

-- | Thread handling incoming kernel events