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>
50 #include "getsection.h"
55 #include "squashfuse_dlopen.h"
60 extern int notify(char *title
, char *body
, int timeout
);
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
);
71 /* Check whether directory is writable */
72 bool is_writable_directory(char* str
) {
73 if(access(str
, W_OK
) == 0) {
80 bool startsWith(const char *pre
, const char *str
)
82 size_t lenpre
= strlen(pre
),
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
;
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
);
114 err
= sqfs_id_get(fs
, inode
->base
.guid
, &id
);
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];
131 write_pipe_thread (void *arg
)
135 // sprintf(stderr, "Called write_pipe_thread");
136 memset (c
, 'x', sizeof (c
));
138 /* Write until we block, on broken pipe, exit */
139 res
= write (keepalive_pipe
[1], c
, sizeof (c
));
141 kill (fuse_pid
, SIGHUP
);
153 pthread_create(&thread
, NULL
, write_pipe_thread
, keepalive_pipe
);
156 char* getArg(int argc
, char *argv
[],char chr
)
159 for (i
=1; i
<argc
; ++i
)
160 if ((argv
[i
][0]=='-') && (argv
[i
][1]==chr
))
161 return &(argv
[i
][2]);
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
];
177 /* Copy string so its mutable */
178 if (len
> sizeof(_path
)-1) {
179 errno
= ENAMETOOLONG
;
184 /* Iterate the string */
185 for (p
= _path
+ 1; *p
; p
++) {
187 /* Temporarily truncate */
190 if (mkdir(_path
, S_IRWXU
) != 0) {
199 if (mkdir(_path
, S_IRWXU
) != 0) {
208 print_help(const char *appimage_path
)
210 // TODO: "--appimage-list List content from embedded filesystem image\n"
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"
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"
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"
243 portable_option(const char *arg
, const char *appimage_path
, const char *name
)
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
));
254 printf("Error getting realpath for %s\n", appimage_path
);
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
);
263 printf("Error creating portable %s directory at %s: %s\n", name
, portable_dir
, strerror(errno
));
270 main (int argc
, char *argv
[])
272 char appimage_path
[PATH_MAX
];
273 char argv0_path
[PATH_MAX
];
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");
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
));
301 printf("Error getting realpath for %s\n", appimage_path
);
304 fullpath
[length
] = '\0';
306 print_help(fullpath
);
310 /* Just print the offset and then exit */
311 if(arg
&& strcmp(arg
,"appimage-offset")==0) {
312 printf("%lu\n", fs_offset
);
316 /* Exract the AppImage */
317 arg
=getArg(argc
,argv
,'-');
318 if(arg
&& strcmp(arg
,"appimage-extract")==0) {
319 sqfs_err err
= SQFS_OK
;
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");
337 if (pattern
[0] == '/') pattern
++; // Remove leading '/'
340 if ((err
= sqfs_open_image(&fs
, appimage_path
, fs_offset
)))
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
)) {
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);
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");
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;
375 f
= fopen (prefixed_path_to_extract
, "w+");
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
;
388 chmod (prefixed_path_to_extract
, st
.st_mode
);
389 } else if(inode
.base
.inode_type
== SQUASHFS_SYMLINK_TYPE
){
391 sqfs_readlink(&fs
, &inode
, NULL
, &size
);
393 int ret
= sqfs_readlink(&fs
, &inode
, buf
, &size
);
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
);
400 die("symlink error");
402 fprintf(stderr
, "TODO: Implement inode.base.inode_type %i\n", inode
.base
.inode_type
);
404 // fprintf(stderr, "\n");
409 die("sqfs_traverse_next error");
410 sqfs_traverse_close(&trv
);
411 sqfs_fd_close(fs
.fd
);
415 if(arg
&& strcmp(arg
,"appimage-version")==0) {
416 fprintf(stderr
,"Version: %s\n", GIT_COMMIT
);
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
);
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
);
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_COMMIT
);
452 LOAD_LIBRARY
; /* exit if libfuse is missing */
457 int namelen
= strlen(basename(argv
[0]));
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" */
471 if (mkdtemp(mount_dir
) == NULL
) {
475 if (pipe (keepalive_pipe
) == -1) {
476 perror ("pipe error");
482 perror ("fork error");
491 /* close read pipe */
492 close (keepalive_pipe
[0]);
494 char *dir
= realpath(appimage_path
, NULL
);
497 sprintf(options
, "ro,offset=%lu", fs_offset
);
500 child_argv
[1] = "-o";
501 child_argv
[2] = options
;
503 child_argv
[4] = mount_dir
;
505 if(0 != fusefs_main (5, child_argv
, fuse_mounted
)){
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
516 /* in parent, child is $pid */
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
);
528 perror ("open dir error");
532 res
= dup2 (dir_fd
, 1023);
534 perror ("dup2 error");
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
];
548 if(arg
&& strcmp(arg
,"appimage-mount")==0) {
549 printf("%s\n", mount_dir
);
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';
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 */
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");