{-# LANGUAGE MultiWayIf #-}
module AWS.Lambda.RuntimeAPI.Package (
packageAwsLambda,
Conf (..),
defaultConf,
confAdditionalLibs,
confReadFile,
findExtraLibs,
) where
import Prelude ()
import Prelude.Compat
import Control.Applicative (Alternative (..), optional)
import Control.DeepSeq (force)
import Control.Exception (evaluate)
import Data.Bits (shiftL)
import Data.Char (isAlphaNum, isHexDigit)
import Data.List (isPrefixOf)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Maybe (catMaybes)
import Data.Traversable (for)
import System.FilePath.Posix (takeFileName)
import System.Process (proc, readCreateProcess)
import qualified Codec.Archive.Zip as Zip
import qualified Data.ByteString.Lazy as LBS
import qualified Text.Parsec as P
packageAwsLambda
:: Conf
-> FilePath
-> IO LBS.ByteString
packageAwsLambda conf exePath = do
exeContents <- _confReadFile conf exePath
let exeEntry :: Zip.Entry
exeEntry = (Zip.toEntry "bootstrap" 0 exeContents)
{ Zip.eExternalFileAttributes = shiftL 0755 16
, Zip.eVersionMadeBy = 0x0300
}
libs <- findExtraLibs (_confAdditionalLibs conf) exePath
libEntries <- for libs $ \lib -> do
libContents <- _confReadFile conf lib
return $ Zip.toEntry (takeFileName lib) 0 libContents
let entries = exeEntry : libEntries
evaluate $ force $ Zip.fromArchive Zip.Archive
{ Zip.zEntries = entries
, Zip.zSignature = Nothing
, Zip.zComment = mempty
}
data Conf = Conf
{ _confAdditionalLibs :: ![String]
, _confReadFile :: !(FilePath -> IO LBS.ByteString)
}
defaultConf :: Conf
defaultConf = Conf
{ _confAdditionalLibs = []
, _confReadFile = LBS.readFile
}
confAdditionalLibs :: Functor f => ([String] -> f [String]) -> Conf -> f Conf
confAdditionalLibs f conf = (\x -> conf { _confAdditionalLibs = x}) <$> f (_confAdditionalLibs conf)
confReadFile :: Functor f => ((FilePath -> IO LBS.ByteString) -> f (FilePath -> IO LBS.ByteString)) -> Conf -> f Conf
confReadFile f conf = (\x -> conf { _confReadFile = x}) <$> f (_confReadFile conf)
findExtraLibs
:: [String]
-> FilePath
-> IO [FilePath]
findExtraLibs additionalCopyLibs fp = do
output <- readCreateProcess (proc "ldd" [fp]) ""
either (fail . show) (return . catMaybes) $
P.parse (many lddLine <* P.eof) "<ldd output>" output
where
lddLine = do
P.spaces
lib <|> ldLinux
lib = do
l :| _ <- sepByNonEmpty
(some $ P.satisfy $ \c -> isAlphaNum c || c == '-' || c == '+' || c == '_')
(P.char '.')
P.spaces
md <- optional $ do
_ <- P.string "=>"
P.spaces
many $ P.satisfy $ \c ->
isAlphaNum c || c == '.' || c == '-' || c == '+' || c == '_' || c == '/'
_ <- address
if | "libHS" `isPrefixOf` l -> return md
| l `elem` skipLibs -> return Nothing
| l `elem` copyLibs -> return md
| otherwise -> fail $ "Unknown lib " ++ l
ldLinux = P.string "/lib64/ld-linux-x86-64.so.2" *> address *> return Nothing
sepByNonEmpty p sep = do
x <- p
xs <- many (sep *> p)
return (x :| xs)
skipLibs =
[ "linux-vdso"
, "libBrokenLocale"
, "libacl"
, "libanl"
, "libasound"
, "libattr"
, "libaudit"
, "libauparse"
, "libblkid"
, "libbz2"
, "libc"
, "libcap-ng"
, "libcap"
, "libcidn"
, "libcrypt"
, "libdbus-1"
, "libdl"
, "libexpat"
, "libgcc_s-4.8.3-20140911"
, "libgcc_s"
, "libgpg-error"
, "libidn"
, "libip4tc"
, "libip6tc"
, "libiptc"
, "libkeyutils"
, "liblber-2.4"
, "libldap-2.4"
, "libldap_r-2.4"
, "libldif-2.4"
, "libm"
, "libmount"
, "libncurses"
, "libncursesw"
, "libnih-dbus"
, "libnih"
, "libnsl"
, "libnss_compat"
, "libnss_db"
, "libnss_dns"
, "libnss_files"
, "libnss_hesiod"
, "libnss_nis"
, "libnss_nisplus"
, "libpam"
, "libpam_misc"
, "libpamc"
, "libpcre"
, "libpopt"
, "libpthread"
, "libpwquality"
, "libreadline"
, "libresolv"
, "librt"
, "libsepol"
, "libthread_db"
, "libtinfo"
, "libudev"
, "libutil"
, "libuuid"
, "libxtables"
, "libz"
]
copyLibs =
[ "libgmp"
, "libffi"
] ++ additionalCopyLibs
address = P.spaces
*> P.char '('
*> P.string "0x"
*> P.skipMany (P.satisfy isHexDigit)
*> P.char ')'
*> P.char '\n'