aboutsummaryrefslogtreecommitdiff
path: root/scripts/package.py
blob: f2841b8d4ffe970063cdd285c635cfeae7cbee72 (plain)
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
#!/usr/bin/env python3

import subprocess
import sys
import os
import shutil
import re
import stat
import tarfile

run_script_linux = """
#!/bin/sh

set -e

script_path=`readlink -f $0`
script_dir=`dirname $script_path`
"$script_dir/$SO_LOADER" --library-path "$script_dir/libs" "$script_dir/$PROGRAM_NAME" "$@"
"""

def get_executable_dynamic_libraries(filepath):
    libs = []
    process = subprocess.Popen(["ldd", filepath], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (stdout, stderr) = process.communicate()
    if process.returncode != 0:
        raise RuntimeError("Failed to execute ldd on executable %s, error:\n%s" % (filepath, stderr))
    lines = stdout.splitlines()
    for line in lines:
        s = line.split()
        if len(s) >= 3 and b"=>" in s:
            lib_path = " ".join(x.decode("UTF-8") for x in s[2:-1])
            if os.path.exists(lib_path):
                libs.append([os.path.realpath(lib_path), os.path.basename(lib_path)])
    return libs

def make_executable(filepath):
    mode = os.stat(filepath).st_mode
    os.chmod(filepath, mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)

def get_libnss_files():
    libnss_pattern = re.compile("libnss.*\\.so.*")
    files = []
    for filepath in os.listdir("/usr/lib"):
        if libnss_pattern.match(filepath):
            files.append(os.path.join("/usr/lib", filepath))
    return files

def main():
    if len(sys.argv) <= 2:
        print("usage: %s executable_path destination_path" % sys.argv[0])
        exit(1)

    os.makedirs(sys.argv[2], exist_ok=True)
    libs = get_executable_dynamic_libraries(sys.argv[1])

    so_loader_pattern = re.compile("ld-linux-x86-64\\.so.*")
    so_loader = None
    for lib in libs:
        if so_loader_pattern.match(lib[1]):
            if so_loader != None:
                print("Unexpected error: found multiple so loaders, unable to recover")
                exit(2)
            so_loader = lib[1]
    
    if not so_loader:
        print("Unexpected error: no so loader found, unable to recover")
        exit(5)

    so_loader = os.path.join("libs", so_loader)

    libnss_files = get_libnss_files()
    for idx, libnss_file in enumerate(libnss_files):
        new_file_path = os.path.join(sys.argv[2], os.path.basename(libnss_file))
        libnss_files[idx] = new_file_path
        if os.path.islink(libnss_file):
            target_name = os.path.basename(os.readlink(libnss_file))
            os.symlink(target_name, new_file_path)
        else:
            shutil.copyfile(libnss_file, new_file_path)

    executable_filename = os.path.basename(sys.argv[1])
    new_executable_path = os.path.join(sys.argv[2], executable_filename)
    shutil.copyfile(sys.argv[1], new_executable_path)
    make_executable(new_executable_path)

    print("Patching executable")
    process = subprocess.Popen(["patchelf", "--set-interpreter", so_loader, new_executable_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (stdout, stderr) = process.communicate()
    if process.returncode != 0:
        print("Failed to execute patchelf --set-interpreter on executable %s, error: %s" % (new_executable_path, stderr))
        exit(3)
    
    process = subprocess.Popen(["patchelf", "--set-rpath", "libs", new_executable_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (stdout, stderr) = process.communicate()
    if process.returncode != 0:
        print("Failed to execute patchelf --set-rpath on executable %s, error: %s" % (new_executable_path, stderr))
        exit(4)

    run_script_path = os.path.join(sys.argv[2], "run.sh")
    with open(run_script_path, "wb") as run_script:
        run_script.write(run_script_linux.replace("$SO_LOADER", so_loader).replace("$PROGRAM_NAME", "program").encode("UTF-8"))
    make_executable(run_script_path)

    package_name = new_executable_path + ".tar.gz"
    print("Creating archive %s" % os.path.basename(package_name))
    with tarfile.open(package_name, "w:gz") as tar:
        for lib in libs:
            print("Adding shared library %s to package" % lib[0])
            tar.add(lib[0], arcname=os.path.join("libs", lib[1]))

        for libnss_file in libnss_files:
            print("Adding libnss shared library %s to package" % libnss_file)
            libnss_name = os.path.basename(libnss_file)
            tar.add(libnss_file, arcname=os.path.join("libs", libnss_name))

        print("Adding executable %s to package" % sys.argv[1])
        tar.add(new_executable_path, arcname="program")
        print("Adding run script %s to package" % run_script_path)
        tar.add(run_script_path, arcname=executable_filename)
    
    print("Removing temporary files")
    os.remove(new_executable_path)
    os.remove(run_script_path)
    for libnss_file in libnss_files:
        os.remove(libnss_file)
    print("Package has been created at %s" % package_name)

if __name__ == "__main__":
    main()