Update README.md
[appimagekit.git] / src / runtime.c
blobfa0aa7d31ed8ecd2343bfd1192915f8e677c4cd8
1 /**************************************************************************
3 * Copyright (c) 2004-18 Simon Peter
4 * Portions Copyright (c) 2007 Alexander Larsson
6 * All Rights Reserved.
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
26 **************************************************************************/
28 #ident "AppImage by Simon Peter, http://appimage.org/"
30 #define _GNU_SOURCE
32 #include "squashfuse.h"
33 #include <squashfs_fs.h>
34 #include <nonstd.h>
36 #include <limits.h>
37 #include <stdlib.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <fcntl.h>
41 #include <ftw.h>
42 #include <stdio.h>
43 #include <signal.h>
44 #include <string.h>
45 #include <unistd.h>
46 #include <pthread.h>
47 #include <errno.h>
48 #include <wait.h>
49 #include <fnmatch.h>
51 #include <appimage/appimage_shared.h>
52 #include <hashlib.h>
54 #ifndef ENABLE_DLOPEN
55 #define ENABLE_DLOPEN
56 #endif
57 #include "squashfuse_dlopen.h"
59 /* Exit status to use when launching an AppImage fails.
60 * For applications that assign meanings to exit status codes (e.g. rsync),
61 * we avoid "cluttering" pre-defined exit status codes by using 127 which
62 * is known to alias an application exit status and also known as launcher
63 * error, see SYSTEM(3POSIX).
65 #define EXIT_EXECERROR 127 /* Execution error exit status. */
67 //#include "notify.c"
68 extern int notify(char *title, char *body, int timeout);
69 struct stat st;
71 static ssize_t fs_offset; // The offset at which a filesystem image is expected = end of this ELF
73 static void die(const char *msg) {
74 fprintf(stderr, "%s\n", msg);
75 exit(EXIT_EXECERROR);
78 /* Check whether directory is writable */
79 bool is_writable_directory(char* str) {
80 if(access(str, W_OK) == 0) {
81 return true;
82 } else {
83 return false;
87 bool startsWith(const char *pre, const char *str)
89 size_t lenpre = strlen(pre),
90 lenstr = strlen(str);
91 return lenstr < lenpre ? false : strncmp(pre, str, lenpre) == 0;
94 /* Fill in a stat structure. Does not set st_ino */
95 sqfs_err private_sqfs_stat(sqfs *fs, sqfs_inode *inode, struct stat *st) {
96 sqfs_err err = SQFS_OK;
97 uid_t id;
99 memset(st, 0, sizeof(*st));
100 st->st_mode = inode->base.mode;
101 st->st_nlink = inode->nlink;
102 st->st_mtime = st->st_ctime = st->st_atime = inode->base.mtime;
104 if (S_ISREG(st->st_mode)) {
105 /* FIXME: do symlinks, dirs, etc have a size? */
106 st->st_size = inode->xtra.reg.file_size;
107 st->st_blocks = st->st_size / 512;
108 } else if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) {
109 st->st_rdev = sqfs_makedev(inode->xtra.dev.major,
110 inode->xtra.dev.minor);
111 } else if (S_ISLNK(st->st_mode)) {
112 st->st_size = inode->xtra.symlink_size;
115 st->st_blksize = fs->sb.block_size; /* seriously? */
117 err = sqfs_id_get(fs, inode->base.uid, &id);
118 if (err)
119 return err;
120 st->st_uid = id;
121 err = sqfs_id_get(fs, inode->base.guid, &id);
122 st->st_gid = id;
123 if (err)
124 return err;
126 return SQFS_OK;
129 /* ================= End ELF parsing */
131 extern int fusefs_main(int argc, char *argv[], void (*mounted) (void));
132 // extern void ext2_quit(void);
134 static pid_t fuse_pid;
135 static int keepalive_pipe[2];
137 static void *
138 write_pipe_thread (void *arg)
140 char c[32];
141 int res;
142 // sprintf(stderr, "Called write_pipe_thread");
143 memset (c, 'x', sizeof (c));
144 while (1) {
145 /* Write until we block, on broken pipe, exit */
146 res = write (keepalive_pipe[1], c, sizeof (c));
147 if (res == -1) {
148 kill (fuse_pid, SIGTERM);
149 break;
152 return NULL;
155 void
156 fuse_mounted (void)
158 pthread_t thread;
159 fuse_pid = getpid();
160 pthread_create(&thread, NULL, write_pipe_thread, keepalive_pipe);
163 char* getArg(int argc, char *argv[],char chr)
165 int i;
166 for (i=1; i<argc; ++i)
167 if ((argv[i][0]=='-') && (argv[i][1]==chr))
168 return &(argv[i][2]);
169 return NULL;
172 /* mkdir -p implemented in C, needed for https://github.com/AppImage/AppImageKit/issues/333
173 * https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950 */
175 mkdir_p(const char* const path)
177 /* Adapted from http://stackoverflow.com/a/2336245/119527 */
178 const size_t len = strlen(path);
179 char _path[PATH_MAX];
180 char *p;
182 errno = 0;
184 /* Copy string so its mutable */
185 if (len > sizeof(_path)-1) {
186 errno = ENAMETOOLONG;
187 return -1;
189 strcpy(_path, path);
191 /* Iterate the string */
192 for (p = _path + 1; *p; p++) {
193 if (*p == '/') {
194 /* Temporarily truncate */
195 *p = '\0';
197 if (mkdir(_path, 0755) != 0) {
198 if (errno != EEXIST)
199 return -1;
202 *p = '/';
206 if (mkdir(_path, 0755) != 0) {
207 if (errno != EEXIST)
208 return -1;
211 return 0;
214 void
215 print_help(const char *appimage_path)
217 // TODO: "--appimage-list List content from embedded filesystem image\n"
218 fprintf(stderr,
219 "AppImage options:\n\n"
220 " --appimage-extract [<pattern>] Extract content from embedded filesystem image\n"
221 " If pattern is passed, only extract matching files\n"
222 " --appimage-help Print this help\n"
223 " --appimage-mount Mount embedded filesystem image and print\n"
224 " mount point and wait for kill with Ctrl-C\n"
225 " --appimage-offset Print byte offset to start of embedded\n"
226 " filesystem image\n"
227 " --appimage-portable-home Create a portable home folder to use as $HOME\n"
228 " --appimage-portable-config Create a portable config folder to use as\n"
229 " $XDG_CONFIG_HOME\n"
230 " --appimage-signature Print digital signature embedded in AppImage\n"
231 " --appimage-updateinfo[rmation] Print update info embedded in AppImage\n"
232 " --appimage-version Print version of AppImageKit\n"
233 "\n"
234 "Portable home:\n"
235 "\n"
236 " If you would like the application contained inside this AppImage to store its\n"
237 " data alongside this AppImage rather than in your home directory, then you can\n"
238 " place a directory named\n"
239 "\n"
240 " %s.home\n"
241 "\n"
242 " Or you can invoke this AppImage with the --appimage-portable-home option,\n"
243 " which will create this directory for you. As long as the directory exists\n"
244 " and is neither moved nor renamed, the application contained inside this\n"
245 " AppImage will store its data in this directory rather than in your home\n"
246 " directory\n"
247 , appimage_path);
250 void
251 portable_option(const char *arg, const char *appimage_path, const char *name)
253 char option[32];
254 sprintf(option, "appimage-portable-%s", name);
256 if (arg && strcmp(arg, option)==0) {
257 char portable_dir[PATH_MAX];
258 char fullpath[PATH_MAX];
260 ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath));
261 if (length < 0) {
262 fprintf(stderr, "Error getting realpath for %s\n", appimage_path);
263 exit(EXIT_FAILURE);
265 fullpath[length] = '\0';
267 sprintf(portable_dir, "%s.%s", fullpath, name);
268 if (!mkdir(portable_dir, S_IRWXU))
269 fprintf(stderr, "Portable %s directory created at %s\n", name, portable_dir);
270 else
271 fprintf(stderr, "Error creating portable %s directory at %s: %s\n", name, portable_dir, strerror(errno));
273 exit(0);
277 bool extract_appimage(const char* const appimage_path, const char* const _prefix, const char* const _pattern, const bool overwrite, const bool verbose) {
278 sqfs_err err = SQFS_OK;
279 sqfs_traverse trv;
280 sqfs fs;
281 char prefixed_path_to_extract[1024];
283 // local copy we can modify safely
284 // allocate 1 more byte than we would need so we can add a trailing slash if there is none yet
285 char* prefix = malloc(strlen(_prefix) + 2);
286 strcpy(prefix, _prefix);
288 // sanitize prefix
289 if (prefix[strlen(prefix) - 1] != '/')
290 strcat(prefix, "/");
292 if (access(prefix, F_OK) == -1) {
293 if (mkdir_p(prefix) == -1) {
294 perror("mkdir_p error");
295 return false;
299 if ((err = sqfs_open_image(&fs, appimage_path, (size_t) fs_offset))) {
300 fprintf(stderr, "Failed to open squashfs image\n");
301 return false;
304 // track duplicate inodes for hardlinks
305 char** created_inode = calloc(fs.sb.inodes, sizeof(char*));
306 if (created_inode == NULL) {
307 fprintf(stderr, "Failed allocating memory to track hardlinks\n");
308 return false;
311 if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs)))) {
312 fprintf(stderr, "sqfs_traverse_open error\n");
313 free(created_inode);
314 return false;
317 bool rv = true;
319 while (sqfs_traverse_next(&trv, &err)) {
320 if (!trv.dir_end) {
321 if (_pattern == NULL || fnmatch(_pattern, trv.path, FNM_FILE_NAME | FNM_LEADING_DIR) == 0) {
322 // fprintf(stderr, "trv.path: %s\n", trv.path);
323 // fprintf(stderr, "sqfs_inode_id: %lu\n", trv.entry.inode);
324 sqfs_inode inode;
325 if (sqfs_inode_get(&fs, &inode, trv.entry.inode)) {
326 fprintf(stderr, "sqfs_inode_get error\n");
327 rv = false;
328 break;
330 // fprintf(stderr, "inode.base.inode_type: %i\n", inode.base.inode_type);
331 // fprintf(stderr, "inode.xtra.reg.file_size: %lu\n", inode.xtra.reg.file_size);
332 strcpy(prefixed_path_to_extract, "");
333 strcat(strcat(prefixed_path_to_extract, prefix), trv.path);
335 if (verbose)
336 fprintf(stdout, "%s\n", prefixed_path_to_extract);
338 if (inode.base.inode_type == SQUASHFS_DIR_TYPE || inode.base.inode_type == SQUASHFS_LDIR_TYPE) {
339 // fprintf(stderr, "inode.xtra.dir.parent_inode: %ui\n", inode.xtra.dir.parent_inode);
340 // fprintf(stderr, "mkdir_p: %s/\n", prefixed_path_to_extract);
341 if (access(prefixed_path_to_extract, F_OK) == -1) {
342 if (mkdir_p(prefixed_path_to_extract) == -1) {
343 perror("mkdir_p error");
344 rv = false;
345 break;
348 } else if (inode.base.inode_type == SQUASHFS_REG_TYPE || inode.base.inode_type == SQUASHFS_LREG_TYPE) {
349 // if we've already created this inode, then this is a hardlink
350 char* existing_path_for_inode = created_inode[inode.base.inode_number - 1];
351 if (existing_path_for_inode != NULL) {
352 unlink(prefixed_path_to_extract);
353 if (link(existing_path_for_inode, prefixed_path_to_extract) == -1) {
354 fprintf(stderr, "Couldn't create hardlink from \"%s\" to \"%s\": %s\n",
355 prefixed_path_to_extract, existing_path_for_inode, strerror(errno));
356 rv = false;
357 break;
358 } else {
359 continue;
361 } else {
362 struct stat st;
363 if (!overwrite && stat(prefixed_path_to_extract, &st) == 0 && st.st_size == inode.xtra.reg.file_size) {
364 fprintf(stderr, "File exists and file size matches, skipping\n");
365 continue;
368 // track the path we extract to for this inode, so that we can `link` if this inode is found again
369 created_inode[inode.base.inode_number - 1] = strdup(prefixed_path_to_extract);
370 // fprintf(stderr, "Extract to: %s\n", prefixed_path_to_extract);
371 if (private_sqfs_stat(&fs, &inode, &st) != 0)
372 die("private_sqfs_stat error");
374 // create parent dir
375 char* p = strrchr(prefixed_path_to_extract, '/');
376 if (p) {
377 // set an \0 to end the split the string
378 *p = '\0';
379 mkdir_p(prefixed_path_to_extract);
381 // restore dir seprator
382 *p = '/';
385 // Read the file in chunks
386 off_t bytes_already_read = 0;
387 sqfs_off_t bytes_at_a_time = 64 * 1024;
388 FILE* f;
389 f = fopen(prefixed_path_to_extract, "w+");
390 if (f == NULL) {
391 perror("fopen error");
392 rv = false;
393 break;
395 while (bytes_already_read < inode.xtra.reg.file_size) {
396 char buf[bytes_at_a_time];
397 if (sqfs_read_range(&fs, &inode, (sqfs_off_t) bytes_already_read, &bytes_at_a_time, buf)) {
398 perror("sqfs_read_range error");
399 rv = false;
400 break;
402 // fwrite(buf, 1, bytes_at_a_time, stdout);
403 fwrite(buf, 1, bytes_at_a_time, f);
404 bytes_already_read = bytes_already_read + bytes_at_a_time;
406 fclose(f);
407 chmod(prefixed_path_to_extract, st.st_mode);
408 if (!rv)
409 break;
411 } else if (inode.base.inode_type == SQUASHFS_SYMLINK_TYPE || inode.base.inode_type == SQUASHFS_LSYMLINK_TYPE) {
412 size_t size;
413 sqfs_readlink(&fs, &inode, NULL, &size);
414 char buf[size];
415 int ret = sqfs_readlink(&fs, &inode, buf, &size);
416 if (ret != 0) {
417 perror("symlink error");
418 rv = false;
419 break;
421 // fprintf(stderr, "Symlink: %s to %s \n", prefixed_path_to_extract, buf);
422 unlink(prefixed_path_to_extract);
423 ret = symlink(buf, prefixed_path_to_extract);
424 if (ret != 0)
425 fprintf(stderr, "WARNING: could not create symlink\n");
426 } else {
427 fprintf(stderr, "TODO: Implement inode.base.inode_type %i\n", inode.base.inode_type);
429 // fprintf(stderr, "\n");
431 if (!rv)
432 break;
436 for (int i = 0; i < fs.sb.inodes; i++) {
437 free(created_inode[i]);
439 free(created_inode);
441 if (err != SQFS_OK) {
442 fprintf(stderr, "sqfs_traverse_next error\n");
443 rv = false;
445 sqfs_traverse_close(&trv);
446 sqfs_fd_close(fs.fd);
448 return rv;
451 int rm_recursive_callback(const char* path, const struct stat* stat, const int type, struct FTW* ftw) {
452 (void) stat;
453 (void) ftw;
455 switch (type) {
456 case FTW_NS:
457 case FTW_DNR:
458 fprintf(stderr, "%s: ftw error: %s\n",
459 path, strerror(errno));
460 return 1;
462 case FTW_D:
463 // ignore directories at first, will be handled by FTW_DP
464 break;
466 case FTW_F:
467 case FTW_SL:
468 case FTW_SLN:
469 if (remove(path) != 0) {
470 fprintf(stderr, "Failed to remove %s: %s\n", path, strerror(errno));
471 return false;
473 break;
476 case FTW_DP:
477 if (rmdir(path) != 0) {
478 fprintf(stderr, "Failed to remove directory %s: %s\n", path, strerror(errno));
479 return false;
481 break;
483 default:
484 fprintf(stderr, "Unexpected fts_info\n");
485 return 1;
488 return 0;
491 bool rm_recursive(const char* const path) {
492 // FTW_DEPTH: perform depth-first search to make sure files are deleted before the containing directories
493 // FTW_MOUNT: prevent deletion of files on other mounted filesystems
494 // FTW_PHYS: do not follow symlinks, but report symlinks as such; this way, the symlink targets, which might point
495 // to locations outside path will not be deleted accidentally (attackers might abuse this)
496 int rv = nftw(path, &rm_recursive_callback, 0, FTW_DEPTH | FTW_MOUNT | FTW_PHYS);
498 return rv == 0;
501 bool build_mount_point(char* mount_dir, const char* const argv0, char const* const temp_base, const size_t templen) {
502 const size_t maxnamelen = 6;
504 // when running for another AppImage, we should use that for building the mountpoint name instead
505 char* target_appimage = getenv("TARGET_APPIMAGE");
507 char* path_basename;
508 if (target_appimage != NULL) {
509 path_basename = basename(target_appimage);
510 } else {
511 path_basename = basename(argv0);
514 size_t namelen = strlen(path_basename);
515 // limit length of tempdir name
516 if (namelen > maxnamelen) {
517 namelen = maxnamelen;
520 strcpy(mount_dir, temp_base);
521 strncpy(mount_dir + templen, "/.mount_", 8);
522 strncpy(mount_dir + templen + 8, path_basename, namelen);
523 strncpy(mount_dir+templen+8+namelen, "XXXXXX", 6);
524 mount_dir[templen+8+namelen+6] = 0; // null terminate destination
527 void set_portable_home_and_config(char *basepath) {
528 char portable_home_dir[PATH_MAX];
529 char portable_config_dir[PATH_MAX];
531 /* If there is a directory with the same name as the AppImage plus ".home", then export $HOME */
532 strcpy (portable_home_dir, basepath);
533 strcat (portable_home_dir, ".home");
534 if(is_writable_directory(portable_home_dir)){
535 fprintf(stderr, "Setting $HOME to %s\n", portable_home_dir);
536 setenv("HOME",portable_home_dir,1);
539 /* If there is a directory with the same name as the AppImage plus ".config", then export $XDG_CONFIG_HOME */
540 strcpy (portable_config_dir, basepath);
541 strcat (portable_config_dir, ".config");
542 if(is_writable_directory(portable_config_dir)){
543 fprintf(stderr, "Setting $XDG_CONFIG_HOME to %s\n", portable_config_dir);
544 setenv("XDG_CONFIG_HOME",portable_config_dir,1);
548 int main(int argc, char *argv[]) {
549 char appimage_path[PATH_MAX];
550 char argv0_path[PATH_MAX];
551 char * arg;
553 /* We might want to operate on a target appimage rather than this file itself,
554 * e.g., for appimaged which must not run untrusted code from random AppImages.
555 * This variable is intended for use by e.g., appimaged and is subject to
556 * change any time. Do not rely on it being present. We might even limit this
557 * functionality specifically for builds used by appimaged.
559 if (getenv("TARGET_APPIMAGE") == NULL) {
560 strcpy(appimage_path, "/proc/self/exe");
561 strcpy(argv0_path, argv[0]);
562 } else {
563 strcpy(appimage_path, getenv("TARGET_APPIMAGE"));
564 strcpy(argv0_path, getenv("TARGET_APPIMAGE"));
566 #ifdef ENABLE_SETPROCTITLE
567 // load libbsd dynamically to change proc title
568 // this is an optional feature, therefore we don't hard require it
569 void* libbsd = dlopen("libbsd.so", RTLD_NOW);
571 if (libbsd != NULL) {
572 // clear error state
573 dlerror();
575 // try to load the two required symbols
576 void (*setproctitle_init)(int, char**, char**) = dlsym(libbsd, "setproctitle_init");
578 char* error;
580 if ((error = dlerror()) == NULL) {
581 void (*setproctitle)(const char*, char*) = dlsym(libbsd, "setproctitle");
583 if (dlerror() == NULL) {
584 char buffer[1024];
585 strcpy(buffer, getenv("TARGET_APPIMAGE"));
586 for (int i = 1; i < argc; i++) {
587 strcat(buffer, " ");
588 strcat(buffer, argv[i]);
591 (*setproctitle_init)(argc, argv, environ);
592 (*setproctitle)("%s", buffer);
596 dlclose(libbsd);
598 #endif
601 // temporary directories are required in a few places
602 // therefore we implement the detection of the temp base dir at the top of the code to avoid redundancy
603 char temp_base[PATH_MAX] = P_tmpdir;
606 const char* const TMPDIR = getenv("TMPDIR");
607 if (TMPDIR != NULL)
608 strcpy(temp_base, getenv("TMPDIR"));
611 fs_offset = appimage_get_elf_size(appimage_path);
613 // error check
614 if (fs_offset < 0) {
615 fprintf(stderr, "Failed to get fs offset for %s\n", appimage_path);
616 exit(EXIT_EXECERROR);
619 arg=getArg(argc,argv,'-');
621 /* Print the help and then exit */
622 if(arg && strcmp(arg,"appimage-help")==0) {
623 char fullpath[PATH_MAX];
625 ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath));
626 if (length < 0) {
627 fprintf(stderr, "Error getting realpath for %s\n", appimage_path);
628 exit(EXIT_EXECERROR);
630 fullpath[length] = '\0';
632 print_help(fullpath);
633 exit(0);
636 /* Just print the offset and then exit */
637 if(arg && strcmp(arg,"appimage-offset")==0) {
638 printf("%lu\n", fs_offset);
639 exit(0);
642 arg=getArg(argc,argv,'-');
644 /* extract the AppImage */
645 if(arg && strcmp(arg,"appimage-extract")==0) {
646 char* pattern;
648 // default use case: use standard prefix
649 if (argc == 2) {
650 pattern = NULL;
651 } else if (argc == 3) {
652 pattern = argv[2];
653 } else {
654 fprintf(stderr, "Unexpected argument count: %d\n", argc - 1);
655 fprintf(stderr, "Usage: %s --appimage-extract [<prefix>]\n", argv0_path);
656 exit(1);
659 if (!extract_appimage(appimage_path, "squashfs-root/", pattern, true, true)) {
660 exit(1);
663 exit(0);
666 // calculate full path of AppImage
667 int length;
668 char fullpath[PATH_MAX];
670 if(getenv("TARGET_APPIMAGE") == NULL) {
671 // If we are operating on this file itself
672 ssize_t len = readlink(appimage_path, fullpath, sizeof(fullpath));
673 if (len < 0) {
674 perror("Failed to obtain absolute path");
675 exit(EXIT_EXECERROR);
677 fullpath[len] = '\0';
678 } else {
679 char* abspath = realpath(appimage_path, NULL);
680 if (abspath == NULL) {
681 perror("Failed to obtain absolute path");
682 exit(EXIT_EXECERROR);
684 strcpy(fullpath, abspath);
685 free(abspath);
688 if (getenv("APPIMAGE_EXTRACT_AND_RUN") != NULL || (arg && strcmp(arg, "appimage-extract-and-run") == 0)) {
689 char* hexlified_digest = NULL;
691 // calculate MD5 hash of file, and use it to make extracted directory name "content-aware"
692 // see https://github.com/AppImage/AppImageKit/issues/841 for more information
694 FILE* f = fopen(appimage_path, "rb");
695 if (f == NULL) {
696 perror("Failed to open AppImage file");
697 exit(EXIT_EXECERROR);
700 Md5Context ctx;
701 Md5Initialise(&ctx);
703 char buf[4096];
704 for (size_t bytes_read; (bytes_read = fread(buf, sizeof(char), sizeof(buf), f)); bytes_read > 0) {
705 Md5Update(&ctx, buf, (uint32_t) bytes_read);
708 MD5_HASH digest;
709 Md5Finalise(&ctx, &digest);
711 hexlified_digest = appimage_hexlify(digest.bytes, sizeof(digest.bytes));
714 char* prefix = malloc(strlen(temp_base) + 20 + strlen(hexlified_digest) + 2);
715 strcpy(prefix, temp_base);
716 strcat(prefix, "/appimage_extracted_");
717 strcat(prefix, hexlified_digest);
718 free(hexlified_digest);
720 const bool verbose = (getenv("VERBOSE") != NULL);
722 if (!extract_appimage(appimage_path, prefix, NULL, false, verbose)) {
723 fprintf(stderr, "Failed to extract AppImage\n");
724 exit(EXIT_EXECERROR);
727 int pid;
728 if ((pid = fork()) == -1) {
729 int error = errno;
730 fprintf(stderr, "fork() failed: %s\n", strerror(error));
731 exit(EXIT_EXECERROR);
732 } else if (pid == 0) {
733 const char apprun_fname[] = "AppRun";
734 char* apprun_path = malloc(strlen(prefix) + 1 + strlen(apprun_fname) + 1);
735 strcpy(apprun_path, prefix);
736 strcat(apprun_path, "/");
737 strcat(apprun_path, apprun_fname);
739 // create copy of argument list without the --appimage-extract-and-run parameter
740 char* new_argv[argc];
741 int new_argc = 0;
742 new_argv[new_argc++] = strdup(apprun_path);
743 for (int i = 1; i < argc; ++i) {
744 if (strcmp(argv[i], "--appimage-extract-and-run") != 0) {
745 new_argv[new_argc++] = strdup(argv[i]);
748 new_argv[new_argc] = NULL;
750 /* Setting some environment variables that the app "inside" might use */
751 setenv("APPIMAGE", fullpath, 1);
752 setenv("ARGV0", argv0_path, 1);
753 setenv("APPDIR", prefix, 1);
755 set_portable_home_and_config(fullpath);
757 execv(apprun_path, new_argv);
759 int error = errno;
760 fprintf(stderr, "Failed to run %s: %s\n", apprun_path, strerror(error));
762 free(apprun_path);
763 exit(EXIT_EXECERROR);
766 int status = 0;
767 int rv = waitpid(pid, &status, 0);
768 status = rv > 0 && WIFEXITED (status) ? WEXITSTATUS (status) : EXIT_EXECERROR;
770 if (getenv("NO_CLEANUP") == NULL) {
771 if (!rm_recursive(prefix)) {
772 fprintf(stderr, "Failed to clean up cache directory\n");
773 if (status == 0) /* avoid messing existing failure exit status */
774 status = EXIT_EXECERROR;
778 // template == prefix, must be freed only once
779 free(prefix);
781 exit(status);
784 if(arg && strcmp(arg,"appimage-version")==0) {
785 fprintf(stderr,"Version: %s\n", GIT_COMMIT);
786 exit(0);
789 if(arg && (strcmp(arg,"appimage-updateinformation")==0 || strcmp(arg,"appimage-updateinfo")==0)) {
790 unsigned long offset = 0;
791 unsigned long length = 0;
792 appimage_get_elf_section_offset_and_length(appimage_path, ".upd_info", &offset, &length);
793 // fprintf(stderr, "offset: %lu\n", offset);
794 // fprintf(stderr, "length: %lu\n", length);
795 // print_hex(appimage_path, offset, length);
796 appimage_print_binary(appimage_path, offset, length);
797 exit(0);
800 if(arg && strcmp(arg,"appimage-signature")==0) {
801 unsigned long offset = 0;
802 unsigned long length = 0;
803 appimage_get_elf_section_offset_and_length(appimage_path, ".sha256_sig", &offset, &length);
804 // fprintf(stderr, "offset: %lu\n", offset);
805 // fprintf(stderr, "length: %lu\n", length);
806 // print_hex(appimage_path, offset, length);
807 appimage_print_binary(appimage_path, offset, length);
808 exit(0);
811 portable_option(arg, appimage_path, "home");
812 portable_option(arg, appimage_path, "config");
814 // If there is an argument starting with appimage- (but not appimage-mount which is handled further down)
815 // then stop here and print an error message
816 if((arg && strncmp(arg, "appimage-", 8) == 0) && (arg && strcmp(arg,"appimage-mount")!=0)) {
817 fprintf(stderr,"--%s is not yet implemented in version %s\n", arg, GIT_COMMIT);
818 exit(1);
821 LOAD_LIBRARY; /* exit if libfuse is missing */
823 int dir_fd, res;
825 size_t templen = strlen(temp_base);
827 // allocate enough memory (size of name won't exceed 60 bytes)
828 char mount_dir[templen + 60];
830 build_mount_point(mount_dir, argv[0], temp_base, templen);
832 size_t mount_dir_size = strlen(mount_dir);
833 pid_t pid;
834 char **real_argv;
835 int i;
837 if (mkdtemp(mount_dir) == NULL) {
838 perror ("create mount dir error");
839 exit (EXIT_EXECERROR);
842 if (pipe (keepalive_pipe) == -1) {
843 perror ("pipe error");
844 exit (EXIT_EXECERROR);
847 pid = fork ();
848 if (pid == -1) {
849 perror ("fork error");
850 exit (EXIT_EXECERROR);
853 if (pid == 0) {
854 /* in child */
856 char *child_argv[5];
858 /* close read pipe */
859 close (keepalive_pipe[0]);
861 char *dir = realpath(appimage_path, NULL );
863 char options[100];
864 sprintf(options, "ro,offset=%lu", fs_offset);
866 child_argv[0] = dir;
867 child_argv[1] = "-o";
868 child_argv[2] = options;
869 child_argv[3] = dir;
870 child_argv[4] = mount_dir;
872 if(0 != fusefs_main (5, child_argv, fuse_mounted)){
873 char *title;
874 char *body;
875 title = "Cannot mount AppImage, please check your FUSE setup.";
876 body = "You might still be able to extract the contents of this AppImage \n"
877 "if you run it with the --appimage-extract option. \n"
878 "See https://github.com/AppImage/AppImageKit/wiki/FUSE \n"
879 "for more information";
880 notify(title, body, 0); // 3 seconds timeout
882 } else {
883 /* in parent, child is $pid */
884 int c;
886 /* close write pipe */
887 close (keepalive_pipe[1]);
889 /* Pause until mounted */
890 read (keepalive_pipe[0], &c, 1);
892 /* Fuse process has now daemonized, reap our child */
893 waitpid(pid, NULL, 0);
895 dir_fd = open (mount_dir, O_RDONLY);
896 if (dir_fd == -1) {
897 perror ("open dir error");
898 exit (EXIT_EXECERROR);
901 res = dup2 (dir_fd, 1023);
902 if (res == -1) {
903 perror ("dup2 error");
904 exit (EXIT_EXECERROR);
906 close (dir_fd);
908 real_argv = malloc (sizeof (char *) * (argc + 1));
909 for (i = 0; i < argc; i++) {
910 real_argv[i] = argv[i];
912 real_argv[i] = NULL;
914 if(arg && strcmp(arg, "appimage-mount") == 0) {
915 char real_mount_dir[PATH_MAX];
917 if (realpath(mount_dir, real_mount_dir) == real_mount_dir) {
918 printf("%s\n", real_mount_dir);
919 } else {
920 printf("%s\n", mount_dir);
923 // stdout is, by default, buffered (unlike stderr), therefore in order to allow other processes to read
924 // the path from stdout, we need to flush the buffers now
925 // this is a less-invasive alternative to setbuf(stdout, NULL);
926 fflush(stdout);
928 for (;;) pause();
930 exit(0);
933 /* Setting some environment variables that the app "inside" might use */
934 setenv( "APPIMAGE", fullpath, 1 );
935 setenv( "ARGV0", argv0_path, 1 );
936 setenv( "APPDIR", mount_dir, 1 );
938 set_portable_home_and_config(fullpath);
940 /* Original working directory */
941 char cwd[1024];
942 if (getcwd(cwd, sizeof(cwd)) != NULL) {
943 setenv( "OWD", cwd, 1 );
946 char filename[mount_dir_size + 8]; /* enough for mount_dir + "/AppRun" */
947 strcpy (filename, mount_dir);
948 strcat (filename, "/AppRun");
950 /* TODO: Find a way to get the exit status and/or output of this */
951 execv (filename, real_argv);
952 /* Error if we continue here */
953 perror("execv error");
954 exit(EXIT_EXECERROR);
957 return 0;