From 2859a6379be53c5f6de34f6dd868ef0f0738b08c Mon Sep 17 00:00:00 2001 From: Ben Gamari <ben@smart-cactus.org> Date: Thu, 25 Jan 2024 15:42:24 -0500 Subject: [PATCH] base: Use strerror_r instead of strerror As noted by #24344, `strerror` is not necessarily thread-safe. Thankfully, POSIX.1-2001 has long offered `strerror_r`, which is safe to use. Fixes #24344. CLC discussion: https://github.com/haskell/core-libraries-committee/issues/249 --- libraries/base/changelog.md | 1 + libraries/ghc-internal/cbits/strerror.c | 29 ++++++++++++++++++ libraries/ghc-internal/configure.ac | 6 ++++ libraries/ghc-internal/ghc-internal.cabal | 1 + libraries/ghc-internal/jsbits/errno.js | 2 +- .../src/GHC/Internal/Foreign/C/Error.hs | 30 +++++++++++++++++-- 6 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 libraries/ghc-internal/cbits/strerror.c diff --git a/libraries/base/changelog.md b/libraries/base/changelog.md index 5d3af3ff34e0..2f77e41d7302 100644 --- a/libraries/base/changelog.md +++ b/libraries/base/changelog.md @@ -28,6 +28,7 @@ and [CLC proposal #258](https://github.com/haskell/core-libraries-committee/issues/258)) * Add `System.Mem.performMajorGC` ([CLC proposal #230](https://github.com/haskell/core-libraries-committee/issues/230)) * Fix exponent overflow/underflow bugs in the `Read` instances for `Float` and `Double` ([CLC proposal #192](https://github.com/haskell/core-libraries-committee/issues/192)) + * `Foreign.C.Error.errnoToIOError` now uses the reentrant `strerror_r` to render system errors when possible ([CLC proposal #249](https://github.com/haskell/core-libraries-committee/issues/249)) * Implement `many` and `some` methods of `instance Alternative (Compose f g)` explicitly. ([CLC proposal #181](https://github.com/haskell/core-libraries-committee/issues/181)) * Change the types of the `GHC.Stack.StackEntry.closureType` and `GHC.InfoProv.InfoProv.ipDesc` record fields to use `GHC.Exts.Heap.ClosureType` rather than an `Int`. To recover the old value use `fromEnum`. ([CLC proposal #210](https://github.com/haskell/core-libraries-committee/issues/210)) diff --git a/libraries/ghc-internal/cbits/strerror.c b/libraries/ghc-internal/cbits/strerror.c new file mode 100644 index 000000000000..1640a4957e79 --- /dev/null +++ b/libraries/ghc-internal/cbits/strerror.c @@ -0,0 +1,29 @@ +// glibc will only expose the POSIX strerror_r if this is defined. +#define _POSIX_C_SOURCE 200112L + +#include <string.h> +#include <errno.h> + +// This must be included after <string.h> lest _GNU_SOURCE may be defined. +#include "HsBaseConfig.h" + +// returns zero on success +int base_strerror_r(int errnum, char *ptr, size_t buflen) +{ +#if defined(HAVE_STRERROR_R) + int ret = strerror_r(errnum, ptr, buflen); + if (ret == ERANGE) { + // Ellipsize the error + ptr[buflen-4] = '.'; + ptr[buflen-3] = '.'; + ptr[buflen-2] = '.'; + ret = 0; + } + return ret; +#elif defined(HAVE_STRERROR_S) + strerror_s(ptr, buflen, errnum); + return 0; +#else +#error neither strerror_r nor strerror_s are supported +#endif +} diff --git a/libraries/ghc-internal/configure.ac b/libraries/ghc-internal/configure.ac index f5befee878ed..4eda0cd9d7b0 100644 --- a/libraries/ghc-internal/configure.ac +++ b/libraries/ghc-internal/configure.ac @@ -41,6 +41,12 @@ AC_CHECK_DECLS([CLOCK_PROCESS_CPUTIME_ID], [], [], [[#include <time.h>]]) AC_CHECK_FUNCS([getclock getrusage times]) AC_CHECK_FUNCS([_chsize_s ftruncate]) +AC_CHECK_FUNCS([strerror_r strerror_s]) + +if test "$ac_cv_func_strerror_r" = no && test "$ac_cv_func_strerror_s" = no; then + AC_MSG_ERROR([Either strerror_r or strerror_s must be available]) +fi + # event-related fun # The line below already defines HAVE_KQUEUE and HAVE_POLL, so technically some of the # subsequent portions that redefine them could be skipped. However, we keep those portions diff --git a/libraries/ghc-internal/ghc-internal.cabal b/libraries/ghc-internal/ghc-internal.cabal index 66933abdb6a5..8113659953c2 100644 --- a/libraries/ghc-internal/ghc-internal.cabal +++ b/libraries/ghc-internal/ghc-internal.cabal @@ -330,6 +330,7 @@ Library cbits/primFloat.c cbits/sysconf.c cbits/fs.c + cbits/strerror.c cmm-sources: cbits/StackCloningDecoding.cmm diff --git a/libraries/ghc-internal/jsbits/errno.js b/libraries/ghc-internal/jsbits/errno.js index b7ea1fb9ae01..ff31029baaa5 100644 --- a/libraries/ghc-internal/jsbits/errno.js +++ b/libraries/ghc-internal/jsbits/errno.js @@ -22,7 +22,7 @@ function h$unsupported(status, c) { return status; } -function h$strerror(err) { +function h$base_strerror(err) { if(err === 12456) { RETURN_UBX_TUP2(h$encodeUtf8("operation unsupported on this platform"), 0); } diff --git a/libraries/ghc-internal/src/GHC/Internal/Foreign/C/Error.hs b/libraries/ghc-internal/src/GHC/Internal/Foreign/C/Error.hs index 169841758b9c..103531d82467 100644 --- a/libraries/ghc-internal/src/GHC/Internal/Foreign/C/Error.hs +++ b/libraries/ghc-internal/src/GHC/Internal/Foreign/C/Error.hs @@ -91,6 +91,9 @@ module GHC.Internal.Foreign.C.Error ( #include "HsBaseConfig.h" import GHC.Internal.Foreign.Ptr +#if !defined(javascript_HOST_ARCH) +import GHC.Internal.Foreign.Marshal.Alloc +#endif import GHC.Internal.Foreign.C.Types import GHC.Internal.Foreign.C.String import GHC.Internal.Data.Functor ( void ) @@ -460,6 +463,29 @@ throwErrnoPathIfMinus1_ = throwErrnoPathIf_ (== -1) -- conversion of an "errno" value into IO error -- -------------------------------------------- +errnoToString :: Errno -> IO String + +#if defined(javascript_HOST_ARCH) +foreign import ccall unsafe "base_strerror" + c_strerror :: Errno -> IO (Ptr CChar) + +errnoToString errno = c_strerror errno >>= peekCString + +#else +foreign import ccall "base_strerror_r" + c_strerror_r :: Errno -> Ptr CChar -> CSize -> IO CInt + +errnoToString errno = + allocaBytes len $ \ptr -> do + ret <- c_strerror_r errno ptr len + if ret /= 0 + then return "errnoToString failed" + else peekCString ptr + where + len :: Num a => a + len = 512 +#endif + -- | Construct an 'IOError' based on the given 'Errno' value. -- The optional information can be used to improve the accuracy of -- error messages. @@ -470,7 +496,7 @@ errnoToIOError :: String -- ^ the location where the error occurred -> Maybe String -- ^ optional filename associated with the error -> IOError errnoToIOError loc errno maybeHdl maybeName = unsafePerformIO $ do - str <- strerror errno >>= peekCString + str <- errnoToString errno return (IOError maybeHdl errType loc str (Just errno') maybeName) where Errno errno' = errno @@ -576,5 +602,3 @@ errnoToIOError loc errno maybeHdl maybeName = unsafePerformIO $ do | errno == eXDEV = UnsupportedOperation | otherwise = OtherError -foreign import ccall unsafe "string.h" strerror :: Errno -> IO (Ptr CChar) - -- GitLab