Fix versioning inconsistencies
[appimagekit/gsi.git] / src / runtime.c
blobb5b2677e82a07ea63356228fc2a30306c5f93f11
1 /**************************************************************************
2 *
3 * Copyright (c) 2004-18 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(const char *appimage_path)
210 // TODO: "--appimage-list List content from embedded filesystem image\n"
211 printf(
212 "AppImage options:\n\n"
213 " --appimage-extract Extract content from embedded filesystem image\n"
214 " --appimage-help Print this help\n"
215 " --appimage-mount Mount embedded filesystem image and print\n"
216 " mount point and wait for kill with Ctrl-C\n"
217 " --appimage-offset Print byte offset to start of embedded\n"
218 " filesystem image\n"
219 " --appimage-portable-home Create a portable home folder to use as $HOME\n"
220 " --appimage-portable-config Create a portable config folder to use as\n"
221 " $XDG_CONFIG_HOME\n"
222 " --appimage-signature Print digital signature embedded in AppImage\n"
223 " --appimage-updateinfo[rmation] Print update info embedded in AppImage\n"
224 " --appimage-version Print version of AppImageKit\n"
225 "\n"
226 "Portable home:\n"
227 "\n"
228 " If you would like the application contained inside this AppImage to store its\n"
229 " data alongside this AppImage rather than in your home directory, then you can\n"
230 " place a directory named\n"
231 "\n"
232 " %s.home\n"
233 "\n"
234 " Or you can invoke this AppImage with the --appimage-portable-home option,\n"
235 " which will create this directory for you. As long as the directory exists\n"
236 " and is neither moved nor renamed, the application contained inside this\n"
237 " AppImage to store its data in this directory rather than in your home\n"
238 " directory\n"
239 , appimage_path);
242 void
243 portable_option(const char *arg, const char *appimage_path, const char *name)
245 char option[32];
246 sprintf(option, "appimage-portable-%s", name);
248 if (arg && strcmp(arg, option)==0) {
249 char portable_dir[PATH_MAX];
250 char fullpath[PATH_MAX];
252 ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath));
253 if (length < 0) {
254 printf("Error getting realpath for %s\n", appimage_path);
255 exit(EXIT_FAILURE);
257 fullpath[length] = '\0';
259 sprintf(portable_dir, "%s.%s", fullpath, name);
260 if (!mkdir(portable_dir, S_IRWXU))
261 printf("Portable %s directory created at %s\n", name, portable_dir);
262 else
263 printf("Error creating portable %s directory at %s: %s\n", name, portable_dir, strerror(errno));
265 exit(0);
270 main (int argc, char *argv[])
272 char appimage_path[PATH_MAX];
273 char argv0_path[PATH_MAX];
274 char * arg;
276 /* We might want to operate on a target appimage rather than this file itself,
277 * e.g., for appimaged which must not run untrusted code from random AppImages.
278 * This variable is intended for use by e.g., appimaged and is subject to
279 * change any time. Do not rely on it being present. We might even limit this
280 * functionality specifically for builds used by appimaged.
282 if(getenv("TARGET_APPIMAGE") == NULL){
283 sprintf(appimage_path, "/proc/self/exe");
284 } else {
285 sprintf(appimage_path, "%s", getenv("TARGET_APPIMAGE"));
286 fprintf(stderr, "Using TARGET_APPIMAGE %s\n", appimage_path);
289 sprintf(argv0_path, argv[0]);
291 fs_offset = get_elf_size(appimage_path);
293 arg=getArg(argc,argv,'-');
295 /* Print the help and then exit */
296 if(arg && strcmp(arg,"appimage-help")==0) {
297 char fullpath[PATH_MAX];
299 ssize_t length = readlink(appimage_path, fullpath, sizeof(fullpath));
300 if (length < 0) {
301 printf("Error getting realpath for %s\n", appimage_path);
302 exit(EXIT_FAILURE);
304 fullpath[length] = '\0';
306 print_help(fullpath);
307 exit(0);
310 /* Just print the offset and then exit */
311 if(arg && strcmp(arg,"appimage-offset")==0) {
312 printf("%lu\n", fs_offset);
313 exit(0);
316 /* Exract the AppImage */
317 arg=getArg(argc,argv,'-');
318 if(arg && strcmp(arg,"appimage-extract")==0) {
319 sqfs_err err = SQFS_OK;
320 sqfs_traverse trv;
321 sqfs fs;
322 char *pattern;
323 char *prefix;
324 char prefixed_path_to_extract[1024];
326 prefix = "squashfs-root/";
328 if(access(prefix, F_OK ) == -1 ) {
329 if (mkdir_p(prefix) == -1) {
330 perror("mkdir_p error");
331 exit(EXIT_FAILURE);
335 if(argc == 3){
336 pattern = argv[2];
337 if (pattern[0] == '/') pattern++; // Remove leading '/'
340 if ((err = sqfs_open_image(&fs, appimage_path, fs_offset)))
341 exit(1);
343 if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs))))
344 die("sqfs_traverse_open error");
345 while (sqfs_traverse_next(&trv, &err)) {
346 if (!trv.dir_end) {
347 if((argc != 3) || (fnmatch (pattern, trv.path, FNM_FILE_NAME) == 0)){
348 // fprintf(stderr, "trv.path: %s\n", trv.path);
349 // fprintf(stderr, "sqfs_inode_id: %lu\n", trv.entry.inode);
350 sqfs_inode inode;
351 if (sqfs_inode_get(&fs, &inode, trv.entry.inode))
352 die("sqfs_inode_get error");
353 // fprintf(stderr, "inode.base.inode_type: %i\n", inode.base.inode_type);
354 // fprintf(stderr, "inode.xtra.reg.file_size: %lu\n", inode.xtra.reg.file_size);
355 strcpy(prefixed_path_to_extract, "");
356 strcat(strcat(prefixed_path_to_extract, prefix), trv.path);
357 fprintf(stderr, "%s\n", prefixed_path_to_extract);
358 if(inode.base.inode_type == SQUASHFS_DIR_TYPE || inode.base.inode_type == SQUASHFS_LDIR_TYPE){
359 // fprintf(stderr, "inode.xtra.dir.parent_inode: %ui\n", inode.xtra.dir.parent_inode);
360 // fprintf(stderr, "mkdir_p: %s/\n", prefixed_path_to_extract);
361 if(access(prefixed_path_to_extract, F_OK ) == -1 ) {
362 if (mkdir_p(prefixed_path_to_extract) == -1) {
363 perror("mkdir_p error");
364 exit(EXIT_FAILURE);
367 } else if(inode.base.inode_type == SQUASHFS_REG_TYPE || inode.base.inode_type == SQUASHFS_LREG_TYPE){
368 // fprintf(stderr, "Extract to: %s\n", prefixed_path_to_extract);
369 if(private_sqfs_stat(&fs, &inode, &st) != 0)
370 die("private_sqfs_stat error");
371 // Read the file in chunks
372 off_t bytes_already_read = 0;
373 sqfs_off_t bytes_at_a_time = 64*1024;
374 FILE * f;
375 f = fopen (prefixed_path_to_extract, "w+");
376 if (f == NULL)
377 die("fopen error");
378 while (bytes_already_read < inode.xtra.reg.file_size)
380 char buf[bytes_at_a_time];
381 if (sqfs_read_range(&fs, &inode, (sqfs_off_t) bytes_already_read, &bytes_at_a_time, buf))
382 die("sqfs_read_range error");
383 // fwrite(buf, 1, bytes_at_a_time, stdout);
384 fwrite(buf, 1, bytes_at_a_time, f);
385 bytes_already_read = bytes_already_read + bytes_at_a_time;
387 fclose(f);
388 chmod (prefixed_path_to_extract, st.st_mode);
389 } else if(inode.base.inode_type == SQUASHFS_SYMLINK_TYPE){
390 size_t size;
391 sqfs_readlink(&fs, &inode, NULL, &size);
392 char buf[size];
393 int ret = sqfs_readlink(&fs, &inode, buf, &size);
394 if (ret != 0)
395 die("symlink error");
396 // fprintf(stderr, "Symlink: %s to %s \n", prefixed_path_to_extract, buf);
397 unlink(prefixed_path_to_extract);
398 ret = symlink(buf, prefixed_path_to_extract);
399 if (ret != 0)
400 die("symlink error");
401 } else {
402 fprintf(stderr, "TODO: Implement inode.base.inode_type %i\n", inode.base.inode_type);
404 // fprintf(stderr, "\n");
408 if (err)
409 die("sqfs_traverse_next error");
410 sqfs_traverse_close(&trv);
411 sqfs_fd_close(fs.fd);
412 exit(0);
415 if(arg && strcmp(arg,"appimage-version")==0) {
416 fprintf(stderr,"Version: %s\n", GIT_VERSION);
417 exit(0);
420 if(arg && (strcmp(arg,"appimage-updateinformation")==0 || strcmp(arg,"appimage-updateinfo")==0)) {
421 unsigned long offset = 0;
422 unsigned long length = 0;
423 get_elf_section_offset_and_length(appimage_path, ".upd_info", &offset, &length);
424 // printf("offset: %lu\n", offset);
425 // printf("length: %lu\n", length);
426 // print_hex(appimage_path, offset, length);
427 print_binary(appimage_path, offset, length);
428 exit(0);
431 if(arg && strcmp(arg,"appimage-signature")==0) {
432 unsigned long offset = 0;
433 unsigned long length = 0;
434 get_elf_section_offset_and_length(appimage_path, ".sha256_sig", &offset, &length);
435 // printf("offset: %lu\n", offset);
436 // printf("length: %lu\n", length);
437 // print_hex(appimage_path, offset, length);
438 print_binary(appimage_path, offset, length);
439 exit(0);
442 portable_option(arg, appimage_path, "home");
443 portable_option(arg, appimage_path, "config");
445 // If there is an argument starting with appimage- (but not appimage-mount which is handled further down)
446 // then stop here and print an error message
447 if((arg && strncmp(arg, "appimage-", 8) == 0) && (arg && strcmp(arg,"appimage-mount")!=0)) {
448 fprintf(stderr,"--%s is not yet implemented in version %s\n", arg, GIT_VERSION);
449 exit(1);
452 LOAD_LIBRARY; /* exit if libfuse is missing */
454 int dir_fd, res;
456 char mount_dir[64];
457 int namelen = strlen(basename(argv[0]));
458 if(namelen>6){
459 namelen=6;
461 strncpy(mount_dir, "/tmp/.mount_", 12);
462 strncpy(mount_dir+12, basename(argv[0]), namelen);
463 strncpy(mount_dir+12+namelen, "XXXXXX", 6);
464 mount_dir[12+namelen+6] = 0; // null terminate destination
466 char filename[100]; /* enough for mount_dir + "/AppRun" */
467 pid_t pid;
468 char **real_argv;
469 int i;
471 if (mkdtemp(mount_dir) == NULL) {
472 exit (1);
475 if (pipe (keepalive_pipe) == -1) {
476 perror ("pipe error");
477 exit (1);
480 pid = fork ();
481 if (pid == -1) {
482 perror ("fork error");
483 exit (1);
486 if (pid == 0) {
487 /* in child */
489 char *child_argv[5];
491 /* close read pipe */
492 close (keepalive_pipe[0]);
494 char *dir = realpath(appimage_path, NULL );
496 char options[100];
497 sprintf(options, "ro,offset=%lu", fs_offset);
499 child_argv[0] = dir;
500 child_argv[1] = "-o";
501 child_argv[2] = options;
502 child_argv[3] = dir;
503 child_argv[4] = mount_dir;
505 if(0 != fusefs_main (5, child_argv, fuse_mounted)){
506 char *title;
507 char *body;
508 title = "Cannot mount AppImage, please check your FUSE setup.";
509 body = "You might still be able to extract the contents of this AppImage \n"
510 "if you run it with the --appimage-extract option. \n"
511 "See https://github.com/AppImage/AppImageKit/wiki/FUSE \n"
512 "for more information";
513 notify(title, body, 0); // 3 seconds timeout
515 } else {
516 /* in parent, child is $pid */
517 int c;
519 /* close write pipe */
520 close (keepalive_pipe[1]);
522 /* Pause until mounted */
523 read (keepalive_pipe[0], &c, 1);
526 dir_fd = open (mount_dir, O_RDONLY);
527 if (dir_fd == -1) {
528 perror ("open dir error");
529 exit (1);
532 res = dup2 (dir_fd, 1023);
533 if (res == -1) {
534 perror ("dup2 error");
535 exit (1);
537 close (dir_fd);
539 strcpy (filename, mount_dir);
540 strcat (filename, "/AppRun");
542 real_argv = malloc (sizeof (char *) * (argc + 1));
543 for (i = 0; i < argc; i++) {
544 real_argv[i] = argv[i];
546 real_argv[i] = NULL;
548 if(arg && strcmp(arg,"appimage-mount")==0) {
549 printf("%s\n", mount_dir);
550 for (;;) pause();
553 int length;
554 char fullpath[PATH_MAX];
556 if(getenv("TARGET_APPIMAGE") == NULL){
557 // If we are operating on this file itself
558 length = readlink(appimage_path, fullpath, sizeof(fullpath));
559 fullpath[length] = '\0';
560 } else {
561 // If we are operating on a different AppImage than this file
562 sprintf(fullpath, "%s", appimage_path); // TODO: Make absolute
565 /* Setting some environment variables that the app "inside" might use */
566 setenv( "APPIMAGE", fullpath, 1 );
567 setenv( "ARGV0", argv0_path, 1 );
568 setenv( "APPDIR", mount_dir, 1 );
570 char portable_home_dir[PATH_MAX];
571 char portable_config_dir[PATH_MAX];
573 /* If there is a directory with the same name as the AppImage plus ".home", then export $HOME */
574 strcpy (portable_home_dir, fullpath);
575 strcat (portable_home_dir, ".home");
576 if(is_writable_directory(portable_home_dir)){
577 printf("Setting $HOME to %s\n", portable_home_dir);
578 setenv("HOME",portable_home_dir,1);
581 /* If there is a directory with the same name as the AppImage plus ".config", then export $XDG_CONFIG_HOME */
582 strcpy (portable_config_dir, fullpath);
583 strcat (portable_config_dir, ".config");
584 if(is_writable_directory(portable_config_dir)){
585 printf("Setting $XDG_CONFIG_HOME to %s\n", portable_config_dir);
586 setenv("XDG_CONFIG_HOME",portable_config_dir,1);
589 /* Original working directory */
590 char cwd[1024];
591 if (getcwd(cwd, sizeof(cwd)) != NULL) {
592 setenv( "OWD", cwd, 1 );
595 /* If we are operating on an AppImage different from this file,
596 * then we do not execute the payload */
597 if(getenv("TARGET_APPIMAGE") == NULL){
598 /* TODO: Find a way to get the exit status and/or output of this */
599 execv (filename, real_argv);
600 /* Error if we continue here */
601 perror ("execv error");
602 exit (1);
606 return 0;