Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: CLI options for bundle directory and program name #284

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.formatOnSave": false
}
1 change: 1 addition & 0 deletions bootloader/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#define ARCHIVE_SECTION ".staticx.archive"
#define INTERP_FILENAME ".staticx.interp"
#define PROG_FILENAME ".staticx.prog"
#define BUNDLE_DIR_FILENAME ".staticx.bundle-dir"

static inline void *
ptr_add(void *p, size_t off)
Expand Down
74 changes: 56 additions & 18 deletions bootloader/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <fcntl.h>
#include <elf.h>
#include <sys/wait.h>
#include <sys/param.h>
#include "xz.h"
#include "error.h"
#include "mmap.h"
Expand All @@ -31,7 +32,7 @@


/* The "bundle" directory, where the archive is extracted */
static const char *m_bundle_dir;
static char *m_bundle_dir;

static char *
path_join(const char *p1, const char *p2)
Expand Down Expand Up @@ -382,30 +383,58 @@ cleanup_bundle_dir(void)
fprintf(stderr, "staticx: Failed to cleanup %s: %m\n", m_bundle_dir);
}

free((void*)m_bundle_dir);
free(m_bundle_dir);
m_bundle_dir = NULL;
}

/**
* Returns the path to the actual user program to execute.
* We resolve this manually rather than executing the symlink
* to ensure the program sees the original argv[0]. See #134.
*
* Note however that we do not currently pass-through argv[0].
* See #133.
* Returns the path stored in the specified symlink file.
*
* We resolve this manually rather than executing the symlink for the program to ensure that the program sees the original argv[0] (see #134). Note however that we do not currently pass-through argv[0] (see #133).
*/
static char *
get_real_prog_path(void)
static char *get_symlink(char *file_path)
{
// PROG_FILENAME is a symlink to the user's program
char *linkpath = path_join(m_bundle_dir, PROG_FILENAME);
char *absolute_path = path_join(m_bundle_dir, file_path);

char *result = realpath(linkpath, NULL);
if (!result)
error(2, errno, "Failed to get realpath for %s", linkpath);
char *linked_path = malloc(MAXPATHLEN + 1);
ssize_t path_length = readlink(absolute_path, linked_path, MAXPATHLEN);

free(linkpath);
return result;
if (path_length == -1)
error(2, errno, "Failed to read the symlink for %s", absolute_path);
if (path_length > MAXPATHLEN)
error(2, errno, "Length of symlink value (%zu) for %s is too long", path_length, absolute_path);

free(absolute_path);
linked_path[path_length] = '\0';
return linked_path;
}

/**
* Moves and renames the bundle from `m_bundle_dir` to the `destination` and updates `m_bundle_dir` (do not free `destination`). Fails when a file or folder already exists at the `destination`.
*/
inline static void move_bundle(char *destination)
{
char *origin = m_bundle_dir;
if (access(destination, F_OK) == 0)
error(2, 0, "Failed to move bundle from %s to %s because the destination already exists", origin, destination);

int result = rename(origin, destination);
if (result == -1)
error(2, errno, "Failed to move the bundle from %s to %s", origin, destination);

free(origin);
m_bundle_dir = destination;
}

/**
* Checks whether or not a file exists within the bundle. Does not follow symlinks.
*/
static bool file_exists(char *file_path)
{
char *absolute_path = path_join(m_bundle_dir, file_path);
bool exists = faccessat(0, absolute_path, F_OK, AT_SYMLINK_NOFOLLOW) == 0;
free(absolute_path);
return exists;
}

static void identify(void)
Expand All @@ -432,8 +461,17 @@ main(int argc, char **argv)
/* Extract the archive embedded in this program */
extract_archive(m_bundle_dir);

/* Check for custom bundle directory and move the bundle */
if (file_exists(BUNDLE_DIR_FILENAME)) {
move_bundle(get_symlink(BUNDLE_DIR_FILENAME));
debug_printf("Moved the bundle dir to: %s\n", m_bundle_dir);
}

/* Get path to user application inside temp dir */
char *prog_path = get_real_prog_path();
char *prog_name = get_symlink(PROG_FILENAME);
char *prog_path = path_join(m_bundle_dir, prog_name);
debug_printf("Program located at: %s\n", prog_path);
free(prog_name);

