{-# LANGUAGE OverloadedStrings #-}
module ArTimestampWiper where
import Control.Applicative
import Control.Monad
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import Data.Char
import System.IO
-- | Removes the time stamps of all files in the .a file.
arFileWipeTimeStamps :: FilePath -> IO ()
arFileWipeTimeStamps path = withBinaryFile path ReadWriteMode $ \h -> do
-- We iterate through the archive stepping from one file header to the next,
-- setting the time stamp field to zero.
-- The size field tells us where the next header is.
-- See: http://en.wikipedia.org/wiki/Ar_%28Unix%29.
archiveSize <- hFileSize h
let go entryOffset | entryOffset == archiveSize = return () -- done, at end
| entryOffset > archiveSize = die "Archive truncated"
-- Headers are aligned to even bytes
| odd entryOffset = go (entryOffset + 1)
| otherwise = do
-- Sanity check
magic <- goto 58 >> BS.hGet h 2
when (magic /= "\x60\x0a") $ die "Bad ar magic"
-- Get size (to find following file)
size <- goto 48 >> parseSize . BS8.unpack <$> BS.hGet h 10
-- Wipe time stamp
goto 16 >> BS.hPut h "0 " -- 12 chars
-- Seek to next file at header + file size
go (entryOffset + 60 + size)
where
goto pos = hSeek h AbsoluteSeek (entryOffset + pos)
parseSize x = case reads x of
[(s, r)] | all isSpace r -> s
_ -> die "Malformed header"
die msg = error $ "arFileWipeTimeStamps: " ++ path ++ ": "
++ msg ++ " at offset " ++ show entryOffset
go 8 -- 8 == size of global header, before first file header