Skip to content

Conversation

luispedro
Copy link
Contributor

This fixes #60. It builds on #65 (I cherry-picked the commit), while addressing the issues raised there.

@luispedro luispedro changed the title Fix issue 60 Fix issue 60: confusing error messages in rename/createLink/createSymbolicLink Nov 11, 2018
Copy link
Contributor

@hs-viktor hs-viktor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also rebase on master, so we run this through the most recent CI incarnation.

POSIX's ENOENT error doesn't specify if the problem was with source or
destination path. Throw error mentioning both paths instead of only
first one.

Fixes haskell#60
@luispedro luispedro force-pushed the fix_issue60 branch 2 times, most recently from 3d7f8a9 to 6e02c14 Compare February 20, 2021 02:51
@luispedro
Copy link
Contributor Author

Previous issues have been address and the branch has been rebased against main. CI looks good too.

Per the comments on PR haskell#65, it should be unambiguous.
@luispedro
Copy link
Contributor Author

I'm so sorry, I really thought I had addressed that. Fixed now (and I will add a test)

Very basic, but tests that the created link is a hard link and points to
the original file
@luispedro
Copy link
Contributor Author

Added a test, which would have caught the previously mistake of creating symlinks when normal links were desired

@luispedro luispedro requested a review from hs-viktor February 22, 2021 07:57
Copy link
Contributor

@hs-viktor hs-viktor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I just noticed that similar changes also need to happen in System/Posix/Files/ByteString.hsc. Please make these consistent. There the filenames will be ByteStrings (RawFilePath), and throw errors via System.Posix.ByteString.FilePath.

@hs-viktor
Copy link
Contributor

Actually, I just noticed that similar changes also need to happen in System/Posix/Files/ByteString.hsc. Please make these consistent. There the filenames will be ByteStrings (RawFilePath), and throw errors via System.Posix.ByteString.FilePath.

The new function for two paths should probably be added to that module and its non-bytestring twin.

Just in case we ever move a String type that is not a [Char], but
something more like UTF-8 text. The more polymorphic <> should work even
if String is no longer a list.

Also, add a signature to throwErrnoTwoPathsIfMinus1_

Co-authored-by: Viktor Dukhovni <ietf-dane@dukhovni.org>
@luispedro
Copy link
Contributor Author

Updated the code to use <> & have a signature.

The ByteString module stuff might have to wait until the evening as it seems a bit more involved..

Same as done for FilePath versions in 484566b
@luispedro
Copy link
Contributor Author

ByteString module updated as well (including tests)

@luispedro luispedro requested a review from hs-viktor February 23, 2021 06:01
Copy link
Contributor

@hs-viktor hs-viktor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice. Thanks.

