packages feed

libfuse3-0.2.0.0: example/XAttr.hs

{-# LANGUAGE CApiFFI #-}
module XAttr where

import Control.Monad (when)
import Data.ByteString (ByteString)
import Foreign (Ptr, allocaBytes, castPtr, nullPtr)
import Foreign.C (CInt(CInt), CSize(CSize), CString, peekCStringLen, throwErrnoIfMinus1, throwErrnoIfMinus1_, withCString)
import System.LibFuse3 (SetxattrFlag(SetxattrCreate, SetxattrDefault, SetxattrReplace))
import System.Posix.Types (CSsize(CSsize))

import qualified Data.ByteString as B
import qualified Data.ByteString.Unsafe as BU

foreign import ccall "lgetxattr"
  c_lgetxattr :: CString -> CString -> Ptr () -> CSize -> IO CSsize

foreign import ccall "llistxattr"
  c_llistxattr :: CString -> CString -> CSize -> IO CSsize

foreign import ccall "lremovexattr"
  c_lremovexattr :: CString -> CString -> IO CInt

foreign import ccall "lsetxattr"
  c_lsetxattr :: CString -> CString -> Ptr () -> CSize -> CInt -> IO CInt

foreign import capi "sys/xattr.h value XATTR_CREATE"
  c_XATTR_CREATE :: CInt

foreign import capi "sys/xattr.h value XATTR_REPLACE"
  c_XATTR_REPLACE :: CInt

get :: FilePath -> String -> IO ByteString
get path name =
  withCString path $ \cpath ->
  withCString name $ \cname -> do
    len <- throwErrnoIfMinus1 "lgetxattr" $ c_lgetxattr cpath cname nullPtr 0
    allocaBytes (fromIntegral len) $ \cvalue -> do
      len2 <- throwErrnoIfMinus1 "lgetxattr" $ c_lgetxattr cpath cname cvalue (fromIntegral len)
      -- fail-fast for the sake of simplicity; serious code should retry C calls
      when (len /= len2) $
        fail "The size of the attribute value changed between lgetxattr calls"
      B.packCStringLen (castPtr cvalue, fromIntegral len2)

list :: FilePath -> IO [String]
list path =
  withCString path $ \cpath -> do
    len <- throwErrnoIfMinus1 "llistxattr" $ c_llistxattr cpath nullPtr 0
    allocaBytes (fromIntegral len) $ \cvalue -> do
      len2 <- throwErrnoIfMinus1 "llistxattr" $ c_llistxattr cpath cvalue (fromIntegral len)
      -- fail-fast for the sake of simplicity; serious code should retry C calls
      when (len /= len2) $
        fail "The size of the attribute value changed between llistxattr calls"
      bs <- BU.unsafePackCStringLen (castPtr cvalue, fromIntegral len2)
      -- use peekCStringLen to make sure we use the same encoding as withCString
      traverse (\b -> BU.unsafeUseAsCStringLen b peekCStringLen) $ B.split 0
        -- remove the last byte, which is NUL, to avoid returning an extra empty String
        $ B.init bs

remove :: FilePath -> String -> IO ()
remove path name =
  withCString path $ \cpath ->
  withCString name $ \cname ->
  throwErrnoIfMinus1_ "lremovexattr" $ c_lremovexattr cpath cname

set :: FilePath -> String -> ByteString -> SetxattrFlag -> IO ()
set path name value flags =
  withCString path $ \cpath ->
  withCString name $ \cname ->
  BU.unsafeUseAsCStringLen value $ \(cvalue, csize) ->
  throwErrnoIfMinus1_ "lsetxattr" $ c_lsetxattr cpath cname (castPtr cvalue) (fromIntegral csize) (toCFlags flags)
  where
  toCFlags SetxattrDefault = 0
  toCFlags SetxattrCreate = c_XATTR_CREATE
  toCFlags SetxattrReplace = c_XATTR_REPLACE