diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,3 +1,11 @@
+0.10.8.2 Duncan Coutts <duncan@community.haskell.org> Feb 2017
+
+ * Make readFile work for files with no size like /dev/null
+ * Extend the cases in which concat and toStrict can avoid copying data
+ * Fix building with ghc-7.0
+ * Minor documentation improvements
+ * Internal code cleanups
+
 0.10.8.1 Duncan Coutts <duncan@community.haskell.org> May 2016
 
  * Fix Builder output on big-endian architectures
diff --git a/Data/ByteString.hs b/Data/ByteString.hs
--- a/Data/ByteString.hs
+++ b/Data/ByteString.hs
@@ -217,7 +217,11 @@
                                 ,scanl,scanl1,scanr,scanr1
                                 ,readFile,writeFile,appendFile,replicate
                                 ,getContents,getLine,putStr,putStrLn,interact
-                                ,zip,zipWith,unzip,notElem)
+                                ,zip,zipWith,unzip,notElem
+#if !MIN_VERSION_base(4,6,0)
+                                ,catch
+#endif
+                                )
 
 #if MIN_VERSION_base(4,7,0)
 import Data.Bits                (finiteBitSize, shiftL, (.|.), (.&.))
@@ -233,7 +237,7 @@
 import Data.Word                (Word8)
 import Data.Maybe               (isJust)
 
-import Control.Exception        (finally, bracket, assert, throwIO)
+import Control.Exception        (IOException, catch, finally, assert, throwIO)
 import Control.Monad            (when)
 
 import Foreign.C.String         (CString, CStringLen)
@@ -251,7 +255,8 @@
 
 -- hGetBuf and hPutBuf not available in yhc or nhc
 import System.IO                (stdin,stdout,hClose,hFileSize
-                                ,hGetBuf,hPutBuf,openBinaryFile
+                                ,hGetBuf,hPutBuf,hGetBufNonBlocking
+                                ,hPutBufNonBlocking,withBinaryFile
                                 ,IOMode(..))
 import System.IO.Error          (mkIOError, illegalOperationErrorType)
 
@@ -259,9 +264,6 @@
 import Data.Monoid              (Monoid(..))
 #endif
 
-
-import System.IO                (hGetBufNonBlocking, hPutBufNonBlocking)
-
 #if MIN_VERSION_base(4,3,0)
 import System.IO                (hGetBufSome)
 #else
@@ -318,14 +320,14 @@
 --
 --
 
--- | /O(n)/ Convert a '[Word8]' into a 'ByteString'.
+-- | /O(n)/ Convert a @['Word8']@ into a 'ByteString'.
 --
 -- For applications with large numbers of string literals, pack can be a
 -- bottleneck. In such cases, consider using packAddress (GHC only).
 pack :: [Word8] -> ByteString
 pack = packBytes
 