/* Patch the user application ELF to run in the temp dir */
patch_app(prog_path);
Expand Down
17 changes: 11 additions & 6 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ Usage

Synopsis
--------
.. code-block::
.. code-block:: shell

staticx [-h]
[-l LIB] [--strip] [--no-compress] [-V]
[--loglevel LEVEL]
[-l LIB] [--strip] [--no-compress]
[--bundle-dir BUNDLE_DIR] [--prog-name PROG_NAME]
[-V] [--loglevel LEVEL]
PROG OUTPUT

Positional Arguments:
Expand All @@ -19,12 +20,17 @@ Positional Arguments:

Options:
-h, --help Show help message and exit

-l LIB Add additional library (absolute path)

This option can be given multiple times.

--strip Strip binaries before adding to archive (reduces size)
--no-compress Don't compress the archive (increases size)
--bundle-dir BUNDLE_DIR
The directory that the program extracts to at runtime. When not specified, staticx extracts to ``/tmp/staticx-XXXXXX``.
--prog-name PROG_NAME
The name that the program extracts to at runtime. When not specified, staticx uses the name of the input program.
--loglevel LEVEL Set the logging level (default: WARNING)

Options: DEBUG,INFO,WARNING,ERROR,CRITICAL
Expand Down Expand Up @@ -70,6 +76,5 @@ Run-time Information
--------------------
StaticX sets the following environment variables for the wrapped user program:

- ``STATICX_BUNDLE_DIR``: The absolute path of the "bundle" directory, the
temporary dir where the archive has been extracted.
- ``STATICX_PROG_PATH``: The absolute path of the program being executed.
- ``STATICX_BUNDLE_DIR``: The absolute path of the "bundle" directory, a temporary directory, where the archive has been extracted.
- ``STATICX_PROG_PATH``: The absolute path of the program being executed (not the extracted program in the bundle directory).
9 changes: 7 additions & 2 deletions staticx/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def parse_args():
help = 'Strip binaries before adding to archive (reduces size)')
ap.add_argument('--no-compress', action='store_true',
help = "Don't compress the archive (increases size)")
ap.add_argument('--bundle-dir',
help = 'The directory that the program extracts to at runtime. When not specified, staticx extracts to /tmp/staticx-XXXXXX.')
ap.add_argument('--prog-name',
help = 'The name that the program extracts to at runtime. When not specified, staticx uses the name of the input program.')

# Special / output-related options
ap.add_argument('-V', '--version', action='version',
Expand All @@ -50,8 +54,9 @@ def main():
libs = args.libs,
strip = args.strip,
compress = not args.no_compress,
debug = args.debug,
)
bundle_dir = args.bundle_dir,
prog_name = args.prog_name,
debug = args.debug)
except Error as e:
if args.debug:
raise
Expand Down
38 changes: 25 additions & 13 deletions staticx/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@ class StaticxGenerator:
"""StaticxGenerator is responsible for producing a staticx-ified executable.
"""

def __init__(self, prog, strip=False, compress=True, debug=False, cleanup=True):
def __init__(self, prog, strip=False, compress=True, bundle_dir=None, prog_name=None, debug=False, cleanup=True):
"""
Parameters:
prog: Dynamic executable to staticx
debug: Run in debug mode (use debug bootloader)
Parameters:
prog: Dynamic executable to staticx
strip: Strip binaries to reduce size
compress: Whether or not to compress the archive
bundle_dir: The directory that the program extracts to at runtime
prog_name: The name that the program extracts to at runtime
debug: Run in debug mode (use debug bootloader)
"""
self.orig_prog = prog
self.strip = strip
self.compress = compress
self.bundle_dir = bundle_dir
self.prog_name = prog_name
self.debug = debug
self.cleanup = cleanup

Expand Down Expand Up @@ -133,8 +139,10 @@ def generate(self, output):
with self.sxar as ar:
run_hooks(self)

ar.add_program(self.tmpprog, basename(self.orig_prog))
ar.add_program(self.tmpprog, self.prog_name or basename(self.orig_prog))
ar.add_interp_symlink(orig_interp)
if self.bundle_dir is not None:
ar.add_bundle_dir_symlink(self.bundle_dir)

# Add all of the libraries
for libpath in get_shobj_deps(self.orig_prog):
Expand Down Expand Up @@ -294,15 +302,18 @@ def _fixup_prog(self):
force_rpath=True, no_default_lib=True)


