file permissions and copying files

Duncan Coutts duncan.coutts at
Fri Feb 20 06:05:55 EST 2009


I have complained before about the System.Directory file permissions
API, and how it relates to copyFile.

Let me give an example of where the existing copyFile API is too
limited. Perhaps this will help us think about what an improved API
might be.

The example of course is build system component of Cabal. It turns out
that we actually need *three* different kinds of copyFile. The only
difference in each case is the treatment of file permissions /

To achieve the worst case consider a source tree that is completely read
only and a user umask that makes new files not world readable.

The cases are:

     1. Copying files within the build tree, eg as a dummy
        pre-processor. In this case it should behave like "cat src >
        dest" and not like cp. That is, it is essential that we create
        the new files with default permissions and not copy the source
        permissions. Otherwise we would end up making read-only files
        which would cause problems later (especially on windows).
     2. Copying source files into a temp directory to prepare a source
        tarball. In this case we really do want to copy file
        permissions. In particular we want to copy executable
        permissions on files like ./configure. In this case it does not
        matter if we also copy read-only permissions because we will
        only delete these files, not try to overwrite them. Also, the
        tarball creation code will only copy executable permissions.
     3. Installing files. In this case it is essential that we specify
        exact permissions and ignore the umask of the installing user or
        the permissions of the source files. The source files could be
        read-only and that would mess things up on windows as we would
        not be able to re-install over the read-only files. We also do
        not want to copy permissions generally because if the umask of
        the user that built the files was such that they're not globally
        readable then installing globally (eg via sudo) will install
        files only readable by root. Instead we have to specify that the
        files are read/write by their owner and readable by everyone
        else (and executable for binaries).

If we were to generalise from these examples we'd have some copyFile
function that took an extra function to generate the permissions from a
combination of the source and default permissions (or it could ignore
both and use specific ones).

Note that in the case where I had to set specific permissions I didn't
really need full control over unix permission bits, I just wanted a
couple properties. I needed executable or not, readable/writable by the
file owner and readable/writable by others. Is that sufficiently
abstract to map onto both Windows and Unix file permissions?

The thing that works least well between unix and windows file
permissions is group ownership and permissions but can these be ignored
in most portable situations?

So at the moment we have:

data Permissions = Permissions {
  readable :: Bool,
  writable :: Bool,
  executable :: Bool,
  searchable :: Bool

The main problem with this is that it is not abstract so it looses
information when we do:

getPermissions src >>= setPermissions dst

However suppose it was an abstract type such that this worked and had
the same query functions as above. Then for setting permissions we'd
also have setReadable, etc :: Permissions -> Permissions. However as I
mentioned above for Cabal we'd need to distinguish setting
readable/writable for the file owner vs for all other users.

So how about this:

data Permissions -- abstract

getPermissions :: FilePath -> IO Permissions
setPermissions :: FilePath -> Permissions -> IO ()

-- | only tells us effective permissions for the current process
readable :: Permissions -> Bool
writable :: Permissions -> Bool
executable :: Permissions -> Bool
searchable :: Permissions -> Bool

setUserReadable, setOtherReadable :: Bool -> Permissions -> Permissions
setUserWritable, setOtherWritable :: Bool -> Permissions -> Permissions
setExecutable :: Bool -> Permissions -> Permissions
setSearchable :: Bool -> Permissions -> Permissions

So for Unix the mapping is:

setUserReadable True/False     chmod u+r / u-r
setUserWritable True/False     chmod u+w / u-w

setOtherReadable True/False    chmod go+r / go-r
setOtherWritable True/False    chmod go+w / go-w

setExecutable True/False       chmod +x / -x (for files)
setSearchable True/False       chmod +x / -x (for dirs)

So I'm aligning group with other and not distinguishing
executable/searchable for user vs others.

Note also that what is set by set* is not necessarily the same as what
we get back from the query functions because those take all permissions
into account including group permissions and other ACLs that we cannot
manipulate via this API.



More information about the Libraries mailing list