1 /**************************************************************************
3 * Copyright (c) 2004-17 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) {
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
];
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");
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) {
253 /* Just print the offset and then exit */
254 if(arg
&& strcmp(arg
,"appimage-offset")==0) {
255 printf("%lu\n", fs_offset
);
259 /* Exract the AppImage */
260 arg
=getArg(argc
,argv
,'-');
261 if(arg
&& strcmp(arg
,"appimage-extract")==0) {
262 sqfs_err err
= SQFS_OK
;
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");
280 if (pattern
[0] == '/') pattern
++; // Remove leading '/'
283 if ((err
= sqfs_open_image(&fs
, appimage_path
, fs_offset
)))
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
)) {
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);
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");
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;
318 f
= fopen (prefixed_path_to_extract
, "w+");
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
;
331 chmod (prefixed_path_to_extract
, st
.st_mode
);
332 } else if(inode
.base
.inode_type
== SQUASHFS_SYMLINK_TYPE
){
334 sqfs_readlink(&fs
, &inode
, NULL
, &size
);
336 int ret
= sqfs_readlink(&fs
, &inode
, buf
, &size
);
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
);
343 die("symlink error");
345 fprintf(stderr
, "TODO: Implement inode.base.inode_type %i\n", inode
.base
.inode_type
);
347 // fprintf(stderr, "\n");
352 die("sqfs_traverse_next error");
353 sqfs_traverse_close(&trv
);
354 sqfs_fd_close(fs
.fd
);
358 if(arg
&& strcmp(arg
,"appimage-version")==0) {
359 fprintf(stderr
,"Version: %s\n", VERSION_NUMBER
);
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
);
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
);
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
);
392 LOAD_LIBRARY
; /* exit if libfuse is missing */
397 int namelen
= strlen(basename(argv
[0]));
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" */
411 if (mkdtemp(mount_dir
) == NULL
) {
415 if (pipe (keepalive_pipe
) == -1) {
416 perror ("pipe error");
422 perror ("fork error");
431 /* close read pipe */
432 close (keepalive_pipe
[0]);
434 char *dir
= realpath(appimage_path
, NULL
);
437 sprintf(options
, "ro,offset=%lu", fs_offset
);
440 child_argv
[1] = "-o";
441 child_argv
[2] = options
;
443 child_argv
[4] = mount_dir
;
445 if(0 != fusefs_main (5, child_argv
, fuse_mounted
)){
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
456 /* in parent, child is $pid */
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
);
468 perror ("open dir error");
472 res
= dup2 (dir_fd
, 1023);
474 perror ("dup2 error");
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
];
488 if(arg
&& strcmp(arg
,"appimage-mount")==0) {
489 printf("%s\n", mount_dir
);
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';
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 */
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");