1 /**************************************************************************
3 * Copyright (c) 2004-18 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 #define RELEASE_NAME "continuous build"
34 #include <glib/gstdio.h>
42 #include "squashfuse.h"
44 #include <sys/types.h>
58 #include "getsection.h"
61 #define HAVE_BINARY_RUNTIME
62 extern int _binary_runtime_start
;
63 extern int _binary_runtime_end
;
73 static gchar
const APPIMAGEIGNORE
[] = ".appimageignore";
74 static char _exclude_file_desc
[256];
76 static gboolean list
= FALSE
;
77 static gboolean verbose
= FALSE
;
78 static gboolean showVersionOnly
= FALSE
;
79 static gboolean sign
= FALSE
;
80 static gboolean no_appstream
= FALSE
;
81 gchar
**remaining_args
= NULL
;
82 gchar
*updateinformation
= NULL
;
83 static gboolean guessupdateinformation
= FALSE
;
84 gchar
*bintray_user
= NULL
;
85 gchar
*bintray_repo
= NULL
;
86 gchar
*sqfs_comp
= "gzip";
87 gchar
*exclude_file
= NULL
;
88 gchar
*runtime_file
= NULL
;
89 gchar
*sign_args
= NULL
;
90 gchar
*pathToMksquashfs
= NULL
;
92 // #####################################################################
94 static void die(const char *msg
) {
95 fprintf(stderr
, "%s\n", msg
);
99 /* Function that prints the contents of a squashfs file
100 * using libsquashfuse (#include "squashfuse.h") */
101 int sfs_ls(char* image
) {
102 sqfs_err err
= SQFS_OK
;
106 unsigned long fs_offset
= get_elf_size(image
);
108 if ((err
= sqfs_open_image(&fs
, image
, fs_offset
)))
109 die("sqfs_open_image error");
111 if ((err
= sqfs_traverse_open(&trv
, &fs
, sqfs_inode_root(&fs
))))
112 die("sqfs_traverse_open error");
113 while (sqfs_traverse_next(&trv
, &err
)) {
115 printf("%s\n", trv
.path
);
119 die("sqfs_traverse_next error");
120 sqfs_traverse_close(&trv
);
122 sqfs_fd_close(fs
.fd
);
126 /* Generate a squashfs filesystem using mksquashfs on the $PATH
127 * execlp(), execvp(), and execvpe() search on the $PATH */
128 int sfs_mksquashfs(char *source
, char *destination
, int offset
) {
132 // error, failed to fork()
134 } else if (pid
> 0) {
136 waitpid(pid
, &status
, 0);
139 gchar
*offset_string
;
140 offset_string
= g_strdup_printf("%i", offset
);
143 bool use_xz
= strcmp(sqfs_comp
, "xz") >= 0;
146 #ifndef AUXILIARY_FILES_DESTINATION
147 args
[i
++] = "mksquashfs";
149 args
[i
++] = pathToMksquashfs
;
152 args
[i
++] = destination
;
153 args
[i
++] = "-offset";
154 args
[i
++] = offset_string
;
160 args
[i
++] = sqfs_comp
;
162 args
[i
++] = "-root-owned";
163 args
[i
++] = "-noappend";
166 // https://jonathancarter.org/2015/04/06/squashfs-performance-testing/ says:
167 // improved performance by using a 16384 block size with a sacrifice of around 3% more squashfs image space
168 args
[i
++] = "-Xdict-size";
174 // check if ignore file exists and use it if possible
175 if(access(APPIMAGEIGNORE
, F_OK
) >= 0) {
176 printf("Including %s", APPIMAGEIGNORE
);
177 args
[i
++] = "-wildcards";
180 // avoid warning: assignment discards ‘const’ qualifier
181 char* buf
= strdup(APPIMAGEIGNORE
);
185 // if an exclude file has been passed on the command line, should be used, too
186 if(exclude_file
!= 0 && strlen(exclude_file
) > 0) {
187 if(access(exclude_file
, F_OK
) < 0) {
188 printf("WARNING: exclude file %s not found!", exclude_file
);
192 args
[i
++] = "-wildcards";
194 args
[i
++] = exclude_file
;
199 #ifndef AUXILIARY_FILES_DESTINATION
200 execvp("mksquashfs", args
);
202 execvp(pathToMksquashfs
, args
);
205 perror("execlp"); // exec*() returns only on error
206 return -1; // exec never returns
211 /* Validate desktop file using desktop-file-validate on the $PATH
212 * execlp(), execvp(), and execvpe() search on the $PATH */
213 int validate_desktop_file(char *file
) {
219 printf("could not fork! \n");
222 else if(child_pid
== 0)
224 execlp("desktop-file-validate", "desktop-file-validate", file
, NULL
);
228 waitpid(child_pid
, &statval
, WUNTRACED
| WCONTINUED
);
229 if(WIFEXITED(statval
)){
230 return(WEXITSTATUS(statval
));
236 /* Generate a squashfs filesystem
237 * The following would work if we link to mksquashfs.o after we renamed
238 * main() to mksquashfs_main() in mksquashfs.c but we don't want to actually do
239 * this because squashfs-tools is not under a permissive license
240 * i *nt sfs_mksquashfs(char *source, char *destination) {
241 * char *child_argv[5];
242 * child_argv[0] = NULL;
243 * child_argv[1] = source;
244 * child_argv[2] = destination;
245 * child_argv[3] = "-root-owned";
246 * child_argv[4] = "-noappend";
247 * mksquashfs_main(5, child_argv);
251 /* in-place modification of the string, and assuming the buffer pointed to by
252 * line is large enough to hold the resulting string*/
253 static void replacestr(char *line
, const char *search
, const char *replace
)
257 if ((sp
= strstr(line
, search
)) == NULL
) {
260 int search_len
= strlen(search
);
261 int replace_len
= strlen(replace
);
262 int tail_len
= strlen(sp
+search_len
);
264 memmove(sp
+replace_len
,sp
+search_len
,tail_len
+1);
265 memcpy(sp
, replace
, replace_len
);
267 /* Do it recursively again until no more work to do */
269 if ((sp
= strstr(line
, search
))) {
270 replacestr(line
, search
, replace
);
274 int count_archs(bool* archs
) {
277 for (i
= 0; i
< 4; i
++) {
278 countArchs
+= archs
[i
];
283 gchar
* getArchName(bool* archs
) {
284 if (archs
[fARCH_i386
])
286 else if (archs
[fARCH_x86_64
])
288 else if (archs
[fARCH_arm
])
290 else if (archs
[fARCH_aarch64
])
291 return "ARM_aarch64";
296 void extract_arch_from_text(gchar
*archname
, const gchar
* sourcename
, bool* archs
) {
298 archname
= g_strstrip(archname
);
300 replacestr(archname
, "-", "_");
301 replacestr(archname
, " ", "_");
302 if (g_ascii_strncasecmp("i386", archname
, 20) == 0
303 || g_ascii_strncasecmp("i486", archname
, 20) == 0
304 || g_ascii_strncasecmp("i586", archname
, 20) == 0
305 || g_ascii_strncasecmp("i686", archname
, 20) == 0
306 || g_ascii_strncasecmp("intel_80386", archname
, 20) == 0
307 || g_ascii_strncasecmp("intel_80486", archname
, 20) == 0
308 || g_ascii_strncasecmp("intel_80586", archname
, 20) == 0
309 || g_ascii_strncasecmp("intel_80686", archname
, 20) == 0
311 archs
[fARCH_i386
] = 1;
313 fprintf(stderr
, "%s used for determining architecture i386\n", sourcename
);
314 } else if (g_ascii_strncasecmp("x86_64", archname
, 20) == 0) {
315 archs
[fARCH_x86_64
] = 1;
317 fprintf(stderr
, "%s used for determining architecture x86_64\n", sourcename
);
318 } else if (g_ascii_strncasecmp("arm", archname
, 20) == 0) {
319 archs
[fARCH_arm
] = 1;
321 fprintf(stderr
, "%s used for determining architecture ARM\n", sourcename
);
322 } else if (g_ascii_strncasecmp("arm_aarch64", archname
, 20) == 0) {
323 archs
[fARCH_aarch64
] = 1;
325 fprintf(stderr
, "%s used for determining architecture ARM aarch64\n", sourcename
);
331 void guess_arch_of_file(const gchar
*archfile
, bool* archs
) {
333 char command
[PATH_MAX
];
334 sprintf(command
, "/usr/bin/file -L -N -b %s", archfile
);
335 FILE* fp
= popen(command
, "r");
337 die("Failed to run file command");
338 fgets(line
, sizeof (line
) - 1, fp
);
340 extract_arch_from_text(g_strsplit_set(line
, ",", -1)[1], archfile
, archs
);
343 void find_arch(const gchar
*real_path
, const gchar
*pattern
, bool* archs
) {
346 dir
= g_dir_open(real_path
, 0, NULL
);
349 while ((entry
= g_dir_read_name(dir
)) != NULL
) {
350 full_name
= g_build_filename(real_path
, entry
, NULL
);
351 if (g_file_test(full_name
, G_FILE_TEST_IS_SYMLINK
)) {
352 } else if (g_file_test(full_name
, G_FILE_TEST_IS_DIR
)) {
353 find_arch(full_name
, pattern
, archs
);
354 } else if (g_file_test(full_name
, G_FILE_TEST_IS_EXECUTABLE
) || g_pattern_match_simple(pattern
, entry
) ) {
355 guess_arch_of_file(full_name
, archs
);
361 g_warning("%s: %s", real_path
, g_strerror(errno
));
365 gchar
* find_first_matching_file_nonrecursive(const gchar
*real_path
, const gchar
*pattern
) {
368 dir
= g_dir_open(real_path
, 0, NULL
);
371 while ((entry
= g_dir_read_name(dir
)) != NULL
) {
372 full_name
= g_build_filename(real_path
, entry
, NULL
);
373 if (g_file_test(full_name
, G_FILE_TEST_IS_REGULAR
)) {
374 if(g_pattern_match_simple(pattern
, entry
))
381 g_warning("%s: %s", real_path
, g_strerror(errno
));
386 gchar
* get_desktop_entry(GKeyFile
*kf
, char *key
) {
387 gchar
*value
= g_key_file_get_string (kf
, "Desktop Entry", key
, NULL
);
389 fprintf(stderr
, "%s entry not found in desktop file\n", key
);
394 bool readFile(char* filename
, int* size
, char** buffer
) {
395 FILE* f
= fopen(filename
, "rb");
402 fseek(f
, 0, SEEK_END
);
403 long fsize
= ftell(f
);
404 fseek(f
, 0, SEEK_SET
);
406 char *indata
= malloc(fsize
);
407 fread(indata
, fsize
, 1, f
);
414 // #####################################################################
416 static GOptionEntry entries
[] =
418 { "list", 'l', 0, G_OPTION_ARG_NONE
, &list
, "List files in SOURCE AppImage", NULL
},
419 { "updateinformation", 'u', 0, G_OPTION_ARG_STRING
, &updateinformation
, "Embed update information STRING; if zsyncmake is installed, generate zsync file", NULL
},
420 { "guess", 'g', 0, G_OPTION_ARG_NONE
, &guessupdateinformation
, "Guess update information based on Travis CI environment variables", NULL
},
421 { "bintray-user", 0, 0, G_OPTION_ARG_STRING
, &bintray_user
, "Bintray user name", NULL
},
422 { "bintray-repo", 0, 0, G_OPTION_ARG_STRING
, &bintray_repo
, "Bintray repository", NULL
},
423 { "version", 0, 0, G_OPTION_ARG_NONE
, &showVersionOnly
, "Show version number", NULL
},
424 { "verbose", 'v', 0, G_OPTION_ARG_NONE
, &verbose
, "Produce verbose output", NULL
},
425 { "sign", 's', 0, G_OPTION_ARG_NONE
, &sign
, "Sign with gpg[2]", NULL
},
426 { "comp", 0, 0, G_OPTION_ARG_STRING
, &sqfs_comp
, "Squashfs compression", NULL
},
427 { "no-appstream", 'n', 0, G_OPTION_ARG_NONE
, &no_appstream
, "Do not check AppStream metadata", NULL
},
428 { "exclude-file", 0, 0, G_OPTION_ARG_STRING
, &exclude_file
, _exclude_file_desc
, NULL
},
429 { "runtime-file", 0, 0, G_OPTION_ARG_STRING
, &runtime_file
, "Runtime file to use", NULL
},
430 { "sign-args", 0, 0, G_OPTION_ARG_STRING
, &sign_args
, "Extra arguments to use when signing with gpg[2]", NULL
},
431 { G_OPTION_REMAINING
, 0, 0, G_OPTION_ARG_FILENAME_ARRAY
, &remaining_args
, NULL
, NULL
},
436 main (int argc
, char *argv
[])
438 /* Parse VERSION environment variable.
439 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
441 version_env
= getenv("VERSION");
443 /* Parse Travis CI environment variables.
444 * https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
445 * TRAVIS_COMMIT: The commit that the current build is testing.
446 * TRAVIS_REPO_SLUG: The slug (in form: owner_name/repo_name) of the repository currently being built.
447 * TRAVIS_TAG: If the current build is for a git tag, this variable is set to the tag’s name.
448 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
449 // char* travis_commit;
450 // travis_commit = getenv("TRAVIS_COMMIT");
451 char* travis_repo_slug
;
452 travis_repo_slug
= getenv("TRAVIS_REPO_SLUG");
454 travis_tag
= getenv("TRAVIS_TAG");
455 char* travis_pull_request
;
456 travis_pull_request
= getenv("TRAVIS_PULL_REQUEST");
457 /* https://github.com/probonopd/uploadtool */
459 github_token
= getenv("GITHUB_TOKEN");
461 /* Parse OWD environment variable.
462 * If it is available then cd there. It is the original CWD prior to running AppRun */
463 char* owd_env
= NULL
;
464 owd_env
= getenv("OWD");
467 ret
= chdir(owd_env
);
469 fprintf(stderr
, "Could not cd into %s\n", owd_env
);
474 GError
*error
= NULL
;
475 GOptionContext
*context
;
476 char command
[PATH_MAX
];
478 // initialize help text of argument
479 sprintf(_exclude_file_desc
, "Uses given file as exclude file for mksquashfs, in addition to %s.", APPIMAGEIGNORE
);
481 context
= g_option_context_new ("SOURCE [DESTINATION] - Generate, extract, and inspect AppImages");
482 g_option_context_add_main_entries (context
, entries
, NULL
);
483 // g_option_context_add_group (context, gtk_get_option_group (TRUE));
484 if (!g_option_context_parse (context
, &argc
, &argv
, &error
))
486 fprintf(stderr
, "Option parsing failed: %s\n", error
->message
);
492 "appimagetool, %s (commit %s), build %s built on %s\n",
493 RELEASE_NAME
, GIT_COMMIT
, BUILD_NUMBER
, BUILD_DATE
496 // always show version, but exit immediately if only the version number was requested
500 if(!((0 == strcmp(sqfs_comp
, "gzip")) || (0 ==strcmp(sqfs_comp
, "xz"))))
501 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.");
502 /* Check for dependencies here. Better fail early if they are not present. */
503 if(! g_find_program_in_path ("file"))
504 die("file command is missing but required, please install it");
505 #ifndef AUXILIARY_FILES_DESTINATION
506 if(! g_find_program_in_path ("mksquashfs"))
507 die("mksquashfs command is missing but required, please install it");
510 // build path relative to appimagetool binary
511 char *appimagetoolDirectory
= dirname(realpath("/proc/self/exe", NULL
));
512 if (!appimagetoolDirectory
) {
513 g_print("Could not access /proc/self/exe\n");
517 pathToMksquashfs
= g_build_filename(appimagetoolDirectory
, "..", AUXILIARY_FILES_DESTINATION
, "mksquashfs", NULL
);
519 if (!g_file_test(pathToMksquashfs
, G_FILE_TEST_EXISTS
| G_FILE_TEST_IS_EXECUTABLE
)) {
520 g_printf("No such file or directory: %s\n", pathToMksquashfs
);
521 g_free(pathToMksquashfs
);
526 if(! g_find_program_in_path ("desktop-file-validate"))
527 die("desktop-file-validate command is missing, please install it");
528 if(! g_find_program_in_path ("zsyncmake"))
529 g_print("WARNING: zsyncmake command is missing, please install it if you want to use binary delta updates\n");
531 if(! g_find_program_in_path ("appstreamcli"))
532 g_print("WARNING: appstreamcli command is missing, please install it if you want to use AppStream metadata\n");
533 if(! g_find_program_in_path ("gpg2") && ! g_find_program_in_path ("gpg"))
534 g_print("WARNING: gpg2 or gpg command is missing, please install it if you want to create digital signatures\n");
535 if(! g_find_program_in_path ("sha256sum") && ! g_find_program_in_path ("shasum"))
536 g_print("WARNING: sha256sum or shasum command is missing, please install it if you want to create digital signatures\n");
538 if(!&remaining_args
[0])
539 die("SOURCE is missing");
541 /* If in list mode */
543 sfs_ls(remaining_args
[0]);
547 /* If the first argument is a directory, then we assume that we should package it */
548 if (g_file_test (remaining_args
[0], G_FILE_TEST_IS_DIR
)){
550 char source
[PATH_MAX
];
551 realpath(remaining_args
[0], source
);
553 /* Check if *.desktop file is present in source AppDir */
554 gchar
*desktop_file
= find_first_matching_file_nonrecursive(source
, "*.desktop");
555 if(desktop_file
== NULL
){
556 die("Desktop file not found, aborting");
559 fprintf (stdout
, "Desktop file: %s\n", desktop_file
);
561 if(g_find_program_in_path ("desktop-file-validate")) {
562 if(validate_desktop_file(desktop_file
) != 0){
563 fprintf(stderr
, "ERROR: Desktop file contains errors. Please fix them. Please see\n");
564 fprintf(stderr
, " https://standards.freedesktop.org/desktop-entry-spec/latest/\n");
565 die(" for more information.");
569 /* Read information from .desktop file */
570 GKeyFile
*kf
= g_key_file_new ();
571 if (!g_key_file_load_from_file (kf
, desktop_file
, 0, NULL
))
572 die(".desktop file cannot be parsed");
575 fprintf (stderr
,"Name: %s\n", get_desktop_entry(kf
, "Name"));
576 fprintf (stderr
,"Icon: %s\n", get_desktop_entry(kf
, "Icon"));
577 fprintf (stderr
,"Exec: %s\n", get_desktop_entry(kf
, "Exec"));
578 fprintf (stderr
,"Comment: %s\n", get_desktop_entry(kf
, "Comment"));
579 fprintf (stderr
,"Type: %s\n", get_desktop_entry(kf
, "Type"));
580 fprintf (stderr
,"Categories: %s\n", get_desktop_entry(kf
, "Categories"));
583 /* Determine the architecture */
584 bool archs
[4] = {0, 0, 0, 0};
585 extract_arch_from_text(getenv("ARCH"), "Environmental variable ARCH", archs
);
586 if (count_archs(archs
) != 1) {
587 /* If no $ARCH variable is set check a file */
588 /* We use the next best .so that we can find to determine the architecture */
589 find_arch(source
, "*.so.*", archs
);
590 int countArchs
= count_archs(archs
);
591 if (countArchs
!= 1) {
593 fprintf(stderr
, "Unable to guess the architecture of the AppDir source directory \"%s\"\n", remaining_args
[0]);
595 fprintf(stderr
, "More than one architectures were found of the AppDir source directory \"%s\"\n", remaining_args
[0]);
596 fprintf(stderr
, "A valid architecture with the ARCH environmental variable should be provided\ne.g. ARCH=x86_64 %s", argv
[0]),
600 gchar
* arch
= getArchName(archs
);
601 fprintf(stderr
, "Using architecture %s\n", arch
);
604 char app_name_for_filename
[PATH_MAX
];
605 sprintf(app_name_for_filename
, "%s", get_desktop_entry(kf
, "Name"));
606 replacestr(app_name_for_filename
, " ", "_");
609 fprintf (stderr
,"App name for filename: %s\n", app_name_for_filename
);
611 if (remaining_args
[1]) {
612 destination
= remaining_args
[1];
614 /* No destination has been specified, to let's construct one
615 * TODO: Find out the architecture and use a $VERSION that might be around in the env */
616 char dest_path
[PATH_MAX
];
617 sprintf (dest_path
, "%s-%s.AppImage", app_name_for_filename
, arch
);
620 fprintf (stderr
,"dest_path: %s\n", dest_path
);
622 if (version_env
!=NULL
)
623 sprintf (dest_path
, "%s-%s-%s.AppImage", app_name_for_filename
, version_env
, arch
);
625 destination
= dest_path
;
626 replacestr(destination
, " ", "_");
628 fprintf (stdout
, "%s should be packaged as %s\n", source
, destination
);
629 /* Check if the Icon file is how it is expected */
630 gchar
* icon_name
= get_desktop_entry(kf
, "Icon");
631 gchar
* icon_file_path
= NULL
;
632 gchar
* icon_file_png
;
633 gchar
* icon_file_svg
;
634 gchar
* icon_file_svgz
;
635 gchar
* icon_file_xpm
;
636 icon_file_png
= g_strdup_printf("%s/%s.png", source
, icon_name
);
637 icon_file_svg
= g_strdup_printf("%s/%s.svg", source
, icon_name
);
638 icon_file_svgz
= g_strdup_printf("%s/%s.svgz", source
, icon_name
);
639 icon_file_xpm
= g_strdup_printf("%s/%s.xpm", source
, icon_name
);
640 if (g_file_test(icon_file_png
, G_FILE_TEST_IS_REGULAR
)) {
641 icon_file_path
= icon_file_png
;
642 } else if(g_file_test(icon_file_svg
, G_FILE_TEST_IS_REGULAR
)) {
643 icon_file_path
= icon_file_svg
;
644 } else if(g_file_test(icon_file_svgz
, G_FILE_TEST_IS_REGULAR
)) {
645 icon_file_path
= icon_file_svgz
;
646 } else if(g_file_test(icon_file_xpm
, G_FILE_TEST_IS_REGULAR
)) {
647 icon_file_path
= icon_file_xpm
;
649 fprintf (stderr
, "%s{.png,.svg,.svgz,.xpm} defined in desktop file but not found\n", icon_name
);
650 fprintf (stderr
, "For example, you could put a 256x256 pixel png into\n");
651 gchar
*icon_name_with_png
= g_strconcat(icon_name
, ".png", NULL
);
652 gchar
*example_path
= g_build_filename(source
, "/", icon_name_with_png
, NULL
);
653 fprintf (stderr
, "%s\n", example_path
);
657 /* Check if .DirIcon is present in source AppDir */
658 gchar
*diricon_path
= g_build_filename(source
, ".DirIcon", NULL
);
660 if (! g_file_test(diricon_path
, G_FILE_TEST_EXISTS
)){
661 fprintf (stderr
, "Deleting pre-existing .DirIcon\n");
662 g_unlink(diricon_path
);
664 if (! g_file_test(diricon_path
, G_FILE_TEST_IS_REGULAR
)){
665 fprintf (stderr
, "Creating .DirIcon symlink based on information from desktop file\n");
666 int res
= symlink(basename(icon_file_path
), diricon_path
);
668 die("Could not symlink .DirIcon");
671 /* Check if AppStream upstream metadata is present in source AppDir */
673 char application_id
[PATH_MAX
];
674 sprintf (application_id
, "%s", basename(desktop_file
));
675 replacestr(application_id
, ".desktop", ".appdata.xml");
676 gchar
*appdata_path
= g_build_filename(source
, "/usr/share/metainfo/", application_id
, NULL
);
677 if (! g_file_test(appdata_path
, G_FILE_TEST_IS_REGULAR
)){
678 fprintf (stderr
, "WARNING: AppStream upstream metadata is missing, please consider creating it\n");
679 fprintf (stderr
, " in usr/share/metainfo/%s\n", application_id
);
680 fprintf (stderr
, " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps\n");
681 fprintf (stderr
, " for more information or use the generator at http://output.jsbin.com/qoqukof.\n");
683 fprintf (stderr
, "AppStream upstream metadata found in usr/share/metainfo/%s\n", application_id
);
684 /* Use ximion's appstreamcli to make sure that desktop file and appdata match together */
685 if(g_find_program_in_path ("appstreamcli")) {
686 sprintf (command
, "%s validate-tree %s", g_find_program_in_path ("appstreamcli"), source
);
687 g_print("Trying to validate AppStream information with the appstreamcli tool\n");
688 g_print("In case of issues, please refer to https://github.com/ximion/appstream\n");
689 int ret
= system(command
);
691 die("Failed to validate AppStream information with appstreamcli");
693 /* It seems that hughsie's appstream-util does additional validations */
694 if(g_find_program_in_path ("appstream-util")) {
695 sprintf (command
, "%s validate-relax %s", g_find_program_in_path ("appstream-util"), appdata_path
);
696 g_print("Trying to validate AppStream information with the appstream-util tool\n");
697 g_print("In case of issues, please refer to https://github.com/hughsie/appstream-glib\n");
698 int ret
= system(command
);
700 die("Failed to validate AppStream information with appstream-util");
705 /* Upstream mksquashfs can currently not start writing at an offset,
706 * so we need a patched one. https://github.com/plougher/squashfs-tools/pull/13
707 * should hopefully change that. */
709 fprintf (stderr
, "Generating squashfs...\n");
712 bool using_external_data
= false;
713 if (runtime_file
!= NULL
) {
714 if (!readFile(runtime_file
, &size
, &data
))
715 die("Unable to load provided runtime file");
716 using_external_data
= true;
718 #ifdef HAVE_BINARY_RUNTIME
719 /* runtime is embedded into this executable
720 * http://stupefydeveloper.blogspot.de/2008/08/cc-embed-binary-data-into-elf.html */
721 size
= (int)((void *)&_binary_runtime_end
- (void *)&_binary_runtime_start
);
722 data
= (char *)&_binary_runtime_start
;
724 die("No runtime file was provided");
728 printf("Size of the embedded runtime: %d bytes\n", size
);
730 int result
= sfs_mksquashfs(source
, destination
, size
);
732 die("sfs_mksquashfs error");
734 fprintf (stderr
, "Embedding ELF...\n");
735 FILE *fpdst
= fopen(destination
, "rb+");
737 die("Not able to open the AppImage for writing, aborting");
740 fseek(fpdst
, 0, SEEK_SET
);
741 fwrite(data
, size
, 1, fpdst
);
743 if (using_external_data
)
746 fprintf (stderr
, "Marking the AppImage as executable...\n");
747 if (chmod (destination
, 0755) < 0) {
748 printf("Could not set executable bit, aborting\n");
752 if(bintray_user
!= NULL
){
753 if(bintray_repo
!= NULL
){
755 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
);
756 updateinformation
= buf
;
757 printf("%s\n", updateinformation
);
761 /* If the user has not provided update information but we know this is a Travis CI build,
762 * then fill in update information based on TRAVIS_REPO_SLUG */
763 if(guessupdateinformation
){
764 if(!travis_repo_slug
){
765 printf("Cannot guess update information since $TRAVIS_REPO_SLUG is missing\n");
766 } else if(!github_token
) {
767 printf("Will not guess update information since $GITHUB_TOKEN is missing,\n");
768 if(0 != strcmp(travis_pull_request
, "false")){
769 printf("please set it in the Travis CI Repository Settings for this project.\n");
770 printf("You can get one from https://github.com/settings/tokens\n");
772 printf("which is expected since this is a pull request\n");
775 gchar
*zsyncmake_path
= g_find_program_in_path ("zsyncmake");
778 gchar
**parts
= g_strsplit (travis_repo_slug
, "/", 2);
779 /* https://github.com/AppImage/AppImageSpec/blob/master/draft.md#github-releases
780 * gh-releases-zsync|probono|AppImages|latest|Subsurface*-x86_64.AppImage.zsync */
781 gchar
*channel
= "continuous";
782 if(travis_tag
!= NULL
){
783 if((strcmp(travis_tag
, "") != 0) && (strcmp(travis_tag
, "continuous") != 0)) {
787 sprintf(buf
, "gh-releases-zsync|%s|%s|%s|%s*-%s.AppImage.zsync", parts
[0], parts
[1], channel
, app_name_for_filename
, arch
);
788 updateinformation
= buf
;
789 printf("Guessing update information based on $TRAVIS_TAG=%s and $TRAVIS_REPO_SLUG=%s\n", travis_tag
, travis_repo_slug
);
790 printf("%s\n", updateinformation
);
792 printf("Will not guess update information since zsyncmake is missing\n");
797 /* If updateinformation was provided, then we check and embed it */
798 if(updateinformation
!= NULL
){
799 if(!g_str_has_prefix(updateinformation
,"zsync|"))
800 if(!g_str_has_prefix(updateinformation
,"bintray-zsync|"))
801 if(!g_str_has_prefix(updateinformation
,"gh-releases-zsync|"))
802 die("The provided updateinformation is not in a recognized format");
804 gchar
**ui_type
= g_strsplit_set(updateinformation
, "|", -1);
807 printf("updateinformation type: %s\n", ui_type
[0]);
808 /* TODO: Further checking of the updateinformation */
811 unsigned long ui_offset
= 0;
812 unsigned long ui_length
= 0;
813 get_elf_section_offset_and_length(destination
, ".upd_info", &ui_offset
, &ui_length
);
815 printf("ui_offset: %lu\n", ui_offset
);
816 printf("ui_length: %lu\n", ui_length
);
819 die("Could not determine offset for updateinformation");
821 if(strlen(updateinformation
)>ui_length
)
822 die("updateinformation does not fit into segment, aborting");
823 FILE *fpdst2
= fopen(destination
, "r+");
825 die("Not able to open the destination file for writing, aborting");
826 fseek(fpdst2
, ui_offset
, SEEK_SET
);
827 // fseek(fpdst2, ui_offset, SEEK_SET);
828 // fwrite(0x00, 1, 1024, fpdst); // FIXME: Segfaults; why?
829 // fseek(fpdst, ui_offset, SEEK_SET);
830 fwrite(updateinformation
, strlen(updateinformation
), 1, fpdst2
);
836 bool using_gpg
= FALSE
;
837 bool using_shasum
= FALSE
;
838 /* The user has indicated that he wants to sign */
839 gchar
*gpg2_path
= g_find_program_in_path ("gpg2");
841 gpg2_path
= g_find_program_in_path ("gpg");
844 gchar
*sha256sum_path
= g_find_program_in_path ("sha256sum");
845 if (!sha256sum_path
) {
846 sha256sum_path
= g_find_program_in_path ("shasum");
850 fprintf (stderr
, "gpg2 or gpg is not installed, cannot sign\n");
852 else if(!sha256sum_path
) {
853 fprintf(stderr
, "sha256sum or shasum is not installed, cannot sign\n");
855 fprintf(stderr
, "%s and %s are installed and user requested to sign, "
856 "hence signing\n", using_gpg
? "gpg" : "gpg2",
857 using_shasum
? "shasum" : "sha256sum");
859 digestfile
= br_strcat(destination
, ".digest");
861 ascfile
= br_strcat(destination
, ".digest.asc");
862 if (g_file_test (digestfile
, G_FILE_TEST_IS_REGULAR
))
865 sprintf (command
, "%s -a256 %s", sha256sum_path
, destination
);
867 sprintf (command
, "%s %s", sha256sum_path
, destination
);
869 fprintf (stderr
, "%s\n", command
);
870 fp
= popen(command
, "r");
872 die(using_shasum
? "shasum command did not succeed" : "sha256sum command did not succeed");
874 fgets(output
, sizeof (output
) - 1, fp
);
876 printf("%s: %s\n", using_shasum
? "shasum" : "sha256sum",
877 g_strsplit_set(output
, " ", -1)[0]);
878 FILE *fpx
= fopen(digestfile
, "w");
881 fputs(g_strsplit_set(output
, " ", -1)[0], fpx
);
884 int shasum_exit_status
= pclose(fp
);
885 if(WEXITSTATUS(shasum_exit_status
) != 0)
886 die(using_shasum
? "shasum command did not succeed" : "sha256sum command did not succeed");
887 if (g_file_test (ascfile
, G_FILE_TEST_IS_REGULAR
))
889 sprintf (command
, "%s --detach-sign --armor %s %s", gpg2_path
, sign_args
? sign_args
: "", digestfile
);
891 fprintf (stderr
, "%s\n", command
);
892 fp
= popen(command
, "r");
893 int gpg_exit_status
= pclose(fp
);
894 if(WEXITSTATUS(gpg_exit_status
) != 0) {
895 fprintf (stderr
, "ERROR: %s command did not succeed, could not sign, continuing\n", using_gpg
? "gpg" : "gpg2");
897 unsigned long sig_offset
= 0;
898 unsigned long sig_length
= 0;
899 get_elf_section_offset_and_length(destination
, ".sha256_sig", &sig_offset
, &sig_length
);
901 printf("sig_offset: %lu\n", sig_offset
);
902 printf("sig_length: %lu\n", sig_length
);
904 if(sig_offset
== 0) {
905 die("Could not determine offset for signature");
907 FILE *fpdst3
= fopen(destination
, "r+");
909 die("Not able to open the destination file for writing, aborting");
910 // if(strlen(updateinformation)>sig_length)
911 // die("signature does not fit into segment, aborting");
912 fseek(fpdst3
, sig_offset
, SEEK_SET
);
913 FILE *fpsrc2
= fopen(ascfile
, "rb");
914 if (fpsrc2
== NULL
) {
915 die("Not able to open the asc file for reading, aborting");
918 while (!feof(fpsrc2
))
920 fread(&byte
, sizeof(char), 1, fpsrc2
);
921 fwrite(&byte
, sizeof(char), 1, fpdst3
);
926 if (g_file_test (ascfile
, G_FILE_TEST_IS_REGULAR
))
928 if (g_file_test (digestfile
, G_FILE_TEST_IS_REGULAR
))
934 /* If updateinformation was provided, then we also generate the zsync file (after having signed the AppImage) */
935 if(updateinformation
!= NULL
){
936 gchar
*zsyncmake_path
= g_find_program_in_path ("zsyncmake");
938 fprintf (stderr
, "zsyncmake is not installed/bundled, skipping\n");
940 fprintf (stderr
, "zsyncmake is available and updateinformation is provided, "
941 "hence generating zsync file\n");
942 sprintf (command
, "%s %s -u %s", zsyncmake_path
, destination
, basename(destination
));
944 fprintf (stderr
, "%s\n", command
);
945 fp
= popen(command
, "r");
947 die("Failed to run zsyncmake command");
948 int exitstatus
= pclose(fp
);
949 if (WEXITSTATUS(exitstatus
) != 0)
950 die("zsyncmake command did not succeed");
954 fprintf (stderr
, "Success\n\n");
955 fprintf (stderr
, "Please consider submitting your AppImage to AppImageHub, the crowd-sourced\n");
956 fprintf (stderr
, "central directory of available AppImages, by opening a pull request\n");
957 fprintf (stderr
, "at https://github.com/AppImage/appimage.github.io\n");
960 /* If the first argument is a regular file, then we assume that we should unpack it */
961 if (g_file_test (remaining_args
[0], G_FILE_TEST_IS_REGULAR
)){
962 fprintf (stdout
, "%s is a file, assuming it is an AppImage and should be unpacked\n", remaining_args
[0]);
963 die("To be implemented");