packages feed

halfs-2.0: Halfs/File.hs

module Halfs.File
  ( FileHandle (..) -- fhInode, fhReadable, fhWritable)
  , createFile
  , fofReadOnly
  , fofWriteOnly
  , fofReadWrite
  , getFHINR_lckd
  , openFilePrim
  , closeFilePrim
  , removeFile
  )
 where

import Halfs.BlockMap
import Halfs.Classes
import Halfs.Directory
import Halfs.Errors
import Halfs.HalfsState
import Halfs.Inode
import Halfs.Monad
import Halfs.MonadUtils
import Halfs.Protection
import Halfs.Types
import Halfs.Utils

import System.Device.BlockDevice

-- import Debug.Trace

type HalfsM b r l m a = HalfsT HalfsError (Maybe (HalfsState b r l m)) m a

--------------------------------------------------------------------------------
-- File creation and manipulation functions

createFile :: HalfsCapable b t r l m =>
              DirHandle r l
           -> FilePath
           -> UserID
           -> GroupID
           -> FileMode
           -> HalfsM b r l m InodeRef
createFile parentDH fname usr grp mode = do
  dev <- hasks hsBlockDev
  bm  <- hasks hsBlockMap
  mfileIR <- fmap blockAddrToInodeRef `fmap` lift (alloc1 bm)
  case mfileIR of
    Nothing      -> throwError HE_AllocFailed
    Just fileIR -> do
      withDHLock parentDH $ do
        pIR <- getDHINR_lckd parentDH
        n   <- lift $ buildEmptyInodeEnc
                        dev
                        RegularFile
                        mode
                        fileIR
                        pIR
                        usr
                        grp
        lift $ bdWriteBlock dev (inodeRefToBlockAddr fileIR) n 
        addDirEnt_lckd parentDH fname fileIR usr grp mode RegularFile
        numNodesRsc <- hasks hsNumFileNodes
        atomicModifyLockedRscRef numNodesRsc (+1)
        return $ fileIR

removeFile :: HalfsCapable b t r l m =>
              Maybe String -- ^ name to remove from parent directory's content
                           -- map (when Nothing, leaves the parent directory's
                           -- content map alone)
           -> InodeRef     -- ^ inr of file to remove
           -> HalfsM b r l m ()
removeFile mfname inr = do
  case mfname of
    Nothing    -> return ()
    Just fname -> do 
      -- Purge the filename from the parent directory
      dhMap <- hasks hsDHMap
      withLockedRscRef dhMap $ \dhMapRef -> do
        pinr <- atomicReadInode inr inoParent
        pdh  <- lookupRM pinr dhMapRef >>= maybe (newDirHandle pinr) return
        rmDirEnt pdh fname

  -- Decrement link count & register deallocation callback
  hbracket (openFilePrim fofReadOnly inr) closeFilePrim $ \fh -> do
    fhMap <- hasks hsFHMap
    withLockedRscRef fhMap $ \fhMapRef -> do
    atomicModifyInode inr  $ \nd       -> do
      when (inoNumLinks nd == 1) $ do
        -- We're removing the last link, so we inject a callback into the fhmap
        -- that will be invoked when all processes have closed the filehandle
        -- associated with inr.  The callback invalidates the filehandle and
        -- releases its inode.  NB: The callback must assume that both the fhmap
        -- and fh locks are held when it is executed.
        mfhData <- lookupRM inr fhMapRef
        case mfhData of 
          Just (_, c, _) -> insertRM inr (fh, c, cb) fhMapRef
            where cb = invalidateFH fh >> freeInode inr
          Nothing        -> error "fh not found for open file, cannot happen"
      return nd{ inoNumLinks = inoNumLinks nd - 1 }
                         
openFilePrim :: HalfsCapable b t r l m =>
                FileOpenFlags -> InodeRef -> HalfsM b r l m (FileHandle r l)
openFilePrim oflags@FileOpenFlags{ openMode = omode } inr = do
  fhMap <- hasks hsFHMap
  withLockedRscRef fhMap $ \fhMapRef -> do
    mfh <- lookupRM inr fhMapRef
    case mfh of
      Just (fh, c, onFinalClose) -> do
        insertRM inr (fh, c + 1, onFinalClose) fhMapRef
        return fh
      Nothing -> do
        fh <- FH (omode /= WriteOnly) (omode /= ReadOnly) oflags
                `fmap` newRef (Just inr)
                `ap`   newLock
        insertRM inr (fh, 1, invalidateFH fh) fhMapRef
        return fh

closeFilePrim :: HalfsCapable b t r l m =>
                 FileHandle r l
              -> HalfsM b r l m ()
closeFilePrim fh = do
  fhMap <- hasks hsFHMap
  withLock (fhLock fh) $ do 
    withLockedRscRef fhMap $ \fhMapRef -> do
      inr <- getFHINR_lckd fh 
      mfh <- lookupRM inr fhMapRef
      case mfh of
        Nothing -> return ()
  
        -- Remove FH from the map when the current open count is 1, otherwise
        -- just decrement it.  When FH is removed from the map, execute the
        -- 'onFinalClose' callback from the fhmap (which, by default, simply
        -- invalidates the FH, but may also, e.g., deallocate resources for the
        -- file if a function like rmlink needs to defer resource release).  NB:
        -- onFinalClose may assume that both fh and fhmap locks are held!
        --
        -- TODO: We may want to do this on a per-process basis (e.g., by mapping
        -- an inr to a pid -> open count map) to disallow accidental multiple
        -- closes across multiple processes resulting in incorrect open counts.
        -- At the very least, we should work through the ramifications should
        -- this multiple-close scenario occur. This includes dealing with
        -- per-process FH invalidation as well.  E.g., in the current scheme, if
        -- two processes p1 and p2 have the FH and p1 closes it, FH invalidation
        -- does not occur (it would occur only after p1 and p2 closed the FH)
        -- and so p1 can still use the FH which is not intended.
        --
        Just (_, c, onFinalClose)
          | c == 1    -> deleteRM inr fhMapRef >> onFinalClose
          | otherwise -> insertRM inr (fh, c - 1, onFinalClose) fhMapRef

-- Get file handle's inode reference
getFHINR_lckd :: HalfsCapable b t r l m =>
                 FileHandle r l
              -> HalfsM b r l m InodeRef
getFHINR_lckd fh = 
  -- Precond: (fhLock fh) has been acquried (TODO: can we assert this?)
  readRef (fhInode fh) >>= maybe (throwError HE_InvalidFileHandle) return

invalidateFH :: HalfsCapable b t r l m =>
                FileHandle r l
             -> HalfsM b r l m ()
invalidateFH fh = writeRef (fhInode fh) Nothing

fofReadOnly :: FileOpenFlags
fofReadOnly = FileOpenFlags False False ReadOnly

fofWriteOnly, fofReadWrite :: Bool -> FileOpenFlags
fofWriteOnly app = FileOpenFlags app False WriteOnly
fofReadWrite app = FileOpenFlags app False ReadWrite