From 2f5315b3c32fbd23ebce05da80712183dc259b7c Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 5 May 2017 14:27:39 -0400 Subject: standalone tarball Adapted the standalone tarball building code from git-annex. --- .gitignore | 4 ++ Build/LinuxMkLibs.hs | 102 +++++++++++++++++++++++++++++++++++++++++ ControlWindow.hs | 9 +++- Makefile | 54 +++++++++++++++------- Utility/LinuxMkLibs.hs | 60 ++++++++++++++++++++++++ standalone/linux/skel/README | 6 +++ standalone/linux/skel/runshell | 80 ++++++++++++++++++++++++++++++++ 7 files changed, 298 insertions(+), 17 deletions(-) create mode 100644 Build/LinuxMkLibs.hs create mode 100644 Utility/LinuxMkLibs.hs create mode 100644 standalone/linux/skel/README create mode 100755 standalone/linux/skel/runshell diff --git a/.gitignore b/.gitignore index 3a34823..01586cf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ dist/* .stack-work/* debug-me doc/.ikiwiki +*.hi +*.o +Build/LinuxMkLibs +tmp diff --git a/Build/LinuxMkLibs.hs b/Build/LinuxMkLibs.hs new file mode 100644 index 0000000..3b4f7f5 --- /dev/null +++ b/Build/LinuxMkLibs.hs @@ -0,0 +1,102 @@ +{- Linux library copier and binary shimmer + - + - Copyright 2013 Joey Hess + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Main where + +import System.Process +import System.Directory hiding (isSymbolicLink) +import System.Environment +import Data.Maybe +import System.FilePath +import Control.Monad +import Data.List +import System.Posix.Files +import Control.Applicative +import Prelude + +import Utility.LinuxMkLibs + +main :: IO () +main = getArgs >>= go + where + go [] = error "specify LINUXSTANDALONE_DIST" + go (top:_) = mklibs top + +mklibs :: FilePath -> IO () +mklibs top = do + fs <- lines <$> readProcess "find" [top, "-type", "f"] "" + exes <- filterM checkExe fs + libs <- parseLdd <$> readProcess "ldd" exes "" + glibclibs <- glibcLibs + let libs' = nub $ libs ++ glibclibs + libdirs <- nub . catMaybes <$> mapM (installLib installFile top) libs' + + -- Various files used by runshell to set up env vars used by the + -- linker shims. + writeFile (top "libdirs") (unlines libdirs) + writeFile (top "gconvdir") + (takeDirectory $ Prelude.head $ filter ("/gconv/" `isInfixOf`) glibclibs) + + let linker = Prelude.head $ filter ("ld-linux" `isInfixOf`) libs' + mapM_ (installLinkerShim top linker) exes + +{- 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 + - than in PATH. + -} +installLinkerShim :: FilePath -> FilePath -> FilePath -> IO () +installLinkerShim top linker exe = do + createDirectoryIfMissing True (top shimdir) + createDirectoryIfMissing True (top exedir) + islink <- isSymbolicLink <$> getSymbolicLinkStatus exe + if islink + then do + sl <- readSymbolicLink exe + removeFile exe + removeFile exedest + -- Assume that for a symlink, the destination + -- will also be shimmed. + let sl' = ".." takeFileName sl takeFileName sl + createSymbolicLink sl' exedest + else renameFile exe exedest + writeFile exe $ unlines + [ "#!/bin/sh" + , "exec \"$DEBUG_ME_DIR/" ++ linker ++ "\" --library-path \"$DEBUG_ME_LD_LIBRARY_PATH\" \"$DEBUG_ME_DIR/shimmed/" ++ base ++ "/" ++ base ++ "\" \"$@\"" + ] + setFileMode exe $ ownerExecuteMode + `unionFileModes` groupExecuteMode + `unionFileModes` otherExecuteMode + `unionFileModes` ownerReadMode + `unionFileModes` groupReadMode + `unionFileModes` otherReadMode + where + base = takeFileName exe + shimdir = "shimmed" base + exedir = "exe" + exedest = top shimdir base + +installFile :: FilePath -> FilePath -> IO () +installFile top f = do + createDirectoryIfMissing True destdir + callProcess "cp" [f, destdir] + where + destdir = inTop top $ takeDirectory f + +checkExe :: FilePath -> IO Bool +checkExe f + | ".so" `isSuffixOf` f = return False + | otherwise = checkFileExe <$> readProcess "file" ["-L", f] "" + +{- 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 + ] diff --git a/ControlWindow.hs b/ControlWindow.hs index 4f806c9..c5a6be9 100644 --- a/ControlWindow.hs +++ b/ControlWindow.hs @@ -67,7 +67,7 @@ openControlWindow = do ichan <- newTMChanIO ochan <- newTMChanIO _ <- async $ serveControlSocket soc ichan ochan - myexe <- getExecutablePath + myexe <- getMyExe mproc <- runInVirtualTerminal winDesc myexe ["--control"] let cannotrun = do putStrLn "You need to open another shell prompt, and run: debug-me --control" @@ -83,6 +83,13 @@ openControlWindow = do Left (Just ControlWindowOpened) -> return (ichan, ochan) Left _ -> error "unexpected message from control process" Right _ -> cannotrun + + +-- | Get path to debug-me program. +-- +-- The standalone bundle sets DEBUG_ME_EXE to the path to use. +getMyExe :: IO FilePath +getMyExe = maybe getExecutablePath return =<< lookupEnv "DEBUG_ME_EXE" type Prompt = () type Response = String diff --git a/Makefile b/Makefile index ca07972..3244942 100644 --- a/Makefile +++ b/Makefile @@ -41,22 +41,44 @@ debug-me: clean: if [ "$(BUILDER)" != ./Setup ] && [ "$(BUILDER)" != cabal ]; then $(BUILDER) clean; fi - rm -rf debug-me dist .stack-work Setup Setup.hi Setup.o + rm -rf dist .stack-work tmp + rm -f debig-me Build/LinuxMkLibs Setup + find . -name \*.o -exec rm {} \; + find . -name \*.hi -exec rm {} \; install: install-files useradd --system debug-me - chmod 700 $(PREFIX)/var/log/debug-me - chown debug-me:debug-me $(PREFIX)/var/log/debug-me - -install-files: debug-me - install -d $(PREFIX)/var/log/debug-me - install -d $(PREFIX)/usr/bin - install -s -m 0755 debug-me $(PREFIX)/usr/bin/debug-me - install -d $(PREFIX)/usr/share/man/man1 - install -m 0644 debug-me.1 $(PREFIX)/usr/share/man/man1/debug-me.1 - install -d $(PREFIX)/lib/systemd/system - install -m 0644 debug-me.service $(PREFIX)/lib/systemd/system/debug-me.service - install -d $(PREFIX)/etc/init.d - install -m 0755 debug-me.init $(PREFIX)/etc/init.d/debug-me - install -d $(PREFIX)/etc/default - install -m 0644 debug-me.default $(PREFIX)/etc/default/debug-me + chmod 700 $(DESTDIR)$(PREFIX)/var/log/debug-me + chown debug-me:debug-me $(DESTDIR)$(PREFIX)/var/log/debug-me + +install-files: debug-me install-mans + install -d $(DESTDIR)$(PREFIX)/var/log/debug-me + install -d $(DESTDIR)$(PREFIX)/usr/bin + install -s -m 0755 debug-me $(DESTDIR)$(PREFIX)/usr/bin/debug-me + install -d $(DESTDIR)$(PREFIX)/lib/systemd/system + install -m 0644 debug-me.service $(DESTDIR)$(PREFIX)/lib/systemd/system/debug-me.service + install -d $(DESTDIR)$(PREFIX)/etc/init.d + install -m 0755 debug-me.init $(DESTDIR)$(PREFIX)/etc/init.d/debug-me + install -d $(DESTDIR)$(PREFIX)/etc/default + install -m 0644 debug-me.default $(DESTDIR)$(PREFIX)/etc/default/debug-me + +install-mans: + install -d $(DESTDIR)$(PREFIX)/usr/share/man/man1 + install -m 0644 debug-me.1 $(DESTDIR)$(PREFIX)/usr/share/man/man1/debug-me.1 + +Build/LinuxMkLibs: Build/LinuxMkLibs.hs + ghc --make $@ -Wall -fno-warn-tabs + +LINUXSTANDALONE_DEST=tmp/debug-me +linuxstandalone: debug-me Build/LinuxMkLibs + rm -rf "$(LINUXSTANDALONE_DEST)" + mkdir -p tmp + cp -R standalone/linux/skel "$(LINUXSTANDALONE_DEST)" + install -d "$(LINUXSTANDALONE_DEST)/bin" + cp debug-me "$(LINUXSTANDALONE_DEST)/bin/" + strip "$(LINUXSTANDALONE_DEST)/bin/debug-me" + ./Build/LinuxMkLibs "$(LINUXSTANDALONE_DEST)" + $(MAKE) install-mans DESTDIR="$(LINUXSTANDALONE_DEST)" + cd tmp && tar c debug-me | gzip -9 --rsyncable > debug-me-standalone-$(shell dpkg --print-architecture).tar.gz + +.PHONY: debug-me diff --git a/Utility/LinuxMkLibs.hs b/Utility/LinuxMkLibs.hs new file mode 100644 index 0000000..5851ed1 --- /dev/null +++ b/Utility/LinuxMkLibs.hs @@ -0,0 +1,60 @@ +{- Linux library copier and binary shimmer + - + - Copyright 2013 Joey Hess + - + - License: BSD-2-clause + -} + +module Utility.LinuxMkLibs where + +import System.IO +import System.Directory (doesFileExist) +import Data.Maybe +import System.FilePath +import Data.List.Utils +import System.Posix.Files +import Data.Char +import Control.Monad.IfElse +import Control.Applicative +import System.Process +import Prelude + +{- Installs a library. If the library is a symlink to another file, + - install the file it links to, and update the symlink to be relative. -} +installLib :: (FilePath -> FilePath -> IO ()) -> FilePath -> FilePath -> IO (Maybe FilePath) +installLib installfile top lib = go =<< doesFileExist lib + where + go False = return Nothing + go True = do + installfile top lib + checksymlink lib + return $ Just $ takeDirectory lib + + checksymlink f = whenM (isSymbolicLink <$> getSymbolicLinkStatus (inTop top f)) $ do + l <- readSymbolicLink (inTop top f) + let absl = takeDirectory f l + --target <- relPathDirToFile (takeDirectory f) absl + installfile top absl + --removeLink (top ++ f) + --createSymbolicLink target (inTop top f) + checksymlink absl + +-- Note that f is not relative, so cannot use +inTop :: FilePath -> FilePath -> FilePath +inTop top f = top ++ f + +{- Parse ldd output, getting all the libraries that the input files + - link to. Note that some of the libraries may not exist + - (eg, linux-vdso.so) -} +parseLdd :: String -> [FilePath] +parseLdd = concatMap (getlib . dropWhile isSpace) . lines + where + getlib = concatMap (take 1) . map words . take 1 . reverse . split " => " + +{- Get all glibc libs and other support files, including gconv files + - + - XXX Debian specific. -} +glibcLibs :: IO [FilePath] +glibcLibs = lines <$> readProcess "sh" + ["-c", "dpkg -L libc6:$(dpkg --print-architecture) libgcc1:$(dpkg --print-architecture) | egrep '\\.so|gconv'"] + mempty diff --git a/standalone/linux/skel/README b/standalone/linux/skel/README new file mode 100644 index 0000000..0126d8e --- /dev/null +++ b/standalone/linux/skel/README @@ -0,0 +1,6 @@ +You can put this directory into your PATH, or symlink the programs in this +directory to anyplace already in your PATH, and use debug-me the same +as if you'd installed it using a package manager. + +This should work on any Linux system of the appropriate architecture. +More or less. diff --git a/standalone/linux/skel/runshell b/standalone/linux/skel/runshell new file mode 100755 index 0000000..ca7dbe5 --- /dev/null +++ b/standalone/linux/skel/runshell @@ -0,0 +1,80 @@ +#!/bin/sh +# Runs a shell command (or interactive shell) using the binaries and +# libraries bundled with this app. + +set -e + +base="$(dirname "$0")" + +if [ ! -d "$base" ]; then + echo "** cannot find base directory (I seem to be $0)" >&2 + exit 1 +fi + +if [ ! -e "$base/bin/debug-me" ]; then + echo "** base directory $base does not contain bin/debug-me" >&2 + exit 1 +fi + +# Get absolute path to base, to avoid breakage when things change directories. +orig="$(pwd)" +cd "$base" +base="$(pwd)" +cd "$orig" + +# --library-path won't work if $base contains : or ; +# Detect this problem, and work around it by using a temp directory. +if echo "$base" | grep -q '[:;]'; then + tbase=$(mktemp -d -p /tmp debugmeshimXXXXXXXXX 2>/dev/null || true) + if [ -z "$tbase" ]; then + tbase="/tmp/debugmeshim.$$" + mkdir "$tbase" + fi + ln -s "$base" "$tbase/link" + base="$tbase/link" + cleanuptbase () { + rm -rf "$tbase" + } + trap cleanuptbase EXIT +fi + +# Put our binaries first, to avoid issues with out of date or incompatible +# system binaries. Extra binaries come after system path. +ORIG_PATH="$PATH" +export ORIG_PATH +PATH="$base/bin:$PATH:$base/extra" +export PATH + +# These env vars are used by the shim wrapper around each binary. +for lib in $(cat "$base/libdirs"); do + DEBUG_ME_LD_LIBRARY_PATH="$base/$lib:$DEBUG_ME_LD_LIBRARY_PATH" +done +export DEBUG_ME_LD_LIBRARY_PATH +DEBUG_ME_DIR="$base" +export DEBUG_ME_DIR + +ORIG_GCONV_PATH="$GCONV_PATH" +export ORIG_GCONV_PATH +GCONV_PATH="$base/$(cat "$base/gconvdir")" +export GCONV_PATH + +ORIG_MANPATH="$MANPATH" +export ORIG_MANPATH +MANPATH="$base/usr/share/man:$MANPATH" +export MANPATH + +DEBUG_ME_EXE="$base/debug-me" +export DEBUG_ME_EXE + +if [ "$1" ]; then + cmd="$1" + shift 1 + if [ -z "$tbase" ]; then + exec "$cmd" "$@" + else + # allow EXIT trap to cleanup + "$cmd" "$@" + fi +else + sh +fi -- cgit v1.2.3