72 Commits

Author SHA1 Message Date
eea53e7113 Some some 2020-04-14 11:29:56 +02:00
e194fdec91 More 2020-04-14 10:40:33 +02:00
dc4e652f3a Random test 2020-04-14 08:41:51 +02:00
b965635d05 Update travis with osc build 2020-04-14 00:00:42 +02:00
d21b7bed3b Spelling 2020-01-30 00:28:15 +01:00
25083b293d Bump hpath-io 2020-01-30 00:28:05 +01:00
09f25deecc Fix hpath-io deps 2020-01-29 23:09:40 +01:00
1d7ffca4ac Update ChangeLog 2020-01-29 23:07:55 +01:00
33e68590b2 Fix hpath-directory.cabal 2020-01-29 23:06:57 +01:00
e1f302b4a6 Split hpath-directory into hpath-posix 2020-01-29 23:06:13 +01:00
4df7e02e27 Bump streamly-bytestring >= 0.1.0.1 for older GHCs 2020-01-27 12:51:44 +01:00
ecb52f5217 Clean up 2020-01-26 22:40:03 +01:00
768443df27 Make sure it builds with 7.10.3 2020-01-26 22:10:32 +01:00
f7e2131192 Release new versions 2020-01-26 21:52:34 +01:00
87e452c49f Add/update READMEs 2020-01-26 21:52:13 +01:00
1d00ae469d Make hpath-io use hpath-directory 2020-01-26 21:52:08 +01:00
d4402a25bb Set hpath-directory version to 0.13.0 2020-01-26 21:48:26 +01:00
0c770be3a5 Don't run toAbs manually, doesn't make sense 2020-01-26 21:48:08 +01:00
f3f232e4c9 Add hpath-directory 2020-01-26 20:40:37 +01:00
b7cd5ba857 Add takeAllParents 2020-01-26 20:40:37 +01:00
7d0ca1c230 Use streamly-bytestring 2020-01-26 14:27:38 +01:00
a6036a7aea Fix documentation 2020-01-24 13:01:28 +01:00
0ff3808544 Bump versions 2020-01-20 20:19:45 +01:00
9d8b7d5bfc Fix HPath.IO after API changes 2020-01-20 19:51:01 +01:00
d0beba227a Add another doctest to splitDirectories 2020-01-20 19:51:01 +01:00
607e67378c Improve documentation 2020-01-20 19:51:01 +01:00
7b66379d49 Add rootPath, isRootPath, and getAllComponents{,AfterRoot} 2020-01-20 19:51:01 +01:00
db23ad6b38 Move isParentOf to its own section 2020-01-20 19:51:01 +01:00
f2986e60db Don't preserve trailing path separators 2020-01-20 19:51:01 +01:00
1eeef0806d Unexpose HPath.Internal 2020-01-20 19:51:00 +01:00
375d8ae0a3 Redo parseAny 2020-01-20 19:51:00 +01:00
3abc68cdd6 Remove RelC and Fn wrt #29
'Path Fn </> Path Fn' leads to incorrect semantics.
The only fix is introducing complicated type family
machinery, which isn't justified.
2020-01-20 19:51:00 +01:00
117641c419 Bump versions 2020-01-18 18:59:21 +01:00
824aff1751 Add various new functions to HPath.IO 2020-01-18 18:53:04 +01:00
94077aa6a6 Add parseAny friends 2020-01-18 18:44:28 +01:00
22ddeeadcc Add isSpecialDirectoryEntry 2020-01-18 18:43:55 +01:00
1f4e289903 Fix run-doctests working dir 2020-01-16 02:05:34 +01:00
bb24a57e36 Fix build on ghc <7.11 2020-01-14 00:07:38 +01:00
9e831749c0 Release hpath-filepath-0.10.1 2020-01-13 23:50:24 +01:00
dc2493427c Release hpath-0.10.1 2020-01-13 23:50:24 +01:00
d898af171d Release hpath-io-0.10.1 2020-01-13 23:50:24 +01:00
80d60845dc Add TODO 2020-01-13 23:50:24 +01:00
5b9958ba23 Switch to safe-exceptions 2020-01-13 23:50:23 +01:00
9b20ce2e72 Redo file reading API 2020-01-13 23:50:23 +01:00
6a1f80bc17 Move file check functions to HPath.IO 2020-01-13 23:50:23 +01:00
931851c8c1 Add quasi quoters for hpath 2020-01-13 17:06:27 +01:00
3b6eb46dc9 More README updates. 2020-01-04 21:48:27 +01:00
0ae398df39 Update CHANGELOGs 2020-01-04 20:14:25 +01:00
fe92de9abd Improve README 2020-01-04 19:58:20 +01:00
c49d36d1c3 Add build status badge to main README 2020-01-04 18:39:42 +01:00
a6f902a9df Merge branch 'split-packages' 2020-01-04 18:36:24 +01:00
d0c3a2c9a7 Fix build with older GHCs 2020-01-04 18:14:52 +01:00
c3d2c0433e Pin dependencies 2020-01-04 18:06:06 +01:00
dc131c3e6b Bump versions to 0.10.0 due to API break 2020-01-04 17:56:40 +01:00
ca59169f02 Set proper optimisation for streamly
https://github.com/composewell/streamly/blob/master/docs/Build.md
2020-01-04 17:55:58 +01:00
09eea518b8 Split packages into hpath{,-io,-filepath} 2020-01-04 17:55:14 +01:00
4e07fcf5b2 Fix update-gh-pages 2019-12-31 01:41:34 +01:00
a343d43b5c Update gh token 2019-12-31 01:27:35 +01:00
fda03eb27a Update GH_TOKEN 2019-12-31 01:04:34 +01:00
a487f02894 Try to fix update-gh-pages 2019-12-31 00:44:06 +01:00
8412ea96fb Merge remote-tracking branch 'origin/fix-doctests' 2019-12-30 23:00:24 +01:00
28e0d1d635 Add tested-with 2019-12-30 22:42:54 +01:00
c9013e5a2a Various travis fixes 2019-12-30 22:42:54 +01:00
1d03ec78b3 Clean up ghc matrix 2019-12-30 22:42:54 +01:00
f5c541b9cc Move doctests to shell script
This fixes various problems with doctest:

* https://github.com/sol/doctest/issues/245
* https://github.com/composewell/streamly/issues/83

Also clean up travis.yml for cabal-3.
2019-12-30 22:42:54 +01:00
3529ec2a15 Revert "Fix support for older GHCs"
This reverts commit 21dd1718c0.
2019-12-30 16:35:42 +01:00
7db7a9402f Merge remote-tracking branch 'origin/streamly' 2019-12-30 16:01:49 +01:00
21dd1718c0 Fix support for older GHCs 2019-12-30 15:16:59 +01:00
2e0fe6b698 Fix travis
Streamly requires at least cabal 2.2.
2019-12-30 15:01:03 +01:00
200fc9b581 Use streamly for copyFile 2019-12-30 14:47:06 +01:00
4ac3ee3e42 Update .gitignore 2019-12-30 14:34:57 +01:00
035c364b35 Fix doctests-posix 2019-12-30 14:34:54 +01:00
202 changed files with 17623 additions and 1974 deletions

2
.ghci
View File

@@ -1,2 +0,0 @@
:set -package HUnit -package hspec
:set -package template-haskell

4
.gitignore vendored
View File

@@ -8,7 +8,7 @@ TAGS
tags
*.tag
.stack-work/
dist/
dist-newstyle/
.cabal-sandbox/
cabal.sandbox.config
dist-newstyle/
.ghc.environment.*

View File

@@ -7,59 +7,77 @@ dist: trusty
matrix:
include:
- env: CABALVER=1.18 GHCVER=7.6.3
addons: {apt: {packages: [cabal-install-1.18,ghc-7.6.3], sources: [hvr-ghc]}}
- env: CABALVER=1.22 GHCVER=7.8.4
addons: {apt: {packages: [cabal-install-1.22,ghc-7.8.4], sources: [hvr-ghc]}}
- env: CABALVER=1.24 GHCVER=7.10.2
addons: {apt: {packages: [cabal-install-1.24,ghc-7.10.2], sources: [hvr-ghc]}}
- env: CABALVER=1.24 GHCVER=8.0.1
addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.1], sources: [hvr-ghc]}}
- env: CABALVER=2.0 GHCVER=8.2.2
addons: {apt: {packages: [cabal-install-2.0,ghc-8.2.2], sources: [hvr-ghc]}}
- env: CABALVER=2.2 GHCVER=8.4.1
addons: {apt: {packages: [cabal-install-2.2,ghc-8.4.1], sources: [hvr-ghc]}}
- env: CABALVER=3.0 GHCVER=7.10.3 SKIP_DOCTESTS=yes
addons: {apt: {packages: [cabal-install-3.0,ghc-7.10.3], sources: [hvr-ghc]}}
before_install:
- sudo apt-get install -y hscolour
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
- env: CABALVER=3.0 GHCVER=8.0.2 SKIP_DOCTESTS=yes
addons: {apt: {packages: [cabal-install-3.0,ghc-8.0.2], sources: [hvr-ghc]}}
before_install:
- sudo apt-get install -y hscolour
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
- env: CABALVER=3.0 GHCVER=8.2.2
addons: {apt: {packages: [cabal-install-3.0,ghc-8.2.2], sources: [hvr-ghc]}}
before_install:
- sudo apt-get install -y hscolour
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
- env: CABALVER=3.0 GHCVER=8.4.4
addons: {apt: {packages: [cabal-install-3.0,ghc-8.4.4], sources: [hvr-ghc]}}
before_install:
- sudo apt-get install -y hscolour
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
- env: CABALVER=3.0 GHCVER=8.6.5
addons: {apt: {packages: [cabal-install-3.0,ghc-8.6.5], sources: [hvr-ghc]}}
before_install:
- sudo apt-get install -y hscolour
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
- env: CABALVER=3.0 GHCVER=8.8.1
addons: {apt: {packages: [cabal-install-3.0,ghc-8.8.1], sources: [hvr-ghc]}}
before_install:
- sudo apt-get install -y hscolour
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
- env: CABALVER=head GHCVER=head
addons: {apt: {packages: [cabal-install-head,ghc-head], sources: [hvr-ghc]}}
before_install:
- sudo apt-get install -y hscolour
- export PATH=~/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
- os: osx
osx_image: xcode11.3
language: generic
before_install:
- mkdir -p ~/.ghcup/bin
- curl https://gitlab.haskell.org/haskell/ghcup/raw/master/ghcup > ~/.ghcup/bin/ghcup
- chmod +x ~/.ghcup/bin/ghcup
- export PATH=~/.cabal/bin:~/.ghcup/bin:$PATH
- ghcup install 8.6.5
- ghcup set 8.6.5
- ghcup install-cabal
allow_failures:
- env: CABALVER=head GHCVER=head
env:
global:
- secure: q++z4DGwOHYjmed00oxMnGhBTzOBzKYunXvVcnCEmvmzW3qZERtXj3B7CLW4vRtmBlo3SiM0fb25NeYao+ByzTjo8jk9noiBVZvffwRmlKCeVwYx7T4/rsDhfV97k2JOeahBSgxWNuTkt+5gv07HpKdTiIxJsiv/QdBxQeq6/Ly6dyRskmCt+VuFvQg+cqPMugxIXtY6F7eZ1zgl/LxlamWjO3E4lX0Myf4o8+SU1HRDVkkVe+ytnRcVcYI2FHuFV/sSoDMTweXQToA9roVjOkfhq4rGlPCuXJkBPyZW2otLXgAV7I2kjwgxqmS5Yw752CcFjMMbG6R1u8sEAcGrJNKHfx8sKqBwI0AVoq4CJn+nKSElTDl0KI1mqazmazK4/mddkD9NGIVXCFmw4b+YGf1uDj8FAR94UmOiEFkEObGkQxG1XK/uzDaUJ1tO3MYXjPPEIE89BJORo+ZskmKFEoqbrBR/vEjbXxJHWP7SaaoM+mWpMiSssEFb/Z5mDBFPb2P/2f7nO4ZDfOYp/9hZdBvDaVM8FmTQfzF6jIUIOFmeeiSZWIBAHoDfdZDRrM/hC5JzqfMumW9frwllsQtYytkAsUqlNnCW86jlc5/5L6D8eY2NERFI2DRqrBi7bP2AfYXsozY0gMO1RL5+iQSQVKlPhk6IyAJYCWCYnrA+dz4=
before_install:
- sudo apt-get install -y hscolour
- export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
- secure: HPBARvNM85ea2U0Ynq5MMe6BRlnuwqXWuSn20VY3EYCAT2njkVPYnR3O7+bGE6aq0KHAV87zz5iUfGJontd86tE0sDVjcSuRY0hqjOeJTkQq5M8WXJZOpVqlBTwDP1Q3x/fwoRa0dt9Z0tZZdKMlrf2XdcKPDdhcP1QYP4aV/jO4ZCfAQr7zVCvTae+Lp/KmwFYcBbFo/pj0duF1M4Oqx/D388b/W4jVE3lgd/TK7Ja1xWP6g+Oyvo6iQK8yJVYGdm6E+cVsNueiisnTJ/rRA53lsaC9dmWtZaFGl41wPviSU5zPq03vOuZMiyE2WtCHoo46ONXrXJ9N2soqdQVfEkr9Nw5LQl+6C5lCPEejZ575YUkuO05H3wvHMk3YY4zWXNFA9eZ47PEH8tpoUk9LPBacCKQFtp5lfRk63crba5CiFtcMyFq++0mLpNthNvtto7ffHMZrt6fvK9axI+r21VPftf/3FiFY4mnCp/Bln+ijklfZSN71VqiT20EWuqxQHw8aCpT00KA/PKGl9iJfoN4OO3XzNRTtmM+L9Im4bc1ni9YQ6N3UYg3z0nEnCLwFcTmTH/tDMHRremE0dM6B++YfcnyIhen8w+hG4bcXk7jbMUizRhUhStN7TZQuC9S4wE5whhp9c03rJZMmH5E2rlXY3lwVgeyWm1TuMp1RYWI=
install:
- cabal --version
- travis_retry cabal update
- cabal sandbox init
- cabal install --only-dependencies --enable-tests -j
- cabal install --installdir=$HOME/.cabal/bin hspec-discover
- cabal install --installdir=$HOME/.cabal/bin doctest
script:
- cabal configure --enable-tests -v2
- cabal build
- cabal test
- cabal check
- cabal sdist
- cabal haddock --hyperlink-source --html-location=https://hackage.haskell.org/package/\$pkg-\$version/docs/
# check that the generated source-distribution can be built & installed
- export SRC_TGZ=$(cabal info . | awk '{print $2 ".tar.gz";exit}') ;
cd dist/;
cabal sandbox init;
if [ -f "$SRC_TGZ" ]; then
cabal install "$SRC_TGZ" --enable-tests;
else
echo "expected '$SRC_TGZ' not found";
exit 1;
fi;
cd ..
after_script:
- ./update-gh-pages.sh
- (cd unix && autoreconf -fi)
- cabal build --enable-tests all
- cabal run spec
- ./hpath/run-doctests.sh
- ./hpath-filepath/run-doctests.sh
- (cd hpath && cabal check)
- (cd hpath-filepath && cabal check)
- (cd hpath-io && cabal check)
- cabal sdist all
- cabal install --lib all
notifications:
email:

View File

