PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://code.google.com/p/hs-ogl-misc/
Haskell | 351 lines | 219 code | 58 blank | 74 comment | 24 complexity | dc4adc1657c00c02995d291f3e9f4734 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. ------------------------------------------------------------------
  2. -- |
  3. -- Module : System.IO9.DevGen
  4. -- Copyright : (c) Dmitry Golubovsky, 2010
  5. -- License : BSD-style
  6. --
  7. -- Maintainer : golubovsky@gmail.com
  8. -- Stability : experimental
  9. -- Portability : GHC >= 6.12.2
  10. --
  11. --
  12. --
  13. -- Generic Virtual Device Driver Implementation
  14. ------------------------------------------------------------------
  15. -- This module is intended as default implementation of a virtual device driver
  16. -- allowing for unlimited file tree depth, and storing all its objects in memory
  17. -- (ByteStrings). Concrete implementations may reuse code from this module
  18. -- for their needs.
  19. --
  20. -- Virtual device drivers that use disk-backed files are recommended to reuse
  21. -- code from the HostAccess driver.
  22. module System.IO9.DevGen (
  23. DirTab (..)
  24. ,DirEntry (..)
  25. ,DevTop (..)
  26. ,devGen
  27. ,genTopDir
  28. ,genAttach
  29. ,genWalk
  30. ,genStat
  31. ,genSize
  32. ,genUGID
  33. ,genPerm
  34. ,dirTab
  35. ) where
  36. import Data.Char
  37. import Data.List
  38. import Data.Word
  39. import Data.Bits
  40. import Data.NineP
  41. import Data.NineP.Bits
  42. import Data.NineP.Posix
  43. import Data.Maybe
  44. import Data.Either
  45. import Data.IORef
  46. import Control.Monad
  47. import System.IO
  48. import System.FilePath
  49. import Control.Concurrent
  50. import System.IO9.Error
  51. import System.IO9.MemoryStream
  52. import Control.Exception (throw, throwIO)
  53. import System.IO9.DevLayer
  54. import System.Posix.Files
  55. import GHC.IO.Handle
  56. import qualified Data.ByteString as B
  57. import qualified Data.ByteString.UTF8 as C
  58. import qualified Data.Map as M
  59. import qualified Data.IntMap as I
  60. -- | A data type to represent a device file or directory entry. Roughly corresponds
  61. -- to Plan 9' DirTab structure. A File entry contains a 'B.ByteString's as file body
  62. -- (hence no need to keep file length here). A Directory entry contains a 'M.Map'
  63. -- from 'FilePath' to 'Int' to address objects belonging to the directory via the
  64. -- device's toplevel index.
  65. data DirTab = DirTab {
  66. dt_qid :: Qid -- ^ Object Qid
  67. ,dt_owner :: ProcPriv -- ^ Object owner
  68. ,dt_time :: Word32 -- ^ Last modification time, if possible
  69. ,dt_perm :: Word32 -- ^ Object permissions
  70. ,dt_entry :: DirEntry} -- ^ Entry itself
  71. -- | A data type to represent an entry in a device directory.
  72. data DirEntry = EmptyFile -- ^ An entry which is just a placeholder.
  73. -- It is used when initializing the device
  74. -- directory, and also when a concrete driver
  75. -- provides its own handlers for file manipulation.
  76. | MemoryFile !(IORef B.ByteString) -- ^ A memory-backed read-writable file.
  77. | BinConst B.ByteString -- ^ Binary constant (read-only)
  78. | DirMap (M.Map FilePath Int) -- ^ For a directory entry, maintain a map
  79. -- of names into file indices.
  80. | HostFile FilePath -- ^ Host file (must exist and will be open as file)
  81. | HostHandle {hhr :: Maybe Handle -- ^ Host handle for reading
  82. ,hhw :: Maybe Handle} -- ^ Host handle for writing
  83. -- | A type alias for a device top directory. All objects that device has are indexed here.
  84. -- A new object gets index one more than the maximum index in the map, so indices are never
  85. -- reused. Each object's 'Qid' has its 'qid_path' field set to the object index (cast from
  86. -- Int to Word64). The very top level represents a map of device subtrees.
  87. -- Each device method holds a 'MVar' reference to the device top directory in its closure,
  88. -- so access to it is properly serialized.
  89. type DevTop = M.Map FilePath (I.IntMap DirTab)
  90. -- | Given a list of device subtrees and their initial contents, build a device table
  91. -- whose methods are all default for a generic device. Concrete implementation may
  92. -- override some later. Assigning 'qid_path' values is up to the calling program.
  93. -- It is however required that the topmost directory of each subtree had the minimal
  94. -- 'qid_path' value within the whole subtree. Values of 'qid_path' must be unique within
  95. -- a subtree.
  96. devGen :: MVar DevTop -> Char -> IO DevTable
  97. devGen mtop c = do
  98. let devtbl = (defDevTable c) {
  99. devname = "generic"
  100. ,attach_ = genAttach devtbl mtop
  101. ,walk_ = genWalk devtbl mtop
  102. ,open_ = genOpen devtbl mtop
  103. ,stat_ = genStat devtbl mtop}
  104. return devtbl
  105. -- | Build the device toplevel directory and store it in a 'MVar'.
  106. genTopDir :: [(FilePath, [DirTab])]
  107. -> IO (MVar DevTop)
  108. genTopDir t = do
  109. let top = M.fromList (map mktree t)
  110. mktree (fp, dts) = (fp, I.fromList $ zip (map (fromIntegral . qid_path . dt_qid) dts) dts)
  111. newMVar top
  112. -- Methods of the generic device. A concrete implementation will call devGen first,
  113. -- then override any method it needs, calling methods from the default table when needed.
  114. -- Each method of a device derived from this generic driver must take two
  115. -- extra left arguments besides those mentioned in DevLayer definitions: reference to the device
  116. -- table itself (in order to place its reference to any DevAttach it creates), and the MVar
  117. -- pointing to the top directory: it is shared across all methods, and if one method updates it,
  118. -- others can see. All methods should be coded in exception-safe manner in the sense that
  119. -- the MVar pointing to the device top directory should be released even on exception.
  120. -- withMVar and modifyMVar (if modifying the directory) are recommended. In such case a method
  121. -- may just throw an exception or error when needed not worrying about releasing the top directory.
  122. -- | Attach the device at the given subtree. The generic implementation fails if the tree
  123. -- does not exist (was not specified when instantiating the device). Concrete implementation
  124. -- may create subtrees on an ad-hoc basis. Device subtree name should not contain slashes
  125. -- unless it is "/".
  126. genAttach :: DevTable -- ^ Device table to store in the result
  127. -> MVar DevTop -- ^ Mutable reference to the top directory
  128. -> ProcPriv -- ^ Attachment privileges, as come from NS layer
  129. -> FilePath -- ^ Device subtree (should not contain slashes)
  130. -> IO DevAttach -- ^ Result
  131. genAttach tbl mvtop priv tree | '/' `elem` tree && tree /= "/" = throwIO Ebadchar
  132. genAttach tbl mvtop priv tree = withMVar mvtop $ \top -> do
  133. let mbtopdir = M.lookup tree top -- try to find the subtree
  134. case mbtopdir of
  135. Nothing -> throwIO Ebadarg -- does not exist
  136. Just topdir -> do
  137. when (I.null topdir) $ throwIO Enonexist -- subtree was empty
  138. let tddir = snd $ I.findMin topdir -- top dir entry
  139. tdqid = dt_qid tddir
  140. when (qid_typ tdqid .&. c_QTDIR == 0) $ -- check if this is indeed a directory
  141. throwIO Enotdir -- error if not
  142. return DevAttach { devtbl = tbl -- copy the device table
  143. ,devpriv = priv -- copy the attach privileges
  144. ,devqid = tdqid -- QID of the top dir
  145. ,devpath = "/" -- at the subtree root
  146. ,devtree = tree} -- copy subtree name
  147. -- | Walk the device to the desired file or directory.
  148. genWalk :: DevTable -- ^ Device table to store in the result
  149. -> MVar DevTop -- ^ Mutable reference to the top directory
  150. -> DevAttach -- ^ Attachment to the starting directory
  151. -> FilePath -- ^ Destination, relative
  152. -> IO DevAttach -- ^ Attachment descriptor for the destination
  153. genWalk tbl mvtop da fp | isAbsolute fp || isDevice fp || null fp = throwIO Ebadarg
  154. genWalk tbl mvtop da fp = withMVar mvtop $ \top -> do
  155. let mbtopdir = M.lookup (devtree da) top -- check if subtree exists
  156. case mbtopdir of
  157. Nothing -> throwIO Ebadarg -- subtree does not exist
  158. Just topdir -> do
  159. let fpsp = splitPath fp -- split the path into components
  160. (dq, nfp) = onestep topdir
  161. (qid_path $ devqid da)
  162. fpsp
  163. (splitPath $ devpath da) -- go stepwise
  164. return da { devqid = dq -- fill in new Qid
  165. ,devpath = joinPath nfp} -- normalized path
  166. -- Walk one step along the given path.
  167. onestep :: I.IntMap DirTab -> Word64 -> [FilePath] -> [FilePath] -> (Qid, [FilePath])
  168. onestep dmap qpath [] acc = case I.lookup (fromIntegral qpath) dmap of
  169. Nothing -> throw Enonexist
  170. Just dt -> (dt_qid dt, acc)
  171. onestep dmap qpath ("." : fps) acc = onestep dmap qpath fps acc -- skip dot and dot-slash
  172. onestep dmap qpath ("./" : fps) acc = onestep dmap qpath fps acc
  173. onestep dmap qpath (".." : fps) acc = onestep dmap qpath ("../" : fps) acc
  174. onestep dmap qpath ("../" : fps) [] = onestep dmap qpath fps [] -- do not go above subtree root
  175. onestep dmap qpath ("../" : fps) acc = onestep dmap qpath fps (reverse $ tail $ reverse acc)
  176. onestep dmap qpath (fp : fps) acc = case I.lookup (fromIntegral qpath) dmap of
  177. Nothing -> throw Enonexist
  178. Just dt -> case dt_entry dt of
  179. DirMap emp -> case M.lookup (unslash fp) emp of
  180. Nothing -> throw Enonexist
  181. Just idx -> onestep dmap (fromIntegral idx) fps (acc ++ [fp])
  182. _ | null fps -> (dt_qid dt, acc ++ [fp])
  183. _ -> throw Enotdir
  184. unslash fp = reverse (u (reverse fp)) where
  185. u ('/' : x) = x
  186. u z = z
  187. -- | Retrieve a 'Stat' structure for the given attachment descriptor.
  188. genStat :: DevTable -- ^ Device table to store in the result
  189. -> MVar DevTop -- ^ Mutable reference to the top directory
  190. -> DevAttach -- ^ File/directory whose 'Stat' is retrieved
  191. -> IO Stat -- ^ Result
  192. genStat tbl mvtop da = withMVar mvtop $ \top -> do
  193. let mbtopdir = M.lookup (devtree da) top -- check if subtree exists
  194. case mbtopdir of
  195. Nothing -> throwIO Ebadarg -- subtree does not exist
  196. Just topdir -> do
  197. let mbdt = I.lookup (fromIntegral $ qid_path $ devqid da) topdir
  198. dt = fromMaybe (throw Enonexist) mbdt
  199. fname = head $ reverse $ splitPath $ devpath da
  200. ug = genUGID (dt_owner dt)
  201. fsize <- genSize (dt_entry dt)
  202. return Stat {
  203. st_typ = fromIntegral $ ord $ devchar tbl
  204. ,st_dev = 0
  205. ,st_qid = dt_qid dt
  206. ,st_mode = dt_perm dt
  207. ,st_atime = dt_time dt
  208. ,st_mtime = dt_time dt
  209. ,st_length = fsize
  210. ,st_name = fname
  211. ,st_uid = fst ug
  212. ,st_gid = snd ug
  213. ,st_muid = fst ug}
  214. -- | Open a handle for the attachment descriptor provided. The generic device provides
  215. -- opening methods for all entry types except 'MemoryFile'. Concrete implementations
  216. -- must override this method if they use this type of files.
  217. genOpen :: DevTable -- ^ Device table to store in the result
  218. -> MVar DevTop -- ^ Mutable reference to the top directory
  219. -> DevAttach -- ^ File/directory whose 'Stat' is retrieved
  220. -> Word8 -- ^ Open mode
  221. -> IO Handle -- ^ Result
  222. genOpen tbl mvtop da om = withMVar mvtop $ \top -> do
  223. let mbtopdir = M.lookup (devtree da) top -- check if subtree exists
  224. case mbtopdir of
  225. Nothing -> throwIO Ebadarg -- subtree does not exist
  226. Just topdir -> do
  227. let mbdt = I.lookup (fromIntegral $ qid_path $ devqid da) topdir
  228. dt = fromMaybe (throw Enonexist) mbdt
  229. om' = om .&. 3
  230. genPerm (devpriv da) (dt_perm dt) om'
  231. case dt_entry dt of
  232. HostHandle hr hw -> do -- open a host handle. Mode arg selects which one.
  233. let mbh = if om' == c_OREAD then hr else hw
  234. h = fromMaybe (throw Eperm) mbh
  235. hDuplicate h
  236. BinConst bs -> openConstHandle (devpath da) bs
  237. HostFile fp -> do
  238. let iom = omode2IOMode om
  239. openFile fp iom
  240. DirMap mp -> do
  241. let bs = C.fromString $ concatMap (++ "\000") (M.keys mp)
  242. openConstHandle (devpath da) bs -- NB: handle type will be RegularFile
  243. _ -> throwIO $ OtherError "Open method not implemented"
  244. -- | Check if the requested open mode is permissible. Error is thrown if not.
  245. -- The general logic for local drivers/servers: user section corresponds to
  246. -- hostowner rights; world section corresponds to everyone else rights;
  247. -- "none" gets denied any access (servers should use authentication to obtain
  248. -- proper attachment descriptors). Group permissions are ignored. All local
  249. -- files are supposed to be owned by the hostowner whoever the name is.
  250. genPerm :: ProcPriv -> Word32 -> Word8 -> IO ()
  251. genPerm req perm om = do
  252. let rqug = genUGID req
  253. rqu = fst rqug
  254. wrt m | m == c_OREAD = False
  255. | m == c_OEXEC = False
  256. | otherwise = True
  257. rd m | m == c_OREAD = True
  258. | otherwise = False
  259. exe m | m == c_OEXEC = True
  260. | otherwise = False
  261. oread = rqu == "~" && perm .&. (c_DMREAD `shiftL` oShift) /=0
  262. wread = perm .&. (c_DMREAD `shiftL` wShift) /=0
  263. owrite = rqu == "~" && perm .&. (c_DMWRITE `shiftL` oShift) /=0
  264. wwrite = perm .&. (c_DMWRITE `shiftL` gShift) /=0
  265. oexec = rqu == "~" && perm .&. (c_DMEXEC `shiftL` oShift) /=0
  266. wexec = perm .&. (c_DMEXEC `shiftL` wShift) /=0
  267. allow = (wrt om && (owrite || wwrite)) ||
  268. (rd om && (oread || wread)) ||
  269. (exe om && (oexec || wexec))
  270. unless (allow && rqu /= "none") $ throwIO Eperm
  271. return ()
  272. -- | Given the device directory entry file body, determine its size.
  273. genSize :: DirEntry -> IO Word64
  274. genSize (MemoryFile ibs) = readIORef ibs >>= return . fromIntegral . B.length
  275. genSize (BinConst b) = return $ fromIntegral $ B.length b
  276. genSize (HostFile fp) = getFileStatus fp >>= return . fromIntegral . fileSize
  277. genSize _ = return 0
  278. -- | Given the file owner privileges, determine user and group id.
  279. genUGID :: ProcPriv -> (String, String)
  280. genUGID (World u g) = (u, g) -- random non-local user
  281. genUGID None = ("none", "none") -- nobody (server processes run with this)
  282. genUGID _ = ("~", "~") -- hostowner: will be substituted with actual name
  283. -- | A helper function to create a 'DirTab' entry, with zero access time and
  284. -- HostOwner as the owner. The directory bit is determined upon the last 'DirEntry' argument.
  285. dirTab :: Word64 -- ^ Becomes 'qid_path'
  286. -> Word32 -- ^ Initial permissions
  287. -> DirEntry -- ^ Object body
  288. -> DirTab -- ^ Result
  289. dirTab path perm ent =
  290. let qt = case ent of
  291. DirMap _ -> c_QTDIR
  292. _ -> 0
  293. in DirTab (Qid qt 0 path) HostOwner 0 perm ent