Merge pull request #865 from AppImage/fix_appimagetool_names_architecture_wrongly
[appimagekit.git] / src / appimagetool.c
blob7b235e672d5460228d1503dfe2d43904117045d5
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 "appimage/appimage.h"
59 #ifdef __linux__
60 #define HAVE_BINARY_RUNTIME
61 extern char runtime[];
62 extern unsigned int runtime_len;
63 #endif
65 enum fARCH {
66 fARCH_i386,
67 fARCH_x86_64,
68 fARCH_arm,
69 fARCH_aarch64
72 static gchar const APPIMAGEIGNORE[] = ".appimageignore";
73 static char _exclude_file_desc[256];
75 static gboolean list = FALSE;
76 static gboolean verbose = FALSE;
77 static gboolean showVersionOnly = FALSE;
78 static gboolean sign = FALSE;
79 static gboolean no_appstream = FALSE;
80 gchar **remaining_args = NULL;
81 gchar *updateinformation = NULL;
82 static gboolean guessupdateinformation = FALSE;
83 gchar *bintray_user = NULL;
84 gchar *bintray_repo = NULL;
85 gchar *sqfs_comp = "gzip";
86 gchar *exclude_file = NULL;
87 gchar *runtime_file = NULL;
88 gchar *sign_args = NULL;
89 gchar *sign_key = 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 ssize_t fs_offset = appimage_get_elf_size(image);
108 // error check
109 if (fs_offset < 0)
110 die("failed to read elf size");
112 if ((err = sqfs_open_image(&fs, image, fs_offset)))
113 die("sqfs_open_image error");
115 if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs))))
116 die("sqfs_traverse_open error");
117 while (sqfs_traverse_next(&trv, &err)) {
118 if (!trv.dir_end) {
119 printf("%s\n", trv.path);
122 if (err)
123 die("sqfs_traverse_next error");
124 sqfs_traverse_close(&trv);
126 sqfs_fd_close(fs.fd);
127 return 0;
130 /* Generate a squashfs filesystem using mksquashfs on the $PATH
131 * execlp(), execvp(), and execvpe() search on the $PATH */
132 int sfs_mksquashfs(char *source, char *destination, int offset) {
133 pid_t pid = fork();
135 if (pid == -1) {
136 // error, failed to fork()
137 return(-1);
138 } else if (pid > 0) {
139 int status;
140 waitpid(pid, &status, 0);
141 } else {
142 // we are the child
143 gchar* offset_string;
144 offset_string = g_strdup_printf("%i", offset);
146 char* args[32];
147 bool use_xz = strcmp(sqfs_comp, "xz") >= 0;
149 int i = 0;
150 #ifndef AUXILIARY_FILES_DESTINATION
151 args[i++] = "mksquashfs";
152 #else
153 args[i++] = pathToMksquashfs;
154 #endif
155 args[i++] = source;
156 args[i++] = destination;
157 args[i++] = "-offset";
158 args[i++] = offset_string;
159 args[i++] = "-comp";
161 if (use_xz)
162 args[i++] = "xz";
163 else
164 args[i++] = sqfs_comp;
166 args[i++] = "-root-owned";
167 args[i++] = "-noappend";
169 if (use_xz) {
170 // https://jonathancarter.org/2015/04/06/squashfs-performance-testing/ says:
171 // improved performance by using a 16384 block size with a sacrifice of around 3% more squashfs image space
172 args[i++] = "-Xdict-size";
173 args[i++] = "100%";
174 args[i++] = "-b";
175 args[i++] = "16384";
178 // check if ignore file exists and use it if possible
179 if (access(APPIMAGEIGNORE, F_OK) >= 0) {
180 printf("Including %s", APPIMAGEIGNORE);
181 args[i++] = "-wildcards";
182 args[i++] = "-ef";
184 // avoid warning: assignment discards ‘const’ qualifier
185 char* buf = strdup(APPIMAGEIGNORE);
186 args[i++] = buf;
189 // if an exclude file has been passed on the command line, should be used, too
190 if (exclude_file != 0 && strlen(exclude_file) > 0) {
191 if (access(exclude_file, F_OK) < 0) {
192 printf("WARNING: exclude file %s not found!", exclude_file);
193 return -1;
196 args[i++] = "-wildcards";
197 args[i++] = "-ef";
198 args[i++] = exclude_file;
201 args[i++] = "-mkfs-fixed-time";
202 args[i++] = "0";
204 args[i++] = 0;
206 if (verbose) {
207 printf("mksquashfs commandline: ");
208 for (char** t = args; *t != 0; t++) {
209 printf("%s ", *t);
211 printf("\n");
214 #ifndef AUXILIARY_FILES_DESTINATION
215 execvp("mksquashfs", args);
216 #else
217 execvp(pathToMksquashfs, args);
218 #endif
220 perror("execlp"); // exec*() returns only on error
221 return -1; // exec never returns
223 return 0;
226 /* Validate desktop file using desktop-file-validate on the $PATH
227 * execlp(), execvp(), and execvpe() search on the $PATH */
228 int validate_desktop_file(char *file) {
229 int statval;
230 int child_pid;
231 child_pid = fork();
232 if(child_pid == -1)
234 printf("could not fork! \n");
235 return 1;
237 else if(child_pid == 0)
239 execlp("desktop-file-validate", "desktop-file-validate", file, NULL);
241 else
243 waitpid(child_pid, &statval, WUNTRACED | WCONTINUED);
244 if(WIFEXITED(statval)){
245 return(WEXITSTATUS(statval));
248 return -1;
251 /* Generate a squashfs filesystem
252 * The following would work if we link to mksquashfs.o after we renamed
253 * main() to mksquashfs_main() in mksquashfs.c but we don't want to actually do
254 * this because squashfs-tools is not under a permissive license
255 * i *nt sfs_mksquashfs(char *source, char *destination) {
256 * char *child_argv[5];
257 * child_argv[0] = NULL;
258 * child_argv[1] = source;
259 * child_argv[2] = destination;
260 * child_argv[3] = "-root-owned";
261 * child_argv[4] = "-noappend";
262 * mksquashfs_main(5, child_argv);
266 /* in-place modification of the string, and assuming the buffer pointed to by
267 * line is large enough to hold the resulting string*/
268 static void replacestr(char *line, const char *search, const char *replace)
270 char *sp = NULL;
272 if ((sp = strstr(line, search)) == NULL) {
273 return;
275 int search_len = strlen(search);
276 int replace_len = strlen(replace);
277 int tail_len = strlen(sp+search_len);
279 memmove(sp+replace_len,sp+search_len,tail_len+1);
280 memcpy(sp, replace, replace_len);
282 /* Do it recursively again until no more work to do */
284 if ((sp = strstr(line, search))) {
285 replacestr(line, search, replace);
289 int count_archs(bool* archs) {
290 int countArchs = 0;
291 int i;
292 for (i = 0; i < 4; i++) {
293 countArchs += archs[i];
295 return countArchs;
298 gchar* getArchName(bool* archs) {
299 if (archs[fARCH_i386])
300 return "i386";
301 else if (archs[fARCH_x86_64])
302 return "x86_64";
303 else if (archs[fARCH_arm])
304 return "ARM";
305 else if (archs[fARCH_aarch64])
306 return "ARM_aarch64";
307 else
308 return "all";
311 void extract_arch_from_e_machine_field(int16_t e_machine, const gchar* sourcename, bool* archs) {
312 if (e_machine == 3) {
313 archs[fARCH_i386] = 1;
314 fprintf(stderr, "%s used for determining architecture i386\n", sourcename);
317 if (e_machine == 62) {
318 archs[fARCH_x86_64] = 1;
319 fprintf(stderr, "%s used for determining architecture x86_64\n", sourcename);
322 if (e_machine == 40) {
323 archs[fARCH_arm] = 1;
324 fprintf(stderr, "%s used for determining architecture ARM\n", sourcename);
327 if (e_machine == 183) {
328 archs[fARCH_aarch64] = 1;
329 fprintf(stderr, "%s used for determining architecture ARM aarch64\n", sourcename);
333 void extract_arch_from_text(gchar *archname, const gchar* sourcename, bool* archs) {
334 if (archname) {
335 archname = g_strstrip(archname);
336 if (archname) {
337 replacestr(archname, "-", "_");
338 replacestr(archname, " ", "_");
339 if (g_ascii_strncasecmp("i386", archname, 20) == 0
340 || g_ascii_strncasecmp("i486", archname, 20) == 0
341 || g_ascii_strncasecmp("i586", archname, 20) == 0
342 || g_ascii_strncasecmp("i686", archname, 20) == 0
343 || g_ascii_strncasecmp("intel_80386", archname, 20) == 0
344 || g_ascii_strncasecmp("intel_80486", archname, 20) == 0
345 || g_ascii_strncasecmp("intel_80586", archname, 20) == 0
346 || g_ascii_strncasecmp("intel_80686", archname, 20) == 0
348 archs[fARCH_i386] = 1;
349 if (verbose)
350 fprintf(stderr, "%s used for determining architecture i386\n", sourcename);
351 } else if (g_ascii_strncasecmp("x86_64", archname, 20) == 0) {
352 archs[fARCH_x86_64] = 1;
353 if (verbose)
354 fprintf(stderr, "%s used for determining architecture x86_64\n", sourcename);
355 } else if (g_ascii_strncasecmp("arm", archname, 20) == 0) {
356 archs[fARCH_arm] = 1;
357 if (verbose)
358 fprintf(stderr, "%s used for determining architecture ARM\n", sourcename);
359 } else if (g_ascii_strncasecmp("arm_aarch64", archname, 20) == 0) {
360 archs[fARCH_aarch64] = 1;
361 if (verbose)
362 fprintf(stderr, "%s used for determining architecture ARM aarch64\n", sourcename);
368 int16_t read_elf_e_machine_field(const gchar* file_path) {
369 int16_t e_machine = 0x00;
370 FILE* file = 0;
371 file = fopen(file_path, "rb");
372 if (file) {
373 fseek(file, 0x12, SEEK_SET);
374 fgets((char*) (&e_machine), 0x02, file);
375 fclose(file);
378 return e_machine;
381 void guess_arch_of_file(const gchar *archfile, bool* archs) {
382 int16_t e_machine_field = read_elf_e_machine_field(archfile);
383 extract_arch_from_e_machine_field(e_machine_field, archfile, archs);
386 void find_arch(const gchar *real_path, const gchar *pattern, bool* archs) {
387 GDir *dir;
388 gchar *full_name;
389 dir = g_dir_open(real_path, 0, NULL);
390 if (dir != NULL) {
391 const gchar *entry;
392 while ((entry = g_dir_read_name(dir)) != NULL) {
393 full_name = g_build_filename(real_path, entry, NULL);
394 if (g_file_test(full_name, G_FILE_TEST_IS_SYMLINK)) {
395 } else if (g_file_test(full_name, G_FILE_TEST_IS_DIR)) {
396 find_arch(full_name, pattern, archs);
397 } else if (g_file_test(full_name, G_FILE_TEST_IS_EXECUTABLE) || g_pattern_match_simple(pattern, entry) ) {
398 guess_arch_of_file(full_name, archs);
401 g_dir_close(dir);
403 else {
404 g_warning("%s: %s", real_path, g_strerror(errno));
408 gchar* find_first_matching_file_nonrecursive(const gchar *real_path, const gchar *pattern) {
409 GDir *dir;
410 gchar *full_name;
411 dir = g_dir_open(real_path, 0, NULL);
412 if (dir != NULL) {
413 const gchar *entry;
414 while ((entry = g_dir_read_name(dir)) != NULL) {
415 full_name = g_build_filename(real_path, entry, NULL);
416 if (g_file_test(full_name, G_FILE_TEST_IS_REGULAR)) {
417 if(g_pattern_match_simple(pattern, entry))
418 return(full_name);
421 g_dir_close(dir);
423 else {
424 g_warning("%s: %s", real_path, g_strerror(errno));
426 return NULL;
429 gchar* get_desktop_entry(GKeyFile *kf, char *key) {
430 gchar *value = g_key_file_get_string (kf, "Desktop Entry", key, NULL);
431 if (! value){
432 fprintf(stderr, "%s entry not found in desktop file\n", key);
434 return value;
437 bool readFile(char* filename, int* size, char** buffer) {
438 FILE* f = fopen(filename, "rb");
439 if (f==NULL) {
440 *buffer = 0;
441 *size = 0;
442 return false;
445 fseek(f, 0, SEEK_END);
446 long fsize = ftell(f);
447 fseek(f, 0, SEEK_SET);
449 char *indata = malloc(fsize);
450 fread(indata, fsize, 1, f);
451 fclose(f);
452 *size = (int)fsize;
453 *buffer = indata;
454 return TRUE;
457 // #####################################################################
459 static GOptionEntry entries[] =
461 { "list", 'l', 0, G_OPTION_ARG_NONE, &list, "List files in SOURCE AppImage", NULL },
462 { "updateinformation", 'u', 0, G_OPTION_ARG_STRING, &updateinformation, "Embed update information STRING; if zsyncmake is installed, generate zsync file", NULL },
463 { "guess", 'g', 0, G_OPTION_ARG_NONE, &guessupdateinformation, "Guess update information based on Travis CI or GitLab environment variables", NULL },
464 { "bintray-user", 0, 0, G_OPTION_ARG_STRING, &bintray_user, "Bintray user name", NULL },
465 { "bintray-repo", 0, 0, G_OPTION_ARG_STRING, &bintray_repo, "Bintray repository", NULL },
466 { "version", 0, 0, G_OPTION_ARG_NONE, &showVersionOnly, "Show version number", NULL },
467 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Produce verbose output", NULL },
468 { "sign", 's', 0, G_OPTION_ARG_NONE, &sign, "Sign with gpg[2]", NULL },
469 { "comp", 0, 0, G_OPTION_ARG_STRING, &sqfs_comp, "Squashfs compression", NULL },
470 { "no-appstream", 'n', 0, G_OPTION_ARG_NONE, &no_appstream, "Do not check AppStream metadata", NULL },
471 { "exclude-file", 0, 0, G_OPTION_ARG_STRING, &exclude_file, _exclude_file_desc, NULL },
472 { "runtime-file", 0, 0, G_OPTION_ARG_STRING, &runtime_file, "Runtime file to use", NULL },
473 { "sign-key", 0, 0, G_OPTION_ARG_STRING, &sign_key, "Key ID to use for gpg[2] signatures", NULL},
474 { "sign-args", 0, 0, G_OPTION_ARG_STRING, &sign_args, "Extra arguments to use when signing with gpg[2]", NULL},
475 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &remaining_args, NULL, NULL },
476 { 0,0,0,0,0,0,0 }
480 main (int argc, char *argv[])
482 /* Parse VERSION environment variable.
483 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
484 char* version_env;
485 version_env = getenv("VERSION");
487 /* Parse Travis CI environment variables.
488 * https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
489 * TRAVIS_COMMIT: The commit that the current build is testing.
490 * TRAVIS_REPO_SLUG: The slug (in form: owner_name/repo_name) of the repository currently being built.
491 * TRAVIS_TAG: If the current build is for a git tag, this variable is set to the tag’s name.
492 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
493 // char* travis_commit;
494 // travis_commit = getenv("TRAVIS_COMMIT");
495 char* travis_repo_slug;
496 travis_repo_slug = getenv("TRAVIS_REPO_SLUG");
497 char* travis_tag;
498 travis_tag = getenv("TRAVIS_TAG");
499 char* travis_pull_request;
500 travis_pull_request = getenv("TRAVIS_PULL_REQUEST");
501 /* https://github.com/probonopd/uploadtool */
502 char* github_token;
503 github_token = getenv("GITHUB_TOKEN");
505 /* Parse GitLab CI environment variables.
506 * https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables
507 * echo "${CI_PROJECT_URL}/-/jobs/artifacts/${CI_COMMIT_REF_NAME}/raw/QtQuickApp-x86_64.AppImage?job=${CI_JOB_NAME}"
509 char* CI_PROJECT_URL;
510 CI_PROJECT_URL = getenv("CI_PROJECT_URL");
511 char* CI_COMMIT_REF_NAME;
512 CI_COMMIT_REF_NAME = getenv("CI_COMMIT_REF_NAME"); // The branch or tag name for which project is built
513 char* CI_JOB_NAME;
514 CI_JOB_NAME = getenv("CI_JOB_NAME"); // The name of the job as defined in .gitlab-ci.yml
516 /* Parse OWD environment variable.
517 * If it is available then cd there. It is the original CWD prior to running AppRun */
518 char* owd_env = NULL;
519 owd_env = getenv("OWD");
520 if(NULL!=owd_env){
521 int ret;
522 ret = chdir(owd_env);
523 if (ret != 0){
524 fprintf(stderr, "Could not cd into %s\n", owd_env);
525 exit(1);
529 GError *error = NULL;
530 GOptionContext *context;
531 char command[PATH_MAX];
533 // initialize help text of argument
534 sprintf(_exclude_file_desc, "Uses given file as exclude file for mksquashfs, in addition to %s.", APPIMAGEIGNORE);
536 context = g_option_context_new ("SOURCE [DESTINATION] - Generate, extract, and inspect AppImages");
537 g_option_context_add_main_entries (context, entries, NULL);
538 // g_option_context_add_group (context, gtk_get_option_group (TRUE));
539 if (!g_option_context_parse (context, &argc, &argv, &error))
541 fprintf(stderr, "Option parsing failed: %s\n", error->message);
542 exit(1);
545 fprintf(
546 stderr,
547 "appimagetool, %s (commit %s), build %s built on %s\n",
548 RELEASE_NAME, GIT_COMMIT, BUILD_NUMBER, BUILD_DATE
551 // always show version, but exit immediately if only the version number was requested
552 if (showVersionOnly)
553 exit(0);
555 if(!((0 == strcmp(sqfs_comp, "gzip")) || (0 ==strcmp(sqfs_comp, "xz"))))
556 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.");
557 /* Check for dependencies here. Better fail early if they are not present. */
558 if(! g_find_program_in_path ("file"))
559 die("file command is missing but required, please install it");
560 #ifndef AUXILIARY_FILES_DESTINATION
561 if(! g_find_program_in_path ("mksquashfs"))
562 die("mksquashfs command is missing but required, please install it");
563 #else
565 // build path relative to appimagetool binary
566 char *appimagetoolDirectory = dirname(realpath("/proc/self/exe", NULL));
567 if (!appimagetoolDirectory) {
568 g_print("Could not access /proc/self/exe\n");
569 exit(1);
572 pathToMksquashfs = g_build_filename(appimagetoolDirectory, "..", AUXILIARY_FILES_DESTINATION, "mksquashfs", NULL);
574 if (!g_file_test(pathToMksquashfs, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_EXECUTABLE)) {
575 g_printf("No such file or directory: %s\n", pathToMksquashfs);
576 g_free(pathToMksquashfs);
577 exit(1);
580 #endif
581 if(! g_find_program_in_path ("desktop-file-validate"))
582 die("desktop-file-validate command is missing, please install it");
583 if(! g_find_program_in_path ("zsyncmake"))
584 g_print("WARNING: zsyncmake command is missing, please install it if you want to use binary delta updates\n");
585 if(! no_appstream)
586 if(! g_find_program_in_path ("appstreamcli"))
587 g_print("WARNING: appstreamcli command is missing, please install it if you want to use AppStream metadata\n");
588 if(! g_find_program_in_path ("gpg2") && ! g_find_program_in_path ("gpg"))
589 g_print("WARNING: gpg2 or gpg command is missing, please install it if you want to create digital signatures\n");
590 if(! g_find_program_in_path ("sha256sum") && ! g_find_program_in_path ("shasum"))
591 g_print("WARNING: sha256sum or shasum command is missing, please install it if you want to create digital signatures\n");
593 if(!&remaining_args[0])
594 die("SOURCE is missing");
596 /* If in list mode */
597 if (list){
598 sfs_ls(remaining_args[0]);
599 exit(0);
602 /* If the first argument is a directory, then we assume that we should package it */
603 if (g_file_test (remaining_args[0], G_FILE_TEST_IS_DIR)){
604 char *destination;
605 char source[PATH_MAX];
606 realpath(remaining_args[0], source);
608 /* Check if *.desktop file is present in source AppDir */
609 gchar *desktop_file = find_first_matching_file_nonrecursive(source, "*.desktop");
610 if(desktop_file == NULL){
611 die("Desktop file not found, aborting");
613 if(verbose)
614 fprintf (stdout, "Desktop file: %s\n", desktop_file);
616 if(g_find_program_in_path ("desktop-file-validate")) {
617 if(validate_desktop_file(desktop_file) != 0){
618 fprintf(stderr, "ERROR: Desktop file contains errors. Please fix them. Please see\n");
619 fprintf(stderr, " https://standards.freedesktop.org/desktop-entry-spec/latest/\n");
620 die(" for more information.");
624 /* Read information from .desktop file */
625 GKeyFile *kf = g_key_file_new ();
626 if (!g_key_file_load_from_file (kf, desktop_file, G_KEY_FILE_KEEP_TRANSLATIONS | G_KEY_FILE_KEEP_COMMENTS, NULL))
627 die(".desktop file cannot be parsed");
629 if(verbose){
630 fprintf (stderr,"Name: %s\n", get_desktop_entry(kf, "Name"));
631 fprintf (stderr,"Icon: %s\n", get_desktop_entry(kf, "Icon"));
632 fprintf (stderr,"Exec: %s\n", get_desktop_entry(kf, "Exec"));
633 fprintf (stderr,"Comment: %s\n", get_desktop_entry(kf, "Comment"));
634 fprintf (stderr,"Type: %s\n", get_desktop_entry(kf, "Type"));
635 fprintf (stderr,"Categories: %s\n", get_desktop_entry(kf, "Categories"));
638 /* Determine the architecture */
639 bool archs[4] = {0, 0, 0, 0};
640 extract_arch_from_text(getenv("ARCH"), "Environmental variable ARCH", archs);
641 if (count_archs(archs) != 1) {
642 /* If no $ARCH variable is set check a file */
643 /* We use the next best .so that we can find to determine the architecture */
644 find_arch(source, "*.so.*", archs);
645 int countArchs = count_archs(archs);
646 if (countArchs != 1) {
647 if (countArchs < 1)
648 fprintf(stderr, "Unable to guess the architecture of the AppDir source directory \"%s\"\n", remaining_args[0]);
649 else
650 fprintf(stderr, "More than one architectures were found of the AppDir source directory \"%s\"\n", remaining_args[0]);
651 fprintf(stderr, "A valid architecture with the ARCH environmental variable should be provided\ne.g. ARCH=x86_64 %s", argv[0]),
652 die(" ...");
655 gchar* arch = getArchName(archs);
656 fprintf(stderr, "Using architecture %s\n", arch);
658 FILE *fp;
659 char app_name_for_filename[PATH_MAX];
660 sprintf(app_name_for_filename, "%s", get_desktop_entry(kf, "Name"));
661 replacestr(app_name_for_filename, " ", "_");
663 if(verbose)
664 fprintf (stderr,"App name for filename: %s\n", app_name_for_filename);
666 if (remaining_args[1]) {
667 destination = remaining_args[1];
668 } else {
669 /* No destination has been specified, to let's construct one
670 * TODO: Find out the architecture and use a $VERSION that might be around in the env */
671 char dest_path[PATH_MAX];
672 sprintf(dest_path, "%s-%s.AppImage", app_name_for_filename, arch);
674 if (version_env != NULL) {
675 sprintf(dest_path, "%s-%s-%s.AppImage", app_name_for_filename, version_env, arch);
677 // set VERSION in desktop file and save it
678 g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, "X-AppImage-Version", version_env);
680 if (!g_key_file_save_to_file(kf, desktop_file, NULL)) {
681 fprintf(stderr, "Could not save modified desktop file\n");
682 exit(1);
686 destination = strdup(dest_path);
687 replacestr(destination, " ", "_");
690 fprintf (stdout, "%s should be packaged as %s\n", source, destination);
691 /* Check if the Icon file is how it is expected */
692 gchar* icon_name = get_desktop_entry(kf, "Icon");
693 gchar* icon_file_path = NULL;
694 gchar* icon_file_png;
695 gchar* icon_file_svg;
696 gchar* icon_file_svgz;
697 gchar* icon_file_xpm;
698 icon_file_png = g_strdup_printf("%s/%s.png", source, icon_name);
699 icon_file_svg = g_strdup_printf("%s/%s.svg", source, icon_name);
700 icon_file_svgz = g_strdup_printf("%s/%s.svgz", source, icon_name);
701 icon_file_xpm = g_strdup_printf("%s/%s.xpm", source, icon_name);
702 if (g_file_test(icon_file_png, G_FILE_TEST_IS_REGULAR)) {
703 icon_file_path = icon_file_png;
704 } else if(g_file_test(icon_file_svg, G_FILE_TEST_IS_REGULAR)) {
705 icon_file_path = icon_file_svg;
706 } else if(g_file_test(icon_file_svgz, G_FILE_TEST_IS_REGULAR)) {
707 icon_file_path = icon_file_svgz;
708 } else if(g_file_test(icon_file_xpm, G_FILE_TEST_IS_REGULAR)) {
709 icon_file_path = icon_file_xpm;
710 } else {
711 fprintf (stderr, "%s{.png,.svg,.svgz,.xpm} defined in desktop file but not found\n", icon_name);
712 fprintf (stderr, "For example, you could put a 256x256 pixel png into\n");
713 gchar *icon_name_with_png = g_strconcat(icon_name, ".png", NULL);
714 gchar *example_path = g_build_filename(source, "/", icon_name_with_png, NULL);
715 fprintf (stderr, "%s\n", example_path);
716 exit(1);
719 /* Check if .DirIcon is present in source AppDir */
720 gchar *diricon_path = g_build_filename(source, ".DirIcon", NULL);
722 if (! g_file_test(diricon_path, G_FILE_TEST_EXISTS)){
723 fprintf (stderr, "Deleting pre-existing .DirIcon\n");
724 g_unlink(diricon_path);
726 if (! g_file_test(diricon_path, G_FILE_TEST_IS_REGULAR)){
727 fprintf (stderr, "Creating .DirIcon symlink based on information from desktop file\n");
728 int res = symlink(basename(icon_file_path), diricon_path);
729 if(res)
730 die("Could not symlink .DirIcon");
733 /* Check if AppStream upstream metadata is present in source AppDir */
734 if(! no_appstream){
735 char application_id[PATH_MAX];
736 sprintf (application_id, "%s", basename(desktop_file));
737 replacestr(application_id, ".desktop", ".appdata.xml");
738 gchar *appdata_path = g_build_filename(source, "/usr/share/metainfo/", application_id, NULL);
739 if (! g_file_test(appdata_path, G_FILE_TEST_IS_REGULAR)){
740 fprintf (stderr, "WARNING: AppStream upstream metadata is missing, please consider creating it\n");
741 fprintf (stderr, " in usr/share/metainfo/%s\n", application_id);
742 fprintf (stderr, " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps\n");
743 fprintf (stderr, " for more information or use the generator at http://output.jsbin.com/qoqukof.\n");
744 } else {
745 fprintf (stderr, "AppStream upstream metadata found in usr/share/metainfo/%s\n", application_id);
746 /* Use ximion's appstreamcli to make sure that desktop file and appdata match together */
747 if(g_find_program_in_path ("appstreamcli")) {
748 sprintf (command, "%s validate-tree %s", g_find_program_in_path ("appstreamcli"), source);
749 g_print("Trying to validate AppStream information with the appstreamcli tool\n");
750 g_print("In case of issues, please refer to https://github.com/ximion/appstream\n");
751 int ret = system(command);
752 if (ret != 0)
753 die("Failed to validate AppStream information with appstreamcli");
755 /* It seems that hughsie's appstream-util does additional validations */
756 if(g_find_program_in_path ("appstream-util")) {
757 sprintf (command, "%s validate-relax %s", g_find_program_in_path ("appstream-util"), appdata_path);
758 g_print("Trying to validate AppStream information with the appstream-util tool\n");
759 g_print("In case of issues, please refer to https://github.com/hughsie/appstream-glib\n");
760 int ret = system(command);
761 if (ret != 0)
762 die("Failed to validate AppStream information with appstream-util");
767 /* Upstream mksquashfs can currently not start writing at an offset,
768 * so we need a patched one. https://github.com/plougher/squashfs-tools/pull/13
769 * should hopefully change that. */
771 fprintf (stderr, "Generating squashfs...\n");
772 int size = 0;
773 char* data = NULL;
774 bool using_external_data = false;
775 if (runtime_file != NULL) {
776 if (!readFile(runtime_file, &size, &data))
777 die("Unable to load provided runtime file");
778 using_external_data = true;
779 } else {
780 #ifdef HAVE_BINARY_RUNTIME
781 /* runtime is embedded into this executable
782 * http://stupefydeveloper.blogspot.de/2008/08/cc-embed-binary-data-into-elf.html */
783 size = runtime_len;
784 data = runtime;
785 #else
786 die("No runtime file was provided");
787 #endif
789 if (verbose)
790 printf("Size of the embedded runtime: %d bytes\n", size);
792 int result = sfs_mksquashfs(source, destination, size);
793 if(result != 0)
794 die("sfs_mksquashfs error");
796 fprintf (stderr, "Embedding ELF...\n");
797 FILE *fpdst = fopen(destination, "rb+");
798 if (fpdst == NULL) {
799 die("Not able to open the AppImage for writing, aborting");
802 fseek(fpdst, 0, SEEK_SET);
803 fwrite(data, size, 1, fpdst);
804 fclose(fpdst);
805 if (using_external_data)
806 free(data);
808 fprintf (stderr, "Marking the AppImage as executable...\n");
809 if (chmod (destination, 0755) < 0) {
810 printf("Could not set executable bit, aborting\n");
811 exit(1);
814 if(bintray_user != NULL){
815 if(bintray_repo != NULL){
816 char buf[1024];
817 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);
818 updateinformation = buf;
819 printf("%s\n", updateinformation);
823 /* If the user has not provided update information but we know this is a Travis CI build,
824 * then fill in update information based on TRAVIS_REPO_SLUG */
825 if(guessupdateinformation){
826 if(travis_repo_slug){
827 if(!github_token) {
828 printf("Will not guess update information since $GITHUB_TOKEN is missing,\n");
829 if(0 != strcmp(travis_pull_request, "false")){
830 printf("please set it in the Travis CI Repository Settings for this project.\n");
831 printf("You can get one from https://github.com/settings/tokens\n");
832 } else {
833 printf("which is expected since this is a pull request\n");
835 } else {
836 gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake");
837 if(zsyncmake_path){
838 char buf[1024];
839 gchar **parts = g_strsplit (travis_repo_slug, "/", 2);
840 /* https://github.com/AppImage/AppImageSpec/blob/master/draft.md#github-releases
841 * gh-releases-zsync|probono|AppImages|latest|Subsurface*-x86_64.AppImage.zsync */
842 gchar *channel = "continuous";
843 if(travis_tag != NULL){
844 if((strcmp(travis_tag, "") != 0) && (strcmp(travis_tag, "continuous") != 0)) {
845 channel = "latest";
848 sprintf(buf, "gh-releases-zsync|%s|%s|%s|%s*-%s.AppImage.zsync", parts[0], parts[1], channel, app_name_for_filename, arch);
849 updateinformation = buf;
850 printf("Guessing update information based on $TRAVIS_TAG=%s and $TRAVIS_REPO_SLUG=%s\n", travis_tag, travis_repo_slug);
851 printf("%s\n", updateinformation);
852 } else {
853 printf("Will not guess update information since zsyncmake is missing\n");
856 } else if(CI_COMMIT_REF_NAME){
857 // ${CI_PROJECT_URL}/-/jobs/artifacts/${CI_COMMIT_REF_NAME}/raw/QtQuickApp-x86_64.AppImage?job=${CI_JOB_NAME}
858 gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake");
859 if(zsyncmake_path){
860 char buf[1024];
861 sprintf(buf, "zsync|%s/-/jobs/artifacts/%s/raw/%s-%s.AppImage.zsync?job=%s", CI_PROJECT_URL, CI_COMMIT_REF_NAME, app_name_for_filename, arch, CI_JOB_NAME);
862 updateinformation = buf;
863 printf("Guessing update information based on $CI_COMMIT_REF_NAME=%s and $CI_JOB_NAME=%s\n", CI_COMMIT_REF_NAME, CI_JOB_NAME);
864 printf("%s\n", updateinformation);
865 } else {
866 printf("Will not guess update information since zsyncmake is missing\n");
871 /* If updateinformation was provided, then we check and embed it */
872 if(updateinformation != NULL){
873 if(!g_str_has_prefix(updateinformation,"zsync|"))
874 if(!g_str_has_prefix(updateinformation,"bintray-zsync|"))
875 if(!g_str_has_prefix(updateinformation,"gh-releases-zsync|"))
876 die("The provided updateinformation is not in a recognized format");
878 gchar **ui_type = g_strsplit_set(updateinformation, "|", -1);
880 if(verbose)
881 printf("updateinformation type: %s\n", ui_type[0]);
882 /* TODO: Further checking of the updateinformation */
885 unsigned long ui_offset = 0;
886 unsigned long ui_length = 0;
888 bool rv = appimage_get_elf_section_offset_and_length(destination, ".upd_info", &ui_offset, &ui_length);
890 if (!rv || ui_offset == 0 || ui_length == 0) {
891 die("Could not find section .upd_info in runtime");
894 if(verbose) {
895 printf("ui_offset: %lu\n", ui_offset);
896 printf("ui_length: %lu\n", ui_length);
898 if(ui_offset == 0) {
899 die("Could not determine offset for updateinformation");
900 } else {
901 if(strlen(updateinformation)>ui_length)
902 die("updateinformation does not fit into segment, aborting");
903 FILE *fpdst2 = fopen(destination, "r+");
904 if (fpdst2 == NULL)
905 die("Not able to open the destination file for writing, aborting");
906 fseek(fpdst2, ui_offset, SEEK_SET);
907 // fseek(fpdst2, ui_offset, SEEK_SET);
908 // fwrite(0x00, 1, 1024, fpdst); // FIXME: Segfaults; why?
909 // fseek(fpdst, ui_offset, SEEK_SET);
910 fwrite(updateinformation, strlen(updateinformation), 1, fpdst2);
911 fclose(fpdst2);
915 // calculate and embed MD5 digest
917 fprintf(stderr, "Embedding MD5 digest\n");
919 unsigned long digest_md5_offset = 0;
920 unsigned long digest_md5_length = 0;
922 bool rv = appimage_get_elf_section_offset_and_length(destination, ".digest_md5", &digest_md5_offset, &digest_md5_length);
924 if (!rv || digest_md5_offset == 0 || digest_md5_length == 0) {
925 die("Could not find section .digest_md5 in runtime");
928 static const unsigned long section_size = 16;
930 if (digest_md5_length < section_size) {
931 fprintf(
932 stderr,
933 ".digest_md5 section in runtime's ELF header is too small"
934 "(found %lu bytes, minimum required: %lu bytes)\n",
935 digest_md5_length, section_size
937 exit(1);
940 char digest_buffer[section_size];
942 if (!appimage_type2_digest_md5(destination, digest_buffer)) {
943 die("Failed to calculate MD5 digest");
946 FILE* destinationfp = fopen(destination, "r+");
948 if (destinationfp == NULL) {
949 die("Failed to open AppImage for updating");
952 if (fseek(destinationfp, digest_md5_offset, SEEK_SET) != 0) {
953 fclose(destinationfp);
954 die("Failed to embed MD5 digest: could not seek to section offset");
957 if (fwrite(digest_buffer, sizeof(char), section_size, destinationfp) != section_size) {
958 fclose(destinationfp);
959 die("Failed to embed MD5 digest: write failed");
962 fclose(destinationfp);
965 if (sign) {
966 bool using_gpg = FALSE;
967 bool using_shasum = FALSE;
969 /* The user has indicated that he wants to sign */
970 gchar* gpg2_path = g_find_program_in_path("gpg2");
972 if (!gpg2_path) {
973 gpg2_path = g_find_program_in_path("gpg");
974 using_gpg = TRUE;
977 gchar* sha256sum_path = g_find_program_in_path("sha256sum");
979 if (!sha256sum_path) {
980 sha256sum_path = g_find_program_in_path("shasum");
981 using_shasum = 1;
984 if (!gpg2_path) {
985 fprintf(stderr, "gpg2 or gpg is not installed, cannot sign\n");
986 } else if (!sha256sum_path) {
987 fprintf(stderr, "sha256sum or shasum is not installed, cannot sign\n");
988 } else {
989 fprintf(stderr, "%s and %s are installed and user requested to sign, "
990 "hence signing\n", using_gpg ? "gpg" : "gpg2",
991 using_shasum ? "shasum" : "sha256sum");
993 char* digestfile;
994 digestfile = br_strcat(destination, ".digest");
996 char* ascfile;
997 ascfile = br_strcat(destination, ".digest.asc");
999 if (g_file_test(digestfile, G_FILE_TEST_IS_REGULAR))
1000 unlink(digestfile);
1002 if (using_shasum)
1003 sprintf(command, "%s -a256 %s", sha256sum_path, destination);
1004 else
1005 sprintf(command, "%s %s", sha256sum_path, destination);
1007 if (verbose)
1008 fprintf(stderr, "%s\n", command);
1010 fp = popen(command, "r");
1012 if (fp == NULL)
1013 die(using_shasum ? "shasum command did not succeed" : "sha256sum command did not succeed");
1015 char output[1024];
1017 fgets(output, sizeof(output) - 1, fp);
1019 if (verbose)
1020 printf("%s: %s\n", using_shasum ? "shasum" : "sha256sum",
1021 g_strsplit_set(output, " ", -1)[0]);
1023 FILE* fpx = fopen(digestfile, "w");
1025 if (fpx != NULL) {
1026 fputs(g_strsplit_set(output, " ", -1)[0], fpx);
1027 fclose(fpx);
1030 if (pclose(fp) != 0)
1031 die(using_shasum ? "shasum command did not succeed" : "sha256sum command did not succeed");
1033 fp = NULL;
1035 if (g_file_test(ascfile, G_FILE_TEST_IS_REGULAR))
1036 unlink(ascfile);
1038 char* key_arg = NULL;
1040 if (sign_key && strlen(sign_key) > 0) {
1041 key_arg = calloc(sizeof(char), strlen(sign_key) + strlen("--local-user ''"));
1043 if (key_arg == NULL)
1044 die("malloc() failed");
1046 strcpy(key_arg, "--local-user '");
1047 strcat(key_arg, sign_key);
1048 strcat(key_arg, "'");
1051 sprintf(command,
1052 "%s --batch --detach-sign --armor %s %s %s",
1053 gpg2_path, key_arg ? key_arg : "", sign_args ? sign_args : "", digestfile
1056 free(key_arg);
1057 key_arg = NULL;
1059 if (verbose)
1060 fprintf(stderr, "%s\n", command);
1062 fp = popen(command, "r");
1064 if (pclose(fp) != 0) {
1065 fprintf(stderr, "ERROR: %s command did not succeed, could not sign, continuing\n", using_gpg ? "gpg" : "gpg2");
1066 return 1;
1069 fp = NULL;
1071 FILE* destinationfp = fopen(destination, "r+");
1073 // calculate signature
1075 unsigned long sig_offset = 0;
1076 unsigned long sig_length = 0;
1078 bool rv = appimage_get_elf_section_offset_and_length(destination, ".sha256_sig", &sig_offset, &sig_length);
1080 if (!rv || sig_offset == 0 || sig_length == 0) {
1081 die("Could not find section .sha256_sig in runtime");
1084 if (verbose) {
1085 printf("sig_offset: %lu\n", sig_offset);
1086 printf("sig_length: %lu\n", sig_length);
1089 if (sig_offset == 0) {
1090 die("Could not determine offset for signature");
1093 if (destinationfp == NULL)
1094 die("Not able to open the destination file for writing, aborting");
1096 // if(strlen(updateinformation)>sig_length)
1097 // die("signature does not fit into segment, aborting");
1099 fseek(destinationfp, sig_offset, SEEK_SET);
1101 FILE* ascfilefp = fopen(ascfile, "rb");
1103 if (ascfilefp == NULL) {
1104 die("Not able to open the asc file for reading, aborting");
1107 static const int bufsize = 1024;
1108 char buffer[bufsize];
1110 size_t totalBytesRead = 0;
1112 while (!feof(ascfilefp)) {
1113 size_t bytesRead = fread(buffer, sizeof(char), bufsize, ascfilefp);
1114 totalBytesRead += bytesRead;
1116 if (totalBytesRead > sig_length) {
1117 die("Error: cannot embed key in AppImage: size exceeds reserved ELF section size");
1120 size_t bytesWritten = fwrite(buffer, sizeof(char), bytesRead, destinationfp);
1122 if (bytesRead != bytesWritten) {
1123 char message[128];
1124 sprintf(message, "Bytes read and written differ: %lu != %lu", (long unsigned) bytesRead,
1125 (long unsigned) bytesWritten);
1126 die(message);
1130 fclose(ascfilefp);
1131 if (g_file_test(digestfile, G_FILE_TEST_IS_REGULAR))
1132 unlink(digestfile);
1134 if (sign_key == NULL || strlen(sign_key) > 0) {
1135 // read which key was used to sign from signature
1136 sprintf(command, "%s --batch --list-packets %s", gpg2_path, ascfile);
1138 fp = popen(command, "r");
1140 if (fp == NULL)
1141 die("Failed to call gpg[2] to detect signature's key ID");
1143 while (!feof(fp)) {
1144 size_t bytesRead = fread(buffer, sizeof(char), bufsize, fp);
1146 char* keyid_pos = strstr(buffer, "keyid");
1148 if (keyid_pos == NULL)
1149 continue;
1151 char* keyIDBegin = keyid_pos + strlen("keyid ");
1152 char* endOfKeyID = strstr(keyIDBegin, "\n");
1154 sign_key = calloc(endOfKeyID - keyIDBegin, sizeof(char));
1155 memcpy(sign_key, keyIDBegin, endOfKeyID - keyIDBegin);
1158 // read rest of process input to avoid broken pipe error
1159 while (!feof(fp)) {
1160 fread(buffer, sizeof(char), bufsize, fp);
1163 int retval = pclose(fp);
1164 fp = NULL;
1166 if (retval != 0)
1167 die("Failed to call gpg[2] to detect signature's key ID");
1170 if (g_file_test(ascfile, G_FILE_TEST_IS_REGULAR))
1171 unlink(ascfile);
1174 // export key and write into section
1176 sprintf(command, "%s --batch --export --armor %s", gpg2_path, sign_key);
1178 unsigned long key_offset = 0, key_length = 0;
1180 bool rv = appimage_get_elf_section_offset_and_length(destination, ".sig_key", &key_offset, &key_length);
1182 if (verbose) {
1183 printf("key_offset: %lu\n", key_offset);
1184 printf("key_length: %lu\n", key_length);
1187 if (!rv || key_offset == 0 || key_length == 0) {
1188 die("Could not find section .sig_key in runtime");
1191 fseek(destinationfp, key_offset, SEEK_SET);
1193 fp = popen(command, "r");
1195 if (fp == NULL)
1196 die("Failed to call gpg[2] to export the signature key");
1198 static const int bufsize = 1024;
1199 char buffer[bufsize];
1201 size_t totalBytesRead = 0;
1202 while (!feof(fp)) {
1203 size_t bytesRead = fread(buffer, sizeof(char), bufsize, fp);
1204 totalBytesRead += bytesRead;
1206 if (totalBytesRead > key_length) {
1207 // read rest of process input to avoid broken pipe error
1208 while (!feof(fp)) {
1209 fread(buffer, sizeof(char), bufsize, fp);
1212 pclose(fp);
1213 die("Error: cannot embed key in AppImage: size exceeds reserved ELF section size");
1216 size_t bytesWritten = fwrite(buffer, sizeof(char), bytesRead, destinationfp);
1218 if (bytesRead != bytesWritten) {
1219 char message[128];
1220 sprintf(message, "Error: Bytes read and written differ: %lu != %lu",
1221 (long unsigned) bytesRead, (long unsigned) bytesWritten);
1222 die(message);
1226 int exportexitcode = pclose(fp);
1227 fp = NULL;
1229 if (exportexitcode != 0) {
1230 char message[128];
1231 sprintf(message, "GPG key export failed: exit code %d", exportexitcode);
1232 die(message);
1235 fclose(destinationfp);
1240 /* If updateinformation was provided, then we also generate the zsync file (after having signed the AppImage) */
1241 if (updateinformation != NULL) {
1242 gchar* zsyncmake_path = g_find_program_in_path("zsyncmake");
1243 if (!zsyncmake_path) {
1244 fprintf(stderr, "zsyncmake is not installed/bundled, skipping\n");
1245 } else {
1246 fprintf(stderr, "zsyncmake is available and updateinformation is provided, "
1247 "hence generating zsync file\n");
1249 int pid = fork();
1251 if (verbose)
1252 fprintf(stderr, "%s %s -u %s", zsyncmake_path, destination, basename(destination));
1254 if (pid == -1) {
1255 // fork error
1256 die("fork() failed");
1257 } else if (pid == 0) {
1258 char* zsyncmake_command[] = {zsyncmake_path, destination, "-u", basename(destination), NULL};
1260 // redirect stdout/stderr to /dev/null
1261 int fd = open("/dev/null", O_WRONLY);
1263 dup2(fd, 1);
1264 dup2(fd, 2);
1265 close(fd);
1267 execv(zsyncmake_path, zsyncmake_command);
1269 die("execv() should not return");
1270 } else {
1271 int exitstatus;
1273 if (waitpid(pid, &exitstatus, 0) == -1) {
1274 perror("waitpid() failed");
1275 exit(EXIT_FAILURE);
1278 if (WEXITSTATUS(exitstatus) != 0)
1279 die("zsyncmake command did not succeed");
1284 fprintf(stderr, "Success\n\n");
1285 fprintf(stderr, "Please consider submitting your AppImage to AppImageHub, the crowd-sourced\n");
1286 fprintf(stderr, "central directory of available AppImages, by opening a pull request\n");
1287 fprintf(stderr, "at https://github.com/AppImage/appimage.github.io\n");
1290 /* If the first argument is a regular file, then we assume that we should unpack it */
1291 if (g_file_test (remaining_args[0], G_FILE_TEST_IS_REGULAR)){
1292 fprintf (stdout, "%s is a file, assuming it is an AppImage and should be unpacked\n", remaining_args[0]);
1293 die("To be implemented");
1296 return 0;