Set typical AppImage vars in extract-and-run mode
[appimagekit/gsi.git] / src / runtime.c
blob7cad5418457f5abe64aa9fec4fa2d270f19ac497
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 length = readlink(appimage_path, fullpath, sizeof(fullpath));
612 fullpath[length] = '\0';
613 } else {
614 // If we are operating on a different AppImage than this file
615 sprintf(fullpath, "%s", appimage_path); // TODO: Make absolute
618 if (arg && strcmp(arg, "appimage-extract-and-run") == 0) {
619 char* hexlified_digest = NULL;
621 // calculate MD5 hash of file, and use it to make extracted directory name "content-aware"
622 // see https://github.com/AppImage/AppImageKit/issues/841 for more information
624 FILE* f = fopen(appimage_path, "rb");
625 if (f == NULL) {
626 perror("Failed to open AppImage file");
627 exit(EXIT_EXECERROR);
630 Md5Context ctx;
631 Md5Initialise(&ctx);
633 char buf[4096];
634 for (size_t bytes_read; (bytes_read = fread(buf, sizeof(char), sizeof(buf), f)); bytes_read > 0) {
635 Md5Update(&ctx, buf, (uint32_t) bytes_read);
638 MD5_HASH digest;
639 Md5Finalise(&ctx, &digest);
641 hexlified_digest = appimage_hexlify(digest.bytes, sizeof(digest.bytes));
644 char* prefix = malloc(strlen(temp_base) + 20 + strlen(hexlified_digest) + 2);
645 strcpy(prefix, temp_base);
646 strcat(prefix, "/appimage_extracted_");
647 strcat(prefix, hexlified_digest);
648 free(hexlified_digest);
650 if (!extract_appimage(appimage_path, prefix, NULL, false)) {
651 fprintf(stderr, "Failed to extract AppImage\n");
652 exit(EXIT_EXECERROR);
655 int pid;
656 if ((pid = fork()) == -1) {
657 int error = errno;
658 fprintf(stderr, "fork() failed: %s\n", strerror(error));
659 exit(EXIT_EXECERROR);
660 } else if (pid == 0) {
661 const char apprun_fname[] = "AppRun";
662 char* apprun_path = malloc(strlen(prefix) + 1 + strlen(apprun_fname) + 1);
663 strcpy(apprun_path, prefix);
664 strcat(apprun_path, "/");
665 strcat(apprun_path, apprun_fname);
667 // create copy of argument list without the --appimage-extract-and-run parameter
668 char* new_argv[argc];
669 int new_argc = 0;
670 new_argv[new_argc++] = strdup(apprun_path);
671 for (int i = 1; i < argc; ++i) {
672 if (strcmp(argv[i], "--appimage-extract-and-run") != 0) {
673 new_argv[new_argc++] = strdup(argv[i]);
676 new_argv[new_argc] = NULL;
678 /* Setting some environment variables that the app "inside" might use */
679 setenv("APPIMAGE", fullpath, 1);
680 setenv("ARGV0", argv0_path, 1);
681 setenv("APPDIR", prefix, 1);
683 execv(apprun_path, new_argv);
685 int error = errno;
686 fprintf(stderr, "Failed to run %s: %s\n", apprun_path, strerror(error));
688 free(apprun_path);
689 exit(EXIT_EXECERROR);
692 int status = 0;
693 int rv = waitpid(pid, &status, 0);
694 status = rv > 0 && WIFEXITED (status) ? WEXITSTATUS (status) : EXIT_EXECERROR;
696 if (getenv("NO_CLEANUP") == NULL) {
697 if (!rm_recursive(prefix)) {
698 fprintf(stderr, "Failed to clean up cache directory\n");
699 if (status == 0) /* avoid messing existing failure exit status */
700 status = EXIT_EXECERROR;
704 // template == prefix, must be freed only once
705 free(prefix);
707 exit(status);
710 if(arg && strcmp(arg,"appimage-version")==0) {
711 fprintf(stderr,"Version: %s\n", GIT_COMMIT);
712 exit(0);
715 if(arg && (strcmp(arg,"appimage-updateinformation")==0 || strcmp(arg,"appimage-updateinfo")==0)) {
716 unsigned long offset = 0;
717 unsigned long length = 0;
718 appimage_get_elf_section_offset_and_length(appimage_path, ".upd_info", &offset, &length);
719 // printf("offset: %lu\n", offset);
720 // printf("length: %lu\n", length);
721 // print_hex(appimage_path, offset, length);
722 appimage_print_binary(appimage_path, offset, length);
723 exit(0);
726 if(arg && strcmp(arg,"appimage-signature")==0) {
727 unsigned long offset = 0;
728 unsigned long length = 0;
729 appimage_get_elf_section_offset_and_length(appimage_path, ".sha256_sig", &offset, &length);
730 // printf("offset: %lu\n", offset);
731 // printf("length: %lu\n", length);
732 // print_hex(appimage_path, offset, length);
733 appimage_print_binary(appimage_path, offset, length);
734 exit(0);
737 portable_option(arg, appimage_path, "home");
738 portable_option(arg, appimage_path, "config");
740 // If there is an argument starting with appimage- (but not appimage-mount which is handled further down)
741 // then stop here and print an error message
742 if((arg && strncmp(arg, "appimage-", 8) == 0) && (arg && strcmp(arg,"appimage-mount")!=0)) {
743 fprintf(stderr,"--%s is not yet implemented in version %s\n", arg, GIT_COMMIT);
744 exit(1);
747 LOAD_LIBRARY; /* exit if libfuse is missing */
749 int dir_fd, res;
751 size_t templen = strlen(temp_base);
753 // allocate enough memory (size of name won't exceed 60 bytes)
754 char mount_dir[templen + 60];
756 size_t namelen = strlen(basename(argv[0]));
757 // limit length of tempdir name
758 if(namelen > 6){
759 namelen = 6;
762 strcpy(mount_dir, temp_base);
763 strncpy(mount_dir+templen, "/.mount_", 8);
764 strncpy(mount_dir+templen+8, basename(argv[0]), namelen);
765 strncpy(mount_dir+templen+8+namelen, "XXXXXX", 6);
766 mount_dir[templen+8+namelen+6] = 0; // null terminate destination
768 size_t mount_dir_size = strlen(mount_dir);
769 pid_t pid;
770 char **real_argv;
771 int i;
773 if (mkdtemp(mount_dir) == NULL) {
774 perror ("create mount dir error");
775 exit (EXIT_EXECERROR);
778 if (pipe (keepalive_pipe) == -1) {
779 perror ("pipe error");
780 exit (EXIT_EXECERROR);
783 pid = fork ();
784 if (pid == -1) {
785 perror ("fork error");
786 exit (EXIT_EXECERROR);
789 if (pid == 0) {
790 /* in child */
792 char *child_argv[5];
794 /* close read pipe */
795 close (keepalive_pipe[0]);
797 char *dir = realpath(appimage_path, NULL );
799 char options[100];
800 sprintf(options, "ro,offset=%lu", fs_offset);
802 child_argv[0] = dir;
803 child_argv[1] = "-o";
804 child_argv[2] = options;
805 child_argv[3] = dir;
806 child_argv[4] = mount_dir;
808 if(0 != fusefs_main (5, child_argv, fuse_mounted)){
809 char *title;
810 char *body;
811 title = "Cannot mount AppImage, please check your FUSE setup.";
812 body = "You might still be able to extract the contents of this AppImage \n"
813 "if you run it with the --appimage-extract option. \n"
814 "See https://github.com/AppImage/AppImageKit/wiki/FUSE \n"
815 "for more information";
816 notify(title, body, 0); // 3 seconds timeout
818 } else {
819 /* in parent, child is $pid */
820 int c;
822 /* close write pipe */
823 close (keepalive_pipe[1]);
825 /* Pause until mounted */
826 read (keepalive_pipe[0], &c, 1);
828 /* Fuse process has now daemonized, reap our child */
829 waitpid(pid, NULL, 0);
831 dir_fd = open (mount_dir, O_RDONLY);
832 if (dir_fd == -1) {
833 perror ("open dir error");
834 exit (EXIT_EXECERROR);
837 res = dup2 (dir_fd, 1023);
838 if (res == -1) {
839 perror ("dup2 error");
840 exit (EXIT_EXECERROR);
842 close (dir_fd);
844 real_argv = malloc (sizeof (char *) * (argc + 1));
845 for (i = 0; i < argc; i++) {
846 real_argv[i] = argv[i];
848 real_argv[i] = NULL;
850 if(arg && strcmp(arg,"appimage-mount")==0) {
851 char real_mount_dir[PATH_MAX];
852 if (realpath(mount_dir, real_mount_dir) == real_mount_dir)
853 printf("%s\n", real_mount_dir);
854 else
855 printf("%s\n", mount_dir);
856 for (;;) pause();
859 /* Setting some environment variables that the app "inside" might use */
860 setenv( "APPIMAGE", fullpath, 1 );
861 setenv( "ARGV0", argv0_path, 1 );
862 setenv( "APPDIR", mount_dir, 1 );
864 char portable_home_dir[PATH_MAX];
865 char portable_config_dir[PATH_MAX];
867 /* If there is a directory with the same name as the AppImage plus ".home", then export $HOME */
868 strcpy (portable_home_dir, fullpath);
869 strcat (portable_home_dir, ".home");
870 if(is_writable_directory(portable_home_dir)){
871 printf("Setting $HOME to %s\n", portable_home_dir);
872 setenv("HOME",portable_home_dir,1);
875 /* If there is a directory with the same name as the AppImage plus ".config", then export $XDG_CONFIG_HOME */
876 strcpy (portable_config_dir, fullpath);
877 strcat (portable_config_dir, ".config");
878 if(is_writable_directory(portable_config_dir)){
879 printf("Setting $XDG_CONFIG_HOME to %s\n", portable_config_dir);
880 setenv("XDG_CONFIG_HOME",portable_config_dir,1);
883 /* Original working directory */
884 char cwd[1024];
885 if (getcwd(cwd, sizeof(cwd)) != NULL) {
886 setenv( "OWD", cwd, 1 );
889 char filename[mount_dir_size + 8]; /* enough for mount_dir + "/AppRun" */
890 strcpy (filename, mount_dir);
891 strcat (filename, "/AppRun");
893 /* TODO: Find a way to get the exit status and/or output of this */
894 execv (filename, real_argv);
895 /* Error if we continue here */
896 perror("execv error");
897 exit(EXIT_EXECERROR);
900 return 0;