Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix createDirectoryIfMissing function #10

Closed
wants to merge 2 commits into from
Closed

Fix createDirectoryIfMissing function #10

wants to merge 2 commits into from

Conversation

jpvillaisaza
Copy link

see #4

@jpvillaisaza jpvillaisaza changed the title Issue 4 fix create directory if missing function [Issue #4] Fix createDirectoryIfMissing function Dec 10, 2014
@hvr hvr added the blocked: need-more-info Progress cannot be made without additional information from the reporter. label Dec 19, 2014
@hvr
Copy link
Member

hvr commented Dec 19, 2014

What was the original problem and how can it be reproduced?

@hvr
Copy link
Member

hvr commented Dec 19, 2014

Oh, I guess this is about #4

@hvr hvr changed the title [Issue #4] Fix createDirectoryIfMissing function Fix createDirectoryIfMissing function Dec 19, 2014
@hvr hvr added type: a-bug The described behavior is not working as intended. and removed blocked: need-more-info Progress cannot be made without additional information from the reporter. labels Dec 19, 2014
@jpvillaisaza
Copy link
Author

Yes, it's about #4.

@hvr hvr modified the milestone: 1.2.1.2 Dec 19, 2014
@hvr
Copy link
Member

hvr commented Jan 16, 2015

Tbh, I'm not convinced this is a proper fix (yet)...

@jpvillaisaza
Copy link
Author

@hvr What do you think or what do you mean by yet? I think the permission error could be handled differently, but it should be separate from the already exists error, and it shouldn't end in a return () so the error is not ignored.

@jpvillaisaza
Copy link
Author

@hvr Or maybe just remove the catch that's doing the return () (so a permission error is not ignored) and take a closer look at the idea of separating both errors (taking into account that there's a comment saying something about Windows with the permission error).

@hvr
Copy link
Member

hvr commented Jan 16, 2015

@jpvillaisaza I've been digging through the Git history, and stumbled over

b513fe8 which was the commit where @dcoutts introduced that confusing throw (NB: it's not a throwIO, which I'm not sure if it was a deliberate hack or not), wrapped by a silencing catch. I consider this highly suspicious.

The isPermissionError case was hacked in several years later via f85cd29 to try to workaround windows-specific semantics.

In any case, I'd like to better understand the reasoning behind b513fe8 before changing anything here.

@jpvillaisaza
Copy link
Author

@hvr f85cd29 explains why the two errors should be handled together in Windows, as you mentioned.

@jpvillaisaza
Copy link
Author

@hvr
Copy link
Member

hvr commented Jan 18, 2015

After a conversation with @dcoutts here's one suggestion how to fix it:

    createDir dir notExistHandler = do
      r <- E.try $ createDirectory dir
      case (r :: Either IOException ()) of
        Right ()                   -> return ()
        Left  e
          | isDoesNotExistError  e -> notExistHandler e

          | isAlreadyExistsError e || isPermissionError e -> do
#ifdef mingw32_HOST_OS
              canIgnore <- (withFileStatus "createDirectoryIfMissing" dir isDirectory)
#else
              canIgnore <- (Posix.isDirectory `fmap` Posix.getFileStatus dir)
#endif
                           `catch` ((\ _ -> return False) :: IOException -> IO Bool)
              unless canIgnore (throwIO e)
          | otherwise              -> throwIO e

@dcoutts
Copy link
Contributor

dcoutts commented Jan 19, 2015

Ok, it's actually more subtle.

It looks to me like we need to split the isAlreadyExistsError e case from the isPermissionError e case.

In the isAlreadyExistsError e case we need to use

canIgnore <- (...)
                      `catch` (\_ -> return True)

while apparently in the isPermissionError e case we need the other way around:

canIgnore <- (...)
                      `catch` (\_ -> return False)

which actually is the same way round as the doesDirectoryExist function.

Why is this? Well, in the isAlreadyExistsError case, if doing the stat() tells us that in fact the file does not exist (e.g. a race with other code that deleted it) then it's safe to ignore. Note that we changed it to be this way round in 2008 because of problems with racing delete vs createDirectoryIfMissing.

But for isPermissionError, the reason stat would fail would be again due to a permission error, and so in that case we still want to throw the exception.

hvr added a commit that referenced this pull request Jan 19, 2015
In some cases, `createDirectoryIfMissing` would silently fail. For
example the following invocation would fail to report via an exception
that it couldn't create a folder:

  let testdir = "/tmp/sometestdir"
  writeFile testdir ""
  createDirectoryIfMissing False testdir

A related issue was the failure to create a folder hierarchy due to lack
of permissions, for instance

  createDirectoryIfMissing True "/foo"

for a non-priviledged user would silently fail (i.e. no exception
thrown), even though "/foo" was not created.

Fixes #4 (see also #10 for discussion)
@jpvillaisaza
Copy link
Author

This pull request is no longer necessary. See 1f11393.

@jpvillaisaza jpvillaisaza deleted the issue_4_fix_createDirectoryIfMissing_function branch January 19, 2015 22:18
@hvr hvr modified the milestones: 1.2.2, 1.2.2.1 Feb 26, 2015
@Rufflewind Rufflewind removed the type: a-bug The described behavior is not working as intended. label Apr 30, 2016
bgamari pushed a commit to bgamari/directory that referenced this pull request Jul 29, 2016
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.

4 participants