packages feed

libfuse3-0.2.0.1: example/passthrough.hs

{-# LANGUAGE CPP #-}
-- |
-- Copyright : (The original C)   2001-2007  Miklos Szeredi <miklos@szeredi.hu>
--                                2011       Sebastian Pipping <sebastian@pipping.org>
--             (The Haskell port) 2020 yohashi
-- License   : GPL-2
--
-- This file system mirrors the existing file system hierarchy of the
-- system, starting at the root file system. This is implemented by
-- just "passing through" all requests to the corresponding user-space
-- libc functions. Its performance is terrible.
--
-- This is a port of the C program distributed with the official libfuse.
-- See \"example/passthrough.c\" in the distribution.
module Main where

import CLoff
import Control.Exception (SomeException, bracket)
import Data.ByteString (ByteString)
import Data.Function (fix)
import Data.Time.Clock.POSIX (POSIXTime)
import Data.Void (Void)
import Foreign (Ptr, with)
import Foreign.C (CInt(CInt), CSize(CSize), CUInt(CUInt), Errno(Errno), eIO, eOK, eOPNOTSUPP)
import System.IO (SeekMode, hPrint, stderr)
import System.LibFuse3
import System.Posix.Directory (closeDirStream, createDirectory, openDirStream, readDirStream, removeDirectory)
import System.Posix.Files (createDevice, createLink, createNamedPipe, createSymbolicLink, readSymbolicLink, removeLink, rename, setFdSize, setFileCreationMask, setFileMode, setFileSize, setSymbolicLinkOwnerAndGroup, setSymbolicLinkTimesHiRes)
import System.Posix.IO (OpenFileFlags, OpenMode(WriteOnly), closeFd, defaultFileFlags, exclusive, fdSeek, openFd)
import System.Posix.Types (ByteCount, COff(COff), CSsize(CSsize), DeviceID, Fd(Fd), FileMode, FileOffset, GroupID, UserID)

import qualified System.LibFuse3.FuseConfig as FuseConfig
import qualified XAttr

#if MIN_VERSION_unix(2,8,0)
import System.Posix.IO (creat)
#endif

openFdCompat :: FilePath -> OpenMode -> Maybe FileMode -> OpenFileFlags -> IO Fd
#if MIN_VERSION_unix(2,8,0)
openFdCompat path openMode mfileMode openFileFlags = openFd path openMode (openFileFlags{creat = mfileMode})
#else
openFdCompat = openFd
#endif

foreign import ccall "posix_fallocate"
  c_posix_fallocate :: CInt -> COff -> COff -> IO CInt

foreign import ccall "copy_file_range"
  c_copy_file_range :: CInt -> Ptr CLoff -> CInt -> Ptr CLoff -> CSize -> CUInt -> IO CSsize

xmpInit :: FuseConfig -> IO FuseConfig
xmpInit cfg = pure $ cfg
  { FuseConfig.useIno = True
  -- Pick up changes from lower filesystem right away. This is
  -- also necessary for better hardlink support. When the kernel
  -- calls the unlink() handler, it does not know the inode of
  -- the to-be-removed entry and can therefore not invalidate
  -- the cache of the associated inode - resulting in an
  -- incorrect st_nlink value being reported for any remaining
  -- hardlinks to this inode.
  , FuseConfig.entryTimeout = 0
  , FuseConfig.attrTimeout = 0
  , FuseConfig.negativeTimeout = 0
  }

xmpGetattr :: FilePath -> IO (Either Errno FileStat)
xmpGetattr = tryErrno . getFileStat

xmpAccess :: FilePath -> AccessMode -> IO Errno
xmpAccess = accessErrno

xmpReadlink :: FilePath -> IO (Either Errno FilePath)
xmpReadlink = tryErrno . readSymbolicLink

xmpReaddir :: FilePath -> IO (Either Errno [(String, Maybe FileStat)])
xmpReaddir path = tryErrno
  $ bracket (openDirStream path) closeDirStream
  $ \dp -> fmap reverse $ flip fix []
  $ \loop acc -> do
    entry <- readDirStream dp
    if null entry
      then pure acc
      else loop $ (entry, Nothing) : acc

xmpMknod :: FilePath -> FileMode -> DeviceID -> IO Errno
xmpMknod path mode rdev = tryErrno_ $ case fileModeToEntryType mode of
  RegularFile -> bracket (openFdCompat path WriteOnly (Just mode) (defaultFileFlags{exclusive=True})) closeFd (\_ -> pure ())
  Directory -> createDirectory path mode
  NamedPipe -> createNamedPipe path mode
  _ -> createDevice path mode rdev

xmpMkdir :: FilePath -> FileMode -> IO Errno
xmpMkdir path mode = tryErrno_ $ createDirectory path mode

xmpUnlink :: FilePath -> IO Errno
xmpUnlink = tryErrno_ . removeLink

xmpRmdir :: FilePath -> IO Errno
xmpRmdir = tryErrno_ . removeDirectory

xmpSymlink :: FilePath -> FilePath -> IO Errno
xmpSymlink from to = tryErrno_ $ createSymbolicLink from to

xmpRename :: FilePath -> FilePath -> IO Errno
xmpRename from to = tryErrno_ $ rename from to

xmpLink :: FilePath -> FilePath -> IO Errno
xmpLink from to = tryErrno_ $ createLink from to

xmpChmod :: FilePath -> FileMode -> IO Errno
xmpChmod path mode = tryErrno_ $ setFileMode path mode

xmpChown :: FilePath -> UserID -> GroupID -> IO Errno
xmpChown path uid gid = tryErrno_ $ setSymbolicLinkOwnerAndGroup path uid gid

xmpTruncate :: FilePath -> Maybe Fd -> FileOffset -> IO Errno
xmpTruncate path mfd size = tryErrno_ $ case mfd of
  Just fd -> setFdSize fd size
  Nothing -> setFileSize path size

xmpUtimens :: FilePath -> POSIXTime -> POSIXTime -> IO Errno
xmpUtimens path atime mtime = tryErrno_ $ setSymbolicLinkTimesHiRes path atime mtime

xmpCreate :: FilePath -> OpenMode -> FileMode -> OpenFileFlags -> IO (Either Errno Fd)
xmpCreate path omode fmode flags = tryErrno $ openFdCompat path omode (Just fmode) flags

xmpOpen :: FilePath -> OpenMode -> OpenFileFlags -> IO (Either Errno Fd)
xmpOpen path mode flags = tryErrno $ openFdCompat path mode Nothing flags

xmpRead :: Fd -> ByteCount -> FileOffset -> IO (Either Errno ByteString)
xmpRead fd size offset = tryErrno $ pread fd size offset

xmpWrite :: Fd -> ByteString -> FileOffset -> IO (Either Errno CInt)
xmpWrite fd bs offset = tryErrno $ fromIntegral <$> pwrite fd bs offset

xmpStatfs :: FilePath -> IO (Either Errno FileSystemStats)
xmpStatfs = tryErrno . getFileSystemStats

xmpRelease :: Fd -> IO ()
xmpRelease = closeFd

xmpFsync :: IO Errno
-- Just a stub. This method is optional and can safely be left unimplemented
xmpFsync = pure eOK

xmpFallocate :: Fd -> CInt -> FileOffset -> FileOffset -> IO Errno
xmpFallocate _ mode _ _ | mode /= 0 = pure eOPNOTSUPP
xmpFallocate (Fd fd) _ offset len =
  -- currently `System.Posix.Fcntl.fileAllocate` is broken
  -- (see https://github.com/haskell/unix/issues/156)
  -- so a custom implementation is used here
  --
  -- posix_fallocate(3) returns error number instead of setting errno
  Errno <$> c_posix_fallocate fd offset len

xmpSetxattr :: FilePath -> String -> ByteString -> SetxattrFlag -> IO Errno
xmpSetxattr path name value flag = tryErrno_ $ XAttr.set path name value flag

xmpGetxattr :: FilePath -> String -> IO (Either Errno ByteString)
xmpGetxattr path name = tryErrno $ XAttr.get path name

xmpListxattr :: FilePath -> IO (Either Errno [String])
xmpListxattr path = tryErrno $ XAttr.list path

xmpRemovexattr :: FilePath -> String -> IO Errno
xmpRemovexattr path name = tryErrno_ $ XAttr.remove path name

xmpCopyFileRange :: Fd -> FileOffset -> Fd -> FileOffset -> ByteCount -> CUInt -> IO (Either Errno CSsize)
xmpCopyFileRange (Fd fdIn) offIn (Fd fdOut) offOut len flags =
  tryErrno $
  with (fromIntegral offIn) $ \plOffIn ->
  with (fromIntegral offOut) $ \plOffOut -> do
    c_copy_file_range fdIn plOffIn fdOut plOffOut len flags
    -- the example of libfuse closes the fds but I don't think it is correct

xmpLseek :: Fd -> SeekMode -> FileOffset -> IO (Either Errno FileOffset)
xmpLseek fd mode offset = tryErrno $ fdSeek fd mode offset

xmpOper :: FuseOperations Fd Void
xmpOper = defaultFuseOperations
  { fuseInit          = Just xmpInit
  , fuseGetattr       = Just $ \path _ -> xmpGetattr path
  , fuseAccess        = Just xmpAccess
  , fuseReadlink      = Just xmpReadlink
  , fuseReaddir       = Just $ \path _ -> xmpReaddir path
  , fuseMknod         = Just xmpMknod
  , fuseMkdir         = Just xmpMkdir
  , fuseSymlink       = Just xmpSymlink
  , fuseUnlink        = Just xmpUnlink
  , fuseRmdir         = Just xmpRmdir
  , fuseRename        = Just xmpRename
  , fuseLink          = Just xmpLink
  , fuseChmod         = Just $ \path _ mode -> xmpChmod path mode
  , fuseChown         = Just $ \path _ uid gid -> xmpChown path uid gid
  , fuseTruncate      = Just xmpTruncate
  , fuseUtimens       = Just $ \path _ atime mtime -> xmpUtimens path (timeSpecToPOSIXTime atime) (timeSpecToPOSIXTime mtime)
  , fuseOpen          = Just xmpOpen
  , fuseCreate        = Just xmpCreate
  , fuseRead          = Just $ \_ -> xmpRead
  , fuseWrite         = Just $ \_ -> xmpWrite
  , fuseStatfs        = Just xmpStatfs
  , fuseRelease       = Just $ \_ -> xmpRelease
  , fuseFsync         = Just $ \_ _ _ -> xmpFsync
  , fuseFallocate     = Just $ \_ fd mode offset len -> xmpFallocate fd mode offset len
  , fuseSetxattr      = Just xmpSetxattr
  , fuseGetxattr      = Just xmpGetxattr
  , fuseListxattr     = Just xmpListxattr
  , fuseRemovexattr   = Just xmpRemovexattr
  , fuseCopyFileRange = Just $ \_ fdIn offIn _ fdOut offOut size flags -> xmpCopyFileRange fdIn offIn fdOut offOut size (fromIntegral flags)
  , fuseLseek         = Just $ \_ fd off mode -> xmpLseek fd mode off
  }

main :: IO ()
main = do
  _ <- setFileCreationMask 0
  fuseMain xmpOper (\e -> hPrint stderr (e :: SomeException) >> pure eIO)