/************************************************************************** * * Copyright (c) 2004-18 Simon Peter * Portions Copyright (c) 2007 Alexander Larsson * * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * **************************************************************************/ #ident "AppImage by Simon Peter, http://appimage.org/" #define _GNU_SOURCE #include "squashfuse.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef ENABLE_DLOPEN #define ENABLE_DLOPEN #endif #include "squashfuse_dlopen.h" /* Exit status to use when launching an AppImage fails. * For applications that assign meanings to exit status codes (e.g. rsync), * we avoid "cluttering" pre-defined exit status codes by using 127 which * is known to alias an application exit status and also known as launcher * error, see SYSTEM(3POSIX). */ #define EXIT_EXECERROR 127 /* Execution error exit status. */ //#include "notify.c" extern int notify(char *title, char *body, int timeout); struct stat st; static ssize_t fs_offset; // The offset at which a filesystem image is expected = end of this ELF static void die(const char *msg) { fprintf(stderr, "%s\n", msg); exit(EXIT_EXECERROR); } /* Check whether directory is writable */ bool is_writable_directory(char* str) { if(access(str, W_OK) == 0) { return true; } else { return false; } } bool startsWith(const char *pre, const char *str) { size_t lenpre = strlen(pre), lenstr = strlen(str); return lenstr < lenpre ? false : strncmp(pre, str, lenpre) == 0; } /* Fill in a stat structure. Does not set st_ino */ sqfs_err private_sqfs_stat(sqfs *fs, sqfs_inode *inode, struct stat *st) { sqfs_err err = SQFS_OK; uid_t id; memset(st, 0, sizeof(*st)); st->st_mode = inode->base.mode; st->st_nlink = inode->nlink; st->st_mtime = st->st_ctime = st->st_atime = inode->base.mtime; if (S_ISREG(st->st_mode)) { /* FIXME: do symlinks, dirs, etc have a size? */ st->st_size = inode->xtra.reg.file_size; st->st_blocks = st->st_size / 512; } else if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) { st->st_rdev = sqfs_makedev(inode->xtra.dev.major, inode->xtra.dev.minor); } else if (S_ISLNK(st->st_mode)) { st->st_size = inode->xtra.symlink_size; } st->st_blksize = fs->sb.block_size; /* seriously? */ err = sqfs_id_get(fs, inode->base.uid, &id); if (err) return err; st->st_uid = id; err = sqfs_id_get(fs, inode->base.guid, &id); st->st_gid = id; if (err) return err; return SQFS_OK; } /* ================= End ELF parsing */ extern int fusefs_main(int argc, char *argv[], void (*mounted) (void)); // extern void ext2_quit(void); static pid_t fuse_pid; static int keepalive_pipe[2]; static void * write_pipe_thread (void *arg) { char c[32]; int res; // sprintf(stderr, "Called write_pipe_thread"); memset (c, 'x', sizeof (c)); while (1) { /* Write until we block, on broken pipe, exit */ res = write (keepalive_pipe[1], c, sizeof (c)); if (res == -1) { kill (fuse_pid, SIGTERM); break; } } return NULL; } void fuse_mounted (void) { pthread_t thread; fuse_pid = getpid(); pthread_create(&thread, NULL, write_pipe_thread, keepalive_pipe); } char* getArg(int argc, char *argv[],char chr) { int i; for (i=1; i sizeof(_path)-1) { errno = ENAMETOOLONG; return -1; } strcpy(_path, path); /* Iterate the string */ for (p = _path + 1; *p; p++) { if (*p == '/') { /* Temporarily truncate */ *p = '\0'; if (mkdir(_path, 0755) != 0) { if (errno != EEXIST) return -1; } *p = '/'; } } if (mkdir(_path, 0755) != 0) { if (errno != EEXIST) return -1; } return 0; } void print_help(const char *appimage_path) { // TODO: "--appimage-list List content from embedded filesystem image\n" fprintf(stderr, "AppImage options:\n\n" " --appimage-extract [] Extract content from embedded filesystem image\n" " If pattern is passed, only extract matching files\n" " --appimage-help Print this help\n" " --appimage-mount Mount embedded filesystem image and print\n" " mount point and wait for kill with Ctrl-C\n" " --appimage-offset Print byte offset to start of embedded\n" " filesystem image\n" " --appimage-portable-home Create a portable home folder to use as $HOME\n" " --appimage-portable-config Create a portable config folder to use as\n" " $XDG_CONFIG_HOME\n" " --appimage-signature Print digital signature embedded in AppImage\n" " --appimage-updateinfo[rmation] Print update info embedded in AppImage\n" "\n" "Portable home:\n" "\n" " If you would like the application contained inside this AppImage to store its\n" " data alongside this AppImage rather than in your home directory, then you can\n" " place a directory named\n" "\n" " %s.home\n" "\n" " Or you can invoke this AppImage with the --appimage-portable-home option,\n" " which will create this directory for you. As long as the directory exists\n" " and is neither moved nor renamed, the application contained inside this\n" " AppImage to store its data in this directory rather than in your home\n" " directory\n" , appimage_path); } void portable_option(const char *arg, const char *appimage_path, const char *name) { char option[32]; sprintf(option, "appimage-portable-%s", name); if (arg && strcmp(arg, option)==0) { char portable_dir[PATH_MAX]; char fullpath[PATH_MAX]; ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath)); if (length < 0) { fprintf(stderr, "Error getting realpath for %s\n", appimage_path); exit(EXIT_FAILURE); } fullpath[length] = '\0'; sprintf(portable_dir, "%s.%s", fullpath, name); if (!mkdir(portable_dir, S_IRWXU)) fprintf(stderr, "Portable %s directory created at %s\n", name, portable_dir); else fprintf(stderr, "Error creating portable %s directory at %s: %s\n", name, portable_dir, strerror(errno)); exit(0); } } bool extract_appimage(const char* const appimage_path, const char* const _prefix, const char* const _pattern, const bool overwrite, const bool verbose) { sqfs_err err = SQFS_OK; sqfs_traverse trv; sqfs fs; char prefixed_path_to_extract[1024]; // local copy we can modify safely // allocate 1 more byte than we would need so we can add a trailing slash if there is none yet char* prefix = malloc(strlen(_prefix) + 2); strcpy(prefix, _prefix); // sanitize prefix if (prefix[strlen(prefix) - 1] != '/') strcat(prefix, "/"); if (access(prefix, F_OK) == -1) { if (mkdir_p(prefix) == -1) { perror("mkdir_p error"); return false; } } if ((err = sqfs_open_image(&fs, appimage_path, (size_t) fs_offset))) { fprintf(stderr, "Failed to open squashfs image\n"); return false; }; // track duplicate inodes for hardlinks char** created_inode = calloc(fs.sb.inodes, sizeof(char*)); if (created_inode == NULL) { fprintf(stderr, "Failed allocating memory to track hardlinks\n"); return false; } if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs)))) { fprintf(stderr, "sqfs_traverse_open error\n"); free(created_inode); return false; } bool rv = true; while (sqfs_traverse_next(&trv, &err)) { if (!trv.dir_end) { if (_pattern == NULL || fnmatch(_pattern, trv.path, FNM_FILE_NAME | FNM_LEADING_DIR) == 0) { // fprintf(stderr, "trv.path: %s\n", trv.path); // fprintf(stderr, "sqfs_inode_id: %lu\n", trv.entry.inode); sqfs_inode inode; if (sqfs_inode_get(&fs, &inode, trv.entry.inode)) { fprintf(stderr, "sqfs_inode_get error\n"); rv = false; break; } // fprintf(stderr, "inode.base.inode_type: %i\n", inode.base.inode_type); // fprintf(stderr, "inode.xtra.reg.file_size: %lu\n", inode.xtra.reg.file_size); strcpy(prefixed_path_to_extract, ""); strcat(strcat(prefixed_path_to_extract, prefix), trv.path); if (verbose) fprintf(stdout, "%s\n", prefixed_path_to_extract); if (inode.base.inode_type == SQUASHFS_DIR_TYPE || inode.base.inode_type == SQUASHFS_LDIR_TYPE) { // fprintf(stderr, "inode.xtra.dir.parent_inode: %ui\n", inode.xtra.dir.parent_inode); // fprintf(stderr, "mkdir_p: %s/\n", prefixed_path_to_extract); if (access(prefixed_path_to_extract, F_OK) == -1) { if (mkdir_p(prefixed_path_to_extract) == -1) { perror("mkdir_p error"); rv = false; break; } } } else if (inode.base.inode_type == SQUASHFS_REG_TYPE || inode.base.inode_type == SQUASHFS_LREG_TYPE) { // if we've already created this inode, then this is a hardlink char* existing_path_for_inode = created_inode[inode.base.inode_number - 1]; if (existing_path_for_inode != NULL) { unlink(prefixed_path_to_extract); if (link(existing_path_for_inode, prefixed_path_to_extract) == -1) { fprintf(stderr, "Couldn't create hardlink from \"%s\" to \"%s\": %s\n", prefixed_path_to_extract, existing_path_for_inode, strerror(errno)); rv = false; break; } else { continue; } } else { struct stat st; if (!overwrite && stat(prefixed_path_to_extract, &st) == 0 && st.st_size == inode.xtra.reg.file_size) { fprintf(stderr, "File exists and file size matches, skipping\n"); continue; } // track the path we extract to for this inode, so that we can `link` if this inode is found again created_inode[inode.base.inode_number - 1] = strdup(prefixed_path_to_extract); // fprintf(stderr, "Extract to: %s\n", prefixed_path_to_extract); if (private_sqfs_stat(&fs, &inode, &st) != 0) die("private_sqfs_stat error"); // create parent dir char* p = strrchr(prefixed_path_to_extract, '/'); if (p) { // set an \0 to end the split the string *p = '\0'; mkdir_p(prefixed_path_to_extract); // restore dir seprator *p = '/'; } // Read the file in chunks off_t bytes_already_read = 0; sqfs_off_t bytes_at_a_time = 64 * 1024; FILE* f; f = fopen(prefixed_path_to_extract, "w+"); if (f == NULL) { perror("fopen error"); rv = false; break; } while (bytes_already_read < inode.xtra.reg.file_size) { char buf[bytes_at_a_time]; if (sqfs_read_range(&fs, &inode, (sqfs_off_t) bytes_already_read, &bytes_at_a_time, buf)) { perror("sqfs_read_range error"); rv = false; break; } // fwrite(buf, 1, bytes_at_a_time, stdout); fwrite(buf, 1, bytes_at_a_time, f); bytes_already_read = bytes_already_read + bytes_at_a_time; } fclose(f); chmod(prefixed_path_to_extract, st.st_mode); if (!rv) break; } } else if (inode.base.inode_type == SQUASHFS_SYMLINK_TYPE || inode.base.inode_type == SQUASHFS_LSYMLINK_TYPE) { size_t size; sqfs_readlink(&fs, &inode, NULL, &size); char buf[size]; int ret = sqfs_readlink(&fs, &inode, buf, &size); if (ret != 0) { perror("symlink error"); rv = false; break; } // fprintf(stderr, "Symlink: %s to %s \n", prefixed_path_to_extract, buf); unlink(prefixed_path_to_extract); ret = symlink(buf, prefixed_path_to_extract); if (ret != 0) fprintf(stderr, "WARNING: could not create symlink\n"); } else { fprintf(stderr, "TODO: Implement inode.base.inode_type %i\n", inode.base.inode_type); } // fprintf(stderr, "\n"); if (!rv) break; } } } for (int i = 0; i < fs.sb.inodes; i++) { free(created_inode[i]); } free(created_inode); if (err != SQFS_OK) { fprintf(stderr, "sqfs_traverse_next error\n"); rv = false; } sqfs_traverse_close(&trv); sqfs_fd_close(fs.fd); return rv; } int rm_recursive_callback(const char* path, const struct stat* stat, const int type, struct FTW* ftw) { (void) stat; (void) ftw; switch (type) { case FTW_NS: case FTW_DNR: fprintf(stderr, "%s: ftw error: %s\n", path, strerror(errno)); return 1; case FTW_D: // ignore directories at first, will be handled by FTW_DP break; case FTW_F: case FTW_SL: case FTW_SLN: if (remove(path) != 0) { fprintf(stderr, "Failed to remove %s: %s\n", path, strerror(errno)); return false; } break; case FTW_DP: if (rmdir(path) != 0) { fprintf(stderr, "Failed to remove directory %s: %s\n", path, strerror(errno)); return false; } break; default: fprintf(stderr, "Unexpected fts_info\n"); return 1; } return 0; }; bool rm_recursive(const char* const path) { // FTW_DEPTH: perform depth-first search to make sure files are deleted before the containing directories // FTW_MOUNT: prevent deletion of files on other mounted filesystems // FTW_PHYS: do not follow symlinks, but report symlinks as such; this way, the symlink targets, which might point // to locations outside path will not be deleted accidentally (attackers might abuse this) int rv = nftw(path, &rm_recursive_callback, 0, FTW_DEPTH | FTW_MOUNT | FTW_PHYS); return rv == 0; } bool build_mount_point(char* mount_dir, const char* const argv0, char const* const temp_base, const size_t templen) { const size_t maxnamelen = 6; // when running for another AppImage, we should use that for building the mountpoint name instead char* target_appimage = getenv("TARGET_APPIMAGE"); char* path_basename; if (target_appimage != NULL) { path_basename = basename(target_appimage); } else { path_basename = basename(argv0); } size_t namelen = strlen(path_basename); // limit length of tempdir name if (namelen > maxnamelen) { namelen = maxnamelen; } strcpy(mount_dir, temp_base); strncpy(mount_dir + templen, "/.mount_", 8); strncpy(mount_dir + templen + 8, path_basename, namelen); strncpy(mount_dir+templen+8+namelen, "XXXXXX", 6); mount_dir[templen+8+namelen+6] = 0; // null terminate destination } int main(int argc, char *argv[]) { char appimage_path[PATH_MAX]; char argv0_path[PATH_MAX]; char * arg; /* We might want to operate on a target appimage rather than this file itself, * e.g., for appimaged which must not run untrusted code from random AppImages. * This variable is intended for use by e.g., appimaged and is subject to * change any time. Do not rely on it being present. We might even limit this * functionality specifically for builds used by appimaged. */ if (getenv("TARGET_APPIMAGE") == NULL) { strcpy(appimage_path, "/proc/self/exe"); strcpy(argv0_path, argv[0]); } else { strcpy(appimage_path, getenv("TARGET_APPIMAGE")); strcpy(argv0_path, getenv("TARGET_APPIMAGE")); #ifdef ENABLE_SETPROCTITLE // load libbsd dynamically to change proc title // this is an optional feature, therefore we don't hard require it void* libbsd = dlopen("libbsd.so", RTLD_NOW); if (libbsd != NULL) { // clear error state dlerror(); // try to load the two required symbols void (*setproctitle_init)(int, char**, char**) = dlsym(libbsd, "setproctitle_init"); char* error; if ((error = dlerror()) == NULL) { void (*setproctitle)(const char*, char*) = dlsym(libbsd, "setproctitle"); if (dlerror() == NULL) { char buffer[1024]; strcpy(buffer, getenv("TARGET_APPIMAGE")); for (int i = 1; i < argc; i++) { strcat(buffer, " "); strcat(buffer, argv[i]); } (*setproctitle_init)(argc, argv, environ); (*setproctitle)("%s", buffer); } } dlclose(libbsd); } #endif } // temporary directories are required in a few places // therefore we implement the detection of the temp base dir at the top of the code to avoid redundancy char temp_base[PATH_MAX] = P_tmpdir; { const char* const TMPDIR = getenv("TMPDIR"); if (TMPDIR != NULL) strcpy(temp_base, getenv("TMPDIR")); } fs_offset = appimage_get_elf_size(appimage_path); // error check if (fs_offset < 0) { fprintf(stderr, "Failed to get fs offset for %s\n", appimage_path); exit(EXIT_EXECERROR); } arg=getArg(argc,argv,'-'); /* Print the help and then exit */ if(arg && strcmp(arg,"appimage-help")==0) { char fullpath[PATH_MAX]; ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath)); if (length < 0) { fprintf(stderr, "Error getting realpath for %s\n", appimage_path); exit(EXIT_EXECERROR); } fullpath[length] = '\0'; print_help(fullpath); exit(0); } /* Just print the offset and then exit */ if(arg && strcmp(arg,"appimage-offset")==0) { printf("%lu\n", fs_offset); exit(0); } arg=getArg(argc,argv,'-'); /* extract the AppImage */ if(arg && strcmp(arg,"appimage-extract")==0) { char* pattern; // default use case: use standard prefix if (argc == 2) { pattern = NULL; } else if (argc == 3) { pattern = argv[2]; } else { fprintf(stderr, "Unexpected argument count: %d\n", argc - 1); fprintf(stderr, "Usage: %s --appimage-extract []\n", argv0_path); exit(1); } if (!extract_appimage(appimage_path, "squashfs-root/", pattern, true, true)) { exit(1); } exit(0); } // calculate full path of AppImage int length; char fullpath[PATH_MAX]; if(getenv("TARGET_APPIMAGE") == NULL) { // If we are operating on this file itself ssize_t len = readlink(appimage_path, fullpath, sizeof(fullpath)); if (len < 0) { perror("Failed to obtain absolute path"); exit(EXIT_EXECERROR); } fullpath[len] = '\0'; } else { char* abspath = realpath(appimage_path, NULL); if (abspath == NULL) { perror("Failed to obtain absolute path"); exit(EXIT_EXECERROR); } strcpy(fullpath, abspath); free(abspath); } if (getenv("APPIMAGE_EXTRACT_AND_RUN") != NULL || (arg && strcmp(arg, "appimage-extract-and-run") == 0)) { char* hexlified_digest = NULL; // calculate MD5 hash of file, and use it to make extracted directory name "content-aware" // see https://github.com/AppImage/AppImageKit/issues/841 for more information { FILE* f = fopen(appimage_path, "rb"); if (f == NULL) { perror("Failed to open AppImage file"); exit(EXIT_EXECERROR); } Md5Context ctx; Md5Initialise(&ctx); char buf[4096]; for (size_t bytes_read; (bytes_read = fread(buf, sizeof(char), sizeof(buf), f)); bytes_read > 0) { Md5Update(&ctx, buf, (uint32_t) bytes_read); } MD5_HASH digest; Md5Finalise(&ctx, &digest); hexlified_digest = appimage_hexlify(digest.bytes, sizeof(digest.bytes)); } char* prefix = malloc(strlen(temp_base) + 20 + strlen(hexlified_digest) + 2); strcpy(prefix, temp_base); strcat(prefix, "/appimage_extracted_"); strcat(prefix, hexlified_digest); free(hexlified_digest); const bool verbose = (getenv("VERBOSE") != NULL); if (!extract_appimage(appimage_path, prefix, NULL, false, verbose)) { fprintf(stderr, "Failed to extract AppImage\n"); exit(EXIT_EXECERROR); } int pid; if ((pid = fork()) == -1) { int error = errno; fprintf(stderr, "fork() failed: %s\n", strerror(error)); exit(EXIT_EXECERROR); } else if (pid == 0) { const char apprun_fname[] = "AppRun"; char* apprun_path = malloc(strlen(prefix) + 1 + strlen(apprun_fname) + 1); strcpy(apprun_path, prefix); strcat(apprun_path, "/"); strcat(apprun_path, apprun_fname); // create copy of argument list without the --appimage-extract-and-run parameter char* new_argv[argc]; int new_argc = 0; new_argv[new_argc++] = strdup(apprun_path); for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "--appimage-extract-and-run") != 0) { new_argv[new_argc++] = strdup(argv[i]); } } new_argv[new_argc] = NULL; /* Setting some environment variables that the app "inside" might use */ setenv("APPIMAGE", fullpath, 1); setenv("ARGV0", argv0_path, 1); setenv("APPDIR", prefix, 1); execv(apprun_path, new_argv); int error = errno; fprintf(stderr, "Failed to run %s: %s\n", apprun_path, strerror(error)); free(apprun_path); exit(EXIT_EXECERROR); } int status = 0; int rv = waitpid(pid, &status, 0); status = rv > 0 && WIFEXITED (status) ? WEXITSTATUS (status) : EXIT_EXECERROR; if (getenv("NO_CLEANUP") == NULL) { if (!rm_recursive(prefix)) { fprintf(stderr, "Failed to clean up cache directory\n"); if (status == 0) /* avoid messing existing failure exit status */ status = EXIT_EXECERROR; } } // template == prefix, must be freed only once free(prefix); exit(status); } if(arg && (strcmp(arg,"appimage-updateinformation")==0 || strcmp(arg,"appimage-updateinfo")==0)) { unsigned long offset = 0; unsigned long length = 0; appimage_get_elf_section_offset_and_length(appimage_path, ".upd_info", &offset, &length); // fprintf(stderr, "offset: %lu\n", offset); // fprintf(stderr, "length: %lu\n", length); // print_hex(appimage_path, offset, length); appimage_print_binary(appimage_path, offset, length); exit(0); } if(arg && strcmp(arg,"appimage-signature")==0) { unsigned long offset = 0; unsigned long length = 0; appimage_get_elf_section_offset_and_length(appimage_path, ".sha256_sig", &offset, &length); // fprintf(stderr, "offset: %lu\n", offset); // fprintf(stderr, "length: %lu\n", length); // print_hex(appimage_path, offset, length); appimage_print_binary(appimage_path, offset, length); exit(0); } portable_option(arg, appimage_path, "home"); portable_option(arg, appimage_path, "config"); // If there is an argument starting with appimage- (but not appimage-mount which is handled further down) // then stop here and print an error message if((arg && strncmp(arg, "appimage-", 8) == 0) && (arg && strcmp(arg,"appimage-mount")!=0)) { fprintf(stderr,"--%s is not yet implemented\n", arg); exit(1); } LOAD_LIBRARY; /* exit if libfuse is missing */ int dir_fd, res; size_t templen = strlen(temp_base); // allocate enough memory (size of name won't exceed 60 bytes) char mount_dir[templen + 60]; build_mount_point(mount_dir, argv[0], temp_base, templen); size_t mount_dir_size = strlen(mount_dir); pid_t pid; char **real_argv; int i; if (mkdtemp(mount_dir) == NULL) { perror ("create mount dir error"); exit (EXIT_EXECERROR); } if (pipe (keepalive_pipe) == -1) { perror ("pipe error"); exit (EXIT_EXECERROR); } pid = fork (); if (pid == -1) { perror ("fork error"); exit (EXIT_EXECERROR); } if (pid == 0) { /* in child */ char *child_argv[5]; /* close read pipe */ close (keepalive_pipe[0]); char *dir = realpath(appimage_path, NULL ); char options[100]; sprintf(options, "ro,offset=%lu", fs_offset); child_argv[0] = dir; child_argv[1] = "-o"; child_argv[2] = options; child_argv[3] = dir; child_argv[4] = mount_dir; if(0 != fusefs_main (5, child_argv, fuse_mounted)){ char *title; char *body; title = "Cannot mount AppImage, please check your FUSE setup."; body = "You might still be able to extract the contents of this AppImage \n" "if you run it with the --appimage-extract option. \n" "See https://github.com/AppImage/AppImageKit/wiki/FUSE \n" "for more information"; notify(title, body, 0); // 3 seconds timeout }; } else { /* in parent, child is $pid */ int c; /* close write pipe */ close (keepalive_pipe[1]); /* Pause until mounted */ read (keepalive_pipe[0], &c, 1); /* Fuse process has now daemonized, reap our child */ waitpid(pid, NULL, 0); dir_fd = open (mount_dir, O_RDONLY); if (dir_fd == -1) { perror ("open dir error"); exit (EXIT_EXECERROR); } res = dup2 (dir_fd, 1023); if (res == -1) { perror ("dup2 error"); exit (EXIT_EXECERROR); } close (dir_fd); real_argv = malloc (sizeof (char *) * (argc + 1)); for (i = 0; i < argc; i++) { real_argv[i] = argv[i]; } real_argv[i] = NULL; if(arg && strcmp(arg, "appimage-mount") == 0) { char real_mount_dir[PATH_MAX]; if (realpath(mount_dir, real_mount_dir) == real_mount_dir) { printf("%s\n", real_mount_dir); } else { printf("%s\n", mount_dir); } // stdout is, by default, buffered (unlike stderr), therefore in order to allow other processes to read // the path from stdout, we need to flush the buffers now // this is a less-invasive alternative to setbuf(stdout, NULL); fflush(stdout); for (;;) pause(); exit(0); } /* Setting some environment variables that the app "inside" might use */ setenv( "APPIMAGE", fullpath, 1 ); setenv( "ARGV0", argv0_path, 1 ); setenv( "APPDIR", mount_dir, 1 ); char portable_home_dir[PATH_MAX]; char portable_config_dir[PATH_MAX]; /* If there is a directory with the same name as the AppImage plus ".home", then export $HOME */ strcpy (portable_home_dir, fullpath); strcat (portable_home_dir, ".home"); if(is_writable_directory(portable_home_dir)){ fprintf(stderr, "Setting $HOME to %s\n", portable_home_dir); setenv("HOME",portable_home_dir,1); } /* If there is a directory with the same name as the AppImage plus ".config", then export $XDG_CONFIG_HOME */ strcpy (portable_config_dir, fullpath); strcat (portable_config_dir, ".config"); if(is_writable_directory(portable_config_dir)){ fprintf(stderr, "Setting $XDG_CONFIG_HOME to %s\n", portable_config_dir); setenv("XDG_CONFIG_HOME",portable_config_dir,1); } /* Original working directory */ char cwd[1024]; if (getcwd(cwd, sizeof(cwd)) != NULL) { setenv( "OWD", cwd, 1 ); } char filename[mount_dir_size + 8]; /* enough for mount_dir + "/AppRun" */ strcpy (filename, mount_dir); strcat (filename, "/AppRun"); /* TODO: Find a way to get the exit status and/or output of this */ execv (filename, real_argv); /* Error if we continue here */ perror("execv error"); exit(EXIT_EXECERROR); } return 0; }