Skip to content
Snippets Groups Projects
Commit ef683c6b authored by redneb's avatar redneb Committed by thoughtpolice
Browse files

Extract the result of get*_r before we deallocate the auxiliary buffer


Also comes with tests. This closes #8108.

Signed-off-by: default avatarAustin Seipp <aseipp@pobox.com>
parent 46bfe3d5
Branches
Tags
No related merge requests found
...@@ -197,21 +197,16 @@ data GroupEntry = ...@@ -197,21 +197,16 @@ data GroupEntry =
groupMembers :: [String] -- ^ A list of zero or more usernames that are members (gr_mem) groupMembers :: [String] -- ^ A list of zero or more usernames that are members (gr_mem)
} deriving (Show, Read, Eq) } deriving (Show, Read, Eq)
-- | @getGroupEntryForID gid@ calls @getgrgid@ to obtain -- | @getGroupEntryForID gid@ calls @getgrgid_r@ to obtain
-- the @GroupEntry@ information associated with @GroupID@ -- the @GroupEntry@ information associated with @GroupID@
-- @gid@. -- @gid@. This operation may fail with 'isDoesNotExistError'
-- if no such group exists.
getGroupEntryForID :: GroupID -> IO GroupEntry getGroupEntryForID :: GroupID -> IO GroupEntry
#ifdef HAVE_GETGRGID_R #ifdef HAVE_GETGRGID_R
getGroupEntryForID gid = do getGroupEntryForID gid =
allocaBytes (#const sizeof(struct group)) $ \pgr -> allocaBytes (#const sizeof(struct group)) $ \pgr ->
alloca $ \ ppgr -> do doubleAllocWhileERANGE "getGroupEntryForID" "group" grBufSize unpackGroupEntry $
throwErrorIfNonZero_ "getGroupEntryForID" $ c_getgrgid_r gid pgr
doubleAllocWhile isERANGE grBufSize $ \s b ->
c_getgrgid_r gid pgr b (fromIntegral s) ppgr
_ <- throwErrnoIfNull "getGroupEntryForID" $
peekElemOff ppgr 0
unpackGroupEntry pgr
foreign import ccall unsafe "getgrgid_r" foreign import ccall unsafe "getgrgid_r"
c_getgrgid_r :: CGid -> Ptr CGroup -> CString c_getgrgid_r :: CGid -> Ptr CGroup -> CString
...@@ -220,26 +215,17 @@ foreign import ccall unsafe "getgrgid_r" ...@@ -220,26 +215,17 @@ foreign import ccall unsafe "getgrgid_r"
getGroupEntryForID = error "System.Posix.User.getGroupEntryForID: not supported" getGroupEntryForID = error "System.Posix.User.getGroupEntryForID: not supported"
#endif #endif
-- | @getGroupEntryForName name@ calls @getgrnam@ to obtain -- | @getGroupEntryForName name@ calls @getgrnam_r@ to obtain
-- the @GroupEntry@ information associated with the group called -- the @GroupEntry@ information associated with the group called
-- @name@. -- @name@. This operation may fail with 'isDoesNotExistError'
-- if no such group exists.
getGroupEntryForName :: String -> IO GroupEntry getGroupEntryForName :: String -> IO GroupEntry
#ifdef HAVE_GETGRNAM_R #ifdef HAVE_GETGRNAM_R
getGroupEntryForName name = do getGroupEntryForName name =
allocaBytes (#const sizeof(struct group)) $ \pgr -> allocaBytes (#const sizeof(struct group)) $ \pgr ->
alloca $ \ ppgr -> withCAString name $ \ pstr ->
withCAString name $ \ pstr -> do doubleAllocWhileERANGE "getGroupEntryForName" "group" grBufSize unpackGroupEntry $
throwErrorIfNonZero_ "getGroupEntryForName" $ c_getgrnam_r pstr pgr
doubleAllocWhile isERANGE grBufSize $ \s b ->
c_getgrnam_r pstr pgr b (fromIntegral s) ppgr
r <- peekElemOff ppgr 0
when (r == nullPtr) $
ioError $ flip ioeSetErrorString "no group name"
$ mkIOError doesNotExistErrorType
"getGroupEntryForName"
Nothing
(Just name)
unpackGroupEntry pgr
foreign import ccall unsafe "getgrnam_r" foreign import ccall unsafe "getgrnam_r"
c_getgrnam_r :: CString -> Ptr CGroup -> CString c_getgrnam_r :: CString -> Ptr CGroup -> CString
...@@ -324,20 +310,16 @@ lock = unsafePerformIO $ newMVar () ...@@ -324,20 +310,16 @@ lock = unsafePerformIO $ newMVar ()
{-# NOINLINE lock #-} {-# NOINLINE lock #-}
#endif #endif
-- | @getUserEntryForID gid@ calls @getpwuid@ to obtain -- | @getUserEntryForID gid@ calls @getpwuid_r@ to obtain
-- the @UserEntry@ information associated with @UserID@ -- the @UserEntry@ information associated with @UserID@
-- @uid@. -- @uid@. This operation may fail with 'isDoesNotExistError'
-- if no such user exists.
getUserEntryForID :: UserID -> IO UserEntry getUserEntryForID :: UserID -> IO UserEntry
#ifdef HAVE_GETPWUID_R #ifdef HAVE_GETPWUID_R
getUserEntryForID uid = do getUserEntryForID uid =
allocaBytes (#const sizeof(struct passwd)) $ \ppw -> allocaBytes (#const sizeof(struct passwd)) $ \ppw ->
alloca $ \ pppw -> do doubleAllocWhileERANGE "getUserEntryForID" "user" pwBufSize unpackUserEntry $
throwErrorIfNonZero_ "getUserEntryForID" $ c_getpwuid_r uid ppw
doubleAllocWhile isERANGE pwBufSize $ \s b ->
c_getpwuid_r uid ppw b (fromIntegral s) pppw
_ <- throwErrnoIfNull "getUserEntryForID" $
peekElemOff pppw 0
unpackUserEntry ppw
foreign import ccall unsafe "__hsunix_getpwuid_r" foreign import ccall unsafe "__hsunix_getpwuid_r"
c_getpwuid_r :: CUid -> Ptr CPasswd -> c_getpwuid_r :: CUid -> Ptr CPasswd ->
...@@ -354,26 +336,17 @@ foreign import ccall unsafe "getpwuid" ...@@ -354,26 +336,17 @@ foreign import ccall unsafe "getpwuid"
getUserEntryForID = error "System.Posix.User.getUserEntryForID: not supported" getUserEntryForID = error "System.Posix.User.getUserEntryForID: not supported"
#endif #endif
-- | @getUserEntryForName name@ calls @getpwnam@ to obtain -- | @getUserEntryForName name@ calls @getpwnam_r@ to obtain
-- the @UserEntry@ information associated with the user login -- the @UserEntry@ information associated with the user login
-- @name@. -- @name@. This operation may fail with 'isDoesNotExistError'
-- if no such user exists.
getUserEntryForName :: String -> IO UserEntry getUserEntryForName :: String -> IO UserEntry
#if HAVE_GETPWNAM_R #if HAVE_GETPWNAM_R
getUserEntryForName name = do getUserEntryForName name =
allocaBytes (#const sizeof(struct passwd)) $ \ppw -> allocaBytes (#const sizeof(struct passwd)) $ \ppw ->
alloca $ \ pppw -> withCAString name $ \ pstr ->
withCAString name $ \ pstr -> do doubleAllocWhileERANGE "getUserEntryForName" "user" pwBufSize unpackUserEntry $
throwErrorIfNonZero_ "getUserEntryForName" $ c_getpwnam_r pstr ppw
doubleAllocWhile isERANGE pwBufSize $ \s b ->
c_getpwnam_r pstr ppw b (fromIntegral s) pppw
r <- peekElemOff pppw 0
when (r == nullPtr) $
ioError $ flip ioeSetErrorString "no user name"
$ mkIOError doesNotExistErrorType
"getUserEntryForName"
Nothing
(Just name)
unpackUserEntry ppw
foreign import ccall unsafe "__hsunix_getpwnam_r" foreign import ccall unsafe "__hsunix_getpwnam_r"
c_getpwnam_r :: CString -> Ptr CPasswd c_getpwnam_r :: CString -> Ptr CPasswd
...@@ -439,13 +412,41 @@ sysconfWithDefault def sc = ...@@ -439,13 +412,41 @@ sysconfWithDefault def sc =
return $ if v == (-1) then def else v return $ if v == (-1) then def else v
#endif #endif
isERANGE :: Integral a => a -> Bool -- The following function is used by the getgr*_r, c_getpw*_r
isERANGE = (== eRANGE) . Errno . fromIntegral -- families of functions. These functions return their result
-- in a struct that contains strings and they need a buffer
doubleAllocWhile :: (a -> Bool) -> Int -> (Int -> Ptr b -> IO a) -> IO a -- that they can use to store those strings. We have to be
doubleAllocWhile p s m = do -- careful to unpack the struct containing the result before
r <- allocaBytes s (m s) -- the buffer is deallocated.
if p r then doubleAllocWhile p (2 * s) m else return r doubleAllocWhileERANGE
:: String
-> String -- entry type: "user" or "group"
-> Int
-> (Ptr r -> IO a)
-> (Ptr b -> CSize -> Ptr (Ptr r) -> IO CInt)
-> IO a
doubleAllocWhileERANGE loc enttype initlen unpack action =
alloca $ go initlen
where
go len res = do
r <- allocaBytes len $ \buf -> do
rc <- action buf (fromIntegral len) res
if rc /= 0
then return (Left rc)
else do p <- peek res
when (p == nullPtr) $ notFoundErr
fmap Right (unpack p)
case r of
Right x -> return x
Left rc | Errno rc == eRANGE ->
-- ERANGE means this is not an error
-- we just have to try again with a larger buffer
go (2 * len) res
Left rc ->
ioError (errnoToIOError loc (Errno rc) Nothing Nothing)
notFoundErr =
ioError $ flip ioeSetErrorString ("no such " ++ enttype)
$ mkIOError doesNotExistErrorType loc Nothing Nothing
unpackUserEntry :: Ptr CPasswd -> IO UserEntry unpackUserEntry :: Ptr CPasswd -> IO UserEntry
unpackUserEntry ptr = do unpackUserEntry ptr = do
...@@ -462,15 +463,6 @@ unpackUserEntry ptr = do ...@@ -462,15 +463,6 @@ unpackUserEntry ptr = do
shell <- (#peek struct passwd, pw_shell) ptr >>= peekCAString shell <- (#peek struct passwd, pw_shell) ptr >>= peekCAString
return (UserEntry name passwd uid gid gecos dir shell) return (UserEntry name passwd uid gid gecos dir shell)
-- Used when calling re-entrant system calls that signal their 'errno'
-- directly through the return value.
throwErrorIfNonZero_ :: String -> IO CInt -> IO ()
throwErrorIfNonZero_ loc act = do
rc <- act
if (rc == 0)
then return ()
else ioError (errnoToIOError loc (Errno rc) Nothing Nothing)
-- Used when a function returns NULL to indicate either an error or -- Used when a function returns NULL to indicate either an error or
-- EOF, depending on whether the global errno is nonzero. -- EOF, depending on whether the global errno is nonzero.
throwErrnoIfNullAndError :: String -> IO (Ptr a) -> IO (Ptr a) throwErrnoIfNullAndError :: String -> IO (Ptr a) -> IO (Ptr a)
......
import Control.Monad
import Control.Concurrent
import System.Posix.User
main = do
void $ forkIO $ forever $ getGroupEntryForID 0
void $ forkIO $ forever $ getGroupEntryForID 0
threadDelay (3*1000*1000)
...@@ -59,3 +59,5 @@ test('T3816', normal, compile_and_run, ['-package unix']) ...@@ -59,3 +59,5 @@ test('T3816', normal, compile_and_run, ['-package unix'])
test('processGroup001', normal, compile_and_run, ['-package unix']) test('processGroup001', normal, compile_and_run, ['-package unix'])
test('processGroup002', normal, compile_and_run, ['-package unix']) test('processGroup002', normal, compile_and_run, ['-package unix'])
test('executeFile001', omit_ways(prof_ways), compile_and_run, ['-package unix']) test('executeFile001', omit_ways(prof_ways), compile_and_run, ['-package unix'])
test('T8108', normal, compile_and_run, ['-package unix'])
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment