Merge pull request #865 from AppImage/fix_appimagetool_names_architecture_wrongly
[appimagekit.git] / src / runtime.c
blob38b132a3466d863e9194a38101947fb1de74b6b5
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, SIGHUP);
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, S_IRWXU) != 0) {
198 if (errno != EEXIST)
199 return -1;
202 *p = '/';
206 if (mkdir(_path, S_IRWXU) != 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 printf(
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 to 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 printf("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 printf("Portable %s directory created at %s\n", name, portable_dir);
270 else
271 printf("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) {
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) == 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);
334 fprintf(stderr, "%s\n", prefixed_path_to_extract);
335 if (inode.base.inode_type == SQUASHFS_DIR_TYPE || inode.base.inode_type == SQUASHFS_LDIR_TYPE) {
336 // fprintf(stderr, "inode.xtra.dir.parent_inode: %ui\n", inode.xtra.dir.parent_inode);
337 // fprintf(stderr, "mkdir_p: %s/\n", prefixed_path_to_extract);
338 if (access(prefixed_path_to_extract, F_OK) == -1) {
339 if (mkdir_p(prefixed_path_to_extract) == -1) {
340 perror("mkdir_p error");
341 rv = false;
342 break;
345 } else if (inode.base.inode_type == SQUASHFS_REG_TYPE || inode.base.inode_type == SQUASHFS_LREG_TYPE) {
346 // if we've already created this inode, then this is a hardlink
347 char* existing_path_for_inode = created_inode[inode.base.inode_number - 1];
348 if (existing_path_for_inode != NULL) {
349 unlink(prefixed_path_to_extract);
350 if (link(existing_path_for_inode, prefixed_path_to_extract) == -1) {
351 fprintf(stderr, "Couldn't create hardlink from \"%s\" to \"%s\": %s\n",
352 prefixed_path_to_extract, existing_path_for_inode, strerror(errno));
353 rv = false;
354 break;
355 } else {
356 continue;
358 } else {
359 struct stat st;
360 if (!overwrite && stat(prefixed_path_to_extract, &st) == 0 && st.st_size == inode.xtra.reg.file_size) {
361 fprintf(stderr, "File exists and file size matches, skipping\n");
362 continue;
365 // track the path we extract to for this inode, so that we can `link` if this inode is found again
366 created_inode[inode.base.inode_number - 1] = strdup(prefixed_path_to_extract);
367 // fprintf(stderr, "Extract to: %s\n", prefixed_path_to_extract);
368 if (private_sqfs_stat(&fs, &inode, &st) != 0)
369 die("private_sqfs_stat error");
370 // Read the file in chunks
371 off_t bytes_already_read = 0;
372 sqfs_off_t bytes_at_a_time = 64 * 1024;
373 FILE* f;
374 f = fopen(prefixed_path_to_extract, "w+");
375 if (f == NULL) {
376 perror("fopen error");
377 rv = false;
378 break;
380 while (bytes_already_read < inode.xtra.reg.file_size) {
381 char buf[bytes_at_a_time];
382 if (sqfs_read_range(&fs, &inode, (sqfs_off_t) bytes_already_read, &bytes_at_a_time, buf)) {
383 perror("sqfs_read_range error");
384 fclose(f);
385 rv = false;
386 break;
388 // fwrite(buf, 1, bytes_at_a_time, stdout);
389 fwrite(buf, 1, bytes_at_a_time, f);
390 bytes_already_read = bytes_already_read + bytes_at_a_time;
392 fclose(f);
393 chmod(prefixed_path_to_extract, st.st_mode);
394 if (!rv)
395 break;
397 } else if (inode.base.inode_type == SQUASHFS_SYMLINK_TYPE) {
398 size_t size;
399 sqfs_readlink(&fs, &inode, NULL, &size);
400 char buf[size];
401 int ret = sqfs_readlink(&fs, &inode, buf, &size);
402 if (ret != 0) {
403 perror("symlink error");
404 rv = false;
405 break;
407 // fprintf(stderr, "Symlink: %s to %s \n", prefixed_path_to_extract, buf);
408 unlink(prefixed_path_to_extract);
409 ret = symlink(buf, prefixed_path_to_extract);
410 if (ret != 0)
411 fprintf(stderr, "WARNING: could not create symlink\n");
412 } else {
413 fprintf(stderr, "TODO: Implement inode.base.inode_type %i\n", inode.base.inode_type);
415 // fprintf(stderr, "\n");
417 if (!rv)
418 break;
422 for (int i = 0; i < fs.sb.inodes; i++) {
423 free(created_inode[i]);
425 free(created_inode);
427 if (err != SQFS_OK) {
428 fprintf(stderr, "sqfs_traverse_next error\n");
429 rv = false;
431 sqfs_traverse_close(&trv);
432 sqfs_fd_close(fs.fd);
434 return rv;
437 int rm_recursive_callback(const char* path, const struct stat* stat, const int type, struct FTW* ftw) {
438 (void) stat;
439 (void) ftw;
441 switch (type) {
442 case FTW_NS:
443 case FTW_DNR:
444 fprintf(stderr, "%s: ftw error: %s\n",
445 path, strerror(errno));
446 return 1;
448 case FTW_D:
449 // ignore directories at first, will be handled by FTW_DP
450 break;
452 case FTW_F:
453 case FTW_SL:
454 case FTW_SLN:
455 if (remove(path) != 0) {
456 fprintf(stderr, "Failed to remove %s: %s\n", path, strerror(errno));
457 return false;
459 break;
462 case FTW_DP:
463 if (rmdir(path) != 0) {
464 fprintf(stderr, "Failed to remove directory %s: %s\n", path, strerror(errno));
465 return false;
467 break;
469 default:
470 fprintf(stderr, "Unexpected fts_info\n");
471 return 1;
474 return 0;
477 bool rm_recursive(const char* const path) {
478 // FTW_DEPTH: perform depth-first search to make sure files are deleted before the containing directories
479 // FTW_MOUNT: prevent deletion of files on other mounted filesystems
480 // FTW_PHYS: do not follow symlinks, but report symlinks as such; this way, the symlink targets, which might point
481 // to locations outside path will not be deleted accidentally (attackers might abuse this)
482 int rv = nftw(path, &rm_recursive_callback, 0, FTW_DEPTH | FTW_MOUNT | FTW_PHYS);
484 return rv == 0;
487 int main(int argc, char *argv[]) {
488 char appimage_path[PATH_MAX];
489 char argv0_path[PATH_MAX];
490 char * arg;
492 /* We might want to operate on a target appimage rather than this file itself,
493 * e.g., for appimaged which must not run untrusted code from random AppImages.
494 * This variable is intended for use by e.g., appimaged and is subject to
495 * change any time. Do not rely on it being present. We might even limit this
496 * functionality specifically for builds used by appimaged.
498 if (getenv("TARGET_APPIMAGE") == NULL) {
499 strcpy(appimage_path, "/proc/self/exe");
500 strcpy(argv0_path, argv[0]);
501 } else {
502 strcpy(appimage_path, getenv("TARGET_APPIMAGE"));
503 strcpy(argv0_path, getenv("TARGET_APPIMAGE"));
505 #ifdef ENABLE_SETPROCTITLE
506 // load libbsd dynamically to change proc title
507 // this is an optional feature, therefore we don't hard require it
508 void* libbsd = dlopen("libbsd.so", RTLD_NOW);
510 if (libbsd != NULL) {
511 // clear error state
512 dlerror();
514 // try to load the two required symbols
515 void (*setproctitle_init)(int, char**, char**) = dlsym(libbsd, "setproctitle_init");
517 char* error;
519 if ((error = dlerror()) == NULL) {
520 void (*setproctitle)(const char*, char*) = dlsym(libbsd, "setproctitle");
522 if (dlerror() == NULL) {
523 char buffer[1024];
524 strcpy(buffer, getenv("TARGET_APPIMAGE"));
525 for (int i = 1; i < argc; i++) {
526 strcat(buffer, " ");
527 strcat(buffer, argv[i]);
530 (*setproctitle_init)(argc, argv, environ);
531 (*setproctitle)("%s", buffer);
535 dlclose(libbsd);
537 #endif
540 // temporary directories are required in a few places
541 // therefore we implement the detection of the temp base dir at the top of the code to avoid redundancy
542 char temp_base[PATH_MAX] = P_tmpdir;
545 const char* const TMPDIR = getenv("TMPDIR");
546 if (TMPDIR != NULL)
547 strcpy(temp_base, getenv("TMPDIR"));
550 fs_offset = appimage_get_elf_size(appimage_path);
552 // error check
553 if (fs_offset < 0) {
554 printf("Failed to get fs offset for %s\n", appimage_path);
555 exit(EXIT_EXECERROR);
558 arg=getArg(argc,argv,'-');
560 /* Print the help and then exit */
561 if(arg && strcmp(arg,"appimage-help")==0) {
562 char fullpath[PATH_MAX];
564 ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath));
565 if (length < 0) {
566 printf("Error getting realpath for %s\n", appimage_path);
567 exit(EXIT_EXECERROR);
569 fullpath[length] = '\0';
571 print_help(fullpath);
572 exit(0);
575 /* Just print the offset and then exit */
576 if(arg && strcmp(arg,"appimage-offset")==0) {
577 printf("%lu\n", fs_offset);
578 exit(0);
581 arg=getArg(argc,argv,'-');
583 /* extract the AppImage */
584 if(arg && strcmp(arg,"appimage-extract")==0) {
585 char* pattern;
587 // default use case: use standard prefix
588 if (argc == 2) {
589 pattern = NULL;
590 } else if (argc == 3) {
591 pattern = argv[2];
592 } else {
593 fprintf(stderr, "Unexpected argument count: %d\n", argc - 1);
594 fprintf(stderr, "Usage: %s --appimage-extract [<prefix>]\n", argv0_path);
595 exit(1);
598 if (!extract_appimage(appimage_path, "squashfs-root/", pattern, true)) {
599 exit(1);
602 exit(0);
605 // calculate full path of AppImage
606 int length;
607 char fullpath[PATH_MAX];
609 if(getenv("TARGET_APPIMAGE") == NULL) {
610 // If we are operating on this file itself
611 ssize_t len = readlink(appimage_path, fullpath, sizeof(fullpath));
612 if (len < 0) {
613 perror("Failed to obtain absolute path");
614 exit(EXIT_EXECERROR);
616 fullpath[len] = '\0';
617 } else {
618 char* abspath = realpath(appimage_path, NULL);
619 if (abspath == NULL) {
620 perror("Failed to obtain absolute path");
621 exit(EXIT_EXECERROR);
623 strcpy(fullpath, abspath);
624 free(abspath);
627 if (arg && strcmp(arg, "appimage-extract-and-run") == 0) {
628 char* hexlified_digest = NULL;
630 // calculate MD5 hash of file, and use it to make extracted directory name "content-aware"
631 // see https://github.com/AppImage/AppImageKit/issues/841 for more information
633 FILE* f = fopen(appimage_path, "rb");
634 if (f == NULL) {
635 perror("Failed to open AppImage file");
636 exit(EXIT_EXECERROR);
639 Md5Context ctx;
640 Md5Initialise(&ctx);
642 char buf[4096];
643 for (size_t bytes_read; (bytes_read = fread(buf, sizeof(char), sizeof(buf), f)); bytes_read > 0) {
644 Md5Update(&ctx, buf, (uint32_t) bytes_read);
647 MD5_HASH digest;
648 Md5Finalise(&ctx, &digest);
650 hexlified_digest = appimage_hexlify(digest.bytes, sizeof(digest.bytes));
653 char* prefix = malloc(strlen(temp_base) + 20 + strlen(hexlified_digest) + 2);
654 strcpy(prefix, temp_base);
655 strcat(prefix, "/appimage_extracted_");
656 strcat(prefix, hexlified_digest);
657 free(hexlified_digest);
659 if (!extract_appimage(appimage_path, prefix, NULL, false)) {
660 fprintf(stderr, "Failed to extract AppImage\n");
661 exit(EXIT_EXECERROR);
664 int pid;
665 if ((pid = fork()) == -1) {
666 int error = errno;
667 fprintf(stderr, "fork() failed: %s\n", strerror(error));
668 exit(EXIT_EXECERROR);
669 } else if (pid == 0) {
670 const char apprun_fname[] = "AppRun";
671 char* apprun_path = malloc(strlen(prefix) + 1 + strlen(apprun_fname) + 1);
672 strcpy(apprun_path, prefix);
673 strcat(apprun_path, "/");
674 strcat(apprun_path, apprun_fname);
676 // create copy of argument list without the --appimage-extract-and-run parameter
677 char* new_argv[argc];
678 int new_argc = 0;
679 new_argv[new_argc++] = strdup(apprun_path);
680 for (int i = 1; i < argc; ++i) {
681 if (strcmp(argv[i], "--appimage-extract-and-run") != 0) {
682 new_argv[new_argc++] = strdup(argv[i]);
685 new_argv[new_argc] = NULL;
687 /* Setting some environment variables that the app "inside" might use */
688 setenv("APPIMAGE", fullpath, 1);
689 setenv("ARGV0", argv0_path, 1);
690 setenv("APPDIR", prefix, 1);
692 execv(apprun_path, new_argv);
694 int error = errno;
695 fprintf(stderr, "Failed to run %s: %s\n", apprun_path, strerror(error));
697 free(apprun_path);
698 exit(EXIT_EXECERROR);
701 int status = 0;
702 int rv = waitpid(pid, &status, 0);
703 status = rv > 0 && WIFEXITED (status) ? WEXITSTATUS (status) : EXIT_EXECERROR;
705 if (getenv("NO_CLEANUP") == NULL) {
706 if (!rm_recursive(prefix)) {
707 fprintf(stderr, "Failed to clean up cache directory\n");
708 if (status == 0) /* avoid messing existing failure exit status */
709 status = EXIT_EXECERROR;
713 // template == prefix, must be freed only once
714 free(prefix);
716 exit(status);
719 if(arg && strcmp(arg,"appimage-version")==0) {
720 fprintf(stderr,"Version: %s\n", GIT_COMMIT);
721 exit(0);
724 if(arg && (strcmp(arg,"appimage-updateinformation")==0 || strcmp(arg,"appimage-updateinfo")==0)) {
725 unsigned long offset = 0;
726 unsigned long length = 0;
727 appimage_get_elf_section_offset_and_length(appimage_path, ".upd_info", &offset, &length);
728 // printf("offset: %lu\n", offset);
729 // printf("length: %lu\n", length);
730 // print_hex(appimage_path, offset, length);
731 appimage_print_binary(appimage_path, offset, length);
732 exit(0);
735 if(arg && strcmp(arg,"appimage-signature")==0) {
736 unsigned long offset = 0;
737 unsigned long length = 0;
738 appimage_get_elf_section_offset_and_length(appimage_path, ".sha256_sig", &offset, &length);
739 // printf("offset: %lu\n", offset);
740 // printf("length: %lu\n", length);
741 // print_hex(appimage_path, offset, length);
742 appimage_print_binary(appimage_path, offset, length);
743 exit(0);
746 portable_option(arg, appimage_path, "home");
747 portable_option(arg, appimage_path, "config");
749 // If there is an argument starting with appimage- (but not appimage-mount which is handled further down)
750 // then stop here and print an error message
751 if((arg && strncmp(arg, "appimage-", 8) == 0) && (arg && strcmp(arg,"appimage-mount")!=0)) {
752 fprintf(stderr,"--%s is not yet implemented in version %s\n", arg, GIT_COMMIT);
753 exit(1);
756 LOAD_LIBRARY; /* exit if libfuse is missing */
758 int dir_fd, res;
760 size_t templen = strlen(temp_base);
762 // allocate enough memory (size of name won't exceed 60 bytes)
763 char mount_dir[templen + 60];
765 size_t namelen = strlen(basename(argv[0]));
766 // limit length of tempdir name
767 if(namelen > 6){
768 namelen = 6;
771 strcpy(mount_dir, temp_base);
772 strncpy(mount_dir+templen, "/.mount_", 8);
773 strncpy(mount_dir+templen+8, basename(argv[0]), namelen);
774 strncpy(mount_dir+templen+8+namelen, "XXXXXX", 6);
775 mount_dir[templen+8+namelen+6] = 0; // null terminate destination
777 size_t mount_dir_size = strlen(mount_dir);
778 pid_t pid;
779 char **real_argv;
780 int i;
782 if (mkdtemp(mount_dir) == NULL) {
783 perror ("create mount dir error");
784 exit (EXIT_EXECERROR);
787 if (pipe (keepalive_pipe) == -1) {
788 perror ("pipe error");
789 exit (EXIT_EXECERROR);
792 pid = fork ();
793 if (pid == -1) {
794 perror ("fork error");
795 exit (EXIT_EXECERROR);
798 if (pid == 0) {
799 /* in child */
801 char *child_argv[5];
803 /* close read pipe */
804 close (keepalive_pipe[0]);
806 char *dir = realpath(appimage_path, NULL );
808 char options[100];
809 sprintf(options, "ro,offset=%lu", fs_offset);
811 child_argv[0] = dir;
812 child_argv[1] = "-o";
813 child_argv[2] = options;
814 child_argv[3] = dir;
815 child_argv[4] = mount_dir;
817 if(0 != fusefs_main (5, child_argv, fuse_mounted)){
818 char *title;
819 char *body;
820 title = "Cannot mount AppImage, please check your FUSE setup.";
821 body = "You might still be able to extract the contents of this AppImage \n"
822 "if you run it with the --appimage-extract option. \n"
823 "See https://github.com/AppImage/AppImageKit/wiki/FUSE \n"
824 "for more information";
825 notify(title, body, 0); // 3 seconds timeout
827 } else {
828 /* in parent, child is $pid */
829 int c;
831 /* close write pipe */
832 close (keepalive_pipe[1]);
834 /* Pause until mounted */
835 read (keepalive_pipe[0], &c, 1);
837 /* Fuse process has now daemonized, reap our child */
838 waitpid(pid, NULL, 0);
840 dir_fd = open (mount_dir, O_RDONLY);
841 if (dir_fd == -1) {
842 perror ("open dir error");
843 exit (EXIT_EXECERROR);
846 res = dup2 (dir_fd, 1023);
847 if (res == -1) {
848 perror ("dup2 error");
849 exit (EXIT_EXECERROR);
851 close (dir_fd);
853 real_argv = malloc (sizeof (char *) * (argc + 1));
854 for (i = 0; i < argc; i++) {
855 real_argv[i] = argv[i];
857 real_argv[i] = NULL;
859 if(arg && strcmp(arg,"appimage-mount")==0) {
860 char real_mount_dir[PATH_MAX];
861 if (realpath(mount_dir, real_mount_dir) == real_mount_dir)
862 printf("%s\n", real_mount_dir);
863 else
864 printf("%s\n", mount_dir);
865 for (;;) pause();
868 /* Setting some environment variables that the app "inside" might use */
869 setenv( "APPIMAGE", fullpath, 1 );
870 setenv( "ARGV0", argv0_path, 1 );
871 setenv( "APPDIR", mount_dir, 1 );
873 char portable_home_dir[PATH_MAX];
874 char portable_config_dir[PATH_MAX];
876 /* If there is a directory with the same name as the AppImage plus ".home", then export $HOME */
877 strcpy (portable_home_dir, fullpath);
878 strcat (portable_home_dir, ".home");
879 if(is_writable_directory(portable_home_dir)){
880 printf("Setting $HOME to %s\n", portable_home_dir);
881 setenv("HOME",portable_home_dir,1);
884 /* If there is a directory with the same name as the AppImage plus ".config", then export $XDG_CONFIG_HOME */
885 strcpy (portable_config_dir, fullpath);
886 strcat (portable_config_dir, ".config");
887 if(is_writable_directory(portable_config_dir)){
888 printf("Setting $XDG_CONFIG_HOME to %s\n", portable_config_dir);
889 setenv("XDG_CONFIG_HOME",portable_config_dir,1);
892 /* Original working directory */
893 char cwd[1024];
894 if (getcwd(cwd, sizeof(cwd)) != NULL) {
895 setenv( "OWD", cwd, 1 );
898 char filename[mount_dir_size + 8]; /* enough for mount_dir + "/AppRun" */
899 strcpy (filename, mount_dir);
900 strcat (filename, "/AppRun");
902 /* TODO: Find a way to get the exit status and/or output of this */
903 execv (filename, real_argv);
904 /* Error if we continue here */
905 perror("execv error");
906 exit(EXIT_EXECERROR);
909 return 0;