2013-12-24 17:13:17 +00:00
|
|
|
{- Linux library copier and binary shimmer
|
|
|
|
-
|
2015-01-21 16:50:09 +00:00
|
|
|
- Copyright 2013 Joey Hess <id@joeyh.name>
|
2013-12-24 17:13:17 +00:00
|
|
|
-
|
2019-03-13 19:48:14 +00:00
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
2013-12-24 17:13:17 +00:00
|
|
|
-}
|
|
|
|
|
|
|
|
module Main where
|
|
|
|
|
|
|
|
import System.Environment
|
|
|
|
import Data.Maybe
|
|
|
|
import System.FilePath
|
|
|
|
import Control.Monad
|
|
|
|
import Data.List
|
|
|
|
import System.Posix.Files
|
2013-12-24 17:25:02 +00:00
|
|
|
import Control.Monad.IfElse
|
2015-12-28 16:41:36 +00:00
|
|
|
import Control.Applicative
|
|
|
|
import Prelude
|
2013-12-24 17:13:17 +00:00
|
|
|
|
2014-04-04 01:25:59 +00:00
|
|
|
import Utility.LinuxMkLibs
|
2013-12-24 17:13:17 +00:00
|
|
|
import Utility.Directory
|
|
|
|
import Utility.Process
|
|
|
|
import Utility.Monad
|
|
|
|
import Utility.Path
|
|
|
|
import Utility.FileMode
|
|
|
|
import Utility.CopyFile
|
|
|
|
|
|
|
|
main :: IO ()
|
|
|
|
main = getArgs >>= go
|
|
|
|
where
|
|
|
|
go [] = error "specify LINUXSTANDALONE_DIST"
|
|
|
|
go (top:_) = mklibs top
|
|
|
|
|
|
|
|
mklibs :: FilePath -> IO ()
|
|
|
|
mklibs top = do
|
|
|
|
fs <- dirContentsRecursive top
|
|
|
|
exes <- filterM checkExe fs
|
|
|
|
libs <- parseLdd <$> readProcess "ldd" exes
|
2020-07-31 18:42:03 +00:00
|
|
|
|
2013-12-24 17:13:17 +00:00
|
|
|
glibclibs <- glibcLibs
|
|
|
|
let libs' = nub $ libs ++ glibclibs
|
2020-07-31 18:42:03 +00:00
|
|
|
let (linkers, otherlibs) = partition ("ld-linux" `isInfixOf`) libs'
|
|
|
|
libdirs <- nub . catMaybes <$> mapM (installLib installFile top) otherlibs
|
|
|
|
libdirs' <- consolidateUsrLib top libdirs
|
|
|
|
|
|
|
|
gconvlibs <- gconvLibs
|
|
|
|
mapM_ (installLib installFile top) gconvlibs
|
2013-12-24 17:25:02 +00:00
|
|
|
|
|
|
|
-- Various files used by runshell to set up env vars used by the
|
|
|
|
-- linker shims.
|
2020-07-31 18:42:03 +00:00
|
|
|
writeFile (top </> "libdirs") (unlines libdirs')
|
|
|
|
writeFile (top </> "gconvdir") (parentDir $ Prelude.head gconvlibs)
|
2013-12-24 17:13:17 +00:00
|
|
|
|
2020-07-31 18:42:03 +00:00
|
|
|
mapM_ (installLib installFile top) linkers
|
|
|
|
let linker = Prelude.head linkers
|
2015-02-16 23:36:26 +00:00
|
|
|
mapM_ (installLinkerShim top linker) exes
|
2020-07-31 18:42:03 +00:00
|
|
|
|
|
|
|
{- If there are two libdirs that are the same except one is in
|
|
|
|
- usr/lib and the other is in lib/, move the contents of the usr/lib one
|
|
|
|
- into the lib/ one. This reduces the number of directories the linker
|
|
|
|
- needs to look in, and so reduces the number of failed stats
|
|
|
|
- and improves startup time.
|
|
|
|
-}
|
|
|
|
consolidateUsrLib :: FilePath -> [FilePath] -> IO [FilePath]
|
|
|
|
consolidateUsrLib top libdirs = map reverse <$> go [] (map reverse libdirs)
|
|
|
|
where
|
|
|
|
go c [] = return c
|
|
|
|
go c (x:[]) = return (x:c)
|
|
|
|
go c (x:y:rest)
|
|
|
|
| x == y ++ reverse ("/usr") = do
|
|
|
|
let x' = reverse x
|
|
|
|
let y' = reverse y
|
|
|
|
fs <- getDirectoryContents (inTop top x')
|
|
|
|
forM_ fs $ \f -> do
|
|
|
|
print f
|
|
|
|
unless (dirCruft f) $
|
|
|
|
renameFile
|
|
|
|
(inTop top (x' </> f))
|
|
|
|
(inTop top (y' </> f))
|
|
|
|
go (y:c) rest
|
|
|
|
| otherwise = do
|
|
|
|
print (x,y)
|
|
|
|
go (x:c) (y:rest)
|
2013-12-24 17:13:17 +00:00
|
|
|
|
|
|
|
{- Installs a linker shim script around a binary.
|
|
|
|
-
|
|
|
|
- Note that each binary is put into its own separate directory,
|
|
|
|
- to avoid eg git looking for binaries in its directory rather
|
2015-02-16 23:36:26 +00:00
|
|
|
- than in PATH.
|
|
|
|
-
|
|
|
|
- The linker is symlinked to a file with the same basename as the binary,
|
|
|
|
- since that looks better in ps than "ld-linux.so".
|
|
|
|
-}
|
|
|
|
installLinkerShim :: FilePath -> FilePath -> FilePath -> IO ()
|
|
|
|
installLinkerShim top linker exe = do
|
|
|
|
createDirectoryIfMissing True (top </> shimdir)
|
|
|
|
createDirectoryIfMissing True (top </> exedir)
|
2016-11-10 19:12:08 +00:00
|
|
|
ifM (isSymbolicLink <$> getSymbolicLinkStatus exe)
|
|
|
|
( do
|
|
|
|
sl <- readSymbolicLink exe
|
|
|
|
nukeFile exe
|
|
|
|
nukeFile exedest
|
|
|
|
-- Assume that for a symlink, the destination
|
|
|
|
-- will also be shimmed.
|
|
|
|
let sl' = ".." </> takeFileName sl </> takeFileName sl
|
|
|
|
createSymbolicLink sl' exedest
|
|
|
|
, renameFile exe exedest
|
|
|
|
)
|
2015-02-16 23:36:26 +00:00
|
|
|
link <- relPathDirToFile (top </> exedir) (top ++ linker)
|
|
|
|
unlessM (doesFileExist (top </> exelink)) $
|
|
|
|
createSymbolicLink link (top </> exelink)
|
2013-12-24 17:13:17 +00:00
|
|
|
writeFile exe $ unlines
|
|
|
|
[ "#!/bin/sh"
|
2015-03-27 20:06:50 +00:00
|
|
|
, "GIT_ANNEX_PROGRAMPATH=\"$0\""
|
|
|
|
, "export GIT_ANNEX_PROGRAMPATH"
|
2015-02-16 23:36:26 +00:00
|
|
|
, "exec \"$GIT_ANNEX_DIR/" ++ exelink ++ "\" --library-path \"$GIT_ANNEX_LD_LIBRARY_PATH\" \"$GIT_ANNEX_DIR/shimmed/" ++ base ++ "/" ++ base ++ "\" \"$@\""
|
2013-12-24 17:13:17 +00:00
|
|
|
]
|
|
|
|
modifyFileMode exe $ addModes executeModes
|
|
|
|
where
|
|
|
|
base = takeFileName exe
|
2015-02-16 23:36:26 +00:00
|
|
|
shimdir = "shimmed" </> base
|
|
|
|
exedir = "exe"
|
|
|
|
exedest = top </> shimdir </> base
|
|
|
|
exelink = exedir </> base
|
2013-12-24 17:13:17 +00:00
|
|
|
|
|
|
|
installFile :: FilePath -> FilePath -> IO ()
|
|
|
|
installFile top f = do
|
|
|
|
createDirectoryIfMissing True destdir
|
2014-08-31 14:57:07 +00:00
|
|
|
void $ copyFileExternal CopyTimeStamps f destdir
|
2013-12-24 17:13:17 +00:00
|
|
|
where
|
2015-01-09 17:11:56 +00:00
|
|
|
destdir = inTop top $ parentDir f
|
2013-12-24 17:13:17 +00:00
|
|
|
|
|
|
|
checkExe :: FilePath -> IO Bool
|
|
|
|
checkExe f
|
|
|
|
| ".so" `isSuffixOf` f = return False
|
|
|
|
| otherwise = ifM (isExecutable . fileMode <$> getFileStatus f)
|
2016-11-10 19:12:08 +00:00
|
|
|
( checkFileExe <$> readProcess "file" ["-L", f]
|
2013-12-24 17:13:17 +00:00
|
|
|
, return False
|
|
|
|
)
|
|
|
|
|
|
|
|
{- Check that file(1) thinks it's a Linux ELF executable, or possibly
|
|
|
|
- a shared library (a few executables like ssh appear as shared libraries). -}
|
|
|
|
checkFileExe :: String -> Bool
|
|
|
|
checkFileExe s = and
|
|
|
|
[ "ELF" `isInfixOf` s
|
|
|
|
, "executable" `isInfixOf` s || "shared object" `isInfixOf` s
|
|
|
|
]
|