xhost + # Workaround for: QXcbConnection: Could not connect to display :0
[appimagekit.git] / runtime.c
blob3762337c870338995b8dad952ce6b3bc78a00745
1 /**************************************************************************
2 *
3 * Copyright (c) 2004-17 Simon Peter
4 * Portions Copyright (c) 2007 Alexander Larsson
5 *
6 * All Rights Reserved.
7 *
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 <stdio.h>
42 #include <signal.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <pthread.h>
46 #include <errno.h>
47 #include <wait.h>
49 #include "elf.h"
50 #include "getsection.h"
52 #ifndef ENABLE_DLOPEN
53 #define ENABLE_DLOPEN
54 #endif
55 #include "squashfuse_dlopen.h"
57 #include <fnmatch.h>
59 //#include "notify.c"
60 extern int notify(char *title, char *body, int timeout);
62 struct stat st;
64 static long unsigned int fs_offset; // The offset at which a filesystem image is expected = end of this ELF
66 static void die(const char *msg) {
67 fprintf(stderr, "%s\n", msg);
68 exit(1);
71 /* Check whether directory is writable */
72 bool is_writable_directory(char* str) {
73 if(access(str, W_OK) == 0) {
74 return true;
75 } else {
76 return false;
80 bool startsWith(const char *pre, const char *str)
82 size_t lenpre = strlen(pre),
83 lenstr = strlen(str);
84 return lenstr < lenpre ? false : strncmp(pre, str, lenpre) == 0;
87 /* Fill in a stat structure. Does not set st_ino */
88 sqfs_err private_sqfs_stat(sqfs *fs, sqfs_inode *inode, struct stat *st) {
89 sqfs_err err = SQFS_OK;
90 uid_t id;
92 memset(st, 0, sizeof(*st));
93 st->st_mode = inode->base.mode;
94 st->st_nlink = inode->nlink;
95 st->st_mtime = st->st_ctime = st->st_atime = inode->base.mtime;
97 if (S_ISREG(st->st_mode)) {
98 /* FIXME: do symlinks, dirs, etc have a size? */
99 st->st_size = inode->xtra.reg.file_size;
100 st->st_blocks = st->st_size / 512;
101 } else if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) {
102 st->st_rdev = sqfs_makedev(inode->xtra.dev.major,
103 inode->xtra.dev.minor);
104 } else if (S_ISLNK(st->st_mode)) {
105 st->st_size = inode->xtra.symlink_size;
108 st->st_blksize = fs->sb.block_size; /* seriously? */
110 err = sqfs_id_get(fs, inode->base.uid, &id);
111 if (err)
112 return err;
113 st->st_uid = id;
114 err = sqfs_id_get(fs, inode->base.guid, &id);
115 st->st_gid = id;
116 if (err)
117 return err;
119 return SQFS_OK;
122 /* ================= End ELF parsing */
124 extern int fusefs_main(int argc, char *argv[], void (*mounted) (void));
125 // extern void ext2_quit(void);
127 static pid_t fuse_pid;
128 static int keepalive_pipe[2];
130 static void *
131 write_pipe_thread (void *arg)
133 char c[32];
134 int res;
135 // sprintf(stderr, "Called write_pipe_thread");
136 memset (c, 'x', sizeof (c));
137 while (1) {
138 /* Write until we block, on broken pipe, exit */
139 res = write (keepalive_pipe[1], c, sizeof (c));
140 if (res == -1) {
141 kill (fuse_pid, SIGHUP);
142 break;
145 return NULL;
148 void
149 fuse_mounted (void)
151 pthread_t thread;
152 fuse_pid = getpid();
153 pthread_create(&thread, NULL, write_pipe_thread, keepalive_pipe);
156 char* getArg(int argc, char *argv[],char chr)
158 int i;
159 for (i=1; i<argc; ++i)
160 if ((argv[i][0]=='-') && (argv[i][1]==chr))
161 return &(argv[i][2]);
162 return NULL;
165 /* mkdir -p implemented in C, needed for https://github.com/AppImage/AppImageKit/issues/333
166 * https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950 */
168 mkdir_p(const char *path)
170 /* Adapted from http://stackoverflow.com/a/2336245/119527 */
171 const size_t len = strlen(path);
172 char _path[PATH_MAX];
173 char *p;
175 errno = 0;
177 /* Copy string so its mutable */
178 if (len > sizeof(_path)-1) {
179 errno = ENAMETOOLONG;
180 return -1;
182 strcpy(_path, path);
184 /* Iterate the string */
185 for (p = _path + 1; *p; p++) {
186 if (*p == '/') {
187 /* Temporarily truncate */
188 *p = '\0';
190 if (mkdir(_path, S_IRWXU) != 0) {
191 if (errno != EEXIST)
192 return -1;
195 *p = '/';
199 if (mkdir(_path, S_IRWXU) != 0) {
200 if (errno != EEXIST)
201 return -1;
204 return 0;
207 void
208 print_help()
210 printf("AppImage Options:\n\n"
211 "--appimage-help Prints this help\n"
212 "--appimage-offset Prints the offset at which the embedded filesystem image starts, and then exits\n"
213 "--appimage-extract Extracts the contents from the embedded filesystem image, then exit\n"
214 "--appimage-mount Mounts the embedded filesystem image and prints the mount point, then waits until it is killed\n"
215 "--appimage-version Prints the version of AppImageKit, then exits\n"
216 "--appimage-updateinformation Prints the update information embedded into the AppImage, then exits\n"
217 "--appimage-signature Prints the digital signature embedded into the AppImage, then exits\n"
222 main (int argc, char *argv[])
224 char appimage_path[PATH_MAX];
225 char argv0_path[PATH_MAX];
226 char * arg;
228 /* We might want to operate on a target appimage rather than this file itself,
229 * e.g., for appimaged which must not run untrusted code from random AppImages.
230 * This variable is intended for use by e.g., appimaged and is subject to
231 * change any time. Do not rely on it being present. We might even limit this
232 * functionality specifically for builds used by appimaged.
234 if(getenv("TARGET_APPIMAGE") == NULL){
235 sprintf(appimage_path, "/proc/self/exe");
236 } else {
237 sprintf(appimage_path, "%s", getenv("TARGET_APPIMAGE"));
238 fprintf(stderr, "Using TARGET_APPIMAGE %s\n", appimage_path);
241 sprintf(argv0_path, argv[0]);
243 fs_offset = get_elf_size(appimage_path);
245 arg=getArg(argc,argv,'-');
247 /* Print the help and then exit */
248 if(arg && strcmp(arg,"appimage-help")==0) {
249 print_help();
250 exit(0);
253 /* Just print the offset and then exit */
254 if(arg && strcmp(arg,"appimage-offset")==0) {
255 printf("%lu\n", fs_offset);
256 exit(0);
259 /* Exract the AppImage */
260 arg=getArg(argc,argv,'-');
261 if(arg && strcmp(arg,"appimage-extract")==0) {
262 sqfs_err err = SQFS_OK;
263 sqfs_traverse trv;
264 sqfs fs;
265 char *pattern;
266 char *prefix;
267 char prefixed_path_to_extract[1024];
269 prefix = "squashfs-root/";
271 if(access(prefix, F_OK ) == -1 ) {
272 if (mkdir_p(prefix) == -1) {
273 perror("mkdir_p error");
274 exit(EXIT_FAILURE);
278 if(argc == 3){
279 pattern = argv[2];
280 if (pattern[0] == '/') pattern++; // Remove leading '/'
283 if ((err = sqfs_open_image(&fs, appimage_path, fs_offset)))
284 exit(1);
286 if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs))))
287 die("sqfs_traverse_open error");
288 while (sqfs_traverse_next(&trv, &err)) {
289 if (!trv.dir_end) {
290 if((argc != 3) || (fnmatch (pattern, trv.path, FNM_FILE_NAME) == 0)){
291 // fprintf(stderr, "trv.path: %s\n", trv.path);
292 // fprintf(stderr, "sqfs_inode_id: %lu\n", trv.entry.inode);
293 sqfs_inode inode;
294 if (sqfs_inode_get(&fs, &inode, trv.entry.inode))
295 die("sqfs_inode_get error");
296 // fprintf(stderr, "inode.base.inode_type: %i\n", inode.base.inode_type);
297 // fprintf(stderr, "inode.xtra.reg.file_size: %lu\n", inode.xtra.reg.file_size);
298 strcpy(prefixed_path_to_extract, "");
299 strcat(strcat(prefixed_path_to_extract, prefix), trv.path);
300 fprintf(stderr, "%s\n", prefixed_path_to_extract);
301 if(inode.base.inode_type == SQUASHFS_DIR_TYPE || inode.base.inode_type == SQUASHFS_LDIR_TYPE){
302 // fprintf(stderr, "inode.xtra.dir.parent_inode: %ui\n", inode.xtra.dir.parent_inode);
303 // fprintf(stderr, "mkdir_p: %s/\n", prefixed_path_to_extract);
304 if(access(prefixed_path_to_extract, F_OK ) == -1 ) {
305 if (mkdir_p(prefixed_path_to_extract) == -1) {
306 perror("mkdir_p error");
307 exit(EXIT_FAILURE);
310 } else if(inode.base.inode_type == SQUASHFS_REG_TYPE || inode.base.inode_type == SQUASHFS_LREG_TYPE){
311 // fprintf(stderr, "Extract to: %s\n", prefixed_path_to_extract);
312 if(private_sqfs_stat(&fs, &inode, &st) != 0)
313 die("private_sqfs_stat error");
314 // Read the file in chunks
315 off_t bytes_already_read = 0;
316 sqfs_off_t bytes_at_a_time = 64*1024;
317 FILE * f;
318 f = fopen (prefixed_path_to_extract, "w+");
319 if (f == NULL)
320 die("fopen error");
321 while (bytes_already_read < inode.xtra.reg.file_size)
323 char buf[bytes_at_a_time];
324 if (sqfs_read_range(&fs, &inode, (sqfs_off_t) bytes_already_read, &bytes_at_a_time, buf))
325 die("sqfs_read_range error");
326 // fwrite(buf, 1, bytes_at_a_time, stdout);
327 fwrite(buf, 1, bytes_at_a_time, f);
328 bytes_already_read = bytes_already_read + bytes_at_a_time;
330 fclose(f);
331 chmod (prefixed_path_to_extract, st.st_mode);
332 } else if(inode.base.inode_type == SQUASHFS_SYMLINK_TYPE){
333 size_t size;
334 sqfs_readlink(&fs, &inode, NULL, &size);
335 char buf[size];
336 int ret = sqfs_readlink(&fs, &inode, buf, &size);
337 if (ret != 0)
338 die("symlink error");
339 // fprintf(stderr, "Symlink: %s to %s \n", prefixed_path_to_extract, buf);
340 unlink(prefixed_path_to_extract);
341 ret = symlink(buf, prefixed_path_to_extract);
342 if (ret != 0)
343 die("symlink error");
344 } else {
345 fprintf(stderr, "TODO: Implement inode.base.inode_type %i\n", inode.base.inode_type);
347 // fprintf(stderr, "\n");
351 if (err)
352 die("sqfs_traverse_next error");
353 sqfs_traverse_close(&trv);
354 sqfs_fd_close(fs.fd);
355 exit(0);
358 if(arg && strcmp(arg,"appimage-version")==0) {
359 fprintf(stderr,"Version: %s\n", VERSION_NUMBER);
360 exit(0);
363 if(arg && strcmp(arg,"appimage-updateinformation")==0) {
364 unsigned long offset = 0;
365 unsigned long length = 0;
366 get_elf_section_offset_and_lenghth(appimage_path, ".upd_info", &offset, &length);
367 // printf("offset: %lu\n", offset);
368 // printf("length: %lu\n", length);
369 // print_hex(appimage_path, offset, length);
370 print_binary(appimage_path, offset, length);
371 exit(0);
374 if(arg && strcmp(arg,"appimage-signature")==0) {
375 unsigned long offset = 0;
376 unsigned long length = 0;
377 get_elf_section_offset_and_lenghth(appimage_path, ".sha256_sig", &offset, &length);
378 // printf("offset: %lu\n", offset);
379 // printf("length: %lu\n", length);
380 // print_hex(appimage_path, offset, length);
381 print_binary(appimage_path, offset, length);
382 exit(0);
385 // If there is an argument starting with appimage- (but not appimage-mount which is handled further down)
386 // then stop here and print an error message
387 if((arg && strncmp(arg, "appimage-", 8) == 0) && (arg && strcmp(arg,"appimage-mount")!=0)) {
388 fprintf(stderr,"--%s is not yet implemented in version %s\n", arg, VERSION_NUMBER);
389 exit(1);
392 LOAD_LIBRARY; /* exit if libfuse is missing */
394 int dir_fd, res;
396 char mount_dir[64];
397 int namelen = strlen(basename(argv[0]));
398 if(namelen>6){
399 namelen=6;
401 strncpy(mount_dir, "/tmp/.mount_", 12);
402 strncpy(mount_dir+12, basename(argv[0]), namelen);
403 strncpy(mount_dir+12+namelen, "XXXXXX", 6);
404 mount_dir[12+namelen+6] = 0; // null terminate destination
406 char filename[100]; /* enough for mount_dir + "/AppRun" */
407 pid_t pid;
408 char **real_argv;
409 int i;
411 if (mkdtemp(mount_dir) == NULL) {
412 exit (1);
415 if (pipe (keepalive_pipe) == -1) {
416 perror ("pipe error");
417 exit (1);
420 pid = fork ();
421 if (pid == -1) {
422 perror ("fork error");
423 exit (1);
426 if (pid == 0) {
427 /* in child */
429 char *child_argv[5];
431 /* close read pipe */
432 close (keepalive_pipe[0]);
434 char *dir = realpath(appimage_path, NULL );
436 char options[100];
437 sprintf(options, "ro,offset=%lu", fs_offset);
439 child_argv[0] = dir;
440 child_argv[1] = "-o";
441 child_argv[2] = options;
442 child_argv[3] = dir;
443 child_argv[4] = mount_dir;
445 if(0 != fusefs_main (5, child_argv, fuse_mounted)){
446 char *title;
447 char *body;
448 title = "Cannot mount AppImage, please check your FUSE setup.";
449 body = "You might still be able to extract the contents of this AppImage \n"
450 "if you run it with the --appimage-extract option. \n"
451 "See https://github.com/AppImage/AppImageKit/wiki/FUSE \n"
452 "for more information";
453 notify(title, body, 0); // 3 seconds timeout
455 } else {
456 /* in parent, child is $pid */
457 int c;
459 /* close write pipe */
460 close (keepalive_pipe[1]);
462 /* Pause until mounted */
463 read (keepalive_pipe[0], &c, 1);
466 dir_fd = open (mount_dir, O_RDONLY);
467 if (dir_fd == -1) {
468 perror ("open dir error");
469 exit (1);
472 res = dup2 (dir_fd, 1023);
473 if (res == -1) {
474 perror ("dup2 error");
475 exit (1);
477 close (dir_fd);
479 strcpy (filename, mount_dir);
480 strcat (filename, "/AppRun");
482 real_argv = malloc (sizeof (char *) * (argc + 1));
483 for (i = 0; i < argc; i++) {
484 real_argv[i] = argv[i];
486 real_argv[i] = NULL;
488 if(arg && strcmp(arg,"appimage-mount")==0) {
489 printf("%s\n", mount_dir);
490 for (;;) pause();
493 int length;
494 char fullpath[PATH_MAX];
496 if(getenv("TARGET_APPIMAGE") == NULL){
497 // If we are operating on this file itself
498 length = readlink(appimage_path, fullpath, sizeof(fullpath));
499 fullpath[length] = '\0';
500 } else {
501 // If we are operating on a different AppImage than this file
502 sprintf(fullpath, "%s", appimage_path); // TODO: Make absolute
505 /* Setting some environment variables that the app "inside" might use */
506 setenv( "APPIMAGE", fullpath, 1 );
507 setenv( "ARGV0", argv0_path, 1 );
508 setenv( "APPDIR", mount_dir, 1 );
510 char portable_home_dir[2048];
511 char portable_config_dir[2048];
513 /* If there is a directory with the same name as the AppImage plus ".home", then export $HOME */
514 strcpy (portable_home_dir, fullpath);
515 strcat (portable_home_dir, ".home");
516 if(is_writable_directory(portable_home_dir)){
517 printf("Setting $HOME to %s\n", portable_home_dir);
518 setenv("HOME",portable_home_dir,1);
521 /* If there is a directory with the same name as the AppImage plus ".config", then export $XDG_CONFIG_HOME */
522 strcpy (portable_config_dir, fullpath);
523 strcat (portable_config_dir, ".config");
524 if(is_writable_directory(portable_config_dir)){
525 printf("Setting $XDG_CONFIG_HOME to %s\n", portable_config_dir);
526 setenv("XDG_CONFIG_HOME",portable_config_dir,1);
529 /* Original working directory */
530 char cwd[1024];
531 if (getcwd(cwd, sizeof(cwd)) != NULL) {
532 setenv( "OWD", cwd, 1 );
535 /* If we are operating on an AppImage different from this file,
536 * then we do not execute the payload */
537 if(getenv("TARGET_APPIMAGE") == NULL){
538 /* TODO: Find a way to get the exit status and/or output of this */
539 execv (filename, real_argv);
540 /* Error if we continue here */
541 perror ("execv error");
542 exit (1);
546 return 0;