@@ -1,87 +1,19 @@
# HPath
# HPath libraries
[![Gitter chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Hackage version](https://img.shields.io/hackage/v/hpath.svg?label=Hackage)](https://hackage.haskell.org/package/hpath) [![Build Status](https://api.travis-ci.org/hasufell/hpath.png?branch=master)](http://travis-ci.org/hasufell/hpath) [![Hackage-Deps](https://img.shields.io/hackage-deps/v/hpath.svg)](http://packdeps.haskellers.com/feed?needle=hpath)
[![Gitter chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://api.travis-ci.org/hasufell/hpath.png?branch=master)](http://travis-ci.org/hasufell/hpath)
Support for well-typed paths in Haskell. Also provides ByteString based filepath
manipulation.
Set of libraries to deal with filepaths and files.
## Motivation
The motivation came during development of
[hsfm](https://github.com/hasufell/hsfm)
which has a pretty strict File type, but lacks a strict Path type, e.g.
for user input.
* filepaths should be type-safe (absolute, relative, ...)
* filepaths should be ByteString under the hood, see [Abstract FilePath Proposal (AFPP)](https://gitlab.haskell.org/ghc/ghc/wikis/proposal/abstract-file-path)
* file high-level operations should be platform-specific, exception-stable, safe and as atomic as possible
The library that came closest to my needs was
[path](https://github.com/chrisdone/path),
but the API turned out to be oddly complicated for my use case, so I
decided to fork it.
Similarly, [posix-paths](https://github.com/JohnLato/posix-paths)
was exactly what I wanted for the low-level operations, but upstream seems dead,
so it is forked as well and merged into this library.
## Goals
* well-typed paths
* high-level API to file operations like recursive directory copy
* safe filepath manipulation, never using String as filepath, but ByteString
* still allowing sufficient control to interact with the underlying low-level calls
Note: this library was written for __posix__ systems and it will probably not support other systems.
## Differences to 'path'
* doesn't attempt to fake IO-related information into the path, so whether a path points to a file or directory is up to your IO-code to decide...
* trailing path separators will be preserved if they exist, no messing with that
* uses safe ByteString for filepaths under the hood instead of unsafe String
* fixes broken [dirname](https://github.com/chrisdone/path/issues/18)
* renames dirname/filename to basename/dirname to match the POSIX shell functions
* introduces a new `Path Fn` for safe filename guarantees and a `RelC` class
* allows pattern matching via unidirectional PatternSynonym
* uses simple doctest for testing
* allows `~/` as relative path, because on posix level `~` is just a regular filename that does _NOT_ point to `$HOME`
* remove TH, it sucks
## Differences to 'posix-paths'
* uses the `word8` package for save word8 literals instead of `OverloadedStrings`
* `hasTrailingPathSeparator` and `dropTrailingPathSeparator` behave in the same way as their `System.FilePath` counterpart
* added various functions:
* `equalFilePath`
* `getSearchPath`
* `hasParentDir`
* `hiddenFile`
* `isFileName`
* `isValid`
* `makeRelative`
* `makeValid`
* `normalise`
* `splitSearchPath`
* `stripExtension`
* has a custom versions of `openFd` which allows more control over the flags than its unix package counterpart
* adds a `getDirectoryContents'` version that works on Fd
## Examples in ghci
Start ghci via `cabal repl`:
```hs
-- enable OverloadedStrings
:set -XOverloadedStrings
-- import HPath.IO
import HPath.IO
-- parse an absolute path
abspath <- parseAbs "/home"
-- parse a relative path (e.g. user users home directory)
relpath <- parseRel "jule"
-- concatenate paths
let newpath = abspath </> relpath
-- get file type
getFileType newpath
-- return all contents of that directory
getDirsFiles newpath
-- return all contents of the parent directory
getDirsFiles (dirname newpath)
```
## Projects
* [![Hackage version](https://img.shields.io/hackage/v/hpath.svg?label=Hackage)](https://hackage.haskell.org/package/hpath) [hpath](./hpath): Support for well-typed paths
* [![Hackage version](https://img.shields.io/hackage/v/hpath-filepath.svg?label=Hackage)](https://hackage.haskell.org/package/hpath-filepath) [hpath-filepath](./hpath-filepath): ByteString based filepath manipulation (can be used without hpath)
* [![Hackage version](https://img.shields.io/hackage/v/hpath-directory.svg?label=Hackage)](https://hackage.haskell.org/package/hpath-directory) [hpath-directory](./hpath-directory): High-level IO operations for files/directories on raw ByteString filepaths (use hpath-io for the type-safe path version)
* [![Hackage version](https://img.shields.io/hackage/v/hpath-io.svg?label=Hackage)](https://hackage.haskell.org/package/hpath-io) [hpath-io](./hpath-io): High-level IO operations for files/directories utilizing type-safe Path
* [![Hackage version](https://img.shields.io/hackage/v/hpath-posix.svg?label=Hackage)](https://hackage.haskell.org/package/hpath-posix) [hpath-posix](./hpath-posix): Some low-level POSIX glue code that is not in 'unix'

13
cabal.project Normal file
View File

@@ -0,0 +1,13 @@
packages: ./hpath
./hpath-directory
./hpath-filepath
./hpath-io
./hpath-posix
./unix
package hpath-io
ghc-options: -O2 -fspec-constr-recursive=16 -fmax-worker-args=16
-- https://github.com/composewell/streamly/blob/master/docs/Build.md
package streamly
ghc-options: -O2 -fspec-constr-recursive=16 -fmax-worker-args=16

View File

@@ -1,14 +0,0 @@
module Main where
import Test.DocTest
import Test.HUnit
main =
doctest
["-isrc"
, "-XOverloadedStrings"
, "-XScopedTypeVariables"
, "src/HPath.hs"
]

View File

@@ -1,25 +0,0 @@
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Applicative
import System.Posix.Directory.Traversals
import Test.DocTest
import Test.HUnit
main = do
doctest
[ "-isrc"
, "-XOverloadedStrings"
, "System.Posix.FilePath"
]
runTestTT unitTests
unitTests :: Test
unitTests = test
[ TestCase $ do
r <- (==) <$> allDirectoryContents "." <*> allDirectoryContents' "."
assertBool "allDirectoryContents == allDirectoryContents'" r
]

View File

@@ -0,0 +1,9 @@
# Revision history for hpath-directory
## 0.13.1 -- 2020-01-29
* Split some functionality out into 'hpath-posix'
## 0.1.0.0 -- 2020-01-26
* First version. Released on an unsuspecting world.

30
hpath-directory/LICENSE Normal file
View File

@@ -0,0 +1,30 @@
Copyright (c) 2020, Julian Ospald
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Julian Ospald nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
hpath-directory/README.md Normal file
View File

@@ -0,0 +1,21 @@
# HPath-filepath
[![Gitter chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Hackage version](https://img.shields.io/hackage/v/hpath-directory.svg?label=Hackage)](https://hackage.haskell.org/package/hpath-directory) [![Build Status](https://api.travis-ci.org/hasufell/hpath.png?branch=master)](http://travis-ci.org/hasufell/hpath) [![Hackage-Deps](https://img.shields.io/hackage-deps/v/hpath-directory.svg)](http://packdeps.haskellers.com/feed?needle=hpath-directory)
Support high-level IO operations on files/directories, utilizing ByteString
as FilePaths.
This package is part of the HPath suite, also check out:
* [hpath](https://hackage.haskell.org/package/hpath)
* [hpath-filepath](https://hackage.haskell.org/package/hpath-filepath)
* [hpath-io](https://hackage.haskell.org/package/hpath-io)
## Motivation
This is basically a fork of [directory](https://hackage.haskell.org/package/directory), but is a complete rewrite and the API doesn't follow the directory package.
## Differences to 'posix-paths'
* has a custom versions of `openFd` which allows more control over the flags than its unix package counterpart
* adds a `getDirectoryContents'` version that works on Fd

View File

@@ -0,0 +1,114 @@
cabal-version: >=1.10
name: hpath-directory
version: 0.13.1
synopsis: Alternative to 'directory' package with ByteString based filepaths
description: This provides a safer alternative to the 'directory'
package. FilePaths are ByteString based, so this
package only works on POSIX systems.
For a more high-level version of this with
proper Path type, use 'hpath-io', which makes
use of this package.
homepage: https://github.com/hasufell/hpath
bug-reports: https://github.com/hasufell/hpath/issues
license: BSD3
license-file: LICENSE
author: Julian Ospald <hasufell@posteo.de>
maintainer: Julian Ospald <hasufell@posteo.de>
copyright: Julian Ospald <hasufell@posteo.de> 2020
category: Filesystem
build-type: Simple
extra-source-files: CHANGELOG.md
tested-with: GHC==7.10.3
, GHC==8.0.2
, GHC==8.2.2
, GHC==8.4.4
, GHC==8.6.5
, GHC==8.8.1
library
if os(windows)
build-depends: unbuildable<0
buildable: False
exposed-modules: System.Posix.RawFilePath.Directory
System.Posix.RawFilePath.Directory.Errors
-- other-modules:
-- other-extensions:
build-depends: base >= 4.8 && <5
, IfElse
, bytestring >= 0.10
, exceptions >= 0.10
, hpath-filepath >= 0.10.3
, hpath-posix >= 0.13
, safe-exceptions >= 0.1
, streamly >= 0.7
, streamly-bytestring >= 0.1.0.1
, time >= 1.8
, unix >= 2.5
, unix-bytestring >= 0.3
, utf8-string
if impl(ghc < 8.0)
build-depends:
fail >= 4.9
hs-source-dirs: src
default-language: Haskell2010
default-extensions: PackageImports
test-suite spec
if os(windows)
build-depends: unbuildable<0
buildable: False
Type: exitcode-stdio-1.0
Default-Language: Haskell2010
Hs-Source-Dirs: test
Main-Is: Main.hs
other-modules:
System.Posix.RawFilePath.Directory.AppendFileSpec
System.Posix.RawFilePath.Directory.CanonicalizePathSpec
System.Posix.RawFilePath.Directory.CopyDirRecursiveCollectFailuresSpec
System.Posix.RawFilePath.Directory.CopyDirRecursiveOverwriteSpec
System.Posix.RawFilePath.Directory.CopyDirRecursiveSpec
System.Posix.RawFilePath.Directory.CopyFileOverwriteSpec
System.Posix.RawFilePath.Directory.CopyFileSpec
System.Posix.RawFilePath.Directory.CreateDirIfMissingSpec
System.Posix.RawFilePath.Directory.CreateDirRecursiveSpec
System.Posix.RawFilePath.Directory.CreateDirSpec
System.Posix.RawFilePath.Directory.CreateRegularFileSpec
System.Posix.RawFilePath.Directory.CreateSymlinkSpec
System.Posix.RawFilePath.Directory.DeleteDirRecursiveSpec
System.Posix.RawFilePath.Directory.DeleteDirSpec
System.Posix.RawFilePath.Directory.DeleteFileSpec
System.Posix.RawFilePath.Directory.GetDirsFilesSpec
System.Posix.RawFilePath.Directory.GetFileTypeSpec
System.Posix.RawFilePath.Directory.MoveFileOverwriteSpec
System.Posix.RawFilePath.Directory.MoveFileSpec
System.Posix.RawFilePath.Directory.ReadFileSpec
System.Posix.RawFilePath.Directory.RecreateSymlinkOverwriteSpec
System.Posix.RawFilePath.Directory.RecreateSymlinkSpec
System.Posix.RawFilePath.Directory.RenameFileSpec
System.Posix.RawFilePath.Directory.ToAbsSpec
System.Posix.RawFilePath.Directory.WriteFileLSpec
System.Posix.RawFilePath.Directory.WriteFileSpec
Spec
Utils
GHC-Options: -Wall
Build-Depends: base
, HUnit
, IfElse
, bytestring >= 0.10.0.0
, hpath-directory
, hpath-filepath >= 0.10
, hpath-posix >= 0.13
, hspec >= 1.3
, process
, time >= 1.8
, unix
, unix-bytestring
, utf8-string
default-extensions: PackageImports
source-repository head
type: git
location: https://github.com/hasufell/hpath

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
module System.Posix.RawFilePath.Directory where
import System.Posix.ByteString.FilePath (RawFilePath)
canonicalizePath :: RawFilePath -> IO RawFilePath
toAbs :: RawFilePath -> IO RawFilePath
doesFileExist :: RawFilePath -> IO Bool
doesDirectoryExist :: RawFilePath -> IO Bool
isWritable :: RawFilePath -> IO Bool
canOpenDirectory :: RawFilePath -> IO Bool

View File

@@ -1,5 +1,5 @@
-- |
-- Module : HPath.IO.Errors
-- Module : System.Posix.RawFilePath.Directory.Errors
-- Copyright : © 2016 Julian Ospald
-- License : BSD3
--
@@ -12,7 +12,7 @@
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ScopedTypeVariables #-}
module HPath.IO.Errors
module System.Posix.RawFilePath.Directory.Errors
(
-- * Types
HPathIOException(..)
@@ -33,15 +33,12 @@ module HPath.IO.Errors
, throwSameFile
, sameFile
, throwDestinationInSource
, doesFileExist
, doesDirectoryExist
, isWritable
, canOpenDirectory
-- * Error handling functions
, catchErrno
, rethrowErrnoAs
, handleIOError
, hideError
, bracketeer
, reactOnError
)
@@ -52,7 +49,7 @@ import Control.Applicative
(
(<$>)
)
import Control.Exception
import Control.Exception.Safe hiding (handleIOError)
import Control.Monad
(
forM
@@ -66,6 +63,7 @@ import Data.ByteString
(
ByteString
)
import qualified Data.ByteString as BS
import Data.ByteString.UTF8
(
toString
@@ -83,24 +81,22 @@ import GHC.IO.Exception
(
IOErrorType
)
import HPath
import HPath.Internal
(
Path(..)
)
import {-# SOURCE #-} HPath.IO
import {-# SOURCE #-} System.Posix.RawFilePath.Directory
(
canonicalizePath
, toAbs
, doesFileExist
, doesDirectoryExist
, isWritable
, canOpenDirectory
)
import System.IO.Error
(
alreadyExistsErrorType
, catchIOError
, ioeGetErrorType
, mkIOError
)
import System.Posix.FilePath
import qualified System.Posix.Directory.ByteString as PFD
import System.Posix.Files.ByteString
(
@@ -149,9 +145,9 @@ toConstr RecursiveFailure {} = "RecursiveFailure"
isSameFile, isDestinationInSource, isRecursiveFailure :: HPathIOException -> Bool
isSameFile ex = toConstr (ex :: HPathIOException) == toConstr SameFile{}
isDestinationInSource ex = toConstr (ex :: HPathIOException) == toConstr DestinationInSource{}
isRecursiveFailure ex = toConstr (ex :: HPathIOException) == toConstr RecursiveFailure{}
isSameFile ex = toConstr (ex :: HPathIOException) == toConstr (SameFile mempty mempty)
isDestinationInSource ex = toConstr (ex :: HPathIOException) == (toConstr $ DestinationInSource mempty mempty)
isRecursiveFailure ex = toConstr (ex :: HPathIOException) == (toConstr $ RecursiveFailure mempty)
isReadContentsFailed, isCreateDirFailed, isCopyFileFailed, isRecreateSymlinkFailed ::RecursiveFailureHint -> Bool
@@ -174,9 +170,9 @@ isRecreateSymlinkFailed _ = False
-- |Throws `AlreadyExists` `IOError` if file exists.
throwFileDoesExist :: Path b -> IO ()
throwFileDoesExist fp@(MkPath bs) =
whenM (doesFileExist fp)
throwFileDoesExist :: RawFilePath -> IO ()
throwFileDoesExist bs =
whenM (doesFileExist bs)
(ioError . mkIOError
alreadyExistsErrorType
"File already exists"
@@ -186,9 +182,9 @@ throwFileDoesExist fp@(MkPath bs) =
-- |Throws `AlreadyExists` `IOError` if directory exists.
throwDirDoesExist :: Path b -> IO ()
throwDirDoesExist fp@(MkPath bs) =
whenM (doesDirectoryExist fp)
throwDirDoesExist :: RawFilePath -> IO ()
throwDirDoesExist bs =
whenM (doesDirectoryExist bs)
(ioError . mkIOError
alreadyExistsErrorType
"Directory already exists"
@@ -198,18 +194,18 @@ throwDirDoesExist fp@(MkPath bs) =
-- |Uses `isSameFile` and throws `SameFile` if it returns True.
throwSameFile :: Path b1
-> Path b2
throwSameFile :: RawFilePath
-> RawFilePath
-> IO ()
throwSameFile fp1@(MkPath bs1) fp2@(MkPath bs2) =
whenM (sameFile fp1 fp2)
throwSameFile bs1 bs2 =
whenM (sameFile bs1 bs2)
(throwIO $ SameFile bs1 bs2)
-- |Check if the files are the same by examining device and file id.
-- This follows symbolic links.
sameFile :: Path b1 -> Path b2 -> IO Bool
sameFile (MkPath fp1) (MkPath fp2) =
sameFile :: RawFilePath -> RawFilePath -> IO Bool
sameFile fp1 fp2 =
handleIOError (\_ -> return False) $ do
fs1 <- getFileStatus fp1
fs2 <- getFileStatus fp2
@@ -225,58 +221,24 @@ sameFile (MkPath fp1) (MkPath fp2) =
-- within the source directory by comparing the device+file ID of the
-- source directory with all device+file IDs of the parent directories
-- of the destination.
throwDestinationInSource :: Path b1 -- ^ source dir
-> Path b2 -- ^ full destination, @dirname dest@
-- must exist
throwDestinationInSource :: RawFilePath -- ^ source dir
-> RawFilePath -- ^ full destination, @dirname dest@
-- must exist
-> IO ()
throwDestinationInSource (MkPath sbs) dest@(MkPath dbs) = do
destAbs <- toAbs dest
dest' <- (\x -> maybe x (\y -> x </> y) $ basename dest)
<$> (canonicalizePath $ dirname destAbs)
dids <- forM (getAllParents dest') $ \p -> do
fs <- PF.getSymbolicLinkStatus (fromAbs p)
throwDestinationInSource sbs dbs = do
destAbs <- toAbs dbs
dest' <- (\x -> maybe x (\y -> x </> y) $ basename dbs)
<$> (canonicalizePath $ takeDirectory destAbs)
dids <- forM (takeAllParents dest') $ \p -> do
fs <- PF.getSymbolicLinkStatus p
return (PF.deviceID fs, PF.fileID fs)
sid <- fmap (\x -> (PF.deviceID x, PF.fileID x))
$ PF.getFileStatus sbs
when (elem sid dids)
(throwIO $ DestinationInSource dbs sbs)
-- |Checks if the given file exists and is not a directory.
-- Does not follow symlinks.
doesFileExist :: Path b -> IO Bool
doesFileExist (MkPath bs) =
handleIOError (\_ -> return False) $ do
fs <- PF.getSymbolicLinkStatus bs
return $ not . PF.isDirectory $ fs
-- |Checks if the given file exists and is a directory.
-- Does not follow symlinks.
doesDirectoryExist :: Path b -> IO Bool
doesDirectoryExist (MkPath bs) =
handleIOError (\_ -> return False) $ do
fs <- PF.getSymbolicLinkStatus bs
return $ PF.isDirectory fs
-- |Checks whether a file or folder is writable.
isWritable :: Path b -> IO Bool
isWritable (MkPath bs) =
handleIOError (\_ -> return False) $
fileAccess bs False True False
-- |Checks whether the directory at the given path exists and can be
-- opened. This invokes `openDirStream` which follows symlinks.
canOpenDirectory :: Path b -> IO Bool
canOpenDirectory (MkPath bs) =
handleIOError (\_ -> return False) $ do
bracket (PFD.openDirStream bs)
PFD.closeDirStream
(\_ -> return ())
return True
where
basename x = let b = takeBaseName x
in if BS.null b then Nothing else Just b
@@ -318,9 +280,13 @@ handleIOError :: (IOError -> IO a) -> IO a -> IO a
handleIOError = flip catchIOError
hideError :: IOErrorType -> IO () -> IO ()
hideError err = handleIO (\e -> if err == ioeGetErrorType e then pure () else ioError e)
-- |Like `bracket`, but allows to have different clean-up
-- actions depending on whether the in-between computation
-- has raised an exception or not.
-- has raised an exception or not.
bracketeer :: IO a -- ^ computation to run first
-> (a -> IO b) -- ^ computation to run last, when
-- no exception was raised
@@ -358,3 +324,4 @@ reactOnError a ios fmios =
(throwIO ex)
fmios

View File

@@ -1,19 +1,24 @@
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString as BS
import Data.IORef
import Test.Hspec
import Test.Hspec.Runner
import Test.Hspec.Formatters
import qualified Spec
import Utils
import System.Posix.Temp.ByteString (mkdtemp)
-- TODO: chardev, blockdev, namedpipe, socket
main :: IO ()
main =
main = do
tmpBase <- mkdtemp "/tmp/"
writeIORef baseTmpDir (Just (tmpBase `BS.append` "/"))
putStrLn $ ("Temporary test directory at: " ++ show tmpBase)
hspecWith
defaultConfig { configFormatter = Just progress }
$ beforeAll_ createBaseTmpDir
$ afterAll_ deleteBaseTmpDir
$ Spec.spec

View File

@@ -1,7 +1,7 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.AppendFileSpec where
module System.Posix.RawFilePath.Directory.AppendFileSpec where
import Test.Hspec
@@ -51,7 +51,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.appendFile" $ do
describe "System.Posix.RawFilePath.Directory.appendFile" $ do
-- successes --
it "appendFile file with content, everything clear" $ do

View File

@@ -1,6 +1,6 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.CanonicalizePathSpec where
module System.Posix.RawFilePath.Directory.CanonicalizePathSpec where
import Test.Hspec
@@ -41,7 +41,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.canonicalizePath" $ do
describe "System.Posix.RawFilePath.Directory.canonicalizePath" $ do
-- successes --
it "canonicalizePath, all fine" $ do

View File

@@ -1,13 +1,13 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.CopyDirRecursiveCollectFailuresSpec where
module System.Posix.RawFilePath.Directory.CopyDirRecursiveCollectFailuresSpec where
import Test.Hspec
import Data.List (sort)
import HPath.IO
import HPath.IO.Errors
import "hpath-directory" System.Posix.RawFilePath.Directory
import System.Posix.RawFilePath.Directory.Errors
import System.IO.Error
(
ioeGetErrorType
@@ -116,7 +116,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.copyDirRecursive" $ do
describe "System.Posix.RawFilePath.Directory.copyDirRecursive" $ do
-- successes --
it "copyDirRecursive (Strict, CollectFailures), all fine and compare" $ do
@@ -127,7 +127,8 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
CollectFailures
(system $ "diff -r --no-dereference "
++ toString tmpDir' ++ "inputDir" ++ " "
++ toString tmpDir' ++ "outputDir")
++ toString tmpDir' ++ "outputDir"
++ " >/dev/null")
`shouldReturn` ExitSuccess
removeDirIfExists "outputDir"

View File

@@ -1,12 +1,12 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.CopyDirRecursiveOverwriteSpec where
module System.Posix.RawFilePath.Directory.CopyDirRecursiveOverwriteSpec where
import Test.Hspec
import HPath.IO
import HPath.IO.Errors
import "hpath-directory" System.Posix.RawFilePath.Directory
import System.Posix.RawFilePath.Directory.Errors
import System.IO.Error
(
ioeGetErrorType
@@ -88,7 +88,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.copyDirRecursive" $ do
describe "System.Posix.RawFilePath.Directory.copyDirRecursive" $ do
-- successes --
it "copyDirRecursive (Overwrite, FailEarly), all fine" $ do
@@ -106,7 +106,8 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
FailEarly
(system $ "diff -r --no-dereference "
++ toString tmpDir' ++ "inputDir" ++ " "
++ toString tmpDir' ++ "outputDir")
++ toString tmpDir' ++ "outputDir"
++ " >/dev/null")
`shouldReturn` ExitSuccess
removeDirIfExists "outputDir"
@@ -114,7 +115,8 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
tmpDir' <- getRawTmpDir
(system $ "diff -r --no-dereference "
++ toString tmpDir' ++ "inputDir" ++ " "
++ toString tmpDir' ++ "alreadyExistsD")
++ toString tmpDir' ++ "alreadyExistsD"
++ " >/dev/null")
`shouldReturn` (ExitFailure 1)
copyDirRecursive' "inputDir"
"alreadyExistsD"
@@ -122,7 +124,8 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
FailEarly
(system $ "diff -r --no-dereference "
++ toString tmpDir' ++ "inputDir" ++ " "
++ toString tmpDir' ++ "alreadyExistsD")
++ toString tmpDir' ++ "alreadyExistsD"
++ " >/dev/null")
`shouldReturn` ExitSuccess
removeDirIfExists "outputDir"

View File

@@ -1,12 +1,12 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.CopyDirRecursiveSpec where
module System.Posix.RawFilePath.Directory.CopyDirRecursiveSpec where
import Test.Hspec
import HPath.IO
import HPath.IO.Errors
import "hpath-directory" System.Posix.RawFilePath.Directory
import System.Posix.RawFilePath.Directory.Errors
import System.IO.Error
(
ioeGetErrorType
@@ -73,7 +73,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.copyDirRecursive" $ do
describe "System.Posix.RawFilePath.Directory.copyDirRecursive" $ do
-- successes --
it "copyDirRecursive (Strict, FailEarly), all fine" $ do
@@ -91,7 +91,8 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
FailEarly
(system $ "diff -r --no-dereference "
++ toString tmpDir' ++ "inputDir" ++ " "
++ toString tmpDir' ++ "outputDir")
++ toString tmpDir' ++ "outputDir"
++ " >/dev/null")
`shouldReturn` ExitSuccess
removeDirIfExists "outputDir"

View File

@@ -1,11 +1,11 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.CopyFileOverwriteSpec where
module System.Posix.RawFilePath.Directory.CopyFileOverwriteSpec where
import Test.Hspec
import HPath.IO
import HPath.IO.Errors
import "hpath-directory" System.Posix.RawFilePath.Directory
import System.Posix.RawFilePath.Directory.Errors
import System.IO.Error
(
ioeGetErrorType
@@ -59,7 +59,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.copyFile" $ do
describe "System.Posix.RawFilePath.Directory.copyFile" $ do
-- successes --
it "copyFile (Overwrite), everything clear" $ do

View File

@@ -1,12 +1,12 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.CopyFileSpec where
module System.Posix.RawFilePath.Directory.CopyFileSpec where
import Test.Hspec
import HPath.IO
import HPath.IO.Errors
import "hpath-directory" System.Posix.RawFilePath.Directory
import System.Posix.RawFilePath.Directory.Errors
import System.IO.Error
(
ioeGetErrorType
@@ -58,7 +58,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.copyFile" $ do
describe "System.Posix.RawFilePath.Directory.copyFile" $ do
-- successes --
it "copyFile (Strict), everything clear" $ do

View File

@@ -0,0 +1,69 @@
{-# LANGUAGE OverloadedStrings #-}
module System.Posix.RawFilePath.Directory.CreateDirIfMissingSpec where
import Test.Hspec
import System.IO.Error
(
ioeGetErrorType
)
import GHC.IO.Exception
(
IOErrorType(..)
)
import Utils
upTmpDir :: IO ()
upTmpDir = do
setTmpDir "CreateDirIfMissingSpec"
createTmpDir
setupFiles :: IO ()
setupFiles = do
createDir' "alreadyExists"
createDir' "noPerms"
createDir' "noWritePerms"
noPerms "noPerms"
noWritableDirPerms "noWritePerms"
cleanupFiles :: IO ()
cleanupFiles = do
normalDirPerms "noPerms"
normalDirPerms "noWritePerms"
deleteDir' "alreadyExists"
deleteDir' "noPerms"
deleteDir' "noWritePerms"
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "System.Posix.RawFilePath.Directory.CreateDirIfMissing" $ do
-- successes --
it "createDirIfMissing, all fine" $ do
createDirIfMissing' "newDir"
removeDirIfExists "newDir"
it "createDirIfMissing, destination directory already exists" $
createDirIfMissing' "alreadyExists"
-- posix failures --
it "createDirIfMissing, parent directories do not exist" $
createDirIfMissing' "some/thing/dada"
`shouldThrow`
(\e -> ioeGetErrorType e == NoSuchThing)
it "createDirIfMissing, can't write to output directory" $
createDirIfMissing' "noWritePerms/newDir"
`shouldThrow`
(\e -> ioeGetErrorType e == PermissionDenied)
it "createDirIfMissing, can't open output directory" $
createDirIfMissing' "noPerms/newDir"
`shouldThrow`
(\e -> ioeGetErrorType e == PermissionDenied)

View File

@@ -1,6 +1,6 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.CreateDirRecursiveSpec where
module System.Posix.RawFilePath.Directory.CreateDirRecursiveSpec where
import Test.Hspec
@@ -42,7 +42,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.createDirRecursive" $ do
describe "System.Posix.RawFilePath.Directory.createDirRecursive" $ do
-- successes --
it "createDirRecursive, all fine" $ do

View File

@@ -1,6 +1,6 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.CreateDirSpec where
module System.Posix.RawFilePath.Directory.CreateDirSpec where
import Test.Hspec
@@ -42,7 +42,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.createDir" $ do
describe "System.Posix.RawFilePath.Directory.createDir" $ do
-- successes --
it "createDir, all fine" $ do

View File

@@ -1,6 +1,6 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.CreateRegularFileSpec where
module System.Posix.RawFilePath.Directory.CreateRegularFileSpec where
import Test.Hspec
@@ -40,7 +40,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.createRegularFile" $ do
describe "System.Posix.RawFilePath.Directory.createRegularFile" $ do
-- successes --
it "createRegularFile, all fine" $ do

View File

@@ -1,6 +1,6 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.CreateSymlinkSpec where
module System.Posix.RawFilePath.Directory.CreateSymlinkSpec where
import Test.Hspec
@@ -41,7 +41,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.createSymlink" $ do
describe "System.Posix.RawFilePath.Directory.createSymlink" $ do
-- successes --
it "createSymlink, all fine" $ do

View File

@@ -1,6 +1,6 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.DeleteDirRecursiveSpec where
module System.Posix.RawFilePath.Directory.DeleteDirRecursiveSpec where
import Test.Hspec
@@ -52,7 +52,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.deleteDirRecursive" $ do
describe "System.Posix.RawFilePath.Directory.deleteDirRecursive" $ do
-- successes --
it "deleteDirRecursive, empty directory, all fine" $ do

View File

@@ -1,6 +1,6 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.DeleteDirSpec where
module System.Posix.RawFilePath.Directory.DeleteDirSpec where
import Test.Hspec
@@ -53,7 +53,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.deleteDir" $ do
describe "System.Posix.RawFilePath.Directory.deleteDir" $ do
-- successes --
it "deleteDir, empty directory, all fine" $ do

View File

@@ -1,10 +1,10 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.DeleteFileSpec where
module System.Posix.RawFilePath.Directory.DeleteFileSpec where
import Test.Hspec
import HPath.IO
import "hpath-directory" System.Posix.RawFilePath.Directory
import System.IO.Error
(
ioeGetErrorType
@@ -47,7 +47,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.deleteFile" $ do
describe "System.Posix.RawFilePath.Directory.deleteFile" $ do
-- successes --
it "deleteFile, regular file, all fine" $ do

View File

@@ -1,14 +1,14 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.GetDirsFilesSpec where
module System.Posix.RawFilePath.Directory.GetDirsFilesSpec where
import Data.List
(
sort
)
import qualified HPath as P
import HPath.IO
import "hpath-directory" System.Posix.RawFilePath.Directory hiding (getDirsFiles')
import System.Posix.FilePath
import Test.Hspec
import System.IO.Error
(
@@ -54,20 +54,20 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.getDirsFiles" $ do
describe "System.Posix.RawFilePath.Directory.getDirsFiles" $ do
-- successes --
it "getDirsFiles, all fine" $
withRawTmpDir $ \p -> do
expectedFiles <- mapM P.parseRel [".hidden"
,"Lala"
,"dir"
,"dirsym"
,"file"
,"noPerms"
,"syml"]
let expectedFiles = [".hidden"
,"Lala"
,"dir"
,"dirsym"
,"file"
,"noPerms"
,"syml"]
(fmap sort $ getDirsFiles p)
`shouldReturn` fmap (p P.</>) expectedFiles
`shouldReturn` fmap (p </>) expectedFiles
-- posix failures --
it "getDirsFiles, nonexistent directory" $

View File

@@ -1,9 +1,9 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.GetFileTypeSpec where
module System.Posix.RawFilePath.Directory.GetFileTypeSpec where
import HPath.IO
import "hpath-directory" System.Posix.RawFilePath.Directory
import Test.Hspec
import System.IO.Error
(
@@ -48,7 +48,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.getFileType" $ do
describe "System.Posix.RawFilePath.Directory.getFileType" $ do
-- successes --
it "getFileType, regular file" $

View File

@@ -1,11 +1,11 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.MoveFileOverwriteSpec where
module System.Posix.RawFilePath.Directory.MoveFileOverwriteSpec where
import Test.Hspec
import HPath.IO
import HPath.IO.Errors
import "hpath-directory" System.Posix.RawFilePath.Directory
import System.Posix.RawFilePath.Directory.Errors
import System.IO.Error
(
ioeGetErrorType
@@ -52,7 +52,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.moveFile" $ do
describe "System.Posix.RawFilePath.Directory.moveFile" $ do
-- successes --
it "moveFile (Overwrite), all fine" $

View File

@@ -1,11 +1,11 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.MoveFileSpec where
module System.Posix.RawFilePath.Directory.MoveFileSpec where
import Test.Hspec
import HPath.IO
import HPath.IO.Errors
import "hpath-directory" System.Posix.RawFilePath.Directory
import System.Posix.RawFilePath.Directory.Errors
import System.IO.Error
(
ioeGetErrorType
@@ -54,7 +54,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.moveFile" $ do
describe "System.Posix.RawFilePath.Directory.moveFile" $ do
-- successes --
it "moveFile (Strict), all fine" $

View File

@@ -1,7 +1,7 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.ReadFileSpec where
module System.Posix.RawFilePath.Directory.ReadFileSpec where
import Test.Hspec
@@ -51,7 +51,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.readFile" $ do
describe "System.Posix.RawFilePath.Directory.readFile" $ do
-- successes --
it "readFile (Strict) file with content, everything clear" $ do

View File

@@ -1,14 +1,14 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.RecreateSymlinkOverwriteSpec where
module System.Posix.RawFilePath.Directory.RecreateSymlinkOverwriteSpec where
-- TODO: exception if destination exists but is not a file + `OverWrite` CopyMode
import Test.Hspec
import HPath.IO
import HPath.IO.Errors
import "hpath-directory" System.Posix.RawFilePath.Directory
import System.Posix.RawFilePath.Directory.Errors
import System.IO.Error
(
ioeGetErrorType
@@ -59,7 +59,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.recreateSymlink" $ do
describe "System.Posix.RawFilePath.Directory.recreateSymlink" $ do
-- successes --
it "recreateSymLink (Overwrite), all fine" $ do

View File

@@ -1,13 +1,13 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.RecreateSymlinkSpec where
module System.Posix.RawFilePath.Directory.RecreateSymlinkSpec where
import Test.Hspec
import HPath.IO
import HPath.IO.Errors
import "hpath-directory" System.Posix.RawFilePath.Directory
import System.Posix.RawFilePath.Directory.Errors
import System.IO.Error
(
ioeGetErrorType
@@ -55,7 +55,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.recreateSymlink" $ do
describe "System.Posix.RawFilePath.Directory.recreateSymlink" $ do
-- successes --
it "recreateSymLink (Strict), all fine" $ do

View File

@@ -1,10 +1,10 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.RenameFileSpec where
module System.Posix.RawFilePath.Directory.RenameFileSpec where
import Test.Hspec
import HPath.IO.Errors
import System.Posix.RawFilePath.Directory.Errors
import System.IO.Error
(
ioeGetErrorType
@@ -52,7 +52,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.renameFile" $ do
describe "System.Posix.RawFilePath.Directory.renameFile" $ do
-- successes --
it "renameFile, all fine" $

View File

@@ -1,26 +1,25 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.ToAbsSpec where
module System.Posix.RawFilePath.Directory.ToAbsSpec where
import Test.Hspec
import HPath
import HPath.IO
import "hpath-directory" System.Posix.RawFilePath.Directory
spec :: Spec
spec = describe "HPath.IO.toAbs" $ do
spec = describe "System.Posix.RawFilePath.Directory.toAbs" $ do
-- successes --
it "toAbs returns absolute paths unchanged" $ do
p1 <- parseAbs "/a/b/c/d"
let p1 = "/a/b/c/d"
to <- toAbs p1
p1 `shouldBe` to
it "toAbs returns even existing absolute paths unchanged" $ do
p1 <- parseAbs "/home"
let p1 = "/home"
to <- toAbs p1
p1 `shouldBe` to

View File

@@ -0,0 +1,108 @@
{-# LANGUAGE OverloadedStrings #-}
module System.Posix.RawFilePath.Directory.WriteFileLSpec where
import Test.Hspec
import System.IO.Error
(
ioeGetErrorType
)
import GHC.IO.Exception
(
IOErrorType(..)
)
import Utils
upTmpDir :: IO ()
upTmpDir = do
setTmpDir "WriteFileLSpec"
createTmpDir
setupFiles :: IO ()
setupFiles = do
createRegularFile' "fileWithContent"
createRegularFile' "fileWithoutContent"
createSymlink' "inputFileSymL" "fileWithContent"
createDir' "alreadyExistsD"
createRegularFile' "noPerms"
noPerms "noPerms"
createDir' "noPermsD"
createRegularFile' "noPermsD/inputFile"
noPerms "noPermsD"
writeFile' "fileWithContent" "BLKASL"
cleanupFiles :: IO ()
cleanupFiles = do
deleteFile' "fileWithContent"
deleteFile' "fileWithoutContent"
deleteFile' "inputFileSymL"
deleteDir' "alreadyExistsD"
normalFilePerms "noPerms"
deleteFile' "noPerms"
normalDirPerms "noPermsD"
deleteFile' "noPermsD/inputFile"
deleteDir' "noPermsD"
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "System.Posix.RawFilePath.Directory.WriteFileL" $ do
-- successes --
it "WriteFileL file with content, everything clear" $ do
writeFileL' "fileWithContent" "blahfaselllll"
out <- readFile' "fileWithContent"
out `shouldBe` "blahfaselllll"
it "WriteFileL file with content, everything clear" $ do
writeFileL' "fileWithContent" "gagagaga"
out <- readFile' "fileWithContent"
out `shouldBe` "gagagaga"
it "WriteFileL file with content, everything clear" $ do
writeFileL' "fileWithContent" ""
out <- readFile' "fileWithContent"
out `shouldBe` ""
it "WriteFileL file without content, everything clear" $ do
writeFileL' "fileWithoutContent" "blahfaselllll"
out <- readFile' "fileWithoutContent"
out `shouldBe` "blahfaselllll"
it "WriteFileL, everything clear" $ do
writeFileL' "fileWithoutContent" "gagagaga"
out <- readFile' "fileWithoutContent"
out `shouldBe` "gagagaga"
it "WriteFileL symlink, everything clear" $ do
writeFileL' "inputFileSymL" "blahfaselllll"
out <- readFile' "inputFileSymL"
out `shouldBe` "blahfaselllll"
it "WriteFileL symlink, everything clear" $ do
writeFileL' "inputFileSymL" "gagagaga"
out <- readFile' "inputFileSymL"
out `shouldBe` "gagagaga"
-- posix failures --
it "WriteFileL to dir, inappropriate type" $ do
writeFileL' "alreadyExistsD" ""
`shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)
it "WriteFileL, no permissions to file" $ do
writeFileL' "noPerms" ""
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
it "WriteFileL, no permissions to file" $ do
writeFileL' "noPermsD/inputFile" ""
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
it "WriteFileL, file does not exist" $ do
writeFileL' "gaga" ""
`shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)

View File

@@ -1,7 +1,7 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.WriteFileSpec where
module System.Posix.RawFilePath.Directory.WriteFileSpec where
import Test.Hspec
@@ -51,7 +51,7 @@ cleanupFiles = do
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.writeFile" $ do
describe "System.Posix.RawFilePath.Directory.writeFile" $ do
-- successes --
it "writeFile file with content, everything clear" $ do

View File

@@ -1,5 +1,4 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PackageImports #-}
module Utils where
@@ -19,6 +18,7 @@ import Control.Monad.IfElse
whenM
)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import Data.IORef
(
newIORef
@@ -26,28 +26,23 @@ import Data.IORef
, writeIORef
, IORef
)
import HPath.IO
import HPath.IO.Errors
import "hpath-directory" System.Posix.RawFilePath.Directory
import Prelude hiding (appendFile, readFile, writeFile)
import Data.Maybe
(
fromJust
)
import qualified HPath as P
import System.IO.Unsafe
(
unsafePerformIO
)
import qualified System.Posix.Directory.Traversals as DT
import System.Posix.Env.ByteString
(
getEnv
)
import qualified System.Posix.RawFilePath.Directory.Traversals as DT
import Data.ByteString
(
ByteString
)
import qualified Data.ByteString.Lazy as L
import System.Posix.FilePath
import System.Posix.Files.ByteString
(
groupExecuteMode
@@ -61,15 +56,14 @@ import System.Posix.Files.ByteString
, unionFileModes
)
baseTmpDir :: IORef (Maybe ByteString)
{-# NOINLINE baseTmpDir #-}
baseTmpDir = unsafePerformIO (newIORef Nothing)
baseTmpDir :: ByteString
baseTmpDir = "test/HPath/IO/tmp/"
tmpDir :: IORef ByteString
tmpDir :: IORef (Maybe ByteString)
{-# NOINLINE tmpDir #-}
tmpDir = unsafePerformIO (newIORef baseTmpDir)
tmpDir = unsafePerformIO (newIORef Nothing)
@@ -80,75 +74,63 @@ tmpDir = unsafePerformIO (newIORef baseTmpDir)
setTmpDir :: ByteString -> IO ()
{-# NOINLINE setTmpDir #-}
setTmpDir bs = writeIORef tmpDir (baseTmpDir `BS.append` bs)
setTmpDir bs = do
tmp <- fromJust <$> readIORef baseTmpDir
writeIORef tmpDir (Just (tmp `BS.append` bs))
createTmpDir :: IO ()
{-# NOINLINE createTmpDir #-}
createTmpDir = do
pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs
tmp <- P.parseRel =<< readIORef tmpDir
void $ createDir newDirPerms (pwd P.</> tmp)
tmp <- fromJust <$> readIORef tmpDir
void $ createDir newDirPerms tmp
deleteTmpDir :: IO ()
{-# NOINLINE deleteTmpDir #-}
deleteTmpDir = do
pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs
tmp <- P.parseRel =<< readIORef tmpDir
void $ deleteDir (pwd P.</> tmp)
createBaseTmpDir :: IO ()
{-# NOINLINE createBaseTmpDir #-}
createBaseTmpDir = do
pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs
tmp <- P.parseRel baseTmpDir
void $ createDir newDirPerms (pwd P.</> tmp)
tmp <- fromJust <$> readIORef tmpDir
void $ deleteDir tmp
deleteBaseTmpDir :: IO ()
{-# NOINLINE deleteBaseTmpDir #-}
deleteBaseTmpDir = do
pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs
tmp <- P.parseRel baseTmpDir
contents <- getDirsFiles (pwd P.</> tmp)
tmp <- fromJust <$> readIORef baseTmpDir
contents <- getDirsFiles tmp
forM_ contents deleteDir
void $ deleteDir (pwd P.</> tmp)
void $ deleteDir tmp
withRawTmpDir :: (P.Path P.Abs -> IO a) -> IO a
withRawTmpDir :: (ByteString -> IO a) -> IO a
{-# NOINLINE withRawTmpDir #-}
withRawTmpDir f = do
pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs
tmp <- P.parseRel =<< readIORef tmpDir
f (pwd P.</> tmp)
tmp <- fromJust <$> readIORef tmpDir
f tmp
getRawTmpDir :: IO ByteString
{-# NOINLINE getRawTmpDir #-}
getRawTmpDir = withRawTmpDir (return . flip BS.append "/" . P.fromAbs)
getRawTmpDir = withRawTmpDir (return . flip BS.append "/")
withTmpDir :: ByteString -> (P.Path P.Abs -> IO a) -> IO a
withTmpDir :: ByteString -> (ByteString -> IO a) -> IO a
{-# NOINLINE withTmpDir #-}
withTmpDir ip f = do
pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs
tmp <- P.parseRel =<< readIORef tmpDir
p <- (pwd P.</> tmp P.</>) <$> P.parseRel ip
tmp <- fromJust <$> readIORef tmpDir
let p = tmp </> ip
f p
withTmpDir' :: ByteString
-> ByteString
-> (P.Path P.Abs -> P.Path P.Abs -> IO a)
-> (ByteString -> ByteString -> IO a)
-> IO a
{-# NOINLINE withTmpDir' #-}
withTmpDir' ip1 ip2 f = do
pwd <- fromJust <$> getEnv "PWD" >>= P.parseAbs
tmp <- P.parseRel =<< readIORef tmpDir
p1 <- (pwd P.</> tmp P.</>) <$> P.parseRel ip1
p2 <- (pwd P.</> tmp P.</>) <$> P.parseRel ip2
tmp <- fromJust <$> readIORef tmpDir
let p1 = tmp </> ip1
let p2 = tmp </> ip2
f p1 p2
@@ -181,6 +163,10 @@ createDir' :: ByteString -> IO ()
{-# NOINLINE createDir' #-}
createDir' dest = withTmpDir dest (createDir newDirPerms)
createDirIfMissing' :: ByteString -> IO ()
{-# NOINLINE createDirIfMissing' #-}
createDirIfMissing' dest = withTmpDir dest (createDirIfMissing newDirPerms)
createDirRecursive' :: ByteString -> IO ()
{-# NOINLINE createDirRecursive' #-}
createDirRecursive' dest = withTmpDir dest (createDirRecursive newDirPerms)
@@ -221,7 +207,7 @@ recreateSymlink' inputFileP outputFileP cm =
noWritableDirPerms :: ByteString -> IO ()
{-# NOINLINE noWritableDirPerms #-}
noWritableDirPerms path = withTmpDir path $ \p ->
setFileMode (P.fromAbs p) perms
setFileMode p perms
where
perms = ownerReadMode
`unionFileModes` ownerExecuteMode
@@ -233,19 +219,19 @@ noWritableDirPerms path = withTmpDir path $ \p ->
noPerms :: ByteString -> IO ()
{-# NOINLINE noPerms #-}
noPerms path = withTmpDir path $ \p -> setFileMode (P.fromAbs p) nullFileMode
noPerms path = withTmpDir path $ \p -> setFileMode p nullFileMode
normalDirPerms :: ByteString -> IO ()
{-# NOINLINE normalDirPerms #-}
normalDirPerms path =
withTmpDir path $ \p -> setFileMode (P.fromAbs p) newDirPerms
withTmpDir path $ \p -> setFileMode p newDirPerms
normalFilePerms :: ByteString -> IO ()
{-# NOINLINE normalFilePerms #-}
normalFilePerms path =
withTmpDir path $ \p -> setFileMode (P.fromAbs p) newFilePerms
withTmpDir path $ \p -> setFileMode p newFilePerms
getFileType' :: ByteString -> IO FileType
@@ -253,7 +239,7 @@ getFileType' :: ByteString -> IO FileType
getFileType' path = withTmpDir path getFileType
getDirsFiles' :: ByteString -> IO [P.Path P.Abs]
getDirsFiles' :: ByteString -> IO [ByteString]
{-# NOINLINE getDirsFiles' #-}
getDirsFiles' path = withTmpDir path getDirsFiles
@@ -273,15 +259,20 @@ deleteDirRecursive' :: ByteString -> IO ()
deleteDirRecursive' p = withTmpDir p deleteDirRecursive
canonicalizePath' :: ByteString -> IO (P.Path P.Abs)
canonicalizePath' :: ByteString -> IO ByteString
{-# NOINLINE canonicalizePath' #-}
canonicalizePath' p = withTmpDir p canonicalizePath
writeFile' :: ByteString -> ByteString -> IO ()
{-# NOINLINE writeFile' #-}
writeFile' ip bs =
withTmpDir ip $ \p -> writeFile p bs
writeFile' ip bs =
withTmpDir ip $ \p -> writeFile p Nothing bs
writeFileL' :: ByteString -> BSL.ByteString -> IO ()
{-# NOINLINE writeFileL' #-}
writeFileL' ip bs =
withTmpDir ip $ \p -> writeFileL p Nothing bs
appendFile' :: ByteString -> ByteString -> IO ()
@@ -293,15 +284,10 @@ appendFile' ip bs =
allDirectoryContents' :: ByteString -> IO [ByteString]
{-# NOINLINE allDirectoryContents' #-}
allDirectoryContents' ip =
withTmpDir ip $ \p -> DT.allDirectoryContents' (P.fromAbs p)
withTmpDir ip $ \p -> DT.allDirectoryContents' p
readFile' :: ByteString -> IO ByteString
{-# NOINLINE readFile' #-}
readFile' p = withTmpDir p readFile
readFileEOF' :: ByteString -> IO L.ByteString
{-# NOINLINE readFileEOF' #-}
readFileEOF' p = withTmpDir p readFileEOF
readFile' p = withTmpDir p (fmap L.toStrict . readFile)

View File

@@ -0,0 +1,14 @@
# Revision history for hpath-filepath
## 0.10.4 -- 2020-01-26
* Add `takeAllParents`
## 0.10.2 -- 2020-01-18
* Add `isSpecialDirectoryEntry`
## 0.10.0 -- 2020-01-04
* First version. Split from 'hpath', contains only the filepath ByteString manipulation parts.

30
hpath-filepath/LICENSE Normal file
View File

@@ -0,0 +1,30 @@
Copyright (c) 2020, Julian Ospald
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Julian Ospald nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

29
hpath-filepath/README.md Normal file
View File

@@ -0,0 +1,29 @@
# HPath-filepath
[![Gitter chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Hackage version](https://img.shields.io/hackage/v/hpath-filepath.svg?label=Hackage)](https://hackage.haskell.org/package/hpath-filepath) [![Build Status](https://api.travis-ci.org/hasufell/hpath.png?branch=master)](http://travis-ci.org/hasufell/hpath) [![Hackage-Deps](https://img.shields.io/hackage-deps/v/hpath-filepath.svg)](http://packdeps.haskellers.com/feed?needle=hpath-filepath)
Support for bytestring based filepath manipulation, similar to 'filepath'.
This package is part of the HPath suite, also check out:
* [hpath](https://hackage.haskell.org/package/hpath)
* [hpath-directory](https://hackage.haskell.org/package/hpath-directory)
* [hpath-io](https://hackage.haskell.org/package/hpath-io)
## Motivation
This is basically a fork of [posix-paths](https://github.com/JohnLato/posix-paths), which seemed to have stalled development.
There is also a similar library [filepath-bytestring](https://hackage.haskell.org/package/filepath-bytestring), but it doesn't follow an open development model and is cross-platform, which this library is not interested in.
## Differences to 'posix-paths'
* uses the `word8` package for save word8 literals instead of `OverloadedStrings`
* `hasTrailingPathSeparator` and `dropTrailingPathSeparator` behave in the same way as their `System.FilePath` counterpart
* has some additional functions
## Differences to 'filepath-bytestring'
* uses the `word8` package for save word8 literals instead of `OverloadedStrings`
* is not cross-platform (less odd code to maintain)
* has some additional functions

2
hpath-filepath/Setup.hs Normal file
View File

@@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View File

@@ -0,0 +1,39 @@
name: hpath-filepath
version: 0.10.4
synopsis: ByteString based filepath manipulation
description: ByteString based filepath manipulation, similar to 'filepath' package. This is POSIX only.
-- bug-reports:
license: BSD3
license-file: LICENSE
author: Julian Ospald <hasufell@posteo.de>
maintainer: Julian Ospald <hasufell@posteo.de>
copyright: Julian Ospald 2016
category: Filesystem
build-type: Simple
cabal-version: 1.14
tested-with: GHC==7.10.3
, GHC==8.0.2
, GHC==8.2.2
, GHC==8.4.4
, GHC==8.6.5
, GHC==8.8.1
extra-source-files: README.md
CHANGELOG.md
library
if os(windows)
build-depends: unbuildable<0
buildable: False
exposed-modules: System.Posix.FilePath
-- other-modules:
-- other-extensions:
build-depends: base >=4.8 && <5
, bytestring >= 0.10.0.0
, unix >= 2.5
, word8
hs-source-dirs: src
default-language: Haskell2010
source-repository head
type: git
location: https://github.com/hasufell/hpath

23
hpath-filepath/run-doctests.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/sh
set -e
if [ -n "${SKIP_DOCTESTS}" ] ; then
echo "Skipping doctests"
exit 0
fi
if ! command -v doctest >/dev/null ; then
tempdir="$(mktemp -d)"
(
cd "${tempdir}"
cabal install --installdir="${tempdir}" doctest
)
export PATH="${tempdir}:$PATH"
fi
set -x
cd "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)"
cabal exec doctest -- -isrc -XOverloadedStrings System.Posix.FilePath

View File

@@ -59,6 +59,7 @@ module System.Posix.FilePath (
, splitPath
, joinPath
, splitDirectories
, takeAllParents
-- * Trailing slash functions
, hasTrailingPathSeparator
@@ -73,6 +74,7 @@ module System.Posix.FilePath (
, isAbsolute
, isValid
, makeValid
, isSpecialDirectoryEntry
, isFileName
, hasParentDir
, hiddenFile
@@ -96,6 +98,7 @@ import Control.Arrow (second)
-- $setup
-- >>> import Data.Char
-- >>> import Data.Maybe
-- >>> import Data.Word8
-- >>> import Test.QuickCheck
-- >>> import Control.Applicative
-- >>> import qualified Data.ByteString as BS
@@ -484,6 +487,8 @@ joinPath = foldr (</>) BS.empty
--
-- >>> splitDirectories "/path/to/file.txt"
-- ["/","path","to","file.txt"]
-- >>> splitDirectories "path/to/file.txt"
-- ["path","to","file.txt"]
-- >>> splitDirectories ""
-- []
splitDirectories :: RawFilePath -> [RawFilePath]
@@ -496,6 +501,21 @@ splitDirectories x
splitter = filter (not . BS.null) . BS.split pathSeparator
-- |Get all parents of a path.
--
-- >>> takeAllParents "/abs/def/dod"
-- ["/abs/def","/abs","/"]
-- >>> takeAllParents "/foo"
-- ["/"]
-- >>> takeAllParents "/"
-- []
takeAllParents :: RawFilePath -> [RawFilePath]
takeAllParents p
| np == BS.singleton pathSeparator = []
| otherwise = takeDirectory np : takeAllParents (takeDirectory np)
where
np = normalise p
------------------------
-- Trailing slash functions
@@ -723,6 +743,22 @@ makeValid path
| otherwise = BS.map (\x -> if x == _nul then _underscore else x) path
-- | Whether the filename is a special directory entry
-- (. and ..). Does not normalise filepaths.
--
-- >>> isSpecialDirectoryEntry "."
-- True
-- >>> isSpecialDirectoryEntry ".."
-- True
-- >>> isSpecialDirectoryEntry "/random_ path:*"
-- False
isSpecialDirectoryEntry :: RawFilePath -> Bool
isSpecialDirectoryEntry filepath
| BS.pack [_period, _period] == filepath = True
| BS.pack [_period] == filepath = True
| otherwise = False
-- | Is the given path a valid filename? This includes
-- "." and "..".
--

43
hpath-io/CHANGELOG.md Normal file
View File

@@ -0,0 +1,43 @@
# Revision history for hpath-io
## 0.13.0 -- 2020-01-26
* switch to using 'hpath-bytestring' for the implementation (this is now just a wrapper module, mostly)
## 0.12.0 -- 2020-01-20
* breaking API changes
* RelC and Fn were removed from `hpath`
* further changes to `parseAny`
## 0.11.0 -- 2020-01-18
* `writeFile` not allows to set file mode and create file if it does not exist (this broke API)
* added various new functions:
* createDirIfMissing
* writeFileL (for lazy bytestring)
* isReadable
* isExecutable
* getModificationTime
* setModificationTime
* setModificationTimeHiRes
* getDirsFiles' (returns filenames instead of paths)
* withRawFilePath
* withHandle
## 0.10.1 -- 2020-01-13
* Move file check functions to HPath.IO
* Add 'doesExist'
* Exception handling of `doesExist`, `doesFileExist`, `doesDirectoryExist` has changed: only eNOENT is catched
* Exception handling of `isWritable` has changed: just a wrapper around `access` now
* switch exception handling to `safe-exceptions`
* Redo file reading API (readFileEOF dropped and now using streamly under the hood, added `readFileStream`)
## 0.10.0 -- 2020-01-04
* First version. Split from 'hpath', contains only the IO parts.
* Now uses streamly for 'copyFile'
* Fixed tmpdir in hspec

30
hpath-io/LICENSE Normal file
View File

@@ -0,0 +1,30 @@
Copyright (c) 2020, Julian Ospald
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Julian Ospald nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

27
hpath-io/README.md Normal file
View File

@@ -0,0 +1,27 @@
# HPath-IO
[![Gitter chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Hackage version](https://img.shields.io/hackage/v/hpath-io.svg?label=Hackage)](https://hackage.haskell.org/package/hpath-io) [![Build Status](https://api.travis-ci.org/hasufell/hpath.png?branch=master)](http://travis-ci.org/hasufell/hpath) [![Hackage-Deps](https://img.shields.io/hackage-deps/v/hpath-io.svg)](http://packdeps.haskellers.com/feed?needle=hpath-io)
High-level IO operations on files/directories, utilizing type-safe Paths. This uses [hpath-directory](https://hackage.haskell.org/package/hpath-directory) under the hood.
This package is part of the HPath suite, also check out:
* [hpath](https://hackage.haskell.org/package/hpath)
* [hpath-directory](https://hackage.haskell.org/package/hpath-directory)
* [hpath-filepath](https://hackage.haskell.org/package/hpath-filepath)
## Motivation
The motivation came during development of
[hsfm](https://github.com/hasufell/hsfm)
in order to have a proper high-level API of file related operations,
while utilizing type-safe Paths.
## Goals
* high-level API to file operations like recursive directory copy
* still allowing sufficient control to interact with the underlying low-level calls
* unit-testing exceptions (because yes, people may rely on them)
Note: this library was written for __posix__ systems and it will probably not support other systems.

2
hpath-io/Setup.hs Normal file
View File

@@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

6
hpath-io/TODO.md Normal file
View File

@@ -0,0 +1,6 @@
# TODO
## Tests
* `doesExist` not tested
* `readFileStream` only implicitly tested by `readFile`

46
hpath-io/hpath-io.cabal Normal file
View File

@@ -0,0 +1,46 @@
name: hpath-io
version: 0.13.1
synopsis: High-level IO operations on files/directories
description: High-level IO operations on files/directories, utilizing type-safe Paths
-- bug-reports:
license: BSD3
license-file: LICENSE
author: Julian Ospald <hasufell@posteo.de>
maintainer: Julian Ospald <hasufell@posteo.de>
copyright: Julian Ospald 2016
category: Filesystem
build-type: Simple
cabal-version: 1.14
tested-with: GHC==7.10.3
, GHC==8.0.2
, GHC==8.2.2
, GHC==8.4.4
, GHC==8.6.5
, GHC==8.8.1
extra-source-files: README.md
CHANGELOG.md
library
if os(windows)
build-depends: unbuildable<0
buildable: False
exposed-modules: HPath.IO
build-depends: base >= 4.8 && <5
, bytestring >= 0.10.0.0
, exceptions
, hpath >= 0.11 && < 0.12
, hpath-directory >= 0.13 && < 0.14
, hpath-posix >= 0.13 && < 0.14
, safe-exceptions >= 0.1
, streamly >= 0.7
, time >= 1.8
, unix >= 2.5
if !impl(ghc>=7.11)
build-depends: transformers
hs-source-dirs: src
default-language: Haskell2010
source-repository head
type: git
location: https://github.com/hasufell/hpath

852
hpath-io/src/HPath/IO.hs Normal file
View File

@@ -0,0 +1,852 @@
-- |
-- Module : HPath.IO
-- Copyright : © 2016 Julian Ospald
-- License : BSD3
--
-- Maintainer : Julian Ospald <hasufell@posteo.de>
-- Stability : experimental
-- Portability : portable
--
-- This module provides high-level IO related file operations like
-- copy, delete, move and so on. It only operates on /Path x/ which
-- guarantees us well-typed paths. This is a thin wrapper over
-- System.Posix.RawFilePath.Directory in 'hpath-directory'. It's
-- encouraged to use this module.
--
-- Some of these operations are due to their nature __not atomic__, which
-- means they may do multiple syscalls which form one context. Some
-- of them also have to examine the filetypes explicitly before the
-- syscalls, so a reasonable decision can be made. That means
-- the result is undefined if another process changes that context
-- while the non-atomic operation is still happening. However, where
-- possible, as few syscalls as possible are used and the underlying
-- exception handling is kept.
--
-- Note: `BlockDevice`, `CharacterDevice`, `NamedPipe` and `Socket`
-- are ignored by some of the more high-level functions (like `easyCopy`).
-- For other functions (like `copyFile`), the behavior on these file types is
-- unreliable/unsafe. Check the documentation of those functions for details.
{-# LANGUAGE PackageImports #-}
module HPath.IO
(
-- * Types
FileType(..)
, RecursiveErrorMode(..)
, CopyMode(..)
-- * File copying
, copyDirRecursive
, recreateSymlink
, copyFile
, easyCopy
-- * File deletion
, deleteFile
, deleteDir
, deleteDirRecursive
, easyDelete
-- * File opening
, openFile
, executeFile
-- * File creation
, createRegularFile
, createDir
, createDirIfMissing
, createDirRecursive
, createSymlink
-- * File renaming/moving
, renameFile
, moveFile
-- * File reading
, readFile
, readFileStream
-- * File writing
, writeFile
, writeFileL
, appendFile
-- * File permissions
, RD.newFilePerms
, RD.newDirPerms
-- * File checks
, doesExist
, doesFileExist
, doesDirectoryExist
, isReadable
, isWritable
, isExecutable
, canOpenDirectory
-- * File times
, getModificationTime
, setModificationTime
, setModificationTimeHiRes
-- * Directory reading
, getDirsFiles
, getDirsFiles'
-- * Filetype operations
, getFileType
-- * Others
, canonicalizePath
, toAbs
, withRawFilePath
, withHandle
, module System.Posix.RawFilePath.Directory.Errors
)
where
import Control.Exception.Safe ( bracketOnError
, finally
)
import Control.Monad.Catch ( MonadThrow(..) )
import Data.ByteString ( ByteString )
import Data.Traversable ( for )
import qualified Data.ByteString.Lazy as L
import Data.Time.Clock
import Data.Time.Clock.POSIX ( POSIXTime )
import HPath
import Prelude hiding ( appendFile
, readFile
, writeFile
)
import Streamly
import qualified System.IO as SIO
import System.Posix.Directory.ByteString
( getWorkingDirectory )
import qualified "unix" System.Posix.IO.ByteString
as SPI
import System.Posix.FD ( openFd )
import System.Posix.RawFilePath.Directory.Errors
import System.Posix.Types ( FileMode
, ProcessID
, EpochTime
)
import qualified System.Posix.RawFilePath.Directory
as RD
import System.Posix.RawFilePath.Directory
( FileType
, RecursiveErrorMode
, CopyMode
)
--------------------
--[ File Copying ]--
--------------------
-- |Copies the contents of a directory recursively to the given destination, while preserving permissions.
-- Does not follow symbolic links. This behaves more or less like
-- the following, without descending into the destination if it
-- already exists:
--
-- @
-- cp -a \/source\/dir \/destination\/somedir
-- @
--
-- For directory contents, this will ignore any file type that is not
-- `RegularFile`, `SymbolicLink` or `Directory`.
--
-- For `Overwrite` copy mode this does not prune destination directory
-- contents, so the destination might contain more files than the source after
-- the operation has completed. Permissions of existing directories are
-- fixed.
--
-- Safety/reliability concerns:
--
-- * not atomic
-- * examines filetypes explicitly
-- * an explicit check `throwDestinationInSource` is carried out for the
-- top directory for basic sanity, because otherwise we might end up
-- with an infinite copy loop... however, this operation is not
-- carried out recursively (because it's slow)
--
-- Throws:
--
-- - `NoSuchThing` if source directory does not exist
-- - `PermissionDenied` if source directory can't be opened
-- - `SameFile` if source and destination are the same file
-- (`HPathIOException`)
-- - `DestinationInSource` if destination is contained in source
-- (`HPathIOException`)
--
-- Throws in `FailEarly` RecursiveErrorMode only:
--
-- - `PermissionDenied` if output directory is not writable
-- - `InvalidArgument` if source directory is wrong type (symlink)
-- - `InappropriateType` if source directory is wrong type (regular file)
--
-- Throws in `CollectFailures` RecursiveErrorMode only:
--
-- - `RecursiveFailure` if any of the recursive operations that are not
-- part of the top-directory sanity-checks fail (`HPathIOException`)
--
-- Throws in `Strict` CopyMode only:
--
-- - `AlreadyExists` if destination already exists
--
-- Note: may call `getcwd` (only if destination is a relative path)
copyDirRecursive :: Path b1 -- ^ source dir
-> Path b2 -- ^ destination (parent dirs
-- are not automatically created)
-> CopyMode
-> RecursiveErrorMode
-> IO ()
copyDirRecursive (Path fromp) (Path destdirp) cm rm =
RD.copyDirRecursive fromp destdirp cm rm
-- |Recreate a symlink.
--
-- In `Overwrite` copy mode only files and empty directories are deleted.
--
-- Safety/reliability concerns:
--
-- * `Overwrite` mode is inherently non-atomic
--
-- Throws:
--
-- - `InvalidArgument` if source file is wrong type (not a symlink)
-- - `PermissionDenied` if output directory cannot be written to
-- - `PermissionDenied` if source directory cannot be opened
-- - `SameFile` if source and destination are the same file
-- (`HPathIOException`)
--
--
-- Throws in `Strict` mode only:
--
-- - `AlreadyExists` if destination already exists
--
-- Throws in `Overwrite` mode only:
--
-- - `UnsatisfiedConstraints` if destination file is non-empty directory
--
-- Notes:
--
-- - calls `symlink`
-- - calls `getcwd` in Overwrite mode (if destination is a relative path)
recreateSymlink :: Path b1 -- ^ the old symlink file
-> Path b2 -- ^ destination file
-> CopyMode
-> IO ()
recreateSymlink (Path symsourceBS) (Path newsymBS) cm =
RD.recreateSymlink symsourceBS newsymBS cm
-- |Copies the given regular file to the given destination.
-- Neither follows symbolic links, nor accepts them.
-- For "copying" symbolic links, use `recreateSymlink` instead.
--
-- Note that this is still sort of a low-level function and doesn't
-- examine file types. For a more high-level version, use `easyCopy`
-- instead.
--
-- In `Overwrite` copy mode only overwrites actual files, not directories.
-- In `Strict` mode the destination file must not exist.
--
-- Safety/reliability concerns:
--
-- * `Overwrite` mode is not atomic
-- * when used on `CharacterDevice`, reads the "contents" and copies
-- them to a regular file, which might take indefinitely
-- * when used on `BlockDevice`, may either read the "contents"
-- and copy them to a regular file (potentially hanging indefinitely)
-- or may create a regular empty destination file
-- * when used on `NamedPipe`, will hang indefinitely
--
-- Throws:
--
-- - `NoSuchThing` if source file does not exist
-- - `NoSuchThing` if source file is a a `Socket`
-- - `PermissionDenied` if output directory is not writable
-- - `PermissionDenied` if source directory can't be opened
-- - `InvalidArgument` if source file is wrong type (symlink or directory)
-- - `SameFile` if source and destination are the same file
-- (`HPathIOException`)
--
-- Throws in `Strict` mode only:
--
-- - `AlreadyExists` if destination already exists
--
-- Notes:
--
-- - may call `getcwd` in Overwrite mode (if destination is a relative path)
copyFile :: Path b1 -- ^ source file
-> Path b2 -- ^ destination file
-> CopyMode
-> IO ()
copyFile (Path from) (Path to) cm = RD.copyFile from to cm
-- |Copies a regular file, directory or symbolic link. In case of a
-- symbolic link it is just recreated, even if it points to a directory.
-- Any other file type is ignored.
--
-- Safety/reliability concerns:
--
-- * examines filetypes explicitly
-- * calls `copyDirRecursive` for directories
--
-- Note: may call `getcwd` in Overwrite mode (if destination is a relative path)
easyCopy :: Path b1 -> Path b2 -> CopyMode -> RecursiveErrorMode -> IO ()
easyCopy (Path from) (Path to) cm rm = RD.easyCopy from to cm rm
---------------------
--[ File Deletion ]--
---------------------
-- |Deletes the given file. Raises `eISDIR`
-- if run on a directory. Does not follow symbolic links.
--
-- Throws:
--
-- - `InappropriateType` for wrong file type (directory)
-- - `NoSuchThing` if the file does not exist
-- - `PermissionDenied` if the directory cannot be read
deleteFile :: Path b -> IO ()
deleteFile (Path p) = RD.deleteFile p
-- |Deletes the given directory, which must be empty, never symlinks.
--
-- Throws:
--
-- - `InappropriateType` for wrong file type (symlink to directory)
-- - `InappropriateType` for wrong file type (regular file)
-- - `NoSuchThing` if directory does not exist
-- - `UnsatisfiedConstraints` if directory is not empty
-- - `PermissionDenied` if we can't open or write to parent directory
--
-- Notes: calls `rmdir`
deleteDir :: Path b -> IO ()
deleteDir (Path p) = RD.deleteDir p
-- |Deletes the given directory recursively. Does not follow symbolic
-- links. Tries `deleteDir` first before attemtping a recursive
-- deletion.
--
-- On directory contents this behaves like `easyDelete`
-- and thus will ignore any file type that is not `RegularFile`,
-- `SymbolicLink` or `Directory`.
--
-- Safety/reliability concerns:
--
-- * not atomic
-- * examines filetypes explicitly
--
-- Throws:
--
-- - `InappropriateType` for wrong file type (symlink to directory)
-- - `InappropriateType` for wrong file type (regular file)
-- - `NoSuchThing` if directory does not exist
-- - `PermissionDenied` if we can't open or write to parent directory
deleteDirRecursive :: Path b -> IO ()
deleteDirRecursive (Path p) = RD.deleteDirRecursive p
-- |Deletes a file, directory or symlink.
-- In case of directory, performs recursive deletion. In case of
-- a symlink, the symlink file is deleted.
-- Any other file type is ignored.
--
-- Safety/reliability concerns:
--
-- * examines filetypes explicitly
-- * calls `deleteDirRecursive` for directories
easyDelete :: Path b -> IO ()
easyDelete (Path p) = RD.easyDelete p
--------------------
--[ File Opening ]--
--------------------
-- |Opens a file appropriately by invoking xdg-open. The file type
-- is not checked. This forks a process.
openFile :: Path b -> IO ProcessID
openFile (Path fp) = RD.openFile fp
-- |Executes a program with the given arguments. This forks a process.
executeFile :: Path b -- ^ program
-> [ByteString] -- ^ arguments
-> IO ProcessID
executeFile (Path fp) args = RD.executeFile fp args
---------------------
--[ File Creation ]--
---------------------
-- |Create an empty regular file at the given directory with the given
-- filename.
--
-- Throws:
--
-- - `PermissionDenied` if output directory cannot be written to
-- - `AlreadyExists` if destination already exists
-- - `NoSuchThing` if any of the parent components of the path
-- do not exist
createRegularFile :: FileMode -> Path b -> IO ()
createRegularFile fm (Path destBS) = RD.createRegularFile fm destBS
-- |Create an empty directory at the given directory with the given filename.
--
-- Throws:
--
-- - `PermissionDenied` if output directory cannot be written to
-- - `AlreadyExists` if destination already exists
-- - `NoSuchThing` if any of the parent components of the path
-- do not exist
createDir :: FileMode -> Path b -> IO ()
createDir fm (Path destBS) = RD.createDir fm destBS
-- |Create an empty directory at the given directory with the given filename.
--
-- Throws:
--
-- - `PermissionDenied` if output directory cannot be written to
-- - `NoSuchThing` if any of the parent components of the path
-- do not exist
createDirIfMissing :: FileMode -> Path b -> IO ()
createDirIfMissing fm (Path destBS) = RD.createDirIfMissing fm destBS
-- |Create an empty directory at the given directory with the given filename.
-- All parent directories are created with the same filemode. This
-- basically behaves like:
--
-- @
-- mkdir -p \/some\/dir
-- @
--
-- Safety/reliability concerns:
--
-- * not atomic
--
-- Throws:
--
-- - `PermissionDenied` if any part of the path components do not
-- exist and cannot be written to
-- - `AlreadyExists` if destination already exists and
-- is *not* a directory
--
-- Note: calls `getcwd` if the input path is a relative path
createDirRecursive :: FileMode -> Path b -> IO ()
createDirRecursive fm (Path p) = RD.createDirRecursive fm p
-- |Create a symlink.
--
-- Throws:
--
-- - `PermissionDenied` if output directory cannot be written to
-- - `AlreadyExists` if destination file already exists
-- - `NoSuchThing` if any of the parent components of the path
-- do not exist
--
-- Note: calls `symlink`
createSymlink :: Path b -- ^ destination file
-> ByteString -- ^ path the symlink points to
-> IO ()
createSymlink (Path destBS) sympoint = RD.createSymlink destBS sympoint
----------------------------
--[ File Renaming/Moving ]--
----------------------------
-- |Rename a given file with the provided filename. Destination and source
-- must be on the same device, otherwise `eXDEV` will be raised.
--
-- Does not follow symbolic links, but renames the symbolic link file.
--
-- Safety/reliability concerns:
--
-- * has a separate set of exception handling, apart from the syscall
--
-- Throws:
--
-- - `NoSuchThing` if source file does not exist
-- - `PermissionDenied` if output directory cannot be written to
-- - `PermissionDenied` if source directory cannot be opened
-- - `UnsupportedOperation` if source and destination are on different
-- devices
-- - `AlreadyExists` if destination already exists
-- - `SameFile` if destination and source are the same file
-- (`HPathIOException`)
--
-- Note: calls `rename` (but does not allow to rename over existing files)
renameFile :: Path b1 -> Path b2 -> IO ()
renameFile (Path from) (Path to) = RD.renameFile from to
-- |Move a file. This also works across devices by copy-delete fallback.
-- And also works on directories.
--
-- Does not follow symbolic links, but renames the symbolic link file.
--
--
-- Safety/reliability concerns:
--
-- * `Overwrite` mode is not atomic
-- * copy-delete fallback is inherently non-atomic
-- * since this function calls `easyCopy` and `easyDelete` as a fallback
-- to `renameFile`, file types that are not `RegularFile`, `SymbolicLink`
-- or `Directory` may be ignored
-- * for `Overwrite` mode, the destination will be deleted (not recursively)
-- before moving
--
-- Throws:
--
-- - `NoSuchThing` if source file does not exist
-- - `PermissionDenied` if output directory cannot be written to
-- - `PermissionDenied` if source directory cannot be opened
-- - `SameFile` if destination and source are the same file
-- (`HPathIOException`)
--
-- Throws in `Strict` mode only:
--
-- - `AlreadyExists` if destination already exists
--
-- Notes:
--
-- - calls `rename` (but does not allow to rename over existing files)
-- - calls `getcwd` in Overwrite mode if destination is a relative path
moveFile :: Path b1 -- ^ file to move
-> Path b2 -- ^ destination
-> CopyMode
-> IO ()
moveFile (Path from) (Path to) cm = RD.moveFile from to cm
--------------------
--[ File Reading ]--
--------------------
-- |Read the given file *at once* into memory as a lazy ByteString.
-- Symbolic links are followed, no sanity checks on file size
-- or file type. File must exist. Uses Builders under the hood
-- (hence lazy ByteString).
--
-- Safety/reliability concerns:
--
-- * the whole file is read into memory, this doesn't read lazily
--
-- Throws:
--
-- - `InappropriateType` if file is not a regular file or a symlink
-- - `PermissionDenied` if we cannot read the file or the directory
-- containting it
-- - `NoSuchThing` if the file does not exist
readFile :: Path b -> IO L.ByteString
readFile (Path path) = RD.readFile path
-- | Open the given file as a filestream. Once the filestream is
-- exits, the filehandle is cleaned up.
--
-- Throws:
--
-- - `InappropriateType` if file is not a regular file or a symlink
-- - `PermissionDenied` if we cannot read the file or the directory
-- containting it
-- - `NoSuchThing` if the file does not exist
readFileStream :: Path b -> IO (SerialT IO ByteString)
readFileStream (Path fp) = RD.readFileStream fp
--------------------
--[ File Writing ]--
--------------------
-- |Write a given ByteString to a file, truncating the file beforehand.
-- Follows symlinks.
--
-- Throws:
--
-- - `InappropriateType` if file is not a regular file or a symlink
-- - `PermissionDenied` if we cannot read the file or the directory
-- containting it
-- - `NoSuchThing` if the file does not exist
writeFile :: Path b
-> Maybe FileMode -- ^ if Nothing, file must exist
-> ByteString
-> IO ()
writeFile (Path fp) fmode bs = RD.writeFile fp fmode bs
-- |Write a given lazy ByteString to a file, truncating the file beforehand.
-- Follows symlinks.
--
-- Throws:
--
-- - `InappropriateType` if file is not a regular file or a symlink
-- - `PermissionDenied` if we cannot read the file or the directory
-- containting it
-- - `NoSuchThing` if the file does not exist
--
-- Note: uses streamly under the hood
writeFileL :: Path b
-> Maybe FileMode -- ^ if Nothing, file must exist
-> L.ByteString
-> IO ()
writeFileL (Path fp) fmode lbs = RD.writeFileL fp fmode lbs
-- |Append a given ByteString to a file.
-- The file must exist. Follows symlinks.
--
-- Throws:
--
-- - `InappropriateType` if file is not a regular file or a symlink
-- - `PermissionDenied` if we cannot read the file or the directory
-- containting it
-- - `NoSuchThing` if the file does not exist
appendFile :: Path b -> ByteString -> IO ()
appendFile (Path fp) bs = RD.appendFile fp bs
-------------------
--[ File checks ]--
-------------------
-- |Checks if the given file exists.
-- Does not follow symlinks.
--
-- Only eNOENT is catched (and returns False).
doesExist :: Path b -> IO Bool
doesExist (Path bs) = RD.doesExist bs
-- |Checks if the given file exists and is not a directory.
-- Does not follow symlinks.
--
-- Only eNOENT is catched (and returns False).
doesFileExist :: Path b -> IO Bool
doesFileExist (Path bs) = RD.doesFileExist bs
-- |Checks if the given file exists and is a directory.
-- Does not follow symlinks.
--
-- Only eNOENT is catched (and returns False).
doesDirectoryExist :: Path b -> IO Bool
doesDirectoryExist (Path bs) = RD.doesDirectoryExist bs
-- |Checks whether a file or folder is readable.
--
-- Only eACCES, eROFS, eTXTBSY, ePERM are catched (and return False).
--
-- Throws:
--
-- - `NoSuchThing` if the file does not exist
isReadable :: Path b -> IO Bool
isReadable (Path bs) = RD.isReadable bs
-- |Checks whether a file or folder is writable.
--
-- Only eACCES, eROFS, eTXTBSY, ePERM are catched (and return False).
--
-- Throws:
--
-- - `NoSuchThing` if the file does not exist
isWritable :: Path b -> IO Bool
isWritable (Path bs) = RD.isWritable bs
-- |Checks whether a file or folder is executable.
--
-- Only eACCES, eROFS, eTXTBSY, ePERM are catched (and return False).
--
-- Throws:
--
-- - `NoSuchThing` if the file does not exist
isExecutable :: Path b -> IO Bool
isExecutable (Path bs) = RD.isExecutable bs
-- |Checks whether the directory at the given path exists and can be
-- opened. This invokes `openDirStream` which follows symlinks.
canOpenDirectory :: Path b -> IO Bool
canOpenDirectory (Path bs) = RD.canOpenDirectory bs
------------------
--[ File times ]--
------------------
getModificationTime :: Path b -> IO UTCTime
getModificationTime (Path bs) = RD.getModificationTime bs
setModificationTime :: Path b -> EpochTime -> IO ()
setModificationTime (Path bs) t = RD.setModificationTime bs t
setModificationTimeHiRes :: Path b -> POSIXTime -> IO ()
setModificationTimeHiRes (Path bs) t = RD.setModificationTimeHiRes bs t
-------------------------
--[ Directory reading ]--
-------------------------
-- |Gets all filenames of the given directory. This excludes "." and "..".
-- This version does not follow symbolic links.
--
-- The contents are not sorted and there is no guarantee on the ordering.
--
-- Throws:
--
-- - `NoSuchThing` if directory does not exist
-- - `InappropriateType` if file type is wrong (file)
-- - `InappropriateType` if file type is wrong (symlink to file)
-- - `InappropriateType` if file type is wrong (symlink to dir)
-- - `PermissionDenied` if directory cannot be opened
-- - `PathParseException` if a filename could not be parsed (should never happen)
getDirsFiles :: Path b -- ^ dir to read
-> IO [Path b]
getDirsFiles p = do
contents <- getDirsFiles' p
pure $ fmap (p </>) contents
-- | Like 'getDirsFiles', but returns the filename only, instead
-- of prepending the base path.
getDirsFiles' :: Path b -- ^ dir to read
-> IO [Path Rel]
getDirsFiles' (Path fp) = do
rawContents <- RD.getDirsFiles' fp
for rawContents $ \r -> parseRel r
---------------------------
--[ FileType operations ]--
---------------------------
-- |Get the file type of the file located at the given path. Does
-- not follow symbolic links.
--
-- Throws:
--
-- - `NoSuchThing` if the file does not exist
-- - `PermissionDenied` if any part of the path is not accessible
getFileType :: Path b -> IO FileType
getFileType (Path fp) = RD.getFileType fp
--------------
--[ Others ]--
--------------
-- |Applies `realpath` on the given path.
--
-- Throws:
--
-- - `NoSuchThing` if the file at the given path does not exist
-- - `NoSuchThing` if the symlink is broken
-- - `PathParseException` if realpath does not return an absolute path
canonicalizePath :: Path b -> IO (Path Abs)
canonicalizePath (Path l) = do
nl <- RD.canonicalizePath l
parseAbs nl
-- |Converts any path to an absolute path.
-- This is done in the following way:
--
-- - if the path is already an absolute one, just return it
-- - if it's a relative path, prepend the current directory to it
toAbs :: Path b -> IO (Path Abs)
toAbs (Path bs) = do
let mabs = parseAbs bs :: Maybe (Path Abs)
case mabs of
Just a -> return a
Nothing -> do
cwd <- getWorkingDirectory >>= parseAbs
r <- parseRel bs -- we know it must be relative now
return $ cwd </> r
-- | Helper function to use the Path library without
-- buying into the Path type too much. This uses 'parseAny'
-- under the hood and may throw `PathParseException`.
--
-- Throws:
--
-- - `PathParseException` if the bytestring could neither be parsed as
-- relative or absolute Path
withRawFilePath :: MonadThrow m
=> ByteString
-> (Either (Path Abs) (Path Rel) -> m b)
-> m b
withRawFilePath bs action = do
path <- parseAny bs
action path
-- | Convenience function to open the path as a handle.
--
-- If the file does not exist, it will be created with 'newFilePerms'.
--
-- Throws:
--
-- - `PathParseException` if the bytestring could neither be parsed as
-- relative or absolute Path
withHandle :: ByteString
-> SPI.OpenMode
-> ((SIO.Handle, Either (Path Abs) (Path Rel)) -> IO a)
-> IO a
withHandle bs mode action = do
path <- parseAny bs
handle <-
bracketOnError (openFd bs mode [] (Just RD.newFilePerms)) (SPI.closeFd)
$ SPI.fdToHandle
finally (action (handle, path)) (SIO.hClose handle)

5
hpath-posix/CHANGELOG.md Normal file
View File

@@ -0,0 +1,5 @@
# Revision history for hpath-posix
## 0.1.0.0 -- 2020-01-29
* First version. Released on an unsuspecting world.

30
hpath-posix/LICENSE Normal file
View File

@@ -0,0 +1,30 @@
Copyright (c) 2020, Julian Ospald
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Julian Ospald nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

13
hpath-posix/README.md Normal file
View File

@@ -0,0 +1,13 @@
# HPath-filepath
[![Gitter chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Hackage version](https://img.shields.io/hackage/v/hpath-posix.svg?label=Hackage)](https://hackage.haskell.org/package/hpath-posix) [![Build Status](https://api.travis-ci.org/hasufell/hpath.png?branch=master)](http://travis-ci.org/hasufell/hpath) [![Hackage-Deps](https://img.shields.io/hackage-deps/v/hpath-posix.svg)](http://packdeps.haskellers.com/feed?needle=hpath-posix)
Some low-level POSIX glue code, that is not in 'unix'.
This package is part of the HPath suite, also check out:
* [hpath](https://hackage.haskell.org/package/hpath)
* [hpath-directory](https://hackage.haskell.org/package/hpath-directory)
* [hpath-filepath](https://hackage.haskell.org/package/hpath-filepath)
* [hpath-io](https://hackage.haskell.org/package/hpath-io)

2
hpath-posix/Setup.hs Normal file
View File

@@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View File

@@ -0,0 +1,57 @@
cabal-version: >=1.10
name: hpath-posix
version: 0.13.0
synopsis: Some low-level POSIX glue code, that is not in 'unix'
homepage: https://github.com/hasufell/hpath
bug-reports: https://github.com/hasufell/hpath/issues
license: BSD3
license-file: LICENSE
author: Julian Ospald <hasufell@posteo.de>
maintainer: Julian Ospald <hasufell@posteo.de>
copyright: Julian Ospald <hasufell@posteo.de> 2020
category: Filesystem
build-type: Simple
extra-source-files: CHANGELOG.md
cbits/dirutils.h
tested-with: GHC==7.10.3
, GHC==8.0.2
, GHC==8.2.2
, GHC==8.4.4
, GHC==8.6.5
, GHC==8.8.1
library
if os(windows)
build-depends: unbuildable<0
buildable: False
exposed-modules: System.Posix.RawFilePath.Directory.Traversals
System.Posix.Foreign
System.Posix.FD
-- other-modules:
-- other-extensions:
c-sources: cbits/dirutils.c
build-depends: base >= 4.8 && <5
, IfElse
, bytestring >= 0.10
, deepseq
, exceptions >= 0.10
, hpath-filepath >= 0.10.3
, safe-exceptions >= 0.1
, streamly >= 0.7
, streamly-bytestring >= 0.1.0.1
, time >= 1.8
, unix >= 2.5
, unix-bytestring >= 0.3
, utf8-string
if impl(ghc < 8.0)
build-depends:
fail >= 4.9
hs-source-dirs: src
default-language: Haskell2010
default-extensions: PackageImports
source-repository head
type: git
location: https://github.com/hasufell/hpath

View File

@@ -26,7 +26,7 @@ module System.Posix.FD (
import Foreign.C.String
import Foreign.C.Types
import System.Posix.Directory.Foreign
import System.Posix.Foreign
import qualified System.Posix as Posix
import System.Posix.ByteString.FilePath

View File

@@ -1,4 +1,4 @@
module System.Posix.Directory.Foreign where
module System.Posix.Foreign where
import Data.Bits
import Data.List (foldl')

View File

@@ -1,5 +1,5 @@
-- |
-- Module : System.Posix.Directory.Traversals
-- Module : System.Posix.RawFilePath.Directory.Traversals
-- Copyright : © 2016 Julian Ospald
-- License : BSD3
--
@@ -13,14 +13,13 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -Wall #-}
module System.Posix.Directory.Traversals (
module System.Posix.RawFilePath.Directory.Traversals (
getDirectoryContents
, getDirectoryContents'
@@ -31,8 +30,6 @@ module System.Posix.Directory.Traversals (
-- lower-level stuff
, readDirEnt
, packDirStream
, unpackDirStream
, fdOpendir
, realpath
@@ -42,9 +39,10 @@ module System.Posix.Directory.Traversals (
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>))
#endif
import Control.DeepSeq
import Control.Monad
import System.Posix.FilePath ((</>))
import System.Posix.Directory.Foreign
import System.Posix.Foreign
import qualified System.Posix as Posix
import System.IO.Error
@@ -52,11 +50,11 @@ import Control.Exception
import qualified Data.ByteString.Char8 as BS
import System.Posix.ByteString.FilePath
import System.Posix.Directory.ByteString as PosixBS
import System.Posix.Directory.Common
import System.Posix.Files.ByteString
import System.IO.Unsafe
import "unix" System.Posix.IO.ByteString (closeFd)
import Unsafe.Coerce (unsafeCoerce)
import Foreign.C.Error
import Foreign.C.String
import Foreign.C.Types
@@ -148,18 +146,6 @@ actOnDirContents pathRelToTop b f =
----------------------------------------------------------
-- dodgy stuff
type CDir = ()
type CDirent = ()
-- Posix doesn't export DirStream, so to re-use that type we need to use
-- unsafeCoerce. It's just a newtype, so this is a legitimate usage.
-- ugly trick.
unpackDirStream :: DirStream -> Ptr CDir
unpackDirStream = unsafeCoerce
packDirStream :: Ptr CDir -> DirStream
packDirStream = unsafeCoerce
-- the __hscore_* functions are defined in the unix package. We can import them and let
-- the linker figure it out.
foreign import ccall unsafe "__hscore_readdir"
@@ -178,14 +164,14 @@ foreign import ccall "realpath"
c_realpath :: CString -> CString -> IO CString
foreign import ccall unsafe "fdopendir"
c_fdopendir :: Posix.Fd -> IO (Ptr ())
c_fdopendir :: Posix.Fd -> IO (Ptr CDir)
----------------------------------------------------------
-- less dodgy but still lower-level
readDirEnt :: DirStream -> IO (DirType, RawFilePath)
readDirEnt (unpackDirStream -> dirp) =
readDirEnt (DirStream dirp) =
alloca $ \ptr_dEnt -> loop ptr_dEnt
where
loop ptr_dEnt = do
@@ -194,12 +180,14 @@ readDirEnt (unpackDirStream -> dirp) =
if (r == 0)
then do
dEnt <- peek ptr_dEnt
putStrLn $ "readDirEnt dEnt " ++ (show dEnt)
if (dEnt == nullPtr)
then return (dtUnknown,BS.empty)
else do
dName <- c_name dEnt >>= peekFilePath
dName <- c_name dEnt >>= peekFilePath >>= evaluate . force
dType <- c_type dEnt
c_freeDirEnt dEnt
putStrLn $ "readDirEnt" ++ (show dName)
return (dType, dName)
else do
errno <- getErrno
@@ -216,7 +204,7 @@ readDirEnt (unpackDirStream -> dirp) =
getDirectoryContents :: RawFilePath -> IO [(DirType, RawFilePath)]
getDirectoryContents path =
modifyIOError ((`ioeSetFileName` (BS.unpack path)) .
(`ioeSetLocation` "System.Posix.Directory.Traversals.getDirectoryContents")) $
(`ioeSetLocation` "System.Posix.RawFilePath.Directory.Traversals.getDirectoryContents")) $
bracket
(PosixBS.openDirStream path)
PosixBS.closeDirStream
@@ -226,7 +214,7 @@ getDirectoryContents path =
-- |Binding to @fdopendir(3)@.
fdOpendir :: Posix.Fd -> IO DirStream
fdOpendir fd =
packDirStream <$> throwErrnoIfNull "fdOpendir" (c_fdopendir fd)
DirStream <$> throwErrnoIfNull "fdOpendir" (c_fdopendir fd)
-- |Like `getDirectoryContents` except for a file descriptor.

View File

@@ -1,132 +0,0 @@
name: hpath
version: 0.9.2
synopsis: Support for well-typed paths
description: Support for well-typed paths, utilizing ByteString under the hood.
license: BSD3
license-file: LICENSE
author: Julian Ospald <hasufell@posteo.de>
maintainer: Julian Ospald <hasufell@posteo.de>
copyright: Julian Ospald 2016
category: Filesystem
build-type: Simple
cabal-version: 1.14
extra-source-files: README.md
CHANGELOG
cbits/dirutils.h
doctests-hpath.hs
doctests-posix.hs
library
if os(windows)
build-depends: unbuildable<0
buildable: False
hs-source-dirs: src/
default-language: Haskell2010
if impl(ghc >= 8.0)
ghc-options: -Wall -Wno-redundant-constraints
else
ghc-options: -Wall
c-sources: cbits/dirutils.c
exposed-modules: HPath,
HPath.IO,
HPath.IO.Errors,
System.Posix.Directory.Foreign,
System.Posix.Directory.Traversals,
System.Posix.FD,
System.Posix.FilePath
other-modules: HPath.Internal
build-depends: base >= 4.6 && <5
, IfElse
, bytestring >= 0.10.0.0
, deepseq
, exceptions
, hspec
, simple-sendfile >= 0.2.24
, unix >= 2.5
, unix-bytestring
, utf8-string
, word8
test-suite doctests-hpath
if os(windows)
build-depends: unbuildable<0
buildable: False
default-language: Haskell2010
type: exitcode-stdio-1.0
ghc-options: -threaded
main-is: doctests-hpath.hs
build-depends: base
, HUnit
, QuickCheck
, doctest >= 0.8
, hpath
test-suite doctests-posix
if os(windows)
build-depends: unbuildable<0
buildable: False
default-language: Haskell2010
type: exitcode-stdio-1.0
ghc-options: -threaded
main-is: doctests-posix.hs
build-depends: base,
bytestring >= 0.10.0.0,
unix,
hpath,
doctest >= 0.8,
HUnit,
QuickCheck
test-suite spec
if os(windows)
build-depends: unbuildable<0
buildable: False
Type: exitcode-stdio-1.0
Default-Language: Haskell2010
Hs-Source-Dirs: test
Main-Is: Main.hs
other-modules:
HPath.IO.AppendFileSpec
HPath.IO.CanonicalizePathSpec
HPath.IO.CopyDirRecursiveCollectFailuresSpec
HPath.IO.CopyDirRecursiveOverwriteSpec
HPath.IO.CopyDirRecursiveSpec
HPath.IO.CopyFileOverwriteSpec
HPath.IO.CopyFileSpec
HPath.IO.CreateDirRecursiveSpec
HPath.IO.CreateDirSpec
HPath.IO.CreateRegularFileSpec
HPath.IO.CreateSymlinkSpec
HPath.IO.DeleteDirRecursiveSpec
HPath.IO.DeleteDirSpec
HPath.IO.DeleteFileSpec
HPath.IO.GetDirsFilesSpec
HPath.IO.GetFileTypeSpec
HPath.IO.MoveFileOverwriteSpec
HPath.IO.MoveFileSpec
HPath.IO.ReadFileEOFSpec
HPath.IO.ReadFileSpec
HPath.IO.RecreateSymlinkOverwriteSpec
HPath.IO.RecreateSymlinkSpec
HPath.IO.RenameFileSpec
HPath.IO.ToAbsSpec
HPath.IO.WriteFileSpec
Spec
Utils
GHC-Options: -Wall
Build-Depends: base
, HUnit
, IfElse
, bytestring >= 0.10.0.0
, hpath
, hspec >= 1.3
, process
, unix
, unix-bytestring
, utf8-string
source-repository head
type: git
location: https://github.com/hasufell/hpath

View File

@@ -1,3 +1,16 @@
0.11.0
* Many API breaking changes
* Remove RelC and Fn, because they complicate API/break semantics (see #29)
* Redo 'parseAny'
* Unexpose HPath.Internal
* Don't preserve trailing path separators (if you need to pass something to a C function that way, do it manually)
* Added `rooPath`, `isRootPath`, `getAllComponents`, `getAllComponentsAfterRoot`
0.10.2
* Add `parseAny` and the related QuasiQuoter
0.10.1
* Add quasi quoters for hpath
0.10.0
* split packages, this one now just contains the type-safe Path wrappers
0.9.2
* fix build with ghc-7.6
* raise required bytestring version

40
hpath/README.md Normal file
View File

@@ -0,0 +1,40 @@
# HPath
[![Gitter chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hasufell/hpath?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Hackage version](https://img.shields.io/hackage/v/hpath.svg?label=Hackage)](https://hackage.haskell.org/package/hpath) [![Build Status](https://api.travis-ci.org/hasufell/hpath.png?branch=master)](http://travis-ci.org/hasufell/hpath) [![Hackage-Deps](https://img.shields.io/hackage-deps/v/hpath.svg)](http://packdeps.haskellers.com/feed?needle=hpath)
Support for well-typed paths in Haskell.
This package is part of the HPath suite, also check out:
* [hpath-directory](https://hackage.haskell.org/package/hpath-directory)
* [hpath-filepath](https://hackage.haskell.org/package/hpath-filepath)
* [hpath-io](https://hackage.haskell.org/package/hpath-io)
## Motivation
The motivation came during development of
[hsfm](https://github.com/hasufell/hsfm)
which has a pretty strict File type, but lacks a strict Path type, e.g.
for user input.
The library that came closest to my needs was
[path](https://github.com/chrisdone/path),
but the API turned out to be oddly complicated for my use case, so I
decided to fork it.
## Goals
* well-typed paths
* safe filepath manipulation, never using String as filepath, but ByteString
Note: this library was written for __posix__ systems and it will probably not support other systems.
## Differences to 'path'
* doesn't attempt to fake IO-related information into the path, so whether a path points to a file or directory is up to your IO-code to decide...
* uses safe ByteString for filepaths under the hood instead of unsafe String
* fixes broken [dirname](https://github.com/chrisdone/path/issues/18)
* renames dirname/filename to basename/dirname to match the POSIX shell functions
* allows pattern matching via unidirectional PatternSynonym
* uses simple doctest for testing
* allows `~/` as relative path, because on posix level `~` is just a regular filename that does _NOT_ point to `$HOME`

2
hpath/Setup.hs Normal file
View File

@@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

46
hpath/hpath.cabal Normal file
View File

@@ -0,0 +1,46 @@
name: hpath
version: 0.11.0
synopsis: Support for well-typed paths
description: Support for well-typed paths, utilizing ByteString under the hood.
license: BSD3
license-file: LICENSE
author: Julian Ospald <hasufell@posteo.de>
maintainer: Julian Ospald <hasufell@posteo.de>
copyright: Julian Ospald 2016
category: Filesystem
build-type: Simple
cabal-version: 1.14
tested-with: GHC==7.10.3
, GHC==8.0.2
, GHC==8.2.2
, GHC==8.4.4
, GHC==8.6.5
, GHC==8.8.1
extra-source-files: README.md
CHANGELOG
library
if os(windows)
build-depends: unbuildable<0
buildable: False
hs-source-dirs: src/
default-language: Haskell2010
if impl(ghc >= 8.0)
ghc-options: -Wall -Wno-redundant-constraints
else
ghc-options: -Wall
exposed-modules: HPath
other-modules: HPath.Internal
build-depends: base >= 4.8 && <5
, bytestring >= 0.10.0.0
, deepseq
, exceptions
, hpath-filepath >= 0.10 && < 0.11
, template-haskell
, utf8-string
, word8
source-repository head
type: git
location: https://github.com/hasufell/hpath

23
hpath/run-doctests.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/sh
set -e
if [ -n "${SKIP_DOCTESTS}" ] ; then
echo "Skipping doctests"
exit 0
fi
if ! command -v doctest >/dev/null ; then
tempdir="$(mktemp -d)"
(
cd "${tempdir}"
cabal install --installdir="${tempdir}" doctest
)
export PATH="${tempdir}:$PATH"
fi
set -x
cd "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)"
cabal exec doctest -- -isrc -XOverloadedStrings -XQuasiQuotes HPath

View File

@@ -10,14 +10,14 @@
-- Support for well-typed paths.
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE EmptyDataDecls #-}
#if __GLASGOW_HASKELL__ >= 708
{-# LANGUAGE PatternSynonyms #-}
#endif
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
module HPath
(
@@ -25,42 +25,44 @@ module HPath
Abs
,Path
,Rel
,Fn
,PathParseException
,PathException
,RelC
#if __GLASGOW_HASKELL__ >= 708
-- * PatternSynonyms/ViewPatterns
,pattern Path
#endif
-- * Path Parsing
-- * Path Construction
,parseAbs
,parseFn
,parseRel
,parseAny
,rootPath
-- * Path Conversion
,fromAbs
,fromRel
,toFilePath
,unsafeToString
,unsafeToString'
,fromAny
-- * Path Operations
,(</>)
,basename
,dirname
,isParentOf
,getAllParents
,getAllComponents
,getAllComponentsAfterRoot
,stripDir
-- * Path Examination
,isParentOf
,isRootPath
-- * Path IO helpers
,withAbsPath
,withRelPath
,withFnPath
-- * Quasiquoters
,abs
,rel
)
where
import Control.Exception (IOException, Exception, catch)
import Control.Monad ((<$!>))
import Control.Exception (Exception)
import Control.Monad.Catch (MonadThrow(..))
import Data.ByteString.Unsafe(unsafeUseAsCStringLen)
#if MIN_VERSION_bytestring(0,10,8)
import Data.ByteString(ByteString, stripPrefix)
#else
@@ -68,20 +70,18 @@ import Data.ByteString(ByteString)
import qualified Data.List as L
#endif
import qualified Data.ByteString as BS
import Data.ByteString.UTF8
import Data.Data
import Data.Maybe
import Data.Word8
import GHC.Foreign(peekCStringLen)
import GHC.IO.Encoding(getLocaleEncoding, TextEncoding)
import HPath.Internal
import System.IO.Unsafe(unsafePerformIO)
import Language.Haskell.TH
import Language.Haskell.TH.Syntax (Exp(..), Lift(..), lift)
import Language.Haskell.TH.Quote (QuasiQuoter(..))
import Prelude hiding (abs, any)
import System.Posix.FilePath hiding ((</>))
-- $setup
-- >>> import GHC.IO.Encoding(utf8)
--------------------------------------------------------------------------------
-- Types
@@ -91,14 +91,10 @@ data Abs deriving (Typeable)
-- | A relative path; one without a root.
data Rel deriving (Typeable)
-- | A filename, without any '/'.
data Fn deriving (Typeable)
-- | Exception when parsing a location.
data PathParseException
= InvalidAbs ByteString
| InvalidRel ByteString
| InvalidFn ByteString
| Couldn'tStripPrefixTPS ByteString ByteString
deriving (Show,Typeable)
instance Exception PathParseException
@@ -107,10 +103,6 @@ data PathException = RootDirHasNoBasename
deriving (Show,Typeable)
instance Exception PathException
class RelC m
instance RelC Rel
instance RelC Fn
--------------------------------------------------------------------------------
-- PatternSynonyms
@@ -138,7 +130,7 @@ pattern Path x <- (MkPath x)
-- >>> parseAbs "/abc/def" :: Maybe (Path Abs)
-- Just "/abc/def"
-- >>> parseAbs "/abc/def/.///" :: Maybe (Path Abs)
-- Just "/abc/def/"
-- Just "/abc/def"
-- >>> parseAbs "abc" :: Maybe (Path Abs)
-- Nothing
-- >>> parseAbs "" :: Maybe (Path Abs)
@@ -151,7 +143,7 @@ parseAbs filepath =
if isAbsolute filepath &&
isValid filepath &&
not (hasParentDir filepath)
then return (MkPath $ normalise filepath)
then return (MkPath . dropTrailingPathSeparator . normalise $ filepath)
else throwM (InvalidAbs filepath)
@@ -166,11 +158,11 @@ parseAbs filepath =
-- >>> parseRel "abc" :: Maybe (Path Rel)
-- Just "abc"
-- >>> parseRel "def/" :: Maybe (Path Rel)
-- Just "def/"
-- Just "def"
-- >>> parseRel "abc/def" :: Maybe (Path Rel)
-- Just "abc/def"
-- >>> parseRel "abc/def/." :: Maybe (Path Rel)
-- Just "abc/def/"
-- Just "abc/def"
-- >>> parseRel "/abc" :: Maybe (Path Rel)
-- Nothing
-- >>> parseRel "" :: Maybe (Path Rel)
@@ -189,46 +181,49 @@ parseRel filepath =
filepath /= BS.pack [_period, _period] &&
not (hasParentDir filepath) &&
isValid filepath
then return (MkPath $ normalise filepath)
then return (MkPath . dropTrailingPathSeparator . normalise $ filepath)
else throwM (InvalidRel filepath)
-- | Parses a filename. Filenames must not contain slashes.
-- | Parses a path, whether it's relative or absolute. Will lose
-- information on whether it's relative or absolute. If you need to know,
-- reparse it.
--
-- Filenames must not contain slashes.
-- Excludes '.' and '..'.
--
-- Throws: 'PathParseException'
--
-- >>> parseFn "abc" :: Maybe (Path Fn)
-- Just "abc"
-- >>> parseFn "..." :: Maybe (Path Fn)
-- Just "..."
-- >>> parseFn "def/" :: Maybe (Path Fn)
-- >>> parseAny "/abc" :: Maybe (Either (Path Abs) (Path Rel))
-- Just (Left "/abc")
-- >>> parseAny "..." :: Maybe (Either (Path Abs) (Path Rel))
-- Just (Right "...")
-- >>> parseAny "abc/def" :: Maybe (Either (Path Abs) (Path Rel))
-- Just (Right "abc/def")
-- >>> parseAny "abc/def/." :: Maybe (Either (Path Abs) (Path Rel))
-- Just (Right "abc/def")
-- >>> parseAny "/abc" :: Maybe (Either (Path Abs) (Path Rel))
-- Just (Left "/abc")
-- >>> parseAny "" :: Maybe (Either (Path Abs) (Path Rel))
-- Nothing
-- >>> parseFn "abc/def" :: Maybe (Path Fn)
-- >>> parseAny "abc/../foo" :: Maybe (Either (Path Abs) (Path Rel))
-- Nothing
-- >>> parseFn "abc/def/." :: Maybe (Path Fn)
-- >>> parseAny "." :: Maybe (Either (Path Abs) (Path Rel))
-- Nothing
-- >>> parseFn "/abc" :: Maybe (Path Fn)
-- >>> parseAny ".." :: Maybe (Either (Path Abs) (Path Rel))
-- Nothing
-- >>> parseFn "" :: Maybe (Path Fn)
-- Nothing
-- >>> parseFn "abc/../foo" :: Maybe (Path Fn)
-- Nothing
-- >>> parseFn "." :: Maybe (Path Fn)
-- Nothing
-- >>> parseFn ".." :: Maybe (Path Fn)
-- Nothing
parseFn :: MonadThrow m
=> ByteString -> m (Path Fn)
parseFn filepath =
if isFileName filepath &&
filepath /= BS.singleton _period &&
filepath /= BS.pack [_period, _period] &&
isValid filepath
then return (MkPath filepath)
else throwM (InvalidFn filepath)
parseAny :: MonadThrow m => ByteString -> m (Either (Path Abs) (Path Rel))
parseAny filepath = case parseAbs filepath of
Just p -> pure $ Left p
Nothing -> case parseRel filepath of
Just p -> pure $ Right p
Nothing -> throwM (InvalidRel filepath)
rootPath :: Path Abs
rootPath = (MkPath (BS.singleton _slash))
--------------------------------------------------------------------------------
-- Path Conversion
@@ -242,44 +237,11 @@ fromAbs :: Path Abs -> ByteString
fromAbs = toFilePath
-- | Convert a relative Path to a ByteString type.
fromRel :: RelC r => Path r -> ByteString
fromRel :: Path Rel -> ByteString
fromRel = toFilePath
-- | This converts the underlying bytestring of the path to an unsafe
-- FilePath by assuming the encoding of the current locale setting. This
-- may be utterly wrong, but isn't particularly worse than what the
-- base library does. Blows up on decoding errors.
--
-- >>> unsafeToString (MkPath "/lal/lad")
-- "/lal/lad"
-- >>> unsafeToString (MkPath "/")
-- "/"
-- >>> unsafeToString (MkPath "lad")
-- "lad"
-- >>> catch (Just <$> unsafeToString (MkPath "<22>")) (\(_ :: IOException) -> pure Nothing)
-- Nothing
unsafeToString :: Path b -> IO FilePath
unsafeToString (MkPath p) = do
enc <- getLocaleEncoding
unsafeUseAsCStringLen p (peekCStringLen enc)
-- | Same as @unsafeToString@, except requires the encoding
-- to be passed explicitly. This uses 'unsafePerformIO' and
-- returns 'Nothing' on decoding errors.
--
-- >>> unsafeToString' (MkPath "/lal/lad") utf8
-- Just "/lal/lad"
-- >>> unsafeToString' (MkPath "/") utf8
-- Just "/"
-- >>> unsafeToString' (MkPath "lad") utf8
-- Just "lad"
-- >>> unsafeToString' (MkPath "<22>") utf8
-- Nothing
unsafeToString' :: Path b -> TextEncoding -> Maybe FilePath
unsafeToString' (MkPath !p) enc =
unsafePerformIO $!
catch (Just <$!> unsafeUseAsCStringLen p (peekCStringLen enc))
(\(_ :: IOException) -> pure Nothing)
fromAny :: Either (Path Abs) (Path Rel) -> ByteString
fromAny = either toFilePath toFilePath
--------------------------------------------------------------------------------
@@ -300,14 +262,15 @@ unsafeToString' (MkPath !p) enc =
-- "/path/to/file"
-- >>> (MkPath "/") </> (MkPath "file/lal" :: Path Rel)
-- "/file/lal"
-- >>> (MkPath "/") </> (MkPath "file/" :: Path Rel)
-- "/file/"
(</>) :: RelC r => Path b -> Path r -> Path b
-- >>> (MkPath "/") </> (MkPath "file" :: Path Rel)
-- "/file"
(</>) :: Path b -> Path Rel -> Path b
(</>) (MkPath a) (MkPath b) = MkPath (a' `BS.append` b)
where
a' = if BS.last a == pathSeparator
then a
else addTrailingPathSeparator a
a' = if hasTrailingPathSeparator a
then a
else addTrailingPathSeparator a
-- | Strip directory from path, making it relative to that directory.
-- Throws 'Couldn'tStripPrefixDir' if directory is not a parent of the path.
@@ -335,6 +298,81 @@ stripDir (MkPath p) (MkPath l) =
where
p' = addTrailingPathSeparator p
-- |Get all parents of a path.
--
-- >>> getAllParents (MkPath "/abs/def/dod")
-- ["/abs/def","/abs","/"]
-- >>> getAllParents (MkPath "/foo")
-- ["/"]
-- >>> getAllParents (MkPath "/")
-- []
getAllParents :: Path Abs -> [Path Abs]
getAllParents (MkPath p)
| np == BS.singleton pathSeparator = []
| otherwise = dirname (MkPath np) : getAllParents (dirname $ MkPath np)
where
np = normalise p
-- | Gets all path components.
--
-- >>> getAllComponents (MkPath "abs/def/dod")
-- ["abs","def","dod"]
-- >>> getAllComponents (MkPath "abs")
-- ["abs"]
getAllComponents :: Path Rel -> [Path Rel]
getAllComponents (MkPath p) = fmap MkPath . splitDirectories $ p
-- | Gets all path components after the "/" root directory.
--
-- >>> getAllComponentsAfterRoot (MkPath "/abs/def/dod")
-- ["abs","def","dod"]
-- >>> getAllComponentsAfterRoot (MkPath "/abs")
-- ["abs"]
getAllComponentsAfterRoot :: Path Abs -> [Path Rel]
getAllComponentsAfterRoot p = getAllComponents (fromJust $ stripDir rootPath p)
-- | Extract the directory name of a path.
--
-- >>> dirname (MkPath "/abc/def/dod")
-- "/abc/def"
-- >>> dirname (MkPath "/")
-- "/"
dirname :: Path Abs -> Path Abs
dirname (MkPath fp) = MkPath (takeDirectory fp)
-- | Extract the file part of a path.
--
--
-- The following properties hold:
--
-- @basename (p \<\/> a) == basename a@
--
-- Throws: `PathException` if given the root path "/"
--
-- >>> basename (MkPath "/abc/def/dod") :: Maybe (Path Rel)
-- Just "dod"
-- >>> basename (MkPath "abc/def/dod") :: Maybe (Path Rel)
-- Just "dod"
-- >>> basename (MkPath "dod") :: Maybe (Path Rel)
-- Just "dod"
-- >>> basename (MkPath "/") :: Maybe (Path Rel)
-- Nothing
basename :: MonadThrow m => Path b -> m (Path Rel)
basename (MkPath l)
| not (isAbsolute rl) = return $ MkPath rl
| otherwise = throwM RootDirHasNoBasename
where
rl = last . splitPath $ l
--------------------------------------------------------------------------------
-- Path Examination
-- | Is p a parent of the given location? Implemented in terms of
-- 'stripDir'. The bases must match.
--
@@ -352,50 +390,14 @@ isParentOf :: Path b -> Path b -> Bool
isParentOf p l = isJust (stripDir p l :: Maybe (Path Rel))
-- |Get all parents of a path.
-- | Check whether the given Path is the root "/" path.
--
-- >>> getAllParents (MkPath "/abs/def/dod")
-- ["/abs/def","/abs","/"]
-- >>> getAllParents (MkPath "/")
-- []
getAllParents :: Path Abs -> [Path Abs]
getAllParents (MkPath p)
| np == BS.singleton pathSeparator = []
| otherwise = dirname (MkPath np) : getAllParents (dirname $ MkPath np)
where
np = dropTrailingPathSeparator . normalise $ p
-- | Extract the directory name of a path.
--
-- >>> dirname (MkPath "/abc/def/dod")
-- "/abc/def"
-- >>> dirname (MkPath "/")
-- "/"
dirname :: Path Abs -> Path Abs
dirname (MkPath fp) = MkPath (takeDirectory $ dropTrailingPathSeparator fp)
-- | Extract the file part of a path.
--
--
-- The following properties hold:
--
-- @basename (p \<\/> a) == basename a@
--
-- Throws: `PathException` if given the root path "/"
--
-- >>> basename (MkPath "/abc/def/dod") :: Maybe (Path Fn)
-- Just "dod"
-- >>> basename (MkPath "/abc/def/dod/") :: Maybe (Path Fn)
-- Just "dod"
-- >>> basename (MkPath "/") :: Maybe (Path Fn)
-- Nothing
basename :: MonadThrow m => Path b -> m (Path Fn)
basename (MkPath l)
| not (isAbsolute rl) = return $ MkPath rl
| otherwise = throwM RootDirHasNoBasename
where
rl = last . splitPath . dropTrailingPathSeparator $ l
-- >>> isRootPath (MkPath "/lal/lad")
-- False
-- >>> isRootPath (MkPath "/")
-- True
isRootPath :: Path Abs -> Bool
isRootPath = (== rootPath)
--------------------------------------------------------------------------------
@@ -410,9 +412,6 @@ withRelPath :: Path Rel -> (ByteString -> IO a) -> IO a
withRelPath (MkPath p) action = action p
withFnPath :: Path Fn -> (ByteString -> IO a) -> IO a
withFnPath (MkPath p) action = action p
------------------------
-- ByteString helpers
@@ -422,3 +421,52 @@ withFnPath (MkPath p) action = action p
stripPrefix :: ByteString -> ByteString -> Maybe ByteString
stripPrefix a b = BS.pack `fmap` L.stripPrefix (BS.unpack a) (BS.unpack b)
#endif
------------------------
-- QuasiQuoters
instance Lift (Path a) where
lift (MkPath bs) = AppE <$> [| MkPath . BS.pack |] <*> lift (BS.unpack bs)
qq :: (ByteString -> Q Exp) -> QuasiQuoter
qq quoteExp' =
QuasiQuoter
{ quoteExp = (\s -> quoteExp' . fromString $ s)
, quotePat = \_ ->
fail "illegal QuasiQuote (allowed as expression only, used as a pattern)"
, quoteType = \_ ->
fail "illegal QuasiQuote (allowed as expression only, used as a type)"
, quoteDec = \_ ->
fail "illegal QuasiQuote (allowed as expression only, used as a declaration)"
}
mkAbs :: ByteString -> Q Exp
mkAbs = either (error . show) lift . parseAbs
mkRel :: ByteString -> Q Exp
mkRel = either (error . show) lift . parseRel
-- | Quasiquote an absolute Path. This accepts Unicode Chars and will encode as UTF-8.
--
-- >>> [abs|/etc/profile|] :: Path Abs
-- "/etc/profile"
-- >>> [abs|/|] :: Path Abs
-- "/"
-- >>> [abs|/|] :: Path Abs
-- "/\239\131\144"
abs :: QuasiQuoter
abs = qq mkAbs
-- | Quasiquote a relative Path. This accepts Unicode Chars and will encode as UTF-8.
--
-- >>> [rel|etc|] :: Path Rel
-- "etc"
-- >>> [rel|bar/baz|] :: Path Rel
-- "bar/baz"
-- >>> [rel||] :: Path Rel
-- "\239\131\144"
rel :: QuasiQuoter
rel = qq mkRel

View File

@@ -10,15 +10,19 @@ import Control.DeepSeq (NFData (..))
import Data.ByteString (ByteString)
import Data.Data
-- | Path of some base and type.
-- | The main Path type.
--
-- Internally is a ByteString. The ByteString can be of two formats only:
-- The type variable 'b' is either:
--
-- 1. without trailing path separator: @file.txt@, @foo\/bar.txt@, @\/foo\/bar.txt@
-- 2. with trailing path separator: @foo\/@, @\/foo\/bar\/@
-- * Abs -- absolute path
-- * Rel -- relative path
--
-- There are no duplicate
-- path separators @\/\/@, no @..@, no @.\/@, no @~\/@, etc.
-- Internally is a ByteString. The path is guaranteed to
-- be normalised and contain no trailing Path separators,
-- except for the '/' root path.
--
-- There are no duplicate path separators
-- @\/\/@, no @..@, no @.\/@, no @~\/@, etc.
data Path b = MkPath ByteString
deriving (Typeable)

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
module HPath.IO where
import HPath
canonicalizePath :: Path b -> IO (Path Abs)
toAbs :: Path b -> IO (Path Abs)

View File

@@ -1,7 +0,0 @@
resolver: lts-12.1
packages:
- '.'
extra-deps:
- IfElse-0.85

View File

@@ -1,85 +0,0 @@
{-# LANGUAGE OverloadedStrings #-}
module HPath.IO.ReadFileEOFSpec where
import Test.Hspec
import System.IO.Error
(
ioeGetErrorType
)
import GHC.IO.Exception
(
IOErrorType(..)
)
import Utils
upTmpDir :: IO ()
upTmpDir = do
setTmpDir "ReadFileEOFSpec"
createTmpDir
setupFiles :: IO ()
setupFiles = do
createRegularFile' "fileWithContent"
createRegularFile' "fileWithoutContent"
createSymlink' "inputFileSymL" "fileWithContent"
createDir' "alreadyExistsD"
createRegularFile' "noPerms"
noPerms "noPerms"
createDir' "noPermsD"
createRegularFile' "noPermsD/inputFile"
noPerms "noPermsD"
writeFile' "fileWithContent" "Blahfaselgagaga"
cleanupFiles :: IO ()
cleanupFiles = do
deleteFile' "fileWithContent"
deleteFile' "fileWithoutContent"
deleteFile' "inputFileSymL"
deleteDir' "alreadyExistsD"
normalFilePerms "noPerms"
deleteFile' "noPerms"
normalDirPerms "noPermsD"
deleteFile' "noPermsD/inputFile"
deleteDir' "noPermsD"
spec :: Spec
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
describe "HPath.IO.readFileEOF" $ do
-- successes --
it "readFileEOF (Strict) file with content, everything clear" $ do
out <- readFileEOF' "fileWithContent"
out `shouldBe` "Blahfaselgagaga"
it "readFileEOF (Strict) symlink, everything clear" $ do
out <- readFileEOF' "inputFileSymL"
out `shouldBe` "Blahfaselgagaga"
it "readFileEOF (Strict) empty file, everything clear" $ do
out <- readFileEOF' "fileWithoutContent"
out `shouldBe` ""
-- posix failures --
it "readFileEOF (Strict) directory, wrong file type" $ do
readFileEOF' "alreadyExistsD"
`shouldThrow` (\e -> ioeGetErrorType e == InappropriateType)
it "readFileEOF (Strict) file, no permissions" $ do
readFileEOF' "noPerms"
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
it "readFileEOF (Strict) file, no permissions on dir" $ do
readFileEOF' "noPermsD/inputFile"
`shouldThrow` (\e -> ioeGetErrorType e == PermissionDenied)
it "readFileEOF (Strict) file, no such file" $ do
readFileEOF' "lalala"
`shouldThrow` (\e -> ioeGetErrorType e == NoSuchThing)

31
unix/LICENSE Normal file
View File

@@ -0,0 +1,31 @@
The Glasgow Haskell Compiler License
Copyright 2004, The University Court of the University of Glasgow.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither name of the University nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.

15
unix/README.md Normal file
View File

@@ -0,0 +1,15 @@
The `unix` Package [![Hackage](https://img.shields.io/hackage/v/unix.svg)](https://hackage.haskell.org/package/unix) [![Build Status](https://travis-ci.org/haskell/unix.svg)](https://travis-ci.org/haskell/unix)
==================
See [`unix` on Hackage](http://hackage.haskell.org/package/unix) for
more information.
Installing from Git
-------------------
To build this package using Cabal directly from Git, you must run
`autoreconf -i` before the usual Cabal build steps (`cabal
{configure,build,install}`). The program `autoreconf` is part of
[GNU autoconf](http://www.gnu.org/software/autoconf/). There is no
need to run the `configure` script: `cabal configure` will do this for
you.

6
unix/Setup.hs Normal file
View File

@@ -0,0 +1,6 @@
module Main (main) where
import Distribution.Simple
main :: IO ()
main = defaultMainWithHooks autoconfUserHooks

189
unix/System/Posix.hs Normal file
View File

@@ -0,0 +1,189 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE Safe #-}
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix
-- Copyright : (c) The University of Glasgow 2002
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : libraries@haskell.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- <http://pubs.opengroup.org/onlinepubs/9699919799/ POSIX.1-2008> support
--
-----------------------------------------------------------------------------
module System.Posix (
module System.Posix.Types,
module System.Posix.Signals,
module System.Posix.Directory,
module System.Posix.Files,
module System.Posix.Unistd,
module System.Posix.IO,
module System.Posix.Env,
module System.Posix.Process,
module System.Posix.Temp,
module System.Posix.Terminal,
module System.Posix.Time,
module System.Posix.User,
module System.Posix.Resource,
module System.Posix.Semaphore,
module System.Posix.SharedMem,
module System.Posix.DynamicLinker,
-- XXX 'Module' type clashes with GHC
-- module System.Posix.DynamicLinker.Module
) where
import System.Posix.Types
import System.Posix.Signals
import System.Posix.Directory
import System.Posix.Files
import System.Posix.Unistd
import System.Posix.Process
import System.Posix.IO
import System.Posix.Env
import System.Posix.Temp
import System.Posix.Terminal
import System.Posix.Time
import System.Posix.User
import System.Posix.Resource
import System.Posix.Semaphore
import System.Posix.SharedMem
-- XXX: bad planning, we have two constructors called "Default"
import System.Posix.DynamicLinker hiding (Default)
--import System.Posix.DynamicLinker.Module
{- TODO
Here we detail our support for the IEEE Std 1003.1-2001 standard. For
each header file defined by the standard, we categorise its
functionality as
- "supported"
Full equivalent functionality is provided by the specified Haskell
module.
- "unsupported" (functionality not provided by a Haskell module)
The functionality is not currently provided.
- "to be supported"
Currently unsupported, but support is planned for the future.
Exceptions are listed where appropriate.
Interfaces supported
--------------------
unix package:
dirent.h System.Posix.Directory
dlfcn.h System.Posix.DynamicLinker
errno.h Foreign.C.Error
fcntl.h System.Posix.IO
signal.h System.Posix.Signals
sys/stat.h System.Posix.Files
sys/times.h System.Posix.Process
sys/types.h System.Posix.Types (with exceptions...)
sys/utsname.h System.Posix.Unistd
sys/wait.h System.Posix.Process
termios.h System.Posix.Terminal (check exceptions)
unistd.h System.Posix.*
utime.h System.Posix.Files
pwd.h System.Posix.User
grp.h System.Posix.User
stdlib.h: System.Posix.Env (getenv()/setenv()/unsetenv())
System.Posix.Temp (mkstemp())
sys/resource.h: System.Posix.Resource (get/setrlimit() only)
regex-posix package:
regex.h Text.Regex.Posix
network package:
arpa/inet.h
net/if.h
netinet/in.h
netinet/tcp.h
sys/socket.h
sys/un.h
To be supported
---------------
limits.h (pathconf()/fpathconf() already done)
poll.h
sys/resource.h (getrusage(): use instead of times() for getProcessTimes?)
sys/select.h
sys/statvfs.h (?)
sys/time.h (but maybe not the itimer?)
time.h (System.Posix.Time)
stdio.h (popen only: System.Posix.IO)
sys/mman.h
Unsupported interfaces
----------------------
aio.h
assert.h
complex.h
cpio.h
ctype.h
fenv.h
float.h
fmtmsg.h
fnmatch.h
ftw.h
glob.h
iconv.h
inttypes.h
iso646.h
langinfo.h
libgen.h
locale.h (see System.Locale)
math.h
monetary.h
mqueue.h
ndbm.h
netdb.h
nl_types.h
pthread.h
sched.h
search.h
semaphore.h
setjmp.h
spawn.h
stdarg.h
stdbool.h
stddef.h
stdint.h
stdio.h except: popen()
stdlib.h except: exit(): System.Posix.Process
free()/malloc(): Foreign.Marshal.Alloc
getenv()/setenv(): ?? System.Environment
rand() etc.: System.Random
string.h
strings.h
stropts.h
sys/ipc.h
sys/msg.h
sys/sem.h
sys/shm.h
sys/timeb.h
sys/uio.h
syslog.h
tar.h
tgmath.h
trace.h
ucontext.h
ulimit.h
utmpx.h
wchar.h
wctype.h
wordexp.h
-}

View File

@@ -0,0 +1,69 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE Safe #-}
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.ByteString
-- Copyright : (c) The University of Glasgow 2002
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : libraries@haskell.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- <http://pubs.opengroup.org/onlinepubs/9699919799/ POSIX.1-2008>
-- support with 'ByteString' file paths and environment strings.
--
-- This module exports exactly the same API as "System.Posix", except
-- that all file paths and environment strings are represented by
-- 'ByteString' instead of 'String'. The "System.Posix" API
-- implicitly translates all file paths and environment strings using
-- the locale encoding, whereas this version of the API does no
-- encoding or decoding and works directly in terms of raw bytes.
--
-- Note that if you do need to interpret file paths or environment
-- strings as text, then some Unicode encoding or decoding should be
-- applied first.
--
-----------------------------------------------------------------------------
module System.Posix.ByteString (
System.Posix.ByteString.FilePath.RawFilePath,
module System.Posix.Types,
module System.Posix.Signals,
module System.Posix.Directory.ByteString,
module System.Posix.Files.ByteString,
module System.Posix.Unistd,
module System.Posix.IO.ByteString,
module System.Posix.Env.ByteString,
module System.Posix.Process.ByteString,
module System.Posix.Temp.ByteString,
module System.Posix.Terminal.ByteString,
module System.Posix.Time,
module System.Posix.User,
module System.Posix.Resource,
module System.Posix.Semaphore,
module System.Posix.SharedMem,
module System.Posix.DynamicLinker.ByteString,
-- XXX 'Module' type clashes with GHC
-- module System.Posix.DynamicLinker.Module.ByteString
) where
import System.Posix.ByteString.FilePath
import System.Posix.Types
import System.Posix.Signals
import System.Posix.Directory.ByteString
import System.Posix.Files.ByteString
import System.Posix.Unistd
import System.Posix.Process.ByteString
import System.Posix.IO.ByteString
import System.Posix.Env.ByteString
import System.Posix.Temp.ByteString
import System.Posix.Terminal.ByteString
import System.Posix.Time
import System.Posix.User
import System.Posix.Resource
import System.Posix.Semaphore
import System.Posix.SharedMem
-- XXX: bad planning, we have two constructors called "Default"
import System.Posix.DynamicLinker.ByteString hiding (Default)
--import System.Posix.DynamicLinker.Module.ByteString

View File

@@ -0,0 +1,127 @@
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.ByteString.FilePath
-- Copyright : (c) The University of Glasgow 2002
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : libraries@haskell.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- Internal stuff: support for ByteString FilePaths
--
-----------------------------------------------------------------------------
module System.Posix.ByteString.FilePath (
RawFilePath, withFilePath, peekFilePath, peekFilePathLen,
throwErrnoPathIfMinus1Retry,
throwErrnoPathIfMinus1Retry_,
throwErrnoPathIfNullRetry,
throwErrnoPathIfRetry,
throwErrnoPath,
throwErrnoPathIf,
throwErrnoPathIf_,
throwErrnoPathIfNull,
throwErrnoPathIfMinus1,
throwErrnoPathIfMinus1_
) where
import Foreign hiding ( void )
import Foreign.C hiding (
throwErrnoPath,
throwErrnoPathIf,
throwErrnoPathIf_,
throwErrnoPathIfNull,
throwErrnoPathIfMinus1,
throwErrnoPathIfMinus1_ )
import Control.Monad
import Data.ByteString
import Data.ByteString.Char8 as BC
import Prelude hiding (FilePath)
-- | A literal POSIX file path
type RawFilePath = ByteString
withFilePath :: RawFilePath -> (CString -> IO a) -> IO a
withFilePath = useAsCString
peekFilePath :: CString -> IO RawFilePath
peekFilePath = packCString
peekFilePathLen :: CStringLen -> IO RawFilePath
peekFilePathLen = packCStringLen
throwErrnoPathIfMinus1Retry :: (Eq a, Num a)
=> String -> RawFilePath -> IO a -> IO a
throwErrnoPathIfMinus1Retry loc path f = do
throwErrnoPathIfRetry (== -1) loc path f
throwErrnoPathIfMinus1Retry_ :: (Eq a, Num a)
=> String -> RawFilePath -> IO a -> IO ()
throwErrnoPathIfMinus1Retry_ loc path f =
void $ throwErrnoPathIfRetry (== -1) loc path f
throwErrnoPathIfNullRetry :: String -> RawFilePath -> IO (Ptr a) -> IO (Ptr a)
throwErrnoPathIfNullRetry loc path f =
throwErrnoPathIfRetry (== nullPtr) loc path f
throwErrnoPathIfRetry :: (a -> Bool) -> String -> RawFilePath -> IO a -> IO a
throwErrnoPathIfRetry pr loc rpath f =
do
res <- f
if pr res
then do
err <- getErrno
if err == eINTR
then throwErrnoPathIfRetry pr loc rpath f
else throwErrnoPath loc rpath
else return res
-- | as 'throwErrno', but exceptions include the given path when appropriate.
--
throwErrnoPath :: String -> RawFilePath -> IO a
throwErrnoPath loc path =
do
errno <- getErrno
ioError (errnoToIOError loc errno Nothing (Just (BC.unpack path)))
-- | as 'throwErrnoIf', but exceptions include the given path when
-- appropriate.
--
throwErrnoPathIf :: (a -> Bool) -> String -> RawFilePath -> IO a -> IO a
throwErrnoPathIf cond loc path f =
do
res <- f
if cond res then throwErrnoPath loc path else return res
-- | as 'throwErrnoIf_', but exceptions include the given path when
-- appropriate.
--
throwErrnoPathIf_ :: (a -> Bool) -> String -> RawFilePath -> IO a -> IO ()
throwErrnoPathIf_ cond loc path f = void $ throwErrnoPathIf cond loc path f
-- | as 'throwErrnoIfNull', but exceptions include the given path when
-- appropriate.
--
throwErrnoPathIfNull :: String -> RawFilePath -> IO (Ptr a) -> IO (Ptr a)
throwErrnoPathIfNull = throwErrnoPathIf (== nullPtr)
-- | as 'throwErrnoIfMinus1', but exceptions include the given path when
-- appropriate.
--
throwErrnoPathIfMinus1 :: (Eq a, Num a) => String -> RawFilePath -> IO a -> IO a
throwErrnoPathIfMinus1 = throwErrnoPathIf (== -1)
-- | as 'throwErrnoIfMinus1_', but exceptions include the given path when
-- appropriate.
--
throwErrnoPathIfMinus1_ :: (Eq a, Num a) => String -> RawFilePath -> IO a -> IO ()
throwErrnoPathIfMinus1_ = throwErrnoPathIf_ (== -1)

View File

@@ -0,0 +1,164 @@
{-# LANGUAGE CApiFFI #-}
{-# LANGUAGE NondecreasingIndentation #-}
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.Directory
-- Copyright : (c) The University of Glasgow 2002
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : libraries@haskell.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- String-based POSIX directory support
--
-----------------------------------------------------------------------------
#include "HsUnix.h"
-- hack copied from System.Posix.Files
#if !defined(PATH_MAX)
# define PATH_MAX 4096
#endif
module System.Posix.Directory (
-- * Creating and removing directories
createDirectory, removeDirectory,
-- * Reading directories
DirStream,
openDirStream,
readDirStream,
rewindDirStream,
closeDirStream,
DirStreamOffset,
#ifdef HAVE_TELLDIR
tellDirStream,
#endif
#ifdef HAVE_SEEKDIR
seekDirStream,
#endif
-- * The working dirctory
getWorkingDirectory,
changeWorkingDirectory,
changeWorkingDirectoryFd,
) where
import System.IO.Error
import System.Posix.Error
import System.Posix.Types
import Foreign
import Foreign.C
import System.Posix.Directory.Common
import System.Posix.Internals (withFilePath, peekFilePath)
-- | @createDirectory dir mode@ calls @mkdir@ to
-- create a new directory, @dir@, with permissions based on
-- @mode@.
createDirectory :: FilePath -> FileMode -> IO ()
createDirectory name mode =
withFilePath name $ \s ->
throwErrnoPathIfMinus1Retry_ "createDirectory" name (c_mkdir s mode)
-- POSIX doesn't allow mkdir() to return EINTR, but it does on
-- OS X (#5184), so we need the Retry variant here.
foreign import ccall unsafe "mkdir"
c_mkdir :: CString -> CMode -> IO CInt
-- | @openDirStream dir@ calls @opendir@ to obtain a
-- directory stream for @dir@.
openDirStream :: FilePath -> IO DirStream
openDirStream name =
withFilePath name $ \s -> do
dirp <- throwErrnoPathIfNullRetry "openDirStream" name $ c_opendir s
return (DirStream dirp)
foreign import capi unsafe "HsUnix.h opendir"
c_opendir :: CString -> IO (Ptr CDir)
-- | @readDirStream dp@ calls @readdir@ to obtain the
-- next directory entry (@struct dirent@) for the open directory
-- stream @dp@, and returns the @d_name@ member of that
-- structure.
readDirStream :: DirStream -> IO FilePath
readDirStream (DirStream dirp) =
alloca $ \ptr_dEnt -> loop ptr_dEnt
where
loop ptr_dEnt = do
resetErrno
r <- c_readdir dirp ptr_dEnt
if (r == 0)
then do dEnt <- peek ptr_dEnt
if (dEnt == nullPtr)
then return []
else do
entry <- (d_name dEnt >>= peekFilePath)
c_freeDirEnt dEnt
return entry
else do errno <- getErrno
if (errno == eINTR) then loop ptr_dEnt else do
let (Errno eo) = errno
if (eo == 0)
then return []
else throwErrno "readDirStream"
-- traversing directories
foreign import ccall unsafe "__hscore_readdir"
c_readdir :: Ptr CDir -> Ptr (Ptr CDirent) -> IO CInt
foreign import ccall unsafe "__hscore_free_dirent"
c_freeDirEnt :: Ptr CDirent -> IO ()
foreign import ccall unsafe "__hscore_d_name"
d_name :: Ptr CDirent -> IO CString
-- | @getWorkingDirectory@ calls @getcwd@ to obtain the name
-- of the current working directory.
getWorkingDirectory :: IO FilePath
getWorkingDirectory = go (#const PATH_MAX)
where
go bytes = do
r <- allocaBytes bytes $ \buf -> do
buf' <- c_getcwd buf (fromIntegral bytes)
if buf' /= nullPtr
then do s <- peekFilePath buf
return (Just s)
else do errno <- getErrno
if errno == eRANGE
-- we use Nothing to indicate that we should
-- try again with a bigger buffer
then return Nothing
else throwErrno "getWorkingDirectory"
maybe (go (2 * bytes)) return r
foreign import ccall unsafe "getcwd"
c_getcwd :: Ptr CChar -> CSize -> IO (Ptr CChar)
-- | @changeWorkingDirectory dir@ calls @chdir@ to change
-- the current working directory to @dir@.
changeWorkingDirectory :: FilePath -> IO ()
changeWorkingDirectory path =
modifyIOError (`ioeSetFileName` path) $
withFilePath path $ \s ->
throwErrnoIfMinus1Retry_ "changeWorkingDirectory" (c_chdir s)
foreign import ccall unsafe "chdir"
c_chdir :: CString -> IO CInt
removeDirectory :: FilePath -> IO ()
removeDirectory path =
modifyIOError (`ioeSetFileName` path) $
withFilePath path $ \s ->
throwErrnoIfMinus1Retry_ "removeDirectory" (c_rmdir s)
foreign import ccall unsafe "rmdir"
c_rmdir :: CString -> IO CInt

View File

@@ -0,0 +1,165 @@
{-# LANGUAGE CApiFFI #-}
{-# LANGUAGE NondecreasingIndentation #-}
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.Directory.ByteString
-- Copyright : (c) The University of Glasgow 2002
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : libraries@haskell.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- String-based POSIX directory support
--
-----------------------------------------------------------------------------
#include "HsUnix.h"
-- hack copied from System.Posix.Files
#if !defined(PATH_MAX)
# define PATH_MAX 4096
#endif
module System.Posix.Directory.ByteString (
-- * Creating and removing directories
createDirectory, removeDirectory,
-- * Reading directories
DirStream,
openDirStream,
readDirStream,
rewindDirStream,
closeDirStream,
DirStreamOffset,
#ifdef HAVE_TELLDIR
tellDirStream,
#endif
#ifdef HAVE_SEEKDIR
seekDirStream,
#endif
-- * The working directory
getWorkingDirectory,
changeWorkingDirectory,
changeWorkingDirectoryFd,
) where
import System.IO.Error
import System.Posix.Types
import Foreign
import Foreign.C
import Data.ByteString.Char8 as BC
import System.Posix.Directory.Common
import System.Posix.ByteString.FilePath
-- | @createDirectory dir mode@ calls @mkdir@ to
-- create a new directory, @dir@, with permissions based on
-- @mode@.
createDirectory :: RawFilePath -> FileMode -> IO ()
createDirectory name mode =
withFilePath name $ \s ->
throwErrnoPathIfMinus1Retry_ "createDirectory" name (c_mkdir s mode)
-- POSIX doesn't allow mkdir() to return EINTR, but it does on
-- OS X (#5184), so we need the Retry variant here.
foreign import ccall unsafe "mkdir"
c_mkdir :: CString -> CMode -> IO CInt
-- | @openDirStream dir@ calls @opendir@ to obtain a
-- directory stream for @dir@.
openDirStream :: RawFilePath -> IO DirStream
openDirStream name =
withFilePath name $ \s -> do
dirp <- throwErrnoPathIfNullRetry "openDirStream" name $ c_opendir s
return (DirStream dirp)
foreign import capi unsafe "HsUnix.h opendir"
c_opendir :: CString -> IO (Ptr CDir)
-- | @readDirStream dp@ calls @readdir@ to obtain the
-- next directory entry (@struct dirent@) for the open directory
-- stream @dp@, and returns the @d_name@ member of that
-- structure.
readDirStream :: DirStream -> IO RawFilePath
readDirStream (DirStream dirp) =
alloca $ \ptr_dEnt -> loop ptr_dEnt
where
loop ptr_dEnt = do
resetErrno
r <- c_readdir dirp ptr_dEnt
if (r == 0)
then do dEnt <- peek ptr_dEnt
if (dEnt == nullPtr)
then return BC.empty
else do
entry <- (d_name dEnt >>= peekFilePath)
c_freeDirEnt dEnt
return entry
else do errno <- getErrno
if (errno == eINTR) then loop ptr_dEnt else do
let (Errno eo) = errno
if (eo == 0)
then return BC.empty
else throwErrno "readDirStream"
-- traversing directories
foreign import ccall unsafe "__hscore_readdir"
c_readdir :: Ptr CDir -> Ptr (Ptr CDirent) -> IO CInt
foreign import ccall unsafe "__hscore_free_dirent"
c_freeDirEnt :: Ptr CDirent -> IO ()
foreign import ccall unsafe "__hscore_d_name"
d_name :: Ptr CDirent -> IO CString
-- | @getWorkingDirectory@ calls @getcwd@ to obtain the name
-- of the current working directory.
getWorkingDirectory :: IO RawFilePath
getWorkingDirectory = go (#const PATH_MAX)
where
go bytes = do
r <- allocaBytes bytes $ \buf -> do
buf' <- c_getcwd buf (fromIntegral bytes)
if buf' /= nullPtr
then do s <- peekFilePath buf
return (Just s)
else do errno <- getErrno
if errno == eRANGE
-- we use Nothing to indicate that we should
-- try again with a bigger buffer
then return Nothing
else throwErrno "getWorkingDirectory"
maybe (go (2 * bytes)) return r
foreign import ccall unsafe "getcwd"
c_getcwd :: Ptr CChar -> CSize -> IO (Ptr CChar)
-- | @changeWorkingDirectory dir@ calls @chdir@ to change
-- the current working directory to @dir@.
changeWorkingDirectory :: RawFilePath -> IO ()
changeWorkingDirectory path =
modifyIOError (`ioeSetFileName` (BC.unpack path)) $
withFilePath path $ \s ->
throwErrnoIfMinus1Retry_ "changeWorkingDirectory" (c_chdir s)
foreign import ccall unsafe "chdir"
c_chdir :: CString -> IO CInt
removeDirectory :: RawFilePath -> IO ()
removeDirectory path =
modifyIOError (`ioeSetFileName` BC.unpack path) $
withFilePath path $ \s ->
throwErrnoIfMinus1Retry_ "removeDirectory" (c_rmdir s)
foreign import ccall unsafe "rmdir"
c_rmdir :: CString -> IO CInt

View File

@@ -0,0 +1,88 @@
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.Directory.Common
-- Copyright : (c) The University of Glasgow 2002
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : libraries@haskell.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- POSIX directory support
--
-----------------------------------------------------------------------------
#include "HsUnix.h"
module System.Posix.Directory.Common (
DirStream(..), CDir, CDirent, DirStreamOffset(..),
rewindDirStream,
closeDirStream,
#ifdef HAVE_SEEKDIR
seekDirStream,
#endif
#ifdef HAVE_TELLDIR
tellDirStream,
#endif
changeWorkingDirectoryFd,
) where
import System.Posix.Types
import Foreign
import Foreign.C
newtype DirStream = DirStream (Ptr CDir)
data {-# CTYPE "DIR" #-} CDir
data {-# CTYPE "struct dirent" #-} CDirent
-- | @rewindDirStream dp@ calls @rewinddir@ to reposition
-- the directory stream @dp@ at the beginning of the directory.
rewindDirStream :: DirStream -> IO ()
rewindDirStream (DirStream dirp) = c_rewinddir dirp
foreign import ccall unsafe "rewinddir"
c_rewinddir :: Ptr CDir -> IO ()
-- | @closeDirStream dp@ calls @closedir@ to close
-- the directory stream @dp@.
closeDirStream :: DirStream -> IO ()
closeDirStream (DirStream dirp) = do
throwErrnoIfMinus1Retry_ "closeDirStream" (c_closedir dirp)
foreign import ccall unsafe "closedir"
c_closedir :: Ptr CDir -> IO CInt
newtype DirStreamOffset = DirStreamOffset COff
#ifdef HAVE_SEEKDIR
seekDirStream :: DirStream -> DirStreamOffset -> IO ()
seekDirStream (DirStream dirp) (DirStreamOffset off) =
c_seekdir dirp (fromIntegral off) -- TODO: check for CLong/COff overflow
foreign import ccall unsafe "seekdir"
c_seekdir :: Ptr CDir -> CLong -> IO ()
#endif
#ifdef HAVE_TELLDIR
tellDirStream :: DirStream -> IO DirStreamOffset
tellDirStream (DirStream dirp) = do
off <- c_telldir dirp
return (DirStreamOffset (fromIntegral off)) -- TODO: check for overflow
foreign import ccall unsafe "telldir"
c_telldir :: Ptr CDir -> IO CLong
#endif
changeWorkingDirectoryFd :: Fd -> IO ()
changeWorkingDirectoryFd (Fd fd) =
throwErrnoIfMinus1Retry_ "changeWorkingDirectoryFd" (c_fchdir fd)
foreign import ccall unsafe "fchdir"
c_fchdir :: CInt -> IO CInt

View File

@@ -0,0 +1,72 @@
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.DynamicLinker
-- Copyright : (c) Volker Stolz <vs@foldr.org> 2003
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : vs@foldr.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- Dynamic linker support through dlopen()
-----------------------------------------------------------------------------
module System.Posix.DynamicLinker (
module System.Posix.DynamicLinker.Prim,
dlopen,
dlsym,
dlerror,
dlclose,
withDL, withDL_,
undl,
)
-- Usage:
-- ******
--
-- Let's assume you want to open a local shared library \'foo\' (.\/libfoo.so)
-- offering a function
-- @char \* mogrify (char\*,int)@
-- and invoke @str = mogrify("test",1)@:
--
--
-- type Fun = CString -> Int -> IO CString
-- foreign import dynamic unsafe fun__ :: FunPtr Fun -> Fun
--
-- withDL "libfoo.so" [RTLD_NOW] \$ \\ mod -> do
-- funptr <- dlsym mod "mogrify"
-- let fun = fun__ funptr
-- withCString "test" \$ \\ str -> do
-- strptr <- fun str 1
-- strstr <- peekCString strptr
-- ...
--
where
import System.Posix.DynamicLinker.Common
import System.Posix.DynamicLinker.Prim
#include "HsUnix.h"
import Control.Exception ( bracket )
import Control.Monad ( liftM )
import Foreign
import System.Posix.Internals ( withFilePath )
dlopen :: FilePath -> [RTLDFlags] -> IO DL
dlopen path flags = do
withFilePath path $ \ p -> do
liftM DLHandle $ throwDLErrorIf "dlopen" (== nullPtr) $ c_dlopen p (packRTLDFlags flags)
withDL :: FilePath -> [RTLDFlags] -> (DL -> IO a) -> IO a
withDL file flags f = bracket (dlopen file flags) (dlclose) f
withDL_ :: FilePath -> [RTLDFlags] -> (DL -> IO a) -> IO ()
withDL_ file flags f = withDL file flags f >> return ()

View File

@@ -0,0 +1,73 @@
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.DynamicLinker.ByteString
-- Copyright : (c) Volker Stolz <vs@foldr.org> 2003
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : vs@foldr.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- Dynamic linker support through dlopen()
-----------------------------------------------------------------------------
module System.Posix.DynamicLinker.ByteString (
module System.Posix.DynamicLinker.Prim,
dlopen,
dlsym,
dlerror,
dlclose,
withDL, withDL_,
undl,
)
-- Usage:
-- ******
--
-- Let's assume you want to open a local shared library \'foo\' (.\/libfoo.so)
-- offering a function
-- @char \* mogrify (char\*,int)@
-- and invoke @str = mogrify("test",1)@:
--
--
-- type Fun = CString -> Int -> IO CString
-- foreign import dynamic unsafe fun__ :: FunPtr Fun -> Fun
--
-- withDL "libfoo.so" [RTLD_NOW] \$ \\ mod -> do
-- funptr <- dlsym mod "mogrify"
-- let fun = fun__ funptr
-- withCString "test" \$ \\ str -> do
-- strptr <- fun str 1
-- strstr <- peekCString strptr
-- ...
--
where
import System.Posix.DynamicLinker.Common
import System.Posix.DynamicLinker.Prim
#include "HsUnix.h"
import Control.Exception ( bracket )
import Control.Monad ( liftM )
import Foreign
import System.Posix.ByteString.FilePath
dlopen :: RawFilePath -> [RTLDFlags] -> IO DL
dlopen path flags = do
withFilePath path $ \ p -> do
liftM DLHandle $ throwDLErrorIf "dlopen" (== nullPtr) $ c_dlopen p (packRTLDFlags flags)
withDL :: RawFilePath -> [RTLDFlags] -> (DL -> IO a) -> IO a
withDL file flags f = bracket (dlopen file flags) (dlclose) f
withDL_ :: RawFilePath -> [RTLDFlags] -> (DL -> IO a) -> IO ()
withDL_ file flags f = withDL file flags f >> return ()

View File

@@ -0,0 +1,92 @@
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.DynamicLinker.Common
-- Copyright : (c) Volker Stolz <vs@foldr.org> 2003
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : vs@foldr.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- Dynamic linker support through dlopen()
-----------------------------------------------------------------------------
module System.Posix.DynamicLinker.Common (
module System.Posix.DynamicLinker.Prim,
dlsym,
dlerror,
dlclose,
undl,
throwDLErrorIf,
Module(..)
)
-- Usage:
-- ******
--
-- Let's assume you want to open a local shared library \'foo\' (.\/libfoo.so)
-- offering a function
-- @char \* mogrify (char\*,int)@
-- and invoke @str = mogrify("test",1)@:
--
--
-- type Fun = CString -> Int -> IO CString
-- foreign import dynamic unsafe fun__ :: FunPtr Fun -> Fun
--
-- withDL "libfoo.so" [RTLD_NOW] \$ \\ mod -> do
-- funptr <- dlsym mod "mogrify"
-- let fun = fun__ funptr
-- withCString "test" \$ \\ str -> do
-- strptr <- fun str 1
-- strstr <- peekCString strptr
-- ...
--
where
#include "HsUnix.h"
import System.Posix.DynamicLinker.Prim
import Foreign
import Foreign.C
dlclose :: DL -> IO ()
dlclose (DLHandle h) = throwDLErrorIf_ "dlclose" (/= 0) $ c_dlclose h
dlclose h = error $ "dlclose: invalid argument" ++ (show h)
dlerror :: IO String
dlerror = c_dlerror >>= peekCString
-- |'dlsym' returns the address binding of the symbol described in @symbol@,
-- as it occurs in the shared object identified by @source@.
dlsym :: DL -> String -> IO (FunPtr a)
dlsym source symbol = do
withCAString symbol $ \ s -> do
throwDLErrorIf "dlsym" (== nullFunPtr) $ c_dlsym (packDL source) s
-- |'undl' obtains the raw handle. You mustn't do something like
-- @withDL mod flags $ liftM undl >>= \ p -> use p@
undl :: DL -> Ptr ()
undl = packDL
throwDLErrorIf :: String -> (a -> Bool) -> IO a -> IO a
throwDLErrorIf s p f = do
r <- f
if (p r)
then dlerror >>= \ err -> ioError (userError ( s ++ ": " ++ err))
else return r
throwDLErrorIf_ :: String -> (a -> Bool) -> IO a -> IO ()
throwDLErrorIf_ s p f = throwDLErrorIf s p f >> return ()
-- abstract handle for dynamically loaded module (EXPORTED)
--
newtype Module = Module (Ptr ())

View File

@@ -0,0 +1,121 @@
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.DynamicLinker.Module
-- Copyright : (c) Volker Stolz <vs@foldr.org> 2003
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : vs@foldr.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- DLOpen support, old API
-- Derived from GModule.chs by M.Weber & M.Chakravarty which is part of c2hs
-- I left the API more or less the same, mostly the flags are different.
--
-----------------------------------------------------------------------------
module System.Posix.DynamicLinker.Module (
-- Usage:
-- ******
--
-- Let's assume you want to open a local shared library 'foo' (./libfoo.so)
-- offering a function
-- char * mogrify (char*,int)
-- and invoke str = mogrify("test",1):
--
-- type Fun = CString -> Int -> IO CString
-- foreign import dynamic unsafe fun__ :: FunPtr Fun -> Fun
--
-- withModule (Just ".") ("libfoo.so") [RTLD_NOW] $ \ mod -> do
-- funptr <- moduleSymbol mod "mogrify"
-- let fun = fun__ funptr
-- withCString "test" $ \ str -> do
-- strptr <- fun str 1
-- strstr <- peekCString strptr
-- ...
Module
, moduleOpen -- :: String -> ModuleFlags -> IO Module
, moduleSymbol -- :: Source -> String -> IO (FunPtr a)
, moduleClose -- :: Module -> IO Bool
, moduleError -- :: IO String
, withModule -- :: Maybe String
-- -> String
-- -> [ModuleFlags ]
-- -> (Module -> IO a)
-- -> IO a
, withModule_ -- :: Maybe String
-- -> String
-- -> [ModuleFlags]
-- -> (Module -> IO a)
-- -> IO ()
)
where
#include "HsUnix.h"
import System.Posix.DynamicLinker
import System.Posix.DynamicLinker.Common
import Foreign.Ptr ( Ptr, nullPtr, FunPtr )
import System.Posix.Internals ( withFilePath )
unModule :: Module -> (Ptr ())
unModule (Module adr) = adr
-- Opens a module (EXPORTED)
--
moduleOpen :: String -> [RTLDFlags] -> IO Module
moduleOpen file flags = do
modPtr <- withFilePath file $ \ modAddr -> c_dlopen modAddr (packRTLDFlags flags)
if (modPtr == nullPtr)
then moduleError >>= \ err -> ioError (userError ("dlopen: " ++ err))
else return $ Module modPtr
-- Gets a symbol pointer from a module (EXPORTED)
--
moduleSymbol :: Module -> String -> IO (FunPtr a)
moduleSymbol file sym = dlsym (DLHandle (unModule file)) sym
-- Closes a module (EXPORTED)
--
moduleClose :: Module -> IO ()
moduleClose file = dlclose (DLHandle (unModule file))
-- Gets a string describing the last module error (EXPORTED)
--
moduleError :: IO String
moduleError = dlerror
-- Convenience function, cares for module open- & closing
-- additionally returns status of `moduleClose' (EXPORTED)
--
withModule :: Maybe String
-> String
-> [RTLDFlags]
-> (Module -> IO a)
-> IO a
withModule mdir file flags p = do
let modPath = case mdir of
Nothing -> file
Just dir -> dir ++ if ((head (reverse dir)) == '/')
then file
else ('/':file)
modu <- moduleOpen modPath flags
result <- p modu
moduleClose modu
return result
withModule_ :: Maybe String
-> String
-> [RTLDFlags]
-> (Module -> IO a)
-> IO ()
withModule_ dir file flags p = withModule dir file flags p >>= \ _ -> return ()

View File

@@ -0,0 +1,79 @@
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.DynamicLinker.Module.ByteString
-- Copyright : (c) Volker Stolz <vs@foldr.org> 2003
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : vs@foldr.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- DLOpen support, old API
-- Derived from GModule.chs by M.Weber & M.Chakravarty which is part of c2hs
-- I left the API more or less the same, mostly the flags are different.
--
-----------------------------------------------------------------------------
module System.Posix.DynamicLinker.Module.ByteString (
-- Usage:
-- ******
--
-- Let's assume you want to open a local shared library 'foo' (./libfoo.so)
-- offering a function
-- char * mogrify (char*,int)
-- and invoke str = mogrify("test",1):
--
-- type Fun = CString -> Int -> IO CString
-- foreign import dynamic unsafe fun__ :: FunPtr Fun -> Fun
--
-- withModule (Just ".") ("libfoo.so") [RTLD_NOW] $ \ mod -> do
-- funptr <- moduleSymbol mod "mogrify"
-- let fun = fun__ funptr
-- withCString "test" $ \ str -> do
-- strptr <- fun str 1
-- strstr <- peekCString strptr
-- ...
Module
, moduleOpen -- :: String -> ModuleFlags -> IO Module
, moduleSymbol -- :: Source -> String -> IO (FunPtr a)
, moduleClose -- :: Module -> IO Bool
, moduleError -- :: IO String
, withModule -- :: Maybe String
-- -> String
-- -> [ModuleFlags ]
-- -> (Module -> IO a)
-- -> IO a
, withModule_ -- :: Maybe String
-- -> String
-- -> [ModuleFlags]
-- -> (Module -> IO a)
-- -> IO ()
)
where
#include "HsUnix.h"
import System.Posix.DynamicLinker.Module hiding (moduleOpen)
import System.Posix.DynamicLinker.Prim
import System.Posix.DynamicLinker.Common
import Foreign
import System.Posix.ByteString.FilePath
-- Opens a module (EXPORTED)
--
moduleOpen :: RawFilePath -> [RTLDFlags] -> IO Module
moduleOpen file flags = do
modPtr <- withFilePath file $ \ modAddr -> c_dlopen modAddr (packRTLDFlags flags)
if (modPtr == nullPtr)
then moduleError >>= \ err -> ioError (userError ("dlopen: " ++ err))
else return $ Module modPtr

View File

@@ -0,0 +1,123 @@
{-# LANGUAGE Trustworthy #-}
#if __GLASGOW_HASKELL__ >= 709
{-# OPTIONS_GHC -fno-warn-trustworthy-safe #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.DynamicLinker.Prim
-- Copyright : (c) Volker Stolz <vs@foldr.org> 2003
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : vs@foldr.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- @dlopen(3)@ and friends
-- Derived from @GModule.chs@ by M.Weber & M.Chakravarty which is part of c2hs.
-- I left the API more or less the same, mostly the flags are different.
--
-----------------------------------------------------------------------------
module System.Posix.DynamicLinker.Prim (
-- * low level API
c_dlopen,
c_dlsym,
c_dlerror,
c_dlclose,
-- dlAddr, -- XXX NYI
haveRtldNext,
haveRtldLocal,
packRTLDFlags,
RTLDFlags(..),
packDL,
DL(..),
)
where
#include "HsUnix.h"
import Data.Bits ( (.|.) )
import Foreign.Ptr ( Ptr, FunPtr, nullPtr )
import Foreign.C.Types
import Foreign.C.String ( CString )
-- |On some hosts (e.g. SuSe and Ubuntu Linux) @RTLD_NEXT@ (and
-- @RTLD_DEFAULT@) are not visible without setting the macro
-- @_GNU_SOURCE@. Since we don\'t want to define this macro, you can use
-- the function 'haveRtldNext' to check wether the flag `Next` is
-- available. Ideally, this will be optimized by the compiler so that it
-- should be as efficient as an @#ifdef@.
--
-- If you fail to test the flag and use it although it is undefined,
-- 'packDL' will throw an error.
haveRtldNext :: Bool
#ifdef HAVE_RTLDNEXT
haveRtldNext = True
foreign import ccall unsafe "__hsunix_rtldNext" rtldNext :: Ptr a
#else /* HAVE_RTLDNEXT */
haveRtldNext = False
#endif /* HAVE_RTLDNEXT */
#ifdef HAVE_RTLDDEFAULT
foreign import ccall unsafe "__hsunix_rtldDefault" rtldDefault :: Ptr a
#endif /* HAVE_RTLDDEFAULT */
haveRtldLocal :: Bool
haveRtldLocal = True
{-# DEPRECATED haveRtldLocal "defaults to True" #-}
-- |Flags for 'System.Posix.DynamicLinker.dlopen'.
data RTLDFlags
= RTLD_LAZY
| RTLD_NOW
| RTLD_GLOBAL
| RTLD_LOCAL
deriving (Show, Read)
foreign import ccall unsafe "dlopen" c_dlopen :: CString -> CInt -> IO (Ptr ())
foreign import ccall unsafe "dlsym" c_dlsym :: Ptr () -> CString -> IO (FunPtr a)
foreign import ccall unsafe "dlerror" c_dlerror :: IO CString
foreign import ccall unsafe "dlclose" c_dlclose :: (Ptr ()) -> IO CInt
packRTLDFlags :: [RTLDFlags] -> CInt
packRTLDFlags flags = foldl (\ s f -> (packRTLDFlag f) .|. s) 0 flags
packRTLDFlag :: RTLDFlags -> CInt
packRTLDFlag RTLD_LAZY = #const RTLD_LAZY
packRTLDFlag RTLD_NOW = #const RTLD_NOW
packRTLDFlag RTLD_GLOBAL = #const RTLD_GLOBAL
packRTLDFlag RTLD_LOCAL = #const RTLD_LOCAL
-- |Flags for 'System.Posix.DynamicLinker.dlsym'. Notice that 'Next'
-- might not be available on your particular platform! Use
-- 'haveRtldNext'.
--
-- If 'RTLD_DEFAULT' is not defined on your platform, 'packDL' 'Default'
-- reduces to 'nullPtr'.
data DL = Null | Next | Default | DLHandle (Ptr ()) deriving (Show)
packDL :: DL -> Ptr ()
packDL Null = nullPtr
#ifdef HAVE_RTLDNEXT
packDL Next = rtldNext
#else
packDL Next = error "RTLD_NEXT not available"
#endif
#ifdef HAVE_RTLDDEFAULT
packDL Default = rtldDefault
#else
packDL Default = nullPtr
#endif
packDL (DLHandle h) = h

205
unix/System/Posix/Env.hsc Normal file
View File

@@ -0,0 +1,205 @@
{-# LANGUAGE CApiFFI #-}
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.Env
-- Copyright : (c) The University of Glasgow 2002
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : libraries@haskell.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- POSIX environment support
--
-----------------------------------------------------------------------------
module System.Posix.Env (
getEnv
, getEnvDefault
, getEnvironmentPrim
, getEnvironment
, setEnvironment
, putEnv
, setEnv
, unsetEnv
, clearEnv
) where
#include "HsUnix.h"
import Foreign.C.Error (throwErrnoIfMinus1_)
import Foreign.C.Types
import Foreign.C.String
import Foreign.Marshal.Array
import Foreign.Ptr
import Foreign.Storable
import Control.Monad
import Data.Maybe (fromMaybe)
import System.Posix.Internals
#if !MIN_VERSION_base(4,7,0)
-- needed for backported local 'newFilePath' binding in 'putEnv'
import GHC.IO.Encoding (getFileSystemEncoding)
import qualified GHC.Foreign as GHC (newCString)
#endif
-- |'getEnv' looks up a variable in the environment.
getEnv ::
String {- ^ variable name -} ->
IO (Maybe String) {- ^ variable value -}
getEnv name = do
litstring <- withFilePath name c_getenv
if litstring /= nullPtr
then liftM Just $ peekFilePath litstring
else return Nothing
-- |'getEnvDefault' is a wrapper around 'getEnv' where the
-- programmer can specify a fallback if the variable is not found
-- in the environment.
getEnvDefault ::
String {- ^ variable name -} ->
String {- ^ fallback value -} ->
IO String {- ^ variable value or fallback value -}
getEnvDefault name fallback = liftM (fromMaybe fallback) (getEnv name)
foreign import ccall unsafe "getenv"
c_getenv :: CString -> IO CString
getEnvironmentPrim :: IO [String]
getEnvironmentPrim = do
c_environ <- getCEnviron
-- environ can be NULL
if c_environ == nullPtr
then return []
else do
arr <- peekArray0 nullPtr c_environ
mapM peekFilePath arr
getCEnviron :: IO (Ptr CString)
#if HAVE__NSGETENVIRON
-- You should not access @char **environ@ directly on Darwin in a bundle/shared library.
-- See #2458 and http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man7/environ.7.html
getCEnviron = nsGetEnviron >>= peek
foreign import ccall unsafe "_NSGetEnviron"
nsGetEnviron :: IO (Ptr (Ptr CString))
#else
getCEnviron = peek c_environ_p
foreign import ccall unsafe "&environ"
c_environ_p :: Ptr (Ptr CString)
#endif
-- |'getEnvironment' retrieves the entire environment as a
-- list of @(key,value)@ pairs.
getEnvironment :: IO [(String,String)] {- ^ @[(key,value)]@ -}
getEnvironment = do
env <- getEnvironmentPrim
return $ map (dropEq.(break ((==) '='))) env
where
dropEq (x,'=':ys) = (x,ys)
dropEq (x,_) = error $ "getEnvironment: insane variable " ++ x
-- |'setEnvironment' resets the entire environment to the given list of
-- @(key,value)@ pairs.
setEnvironment ::
[(String,String)] {- ^ @[(key,value)]@ -} ->
IO ()
setEnvironment env = do
clearEnv
forM_ env $ \(key,value) ->
setEnv key value True {-overwrite-}
-- |The 'unsetEnv' function deletes all instances of the variable name
-- from the environment.
unsetEnv :: String {- ^ variable name -} -> IO ()
#if HAVE_UNSETENV
# if !UNSETENV_RETURNS_VOID
unsetEnv name = withFilePath name $ \ s ->
throwErrnoIfMinus1_ "unsetenv" (c_unsetenv s)
-- POSIX.1-2001 compliant unsetenv(3)
foreign import capi unsafe "HsUnix.h unsetenv"
c_unsetenv :: CString -> IO CInt
# else
unsetEnv name = withFilePath name c_unsetenv
-- pre-POSIX unsetenv(3) returning @void@
foreign import capi unsafe "HsUnix.h unsetenv"
c_unsetenv :: CString -> IO ()
# endif
#else
unsetEnv name = putEnv (name ++ "=")
#endif
-- |'putEnv' function takes an argument of the form @name=value@
-- and is equivalent to @setEnv(key,value,True{-overwrite-})@.
putEnv :: String {- ^ "key=value" -} -> IO ()
putEnv keyvalue = do s <- newFilePath keyvalue
-- Do not free `s` after calling putenv.
-- According to SUSv2, the string passed to putenv
-- becomes part of the environment. #7342
throwErrnoIfMinus1_ "putenv" (c_putenv s)
#if !MIN_VERSION_base(4,7,0)
where
newFilePath :: FilePath -> IO CString
newFilePath fp = getFileSystemEncoding >>= \enc -> GHC.newCString enc fp
#endif
foreign import ccall unsafe "putenv"
c_putenv :: CString -> IO CInt
{- |The 'setEnv' function inserts or resets the environment variable name in
the current environment list. If the variable @name@ does not exist in the
list, it is inserted with the given value. If the variable does exist,
the argument @overwrite@ is tested; if @overwrite@ is @False@, the variable is
not reset, otherwise it is reset to the given value.
-}
setEnv ::
String {- ^ variable name -} ->
String {- ^ variable value -} ->
Bool {- ^ overwrite -} ->
IO ()
#ifdef HAVE_SETENV
setEnv key value ovrwrt = do
withFilePath key $ \ keyP ->
withFilePath value $ \ valueP ->
throwErrnoIfMinus1_ "setenv" $
c_setenv keyP valueP (fromIntegral (fromEnum ovrwrt))
foreign import ccall unsafe "setenv"
c_setenv :: CString -> CString -> CInt -> IO CInt
#else
setEnv key value True = putEnv (key++"="++value)
setEnv key value False = do
res <- getEnv key
case res of
Just _ -> return ()
Nothing -> putEnv (key++"="++value)
#endif
-- |The 'clearEnv' function clears the environment of all name-value pairs.
clearEnv :: IO ()
#if HAVE_CLEARENV
clearEnv = void c_clearenv
foreign import ccall unsafe "clearenv"
c_clearenv :: IO Int
#else
-- Fallback to 'environ[0] = NULL'.
clearEnv = do
c_environ <- getCEnviron
unless (c_environ == nullPtr) $
poke c_environ nullPtr
#endif

View File

@@ -0,0 +1,184 @@
{-# LANGUAGE CApiFFI #-}
{-# LANGUAGE Trustworthy #-}
#if __GLASGOW_HASKELL__ >= 709
{-# OPTIONS_GHC -fno-warn-trustworthy-safe #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.Env.ByteString
-- Copyright : (c) The University of Glasgow 2002
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : libraries@haskell.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- POSIX environment support
--
-----------------------------------------------------------------------------
module System.Posix.Env.ByteString (
-- * Environment Variables
getEnv
, getEnvDefault
, getEnvironmentPrim
, getEnvironment
, putEnv
, setEnv
, unsetEnv
-- * Program arguments
, getArgs
) where
#include "HsUnix.h"
import Foreign
import Foreign.C
import Control.Monad ( liftM )
import Data.Maybe ( fromMaybe )
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC
import Data.ByteString (ByteString)
-- |'getEnv' looks up a variable in the environment.
getEnv ::
ByteString {- ^ variable name -} ->
IO (Maybe ByteString) {- ^ variable value -}
getEnv name = do
litstring <- B.useAsCString name c_getenv
if litstring /= nullPtr
then liftM Just $ B.packCString litstring
else return Nothing
-- |'getEnvDefault' is a wrapper around 'getEnv' where the
-- programmer can specify a fallback if the variable is not found
-- in the environment.
getEnvDefault ::
ByteString {- ^ variable name -} ->
ByteString {- ^ fallback value -} ->
IO ByteString {- ^ variable value or fallback value -}
getEnvDefault name fallback = liftM (fromMaybe fallback) (getEnv name)
foreign import ccall unsafe "getenv"
c_getenv :: CString -> IO CString
getEnvironmentPrim :: IO [ByteString]
getEnvironmentPrim = do
c_environ <- getCEnviron
arr <- peekArray0 nullPtr c_environ
mapM B.packCString arr
getCEnviron :: IO (Ptr CString)
#if HAVE__NSGETENVIRON
-- You should not access @char **environ@ directly on Darwin in a bundle/shared library.
-- See #2458 and http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man7/environ.7.html
getCEnviron = nsGetEnviron >>= peek
foreign import ccall unsafe "_NSGetEnviron"
nsGetEnviron :: IO (Ptr (Ptr CString))
#else
getCEnviron = peek c_environ_p
foreign import ccall unsafe "&environ"
c_environ_p :: Ptr (Ptr CString)
#endif
-- |'getEnvironment' retrieves the entire environment as a
-- list of @(key,value)@ pairs.
getEnvironment :: IO [(ByteString,ByteString)] {- ^ @[(key,value)]@ -}
getEnvironment = do
env <- getEnvironmentPrim
return $ map (dropEq.(BC.break ((==) '='))) env
where
dropEq (x,y)
| BC.head y == '=' = (x,B.tail y)
| otherwise = error $ "getEnvironment: insane variable " ++ BC.unpack x
-- |The 'unsetEnv' function deletes all instances of the variable name
-- from the environment.
unsetEnv :: ByteString {- ^ variable name -} -> IO ()
#if HAVE_UNSETENV
# if !UNSETENV_RETURNS_VOID
unsetEnv name = B.useAsCString name $ \ s ->
throwErrnoIfMinus1_ "unsetenv" (c_unsetenv s)
-- POSIX.1-2001 compliant unsetenv(3)
foreign import capi unsafe "HsUnix.h unsetenv"
c_unsetenv :: CString -> IO CInt
# else
unsetEnv name = B.useAsCString name c_unsetenv
-- pre-POSIX unsetenv(3) returning @void@
foreign import capi unsafe "HsUnix.h unsetenv"
c_unsetenv :: CString -> IO ()
# endif
#else
unsetEnv name = putEnv (name ++ "=")
#endif
-- |'putEnv' function takes an argument of the form @name=value@
-- and is equivalent to @setEnv(key,value,True{-overwrite-})@.
putEnv :: ByteString {- ^ "key=value" -} -> IO ()
putEnv keyvalue = B.useAsCString keyvalue $ \s ->
throwErrnoIfMinus1_ "putenv" (c_putenv s)
foreign import ccall unsafe "putenv"
c_putenv :: CString -> IO CInt
{- |The 'setEnv' function inserts or resets the environment variable name in
the current environment list. If the variable @name@ does not exist in the
list, it is inserted with the given value. If the variable does exist,
the argument @overwrite@ is tested; if @overwrite@ is @False@, the variable is
not reset, otherwise it is reset to the given value.
-}
setEnv ::
ByteString {- ^ variable name -} ->
ByteString {- ^ variable value -} ->
Bool {- ^ overwrite -} ->
IO ()
#ifdef HAVE_SETENV
setEnv key value ovrwrt = do
B.useAsCString key $ \ keyP ->
B.useAsCString value $ \ valueP ->
throwErrnoIfMinus1_ "setenv" $
c_setenv keyP valueP (fromIntegral (fromEnum ovrwrt))
foreign import ccall unsafe "setenv"
c_setenv :: CString -> CString -> CInt -> IO CInt
#else
setEnv key value True = putEnv (key++"="++value)
setEnv key value False = do
res <- getEnv key
case res of
Just _ -> return ()
Nothing -> putEnv (key++"="++value)
#endif
-- | Computation 'getArgs' returns a list of the program's command
-- line arguments (not including the program name), as 'ByteString's.
--
-- Unlike 'System.Environment.getArgs', this function does no Unicode
-- decoding of the arguments; you get the exact bytes that were passed
-- to the program by the OS. To interpret the arguments as text, some
-- Unicode decoding should be applied.
--
getArgs :: IO [ByteString]
getArgs =
alloca $ \ p_argc ->
alloca $ \ p_argv -> do
getProgArgv p_argc p_argv
p <- fromIntegral `liftM` peek p_argc
argv <- peek p_argv
peekArray (p - 1) (advancePtr argv 1) >>= mapM B.packCString
foreign import ccall unsafe "getProgArgv"
getProgArgv :: Ptr CInt -> Ptr (Ptr CString) -> IO ()

View File

@@ -0,0 +1,63 @@
{-# LANGUAGE CPP #-}
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.Error
-- Copyright : (c) The University of Glasgow 2002
-- License : BSD-style (see the file libraries/base/LICENSE)
--
-- Maintainer : libraries@haskell.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- POSIX error support
--
-----------------------------------------------------------------------------
module System.Posix.Error (
throwErrnoPath,
throwErrnoPathIf,
throwErrnoPathIf_,
throwErrnoPathIfRetry,
throwErrnoPathIfNull,
throwErrnoPathIfNullRetry,
throwErrnoPathIfMinus1,
throwErrnoPathIfMinus1_,
throwErrnoPathIfMinus1Retry,
throwErrnoPathIfMinus1Retry_
) where
import Foreign hiding (void)
import Foreign.C
import Control.Monad
throwErrnoPathIfMinus1Retry :: (Eq a, Num a)
=> String -> FilePath -> IO a -> IO a
throwErrnoPathIfMinus1Retry loc path f =
throwErrnoPathIfRetry (== -1) loc path f
throwErrnoPathIfMinus1Retry_ :: (Eq a, Num a)
=> String -> FilePath -> IO a -> IO ()
throwErrnoPathIfMinus1Retry_ loc path f =
void $ throwErrnoPathIfRetry (== -1) loc path f
throwErrnoPathIfNullRetry :: String -> FilePath -> IO (Ptr a) -> IO (Ptr a)
throwErrnoPathIfNullRetry loc path f =
throwErrnoPathIfRetry (== nullPtr) loc path f
throwErrnoPathIfRetry :: (a -> Bool) -> String -> FilePath -> IO a -> IO a
throwErrnoPathIfRetry pr loc path f =
do
res <- f
if pr res
then do
err <- getErrno
if err == eINTR
then throwErrnoPathIfRetry pr loc path f
else throwErrnoPath loc path
else return res

104
unix/System/Posix/Fcntl.hsc Normal file
View File

@@ -0,0 +1,104 @@
{-# LANGUAGE CApiFFI #-}
#if __GLASGOW_HASKELL__ >= 709
{-# LANGUAGE Safe #-}
#else
{-# LANGUAGE Trustworthy #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : System.Posix.Fcntl
-- Copyright : (c) The University of Glasgow 2014
-- License : BSD-style (see the file LICENSE)
--
-- Maintainer : libraries@haskell.org
-- Stability : provisional
-- Portability : non-portable (requires POSIX)
--
-- POSIX file control support
--
-- @since 2.7.1.0
-----------------------------------------------------------------------------
#include "HsUnix.h"
module System.Posix.Fcntl (
-- * File allocation
Advice(..), fileAdvise,
fileAllocate,
) where
#if HAVE_POSIX_FALLOCATE || HAVE_POSIX_FADVISE
import Foreign.C
#endif
import System.Posix.Types
#if !HAVE_POSIX_FALLOCATE
import System.IO.Error ( ioeSetLocation )
import GHC.IO.Exception ( unsupportedOperation )
#endif
-- -----------------------------------------------------------------------------
-- File control
-- | Advice parameter for 'fileAdvise' operation.
--
-- For more details, see documentation of @posix_fadvise(2)@.
--
-- @since 2.7.1.0
data Advice
= AdviceNormal
| AdviceRandom
| AdviceSequential
| AdviceWillNeed
| AdviceDontNeed
| AdviceNoReuse
deriving Eq
-- | Performs @posix_fadvise(2)@ operation on file-descriptor.
--
-- If platform does not provide @posix_fadvise(2)@ 'fileAdvise'
-- becomes a no-op.
--
-- (use @#if HAVE_POSIX_FADVISE@ CPP guard to detect availability)
--
-- @since 2.7.1.0
fileAdvise :: Fd -> FileOffset -> FileOffset -> Advice -> IO ()
#if HAVE_POSIX_FADVISE
fileAdvise fd off len adv = do
throwErrnoIfMinus1_ "fileAdvise" (c_posix_fadvise (fromIntegral fd) (fromIntegral off) (fromIntegral len) (packAdvice adv))
foreign import capi safe "fcntl.h posix_fadvise"
c_posix_fadvise :: CInt -> COff -> COff -> CInt -> IO CInt
packAdvice :: Advice -> CInt
packAdvice AdviceNormal = (#const POSIX_FADV_NORMAL)
packAdvice AdviceRandom = (#const POSIX_FADV_RANDOM)
packAdvice AdviceSequential = (#const POSIX_FADV_SEQUENTIAL)
packAdvice AdviceWillNeed = (#const POSIX_FADV_WILLNEED)
packAdvice AdviceDontNeed = (#const POSIX_FADV_DONTNEED)
packAdvice AdviceNoReuse = (#const POSIX_FADV_NOREUSE)
#else
fileAdvise _ _ _ _ = return ()
#endif
-- | Performs @posix_fallocate(2)@ operation on file-descriptor.
--
-- Throws 'IOError' (\"unsupported operation\") if platform does not
-- provide @posix_fallocate(2)@.
--
-- (use @#if HAVE_POSIX_FALLOCATE@ CPP guard to detect availability).
--
-- @since 2.7.1.0
fileAllocate :: Fd -> FileOffset -> FileOffset -> IO ()
#if HAVE_POSIX_FALLOCATE
fileAllocate fd off len = do
throwErrnoIfMinus1_ "fileAllocate" (c_posix_fallocate (fromIntegral fd) (fromIntegral off) (fromIntegral len))
foreign import capi safe "fcntl.h posix_fallocate"
c_posix_fallocate :: CInt -> COff -> COff -> IO CInt
#else
{-# WARNING fileAllocate
"operation will throw 'IOError' \"unsupported operation\" (CPP guard: @#if HAVE_POSIX_FALLOCATE@)" #-}
fileAllocate _ _ _ = ioError (ioeSetLocation unsupportedOperation
"fileAllocate")
#endif

Some files were not shown because too many files have changed in this diff Show More