PageRenderTime 53ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/io-layer/System/IO9/NameSpaceT.hs

https://code.google.com/p/hs-ogl-misc/
Haskell | 414 lines | 272 code | 58 blank | 84 comment | 9 complexity | b900a7d720b062c8f26d48666bf0e515 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. {-# Language PatternGuards #-}
  2. ------------------------------------------------------------------
  3. -- |
  4. -- Module : System.IO9.NameSpaceT
  5. -- Copyright : (c) Dmitry Golubovsky, 2010
  6. -- License : BSD-style
  7. --
  8. -- Maintainer : golubovsky@gmail.com
  9. -- Stability : experimental
  10. -- Portability : GHC >= 6.12.2
  11. --
  12. --
  13. --
  14. -- NameSpace Layer and Monad Transformer
  15. ------------------------------------------------------------------
  16. module System.IO9.NameSpaceT (
  17. BindFlag (..)
  18. ,NameSpaceT
  19. ,nsInit
  20. ,nsFork
  21. ,nsWait
  22. ,nsBuiltIn
  23. ,dbgPrint
  24. ,dbgChunks
  25. ,PathHandle (phCanon)
  26. ,nsBind
  27. ,nsEval
  28. ,nsCreate
  29. ,nsRemove
  30. ,nsStat
  31. ,nsWstat
  32. ,nsWithText
  33. ,nsWithBin
  34. ,nsEnumText
  35. ,nsEnumBin
  36. ,nsEnumDir
  37. ,nsThrow
  38. ,nsCatch
  39. ,nsFinally
  40. ,nsStdIn
  41. ,nsStdOut
  42. ,Filter (..)
  43. ,Application (..)
  44. ,AppTable (..)
  45. ,AppHandle
  46. ,appTable
  47. ,appEntry
  48. ,nestText
  49. ,nestLines
  50. ,nestBin
  51. ) where
  52. import Data.Bits
  53. import Data.Word
  54. import Data.NineP
  55. import Data.Maybe
  56. import Data.NineP.Bits
  57. import Control.Monad
  58. import Control.Monad.Trans.Class
  59. import Control.Monad.IO.Class
  60. import Control.Monad.Trans.Reader
  61. import Control.Concurrent
  62. import qualified Control.Concurrent.Forkable as F
  63. import System.FilePath
  64. import System.IO9.DevLayer
  65. import System.IO9.Error
  66. import Control.Exception
  67. import Control.Concurrent
  68. import System.Environment
  69. import qualified Data.Map as M
  70. import System.IO9.NameSpace.Monad
  71. import System.IO9.NameSpace.Types
  72. import System.IO9.NameSpace.Util
  73. import System.IO9.NameSpace.Enumerator
  74. import System.IO9.DevCons
  75. import System.IO9.DevApps
  76. import qualified Data.Text as T
  77. import qualified Data.ByteString as B
  78. import qualified Control.Monad.CatchIO as C
  79. import Data.Nesteratee
  80. import Data.Enumerator (run, ($$))
  81. -- | Run the "init" program with the given device list and empty namespace
  82. -- (it is expected that it builds the namespace from scratch). The parent
  83. -- thread handle is set to this thread's handle. Standard input and output
  84. -- are directed to the console. Console device and the builtin applications
  85. -- device are always initialized.
  86. nsInit :: (MonadIO m, C.MonadCatchIO m)
  87. => AppTable m -- ^ Builtin applications table
  88. -> [DevTable] -- ^ User-configurable devices table
  89. -> NameSpaceT m () -- ^ Initialization code
  90. -> m ()
  91. nsInit aptb dts nsi = do
  92. mv <- liftIO $ newMVar (M.empty)
  93. hu <- liftIO logName
  94. cons <- liftIO devCons
  95. apps <- liftIO $ devApps aptb
  96. thr <- liftIO myThreadId
  97. attcons <- liftIO (devAttach cons Init "/" >>= flip devWalk "cons")
  98. let consph = PathHandle {phAttach = attcons, phCanon = "#c/cons", phAdvisory = AdviceAny}
  99. let bids = [cons, apps]
  100. dts' = dts ++ bids
  101. let dvm = M.fromList $ zip (map devchar dts') dts'
  102. env = NsEnv {
  103. hown = hu
  104. ,priv = Init
  105. ,kdtbl = dvm
  106. ,nspace = mv
  107. ,stdinp = consph
  108. ,stdoutp = consph
  109. ,parent = thr
  110. ,runapp = runBuiltIn aptb
  111. }
  112. runNameSpaceT nsi `runReaderT` env
  113. runBuiltIn :: (MonadIO m, C.MonadCatchIO m)
  114. => AppTable m
  115. -> FilePath
  116. -> [Argument]
  117. -> NameSpaceT m NineError
  118. runBuiltIn aptb nbi args = case M.lookup nbi aptb of
  119. Nothing -> return $ Located nbi Enonexist
  120. Just (Monadic appf) -> appf args
  121. Just (TextFilter appf) -> do
  122. appin <- nsStdIn
  123. appout <- nsStdOut
  124. nsWithText appout 0 $ \out -> do
  125. r <- run (nsEnumText appin $$ appf args . nestYield EmptyStatus $ out)
  126. case r of
  127. Left err -> return $ Located nbi $ OtherError $ show err
  128. Right res -> return res
  129. Just (BinFilter appf) -> do
  130. appin <- nsStdIn
  131. appout <- nsStdOut
  132. nsWithBin appout 0 $ \out -> do
  133. r <- run (nsEnumBin 1024 appin $$ appf args . nestYield EmptyStatus $ out)
  134. case r of
  135. Left err -> return $ Located nbi $ OtherError $ show err
  136. Right res -> return res
  137. -- | Run a built-in function by name. Given the application descriptor, retrieve
  138. -- the builtin function name, find the function in the application table, and
  139. -- invoke the function with arguments provided. Usually this is done as the
  140. -- final part of running an application.
  141. nsBuiltIn :: (MonadIO m, C.MonadCatchIO m)
  142. => AppDescr -- ^ Application descriptor
  143. -> [Argument] -- ^ Arguments including redirections
  144. -> NameSpaceT m NineError -- ^ Application result
  145. nsBuiltIn ad args = do
  146. runf <- NameSpaceT $ asks runapp
  147. let nbi = appBuiltIn ad
  148. runf nbi args
  149. -- | Fork a new thread. This is the "privileged" part of running an application: actions
  150. -- allowed at the user level are handled in the 'System.IO9.Application' module,
  151. -- such as building an application descriptor, adjusting redirected path handles, etc.
  152. -- This function sets the relevant parts of the application context and either
  153. -- forks a thread for a new application or continues running in the original thread
  154. -- (the latter is only allowed if running with the 'Init' privileges, and is not to be
  155. -- generally used).
  156. --
  157. -- This function takes an 'AppDescr' (application descriptor) data structure, and
  158. -- runs the supplied application function with type @Monad m => NameSpaceT m NineError@.
  159. -- Note that this is not yet application itself which is supposed to be a 'Nesteratee'
  160. -- in order to access the file I/O. The user supplied code is expected to handle that itself.
  161. --
  162. -- The following rules apply:
  163. --
  164. -- - Jumping to an application is only allowed if the current process privilege level is 'Init'.
  165. --
  166. -- - If the application descriptor requests certain privilege level, it can only be same
  167. -- or lower than the parent thread has. Default ('appPriv' = 'Nothing') corresponds to
  168. -- 'HostOwner' privileges; 'Admin' or 'Init' or 'None' must be requested explicitly.
  169. -- 'World' cannot be requested: this privilege level can appear only in the 'DevAttach'
  170. -- structure.
  171. --
  172. -- - If the application descriptor requests the namespace to be shared, the requested
  173. -- privilege level should be the same as the parent thread has.
  174. --
  175. -- - If the 'AppJump' mode is requested, new thread is not created, and the value returned
  176. -- is an 'AppHandle' representing a completed thread. Namespace may only be shared (in fact,
  177. -- the current thread's environment is just reused).
  178. --
  179. -- If any of the above checks fails, 'nsFork' throws an exception. Execution of the new thread
  180. -- does not even start in such case.
  181. nsFork :: (MonadIO m, F.ForkableMonad m, C.MonadCatchIO m)
  182. => AppDescr -- ^ Application descriptor
  183. -> NameSpaceT m NineError -- ^ To run in the forked thread
  184. -> NameSpaceT m AppHandle -- ^ Returned value
  185. nsFork ad thr = do
  186. let runerr = NameSpaceT . liftIO . throwIO . Located "nsFork"
  187. env <- NameSpaceT $ ask
  188. let ppriv = priv env
  189. ptid <- NameSpaceT $ liftIO myThreadId
  190. case appMode ad of
  191. AppJump -> do
  192. when (ppriv < Init) $ runerr Eperm
  193. thr >>= return . AppCompleted
  194. AppFork -> do
  195. let epriv = case appPriv ad of
  196. Nothing -> HostOwner
  197. Just p -> p
  198. world (World _ _) = True
  199. world _ = False
  200. nshare NsShare = True
  201. nshare _ = False
  202. when (world epriv) $ runerr Ebadarg
  203. when (epriv > ppriv) $ runerr Eperm
  204. when (epriv /= ppriv && nshare (appNsAdjust ad)) $ runerr Ebadarg
  205. let redir x y = case x of
  206. Nothing -> return y
  207. Just p -> nsEval p
  208. appinph <- nsStdIn >>= redir (appStdIn ad)
  209. appoutph <- nsStdOut >>= redir (appStdOut ad)
  210. NameSpaceT $ do
  211. mvwait <- liftIO $ newEmptyMVar
  212. child <- F.forkIO $ lift $ do
  213. newns <- liftIO $ case appNsAdjust ad of
  214. NsBuild _ -> newMVar (M.empty)
  215. NsShare -> return $ nspace env
  216. NsClone -> do
  217. nmv <- newEmptyMVar
  218. omv <- readMVar (nspace env)
  219. putMVar nmv omv
  220. return nmv
  221. let newenv = env {
  222. priv = epriv
  223. ,nspace = newns
  224. ,parent = ptid
  225. ,stdinp = appinph
  226. ,stdoutp = appoutph
  227. }
  228. (runNameSpaceT thr `runReaderT` newenv >>= liftIO . putMVar mvwait) `C.catches`
  229. [ C.Handler (liftIO . putMVar mvwait)
  230. ,C.Handler (\(e :: SomeException) -> liftIO $ putMVar mvwait $ OtherError $ show e)]
  231. return $ AppRunning child mvwait
  232. -- | Given an 'AppHandle', wait/check for application thread completion.
  233. nsWait :: MonadIO m
  234. => Bool -- ^ 'True' to wait, 'False' otherwise
  235. -> AppHandle -- ^ Handle of the application thread
  236. -> NameSpaceT m NineError -- ^ Process status ('StillRunning' or error value)
  237. nsWait _ (AppCompleted e) = return e
  238. nsWait w (AppRunning _ v) = NameSpaceT $ liftIO $ case w of
  239. True -> takeMVar v
  240. False -> tryTakeMVar v >>= return . fromMaybe StillRunning
  241. -- | Bind a path somewhere in the namespace. Both paths should be absolute or device, and will be
  242. -- evaluated. One exception however applies when binding to the "/" old path to the empty
  243. -- namespace, evaluation does not occur provided that the new path is a device path.
  244. -- If any of paths is neither absolute nor device, failure occurs.
  245. nsBind :: MonadIO m
  246. => BindFlag -- ^ Bind options (before, after, create etc.)
  247. -> FilePath -- ^ New path
  248. -> FilePath -- ^ Old path
  249. -> NameSpaceT m () -- ^ No return value, namespace updated under the hood
  250. nsBind _ new old | not ((isAbsolute new || isDevice new) && (isAbsolute old || isDevice old)) =
  251. NameSpaceT $ liftIO $ throwIO $ Located new Efilename
  252. nsBind fl new old | old == "/" && isDevice new = NameSpaceT $ do
  253. mv <- asks nspace
  254. dtb <- asks kdtbl
  255. pv <- asks priv
  256. liftIO $ withNameSpace mv $ \ns -> case M.null ns of
  257. True -> do
  258. let newnorm = normalise new
  259. attnew <- attdev newnorm `runReaderT` (dtb, ns, pv)
  260. let phnew = PathHandle {phAttach = attnew, phCanon = newnorm, phAdvisory = AdviceAny}
  261. ud = unionDir phnew
  262. return $ M.insert old (UnionPoint ud new) ns
  263. False -> bind_common fl new old dtb pv ns
  264. nsBind fl new old = NameSpaceT $ do
  265. mv <- asks nspace
  266. dtb <- asks kdtbl
  267. pv <- asks priv
  268. liftIO $ withNameSpace mv $ bind_common fl new old dtb pv
  269. -- | Evaluate a file path (absolute or device) using the current namespace. The function will try
  270. -- to evaluate the entire path given, so for file creation, strip the last (not-existing-yet) part
  271. -- of the path off. If successful, an attachment descriptor for the path is returned. Otherwise
  272. -- the function fails (e. g. if a device driver returns an error message).
  273. nsEval :: (MonadIO m) => FilePath -> NameSpaceT m PathHandle
  274. nsEval fp | not (isAbsolute fp || isDevice fp) =
  275. NameSpaceT $ liftIO $ throw $ Located fp Efilename
  276. nsEval fp = NameSpaceT $ do
  277. ns <- asks nspace >>= liftIO . readMVar
  278. kd <- asks kdtbl
  279. pv <- asks priv
  280. liftIO $ eval_common fp `runReaderT` (kd, ns, pv)
  281. -- | Create a new file or directory (set 'c_DMDIR' in the @mode@ argument).
  282. -- Creation in an union directory follows the Plan9 semantics by finding the
  283. -- first member of the union that allows creation. The 'FilePath' supplied should not
  284. -- contain slashes, otherwise an error will be thrown.
  285. nsCreate :: MonadIO m
  286. => PathHandle -- ^ Handle of the directory
  287. -> FilePath -- ^ Name of the file or directory to create
  288. -> Word32 -- ^ Creation mode/permissions
  289. -> NameSpaceT m PathHandle -- ^ Handle of the created object
  290. nsCreate dph fp mode | '/' `elem` fp = NameSpaceT $ liftIO $ throwIO $ Located fp Ebadarg
  291. nsCreate dph fp mode = NameSpaceT $ do
  292. when (((qid_typ $ devqid $ phAttach dph) .&. c_QTDIR) == 0) $
  293. liftIO $ throwIO $ Located (show dph) Enotdir
  294. ns <- asks nspace >>= liftIO . readMVar
  295. let un = findunion (phCanon dph) ns
  296. dirs = filter dircr un
  297. dda = case dirs of
  298. [] -> phAttach dph
  299. (d:_) -> phAttach $ dirph d
  300. when (null dirs && not (null un)) $ liftIO $ throwIO $ Located (show dph) Enocreate
  301. da <- liftIO $ devCreate dda fp mode
  302. let newpath = tail $ normalise ("x" ++ phCanon dph ++ "/" ++ fp)
  303. return PathHandle {
  304. phCanon = newpath
  305. ,phAdvisory = AdviceAny
  306. ,phAttach = da}
  307. -- | Remove a file or a directory whose 'PathHandle' is provided. Fails if a non-empty
  308. -- directory is to be removed.
  309. nsRemove :: MonadIO m
  310. => PathHandle -- ^ Handle of the object to be removed
  311. -> NameSpaceT m () -- ^ Nothing is returned
  312. nsRemove ph = NameSpaceT $ liftIO $ devRemove $ phAttach ph
  313. -- | Obtain attributes of a file or directory. Note that for directories,
  314. -- attributes of their server objects will be returned rather than of anything
  315. -- unioned with them.
  316. nsStat :: MonadIO m
  317. => PathHandle -- ^ Handle of the object whose attributes are requested
  318. -> NameSpaceT m Stat -- ^ Result
  319. nsStat ph = NameSpaceT $ do
  320. st <- liftIO $ devStat (phAttach ph)
  321. u <- asks hown
  322. return $ mapUser u st
  323. -- | Change some attributes of a file or directory. See <http://man.cat-v.org/plan_9/5/stat>.
  324. -- If the 'st_name' member of the provided 'Stat' structure contains a slash, error
  325. -- will be thrown.
  326. nsWstat :: (MonadIO m)
  327. => PathHandle -- ^ Handle of the object whose attributes to change
  328. -> Stat -- ^ A 'Stat' structure whose fields specify changes
  329. -> NameSpaceT m PathHandle -- ^ Handle of the same object with updated attrs.
  330. nsWstat ph st | '/' `elem` st_name st = NameSpaceT $
  331. liftIO $ throwIO $ Located (st_name st) Ebadarg
  332. nsWstat ph st = NameSpaceT $ liftIO $ do
  333. nda <- devWstat (phAttach ph) st
  334. nst <- devStat nda
  335. let ncn = replaceFileName (phCanon ph) (st_name nst)
  336. return PathHandle {
  337. phCanon = ncn
  338. ,phAdvisory = phAdvisory ph
  339. ,phAttach = nda}
  340. -- | Create a 'PathHandle' for the standard input (as set in the Namespace environment)
  341. nsStdIn :: (MonadIO m)
  342. => NameSpaceT m PathHandle
  343. nsStdIn = NameSpaceT (asks stdinp)
  344. -- | Create a 'PathHandle' for the standard output (as set in the Namespace environment)
  345. nsStdOut :: (MonadIO m)
  346. => NameSpaceT m PathHandle
  347. nsStdOut = NameSpaceT (asks stdoutp)
  348. -- | A smart constructor for an 'AppTable' making it not necessary to explicitly
  349. -- import "Data.Map". Each application module is expected to provide its own 'AppTable'
  350. appTable :: Monad m => [AppTable m] -> AppTable m
  351. appTable = M.unions
  352. -- | A smart constructor for an individual application exporting one or more entry point.
  353. -- It expects a list of tuples where first elements are application names, and second
  354. -- elements are entry points.
  355. appEntry :: (Monad m) => [(FilePath, Application m)] -> AppTable m
  356. appEntry = M.fromList . filter (not . null . fst)