--- | /O(n)/ Converts a 'ByteString' to a '[Word8]'.
+-- | /O(n)/ Converts a 'ByteString' to a @['Word8']@.
 unpack :: ByteString -> [Word8]
 unpack bs = build (unpackFoldr bs)
 {-# INLINE unpack #-}
@@ -353,7 +355,7 @@
 -- ---------------------------------------------------------------------
 -- | /O(1)/ 'length' returns the length of a ByteString as an 'Int'.
 length :: ByteString -> Int
-length (PS _ _ l) = assert (l >= 0) $ l
+length (PS _ _ l) = assert (l >= 0) l
 {-# INLINE length #-}
 
 ------------------------------------------------------------------------
@@ -472,7 +474,7 @@
 -- | The 'transpose' function transposes the rows and columns of its
 -- 'ByteString' argument.
 transpose :: [ByteString] -> [ByteString]
-transpose ps = P.map pack (List.transpose (P.map unpack ps))
+transpose ps = P.map pack . List.transpose . P.map unpack $ ps
 
 -- ---------------------------------------------------------------------
 -- Reducing 'ByteString's
@@ -645,7 +647,7 @@
 mapAccumL f acc (PS fp o len) = unsafeDupablePerformIO $ withForeignPtr fp $ \a -> do
     gp   <- mallocByteString len
     acc' <- withForeignPtr gp $ \p -> mapAccumL_ acc 0 (a `plusPtr` o) p
-    return $! (acc', PS gp 0 len)
+    return (acc', PS gp 0 len)
   where
     mapAccumL_ !s !n !p1 !p2
        | n >= len = return s
@@ -1042,12 +1044,12 @@
 -- 'ByteString's and concatenates the list after interspersing the first
 -- argument between each element of the list.
 intercalate :: ByteString -> [ByteString] -> ByteString
-intercalate s = concat . (List.intersperse s)
+intercalate s = concat . List.intersperse s
 {-# INLINE [1] intercalate #-}
 
 {-# RULES
 "ByteString specialise intercalate c -> intercalateByte" forall c s1 s2 .
-    intercalate (singleton c) (s1 : s2 : []) = intercalateWithByte c s1 s2
+    intercalate (singleton c) [s1, s2] = intercalateWithByte c s1 s2
   #-}
 
 -- | /O(n)/ intercalateWithByte. An efficient way to join to two ByteStrings
@@ -1275,6 +1277,8 @@
 -- | /O(n)/ The 'stripPrefix' function takes two ByteStrings and returns 'Just'
 -- the remainder of the second iff the first is its prefix, and otherwise
 -- 'Nothing'.
+--
+-- @since 0.10.8.0
 stripPrefix :: ByteString -> ByteString -> Maybe ByteString
 stripPrefix bs1@(PS _ _ l1) bs2
    | bs1 `isPrefixOf` bs2 = Just (unsafeDrop l1 bs2)
@@ -1514,7 +1518,7 @@
     let go 256 !_   = return ()
         go i   !ptr = do n <- peekElemOff arr i
                          when (n /= 0) $ memset ptr (fromIntegral i) n >> return ()
-                         go (i + 1) (ptr `plusPtr` (fromIntegral n))
+                         go (i + 1) (ptr `plusPtr` fromIntegral n)
     go 0 p
   where
     -- | Count the number of occurrences of each byte.
@@ -1535,9 +1539,10 @@
 
 -- | /O(n) construction/ Use a @ByteString@ with a function requiring a
 -- null-terminated @CString@.  The @CString@ is a copy and will be freed
--- automatically.
+-- automatically; it must not be stored or used after the
+-- subcomputation finishes.
 useAsCString :: ByteString -> (CString -> IO a) -> IO a
-useAsCString (PS fp o l) action = do
+useAsCString (PS fp o l) action =
  allocaBytes (l+1) $ \buf ->
    withForeignPtr fp $ \p -> do
      memcpy buf (p `plusPtr` o) (fromIntegral l)
@@ -1546,6 +1551,7 @@
 
 -- | /O(n) construction/ Use a @ByteString@ with a function requiring a @CStringLen@.
 -- As for @useAsCString@ this function makes a copy of the original @ByteString@.
+-- It must not be stored or used after the subcomputation finishes.
 useAsCStringLen :: ByteString -> (CStringLen -> IO a) -> IO a
 useAsCStringLen p@(PS _ _ l) f = useAsCString p $ \cstr -> f (cstr,l)
 
@@ -1622,12 +1628,11 @@
       -- if eol == True, then off is the offset of the '\n'
       -- otherwise off == w and the buffer is now empty.
         if off /= w
-            then do if (w == off + 1)
+            then do if w == off + 1
                             then writeIORef haByteBuffer buf{ bufL=0, bufR=0 }
                             else writeIORef haByteBuffer buf{ bufL = off + 1 }
                     mkBigPS new_len (xs:xss)
-            else do
-                 fill h_ buf{ bufL=0, bufR=0 } new_len (xs:xss)
+            else fill h_ buf{ bufL=0, bufR=0 } new_len (xs:xss)
 
   -- find the end-of-line character, if there is one
   findEOL r w raw
@@ -1641,8 +1646,7 @@
 mkPS :: RawBuffer Word8 -> Int -> Int -> IO ByteString
 mkPS buf start end =
  create len $ \p ->
-   withRawBuffer buf $ \pbuf -> do
-   copyBytes p (pbuf `plusPtr` start) len
+   withRawBuffer buf $ \pbuf -> copyBytes p (pbuf `plusPtr` start) len
  where
    len = end - start
 
@@ -1820,24 +1824,30 @@
 --
 readFile :: FilePath -> IO ByteString
 readFile f =
-    bracket (openBinaryFile f ReadMode) hClose $ \h -> do
-      filesz <- hFileSize h
+    withBinaryFile f ReadMode $ \h -> do
+      -- hFileSize fails if file is not regular file (like
+      -- /dev/null). Catch exception and try reading anyway.
+      filesz <- catch (hFileSize h) useZeroIfNotRegularFile
       let readsz = (fromIntegral filesz `max` 0) + 1
       hGetContentsSizeHint h readsz (readsz `max` 255)
       -- Our initial size is one bigger than the file size so that in the
       -- typical case we will read the whole file in one go and not have
       -- to allocate any more chunks. We'll still do the right thing if the
       -- file size is 0 or is changed before we do the read.
+  where
+    useZeroIfNotRegularFile :: IOException -> IO Integer
+    useZeroIfNotRegularFile _ = return 0
 
+modifyFile :: IOMode -> FilePath -> ByteString -> IO ()
+modifyFile mode f txt = withBinaryFile f mode (`hPut` txt)
+
 -- | Write a 'ByteString' to a file.
 writeFile :: FilePath -> ByteString -> IO ()
-writeFile f txt = bracket (openBinaryFile f WriteMode) hClose
-    (\h -> hPut h txt)
+writeFile = modifyFile WriteMode
 
 -- | Append a 'ByteString' to a file.
 appendFile :: FilePath -> ByteString -> IO ()
-appendFile f txt = bracket (openBinaryFile f AppendMode) hClose
-    (\h -> hPut h txt)
+appendFile = modifyFile AppendMode
 
 -- ---------------------------------------------------------------------
 -- Internal utilities
@@ -1876,7 +1886,7 @@
 
 -- Find from the end of the string using predicate
 findFromEndUntil :: (Word8 -> Bool) -> ByteString -> Int
-findFromEndUntil f ps@(PS x s l) =
-    if null ps then 0
-    else if f (unsafeLast ps) then l
-         else findFromEndUntil f (PS x s (l-1))
+findFromEndUntil f ps@(PS x s l)
+  | null ps = 0
+  | f (unsafeLast ps) = l
+  | otherwise = findFromEndUntil f (PS x s (l - 1))
diff --git a/Data/ByteString/Builder/Internal.hs b/Data/ByteString/Builder/Internal.hs
--- a/Data/ByteString/Builder/Internal.hs
+++ b/Data/ByteString/Builder/Internal.hs
@@ -1,4 +1,8 @@
 {-# LANGUAGE ScopedTypeVariables, CPP, BangPatterns, RankNTypes #-}
+#if __GLASGOW_HASKELL__ == 700
+-- This is needed as a workaround for an old bug in GHC 7.0.1 (Trac #4498)
+{-# LANGUAGE MonoPatBinds #-}
+#endif
 #if __GLASGOW_HASKELL__ >= 703
 {-# LANGUAGE Unsafe #-}
 #endif
@@ -92,7 +96,7 @@
   , lazyByteStringCopy
   , lazyByteStringInsert
   , lazyByteStringThreshold
-  
+
   , shortByteString
 
   , maximalCopySize
@@ -201,8 +205,8 @@
 byteStringFromBuffer (Buffer fpbuf (BufferRange op _)) =
     S.PS fpbuf 0 (op `minusPtr` unsafeForeignPtrToPtr fpbuf)
 
---- | Prepend the filled part of a 'Buffer' to a lazy 'L.ByteString'
---- trimming it if necessary.
+-- | Prepend the filled part of a 'Buffer' to a lazy 'L.ByteString'
+-- trimming it if necessary.
 {-# INLINE trimmedChunkFromBuffer #-}
 trimmedChunkFromBuffer :: AllocationStrategy -> Buffer
                        -> L.ByteString -> L.ByteString
diff --git a/Data/ByteString/Builder/Prim.hs b/Data/ByteString/Builder/Prim.hs
--- a/Data/ByteString/Builder/Prim.hs
+++ b/Data/ByteString/Builder/Prim.hs
@@ -1,5 +1,9 @@
 {-# LANGUAGE CPP, BangPatterns, ScopedTypeVariables #-}
 {-# OPTIONS_GHC -fno-warn-unused-imports #-}
+#if __GLASGOW_HASKELL__ == 700
+-- This is needed as a workaround for an old bug in GHC 7.0.1 (Trac #4498)
+{-# LANGUAGE MonoPatBinds #-}
+#endif
 #if __GLASGOW_HASKELL__ >= 701
 {-# LANGUAGE Trustworthy #-}
 #endif
diff --git a/Data/ByteString/Char8.hs b/Data/ByteString/Char8.hs
--- a/Data/ByteString/Char8.hs
+++ b/Data/ByteString/Char8.hs
@@ -1,5 +1,5 @@
 {-# LANGUAGE CPP, BangPatterns #-}
-{-# LANGUAGE MagicHash, UnboxedTuples #-}
+{-# LANGUAGE MagicHash #-}
 {-# OPTIONS_HADDOCK prune #-}
 #if __GLASGOW_HASKELL__ >= 701
 {-# LANGUAGE Trustworthy #-}
@@ -248,6 +248,7 @@
                        ,findSubstring,findSubstrings,breakSubstring,copy,group
 
                        ,getLine, getContents, putStr, interact
+                       ,readFile, writeFile, appendFile
                        ,hGetContents, hGet, hGetSome, hPut, hPutStr
                        ,hGetLine, hGetNonBlocking, hPutNonBlocking
                        ,packCString,packCStringLen
@@ -263,8 +264,7 @@
 #endif
 import qualified Data.List as List (intersperse)
 
-import System.IO    (Handle,stdout,openBinaryFile,hClose,hFileSize,IOMode(..))
-import Control.Exception        (bracket)
+import System.IO    (Handle,stdout)
 import Foreign
 
 
@@ -460,7 +460,7 @@
 --
 -- This implemenation uses @memset(3)@
 replicate :: Int -> Char -> ByteString
-replicate w = B.replicate w . c2w
+replicate n = B.replicate n . c2w
 {-# INLINE replicate #-}
 
 -- | /O(n)/, where /n/ is the length of the result.  The 'unfoldr'
@@ -474,7 +474,7 @@
 --
 -- > unfoldr (\x -> if x <= '9' then Just (x, succ x) else Nothing) '0' == "0123456789"
 unfoldr :: (a -> Maybe (Char, a)) -> a -> ByteString
-unfoldr f x0 = B.unfoldr (fmap k . f) x0
+unfoldr f x = B.unfoldr (fmap k . f) x
     where k (i, j) = (c2w i, j)
 
 -- | /O(n)/ Like 'unfoldr', 'unfoldrN' builds a ByteString from a seed
@@ -486,7 +486,7 @@
 --
 -- > unfoldrN n f s == take n (unfoldr f s)
 unfoldrN :: Int -> (a -> Maybe (Char, a)) -> a -> (ByteString, Maybe a)
-unfoldrN n f w = B.unfoldrN n ((k `fmap`) . f) w
+unfoldrN n f = B.unfoldrN n ((k `fmap`) . f)
     where k (i,j) = (c2w i, j)
 {-# INLINE unfoldrN #-}
 
@@ -877,7 +877,7 @@
 -- after appending a terminating newline to each.
 unlines :: [ByteString] -> ByteString
 unlines [] = empty
-unlines ss = (concat $ List.intersperse nl ss) `append` nl -- half as much space
+unlines ss = concat (List.intersperse nl ss) `append` nl -- half as much space
     where nl = singleton '\n'
 
 -- | 'words' breaks a ByteString up into a list of words, which
@@ -957,7 +957,7 @@
 
           combine _ acc [] ps = (toInteger acc, ps)
           combine d acc ns ps =
-              ((10^d * combine1 1000000000 ns + toInteger acc), ps)
+              (10^d * combine1 1000000000 ns + toInteger acc, ps)
 
           combine1 _ [n] = n
           combine1 b ns  = combine1 (b*b) $ combine2 b ns
@@ -968,30 +968,11 @@
 ------------------------------------------------------------------------
 -- For non-binary text processing:
 
--- | Read an entire file strictly into a 'ByteString'.  This is far more
--- efficient than reading the characters into a 'String' and then using
--- 'pack'.  It also may be more efficient than opening the file and
--- reading it using hGet.
-readFile :: FilePath -> IO ByteString
-readFile f = bracket (openBinaryFile f ReadMode) hClose
-    (\h -> hFileSize h >>= hGet h . fromIntegral)
-
--- | Write a 'ByteString' to a file.
-writeFile :: FilePath -> ByteString -> IO ()
-writeFile f txt = bracket (openBinaryFile f WriteMode) hClose
-    (\h -> hPut h txt)
-
--- | Append a 'ByteString' to a file.
-appendFile :: FilePath -> ByteString -> IO ()
-appendFile f txt = bracket (openBinaryFile f AppendMode) hClose
-    (\h -> hPut h txt)
-
-
 -- | Write a ByteString to a handle, appending a newline byte
 hPutStrLn :: Handle -> ByteString -> IO ()
 hPutStrLn h ps
     | length ps < 1024 = hPut h (ps `B.snoc` 0x0a)
-    | otherwise        = hPut h ps >> hPut h (B.singleton (0x0a)) -- don't copy
+    | otherwise        = hPut h ps >> hPut h (B.singleton 0x0a) -- don't copy
 
 -- | Write a ByteString to stdout, appending a newline byte
 putStrLn :: ByteString -> IO ()
diff --git a/Data/ByteString/Internal.hs b/Data/ByteString/Internal.hs
--- a/Data/ByteString/Internal.hs
+++ b/Data/ByteString/Internal.hs
@@ -34,7 +34,6 @@
         unpackBytes, unpackAppendBytesLazy, unpackAppendBytesStrict,
         unpackChars, unpackAppendCharsLazy, unpackAppendCharsStrict,
         unsafePackAddress,
-        checkedSum,
 
         -- * Low level imperative construction
         create,                 -- :: Int -> (Ptr Word8 -> IO ()) -> IO ByteString
@@ -51,6 +50,7 @@
 
         -- * Utilities
         nullForeignPtr,         -- :: ForeignPtr Word8
+        checkedAdd,             -- :: String -> Int -> Int -> Int
 
         -- * Standard C Functions
         c_strlen,               -- :: CString -> IO CInt
@@ -76,17 +76,19 @@
         inlinePerformIO               -- :: IO a -> a
   ) where
 
-import Prelude hiding (concat)
+import Prelude hiding (concat, null)
 import qualified Data.List as List
 
 import Foreign.ForeignPtr       (ForeignPtr, withForeignPtr)
 import Foreign.Ptr              (Ptr, FunPtr, plusPtr)
 import Foreign.Storable         (Storable(..))
+
 #if MIN_VERSION_base(4,5,0) || __GLASGOW_HASKELL__ >= 703
 import Foreign.C.Types          (CInt(..), CSize(..), CULong(..))
 #else
 import Foreign.C.Types          (CInt, CSize, CULong)
 #endif
+
 import Foreign.C.String         (CString)
 
 #if MIN_VERSION_base(4,9,0)
@@ -95,6 +97,7 @@
 #if !(MIN_VERSION_base(4,8,0))
 import Data.Monoid              (Monoid(..))
 #endif
+
 import Control.DeepSeq          (NFData(rnf))
 
 import Data.String              (IsString(..))
@@ -107,25 +110,22 @@
 import Data.Typeable            (Typeable)
 import Data.Data                (Data(..), mkNoRepType)
 
-import GHC.Base                 (realWorld#,unsafeChr)
+import GHC.Base                 (nullAddr#,realWorld#,unsafeChr)
+
 #if MIN_VERSION_base(4,4,0)
 import GHC.CString              (unpackCString#)
 #else
 import GHC.Base                 (unpackCString#)
 #endif
+
 import GHC.Prim                 (Addr#)
-#if __GLASGOW_HASKELL__ >= 611
-import GHC.IO                   (IO(IO))
-#else
-import GHC.IOBase               (IO(IO),RawBuffer)
-#endif
+
 #if __GLASGOW_HASKELL__ >= 611
-import GHC.IO                   (unsafeDupablePerformIO)
+import GHC.IO                   (IO(IO),unsafeDupablePerformIO)
 #else
-import GHC.IOBase               (unsafeDupablePerformIO)
+import GHC.IOBase               (IO(IO),RawBuffer,unsafeDupablePerformIO)
 #endif
 
-import GHC.Base                 (nullAddr#)
 import GHC.ForeignPtr           (ForeignPtr(ForeignPtr)
                                 ,newForeignPtr_, mallocPlainForeignPtrBytes)
 import GHC.Ptr                  (Ptr(..), castPtr)
@@ -168,7 +168,7 @@
     mconcat = concat
 
 instance NFData ByteString where
-    rnf (PS _ _ _) = ()
+    rnf PS{} = ()
 
 instance Show ByteString where
     showsPrec p ps r = showsPrec p (unpackChars ps) r
@@ -180,7 +180,7 @@
     fromString = packChars
 
 instance Data ByteString where
-  gfoldl f z txt = z packBytes `f` (unpackBytes txt)
+  gfoldl f z txt = z packBytes `f` unpackBytes txt
   toConstr _     = error "Data.ByteString.ByteString.toConstr"
   gunfold _ _    = error "Data.ByteString.ByteString.gunfold"
   dataTypeOf _   = mkNoRepType "Data.ByteString.ByteString"
@@ -306,7 +306,7 @@
 
 unpackAppendBytesStrict :: ByteString -> [Word8] -> [Word8]
 unpackAppendBytesStrict (PS fp off len) xs =
-    accursedUnutterablePerformIO $ withForeignPtr fp $ \base -> do
+    accursedUnutterablePerformIO $ withForeignPtr fp $ \base ->
       loop (base `plusPtr` (off-1)) (base `plusPtr` (off-1+len)) xs
   where
     loop !sentinal !p acc
@@ -343,7 +343,7 @@
                -> Int -- ^ Offset
                -> Int -- ^ Length
                -> ByteString
-fromForeignPtr fp s l = PS fp s l
+fromForeignPtr = PS
 {-# INLINE fromForeignPtr #-}
 
 -- | /O(1)/ Deconstruct a ForeignPtr from a ByteString
@@ -418,15 +418,15 @@
     withForeignPtr fp $ \p -> do
         (off, l', res) <- f p
         if assert (l' <= l) $ l' >= l
-            then return $! (PS fp 0 l, res)
+            then return (PS fp 0 l, res)
             else do ps <- create l' $ \p' ->
                             memcpy p' (p `plusPtr` off) l'
-                    return $! (ps, res)
+                    return (ps, res)
 
 -- | Wrapper of 'mallocForeignPtrBytes' with faster implementation for GHC
 --
 mallocByteString :: Int -> IO (ForeignPtr a)
-mallocByteString l = mallocPlainForeignPtrBytes l
+mallocByteString = mallocPlainForeignPtrBytes
 {-# INLINE mallocByteString #-}
 
 ------------------------------------------------------------------------
@@ -461,24 +461,63 @@
       withForeignPtr fp2 $ \p2 -> memcpy destptr2 (p2 `plusPtr` off2) len2
 
 concat :: [ByteString] -> ByteString
-concat []     = mempty
-concat [bs]   = bs
-concat bss0   = unsafeCreate totalLen $ \ptr -> go bss0 ptr
+concat = \bss0 -> goLen0 bss0 bss0
+    -- The idea here is we first do a pass over the input list to determine:
+    --
+    --  1. is a copy necessary? e.g. @concat []@, @concat [mempty, "hello"]@,
+    --     and @concat ["hello", mempty, mempty]@ can all be handled without
+    --     copying.
+    --  2. if a copy is necessary, how large is the result going to be?
+    --
+    -- If a copy is necessary then we create a buffer of the appropriate size
+    -- and do another pass over the input list, copying the chunks into the
+    -- buffer. Also, since foreign calls aren't entirely free we skip over
+    -- empty chunks while copying.
+    --
+    -- We pass the original [ByteString] (bss0) through as an argument through
+    -- goLen0, goLen1, and goLen since we will need it again in goCopy. Passing
+    -- it as an explicit argument avoids capturing it in these functions'
+    -- closures which would result in unnecessary closure allocation.
   where
-    totalLen = checkedSum "concat" [ len | (PS _ _ len) <- bss0 ]
-    go []                  !_   = return ()
-    go (PS fp off len:bss) !ptr = do
+    -- It's still possible that the result is empty
+    goLen0 _    []                     = mempty
+    goLen0 bss0 (PS _ _ 0     :bss)    = goLen0 bss0 bss
+    goLen0 bss0 (bs           :bss)    = goLen1 bss0 bs bss
+
+    -- It's still possible that the result is a single chunk
+    goLen1 _    bs []                  = bs
+    goLen1 bss0 bs (PS _ _ 0  :bss)    = goLen1 bss0 bs bss
+    goLen1 bss0 bs (PS _ _ len:bss)    = goLen bss0 (checkedAdd "concat" len' len) bss
+      where PS _ _ len' = bs
+
+    -- General case, just find the total length we'll need
+    goLen bss0 !total (PS _ _ len:bss) = goLen bss0 total' bss
+      where total' = checkedAdd "concat" total len
+    goLen bss0 total [] =
+      unsafeCreate total $ \ptr -> goCopy bss0 ptr
+
+    -- Copy the data
+    goCopy []                  !_   = return ()
+    goCopy (PS _  _   0  :bss) !ptr = goCopy bss ptr
+    goCopy (PS fp off len:bss) !ptr = do
       withForeignPtr fp $ \p -> memcpy ptr (p `plusPtr` off) len
-      go bss (ptr `plusPtr` len)
+      goCopy bss (ptr `plusPtr` len)
+{-# NOINLINE concat #-}
 
--- | Add a list of non-negative numbers.  Errors out on overflow.
-checkedSum :: String -> [Int] -> Int
-checkedSum fun = go 0
-  where go !a (x:xs)
-            | ax >= 0   = go ax xs
-            | otherwise = overflowError fun
-          where ax = a + x
-        go a  _         = a
+{-# RULES
+"ByteString concat [] -> mempty"
+   concat [] = mempty
+"ByteString concat [bs] -> bs" forall x.
+   concat [x] = x
+ #-}
+
+-- | Add two non-negative numbers. Errors out on overflow.
+checkedAdd :: String -> Int -> Int -> Int
+checkedAdd fun x y
+  | r >= 0    = r
+  | otherwise = overflowError fun
+  where r = x + y
+{-# INLINE checkedAdd #-}
 
 ------------------------------------------------------------------------
 
diff --git a/Data/ByteString/Lazy.hs b/Data/ByteString/Lazy.hs
--- a/Data/ByteString/Lazy.hs
+++ b/Data/ByteString/Lazy.hs
@@ -229,14 +229,12 @@
 import Data.Monoid              (Monoid(..))
 #endif
 import Control.Monad            (mplus)
-
 import Data.Word                (Word8)
 import Data.Int                 (Int64)
-import System.IO                (Handle,stdin,stdout,openBinaryFile,IOMode(..)
+import System.IO                (Handle,openBinaryFile,stdin,stdout,withBinaryFile,IOMode(..)
                                 ,hClose)
 import System.IO.Error          (mkIOError, illegalOperationErrorType)
 import System.IO.Unsafe
-import Control.Exception        (bracket)
 
 import Foreign.ForeignPtr       (withForeignPtr)
 import Foreign.Ptr
@@ -284,17 +282,40 @@
 -- avoid converting back and forth between strict and lazy bytestrings.
 --
 toStrict :: ByteString -> S.ByteString
-toStrict Empty           = S.empty
-toStrict (Chunk c Empty) = c
-toStrict cs0 = S.unsafeCreate totalLen $ \ptr -> go cs0 ptr
+toStrict = \cs -> goLen0 cs cs
+    -- We pass the original [ByteString] (bss0) through as an argument through
+    -- goLen0, goLen1, and goLen since we will need it again in goCopy. Passing
+    -- it as an explicit argument avoids capturing it in these functions'
+    -- closures which would result in unnecessary closure allocation.
   where
-    totalLen = S.checkedSum "Lazy.toStrict" . L.map S.length . toChunks $ cs0
+    -- It's still possible that the result is empty
+    goLen0 _   Empty                   = S.empty
+    goLen0 cs0 (Chunk c cs) | S.null c = goLen0 cs0 cs
+    goLen0 cs0 (Chunk c cs)            = goLen1 cs0 c cs
 
-    go Empty                        !_       = return ()
-    go (Chunk (S.PS fp off len) cs) !destptr =
+    -- It's still possible that the result is a single chunk
+    goLen1 _   bs Empty                = bs
+    goLen1 cs0 bs (Chunk c cs)
+      | S.null c                   = goLen1 cs0 bs cs
+      | otherwise                  =
+        goLen cs0 (S.checkedAdd "Lazy.concat" (S.length bs) (S.length c)) cs
+
+    -- General case, just find the total length we'll need
+    goLen cs0 !total (Chunk c cs)      = goLen cs0 total' cs
+      where
+        total' = S.checkedAdd "Lazy.concat" total (S.length c)
+    goLen cs0 total Empty =
+      S.unsafeCreate total $ \ptr -> goCopy cs0 ptr
+
+    -- Copy the data
+    goCopy Empty                        !_   = return ()
+    goCopy (Chunk (S.PS _  _   0  ) cs) !ptr = goCopy cs ptr
+    goCopy (Chunk (S.PS fp off len) cs) !ptr = do
       withForeignPtr fp $ \p -> do
-        S.memcpy destptr (p `plusPtr` off) len
-        go cs (destptr `plusPtr` len)
+        S.memcpy ptr (p `plusPtr` off) len
+        goCopy cs (ptr `plusPtr` len)
+-- See the comment on Data.ByteString.Internal.concat for some background on
+-- this implementation.
 
 ------------------------------------------------------------------------
 
@@ -478,7 +499,7 @@
 -- (typically the right-identity of the operator), and a ByteString,
 -- reduces the ByteString using the binary operator, from right to left.
 foldr :: (Word8 -> a -> a) -> a -> ByteString -> a
-foldr k z cs = foldrChunks (flip (S.foldr k)) z cs
+foldr k z = foldrChunks (flip (S.foldr k)) z
 {-# INLINE foldr #-}
 
 -- | 'foldl1' is a variant of 'foldl' that has no starting value
@@ -555,7 +576,7 @@
 -- passing an accumulating parameter from left to right, and returning a
 -- final value of this accumulator together with the new ByteString.
 mapAccumL :: (acc -> Word8 -> (acc, Word8)) -> acc -> ByteString -> (acc, ByteString)
-mapAccumL f s0 cs0 = go s0 cs0
+mapAccumL f s0 = go s0
   where
     go s Empty        = (s, Empty)
     go s (Chunk c cs) = (s'', Chunk c' cs')
@@ -567,7 +588,7 @@
 -- passing an accumulating parameter from right to left, and returning a
 -- final value of this accumulator together with the new ByteString.
 mapAccumR :: (acc -> Word8 -> (acc, Word8)) -> acc -> ByteString -> (acc, ByteString)
-mapAccumR f s0 cs0 = go s0 cs0
+mapAccumR f s0 = go s0
   where
     go s Empty        = (s, Empty)
     go s (Chunk c cs) = (s'', Chunk c' cs')
@@ -638,13 +659,13 @@
 -- prepending to the ByteString and @b@ is used as the next element in a
 -- recursive call.
 unfoldr :: (a -> Maybe (Word8, a)) -> a -> ByteString
-unfoldr f s0 = unfoldChunk 32 s0
-  where unfoldChunk n s =
-          case S.unfoldrN n f s of
+unfoldr f z = unfoldChunk 32 z
+  where unfoldChunk n x =
+          case S.unfoldrN n f x of
             (c, Nothing)
               | S.null c  -> Empty
               | otherwise -> Chunk c Empty
-            (c, Just s')  -> Chunk c (unfoldChunk (n*2) s')
+            (c, Just x')  -> Chunk c (unfoldChunk (n*2) x')
 
 -- ---------------------------------------------------------------------
 -- Substrings
@@ -781,7 +802,6 @@
         comb acc (s:[]) Empty        = revChunks (s:acc) : []
         comb acc (s:[]) (Chunk c cs) = comb (s:acc) (S.splitWith p c) cs
         comb acc (s:ss) cs           = revChunks (s:acc) : comb [] ss cs
-
 {-# INLINE splitWith #-}
 
 -- | /O(n)/ Break a 'ByteString' into pieces separated by the byte
@@ -807,7 +827,7 @@
   where comb :: [P.ByteString] -> [P.ByteString] -> ByteString -> [ByteString]
         comb acc (s:[]) Empty        = revChunks (s:acc) : []
         comb acc (s:[]) (Chunk c cs) = comb (s:acc) (S.split w c) cs
-        comb acc (s:ss) cs           = revChunks (s:acc) : comb [] ss cs
+        comb acc (s:ss) cs        = revChunks (s:acc) : comb [] ss cs
 {-# INLINE split #-}
 
 -- | The 'group' function takes a ByteString and returns a list of
@@ -859,7 +879,7 @@
 -- 'ByteString's and concatenates the list after interspersing the first
 -- argument between each element of the list.
 intercalate :: ByteString -> [ByteString] -> ByteString
-intercalate s = concat . (L.intersperse s)
+intercalate s = concat . L.intersperse s
 
 -- ---------------------------------------------------------------------
 -- Indexing ByteStrings
@@ -893,7 +913,8 @@
 --
 -- > elemIndexEnd c xs ==
 -- > (-) (length xs - 1) `fmap` elemIndex c (reverse xs)
-
+--
+-- @since 0.10.6.0
 elemIndexEnd :: Word8 -> ByteString -> Maybe Int64
 elemIndexEnd w = elemIndexEnd' 0
   where
@@ -1040,6 +1061,8 @@
 -- | /O(n)/ The 'stripPrefix' function takes two ByteStrings and returns 'Just'
 -- the remainder of the second iff the first is its prefix, and otherwise
 -- 'Nothing'.
+--
+-- @since 0.10.8.0
 stripPrefix :: ByteString -> ByteString -> Maybe ByteString
 stripPrefix Empty bs  = Just bs
 stripPrefix _ Empty  = Nothing
@@ -1111,7 +1134,7 @@
 inits :: ByteString -> [ByteString]
 inits = (Empty :) . inits'
   where inits' Empty        = []
-        inits' (Chunk c cs) = L.map (\c' -> Chunk c' Empty) (L.tail (S.inits c))
+        inits' (Chunk c cs) = L.map (`Chunk` Empty) (L.tail (S.inits c))
                            ++ L.map (Chunk c) (inits' cs)
 
 -- | /O(n)/ Return all final segments of the given 'ByteString', longest first.
@@ -1166,7 +1189,7 @@
     loop = do
         c <- S.hGetSome h k -- only blocks if there is no data available
         if S.null c
-          then do hClose h >> return Empty
+          then hClose h >> return Empty
           else do cs <- lazyRead
                   return (Chunk c cs)
 
@@ -1244,24 +1267,27 @@
 readFile :: FilePath -> IO ByteString
 readFile f = openBinaryFile f ReadMode >>= hGetContents
 
+modifyFile :: IOMode -> FilePath -> ByteString -> IO ()
+modifyFile mode f txt = withBinaryFile f mode (`hPut` txt)
+
 -- | Write a 'ByteString' to a file.
 --
 writeFile :: FilePath -> ByteString -> IO ()
-writeFile f txt = bracket (openBinaryFile f WriteMode) hClose
-    (\hdl -> hPut hdl txt)
+writeFile = modifyFile WriteMode
 
 -- | Append a 'ByteString' to a file.
 --
 appendFile :: FilePath -> ByteString -> IO ()
-appendFile f txt = bracket (openBinaryFile f AppendMode) hClose
-    (\hdl -> hPut hdl txt)
+appendFile = modifyFile AppendMode
 
 -- | getContents. Equivalent to hGetContents stdin. Will read /lazily/
 --
 getContents :: IO ByteString
 getContents = hGetContents stdin
 
--- | Outputs a 'ByteString' to the specified 'Handle'.
+-- | Outputs a 'ByteString' to the specified 'Handle'. The chunks will be
+-- written one at a time. Other threads might write to the 'Handle' between the
+-- writes, and hence 'hPut' alone might not be suitable for concurrent writes.
 --
 hPut :: Handle -> ByteString -> IO ()
 hPut h cs = foldrChunks (\c rest -> S.hPut h c >> rest) (return ()) cs
diff --git a/Data/ByteString/Lazy/Char8.hs b/Data/ByteString/Lazy/Char8.hs
--- a/Data/ByteString/Lazy/Char8.hs
+++ b/Data/ByteString/Lazy/Char8.hs
@@ -204,7 +204,8 @@
         ,stripPrefix,stripSuffix
         ,hGetContents, hGet, hPut, getContents
         ,hGetNonBlocking, hPutNonBlocking
-        ,putStr, hPutStr, interact)
+        ,putStr, hPutStr, interact
+        ,readFile,writeFile,appendFile)
 
 -- Functions we need to wrap.
 import qualified Data.ByteString.Lazy as L
@@ -225,8 +226,7 @@
         ,readFile,writeFile,appendFile,replicate,getContents,getLine,putStr,putStrLn
         ,zip,zipWith,unzip,notElem,repeat,iterate,interact,cycle)
 
-import System.IO            (Handle,stdout,hClose,openBinaryFile,IOMode(..))
-import Control.Exception    (bracket)
+import System.IO            (Handle, stdout)
 
 ------------------------------------------------------------------------
 
@@ -735,7 +735,7 @@
 -- after appending a terminating newline to each.
 unlines :: [ByteString] -> ByteString
 unlines [] = empty
-unlines ss = (concat $ List.intersperse nl ss) `append` nl -- half as much space
+unlines ss = concat (List.intersperse nl ss) `append` nl -- half as much space
     where nl = singleton '\n'
 
 -- | 'words' breaks a ByteString up into a list of words, which
@@ -781,10 +781,10 @@
 
           {-# INLINE end #-}
           end _   0 _ _  _ = Nothing
-          end neg _ n c cs = e `seq` e
+          end neg _ n c cs = e
                 where n' = if neg then negate n else n
                       c' = chunk c cs
-                      e  = n' `seq` c' `seq` Just $! (n',c')
+                      e  = n' `seq` c' `seq` Just (n',c')
          --                  in n' `seq` c' `seq` JustS n' c'
 
 
@@ -840,30 +840,16 @@
           end n c cs = let c' = chunk c cs
                         in c' `seq` (n, c')
 
--- | Read an entire file /lazily/ into a 'ByteString'.
-readFile :: FilePath -> IO ByteString
-readFile f = openBinaryFile f ReadMode >>= hGetContents
 
--- | Write a 'ByteString' to a file.
-writeFile :: FilePath -> ByteString -> IO ()
-writeFile f txt = bracket (openBinaryFile f WriteMode) hClose
-    (\hdl -> hPut hdl txt)
-
--- | Append a 'ByteString' to a file.
-appendFile :: FilePath -> ByteString -> IO ()
-appendFile f txt = bracket (openBinaryFile f AppendMode) hClose
-    (\hdl -> hPut hdl txt)
-
-
 -- | Write a ByteString to a handle, appending a newline byte
 --
 hPutStrLn :: Handle -> ByteString -> IO ()
 hPutStrLn h ps = hPut h ps >> hPut h (L.singleton 0x0a)
 
 -- | Write a ByteString to stdout, appending a newline byte
+--
 putStrLn :: ByteString -> IO ()
 putStrLn = hPutStrLn stdout
-
 
 -- ---------------------------------------------------------------------
 -- Internal utilities
diff --git a/Data/ByteString/Lazy/Internal.hs b/Data/ByteString/Lazy/Internal.hs
--- a/Data/ByteString/Lazy/Internal.hs
+++ b/Data/ByteString/Lazy/Internal.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE CPP, ForeignFunctionInterface, BangPatterns #-}
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveDataTypeable #-}
 #if __GLASGOW_HASKELL__ >= 703
 {-# LANGUAGE Unsafe #-}
@@ -73,6 +73,7 @@
 --
 data ByteString = Empty | Chunk {-# UNPACK #-} !S.ByteString ByteString
     deriving (Typeable)
+-- See 'invariant' function later in this module for internal invariants.
 
 instance Eq  ByteString where
     (==)    = eq
@@ -125,8 +126,7 @@
       (bs, cs') -> Chunk bs (packChunks (min (n * 2) smallChunkSize) cs')
 
 packChars :: [Char] -> ByteString
-packChars cs0 =
-    packChunks 32 cs0
+packChars cs0 = packChunks 32 cs0
   where
     packChunks n cs = case S.packUptoLenChars n cs of
       (bs, [])  -> chunk bs Empty
@@ -218,9 +218,9 @@
 eq _     Empty = False
 eq (Chunk a as) (Chunk b bs) =
   case compare (S.length a) (S.length b) of
-    LT -> a == (S.take (S.length a) b) && eq as (Chunk (S.drop (S.length a) b) bs)
-    EQ -> a == b                       && eq as bs
-    GT -> (S.take (S.length b) a) == b && eq (Chunk (S.drop (S.length b) a) as) bs
+    LT -> a == S.take (S.length a) b && eq as (Chunk (S.drop (S.length a) b) bs)
+    EQ -> a == b                     && eq as bs
+    GT -> S.take (S.length b) a == b && eq (Chunk (S.drop (S.length b) a) as) bs
 
 cmp :: ByteString -> ByteString -> Ordering
 cmp Empty Empty = EQ
diff --git a/Data/ByteString/Short/Internal.hs b/Data/ByteString/Short/Internal.hs
--- a/Data/ByteString/Short/Internal.hs
+++ b/Data/ByteString/Short/Internal.hs
@@ -149,7 +149,7 @@
     mconcat = concat
 
 instance NFData ShortByteString where
-    rnf (SBS {}) = ()
+    rnf SBS{} = ()
 
 instance Show ShortByteString where
     showsPrec p ps r = showsPrec p (unpackChars ps) r
@@ -161,7 +161,7 @@
     fromString = packChars
 
 instance Data ShortByteString where
-  gfoldl f z txt = z packBytes `f` (unpackBytes txt)
+  gfoldl f z txt = z packBytes `f` unpackBytes txt
   toConstr _     = error "Data.ByteString.Short.ShortByteString.toConstr"
   gunfold _ _    = error "Data.ByteString.Short.ShortByteString.gunfold"
   dataTypeOf _   = mkNoRepType "Data.ByteString.Short.ShortByteString"
@@ -320,8 +320,7 @@
 -- (5 words per list element, 8 bytes per word, 100 elements = 4000 bytes)
 
 unpackAppendCharsLazy :: ShortByteString -> [Char] -> [Char]
-unpackAppendCharsLazy sbs cs0 =
-    go 0 (length sbs) cs0
+unpackAppendCharsLazy sbs cs0 = go 0 (length sbs) cs0
   where
     sz = 100
 
@@ -331,8 +330,7 @@
                       where remainder = go (off+sz) (len-sz) cs
 
 unpackAppendBytesLazy :: ShortByteString -> [Word8] -> [Word8]
-unpackAppendBytesLazy sbs ws0 =
-    go 0 (length sbs) ws0
+unpackAppendBytesLazy sbs ws0 = go 0 (length sbs) ws0
   where
     sz = 100
 
@@ -347,8 +345,7 @@
 -- buffer and loops down until we hit the sentinal:
 
 unpackAppendCharsStrict :: ShortByteString -> Int -> Int -> [Char] -> [Char]
-unpackAppendCharsStrict !sbs off len cs =
-    go (off-1) (off-1 + len) cs
+unpackAppendCharsStrict !sbs off len cs = go (off-1) (off-1 + len) cs
   where
     go !sentinal !i !acc
       | i == sentinal = acc
@@ -356,8 +353,7 @@
                         in go sentinal (i-1) (c:acc)
 
 unpackAppendBytesStrict :: ShortByteString -> Int -> Int -> [Word8] -> [Word8]
-unpackAppendBytesStrict !sbs off len ws =
-    go (off-1) (off-1 + len) ws
+unpackAppendBytesStrict !sbs off len ws = go (off-1) (off-1 + len) ws
   where
     go !sentinal !i !acc
       | i == sentinal = acc
diff --git a/Data/ByteString/Unsafe.hs b/Data/ByteString/Unsafe.hs
--- a/Data/ByteString/Unsafe.hs
+++ b/Data/ByteString/Unsafe.hs
@@ -260,6 +260,10 @@
 -- to guarantee that the @ByteString@ is indeed null terminated. If in
 -- doubt, use @useAsCString@.
 --
+-- * The memory may freed at any point after the subcomputation
+-- terminates, so the pointer to the storage must *not* be used
+-- after this.
+--
 unsafeUseAsCString :: ByteString -> (CString -> IO a) -> IO a
 unsafeUseAsCString (PS ps s _) ac = withForeignPtr ps $ \p -> ac (castPtr p `plusPtr` s)
 
diff --git a/bytestring.cabal b/bytestring.cabal
--- a/bytestring.cabal
+++ b/bytestring.cabal
@@ -1,5 +1,5 @@
 Name:                bytestring
-Version:             0.10.8.1
+Version:             0.10.8.2
 Synopsis:            Fast, compact, strict and lazy byte strings with a list interface
 Description:
     An efficient compact, immutable byte string type (both strict and lazy)
@@ -200,7 +200,7 @@
                     deepseq,
                     QuickCheck                 >= 2.4,
                     byteorder                  == 1.0.*,
-                    dlist                      >= 0.5 && < 0.8,
+                    dlist                      >= 0.5 && < 0.9,
                     directory,
                     mtl                        >= 2.0 && < 2.3,
                     HUnit,
diff --git a/tests/Properties.hs b/tests/Properties.hs
--- a/tests/Properties.hs
+++ b/tests/Properties.hs
@@ -2313,7 +2313,7 @@
     , testProperty "reverse"            prop_reverse
     , testProperty "reverse1"           prop_reverse1
     , testProperty "reverse2"           prop_reverse2
-    --, testProperty "transpose"          prop_transpose
+--  , testProperty "transpose"          prop_transpose
     , testProperty "foldl"              prop_foldl
     , testProperty "foldl/reverse"      prop_foldl_1
     , testProperty "foldr"              prop_foldr
@@ -2331,7 +2331,7 @@
     , testProperty "all"                prop_all
     , testProperty "maximum"            prop_maximum
     , testProperty "minimum"            prop_minimum
-    , testProperty "replicate 1"        prop_replicate1
+--  , testProperty "replicate 1"        prop_replicate1
     , testProperty "replicate 2"        prop_replicate2
     , testProperty "take"               prop_take1
     , testProperty "drop"               prop_drop1
