PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/dead-code/DevPosix.hs

https://code.google.com/p/hs-ogl-misc/
Haskell | 400 lines | 295 code | 58 blank | 47 comment | 29 complexity | 56daa36730e6b48fe58dcc96869872aa MD5 | raw file
Possible License(s): BSD-3-Clause
  1. ------------------------------------------------------------------
  2. -- |
  3. -- Module : System.IO9.Devices.DevPosix
  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. -- Implementation of virtual device to access the host filesystem
  14. ------------------------------------------------------------------
  15. module System.IO9.Devices.DevPosix (
  16. devPosix
  17. ) where
  18. import Data.Word
  19. import Data.Bits
  20. import Data.List
  21. import Data.Maybe
  22. import Data.Binary.Put
  23. import Data.Binary.Get
  24. import Data.Either.Unwrap
  25. import System.FilePath
  26. import System.Directory
  27. import System.IO9.Device
  28. import System.IO9.Error
  29. import System.Posix.Types
  30. import System.Posix.Files
  31. import System.Posix.User
  32. import System.Posix.IO
  33. import System.IO
  34. import GHC.IO.Device
  35. import Control.Monad
  36. import Control.Concurrent
  37. import qualified Data.ByteString.Lazy as B
  38. import qualified Control.Exception as E
  39. import qualified Data.Map as M
  40. -- | Initialization of the device. The function returns a thunk holding a host
  41. -- path that becomes a "root" of the device. If the path does not exist, the function
  42. -- fails.
  43. devPosix :: Bool -- ^ True if device connection can be shared between threads
  44. -> FilePath -- ^ Host path to access via this device (preferrably
  45. -- absolute, but will be canonicalized)
  46. -> IO Device9P -- ^ Function implementing the device
  47. devPosix shr fp = do
  48. cfp <- canonicalizePath fp
  49. ex <- doesDirectoryExist cfp
  50. case ex of
  51. True -> return $ dpvers $ newdata shr cfp
  52. False -> return $ devError $ "Directory " ++ fp ++ "does not exist"
  53. -- Device internal data.
  54. data DevPosix = DevPosix {
  55. hfp :: FilePath -- root of the host file path
  56. ,fidmap :: M.Map Word32 FilePath -- map of FIDs to actual paths
  57. ,openmap :: M.Map Word32 (Word8, Either Handle [FilePath]) -- map of FIDs currently open
  58. ,uname :: String -- name of the (authenticated) user
  59. ,thrid :: Maybe ThreadId -- ID of the thread that attached
  60. ,devshr :: Bool -- Device connection can be shared
  61. }
  62. -- Initialize device data.
  63. newdata :: Bool -> FilePath -> DevPosix
  64. newdata shr fp = DevPosix fp M.empty M.empty "" Nothing shr
  65. -- Process the "Version" message, then the "Auth" message. Any other
  66. -- sequence results in error.
  67. -- Version: ignore the message size for now, but only proceed if the protocol version
  68. -- is "9P2000". Any message other than "Version" results in an error, but the same
  69. -- device connection may be reused for protocol version negotiation again.
  70. dpvers :: DevPosix -> Device9P
  71. dpvers devd msg@(Msg TTversion tg tv@Tversion {}) | tv_version tv == "9P2000" =
  72. return $ Resp9P (msg {msg_typ = TRversion}) (dpatch devd)
  73. dpvers devd msg = return $ Resp9P (errorMsg (msg_tag msg) $
  74. "Protocol negotiation failure" ++ show msg)
  75. (dpvers devd)
  76. -- Attach: afid is ignored. Fid and aname are written into the fidmap (aname is
  77. -- prefixed with the stored root filepath, canonicalized, and checked to be within the root).
  78. -- Uname as well as the current thread ID are stored in the device data.
  79. -- Uname is currently ignored by this implementation. Note that aname may also represent
  80. -- a regular file. If aname points to a non-existent file or directory, the operation
  81. -- fails, but the device remains in negotiated state.
  82. dpatch :: DevPosix -> Device9P
  83. dpatch devd msg@(Msg TTattach tg ta@Tattach {}) = do
  84. let norm = normalise (hfp devd ++ "/" ++ tat_aname ta)
  85. tree <- canonicalizePath norm
  86. ex <- fileExist tree
  87. case ex && isSubdir (hfp devd) tree of
  88. False -> return $ Resp9P (errorMsg (msg_tag msg) $ "Invalid or non-existent path " ++
  89. tat_aname ta) (dpatch devd)
  90. True -> do
  91. let fidmap' = M.insert (tat_fid ta) tree (fidmap devd)
  92. stat <- getFileStatus tree
  93. let qid = stat2qid stat
  94. tid <- myThreadId
  95. return $ Resp9P (Msg TRattach tg (Rattach qid))
  96. (dpacc devd {
  97. fidmap = fidmap'
  98. ,uname = tat_uname ta
  99. ,thrid = Just tid})
  100. -- This device does not require authentication.
  101. dpatch devd msg@(Msg TTauth tg ta) =
  102. return $ Resp9P (errorMsg (msg_tag msg) $ "Authentication not reauired")
  103. (dpvers $ newdata (devshr devd) (hfp devd))
  104. -- Any message other than Auth throws a unnegotiated device entry to the client.
  105. dpatch devd msg = return $ Resp9P (errorMsg (msg_tag msg) $ "Not attached")
  106. (dpvers $ newdata (devshr devd) (hfp devd))
  107. -- At this point we can accept walk/open/read/write/etc. messages. If the "shared" flag
  108. -- is True, thread ID is not checked, and any thread may reuse the authenticated connection.
  109. -- If it is False, and a request comes from the thread other authenticated thread, a fresh
  110. -- unnegotiated device is returned. Messages like Version and Auth are not accepted at this point.
  111. -- They will return a unnegotiated or unauthenticated device respectively.
  112. -- The Flush message has no action as all operations are synchronous.
  113. dpacc :: DevPosix -> Device9P
  114. dpacc devd msg | msg_typ msg == TTversion = dpvers (newdata (devshr devd) (hfp devd)) msg
  115. dpacc devd msg | msg_typ msg == TTauth = dpatch (newdata (devshr devd) (hfp devd)) msg
  116. dpacc devd msg | msg_typ msg == TTflush =
  117. return $ Resp9P (msg {msg_typ = TRflush, msg_body = Rflush}) (dpacc devd)
  118. dpacc devd msg = do
  119. let emsg x = return $ Resp9P (errorMsg (msg_tag msg) x) (dpacc devd)
  120. tid <- myThreadId
  121. if (devshr devd) && isJust (thrid devd) && (tid /= fromJust (thrid devd))
  122. then dpvers (newdata (devshr devd) (hfp devd)) msg
  123. else case (msg_typ msg, msg_body msg) of
  124. (TTstat, Tstat {}) -> do -- if file/dir does not exist, return an error message
  125. let sfid = ts_fid $ msg_body msg
  126. spath = M.lookup sfid $ fidmap devd
  127. case spath of
  128. Nothing -> emsg $ "Incorrect fid: " ++ show sfid
  129. Just sfp -> do
  130. ex <- fileExist sfp -- file could have disappeared
  131. case ex of
  132. False -> emsg $ "File/directory does not exist: " ++ sfp
  133. True -> do
  134. st <- getFileStatus sfp
  135. let fname = if normalise (sfp ++ "/") == normalise (hfp devd ++ "/")
  136. then "/"
  137. else head . reverse $ splitPath sfp
  138. ret <- stat2stat st fname
  139. return $ Resp9P (msg {msg_typ = TRstat, msg_body = Rstat [ret]}) (dpacc devd)
  140. (TTclunk, Tclunk {}) -> do
  141. let clfid = tcl_fid $ msg_body msg
  142. rclunk dv = return $ Resp9P (msg {msg_typ = TRclunk, msg_body = Rclunk}) dv
  143. if clfid == c_NOFID -- this special value causes all FIDs to be clunked,
  144. then do -- and the device returned to the unauthenticated state
  145. let fids = M.keys (fidmap devd)
  146. mapM_ (clunk devd) fids
  147. rclunk . dpvers $ newdata (devshr devd) (hfp devd)
  148. else do
  149. devd' <- clunk devd clfid
  150. rclunk . dpacc $ devd'
  151. (TTwalk, Twalk {}) -> do
  152. let twfid = tw_fid $ msg_body msg
  153. twnfid = tw_newfid $ msg_body msg
  154. twnames = tw_wnames $ msg_body msg
  155. twpath = M.lookup twfid $ fidmap devd
  156. twex = isJust twpath
  157. twopen = M.member twfid $ openmap devd
  158. twnused = M.member twnfid $ fidmap devd
  159. difffid = twfid /= twnfid
  160. rwalk q d = return $ Resp9P (msg {msg_typ = TRwalk, msg_body = Rwalk q}) (dpacc d)
  161. case (twex, twopen, twnused && difffid) of
  162. (False, _, _) -> emsg $ "Fid is invalid: " ++ show twfid
  163. (True, True, _) -> emsg $ "Fid is open: " ++ show twfid
  164. (True, False, True) -> emsg $ "New fid " ++ show twnfid ++ " is in use"
  165. (True, False, False) -> do
  166. (rpth, res) <- walk (hfp devd) (fromJust twpath) twnames []
  167. let fidmap' = M.insert twnfid rpth (fidmap devd)
  168. case (length twnames, length res) of
  169. (0, 1) -> rwalk res (devd {fidmap = fidmap'})
  170. (_, 1) -> emsg $ show Enonexist
  171. (m, n) | n == m + 1 -> rwalk (tail res) (devd {fidmap = fidmap'})
  172. _ -> rwalk (tail res) devd
  173. (TTopen, Topen ofid omode) -> do -- for directory, just store its contents
  174. let opath = M.lookup ofid $ fidmap devd
  175. case opath of
  176. Nothing -> emsg $ "Incorrect fid: " ++ show ofid
  177. Just ofp -> do
  178. ex <- fileExist ofp -- file could have disappeared
  179. case ex of
  180. False -> emsg $ "File/directory does not exist: " ++ ofp
  181. True -> do
  182. let dot "." = True
  183. dot ".." = True
  184. dot _ = False
  185. st <- getFileStatus ofp
  186. oval <- if isDirectory st
  187. then getDirectoryContents ofp >>=
  188. return . filter (not . dot) >>=
  189. return . Right
  190. else openFd ofp (mod2mod $ omode .&. 3) Nothing (mod2flg omode) >>=
  191. fdToHandle >>=
  192. return . Left
  193. let openmap' = M.insert ofid (omode, oval) (openmap devd)
  194. oqid = stat2qid st
  195. devd' = devd {openmap = openmap'}
  196. return $ Resp9P msg {msg_typ = TRopen, msg_body = Ropen oqid 0} (dpacc devd')
  197. (TTread, Tread rfid roff rcnt) -> do -- directory contents always reads entirely
  198. let rpath = M.lookup rfid $ fidmap devd
  199. rval = M.lookup rfid $ openmap devd
  200. rread b = return $ Resp9P (msg {msg_typ = TRread, msg_body = Rread b}) (dpacc devd)
  201. case rpath of
  202. Nothing -> emsg $ "Incorrect fid: " ++ show rfid
  203. Just rfp -> do
  204. ex <- fileExist rfp -- file could have disappeared
  205. case ex of
  206. False -> emsg $ show Enonexist
  207. True -> case rval of
  208. Nothing -> emsg $ "Fid " ++ show rfid ++ " was not open"
  209. Just (m, Left h) | m .&. 3 /= c_OWRITE -> do
  210. hSeek h AbsoluteSeek (fromIntegral roff)
  211. B.hGet h (fromIntegral rcnt) >>= rread
  212. Just (m, Right fps) | m .&. 3 == c_OREAD && roff == 0 -> do
  213. let rfps = map (rfp </>) fps
  214. fstz f = (getFileStatus f >>= \s -> return [(s, f)]) `catch`
  215. (\_ -> return [])
  216. sts <- mapM fstz rfps >>= return . concat
  217. nsts <- zipWithM stat2stat (map fst sts) (map (snd . splitFileName . snd) sts)
  218. let bs = map (runPut . put) nsts -- get Stat for each file
  219. cbs = B.concat bs -- serialize each Stat and concat
  220. rread cbs -- send whatever results from concatenation
  221. _ -> emsg $ "Incorrect fid mode: " ++ show rfid
  222. (TTwrite, Twrite wfid woff wdat) -> do -- directories cannot be written
  223. let wpath = M.lookup wfid $ fidmap devd
  224. wval = M.lookup wfid $ openmap devd
  225. rwrite b = return $ Resp9P (msg {msg_typ = TRwrite, msg_body = Rwrite b}) (dpacc devd)
  226. case wpath of
  227. Nothing -> emsg $ "Incorrect fid: " ++ show wfid
  228. Just wfp -> do
  229. ex <- fileExist wfp -- file could have disappeared
  230. case ex of
  231. False -> emsg $ show Enonexist
  232. True -> case wval of
  233. Nothing -> emsg $ "Fid " ++ show wfid ++ " was not open"
  234. Just (m, Left h) -> do -- append-only files not supported, always write at woff
  235. hSeek h AbsoluteSeek (fromIntegral woff)
  236. B.hPut h wdat >> hFlush h >> rwrite (fromIntegral $ B.length wdat)
  237. _ -> emsg $ show Eisdir
  238. _ -> emsg $ "Incorrect message " ++ show msg
  239. -- Clunk one fid, update the internal data as needed.
  240. clunk :: DevPosix -> Word32 -> IO DevPosix
  241. clunk devd clfid = do
  242. let clmode = M.lookup clfid $ openmap devd
  243. clpath = M.lookup clfid $ fidmap devd
  244. (rm, cl) = case clmode of
  245. Nothing -> (False, False)
  246. Just (m, mbfd) -> ((m .&. c_ORCLOSE) /= 0, isLeft mbfd)
  247. openmap' = M.delete clfid $ openmap devd
  248. fidmap' = M.delete clfid $ fidmap devd
  249. devd' = devd {openmap = openmap', fidmap = fidmap'}
  250. when cl $ hClose (fromLeft . snd . fromJust $ clmode)
  251. when (rm && isJust clpath && fromJust clpath /= hfp devd) $ removeLink (fromJust clpath)
  252. return devd'
  253. -- Walk the given path from the base step by step.
  254. walk :: FilePath -> FilePath -> [FilePath] -> [Qid] -> IO (FilePath, [Qid])
  255. walk root base fps fpqs = do
  256. nbase <- canonicalizePath base
  257. ex <- fileExist nbase
  258. case ex of
  259. False -> return ("", fpqs)
  260. True -> do
  261. let nbase' = case isSubdir root nbase of
  262. True -> nbase
  263. False -> root
  264. stat <- getFileStatus nbase'
  265. let qid = stat2qid stat
  266. nxt = fpqs ++ [qid]
  267. case fps of
  268. [] -> return (nbase', nxt)
  269. fph:fpt -> walk root (nbase' </> fph) fpt nxt
  270. -- Convert a 9P2000 open mode to Posix open mode.
  271. mod2mod :: Word8 -> OpenMode
  272. mod2mod omode | omode == c_OREAD = ReadOnly
  273. | omode == c_OWRITE = WriteOnly
  274. | omode == c_ORDWR = ReadWrite
  275. | otherwise = error $ "Incorrect open mode: " ++ show omode
  276. -- Convert a 9P2000 open mode to Posix open flags (in fact only O_TRUNC is affected).
  277. mod2flg :: Word8 -> OpenFileFlags
  278. mod2flg omode | (omode .&. c_OTRUNC) /= 0 = defaultFileFlags {trunc = True}
  279. | otherwise = defaultFileFlags
  280. -- Check that the path2 is a subdirectory of path1 (or equal to path1).
  281. isSubdir p1 p2 | equalFilePath p1 p2 = True
  282. isSubdir p1 p2 =
  283. let sp1 = splitPath p1
  284. sp2 = splitPath p2
  285. in p1 `isPrefixOf` p2
  286. -- Build a Qid from file status.
  287. stat2qid :: FileStatus -> Qid
  288. stat2qid stat =
  289. let isdir = isDirectory stat
  290. inode = fileID stat
  291. ctime = modificationTime stat
  292. qid = Qid {
  293. qid_typ = if isdir then c_QTDIR else 0
  294. ,qid_vers = round(realToFrac ctime)
  295. ,qid_path = fromIntegral inode
  296. }
  297. in qid
  298. -- Build a filemode mask in terms of the 9P definition.
  299. stat2mode :: FileStatus -> Word32
  300. stat2mode st =
  301. let umode = fileMode st
  302. oshift = 6
  303. gshift = 3
  304. wshift = 0
  305. permmap = [(ownerReadMode, c_DMREAD `shiftL` oshift)
  306. ,(ownerWriteMode, c_DMWRITE `shiftL` oshift)
  307. ,(ownerExecuteMode, c_DMEXEC `shiftL` oshift)
  308. ,(groupReadMode, c_DMREAD `shiftL` gshift)
  309. ,(groupWriteMode, c_DMWRITE `shiftL` gshift)
  310. ,(groupExecuteMode, c_DMEXEC `shiftL` gshift)
  311. ,(otherReadMode, c_DMREAD `shiftL` wshift)
  312. ,(otherWriteMode, c_DMWRITE `shiftL` wshift)
  313. ,(otherExecuteMode, c_DMEXEC `shiftL` wshift)
  314. ,(directoryMode, c_DMDIR)]
  315. nmode = foldl mbit 0 permmap
  316. mbit acc (umb, nmb) = case umb .&. umode of
  317. 0 -> acc
  318. _ -> acc .|. nmb
  319. in nmode
  320. -- Convert a Unix stat record to 9P2000 stat record.
  321. stat2stat :: FileStatus -> FilePath -> IO Stat
  322. stat2stat st fname = do
  323. funame <- (getUserEntryForID (fileOwner st) >>= return . userName) `catch`
  324. (\_ -> return . show $ fileOwner st)
  325. fgroup <- (getGroupEntryForID (fileGroup st) >>= return . groupName) `catch`
  326. (\_ -> return . show $ fileGroup st)
  327. let qid = stat2qid st
  328. mode = stat2mode st
  329. ret = Stat {
  330. st_typ = 0 -- these are not filled in by the driver, but
  331. ,st_dev = 0 -- rather by the surrounding framework
  332. ,st_qid = qid
  333. ,st_mode = mode
  334. ,st_atime = round $ realToFrac $ accessTime st
  335. ,st_mtime = round $ realToFrac $ modificationTime st
  336. ,st_length = fromIntegral $ fileSize st
  337. ,st_name = fname
  338. ,st_uid = funame
  339. ,st_gid = fgroup
  340. ,st_muid = funame
  341. }
  342. return ret