packages feed

libfuse3-0.2.0.0: src/System/LibFuse3/FileStat.hsc

{-# LANGUAGE RecordWildCards #-}
-- | @struct stat@ in Haskell.
module System.LibFuse3.FileStat where

#include <sys/stat.h>

import Foreign (Storable, alloca, castPtr, peek, peekByteOff, pokeByteOff)
import Foreign.C (throwErrnoIfMinus1Retry_)
import System.Clock (TimeSpec)
import System.Posix.Error (throwErrnoPathIfMinus1Retry_)
import System.Posix.Internals (c_fstat, lstat, withFilePath)
import System.Posix.Types
  ( CBlkCnt
  , DeviceID
  , Fd(Fd)
  , FileID
  , FileMode
  , FileOffset
  , GroupID
  , LinkCount
  , UserID
  )

import qualified Foreign

-- | A file status a.k.a. metadata.
--
-- The differences from `System.Posix.Files.FileStatus` are:
--
--   - Is a record type with a `Storable` instance.
--
--   - Has an extra field `blockCount`.
--
--       - An equivalent accessor @fileBlocks@ was added in unix-2.8.0.0, but it is a @Maybe@.
--
--   - Provides an exact representation (`TimeSpec`) of the time fields without converting to `Date.Time.Clock.POSIX.POSIXTime`.
--
--       - This assumes that the @struct stat@ has @st_atim@, @st_mtim@ and @st_ctim@ fields.
--         On Linux this requires Linux >= 2.6.
--
-- @Ptr FileStat@ can be cast to @Ptr `System.Posix.Internals.CStat`@ and vice versa.
--
-- Use `defaultFileStat` and modify its fields you are interested in.
--
-- The @st_ino@ field is ignored unless the @use_ino@ mount option is given.
--
-- The @st_dev@ and @st_blksize@ fields are ignored by libfuse, so not provided.
data FileStat = FileStat
  { -- | Inode number. @st_ino@
    fileID :: FileID
  , -- | File type and mode. @st_mode@
    fileMode :: FileMode
  , -- | Number of hard links. @st_nlink@
    linkCount :: LinkCount
  , -- | User ID of owner. @st_uid@
    fileOwner :: UserID
  , -- | Group ID of owner. @st_gid@
    fileGroup :: GroupID
  , -- | Device ID (if special file). @st_rdev@
    specialDeviceID :: DeviceID
  , -- | Total size, in bytes. @st_size@
    fileSize :: FileOffset
  , -- | Number of 512B blocks allocated. @st_blocks@
    blockCount :: CBlkCnt
  -- these assumes Linux >= 2.6
  , -- | Time of last access. @st_atim@
    accessTimeHiRes :: TimeSpec
  , -- | Time of last modification. @st_mtim@
    modificationTimeHiRes :: TimeSpec
  , -- | Time of last status change. @st_ctim@
    statusChangeTimeHiRes :: TimeSpec
  }
  deriving (Eq, Show)

-- | Targets @struct stat@.
instance Storable FileStat where
  sizeOf _ = #size struct stat

  alignment _ = #alignment struct stat

  peek ptr = do
    fileID     <- (#peek struct stat, st_ino) ptr
    fileMode   <- (#peek struct stat, st_mode) ptr
    linkCount  <- (#peek struct stat, st_nlink) ptr
    fileOwner  <- (#peek struct stat, st_uid) ptr
    fileGroup  <- (#peek struct stat, st_gid) ptr
    specialDeviceID <- (#peek struct stat, st_rdev) ptr
    fileSize   <- (#peek struct stat, st_size) ptr
    blockCount <- (#peek struct stat, st_blocks) ptr
    accessTimeHiRes       <- (#peek struct stat, st_atim) ptr
    modificationTimeHiRes <- (#peek struct stat, st_mtim) ptr
    statusChangeTimeHiRes <- (#peek struct stat, st_ctim) ptr
    pure FileStat{..}

  poke ptr FileStat{..} = do
    (#poke struct stat, st_ino)    ptr fileID
    (#poke struct stat, st_mode)   ptr fileMode
    (#poke struct stat, st_nlink)  ptr linkCount
    (#poke struct stat, st_uid)    ptr fileOwner
    (#poke struct stat, st_gid)    ptr fileGroup
    (#poke struct stat, st_rdev)   ptr specialDeviceID
    (#poke struct stat, st_size)   ptr fileSize
    (#poke struct stat, st_blocks) ptr blockCount
    (#poke struct stat, st_atim)   ptr accessTimeHiRes
    (#poke struct stat, st_mtim)   ptr modificationTimeHiRes
    (#poke struct stat, st_ctim)   ptr statusChangeTimeHiRes

-- | The default value of `FileStat`.
--
-- The Haskell Equivalent of zero-setting C code @{ struct stat st; memset(&st, 0, sizeof(struct stat)); }@.
defaultFileStat :: FileStat
defaultFileStat = FileStat 0 0 0 0 0 0 0 0 0 0 0

-- | Reads a file status of a given file.
--
-- Calls @lstat@.
getFileStat :: FilePath -> IO FileStat
getFileStat path =
  alloca $ \buf ->
    withFilePath path $ \cpath -> do
      throwErrnoPathIfMinus1Retry_ "getFileStat" path (lstat cpath (castPtr buf))
      peek buf

-- | Reads a file status of a given file.
--
-- Calls @fstat@.
getFileStatFd :: Fd -> IO FileStat
getFileStatFd (Fd fd) =
  alloca $ \buf -> do
    throwErrnoIfMinus1Retry_ "getFileStatFd" (c_fstat fd (castPtr buf))
    peek buf