@hs-viktor hs-viktor requested a review from Bodigrim February 23, 2021 06:26
@hs-viktor hs-viktor merged commit 49cb62d into haskell:master Feb 24, 2021
hs-viktor pushed a commit that referenced this pull request Feb 24, 2021
@luispedro luispedro deleted the fix_issue60 branch February 25, 2021 02:42
@Bodigrim Bodigrim added this to the 2.8.0.0 milestone Apr 20, 2022
hasufell pushed a commit to hasufell/unix that referenced this pull request Jul 19, 2022
@hasufell hasufell mentioned this pull request Jul 19, 2022
createSymbolicLink name1 name2 =
withFilePath name1 $ \s1 ->
withFilePath name2 $ \s2 ->
throwErrnoTwoPathsIfMinus1_ "createSymbolicLink" name1 name2 (c_symlink s1 s2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luispedro The error message created by this is wrong, see new bug #353.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this should be:

  throwErrnoPathIfMinus1_ ("createSymbolicLink to " <> name1) name2 (c_symlink s1 s2)

Because we should make a reasonable effort to record the file location (source of the symlink) where the error occurred, that can be as or more useful than the descriptive string.

Example results for comparison:

λ> do { tryIOError (createSymbolicLink "/foo" "/bar") ; throwErrnoPathIfMinus1_ "createSymlink to /foo" "/bar" (pure (-1)) }
*** Exception: /bar: createSymlink to /foo: permission denied (Permission denied)

λ> do { tryIOError (createSymbolicLink "/foo" "/bar") ; throwErrnoIfMinus1_ "createSymlink /bar to /foo" (pure (-1)) }
*** Exception: createSymlink /bar to /foo: permission denied (Permission denied)

This is different from the rename case where either of the paths could be the source of the problem, but one could make a case for the source path as a sensible default, and consider also recording that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vdukhovni I don't fully understand what you're saying: Isn't this simply slightly rewording the same sentence?

(I have nothing against the wording, it's good as well, but "we should make a reasonable effort to record the file location (source of the symlink)" is true either way, isn't it?)

Copy link

@vdukhovni vdukhovni Aug 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vdukhovni I don't fully understand what you're saying: Isn't this simply slightly rewording the same sentence?

(I have nothing against the wording, it's good as well, but "we should make a reasonable effort to record the file location (source of the symlink)" is true either way, isn't it?)

My issue isn't with how the resulting error is rendered by its Show method, but rather with its structure as an IOException.
See below:

λ> :set -XLambdaCase 
λ> import GHC.IO.Exception 
λ> import Control.Exception
λ> import  GHC.Internal.Foreign.C.Error 
λ> import System.Posix.Files
λ> import System.IO.Error 
λ> handler :: IOException -> Maybe IOException; handler = Just
λ> eperm :: IO (Either IOException ()); eperm = try $ createSymbolicLink "/foo" "/bar"
λ> raisePath = throwErrnoPathIfMinus1_ "createSymlink to /foo" "/bar" $ pure (-1)
λ>
λ> tryJust handler (createSymbolicLink "/foo" "/bar") >>= \ case { Left e -> pure $ ioe_filename e; Right _ -> pure (Just "no error") }
Nothing
λ> tryJust handler (eperm >> raisePath) >>= \ case { Left e -> pure $ ioe_filename e; Right _ -> pure (Just "no error") }
Just "/bar"

The difference is in the ice_filename element of the IOException:

λ> :info IOException
type IOException :: *
data IOException
  = IOError {ioe_handle :: Maybe GHC.Internal.IO.Handle.Types.Handle,
             ioe_type :: IOErrorType,
             ioe_location :: String,
             ioe_description :: String,
             ioe_errno :: Maybe GHC.Internal.Foreign.C.Types.CInt,
             ioe_filename :: Maybe FilePath}
        -- Defined in ‘GHC.Internal.IO.Exception’

By using throwErrnoPathIfMinus1_ we do a better job of populating the IOException, giving a non-vacuous ioe_filename field. We can examine all the fields and observe the difference in context, showing just ice_filename not populated (also with the current code the two names swapped per your correct bug report).

λ> tryJust handler (eperm >> raisePath) >>= \ case { Left e -> pure . Just $ (,,,,,) <$> ioe_handle <*> ioe_type <*> ioe_location <*> ioe_description <*> ioe_errno <*> ioe_filename $ e; _ -> pure Nothing }
Just (Nothing,permission denied,"createSymlink to /foo","Permission denied",Just 13,Just "/bar")
λ> 
λ> tryJust handler (createSymbolicLink "/foo" "/bar") >>= \ case { Left e -> pure . Just $ (,,,,,) <$> ioe_handle <*> ioe_type <*> ioe_location <*> ioe_description <*> ioe_errno <*> ioe_filename $ e; _ -> pure Nothing }
Just (Nothing,permission denied,"createSymbolicLink '/foo' to '/bar'","Permission denied",Just 13,Nothing)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vdukhovni Thanks for the example!

I agree, we definitely should populate ioe_filename as you say.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Misleading error messages in rename
6 participants