def generate(prog, output, libs=None, strip=False, compress=True, debug=False):
def generate(prog, output, libs=None, strip=False, compress=True, bundle_dir=None, prog_name=None, debug=False):
"""Main API: Generate a staticx executable

Parameters:
prog: Dynamic executable to staticx
output: Path to result
libs: Extra libraries to include
strip: Strip binaries to reduce size
debug: Run in debug mode (use debug bootloader)
prog: Dynamic executable to staticx
output: Path to result
libs: Extra libraries to include
strip: Strip binaries to reduce size
compress: Whether or not to compress the archive
bundle_dir: The directory that the program extracts to at runtime
prog_name: The name that the program extracts to at runtime
debug: Run in debug mode (use debug bootloader)
"""

logging.info(f"Running StaticX version {__version__}")
Expand All @@ -319,8 +330,9 @@ def generate(prog, output, libs=None, strip=False, compress=True, debug=False):
prog=prog,
strip=strip,
compress=compress,
debug=debug,
)
bundle_dir=bundle_dir,
prog_name=prog_name,
debug=debug)
with gen:
for lib in (libs or []):
gen.add_library(lib)
Expand Down
4 changes: 4 additions & 0 deletions staticx/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,7 @@ def add_file(self, path, arcname=None):
def add_interp_symlink(self, interp):
"""Add symlink for ld.so interpreter"""
self.add_symlink(INTERP_FILENAME, basename(interp))

def add_bundle_dir_symlink(self, bundle_dir):
"""Adds a symlink that holds the path that the bundle directory will be moved to."""
self.add_symlink(BUNDLE_DIR_FILENAME, bundle_dir)
7 changes: 4 additions & 3 deletions staticx/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
ARCHIVE_SECTION = ".staticx.archive"
INTERP_FILENAME = ".staticx.interp"
PROG_FILENAME = ".staticx.prog"
ARCHIVE_SECTION = ".staticx.archive"
INTERP_FILENAME = ".staticx.interp"
PROG_FILENAME = ".staticx.prog"
BUNDLE_DIR_FILENAME = ".staticx.bundle-dir"

MAX_INTERP_LEN = 256
MAX_RPATH_LEN = 256
34 changes: 34 additions & 0 deletions test/bundle-dir-prog-name.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash
set -e

echo -e "\n\n--------------------------------------------------------------------------------"
echo -e "Test StaticX --bundle-dir and --prog-name options."

cd "$(dirname "${BASH_SOURCE[0]}")"

app="$(which sh)"
outfile="./sh.staticx"

# The values we're going to be testing for.
bundle_dir=$(mktemp --dry-run --tmpdir -d systemd-private-60ce0209a865480b817f3623184c43e2-dmesg.service-XXXXXX)
prog_name="tmp"

# Make a staticx executable from bash so we can check the STATICX_ env vars to know the bundle directory and program path.
echo -e "\nMaking staticx executable (\$STATICX_FLAGS=$STATICX_FLAGS):"
staticx $STATICX_FLAGS --bundle-dir $bundle_dir --prog-name $prog_name $app $outfile

# Verify STATICX_BUNDLE_DIR matches the `--bundle-dir` option.
test_bundle_dir=$($outfile -c 'echo $STATICX_BUNDLE_DIR')
echo "STATICX_BUNDLE_DIR: $test_bundle_dir"
if [[ "$test_bundle_dir" != "$bundle_dir" ]]; then
echo "STATICX_BUNDLE_DIR does not match --bundle-dir: \"$test_bundle_dir\" != \"$bundle_dir\""
exit 1
fi

# Verify that the program is renamed to `--prog-name` when specified.
test_prog_name=$($outfile -c 'basename $0')
echo "The program name is: $test_prog_name"
if [[ "$test_prog_name" != "$prog_name" ]]; then
echo "The program name does not match --prog-name: \"$test_prog_name\" != \"$prog_name\""
exit 1
fi
3 changes: 3 additions & 0 deletions test/run_all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
# Test environment variables
./staticx-env-vars.sh

# Test --bundle-dir and --prog-name options
./bundle-dir-prog-name.sh

# Run test an executable linked against musl-libc
musl/run_test.sh

Expand Down