1 /**************************************************************************
3 * Copyright (c) 2004-18 Simon Peter
4 * Portions Copyright (c) 2007 Alexander Larsson
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
26 **************************************************************************/
28 #ident "AppImage by Simon Peter, http://appimage.org/"
32 #include "squashfuse.h"
33 #include <squashfs_fs.h>
38 #include <sys/types.h>
51 #include <appimage/appimage_shared.h>
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. */
68 extern int notify(char *title
, char *body
, int timeout
);
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
);
78 /* Check whether directory is writable */
79 bool is_writable_directory(char* str
) {
80 if(access(str
, W_OK
) == 0) {
87 bool startsWith(const char *pre
, const char *str
)
89 size_t lenpre
= strlen(pre
),
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
;
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
);
121 err
= sqfs_id_get(fs
, inode
->base
.guid
, &id
);
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];
138 write_pipe_thread (void *arg
)
142 // sprintf(stderr, "Called write_pipe_thread");
143 memset (c
, 'x', sizeof (c
));
145 /* Write until we block, on broken pipe, exit */
146 res
= write (keepalive_pipe
[1], c
, sizeof (c
));
148 kill (fuse_pid
, SIGHUP
);
160 pthread_create(&thread
, NULL
, write_pipe_thread
, keepalive_pipe
);
163 char* getArg(int argc
, char *argv
[],char chr
)
166 for (i
=1; i
<argc
; ++i
)
167 if ((argv
[i
][0]=='-') && (argv
[i
][1]==chr
))
168 return &(argv
[i
][2]);
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
];
184 /* Copy string so its mutable */
185 if (len
> sizeof(_path
)-1) {
186 errno
= ENAMETOOLONG
;
191 /* Iterate the string */
192 for (p
= _path
+ 1; *p
; p
++) {
194 /* Temporarily truncate */
197 if (mkdir(_path
, S_IRWXU
) != 0) {
206 if (mkdir(_path
, S_IRWXU
) != 0) {
215 print_help(const char *appimage_path
)
217 // TODO: "--appimage-list List content from embedded filesystem image\n"
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"
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"
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"
251 portable_option(const char *arg
, const char *appimage_path
, const char *name
)
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
));
262 printf("Error getting realpath for %s\n", appimage_path
);
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
);
271 printf("Error creating portable %s directory at %s: %s\n", name
, portable_dir
, strerror(errno
));
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
;
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
);
289 if (prefix
[strlen(prefix
) - 1] != '/')
292 if (access(prefix
, F_OK
) == -1) {
293 if (mkdir_p(prefix
) == -1) {
294 perror("mkdir_p error");
299 if ((err
= sqfs_open_image(&fs
, appimage_path
, (size_t) fs_offset
))) {
300 fprintf(stderr
, "Failed to open squashfs image\n");
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");
311 if ((err
= sqfs_traverse_open(&trv
, &fs
, sqfs_inode_root(&fs
)))) {
312 fprintf(stderr
, "sqfs_traverse_open error\n");
319 while (sqfs_traverse_next(&trv
, &err
)) {
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);
325 if (sqfs_inode_get(&fs
, &inode
, trv
.entry
.inode
)) {
326 fprintf(stderr
, "sqfs_inode_get error\n");
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");
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
));
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");
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;
374 f
= fopen(prefixed_path_to_extract
, "w+");
376 perror("fopen error");
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");
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
;
393 chmod(prefixed_path_to_extract
, st
.st_mode
);
397 } else if (inode
.base
.inode_type
== SQUASHFS_SYMLINK_TYPE
) {
399 sqfs_readlink(&fs
, &inode
, NULL
, &size
);
401 int ret
= sqfs_readlink(&fs
, &inode
, buf
, &size
);
403 perror("symlink error");
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
);
411 fprintf(stderr
, "WARNING: could not create symlink\n");
413 fprintf(stderr
, "TODO: Implement inode.base.inode_type %i\n", inode
.base
.inode_type
);
415 // fprintf(stderr, "\n");
422 for (int i
= 0; i
< fs
.sb
.inodes
; i
++) {
423 free(created_inode
[i
]);
427 if (err
!= SQFS_OK
) {
428 fprintf(stderr
, "sqfs_traverse_next error\n");
431 sqfs_traverse_close(&trv
);
432 sqfs_fd_close(fs
.fd
);
437 int rm_recursive_callback(const char* path
, const struct stat
* stat
, const int type
, struct FTW
* ftw
) {
444 fprintf(stderr
, "%s: ftw error: %s\n",
445 path
, strerror(errno
));
449 // ignore directories at first, will be handled by FTW_DP
455 if (remove(path
) != 0) {
456 fprintf(stderr
, "Failed to remove %s: %s\n", path
, strerror(errno
));
463 if (rmdir(path
) != 0) {
464 fprintf(stderr
, "Failed to remove directory %s: %s\n", path
, strerror(errno
));
470 fprintf(stderr
, "Unexpected fts_info\n");
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
);
487 int main(int argc
, char *argv
[]) {
488 char appimage_path
[PATH_MAX
];
489 char argv0_path
[PATH_MAX
];
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]);
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
) {
514 // try to load the two required symbols
515 void (*setproctitle_init
)(int, char**, char**) = dlsym(libbsd
, "setproctitle_init");
519 if ((error
= dlerror()) == NULL
) {
520 void (*setproctitle
)(const char*, char*) = dlsym(libbsd
, "setproctitle");
522 if (dlerror() == NULL
) {
524 strcpy(buffer
, getenv("TARGET_APPIMAGE"));
525 for (int i
= 1; i
< argc
; i
++) {
527 strcat(buffer
, argv
[i
]);
530 (*setproctitle_init
)(argc
, argv
, environ
);
531 (*setproctitle
)("%s", buffer
);
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");
547 strcpy(temp_base
, getenv("TMPDIR"));
550 fs_offset
= appimage_get_elf_size(appimage_path
);
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
));
566 printf("Error getting realpath for %s\n", appimage_path
);
567 exit(EXIT_EXECERROR
);
569 fullpath
[length
] = '\0';
571 print_help(fullpath
);
575 /* Just print the offset and then exit */
576 if(arg
&& strcmp(arg
,"appimage-offset")==0) {
577 printf("%lu\n", fs_offset
);
581 arg
=getArg(argc
,argv
,'-');
583 /* extract the AppImage */
584 if(arg
&& strcmp(arg
,"appimage-extract")==0) {
587 // default use case: use standard prefix
590 } else if (argc
== 3) {
593 fprintf(stderr
, "Unexpected argument count: %d\n", argc
- 1);
594 fprintf(stderr
, "Usage: %s --appimage-extract [<prefix>]\n", argv0_path
);
598 if (!extract_appimage(appimage_path
, "squashfs-root/", pattern
, true)) {
605 // calculate full path of AppImage
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';
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");
626 perror("Failed to open AppImage file");
627 exit(EXIT_EXECERROR
);
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
);
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
);
656 if ((pid
= fork()) == -1) {
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
];
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
);
686 fprintf(stderr
, "Failed to run %s: %s\n", apprun_path
, strerror(error
));
689 exit(EXIT_EXECERROR
);
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
710 if(arg
&& strcmp(arg
,"appimage-version")==0) {
711 fprintf(stderr
,"Version: %s\n", GIT_COMMIT
);
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
);
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
);
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
);
747 LOAD_LIBRARY
; /* exit if libfuse is missing */
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
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
);
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
);
785 perror ("fork error");
786 exit (EXIT_EXECERROR
);
794 /* close read pipe */
795 close (keepalive_pipe
[0]);
797 char *dir
= realpath(appimage_path
, NULL
);
800 sprintf(options
, "ro,offset=%lu", fs_offset
);
803 child_argv
[1] = "-o";
804 child_argv
[2] = options
;
806 child_argv
[4] = mount_dir
;
808 if(0 != fusefs_main (5, child_argv
, fuse_mounted
)){
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
819 /* in parent, child is $pid */
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
);
833 perror ("open dir error");
834 exit (EXIT_EXECERROR
);
837 res
= dup2 (dir_fd
, 1023);
839 perror ("dup2 error");
840 exit (EXIT_EXECERROR
);
844 real_argv
= malloc (sizeof (char *) * (argc
+ 1));
845 for (i
= 0; i
< argc
; i
++) {
846 real_argv
[i
] = argv
[i
];
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
);
855 printf("%s\n", mount_dir
);
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 */
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
);