Blame Codec/Archive/Tar/Write.hs

Packit 8cecbd
-----------------------------------------------------------------------------
Packit 8cecbd
-- |
Packit 8cecbd
-- Module      :  Codec.Archive.Tar.Write
Packit 8cecbd
-- Copyright   :  (c) 2007 Bjorn Bringert,
Packit 8cecbd
--                    2008 Andrea Vezzosi,
Packit 8cecbd
--                    2008-2009 Duncan Coutts
Packit 8cecbd
-- License     :  BSD3
Packit 8cecbd
--
Packit 8cecbd
-- Maintainer  :  duncan@community.haskell.org
Packit 8cecbd
-- Portability :  portable
Packit 8cecbd
--
Packit 8cecbd
-----------------------------------------------------------------------------
Packit 8cecbd
module Codec.Archive.Tar.Write (write) where
Packit 8cecbd
Packit 8cecbd
import Codec.Archive.Tar.Types
Packit 8cecbd
Packit 8cecbd
import Data.Char     (ord)
Packit 8cecbd
import Data.List     (foldl')
Packit 8cecbd
import Data.Monoid   (mempty)
Packit 8cecbd
import Numeric       (showOct)
Packit 8cecbd
Packit 8cecbd
import qualified Data.ByteString             as BS
Packit 8cecbd
import qualified Data.ByteString.Char8       as BS.Char8
Packit 8cecbd
import qualified Data.ByteString.Lazy        as LBS
Packit 8cecbd
import qualified Data.ByteString.Lazy.Char8  as LBS.Char8
Packit 8cecbd
Packit 8cecbd
Packit 8cecbd
-- | Create the external representation of a tar archive by serialising a list
Packit 8cecbd
-- of tar entries.
Packit 8cecbd
--
Packit 8cecbd
-- * The conversion is done lazily.
Packit 8cecbd
--
Packit 8cecbd
write :: [Entry] -> LBS.ByteString
Packit 8cecbd
write es = LBS.concat $ map putEntry es ++ [LBS.replicate (512*2) 0]
Packit 8cecbd
Packit 8cecbd
putEntry :: Entry -> LBS.ByteString
Packit 8cecbd
putEntry entry = case entryContent entry of
Packit 8cecbd
  NormalFile       content size -> LBS.concat [ header, content, padding size ]
Packit 8cecbd
  OtherEntryType _ content size -> LBS.concat [ header, content, padding size ]
Packit 8cecbd
  _                             -> header
Packit 8cecbd
  where
Packit 8cecbd
    header       = putHeader entry
Packit 8cecbd
    padding size = LBS.replicate paddingSize 0
Packit 8cecbd
      where paddingSize = fromIntegral (negate size `mod` 512)
Packit 8cecbd
Packit 8cecbd
putHeader :: Entry -> LBS.ByteString
Packit 8cecbd
putHeader entry =
Packit 8cecbd
     LBS.Char8.pack
Packit 8cecbd
   $ take 148 block
Packit 8cecbd
  ++ putOct 7 checksum
Packit 8cecbd
  ++ ' ' : drop 156 block
Packit 8cecbd
--  ++ putOct 8 checksum
Packit 8cecbd
--  ++ drop 156 block
Packit 8cecbd
  where
Packit 8cecbd
    block    = putHeaderNoChkSum entry
Packit 8cecbd
    checksum = foldl' (\x y -> x + ord y) 0 block
Packit 8cecbd
Packit 8cecbd
putHeaderNoChkSum :: Entry -> String
Packit 8cecbd
putHeaderNoChkSum Entry {
Packit 8cecbd
    entryTarPath     = TarPath name prefix,
Packit 8cecbd
    entryContent     = content,
Packit 8cecbd
    entryPermissions = permissions,
Packit 8cecbd
    entryOwnership   = ownership,
Packit 8cecbd
    entryTime        = modTime,
Packit 8cecbd
    entryFormat      = format
Packit 8cecbd
  } =
Packit 8cecbd
Packit 8cecbd
  concat
Packit 8cecbd
    [ putBString 100 $ name
Packit 8cecbd
    , putOct       8 $ permissions
Packit 8cecbd
    , putOct       8 $ ownerId ownership
Packit 8cecbd
    , putOct       8 $ groupId ownership
Packit 8cecbd
    , putOct      12 $ contentSize
Packit 8cecbd
    , putOct      12 $ modTime
Packit 8cecbd
    , fill         8 $ ' ' -- dummy checksum
Packit 8cecbd
    , putChar8       $ typeCode
Packit 8cecbd
    , putBString 100 $ linkTarget
Packit 8cecbd
    ] ++
Packit 8cecbd
  case format of
Packit 8cecbd
  V7Format    ->
Packit 8cecbd
      fill 255 '\NUL'
Packit 8cecbd
  UstarFormat -> concat
Packit 8cecbd
    [ putBString   8 $ ustarMagic
Packit 8cecbd
    , putString   32 $ ownerName ownership
Packit 8cecbd
    , putString   32 $ groupName ownership
Packit 8cecbd
    , putOct       8 $ deviceMajor
Packit 8cecbd
    , putOct       8 $ deviceMinor
Packit 8cecbd
    , putBString 155 $ prefix
Packit 8cecbd
    , fill        12 $ '\NUL'
Packit 8cecbd
    ]
Packit 8cecbd
  GnuFormat -> concat
Packit 8cecbd
    [ putBString   8 $ gnuMagic
Packit 8cecbd
    , putString   32 $ ownerName ownership
Packit 8cecbd
    , putString   32 $ groupName ownership
Packit 8cecbd
    , putGnuDev    8 $ deviceMajor
Packit 8cecbd
    , putGnuDev    8 $ deviceMinor
Packit 8cecbd
    , putBString 155 $ prefix
Packit 8cecbd
    , fill        12 $ '\NUL'
Packit 8cecbd
    ]
Packit 8cecbd
  where
Packit 8cecbd
    (typeCode, contentSize, linkTarget,
Packit 8cecbd
     deviceMajor, deviceMinor) = case content of
Packit 8cecbd
       NormalFile      _ size            -> ('0' , size, mempty, 0,     0)
Packit 8cecbd
       Directory                         -> ('5' , 0,    mempty, 0,     0)
Packit 8cecbd
       SymbolicLink    (LinkTarget link) -> ('2' , 0,    link,   0,     0)
Packit 8cecbd
       HardLink        (LinkTarget link) -> ('1' , 0,    link,   0,     0)
Packit 8cecbd
       CharacterDevice major minor       -> ('3' , 0,    mempty, major, minor)
Packit 8cecbd
       BlockDevice     major minor       -> ('4' , 0,    mempty, major, minor)
Packit 8cecbd
       NamedPipe                         -> ('6' , 0,    mempty, 0,     0)
Packit 8cecbd
       OtherEntryType  code _ size       -> (code, size, mempty, 0,     0)
Packit 8cecbd
Packit 8cecbd
    putGnuDev w n = case content of
Packit 8cecbd
      CharacterDevice _ _ -> putOct w n
Packit 8cecbd
      BlockDevice     _ _ -> putOct w n
Packit 8cecbd
      _                   -> replicate w '\NUL'
Packit 8cecbd
Packit 8cecbd
ustarMagic, gnuMagic :: BS.ByteString
Packit 8cecbd
ustarMagic = BS.Char8.pack "ustar\NUL00"
Packit 8cecbd
gnuMagic   = BS.Char8.pack "ustar  \NUL"
Packit 8cecbd
Packit 8cecbd
-- * TAR format primitive output
Packit 8cecbd
Packit 8cecbd
type FieldWidth = Int
Packit 8cecbd
Packit 8cecbd
putBString :: FieldWidth -> BS.ByteString -> String
Packit 8cecbd
putBString n s = BS.Char8.unpack (BS.take n s) ++ fill (n - BS.length s) '\NUL'
Packit 8cecbd
Packit 8cecbd
putString :: FieldWidth -> String -> String
Packit 8cecbd
putString n s = take n s ++ fill (n - length s) '\NUL'
Packit 8cecbd
Packit 8cecbd
--TODO: check integer widths, eg for large file sizes
Packit 8cecbd
putOct :: (Integral a, Show a) => FieldWidth -> a -> String
Packit 8cecbd
putOct n x =
Packit 8cecbd
  let octStr = take (n-1) $ showOct x ""
Packit 8cecbd
   in fill (n - length octStr - 1) '0'
Packit 8cecbd
   ++ octStr
Packit 8cecbd
   ++ putChar8 '\NUL'
Packit 8cecbd
Packit 8cecbd
putChar8 :: Char -> String
Packit 8cecbd
putChar8 c = [c]
Packit 8cecbd
Packit 8cecbd
fill :: FieldWidth -> Char -> String
Packit 8cecbd
fill n c = replicate n c