/lib/live/mount/findiso/Applications for Manjaro
[appimagekit/gsi.git] / src / appimagetool.c
blobc239b56ba80313c1193864c91d6b7fbbadbd323c
1 /**************************************************************************
2 *
3 * Copyright (c) 2004-18 Simon Peter
4 *
5 * All Rights Reserved.
6 *
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
23 * THE SOFTWARE.
25 **************************************************************************/
27 #ident "AppImage by Simon Peter, http://appimage.org/"
29 #ifndef RELEASE_NAME
30 #define RELEASE_NAME "continuous build"
31 #endif
33 #include <glib.h>
34 #include <glib/gstdio.h>
35 #include <stdlib.h>
37 #include <stdio.h>
38 #include <argp.h>
40 #include <stdlib.h>
41 #include <fcntl.h>
42 #include "squashfuse.h"
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <sys/wait.h>
48 #include "binreloc.h"
50 #include <libgen.h>
52 #include <unistd.h>
53 #include <string.h>
54 #include <limits.h>
55 #include <stdbool.h>
57 #include "elf.h"
58 #include "getsection.h"
60 #ifdef __linux__
61 #define HAVE_BINARY_RUNTIME
62 extern int _binary_runtime_start;
63 extern int _binary_runtime_end;
64 #endif
66 enum fARCH {
67 fARCH_i386,
68 fARCH_x86_64,
69 fARCH_arm,
70 fARCH_aarch64
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);
96 exit(1);
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;
103 sqfs_traverse trv;
104 sqfs fs;
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)) {
114 if (!trv.dir_end) {
115 printf("%s\n", trv.path);
118 if (err)
119 die("sqfs_traverse_next error");
120 sqfs_traverse_close(&trv);
122 sqfs_fd_close(fs.fd);
123 return 0;
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) {
129 pid_t pid = fork();
131 if (pid == -1) {
132 // error, failed to fork()
133 return(-1);
134 } else if (pid > 0) {
135 int status;
136 waitpid(pid, &status, 0);
137 } else {
138 // we are the child
139 gchar *offset_string;
140 offset_string = g_strdup_printf("%i", offset);
142 char* args[32];
143 bool use_xz = strcmp(sqfs_comp, "xz") >= 0;
145 int i = 0;
146 #ifndef AUXILIARY_FILES_DESTINATION
147 args[i++] = "mksquashfs";
148 #else
149 args[i++] = pathToMksquashfs;
150 #endif
151 args[i++] = source;
152 args[i++] = destination;
153 args[i++] = "-offset";
154 args[i++] = offset_string;
155 args[i++] = "-comp";
157 if(use_xz)
158 args[i++] = "xz";
159 else
160 args[i++] = sqfs_comp;
162 args[i++] = "-root-owned";
163 args[i++] = "-noappend";
165 if(use_xz) {
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";
169 args[i++] = "100%";
170 args[i++] = "-b";
171 args[i++] = "16384";
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";
178 args[i++] = "-ef";
180 // avoid warning: assignment discards ‘const’ qualifier
181 char* buf = strdup(APPIMAGEIGNORE);
182 args[i++] = buf;
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);
189 return -1;
192 args[i++] = "-wildcards";
193 args[i++] = "-ef";
194 args[i++] = exclude_file;
197 args[i++] = 0;
199 #ifndef AUXILIARY_FILES_DESTINATION
200 execvp("mksquashfs", args);
201 #else
202 execvp(pathToMksquashfs, args);
203 #endif
205 perror("execlp"); // exec*() returns only on error
206 return -1; // exec never returns
208 return 0;
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) {
214 int statval;
215 int child_pid;
216 child_pid = fork();
217 if(child_pid == -1)
219 printf("could not fork! \n");
220 return 1;
222 else if(child_pid == 0)
224 execlp("desktop-file-validate", "desktop-file-validate", file, NULL);
226 else
228 waitpid(child_pid, &statval, WUNTRACED | WCONTINUED);
229 if(WIFEXITED(statval)){
230 return(WEXITSTATUS(statval));
233 return -1;
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)
255 char *sp = NULL;
257 if ((sp = strstr(line, search)) == NULL) {
258 return;
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) {
275 int countArchs = 0;
276 int i;
277 for (i = 0; i < 4; i++) {
278 countArchs += archs[i];
280 return countArchs;
283 gchar* getArchName(bool* archs) {
284 if (archs[fARCH_i386])
285 return "i386";
286 else if (archs[fARCH_x86_64])
287 return "x86_64";
288 else if (archs[fARCH_arm])
289 return "ARM";
290 else if (archs[fARCH_aarch64])
291 return "ARM_aarch64";
292 else
293 return "all";
296 void extract_arch_from_text(gchar *archname, const gchar* sourcename, bool* archs) {
297 if (archname) {
298 archname = g_strstrip(archname);
299 if (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;
312 if (verbose)
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;
316 if (verbose)
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;
320 if (verbose)
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;
324 if (verbose)
325 fprintf(stderr, "%s used for determining architecture ARM aarch64\n", sourcename);
331 void guess_arch_of_file(const gchar *archfile, bool* archs) {
332 char line[PATH_MAX];
333 char command[PATH_MAX];
334 sprintf(command, "/usr/bin/file -L -N -b %s", archfile);
335 FILE* fp = popen(command, "r");
336 if (fp == NULL)
337 die("Failed to run file command");
338 fgets(line, sizeof (line) - 1, fp);
339 pclose(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) {
344 GDir *dir;
345 gchar *full_name;
346 dir = g_dir_open(real_path, 0, NULL);
347 if (dir != NULL) {
348 const gchar *entry;
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);
358 g_dir_close(dir);
360 else {
361 g_warning("%s: %s", real_path, g_strerror(errno));
365 gchar* find_first_matching_file_nonrecursive(const gchar *real_path, const gchar *pattern) {
366 GDir *dir;
367 gchar *full_name;
368 dir = g_dir_open(real_path, 0, NULL);
369 if (dir != NULL) {
370 const gchar *entry;
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))
375 return(full_name);
378 g_dir_close(dir);
380 else {
381 g_warning("%s: %s", real_path, g_strerror(errno));
383 return NULL;
386 gchar* get_desktop_entry(GKeyFile *kf, char *key) {
387 gchar *value = g_key_file_get_string (kf, "Desktop Entry", key, NULL);
388 if (! value){
389 fprintf(stderr, "%s entry not found in desktop file\n", key);
391 return value;
394 bool readFile(char* filename, int* size, char** buffer) {
395 FILE* f = fopen(filename, "rb");
396 if (f==NULL) {
397 *buffer = 0;
398 *size = 0;
399 return false;
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);
408 fclose(f);
409 *size = (int)fsize;
410 *buffer = indata;
411 return TRUE;
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 },
432 { 0,0,0,0,0,0,0 }
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 */
440 char* version_env;
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");
453 char* travis_tag;
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 */
458 char* github_token;
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");
465 if(NULL!=owd_env){
466 int ret;
467 ret = chdir(owd_env);
468 if (ret != 0){
469 fprintf(stderr, "Could not cd into %s\n", owd_env);
470 exit(1);
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);
487 exit(1);
490 fprintf(
491 stderr,
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
497 if (showVersionOnly)
498 exit(0);
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");
508 #else
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");
514 exit(1);
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);
522 exit(1);
525 #endif
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");
530 if(! no_appstream)
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 */
542 if (list){
543 sfs_ls(remaining_args[0]);
544 exit(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)){
549 char *destination;
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");
558 if(verbose)
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");
574 if(verbose){
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) {
592 if (countArchs < 1)
593 fprintf(stderr, "Unable to guess the architecture of the AppDir source directory \"%s\"\n", remaining_args[0]);
594 else
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]),
597 die(" ...");
600 gchar* arch = getArchName(archs);
601 fprintf(stderr, "Using architecture %s\n", arch);
603 FILE *fp;
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, " ", "_");
608 if(verbose)
609 fprintf (stderr,"App name for filename: %s\n", app_name_for_filename);
611 if (remaining_args[1]) {
612 destination = remaining_args[1];
613 } else {
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);
619 if(verbose)
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;
648 } else {
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);
654 exit(1);
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);
667 if(res)
668 die("Could not symlink .DirIcon");
671 /* Check if AppStream upstream metadata is present in source AppDir */
672 if(! no_appstream){
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");
682 } else {
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);
690 if (ret != 0)
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);
699 if (ret != 0)
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");
710 int size = 0;
711 char* data = NULL;
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;
717 } else {
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;
723 #else
724 die("No runtime file was provided");
725 #endif
727 if (verbose)
728 printf("Size of the embedded runtime: %d bytes\n", size);
730 int result = sfs_mksquashfs(source, destination, size);
731 if(result != 0)
732 die("sfs_mksquashfs error");
734 fprintf (stderr, "Embedding ELF...\n");
735 FILE *fpdst = fopen(destination, "rb+");
736 if (fpdst == NULL) {
737 die("Not able to open the AppImage for writing, aborting");
740 fseek(fpdst, 0, SEEK_SET);
741 fwrite(data, size, 1, fpdst);
742 fclose(fpdst);
743 if (using_external_data)
744 free(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");
749 exit(1);
752 if(bintray_user != NULL){
753 if(bintray_repo != NULL){
754 char buf[1024];
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");
771 } else {
772 printf("which is expected since this is a pull request\n");
774 } else {
775 gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake");
776 if(zsyncmake_path){
777 char buf[1024];
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)) {
784 channel = "latest";
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);
791 } else {
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);
806 if(verbose)
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);
814 if(verbose) {
815 printf("ui_offset: %lu\n", ui_offset);
816 printf("ui_length: %lu\n", ui_length);
818 if(ui_offset == 0) {
819 die("Could not determine offset for updateinformation");
820 } else {
821 if(strlen(updateinformation)>ui_length)
822 die("updateinformation does not fit into segment, aborting");
823 FILE *fpdst2 = fopen(destination, "r+");
824 if (fpdst2 == NULL)
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);
831 fclose(fpdst2);
835 if(sign){
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");
840 if (!gpg2_path) {
841 gpg2_path = g_find_program_in_path ("gpg");
842 using_gpg = TRUE;
844 gchar *sha256sum_path = g_find_program_in_path ("sha256sum");
845 if (!sha256sum_path) {
846 sha256sum_path = g_find_program_in_path ("shasum");
847 using_shasum = 1;
849 if(!gpg2_path){
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");
854 } else {
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");
858 char *digestfile;
859 digestfile = br_strcat(destination, ".digest");
860 char *ascfile;
861 ascfile = br_strcat(destination, ".digest.asc");
862 if (g_file_test (digestfile, G_FILE_TEST_IS_REGULAR))
863 unlink(digestfile);
864 if (using_shasum)
865 sprintf (command, "%s -a256 %s", sha256sum_path, destination);
866 else
867 sprintf (command, "%s %s", sha256sum_path, destination);
868 if(verbose)
869 fprintf (stderr, "%s\n", command);
870 fp = popen(command, "r");
871 if (fp == NULL)
872 die(using_shasum ? "shasum command did not succeed" : "sha256sum command did not succeed");
873 char output[1024];
874 fgets(output, sizeof (output) - 1, fp);
875 if (verbose)
876 printf("%s: %s\n", using_shasum ? "shasum" : "sha256sum",
877 g_strsplit_set(output, " ", -1)[0]);
878 FILE *fpx = fopen(digestfile, "w");
879 if (fpx != NULL)
881 fputs(g_strsplit_set(output, " ", -1)[0], fpx);
882 fclose(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))
888 unlink(ascfile);
889 sprintf (command, "%s --detach-sign --armor %s %s", gpg2_path, sign_args ? sign_args : "", digestfile);
890 if(verbose)
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");
896 } else {
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);
900 if(verbose) {
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");
906 } else {
907 FILE *fpdst3 = fopen(destination, "r+");
908 if (fpdst3 == NULL)
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");
917 char byte;
918 while (!feof(fpsrc2))
920 fread(&byte, sizeof(char), 1, fpsrc2);
921 fwrite(&byte, sizeof(char), 1, fpdst3);
923 fclose(fpsrc2);
924 fclose(fpdst3);
926 if (g_file_test (ascfile, G_FILE_TEST_IS_REGULAR))
927 unlink(ascfile);
928 if (g_file_test (digestfile, G_FILE_TEST_IS_REGULAR))
929 unlink(digestfile);
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");
937 if(!zsyncmake_path){
938 fprintf (stderr, "zsyncmake is not installed/bundled, skipping\n");
939 } else {
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));
943 if(verbose)
944 fprintf (stderr, "%s\n", command);
945 fp = popen(command, "r");
946 if (fp == NULL)
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");
966 return 0;