1 /**************************************************************************
3 * Copyright (c) 2004-17 Simon Peter
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 **************************************************************************/
27 #ident "AppImage by Simon Peter, http://appimage.org/"
30 #include <glib/gstdio.h>
38 #include "squashfuse.h"
40 #include <sys/types.h>
54 #include "getsection.h"
57 #define HAVE_BINARY_RUNTIME
58 extern int _binary_runtime_start
;
59 extern int _binary_runtime_end
;
69 static gchar
const APPIMAGEIGNORE
[] = ".appimageignore";
70 static char _exclude_file_desc
[256];
72 static gboolean list
= FALSE
;
73 static gboolean verbose
= FALSE
;
74 static gboolean version
= FALSE
;
75 static gboolean sign
= FALSE
;
76 static gboolean no_appstream
= FALSE
;
77 gchar
**remaining_args
= NULL
;
78 gchar
*updateinformation
= NULL
;
79 gchar
*bintray_user
= NULL
;
80 gchar
*bintray_repo
= NULL
;
81 gchar
*sqfs_comp
= "gzip";
82 gchar
*exclude_file
= NULL
;
83 gchar
*runtime_file
= NULL
;
84 gchar
*sign_args
= NULL
;
86 // #####################################################################
88 static void die(const char *msg
) {
89 fprintf(stderr
, "%s\n", msg
);
93 /* Function that prints the contents of a squashfs file
94 * using libsquashfuse (#include "squashfuse.h") */
95 int sfs_ls(char* image
) {
96 sqfs_err err
= SQFS_OK
;
100 unsigned long fs_offset
= get_elf_size(image
);
102 if ((err
= sqfs_open_image(&fs
, image
, fs_offset
)))
103 die("sqfs_open_image error");
105 if ((err
= sqfs_traverse_open(&trv
, &fs
, sqfs_inode_root(&fs
))))
106 die("sqfs_traverse_open error");
107 while (sqfs_traverse_next(&trv
, &err
)) {
109 printf("%s\n", trv
.path
);
113 die("sqfs_traverse_next error");
114 sqfs_traverse_close(&trv
);
116 sqfs_fd_close(fs
.fd
);
120 /* Generate a squashfs filesystem using mksquashfs on the $PATH
121 * execlp(), execvp(), and execvpe() search on the $PATH */
122 int sfs_mksquashfs(char *source
, char *destination
, int offset
) {
126 // error, failed to fork()
128 } else if (pid
> 0) {
130 waitpid(pid
, &status
, 0);
133 gchar
*offset_string
;
134 offset_string
= g_strdup_printf("%i", offset
);
137 bool use_xz
= strcmp(sqfs_comp
, "xz") >= 0;
140 args
[i
++] = "mksquashfs";
142 args
[i
++] = destination
;
143 args
[i
++] = "-offset";
144 args
[i
++] = offset_string
;
150 args
[i
++] = sqfs_comp
;
152 args
[i
++] = "-root-owned";
153 args
[i
++] = "-noappend";
156 // https://jonathancarter.org/2015/04/06/squashfs-performance-testing/ says:
157 // improved performance by using a 16384 block size with a sacrifice of around 3% more squashfs image space
158 args
[i
++] = "-Xdict-size";
164 // check if ignore file exists and use it if possible
165 if(access(APPIMAGEIGNORE
, F_OK
) >= 0) {
166 printf("Including %s", APPIMAGEIGNORE
);
167 args
[i
++] = "-wildcards";
170 // avoid warning: assignment discards ‘const’ qualifier
172 strcpy(buf
, APPIMAGEIGNORE
);
176 // if an exclude file has been passed on the command line, should be used, too
177 if(exclude_file
!= 0 && strlen(exclude_file
) > 0) {
178 if(access(exclude_file
, F_OK
) < 0) {
179 printf("WARNING: exclude file %s not found!", exclude_file
);
183 args
[i
++] = "-wildcards";
185 args
[i
++] = exclude_file
;
190 execvp("mksquashfs", args
);
192 perror("execlp"); // exec*() returns only on error
193 return -1; // exec never returns
198 /* Validate desktop file using desktop-file-validate on the $PATH
199 * execlp(), execvp(), and execvpe() search on the $PATH */
200 int validate_desktop_file(char *file
) {
206 printf("could not fork! \n");
209 else if(child_pid
== 0)
211 execlp("desktop-file-validate", "desktop-file-validate", file
, NULL
);
215 waitpid(child_pid
, &statval
, WUNTRACED
| WCONTINUED
);
216 if(WIFEXITED(statval
)){
217 return(WEXITSTATUS(statval
));
223 /* Generate a squashfs filesystem
224 * The following would work if we link to mksquashfs.o after we renamed
225 * main() to mksquashfs_main() in mksquashfs.c but we don't want to actually do
226 * this because squashfs-tools is not under a permissive license
227 * i *nt sfs_mksquashfs(char *source, char *destination) {
228 * char *child_argv[5];
229 * child_argv[0] = NULL;
230 * child_argv[1] = source;
231 * child_argv[2] = destination;
232 * child_argv[3] = "-root-owned";
233 * child_argv[4] = "-noappend";
234 * mksquashfs_main(5, child_argv);
238 /* in-place modification of the string, and assuming the buffer pointed to by
239 * line is large enough to hold the resulting string*/
240 static void replacestr(char *line
, const char *search
, const char *replace
)
244 if ((sp
= strstr(line
, search
)) == NULL
) {
247 int search_len
= strlen(search
);
248 int replace_len
= strlen(replace
);
249 int tail_len
= strlen(sp
+search_len
);
251 memmove(sp
+replace_len
,sp
+search_len
,tail_len
+1);
252 memcpy(sp
, replace
, replace_len
);
254 /* Do it recursively again until no more work to do */
256 if ((sp
= strstr(line
, search
))) {
257 replacestr(line
, search
, replace
);
261 int count_archs(bool* archs
) {
264 for (i
= 0; i
< 4; i
++) {
265 countArchs
+= archs
[i
];
270 gchar
* getArchName(bool* archs
) {
271 if (archs
[fARCH_i386
])
273 else if (archs
[fARCH_x86_64
])
275 else if (archs
[fARCH_arm
])
277 else if (archs
[fARCH_aarch64
])
278 return "ARM_aarch64";
283 void extract_arch_from_text(gchar
*archname
, const gchar
* sourcename
, bool* archs
) {
285 archname
= g_strstrip(archname
);
287 replacestr(archname
, "-", "_");
288 replacestr(archname
, " ", "_");
289 if (g_ascii_strncasecmp("i386", archname
, 20) == 0
290 || g_ascii_strncasecmp("i486", archname
, 20) == 0
291 || g_ascii_strncasecmp("i586", archname
, 20) == 0
292 || g_ascii_strncasecmp("i686", archname
, 20) == 0
293 || g_ascii_strncasecmp("intel_80386", archname
, 20) == 0
294 || g_ascii_strncasecmp("intel_80486", archname
, 20) == 0
295 || g_ascii_strncasecmp("intel_80586", archname
, 20) == 0
296 || g_ascii_strncasecmp("intel_80686", archname
, 20) == 0
298 archs
[fARCH_i386
] = 1;
300 fprintf(stderr
, "%s used for determining architecture i386\n", sourcename
);
301 } else if (g_ascii_strncasecmp("x86_64", archname
, 20) == 0) {
302 archs
[fARCH_x86_64
] = 1;
304 fprintf(stderr
, "%s used for determining architecture x86_64\n", sourcename
);
305 } else if (g_ascii_strncasecmp("arm", archname
, 20) == 0) {
306 archs
[fARCH_arm
] = 1;
308 fprintf(stderr
, "%s used for determining architecture ARM\n", sourcename
);
309 } else if (g_ascii_strncasecmp("arm_aarch64", archname
, 20) == 0) {
310 archs
[fARCH_aarch64
] = 1;
312 fprintf(stderr
, "%s used for determining architecture ARM aarch64\n", sourcename
);
318 void guess_arch_of_file(const gchar
*archfile
, bool* archs
) {
320 char command
[PATH_MAX
];
321 sprintf(command
, "/usr/bin/file -L -N -b %s", archfile
);
322 FILE* fp
= popen(command
, "r");
324 die("Failed to run file command");
325 fgets(line
, sizeof (line
) - 1, fp
);
327 extract_arch_from_text(g_strsplit_set(line
, ",", -1)[1], archfile
, archs
);
330 void find_arch(const gchar
*real_path
, const gchar
*pattern
, bool* archs
) {
333 dir
= g_dir_open(real_path
, 0, NULL
);
336 while ((entry
= g_dir_read_name(dir
)) != NULL
) {
337 full_name
= g_build_filename(real_path
, entry
, NULL
);
338 if (g_file_test(full_name
, G_FILE_TEST_IS_DIR
)) {
339 find_arch(full_name
, pattern
, archs
);
340 } else if (g_file_test(full_name
, G_FILE_TEST_IS_EXECUTABLE
) || g_pattern_match_simple(pattern
, entry
) ) {
341 guess_arch_of_file(full_name
, archs
);
347 g_warning("%s: %s", real_path
, g_strerror(errno
));
351 gchar
* find_first_matching_file_nonrecursive(const gchar
*real_path
, const gchar
*pattern
) {
354 dir
= g_dir_open(real_path
, 0, NULL
);
357 while ((entry
= g_dir_read_name(dir
)) != NULL
) {
358 full_name
= g_build_filename(real_path
, entry
, NULL
);
359 if (g_file_test(full_name
, G_FILE_TEST_IS_REGULAR
)) {
360 if(g_pattern_match_simple(pattern
, entry
))
367 g_warning("%s: %s", real_path
, g_strerror(errno
));
372 gchar
* get_desktop_entry(GKeyFile
*kf
, char *key
) {
373 gchar
*value
= g_key_file_get_string (kf
, "Desktop Entry", key
, NULL
);
375 fprintf(stderr
, "%s entry not found in desktop file\n", key
);
380 bool readFile(char* filename
, int* size
, char** buffer
) {
381 FILE* f
= fopen(filename
, "rb");
388 fseek(f
, 0, SEEK_END
);
389 long fsize
= ftell(f
);
390 fseek(f
, 0, SEEK_SET
);
392 char *indata
= malloc(fsize
);
393 fread(indata
, fsize
, 1, f
);
400 // #####################################################################
402 static GOptionEntry entries
[] =
404 { "list", 'l', 0, G_OPTION_ARG_NONE
, &list
, "List files in SOURCE AppImage", NULL
},
405 { "updateinformation", 'u', 0, G_OPTION_ARG_STRING
, &updateinformation
, "Embed update information STRING; if zsyncmake is installed, generate zsync file", NULL
},
406 { "bintray-user", 0, 0, G_OPTION_ARG_STRING
, &bintray_user
, "Bintray user name", NULL
},
407 { "bintray-repo", 0, 0, G_OPTION_ARG_STRING
, &bintray_repo
, "Bintray repository", NULL
},
408 { "version", 0, 0, G_OPTION_ARG_NONE
, &version
, "Show version number", NULL
},
409 { "verbose", 'v', 0, G_OPTION_ARG_NONE
, &verbose
, "Produce verbose output", NULL
},
410 { "sign", 's', 0, G_OPTION_ARG_NONE
, &sign
, "Sign with gpg[2]", NULL
},
411 { "comp", 0, 0, G_OPTION_ARG_STRING
, &sqfs_comp
, "Squashfs compression", NULL
},
412 { "no-appstream", 'n', 0, G_OPTION_ARG_NONE
, &no_appstream
, "Do not check AppStream metadata", NULL
},
413 { "exclude-file", 0, 0, G_OPTION_ARG_STRING
, &exclude_file
, _exclude_file_desc
, NULL
},
414 { "runtime-file", 0, 0, G_OPTION_ARG_STRING
, &runtime_file
, "Runtime file to use", NULL
},
415 { "sign-args", 0, 0, G_OPTION_ARG_STRING
, &sign_args
, "Extra arguments to use when signing with gpg[2]", NULL
},
416 { G_OPTION_REMAINING
, 0, 0, G_OPTION_ARG_FILENAME_ARRAY
, &remaining_args
, NULL
, NULL
},
421 main (int argc
, char *argv
[])
423 /* Parse VERSION environment variable.
424 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
426 version_env
= getenv("VERSION");
428 /* Parse Travis CI environment variables.
429 * https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
430 * TRAVIS_COMMIT: The commit that the current build is testing.
431 * TRAVIS_REPO_SLUG: The slug (in form: owner_name/repo_name) of the repository currently being built.
432 * TRAVIS_TAG: If the current build is for a git tag, this variable is set to the tag’s name.
433 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
435 travis_commit
= getenv("TRAVIS_COMMIT");
436 char* travis_repo_slug
;
437 travis_repo_slug
= getenv("TRAVIS_REPO_SLUG");
439 travis_tag
= getenv("TRAVIS_TAG");
441 /* Parse OWD environment variable.
442 * If it is available then cd there. It is the original CWD prior to running AppRun */
443 char* owd_env
= NULL
;
444 owd_env
= getenv("OWD");
447 ret
= chdir(owd_env
);
449 fprintf(stderr
, "Could not cd into %s\n", owd_env
);
455 GError
*error
= NULL
;
456 GOptionContext
*context
;
457 char command
[PATH_MAX
];
459 // initialize help text of argument
460 sprintf(_exclude_file_desc
, "Uses given file as exclude file for mksquashfs, in addition to %s.", APPIMAGEIGNORE
);
462 context
= g_option_context_new ("SOURCE [DESTINATION] - Generate, extract, and inspect AppImages");
463 g_option_context_add_main_entries (context
, entries
, NULL
);
464 // g_option_context_add_group (context, gtk_get_option_group (TRUE));
465 if (!g_option_context_parse (context
, &argc
, &argv
, &error
))
467 fprintf(stderr
, "Option parsing failed: %s\n", error
->message
);
472 fprintf(stderr
,"Version: %s\n", VERSION_NUMBER
);
476 if(!((0 == strcmp(sqfs_comp
, "gzip")) || (0 ==strcmp(sqfs_comp
, "xz"))))
477 die("Only gzip (faster execution, larger files) and xz (slower execution, smaller files) compression is supported at the moment. Let us know if there are reasons for more, should be easy to add. You could help the project by doing some systematic size/performance measurements. Watch for size, execution speed, and zsync delta size.");
478 /* Check for dependencies here. Better fail early if they are not present. */
479 if(! g_find_program_in_path ("mksquashfs"))
480 die("mksquashfs is missing but required, please install it");
481 if(! g_find_program_in_path ("desktop-file-validate"))
482 g_print("WARNING: desktop-file-validate is missing, please install it so that desktop files can be checked for potential errors\n");
483 if(! g_find_program_in_path ("zsyncmake"))
484 g_print("WARNING: zsyncmake is missing, please install it if you want to use binary delta updates\n");
486 if(! g_find_program_in_path ("appstreamcli"))
487 g_print("WARNING: appstreamcli is missing, please install it if you want to use AppStream metadata\n");
488 if(! g_find_program_in_path ("gpg2") && ! g_find_program_in_path ("gpg"))
489 g_print("WARNING: gpg2 or gpg is missing, please install it if you want to create digital signatures\n");
490 if(! g_find_program_in_path ("sha256sum") && ! g_find_program_in_path ("shasum"))
491 g_print("WARNING: sha256sum or shasum is missing, please install it if you want to create digital signatures\n");
493 if(!&remaining_args
[0])
494 die("SOURCE is missing");
496 /* If in list mode */
498 sfs_ls(remaining_args
[0]);
502 /* If the first argument is a directory, then we assume that we should package it */
503 if (g_file_test (remaining_args
[0], G_FILE_TEST_IS_DIR
)){
505 char source
[PATH_MAX
];
506 realpath(remaining_args
[0], source
);
508 /* Check if *.desktop file is present in source AppDir */
509 gchar
*desktop_file
= find_first_matching_file_nonrecursive(source
, "*.desktop");
510 if(desktop_file
== NULL
){
511 die("Desktop file not found, aborting");
514 fprintf (stdout
, "Desktop file: %s\n", desktop_file
);
516 if(g_find_program_in_path ("desktop-file-validate")) {
517 if(validate_desktop_file(desktop_file
) != 0){
518 fprintf(stderr
, "ERROR: Desktop file contains errors. Please fix them. Please see\n");
519 fprintf(stderr
, " https://standards.freedesktop.org/desktop-entry-spec/latest/\n");
520 die(" for more information.");
524 /* Read information from .desktop file */
525 GKeyFile
*kf
= g_key_file_new ();
526 if (!g_key_file_load_from_file (kf
, desktop_file
, 0, NULL
))
527 die(".desktop file cannot be parsed");
530 fprintf (stderr
,"Name: %s\n", get_desktop_entry(kf
, "Name"));
531 fprintf (stderr
,"Icon: %s\n", get_desktop_entry(kf
, "Icon"));
532 fprintf (stderr
,"Exec: %s\n", get_desktop_entry(kf
, "Exec"));
533 fprintf (stderr
,"Comment: %s\n", get_desktop_entry(kf
, "Comment"));
534 fprintf (stderr
,"Type: %s\n", get_desktop_entry(kf
, "Type"));
535 fprintf (stderr
,"Categories: %s\n", get_desktop_entry(kf
, "Categories"));
538 /* Determine the architecture */
539 bool archs
[4] = {0, 0, 0, 0};
540 extract_arch_from_text(getenv("ARCH"), "Environmental variable ARCH", archs
);
541 if (count_archs(archs
) != 1) {
542 /* If no $ARCH variable is set check a file */
543 /* We use the next best .so that we can find to determine the architecture */
544 find_arch(source
, "*.so.*", archs
);
545 int countArchs
= count_archs(archs
);
546 if (countArchs
!= 1) {
548 fprintf(stderr
, "Unable to guess the architecture of the AppDir source directory \"%s\"\n", remaining_args
[0]);
550 fprintf(stderr
, "More than one architectures were found of the AppDir source directory \"%s\"\n", remaining_args
[0]);
551 fprintf(stderr
, "A valid architecture with the ARCH environmental variable should be provided\ne.g. ARCH=x86_64 %s", argv
[0]),
555 gchar
* arch
= getArchName(archs
);
556 fprintf(stderr
, "Using architecture %s\n", arch
);
559 char app_name_for_filename
[PATH_MAX
];
560 sprintf(app_name_for_filename
, "%s", get_desktop_entry(kf
, "Name"));
561 replacestr(app_name_for_filename
, " ", "_");
564 fprintf (stderr
,"App name for filename: %s\n", app_name_for_filename
);
566 if (remaining_args
[1]) {
567 destination
= remaining_args
[1];
569 /* No destination has been specified, to let's construct one
570 * TODO: Find out the architecture and use a $VERSION that might be around in the env */
571 char dest_path
[PATH_MAX
];
572 sprintf (dest_path
, "%s-%s.AppImage", app_name_for_filename
, arch
);
575 fprintf (stderr
,"dest_path: %s\n", dest_path
);
577 if (version_env
!=NULL
)
578 sprintf (dest_path
, "%s-%s-%s.AppImage", app_name_for_filename
, version_env
, arch
);
580 destination
= dest_path
;
581 replacestr(destination
, " ", "_");
583 fprintf (stdout
, "%s should be packaged as %s\n", source
, destination
);
584 /* Check if the Icon file is how it is expected */
585 gchar
* icon_name
= get_desktop_entry(kf
, "Icon");
586 gchar
* icon_file_path
= NULL
;
587 gchar
* icon_file_png
;
588 gchar
* icon_file_svg
;
589 gchar
* icon_file_svgz
;
590 gchar
* icon_file_xpm
;
591 icon_file_png
= g_strdup_printf("%s/%s.png", source
, icon_name
);
592 icon_file_svg
= g_strdup_printf("%s/%s.svg", source
, icon_name
);
593 icon_file_svgz
= g_strdup_printf("%s/%s.svgz", source
, icon_name
);
594 icon_file_xpm
= g_strdup_printf("%s/%s.xpm", source
, icon_name
);
595 if (g_file_test(icon_file_png
, G_FILE_TEST_IS_REGULAR
)) {
596 icon_file_path
= icon_file_png
;
597 } else if(g_file_test(icon_file_svg
, G_FILE_TEST_IS_REGULAR
)) {
598 icon_file_path
= icon_file_svg
;
599 } else if(g_file_test(icon_file_svgz
, G_FILE_TEST_IS_REGULAR
)) {
600 icon_file_path
= icon_file_svgz
;
601 } else if(g_file_test(icon_file_xpm
, G_FILE_TEST_IS_REGULAR
)) {
602 icon_file_path
= icon_file_xpm
;
604 fprintf (stderr
, "%s{.png,.svg,.svgz,.xpm} not present but defined in desktop file\n", icon_name
);
608 /* Check if .DirIcon is present in source AppDir */
609 gchar
*diricon_path
= g_build_filename(source
, ".DirIcon", NULL
);
611 if (! g_file_test(diricon_path
, G_FILE_TEST_EXISTS
)){
612 fprintf (stderr
, "Deleting pre-existing .DirIcon\n");
613 g_unlink(diricon_path
);
615 if (! g_file_test(diricon_path
, G_FILE_TEST_IS_REGULAR
)){
616 fprintf (stderr
, "Creating .DirIcon symlink based on information from desktop file\n");
617 int res
= symlink(basename(icon_file_path
), diricon_path
);
619 die("Could not symlink .DirIcon");
622 /* Check if AppStream upstream metadata is present in source AppDir */
624 char application_id
[PATH_MAX
];
625 sprintf (application_id
, "%s", basename(desktop_file
));
626 replacestr(application_id
, ".desktop", ".appdata.xml");
627 gchar
*appdata_path
= g_build_filename(source
, "/usr/share/metainfo/", application_id
, NULL
);
628 if (! g_file_test(appdata_path
, G_FILE_TEST_IS_REGULAR
)){
629 fprintf (stderr
, "WARNING: AppStream upstream metadata is missing, please consider creating it\n");
630 fprintf (stderr
, " in usr/share/metainfo/%s\n", application_id
);
631 fprintf (stderr
, " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps\n");
632 fprintf (stderr
, " for more information.\n");
633 /* As a courtesy, generate one to be filled by the user */
634 if(g_find_program_in_path ("appstream-util")) {
635 gchar
*appdata_dir
= g_build_filename(source
, "/usr/share/metainfo/", NULL
);
636 g_mkdir_with_parents(appdata_dir
, 0755);
637 sprintf (command
, "%s appdata-from-desktop %s %s", g_find_program_in_path ("appstream-util"), desktop_file
, appdata_path
);
638 int ret
= system(command
);
640 die("Failed to generate AppStream template");
641 fprintf (stderr
, "AppStream template has been generated in in %s, please edit it\n", appdata_path
);
645 fprintf (stderr
, "AppStream upstream metadata found in usr/share/metainfo/%s\n", application_id
);
646 /* Use ximion's appstreamcli to make sure that desktop file and appdata match together */
647 if(g_find_program_in_path ("appstreamcli")) {
648 sprintf (command
, "%s validate-tree %s", g_find_program_in_path ("appstreamcli"), source
);
649 int ret
= system(command
);
651 die("Failed to validate AppStream information with appstreamcli");
653 /* It seems that hughsie's appstream-util does additional validations */
654 if(g_find_program_in_path ("appstream-util")) {
655 sprintf (command
, "%s validate-relax %s", g_find_program_in_path ("appstream-util"), appdata_path
);
656 int ret
= system(command
);
658 die("Failed to validate AppStream information with appstream-util");
663 /* Upstream mksquashfs can currently not start writing at an offset,
664 * so we need a patched one. https://github.com/plougher/squashfs-tools/pull/13
665 * should hopefully change that. */
667 fprintf (stderr
, "Generating squashfs...\n");
670 bool using_external_data
= false;
671 if (runtime_file
!= NULL
) {
672 if (!readFile(runtime_file
, &size
, &data
))
673 die("Unable to load provided runtime file");
674 using_external_data
= true;
676 #ifdef HAVE_BINARY_RUNTIME
677 /* runtime is embedded into this executable
678 * http://stupefydeveloper.blogspot.de/2008/08/cc-embed-binary-data-into-elf.html */
679 size
= (int)((void *)&_binary_runtime_end
- (void *)&_binary_runtime_start
);
680 data
= (char *)&_binary_runtime_start
;
682 die("No runtime file was provided");
686 printf("Size of the embedded runtime: %d bytes\n", size
);
688 int result
= sfs_mksquashfs(source
, destination
, size
);
690 die("sfs_mksquashfs error");
692 fprintf (stderr
, "Embedding ELF...\n");
693 FILE *fpdst
= fopen(destination
, "rb+");
695 die("Not able to open the AppImage for writing, aborting");
698 fseek(fpdst
, 0, SEEK_SET
);
699 fwrite(data
, size
, 1, fpdst
);
701 if (using_external_data
)
704 fprintf (stderr
, "Marking the AppImage as executable...\n");
705 if (chmod (destination
, 0755) < 0) {
706 printf("Could not set executable bit, aborting\n");
710 if(bintray_user
!= NULL
){
711 if(bintray_repo
!= NULL
){
713 sprintf(buf
, "bintray-zsync|%s|%s|%s|%s-_latestVersion-%s.AppImage.zsync", bintray_user
, bintray_repo
, app_name_for_filename
, app_name_for_filename
, arch
);
714 updateinformation
= buf
;
715 printf("%s\n", updateinformation
);
719 /* If the user has not provided update information but we know this is a Travis CI build,
720 * then fill in update information based on TRAVIS_REPO_SLUG */
721 if(updateinformation
== NULL
){
722 if(travis_repo_slug
!= NULL
){
724 gchar
**parts
= g_strsplit (travis_repo_slug
, ",", 2);
725 /* https://github.com/AppImage/AppImageSpec/blob/master/draft.md#github-releases
726 * gh-releases-zsync|probono|AppImages|latest|Subsurface-*-x86_64.AppImage.zsync */
727 sprintf(buf
, "gh-releases-zsync|%s|%s|latest|%s-_*-%s.AppImage.zsync", parts
[0], parts
[1], app_name_for_filename
, arch
);
728 updateinformation
= buf
;
729 printf("As a courtesy, automatically embedding update information based on $TRAVIS_REPO_SLUG=%s\n", travis_repo_slug
);
730 printf("%s\n", updateinformation
);
734 /* If updateinformation was provided, then we check and embed it */
735 if(updateinformation
!= NULL
){
736 if(!g_str_has_prefix(updateinformation
,"zsync|"))
737 if(!g_str_has_prefix(updateinformation
,"bintray-zsync|"))
738 if(!g_str_has_prefix(updateinformation
,"gh-releases-zsync|"))
739 die("The provided updateinformation is not in a recognized format");
741 gchar
**ui_type
= g_strsplit_set(updateinformation
, "|", -1);
744 printf("updateinformation type: %s\n", ui_type
[0]);
745 /* TODO: Further checking of the updateinformation */
748 unsigned long ui_offset
= 0;
749 unsigned long ui_length
= 0;
750 get_elf_section_offset_and_lenghth(destination
, ".upd_info", &ui_offset
, &ui_length
);
752 printf("ui_offset: %lu\n", ui_offset
);
753 printf("ui_length: %lu\n", ui_length
);
756 die("Could not determine offset for updateinformation");
758 if(strlen(updateinformation
)>ui_length
)
759 die("updateinformation does not fit into segment, aborting");
760 FILE *fpdst2
= fopen(destination
, "r+");
762 die("Not able to open the destination file for writing, aborting");
763 fseek(fpdst2
, ui_offset
, SEEK_SET
);
764 // fseek(fpdst2, ui_offset, SEEK_SET);
765 // fwrite(0x00, 1, 1024, fpdst); // FIXME: Segfaults; why?
766 // fseek(fpdst, ui_offset, SEEK_SET);
767 fwrite(updateinformation
, strlen(updateinformation
), 1, fpdst2
);
773 bool using_gpg
= FALSE
;
774 bool using_shasum
= FALSE
;
775 /* The user has indicated that he wants to sign */
776 gchar
*gpg2_path
= g_find_program_in_path ("gpg2");
778 gpg2_path
= g_find_program_in_path ("gpg");
781 gchar
*sha256sum_path
= g_find_program_in_path ("sha256sum");
782 if (!sha256sum_path
) {
783 sha256sum_path
= g_find_program_in_path ("shasum");
787 fprintf (stderr
, "gpg2 or gpg is not installed, cannot sign\n");
789 else if(!sha256sum_path
) {
790 fprintf(stderr
, "sha256sum or shasum is not installed, cannot sign\n");
792 fprintf(stderr
, "%s and %s are installed and user requested to sign, "
793 "hence signing\n", using_gpg
? "gpg" : "gpg2",
794 using_shasum
? "shasum" : "sha256sum");
796 digestfile
= br_strcat(destination
, ".digest");
798 ascfile
= br_strcat(destination
, ".digest.asc");
799 if (g_file_test (digestfile
, G_FILE_TEST_IS_REGULAR
))
802 sprintf (command
, "%s -a256 %s", sha256sum_path
, destination
);
804 sprintf (command
, "%s %s", sha256sum_path
, destination
);
806 fprintf (stderr
, "%s\n", command
);
807 fp
= popen(command
, "r");
809 die(using_shasum
? "shasum command did not succeed" : "sha256sum command did not succeed");
811 fgets(output
, sizeof (output
) - 1, fp
);
813 printf("%s: %s\n", using_shasum
? "shasum" : "sha256sum",
814 g_strsplit_set(output
, " ", -1)[0]);
815 FILE *fpx
= fopen(digestfile
, "w");
818 fputs(g_strsplit_set(output
, " ", -1)[0], fpx
);
821 int shasum_exit_status
= pclose(fp
);
822 if(WEXITSTATUS(shasum_exit_status
) != 0)
823 die(using_shasum
? "shasum command did not succeed" : "sha256sum command did not succeed");
824 if (g_file_test (ascfile
, G_FILE_TEST_IS_REGULAR
))
826 sprintf (command
, "%s --detach-sign --armor %s %s", gpg2_path
, sign_args
? sign_args
: "", digestfile
);
828 fprintf (stderr
, "%s\n", command
);
829 fp
= popen(command
, "r");
830 int gpg_exit_status
= pclose(fp
);
831 if(WEXITSTATUS(gpg_exit_status
) != 0) {
832 fprintf (stderr
, "ERROR: %s command did not succeed, could not sign, continuing\n", using_gpg
? "gpg" : "gpg2");
834 unsigned long sig_offset
= 0;
835 unsigned long sig_length
= 0;
836 get_elf_section_offset_and_lenghth(destination
, ".sha256_sig", &sig_offset
, &sig_length
);
838 printf("sig_offset: %lu\n", sig_offset
);
839 printf("sig_length: %lu\n", sig_length
);
841 if(sig_offset
== 0) {
842 die("Could not determine offset for signature");
844 FILE *fpdst3
= fopen(destination
, "r+");
846 die("Not able to open the destination file for writing, aborting");
847 // if(strlen(updateinformation)>sig_length)
848 // die("signature does not fit into segment, aborting");
849 fseek(fpdst3
, sig_offset
, SEEK_SET
);
850 FILE *fpsrc2
= fopen(ascfile
, "rb");
851 if (fpsrc2
== NULL
) {
852 die("Not able to open the asc file for reading, aborting");
855 while (!feof(fpsrc2
))
857 fread(&byte
, sizeof(char), 1, fpsrc2
);
858 fwrite(&byte
, sizeof(char), 1, fpdst3
);
863 if (g_file_test (ascfile
, G_FILE_TEST_IS_REGULAR
))
865 if (g_file_test (digestfile
, G_FILE_TEST_IS_REGULAR
))
871 /* If updateinformation was provided, then we also generate the zsync file (after having signed the AppImage) */
872 if(updateinformation
!= NULL
){
873 gchar
*zsyncmake_path
= g_find_program_in_path ("zsyncmake");
875 fprintf (stderr
, "zsyncmake is not installed/bundled, skipping\n");
877 fprintf (stderr
, "zsyncmake is bundled and updateinformation is provided, "
878 "hence generating zsync file\n");
879 sprintf (command
, "%s %s -u %s", zsyncmake_path
, destination
, basename(destination
));
881 fprintf (stderr
, "%s\n", command
);
882 fp
= popen(command
, "r");
884 die("Failed to run zsyncmake command");
885 int exitstatus
= pclose(fp
);
886 if (WEXITSTATUS(exitstatus
) != 0)
887 die("zsyncmake command did not succeed");
891 fprintf (stderr
, "Success\n\n");
892 fprintf (stderr
, "Please consider submitting your AppImage to AppImageHub, the crowd-sourced\n");
893 fprintf (stderr
, "central directory of available AppImages, by opening a pull request\n");
894 fprintf (stderr
, "at https://github.com/AppImage/appimage.github.io\n");
897 /* If the first argument is a regular file, then we assume that we should unpack it */
898 if (g_file_test (remaining_args
[0], G_FILE_TEST_IS_REGULAR
)){
899 fprintf (stdout
, "%s is a file, assuming it is an AppImage and should be unpacked\n", remaining_args
[0]);
900 die("To be implemented");