File: Write.hs

package info (click to toggle)
haskell-tar 0.6.4.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 336 kB
  • sloc: haskell: 3,237; makefile: 4
file content (170 lines) | stat: -rw-r--r-- 6,141 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
{-# LANGUAGE PackageImports #-}
{-# OPTIONS_HADDOCK hide #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Codec.Archive.Tar.Write
-- Copyright   :  (c) 2007 Bjorn Bringert,
--                    2008 Andrea Vezzosi,
--                    2008-2009 Duncan Coutts
-- License     :  BSD3
--
-- Maintainer  :  duncan@community.haskell.org
-- Portability :  portable
--
-----------------------------------------------------------------------------
module Codec.Archive.Tar.Write (write) where

import Codec.Archive.Tar.PackAscii
import Codec.Archive.Tar.Types

import Data.Bits
import Data.Char     (chr,ord)
import Data.Int
import Data.List     (foldl')
import Data.Monoid   (mempty)
import Numeric       (showOct)

import qualified Data.ByteString             as BS
import qualified Data.ByteString.Char8       as BS.Char8
import qualified Data.ByteString.Lazy        as LBS
import qualified Data.ByteString.Lazy.Char8  as LBS.Char8
import "os-string" System.OsString.Posix (PosixString)
import qualified "os-string" System.OsString.Posix as PS

-- | Create the external representation of a tar archive by serialising a list
-- of tar entries.
--
-- * The conversion is done lazily.
--
write :: [Entry] -> LBS.ByteString
write es = LBS.concat $ map putEntry es ++ [LBS.replicate (512*2) 0]

putEntry :: Entry -> LBS.ByteString
putEntry entry = case entryContent entry of
  NormalFile       content size
    -- size field is 12 bytes long, so in octal format (see 'putOct')
    -- it can hold numbers up to 8Gb
    | size >= 1 `shiftL` (3 * (12 -1))
    , entryFormat entry == V7Format
    -> error "putEntry: support for files over 8Gb is a Ustar extension"
    | otherwise -> LBS.concat [ header, content, padding size ]
  OtherEntryType 'K' _ _
    | entryFormat entry /= GnuFormat -> error "putEntry: long symlink support is a GNU extension"
  OtherEntryType 'L' _ _
    | entryFormat entry /= GnuFormat -> error "putEntry: long filename support is a GNU extension"
  OtherEntryType _ content size -> LBS.concat [ header, content, padding size ]
  _                             -> header
  where
    header       = putHeader entry
    padding size = LBS.replicate paddingSize 0
      where paddingSize = fromIntegral (negate size `mod` 512)

putHeader :: Entry -> LBS.ByteString
putHeader entry =
     LBS.fromStrict
   $ BS.take 148 block
  <> putOct 7 checksum
  <> BS.cons 0x20 (BS.drop 156 block)
  where
    block    = putHeaderNoChkSum entry
    checksum :: Int
    checksum = BS.foldl' (\x y -> x + fromIntegral y) 0 block

putHeaderNoChkSum :: Entry -> BS.ByteString
putHeaderNoChkSum Entry {
    entryTarPath     = TarPath name prefix,
    entryContent     = content,
    entryPermissions = permissions,
    entryOwnership   = ownership,
    entryTime        = modTime,
    entryFormat      = format
  } =

  BS.concat
    [ putPosixString 100 name
    , putOct       8 permissions
    , putOct       8 $ ownerId ownership
    , putOct       8 $ groupId ownership
    , numField    12 contentSize
    , putOct      12 modTime
    , BS.replicate 8 0x20 -- dummy checksum
    , putChar8       typeCode
    , putPosixString 100 linkTarget
    ] <>
  case format of
  V7Format    ->
      BS.replicate 255 0x00
  UstarFormat -> BS.concat
    [ putBString   8 ustarMagic
    , putString   32 $ ownerName ownership
    , putString   32 $ groupName ownership
    , putOct       8 deviceMajor
    , putOct       8 deviceMinor
    , putPosixString 155 prefix
    , BS.replicate   12 0x00
    ]
  GnuFormat -> BS.concat
    [ putBString   8 gnuMagic
    , putString   32 $ ownerName ownership
    , putString   32 $ groupName ownership
    , putGnuDev    8 deviceMajor
    , putGnuDev    8 deviceMinor
    , putPosixString 155 prefix
    , BS.replicate   12 0x00
    ]
  where
    numField :: FieldWidth -> Int64 -> BS.ByteString
    numField w n
      | n >= 0 && n < 1 `shiftL` (3 * (w - 1))
      = putOct w n
      | otherwise
      = putLarge w n

    (typeCode, contentSize, linkTarget,
     deviceMajor, deviceMinor) = case content of
       NormalFile      _ size            -> ('0' , size, mempty, 0,     0)
       Directory                         -> ('5' , 0,    mempty, 0,     0)
       SymbolicLink    (LinkTarget link) -> ('2' , 0,    link,   0,     0)
       HardLink        (LinkTarget link) -> ('1' , 0,    link,   0,     0)
       CharacterDevice major minor       -> ('3' , 0,    mempty, major, minor)
       BlockDevice     major minor       -> ('4' , 0,    mempty, major, minor)
       NamedPipe                         -> ('6' , 0,    mempty, 0,     0)
       OtherEntryType  code _ size       -> (code, size, mempty, 0,     0)

    putGnuDev w n = case content of
      CharacterDevice _ _ -> putOct w n
      BlockDevice     _ _ -> putOct w n
      _                   -> BS.replicate w 0x00

ustarMagic, gnuMagic :: BS.ByteString
ustarMagic = BS.pack [0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30]  -- ustar\NUL00
gnuMagic   = BS.pack [0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00]  -- ustar  \NUL

-- * TAR format primitive output

type FieldWidth = Int

putBString :: FieldWidth -> BS.ByteString -> BS.ByteString
putBString n s = BS.take n s <> BS.replicate (n - BS.length s) 0x00

putPosixString :: FieldWidth -> PosixString -> BS.ByteString
putPosixString n s = posixToByteString (PS.take n s) <> BS.replicate (n - PS.length s) 0x00

putString :: FieldWidth -> String -> BS.ByteString
putString n s = BS.take n (packAscii s) <> BS.replicate (n - length s) 0x00

{-# SPECIALISE putLarge :: FieldWidth -> Int64 -> BS.ByteString #-}
putLarge :: (Bits a, Integral a) => FieldWidth -> a -> BS.ByteString
putLarge n0 x0 = BS.Char8.pack $ '\x80' : reverse (go (n0-1) x0)
  where go 0 _ = []
        go n x = chr (fromIntegral (x .&. 0xff)) : go (n-1) (x `shiftR` 8)

putOct :: (Integral a, Show a) => FieldWidth -> a -> BS.ByteString
putOct n x =
  let octStr = BS.take (n-1) $ BS.Char8.pack $ showOct x ""
   in BS.replicate (n - BS.length octStr - 1) 0x30
   <> octStr
   <> BS.singleton 0x00

putChar8 :: Char -> BS.ByteString
putChar8 = BS.Char8.singleton