diff --git a/AppRun.c.patch b/AppRun.c.patch index 10bf595..49b98ab 100644 --- a/AppRun.c.patch +++ b/AppRun.c.patch @@ -1,19 +1,34 @@ +https://github.com/AppImage/AppImageKit/blob/ddcb5b98073ec251cbddda7a4eaa17545f06bbff/AppRun.c + --- a/AppRun.c +++ b/AppRun.c -@@ -156,6 +156,7 @@ +@@ -164,6 +164,10 @@ char *old_env; - const int length = 2047; - char new_env[8][length+1]; + size_t length; + const char *format; + checkrt(usr_in_appdir); ++ ++ if (optional_ld_preload) ++ putenv(optional_ld_preload); /* https://docs.python.org/2/using/cmdline.html#envvar-PYTHONHOME */ - snprintf(new_env[0], length, "PYTHONHOME=%s/usr/", appdir); -@@ -164,7 +165,7 @@ - snprintf(new_env[1], length, "PATH=%s/usr/bin/:%s/usr/sbin/:%s/usr/games/:%s/bin/:%s/sbin/:%s", appdir, appdir, appdir, appdir, appdir, old_env); + SET_NEW_ENV(new_pythonhome, appdir_s, "PYTHONHOME=%s/usr/", appdir); +@@ -172,7 +176,7 @@ + SET_NEW_ENV(new_path, appdir_s*5 + strlen(old_env), "PATH=%s/usr/bin/:%s/usr/sbin/:%s/usr/games/:%s/bin/:%s/sbin/:%s", appdir, appdir, appdir, appdir, appdir, old_env); old_env = getenv("LD_LIBRARY_PATH") ?: ""; -- snprintf(new_env[2], length, "LD_LIBRARY_PATH=%s/usr/lib/:%s/usr/lib/i386-linux-gnu/:%s/usr/lib/x86_64-linux-gnu/:%s/usr/lib32/:%s/usr/lib64/:%s/lib/:%s/lib/i386-linux-gnu/:%s/lib/x86_64-linux-gnu/:%s/lib32/:%s/lib64/:%s", appdir, appdir, appdir, appdir, appdir, appdir, appdir, appdir, appdir, appdir, old_env); -+ snprintf(new_env[2], length, "LD_LIBRARY_PATH=%s%s/usr/lib/:%s/usr/lib/i386-linux-gnu/:%s/usr/lib/x86_64-linux-gnu/:%s/usr/lib32/:%s/usr/lib64/:%s/lib/:%s/lib/i386-linux-gnu/:%s/lib/x86_64-linux-gnu/:%s/lib32/:%s/lib64/:%s", optional, appdir, appdir, appdir, appdir, appdir, appdir, appdir, appdir, appdir, appdir, old_env); +- SET_NEW_ENV(new_ld_library_path, appdir_s*10 + strlen(old_env), "LD_LIBRARY_PATH=%s/usr/lib/:%s/usr/lib/i386-linux-gnu/:%s/usr/lib/x86_64-linux-gnu/:%s/usr/lib32/:%s/usr/lib64/:%s/lib/:%s/lib/i386-linux-gnu/:%s/lib/x86_64-linux-gnu/:%s/lib32/:%s/lib64/:%s", appdir, appdir, appdir, appdir, appdir, appdir, appdir, appdir, appdir, appdir, old_env); ++ SET_NEW_ENV(new_ld_library_path, appdir_s*10 + strlen(old_env), "LD_LIBRARY_PATH=%s%s/usr/lib/:%s/usr/lib/i386-linux-gnu/:%s/usr/lib/x86_64-linux-gnu/:%s/usr/lib32/:%s/usr/lib64/:%s/lib/:%s/lib/i386-linux-gnu/:%s/lib/x86_64-linux-gnu/:%s/lib32/:%s/lib64/:%s", optional, appdir, appdir, appdir, appdir, appdir, appdir, appdir, appdir, appdir, appdir, old_env); old_env = getenv("PYTHONPATH") ?: ""; - snprintf(new_env[3], length, "PYTHONPATH=%s/usr/share/pyshared/:%s", appdir, old_env); + SET_NEW_ENV(new_pythonpath, appdir_s + strlen(old_env), "PYTHONPATH=%s/usr/share/pyshared/:%s", appdir, old_env); +@@ -201,6 +205,9 @@ + if (ret == -1) + die("Error executing '%s': %s\n", exe, strerror(error)); + ++ free(optional); ++ if (optional_ld_preload) ++ free(optional_ld_preload); + free(line); + free(desktop_file); + free(usr_in_appdir); diff --git a/Makefile b/Makefile index 4d71d35..f8b53df 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,22 @@ CFLAGS ?= -O2 -Wall -Wextra -CFLAGS += -include checkrt.h LDFLAGS += -s BIN = AppRun_patched +LIB = exec.so -all: $(BIN) +all: $(BIN) $(LIB) clean: - -rm -f $(BIN) *.o AppRun.c AppRun_patched.c + -rm -f $(BIN) $(LIB) *.o AppRun.c AppRun_patched.c $(BIN): AppRun_patched.o checkrt.o +$(LIB): exec.o + $(CC) -shared $(LDFLAGS) -o $@ $^ -ldl + +AppRun_patched.o checkrt.o: CFLAGS += -include checkrt.h +exec.o: CFLAGS += -fPIC + AppRun_patched.c: AppRun.c patch -p1 --output $@ < AppRun.c.patch diff --git a/README.md b/README.md index cd4fc05..23571f8 100644 --- a/README.md +++ b/README.md @@ -15,5 +15,5 @@ That's because both libraries are part of GCC. You would have to know the library version of the host system and decide whether to use a bundled library or not before the application is started. This is exactly what the patched AppRun binary does. It will search for `usr/optional/libstdc++/libstdc++.so.6` and `usr/optional/libgcc_s/libgcc_s.so.1` inside the AppImage or AppDir. -If found it will compare their internal versions with the ones found on the system and prepend their paths to `LD_LIBRARY_PATH` -if necessary. +If found it will compare their internal versions with the ones found on the system and prepend their paths to `LD_LIBRARY_PATH` if necessary. +You should also put `exec.so` into `usr/optional`. diff --git a/checkrt.c b/checkrt.c index 7497162..ef6dd80 100644 --- a/checkrt.c +++ b/checkrt.c @@ -41,10 +41,12 @@ ret = fscanf(f, "%s", sym); (void)ret; \ pclose(f); -#define CXXDIR "optional/libstdc++" -#define GCCDIR "optional/libgcc" +#define CXXDIR "optional/libstdc++" +#define GCCDIR "optional/libgcc" +#define EXEC_SO "optional/exec.so" -char optional[1024]; +char *optional = NULL; +char *optional_ld_preload = NULL; void checkrt(char *usr_in_appdir) { @@ -92,6 +94,7 @@ void checkrt(char *usr_in_appdir) int bundle_cxx = 0; int bundle_gcc = 0; + size_t len = strlen(usr_in_appdir); if (stdcxx_bundle_ver > stdcxx_sys_ver) bundle_cxx = 1; @@ -99,13 +102,22 @@ void checkrt(char *usr_in_appdir) if (gcc_bundle_ver > gcc_sys_ver) bundle_gcc = 1; + if (bundle_cxx == 1 || bundle_gcc == 1) { + optional_ld_preload = malloc(strlen("LD_PRELOAD=") + strlen(EXEC_SO) + 1 + len); + sprintf(optional_ld_preload, "LD_PRELOAD=%s/" EXEC_SO, usr_in_appdir); + } + if (bundle_cxx == 1 && bundle_gcc == 0) { + optional = malloc(strlen(CXXDIR) + 2 + len); sprintf(optional, "%s/" CXXDIR ":", usr_in_appdir); } else if (bundle_cxx == 0 && bundle_gcc == 1) { + optional = malloc(strlen(GCCDIR) + 2 + len); sprintf(optional, "%s/" GCCDIR ":", usr_in_appdir); } else if (bundle_cxx == 1 && bundle_gcc == 1) { + optional = malloc(strlen(GCCDIR) + strlen(CXXDIR) + 4 + len*2); sprintf(optional, "%s/" GCCDIR ":%s/" CXXDIR ":", usr_in_appdir, usr_in_appdir); } else { + optional = malloc(2); sprintf(optional, "%s", ""); } } diff --git a/checkrt.h b/checkrt.h index 1cc9300..aafe389 100644 --- a/checkrt.h +++ b/checkrt.h @@ -1,2 +1,3 @@ -extern char optional[1024]; +extern char *optional; +extern char *optional_ld_preload; extern void checkrt(char *usr_in_appdir); diff --git a/exec.c b/exec.c new file mode 100644 index 0000000..7632b74 --- /dev/null +++ b/exec.c @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2016 Sven Brauch + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** +This library is intended to be used together with the AppImage distribution mechanism. +Place the library somewhere in your AppImage and point LD_PRELOAD to it +before launching your application. + +Whenever your application invokes a child process through execv() or execve(), +this wrapper will intercept the call and see if the child process lies +outside of the bundled appdir. If it does, the wrapper will attempt to undo +any changes done to environment variables before launching the process, +since you probably did not intend to launch it with e.g. the LD_LIBRARY_PATH +you previously set for your application. + +To perform this operation, you have to set the following environment variables: + $APPDIR -- path of the AppDir you are launching your application from. If this + is not present, the wrapper will do nothing. + +For each environment variable you want restored, where {VAR} is the name of the environment +variable (e.g. "PATH"): + $APPIMAGE_ORIGINAL_{VAR} -- original value of the environment variable + $APPIMAGE_STARTUP_{VAR} -- value of the variable when you were starting up + your application +*/ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +typedef ssize_t (*execve_func_t)(const char* filename, char* const argv[], char* const envp[]); +static execve_func_t old_execve = NULL; + +typedef ssize_t (*execvp_func_t)(const char* filename, char* const argv[]); +//static execvp_func_t old_execvp = NULL; + +// TODO implement me: execl, execlp, execle; but it's annoying work and nothing seems to use them +// typedef int (*execl_func_t)(const char *path, const char *arg); +// static execl_func_t old_execl = NULL; +// +// typedef int (*execlp_func_t)(const char *file, const char *arg); +// static execlp_func_t old_execlp = NULL; +// +// typedef int (*execle_func_t)(const char *path, const char *arg, char * const envp[]); +// static execle_func_t old_execle = NULL; + +typedef int (*execv_func_t)(const char *path, char *const argv[]); +//static execv_func_t old_execv = NULL; + +typedef int (*execvpe_func_t)(const char *file, char *const argv[], char *const envp[]); +static execvpe_func_t old_execvpe = NULL; + +char* APPIMAGE_ORIG_PREFIX = "APPIMAGE_ORIGINAL_"; +char* APPIMAGE_STARTUP_PREFIX = "APPIMAGE_STARTUP_"; +char* APPDIR = "APPDIR"; + +typedef struct { + char** names; + char** values; +} environment; + +environment environment_alloc(size_t envc) { + environment env; + env.names = calloc(envc+1, sizeof(char*)); + env.values = calloc(envc+1, sizeof(char*)); + return env; +} + +int arr_len(char* const x[]) { + int len = 0; + while ( x[len] != 0 ) { + len++; + } + return len; +} + +void stringlist_free(char* const envp[]) { + if ( envp ) { + for ( int i = 0; i < arr_len(envp); i++ ) { + free(envp[i]); + } + } +} + +char** stringlist_alloc(int size) { + char** ret = calloc(size, sizeof(char*)); + return ret; +} + +int environment_len(const environment env) { + return arr_len(env.names); +} + +void environment_free(environment env) { + stringlist_free(env.names); + stringlist_free(env.values); +} + +void environment_append_item(environment env, char* name, int name_size, char* val, int val_size) { + int count = environment_len(env); + env.names[count] = calloc(name_size+1, sizeof(char)); + env.values[count] = calloc(val_size+1, sizeof(char)); + strncpy(env.names[count], name, name_size); + strncpy(env.values[count], val, val_size); +} + +int environment_find_name(environment env, char* name, int name_size) { + int count = environment_len(env); + for ( int i = 0; i < count; i++ ) { + if ( !strncmp(env.names[i], name, name_size) ) { + return i; + } + } + return -1; +} + +char** environment_to_stringlist(environment env) { + int len = environment_len(env); + char** ret = stringlist_alloc(len+1); + for ( int i = 0; i < len; i++ ) { + char* name = env.names[i]; + char* value = env.values[i]; + int result_len = strlen(name) + strlen(value) + 1; + ret[i] = calloc(result_len+1, sizeof(char)); + strcat(ret[i], name); + strcat(ret[i], "="); + strcat(ret[i], value); + } + return ret; +} + +char** adjusted_environment(const char* filename, char* const envp[]) { + if ( !envp ) { + return NULL; + } + + int envc = arr_len(envp); + + char* appdir = NULL; + + environment orig = environment_alloc(envc); + environment startup = environment_alloc(envc); + int orig_prefix_len = strlen(APPIMAGE_ORIG_PREFIX); + int startup_prefix_len = strlen(APPIMAGE_STARTUP_PREFIX); + for ( int i = 0; i < envc; i++ ) { + char* line = envp[i]; + int name_size = strchr(line, '=')-line; + int val_size = strlen(line)-name_size-1; + + if ( !strncmp(line, APPIMAGE_ORIG_PREFIX, orig_prefix_len) ) { + environment_append_item(orig, line+orig_prefix_len, name_size-orig_prefix_len, + line+name_size+1, val_size); + } + if ( !strncmp(line, APPIMAGE_STARTUP_PREFIX, startup_prefix_len) ) { + environment_append_item(startup, line+startup_prefix_len, name_size-startup_prefix_len, + line+name_size+1, val_size); + } + if ( !strncmp(line, APPDIR, strlen(APPDIR)) ) { + appdir = calloc(val_size+1, sizeof(char)); + strncpy(appdir, line+name_size+1, val_size); + } + } + + environment new_env = environment_alloc(envc); + if ( appdir && strncmp(filename, appdir, strlen(appdir)) ) { + // we have a value for $APPDIR and are leaving it -- perform replacement + for ( int i = 0; i < envc; i++ ) { + char* line = envp[i]; + if ( !strncmp(line, APPIMAGE_ORIG_PREFIX, strlen(APPIMAGE_ORIG_PREFIX)) || + !strncmp(line, APPIMAGE_STARTUP_PREFIX, strlen(APPIMAGE_STARTUP_PREFIX)) ) + { + // we are not interested in the backup vars here, don't copy them over + continue; + } + + int name_size = strchr(line, '=')-line; + int val_size = strlen(line)-name_size-1; + char* value = line+name_size+1; + int value_len = strlen(value); + + int at_startup = environment_find_name(startup, line, name_size); + int at_original = environment_find_name(orig, line, name_size); + if ( at_startup == -1 || at_original == -1 ) { + // no information, just keep it + environment_append_item(new_env, line, name_size, value, value_len); + continue; + } + + char* at_start = startup.values[at_startup]; + int at_start_len = strlen(at_start); + char* at_orig = orig.values[at_original]; + int at_orig_len = strlen(at_orig); + + // TODO HACK: do not copy over empty vars + if ( strlen(at_orig) == 0 ) { + continue; + } + + if ( !strncmp(line+name_size+1, startup.values[at_startup], val_size) ) { + // nothing changed since startup, restore old value + environment_append_item(new_env, line, name_size, at_orig, at_orig_len); + continue; + } + + int chars_added = value_len > at_start_len; + char* use_value = NULL; + if ( chars_added > 0 ) { + // something was added to the current value + // take _original_ value of the env var and append/prepend the same thing + use_value = calloc(strlen(at_orig) + chars_added + 1, sizeof(char)); + if ( !strncmp(value, at_start, at_start_len) ) { + // append case + strcat(use_value, value); + strcat(use_value, at_orig + strlen(value)); + } + else if ( !strncmp(value+(value_len-at_start_len), at_start, at_start_len) ) { + // prepend case + strcat(use_value, at_orig + strlen(value)); + strcat(use_value, value); + } + else { + // none of the above methods matched + // assume the value changed completely and simply keep what the application set + free(use_value); + use_value = NULL; + } + } + if ( !use_value ) { + environment_append_item(new_env, line, name_size, value, value_len); + } + else { + environment_append_item(new_env, line, name_size, use_value, strlen(use_value)); + free(use_value); + } + } + } + + char** ret = NULL; + if ( environment_len(new_env) > 0 ) { + ret = environment_to_stringlist(new_env); + } + else { + // nothing changed + ret = stringlist_alloc(envc+1); + for ( int i = 0; i < envc; i++ ) { + int len = strlen(envp[i]); + ret[i] = calloc(len+1, sizeof(char)); + strncpy(ret[i], envp[i], len); + } + } + environment_free(orig); + environment_free(startup); + environment_free(new_env); + free(appdir); + return ret; +} + +int execve(const char* filename, char* const argv[], char* const envp[]) { + char** new_envp = adjusted_environment(filename, envp); + old_execve = dlsym(RTLD_NEXT, "execve"); + int ret = old_execve(filename, argv, new_envp); + stringlist_free(new_envp); + //printf(">>> custom execve()!\n"); + return ret; +} + +int execv(const char* filename, char* const argv[]) { + char** new_envp = adjusted_environment(filename, environ); + old_execve = dlsym(RTLD_NEXT, "execve"); + int ret = old_execve(filename, argv, new_envp); + stringlist_free(new_envp); + //printf(">>> custom execv()!\n"); + return ret; +} + +int execvpe(const char* filename, char* const argv[], char* const envp[]) { + // TODO: might not be full path + char** new_envp = adjusted_environment(filename, envp); + old_execvpe = dlsym(RTLD_NEXT, "execvpe"); + int ret = old_execvpe(filename, argv, new_envp); + stringlist_free(new_envp); + //printf(">>> custom execvpe()!\n"); + return ret; +} + +int execvp(const char* filename, char* const argv[]) { + // TODO: might not be full path + char** new_envp = adjusted_environment(filename, environ); + old_execvpe = dlsym(RTLD_NEXT, "execvpe"); + int ret = old_execvpe(filename, argv, new_envp); + stringlist_free(new_envp); + //printf(">>> custom execvp()!\n"); + return ret; +}