Not every executable is a binary
[appimagekit/gsi.git] / src / appimagetool.c
blobfeaeb4c80f9e1284999935f855db9fbc381734f7
1 /**************************************************************************
2 *
3 * Copyright (c) 2004-19 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 "armhf";
305 else if (archs[fARCH_aarch64])
306 return "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 if(verbose)
315 fprintf(stderr, "%s used for determining architecture i386\n", sourcename);
318 if (e_machine == 62) {
319 archs[fARCH_x86_64] = 1;
320 if(verbose)
321 fprintf(stderr, "%s used for determining architecture x86_64\n", sourcename);
324 if (e_machine == 40) {
325 archs[fARCH_arm] = 1;
326 if(verbose)
327 fprintf(stderr, "%s used for determining architecture armhf\n", sourcename);
330 if (e_machine == 183) {
331 archs[fARCH_aarch64] = 1;
332 if(verbose)
333 fprintf(stderr, "%s used for determining architecture aarch64\n", sourcename);
337 void extract_arch_from_text(gchar *archname, const gchar* sourcename, bool* archs) {
338 if (archname) {
339 archname = g_strstrip(archname);
340 if (archname) {
341 replacestr(archname, "-", "_");
342 replacestr(archname, " ", "_");
343 if (g_ascii_strncasecmp("i386", archname, 20) == 0
344 || g_ascii_strncasecmp("i486", archname, 20) == 0
345 || g_ascii_strncasecmp("i586", archname, 20) == 0
346 || g_ascii_strncasecmp("i686", archname, 20) == 0
347 || g_ascii_strncasecmp("intel_80386", archname, 20) == 0
348 || g_ascii_strncasecmp("intel_80486", archname, 20) == 0
349 || g_ascii_strncasecmp("intel_80586", archname, 20) == 0
350 || g_ascii_strncasecmp("intel_80686", archname, 20) == 0
352 archs[fARCH_i386] = 1;
353 if (verbose)
354 fprintf(stderr, "%s used for determining architecture i386\n", sourcename);
355 } else if (g_ascii_strncasecmp("x86_64", archname, 20) == 0) {
356 archs[fARCH_x86_64] = 1;
357 if (verbose)
358 fprintf(stderr, "%s used for determining architecture x86_64\n", sourcename);
359 } else if (g_ascii_strncasecmp("arm", archname, 20) == 0) {
360 archs[fARCH_arm] = 1;
361 if (verbose)
362 fprintf(stderr, "%s used for determining architecture ARM\n", sourcename);
363 } else if (g_ascii_strncasecmp("arm_aarch64", archname, 20) == 0) {
364 archs[fARCH_aarch64] = 1;
365 if (verbose)
366 fprintf(stderr, "%s used for determining architecture ARM aarch64\n", sourcename);
372 int16_t read_elf_e_machine_field(const gchar* file_path) {
373 int16_t e_machine = 0x00;
374 FILE* file = 0;
375 file = fopen(file_path, "rb");
376 if (file) {
377 fseek(file, 0x12, SEEK_SET);
378 fgets((char*) (&e_machine), 0x02, file);
379 fclose(file);
382 return e_machine;
385 void guess_arch_of_file(const gchar *archfile, bool* archs) {
386 int16_t e_machine_field = read_elf_e_machine_field(archfile);
387 extract_arch_from_e_machine_field(e_machine_field, archfile, archs);
390 void find_arch(const gchar *real_path, const gchar *pattern, bool* archs) {
391 GDir *dir;
392 gchar *full_name;
393 dir = g_dir_open(real_path, 0, NULL);
394 if (dir != NULL) {
395 const gchar *entry;
396 while ((entry = g_dir_read_name(dir)) != NULL) {
397 full_name = g_build_filename(real_path, entry, NULL);
398 if (g_file_test(full_name, G_FILE_TEST_IS_SYMLINK)) {
399 } else if (g_file_test(full_name, G_FILE_TEST_IS_DIR)) {
400 find_arch(full_name, pattern, archs);
401 } else if (g_file_test(g_pattern_match_simple(pattern, entry))) {
402 guess_arch_of_file(full_name, archs);
405 g_dir_close(dir);
407 else {
408 g_warning("%s: %s", real_path, g_strerror(errno));
412 gchar* find_first_matching_file_nonrecursive(const gchar *real_path, const gchar *pattern) {
413 GDir *dir;
414 gchar *full_name;
415 dir = g_dir_open(real_path, 0, NULL);
416 if (dir != NULL) {
417 const gchar *entry;
418 while ((entry = g_dir_read_name(dir)) != NULL) {
419 full_name = g_build_filename(real_path, entry, NULL);
420 if (g_file_test(full_name, G_FILE_TEST_IS_REGULAR)) {
421 if(g_pattern_match_simple(pattern, entry))
422 return(full_name);
425 g_dir_close(dir);
427 else {
428 g_warning("%s: %s", real_path, g_strerror(errno));
430 return NULL;
433 gchar* get_desktop_entry(GKeyFile *kf, char *key) {
434 gchar *value = g_key_file_get_string (kf, "Desktop Entry", key, NULL);
435 if (! value){
436 fprintf(stderr, "%s entry not found in desktop file\n", key);
438 return value;
441 bool readFile(char* filename, int* size, char** buffer) {
442 FILE* f = fopen(filename, "rb");
443 if (f==NULL) {
444 *buffer = 0;
445 *size = 0;
446 return false;
449 fseek(f, 0, SEEK_END);
450 long fsize = ftell(f);
451 fseek(f, 0, SEEK_SET);
453 char *indata = malloc(fsize);
454 fread(indata, fsize, 1, f);
455 fclose(f);
456 *size = (int)fsize;
457 *buffer = indata;
458 return TRUE;
461 // #####################################################################
463 static GOptionEntry entries[] =
465 { "list", 'l', 0, G_OPTION_ARG_NONE, &list, "List files in SOURCE AppImage", NULL },
466 { "updateinformation", 'u', 0, G_OPTION_ARG_STRING, &updateinformation, "Embed update information STRING; if zsyncmake is installed, generate zsync file", NULL },
467 { "guess", 'g', 0, G_OPTION_ARG_NONE, &guessupdateinformation, "Guess update information based on Travis CI or GitLab environment variables", NULL },
468 { "bintray-user", 0, 0, G_OPTION_ARG_STRING, &bintray_user, "Bintray user name", NULL },
469 { "bintray-repo", 0, 0, G_OPTION_ARG_STRING, &bintray_repo, "Bintray repository", NULL },
470 { "version", 0, 0, G_OPTION_ARG_NONE, &showVersionOnly, "Show version number", NULL },
471 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Produce verbose output", NULL },
472 { "sign", 's', 0, G_OPTION_ARG_NONE, &sign, "Sign with gpg[2]", NULL },
473 { "comp", 0, 0, G_OPTION_ARG_STRING, &sqfs_comp, "Squashfs compression", NULL },
474 { "no-appstream", 'n', 0, G_OPTION_ARG_NONE, &no_appstream, "Do not check AppStream metadata", NULL },
475 { "exclude-file", 0, 0, G_OPTION_ARG_STRING, &exclude_file, _exclude_file_desc, NULL },
476 { "runtime-file", 0, 0, G_OPTION_ARG_STRING, &runtime_file, "Runtime file to use", NULL },
477 { "sign-key", 0, 0, G_OPTION_ARG_STRING, &sign_key, "Key ID to use for gpg[2] signatures", NULL},
478 { "sign-args", 0, 0, G_OPTION_ARG_STRING, &sign_args, "Extra arguments to use when signing with gpg[2]", NULL},
479 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &remaining_args, NULL, NULL },
480 { 0,0,0,0,0,0,0 }
484 main (int argc, char *argv[])
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 /* Parse VERSION environment variable.
556 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6
557 * Also, if VERSION is not set and -g is called and if git is on the path, use
558 * git rev-parse --short HEAD
559 * TODO: Might also want to somehow make use of
560 * git rev-parse --abbrev-ref HEAD
561 * git log -1 --format=%ci */
562 gchar *version_env; // In which cases do we need to malloc() here?
563 version_env = getenv("VERSION");
564 if(guessupdateinformation){
565 if(g_find_program_in_path ("git")) {
566 if (version_env == NULL) {
567 GError *error = NULL;
568 gchar *out = NULL;
569 GString *command_line = g_string_new("git");
570 g_string_append_printf(command_line, " rev-parse --short HEAD");
571 int ret = g_spawn_command_line_sync(command_line->str, &out, NULL, NULL, &error);
572 g_assert_no_error(error);
573 if (ret) {
574 version_env = g_strstrip(out);
575 } else {
576 g_print("Failed to run 'git rev-parse --short HEAD'");
578 g_string_free(command_line, true);
579 if (version_env != NULL) {
580 g_print("NOTE: Using the output of 'git rev-parse --short HEAD' as the version:\n");
581 g_print(" %s\n", version_env);
582 g_print(" Please set the $VERSION environment variable if this is not intended\n");
588 if(!((0 == strcmp(sqfs_comp, "gzip")) || (0 ==strcmp(sqfs_comp, "xz"))))
589 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.");
590 /* Check for dependencies here. Better fail early if they are not present. */
591 if(! g_find_program_in_path ("file"))
592 die("file command is missing but required, please install it");
593 #ifndef AUXILIARY_FILES_DESTINATION
594 if(! g_find_program_in_path ("mksquashfs"))
595 die("mksquashfs command is missing but required, please install it");
596 #else
598 // build path relative to appimagetool binary
599 char *appimagetoolDirectory = dirname(realpath("/proc/self/exe", NULL));
600 if (!appimagetoolDirectory) {
601 g_print("Could not access /proc/self/exe\n");
602 exit(1);
605 pathToMksquashfs = g_build_filename(appimagetoolDirectory, "..", AUXILIARY_FILES_DESTINATION, "mksquashfs", NULL);
607 if (!g_file_test(pathToMksquashfs, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_EXECUTABLE)) {
608 g_printf("No such file or directory: %s\n", pathToMksquashfs);
609 g_free(pathToMksquashfs);
610 exit(1);
613 #endif
614 if(! g_find_program_in_path ("desktop-file-validate"))
615 die("desktop-file-validate command is missing, please install it");
616 if(! g_find_program_in_path ("zsyncmake"))
617 g_print("WARNING: zsyncmake command is missing, please install it if you want to use binary delta updates\n");
618 if(! no_appstream)
619 if(! g_find_program_in_path ("appstreamcli"))
620 g_print("WARNING: appstreamcli command is missing, please install it if you want to use AppStream metadata\n");
621 if(! g_find_program_in_path ("gpg2") && ! g_find_program_in_path ("gpg"))
622 g_print("WARNING: gpg2 or gpg command is missing, please install it if you want to create digital signatures\n");
623 if(! g_find_program_in_path ("sha256sum") && ! g_find_program_in_path ("shasum"))
624 g_print("WARNING: sha256sum or shasum command is missing, please install it if you want to create digital signatures\n");
626 if(!&remaining_args[0])
627 die("SOURCE is missing");
629 /* If in list mode */
630 if (list){
631 sfs_ls(remaining_args[0]);
632 exit(0);
635 /* If the first argument is a directory, then we assume that we should package it */
636 if (g_file_test (remaining_args[0], G_FILE_TEST_IS_DIR)){
637 char *destination;
638 char source[PATH_MAX];
639 realpath(remaining_args[0], source);
641 /* Check if *.desktop file is present in source AppDir */
642 gchar *desktop_file = find_first_matching_file_nonrecursive(source, "*.desktop");
643 if(desktop_file == NULL){
644 die("Desktop file not found, aborting");
646 if(verbose)
647 fprintf (stdout, "Desktop file: %s\n", desktop_file);
649 if(g_find_program_in_path ("desktop-file-validate")) {
650 if(validate_desktop_file(desktop_file) != 0){
651 fprintf(stderr, "ERROR: Desktop file contains errors. Please fix them. Please see\n");
652 fprintf(stderr, " https://standards.freedesktop.org/desktop-entry-spec/1.0/n");
653 die(" for more information.");
657 /* Read information from .desktop file */
658 GKeyFile *kf = g_key_file_new ();
659 if (!g_key_file_load_from_file (kf, desktop_file, G_KEY_FILE_KEEP_TRANSLATIONS | G_KEY_FILE_KEEP_COMMENTS, NULL))
660 die(".desktop file cannot be parsed");
662 if(verbose){
663 fprintf (stderr,"Name: %s\n", get_desktop_entry(kf, "Name"));
664 fprintf (stderr,"Icon: %s\n", get_desktop_entry(kf, "Icon"));
665 fprintf (stderr,"Exec: %s\n", get_desktop_entry(kf, "Exec"));
666 fprintf (stderr,"Comment: %s\n", get_desktop_entry(kf, "Comment"));
667 fprintf (stderr,"Type: %s\n", get_desktop_entry(kf, "Type"));
668 fprintf (stderr,"Categories: %s\n", get_desktop_entry(kf, "Categories"));
671 /* Determine the architecture */
672 bool archs[4] = {0, 0, 0, 0};
673 extract_arch_from_text(getenv("ARCH"), "Environmental variable ARCH", archs);
674 if (count_archs(archs) != 1) {
675 /* If no $ARCH variable is set check a file */
676 /* We use the next best .so that we can find to determine the architecture */
677 find_arch(source, "*.so.*", archs);
678 int countArchs = count_archs(archs);
679 if (countArchs != 1) {
680 if (countArchs < 1)
681 fprintf(stderr, "Unable to guess the architecture of the AppDir source directory \"%s\"\n", remaining_args[0]);
682 else
683 fprintf(stderr, "More than one architectures were found of the AppDir source directory \"%s\"\n", remaining_args[0]);
684 fprintf(stderr, "A valid architecture with the ARCH environmental variable should be provided\ne.g. ARCH=x86_64 %s", argv[0]),
685 die(" ...");
688 gchar* arch = getArchName(archs);
689 fprintf(stderr, "Using architecture %s\n", arch);
691 FILE *fp;
692 char app_name_for_filename[PATH_MAX];
693 sprintf(app_name_for_filename, "%s", get_desktop_entry(kf, "Name"));
694 replacestr(app_name_for_filename, " ", "_");
696 if(verbose)
697 fprintf (stderr,"App name for filename: %s\n", app_name_for_filename);
699 if (remaining_args[1]) {
700 destination = remaining_args[1];
701 } else {
702 /* No destination has been specified, to let's construct one
703 * TODO: Find out the architecture and use a $VERSION that might be around in the env */
704 char dest_path[PATH_MAX];
705 sprintf(dest_path, "%s-%s.AppImage", app_name_for_filename, arch);
707 if (version_env != NULL) {
708 sprintf(dest_path, "%s-%s-%s.AppImage", app_name_for_filename, version_env, arch);
710 // set VERSION in desktop file and save it
711 g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, "X-AppImage-Version", version_env);
713 if (!g_key_file_save_to_file(kf, desktop_file, NULL)) {
714 fprintf(stderr, "Could not save modified desktop file\n");
715 exit(1);
719 destination = strdup(dest_path);
720 replacestr(destination, " ", "_");
723 fprintf (stdout, "%s should be packaged as %s\n", source, destination);
724 /* Check if the Icon file is how it is expected */
725 gchar* icon_name = get_desktop_entry(kf, "Icon");
726 gchar* icon_file_path = NULL;
727 gchar* icon_file_png;
728 gchar* icon_file_svg;
729 gchar* icon_file_svgz;
730 gchar* icon_file_xpm;
731 icon_file_png = g_strdup_printf("%s/%s.png", source, icon_name);
732 icon_file_svg = g_strdup_printf("%s/%s.svg", source, icon_name);
733 icon_file_svgz = g_strdup_printf("%s/%s.svgz", source, icon_name);
734 icon_file_xpm = g_strdup_printf("%s/%s.xpm", source, icon_name);
735 if (g_file_test(icon_file_png, G_FILE_TEST_IS_REGULAR)) {
736 icon_file_path = icon_file_png;
737 } else if(g_file_test(icon_file_svg, G_FILE_TEST_IS_REGULAR)) {
738 icon_file_path = icon_file_svg;
739 } else if(g_file_test(icon_file_svgz, G_FILE_TEST_IS_REGULAR)) {
740 icon_file_path = icon_file_svgz;
741 } else if(g_file_test(icon_file_xpm, G_FILE_TEST_IS_REGULAR)) {
742 icon_file_path = icon_file_xpm;
743 } else {
744 fprintf (stderr, "%s{.png,.svg,.svgz,.xpm} defined in desktop file but not found\n", icon_name);
745 fprintf (stderr, "For example, you could put a 256x256 pixel png into\n");
746 gchar *icon_name_with_png = g_strconcat(icon_name, ".png", NULL);
747 gchar *example_path = g_build_filename(source, "/", icon_name_with_png, NULL);
748 fprintf (stderr, "%s\n", example_path);
749 exit(1);
752 /* Check if .DirIcon is present in source AppDir */
753 gchar *diricon_path = g_build_filename(source, ".DirIcon", NULL);
755 if (! g_file_test(diricon_path, G_FILE_TEST_EXISTS)){
756 fprintf (stderr, "Deleting pre-existing .DirIcon\n");
757 g_unlink(diricon_path);
759 if (! g_file_test(diricon_path, G_FILE_TEST_IS_REGULAR)){
760 fprintf (stderr, "Creating .DirIcon symlink based on information from desktop file\n");
761 int res = symlink(basename(icon_file_path), diricon_path);
762 if(res)
763 die("Could not symlink .DirIcon");
766 /* Check if AppStream upstream metadata is present in source AppDir */
767 if(! no_appstream){
768 char application_id[PATH_MAX];
769 sprintf (application_id, "%s", basename(desktop_file));
770 replacestr(application_id, ".desktop", ".appdata.xml");
771 gchar *appdata_path = g_build_filename(source, "/usr/share/metainfo/", application_id, NULL);
772 if (! g_file_test(appdata_path, G_FILE_TEST_IS_REGULAR)){
773 fprintf (stderr, "WARNING: AppStream upstream metadata is missing, please consider creating it\n");
774 fprintf (stderr, " in usr/share/metainfo/%s\n", application_id);
775 fprintf (stderr, " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps\n");
776 fprintf (stderr, " for more information or use the generator at http://output.jsbin.com/qoqukof.\n");
777 } else {
778 fprintf (stderr, "AppStream upstream metadata found in usr/share/metainfo/%s\n", application_id);
779 /* Use ximion's appstreamcli to make sure that desktop file and appdata match together */
780 if(g_find_program_in_path ("appstreamcli")) {
781 sprintf (command, "%s validate-tree %s", g_find_program_in_path ("appstreamcli"), source);
782 g_print("Trying to validate AppStream information with the appstreamcli tool\n");
783 g_print("In case of issues, please refer to https://github.com/ximion/appstream\n");
784 int ret = system(command);
785 if (ret != 0)
786 die("Failed to validate AppStream information with appstreamcli");
788 /* It seems that hughsie's appstream-util does additional validations */
789 if(g_find_program_in_path ("appstream-util")) {
790 sprintf (command, "%s validate-relax %s", g_find_program_in_path ("appstream-util"), appdata_path);
791 g_print("Trying to validate AppStream information with the appstream-util tool\n");
792 g_print("In case of issues, please refer to https://github.com/hughsie/appstream-glib\n");
793 int ret = system(command);
794 if (ret != 0)
795 die("Failed to validate AppStream information with appstream-util");
800 /* Upstream mksquashfs can currently not start writing at an offset,
801 * so we need a patched one. https://github.com/plougher/squashfs-tools/pull/13
802 * should hopefully change that. */
804 fprintf (stderr, "Generating squashfs...\n");
805 int size = 0;
806 char* data = NULL;
807 bool using_external_data = false;
808 if (runtime_file != NULL) {
809 if (!readFile(runtime_file, &size, &data))
810 die("Unable to load provided runtime file");
811 using_external_data = true;
812 } else {
813 #ifdef HAVE_BINARY_RUNTIME
814 /* runtime is embedded into this executable
815 * http://stupefydeveloper.blogspot.de/2008/08/cc-embed-binary-data-into-elf.html */
816 size = runtime_len;
817 data = runtime;
818 #else
819 die("No runtime file was provided");
820 #endif
822 if (verbose)
823 printf("Size of the embedded runtime: %d bytes\n", size);
825 int result = sfs_mksquashfs(source, destination, size);
826 if(result != 0)
827 die("sfs_mksquashfs error");
829 fprintf (stderr, "Embedding ELF...\n");
830 FILE *fpdst = fopen(destination, "rb+");
831 if (fpdst == NULL) {
832 die("Not able to open the AppImage for writing, aborting");
835 fseek(fpdst, 0, SEEK_SET);
836 fwrite(data, size, 1, fpdst);
837 fclose(fpdst);
838 if (using_external_data)
839 free(data);
841 fprintf (stderr, "Marking the AppImage as executable...\n");
842 if (chmod (destination, 0755) < 0) {
843 printf("Could not set executable bit, aborting\n");
844 exit(1);
847 if(bintray_user != NULL){
848 if(bintray_repo != NULL){
849 char buf[1024];
850 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);
851 updateinformation = buf;
852 printf("%s\n", updateinformation);
856 /* If the user has not provided update information but we know this is a Travis CI build,
857 * then fill in update information based on TRAVIS_REPO_SLUG */
858 if(guessupdateinformation){
859 if(travis_repo_slug){
860 if(!github_token) {
861 printf("Will not guess update information since $GITHUB_TOKEN is missing,\n");
862 if(0 != strcmp(travis_pull_request, "false")){
863 printf("please set it in the Travis CI Repository Settings for this project.\n");
864 printf("You can get one from https://github.com/settings/tokens\n");
865 } else {
866 printf("which is expected since this is a pull request\n");
868 } else {
869 gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake");
870 if(zsyncmake_path){
871 char buf[1024];
872 gchar **parts = g_strsplit (travis_repo_slug, "/", 2);
873 /* https://github.com/AppImage/AppImageSpec/blob/master/draft.md#github-releases
874 * gh-releases-zsync|probono|AppImages|latest|Subsurface*-x86_64.AppImage.zsync */
875 gchar *channel = "continuous";
876 if(travis_tag != NULL){
877 if((strcmp(travis_tag, "") != 0) && (strcmp(travis_tag, "continuous") != 0)) {
878 channel = "latest";
881 sprintf(buf, "gh-releases-zsync|%s|%s|%s|%s*-%s.AppImage.zsync", parts[0], parts[1], channel, app_name_for_filename, arch);
882 updateinformation = buf;
883 printf("Guessing update information based on $TRAVIS_TAG=%s and $TRAVIS_REPO_SLUG=%s\n", travis_tag, travis_repo_slug);
884 printf("%s\n", updateinformation);
885 } else {
886 printf("Will not guess update information since zsyncmake is missing\n");
889 } else if(CI_COMMIT_REF_NAME){
890 // ${CI_PROJECT_URL}/-/jobs/artifacts/${CI_COMMIT_REF_NAME}/raw/QtQuickApp-x86_64.AppImage?job=${CI_JOB_NAME}
891 gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake");
892 if(zsyncmake_path){
893 char buf[1024];
894 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);
895 updateinformation = buf;
896 printf("Guessing update information based on $CI_COMMIT_REF_NAME=%s and $CI_JOB_NAME=%s\n", CI_COMMIT_REF_NAME, CI_JOB_NAME);
897 printf("%s\n", updateinformation);
898 } else {
899 printf("Will not guess update information since zsyncmake is missing\n");
904 /* If updateinformation was provided, then we check and embed it */
905 if(updateinformation != NULL){
906 if(!g_str_has_prefix(updateinformation,"zsync|"))
907 if(!g_str_has_prefix(updateinformation,"bintray-zsync|"))
908 if(!g_str_has_prefix(updateinformation,"gh-releases-zsync|"))
909 die("The provided updateinformation is not in a recognized format");
911 gchar **ui_type = g_strsplit_set(updateinformation, "|", -1);
913 if(verbose)
914 printf("updateinformation type: %s\n", ui_type[0]);
915 /* TODO: Further checking of the updateinformation */
918 unsigned long ui_offset = 0;
919 unsigned long ui_length = 0;
921 bool rv = appimage_get_elf_section_offset_and_length(destination, ".upd_info", &ui_offset, &ui_length);
923 if (!rv || ui_offset == 0 || ui_length == 0) {
924 die("Could not find section .upd_info in runtime");
927 if(verbose) {
928 printf("ui_offset: %lu\n", ui_offset);
929 printf("ui_length: %lu\n", ui_length);
931 if(ui_offset == 0) {
932 die("Could not determine offset for updateinformation");
933 } else {
934 if(strlen(updateinformation)>ui_length)
935 die("updateinformation does not fit into segment, aborting");
936 FILE *fpdst2 = fopen(destination, "r+");
937 if (fpdst2 == NULL)
938 die("Not able to open the destination file for writing, aborting");
939 fseek(fpdst2, ui_offset, SEEK_SET);
940 // fseek(fpdst2, ui_offset, SEEK_SET);
941 // fwrite(0x00, 1, 1024, fpdst); // FIXME: Segfaults; why?
942 // fseek(fpdst, ui_offset, SEEK_SET);
943 fwrite(updateinformation, strlen(updateinformation), 1, fpdst2);
944 fclose(fpdst2);
948 // calculate and embed MD5 digest
950 fprintf(stderr, "Embedding MD5 digest\n");
952 unsigned long digest_md5_offset = 0;
953 unsigned long digest_md5_length = 0;
955 bool rv = appimage_get_elf_section_offset_and_length(destination, ".digest_md5", &digest_md5_offset, &digest_md5_length);
957 if (!rv || digest_md5_offset == 0 || digest_md5_length == 0) {
958 die("Could not find section .digest_md5 in runtime");
961 static const unsigned long section_size = 16;
963 if (digest_md5_length < section_size) {
964 fprintf(
965 stderr,
966 ".digest_md5 section in runtime's ELF header is too small"
967 "(found %lu bytes, minimum required: %lu bytes)\n",
968 digest_md5_length, section_size
970 exit(1);
973 char digest_buffer[section_size];
975 if (!appimage_type2_digest_md5(destination, digest_buffer)) {
976 die("Failed to calculate MD5 digest");
979 FILE* destinationfp = fopen(destination, "r+");
981 if (destinationfp == NULL) {
982 die("Failed to open AppImage for updating");
985 if (fseek(destinationfp, digest_md5_offset, SEEK_SET) != 0) {
986 fclose(destinationfp);
987 die("Failed to embed MD5 digest: could not seek to section offset");
990 if (fwrite(digest_buffer, sizeof(char), section_size, destinationfp) != section_size) {
991 fclose(destinationfp);
992 die("Failed to embed MD5 digest: write failed");
995 fclose(destinationfp);
998 if (sign) {
999 bool using_gpg = FALSE;
1000 bool using_shasum = FALSE;
1002 /* The user has indicated that he wants to sign */
1003 gchar* gpg2_path = g_find_program_in_path("gpg2");
1005 if (!gpg2_path) {
1006 gpg2_path = g_find_program_in_path("gpg");
1007 using_gpg = TRUE;
1010 gchar* sha256sum_path = g_find_program_in_path("sha256sum");
1012 if (!sha256sum_path) {
1013 sha256sum_path = g_find_program_in_path("shasum");
1014 using_shasum = 1;
1017 if (!gpg2_path) {
1018 fprintf(stderr, "gpg2 or gpg is not installed, cannot sign\n");
1019 } else if (!sha256sum_path) {
1020 fprintf(stderr, "sha256sum or shasum is not installed, cannot sign\n");
1021 } else {
1022 fprintf(stderr, "%s and %s are installed and user requested to sign, "
1023 "hence signing\n", using_gpg ? "gpg" : "gpg2",
1024 using_shasum ? "shasum" : "sha256sum");
1026 char* digestfile;
1027 digestfile = br_strcat(destination, ".digest");
1029 char* ascfile;
1030 ascfile = br_strcat(destination, ".digest.asc");
1032 if (g_file_test(digestfile, G_FILE_TEST_IS_REGULAR))
1033 unlink(digestfile);
1035 if (using_shasum)
1036 sprintf(command, "%s -a256 %s", sha256sum_path, destination);
1037 else
1038 sprintf(command, "%s %s", sha256sum_path, destination);
1040 if (verbose)
1041 fprintf(stderr, "%s\n", command);
1043 fp = popen(command, "r");
1045 if (fp == NULL)
1046 die(using_shasum ? "shasum command did not succeed" : "sha256sum command did not succeed");
1048 char output[1024];
1050 fgets(output, sizeof(output) - 1, fp);
1052 if (verbose)
1053 printf("%s: %s\n", using_shasum ? "shasum" : "sha256sum",
1054 g_strsplit_set(output, " ", -1)[0]);
1056 FILE* fpx = fopen(digestfile, "w");
1058 if (fpx != NULL) {
1059 fputs(g_strsplit_set(output, " ", -1)[0], fpx);
1060 fclose(fpx);
1063 if (pclose(fp) != 0)
1064 die(using_shasum ? "shasum command did not succeed" : "sha256sum command did not succeed");
1066 fp = NULL;
1068 if (g_file_test(ascfile, G_FILE_TEST_IS_REGULAR))
1069 unlink(ascfile);
1071 char* key_arg = NULL;
1073 if (sign_key && strlen(sign_key) > 0) {
1074 key_arg = calloc(sizeof(char), strlen(sign_key) + strlen("--local-user ''"));
1076 if (key_arg == NULL)
1077 die("malloc() failed");
1079 strcpy(key_arg, "--local-user '");
1080 strcat(key_arg, sign_key);
1081 strcat(key_arg, "'");
1084 sprintf(command,
1085 "%s --batch --detach-sign --armor %s %s %s",
1086 gpg2_path, key_arg ? key_arg : "", sign_args ? sign_args : "", digestfile
1089 free(key_arg);
1090 key_arg = NULL;
1092 if (verbose)
1093 fprintf(stderr, "%s\n", command);
1095 fp = popen(command, "r");
1097 if (pclose(fp) != 0) {
1098 fprintf(stderr, "ERROR: %s command did not succeed, could not sign, continuing\n", using_gpg ? "gpg" : "gpg2");
1099 return 1;
1102 fp = NULL;
1104 FILE* destinationfp = fopen(destination, "r+");
1106 // calculate signature
1108 unsigned long sig_offset = 0;
1109 unsigned long sig_length = 0;
1111 bool rv = appimage_get_elf_section_offset_and_length(destination, ".sha256_sig", &sig_offset, &sig_length);
1113 if (!rv || sig_offset == 0 || sig_length == 0) {
1114 die("Could not find section .sha256_sig in runtime");
1117 if (verbose) {
1118 printf("sig_offset: %lu\n", sig_offset);
1119 printf("sig_length: %lu\n", sig_length);
1122 if (sig_offset == 0) {
1123 die("Could not determine offset for signature");
1126 if (destinationfp == NULL)
1127 die("Not able to open the destination file for writing, aborting");
1129 // if(strlen(updateinformation)>sig_length)
1130 // die("signature does not fit into segment, aborting");
1132 fseek(destinationfp, sig_offset, SEEK_SET);
1134 FILE* ascfilefp = fopen(ascfile, "rb");
1136 if (ascfilefp == NULL) {
1137 die("Not able to open the asc file for reading, aborting");
1140 static const int bufsize = 1024;
1141 char buffer[bufsize];
1143 size_t totalBytesRead = 0;
1145 while (!feof(ascfilefp)) {
1146 size_t bytesRead = fread(buffer, sizeof(char), bufsize, ascfilefp);
1147 totalBytesRead += bytesRead;
1149 if (totalBytesRead > sig_length) {
1150 die("Error: cannot embed key in AppImage: size exceeds reserved ELF section size");
1153 size_t bytesWritten = fwrite(buffer, sizeof(char), bytesRead, destinationfp);
1155 if (bytesRead != bytesWritten) {
1156 char message[128];
1157 sprintf(message, "Bytes read and written differ: %lu != %lu", (long unsigned) bytesRead,
1158 (long unsigned) bytesWritten);
1159 die(message);
1163 fclose(ascfilefp);
1164 if (g_file_test(digestfile, G_FILE_TEST_IS_REGULAR))
1165 unlink(digestfile);
1167 if (sign_key == NULL || strlen(sign_key) > 0) {
1168 // read which key was used to sign from signature
1169 sprintf(command, "%s --batch --list-packets %s", gpg2_path, ascfile);
1171 fp = popen(command, "r");
1173 if (fp == NULL)
1174 die("Failed to call gpg[2] to detect signature's key ID");
1176 while (!feof(fp)) {
1177 size_t bytesRead = fread(buffer, sizeof(char), bufsize, fp);
1179 char* keyid_pos = strstr(buffer, "keyid");
1181 if (keyid_pos == NULL)
1182 continue;
1184 char* keyIDBegin = keyid_pos + strlen("keyid ");
1185 char* endOfKeyID = strstr(keyIDBegin, "\n");
1187 sign_key = calloc(endOfKeyID - keyIDBegin, sizeof(char));
1188 memcpy(sign_key, keyIDBegin, endOfKeyID - keyIDBegin);
1191 // read rest of process input to avoid broken pipe error
1192 while (!feof(fp)) {
1193 fread(buffer, sizeof(char), bufsize, fp);
1196 int retval = pclose(fp);
1197 fp = NULL;
1199 if (retval != 0)
1200 die("Failed to call gpg[2] to detect signature's key ID");
1203 if (g_file_test(ascfile, G_FILE_TEST_IS_REGULAR))
1204 unlink(ascfile);
1207 // export key and write into section
1209 sprintf(command, "%s --batch --export --armor %s", gpg2_path, sign_key);
1211 unsigned long key_offset = 0, key_length = 0;
1213 bool rv = appimage_get_elf_section_offset_and_length(destination, ".sig_key", &key_offset, &key_length);
1215 if (verbose) {
1216 printf("key_offset: %lu\n", key_offset);
1217 printf("key_length: %lu\n", key_length);
1220 if (!rv || key_offset == 0 || key_length == 0) {
1221 die("Could not find section .sig_key in runtime");
1224 fseek(destinationfp, key_offset, SEEK_SET);
1226 fp = popen(command, "r");
1228 if (fp == NULL)
1229 die("Failed to call gpg[2] to export the signature key");
1231 static const int bufsize = 1024;
1232 char buffer[bufsize];
1234 size_t totalBytesRead = 0;
1235 while (!feof(fp)) {
1236 size_t bytesRead = fread(buffer, sizeof(char), bufsize, fp);
1237 totalBytesRead += bytesRead;
1239 if (totalBytesRead > key_length) {
1240 // read rest of process input to avoid broken pipe error
1241 while (!feof(fp)) {
1242 fread(buffer, sizeof(char), bufsize, fp);
1245 pclose(fp);
1246 die("Error: cannot embed key in AppImage: size exceeds reserved ELF section size");
1249 size_t bytesWritten = fwrite(buffer, sizeof(char), bytesRead, destinationfp);
1251 if (bytesRead != bytesWritten) {
1252 char message[128];
1253 sprintf(message, "Error: Bytes read and written differ: %lu != %lu",
1254 (long unsigned) bytesRead, (long unsigned) bytesWritten);
1255 die(message);
1259 int exportexitcode = pclose(fp);
1260 fp = NULL;
1262 if (exportexitcode != 0) {
1263 char message[128];
1264 sprintf(message, "GPG key export failed: exit code %d", exportexitcode);
1265 die(message);
1268 fclose(destinationfp);
1273 /* If updateinformation was provided, then we also generate the zsync file (after having signed the AppImage) */
1274 if (updateinformation != NULL) {
1275 gchar* zsyncmake_path = g_find_program_in_path("zsyncmake");
1276 if (!zsyncmake_path) {
1277 fprintf(stderr, "zsyncmake is not installed/bundled, skipping\n");
1278 } else {
1279 fprintf(stderr, "zsyncmake is available and updateinformation is provided, "
1280 "hence generating zsync file\n");
1282 int pid = fork();
1284 if (verbose)
1285 fprintf(stderr, "%s %s -u %s", zsyncmake_path, destination, basename(destination));
1287 if (pid == -1) {
1288 // fork error
1289 die("fork() failed");
1290 } else if (pid == 0) {
1291 char* zsyncmake_command[] = {zsyncmake_path, destination, "-u", basename(destination), NULL};
1293 // redirect stdout/stderr to /dev/null
1294 int fd = open("/dev/null", O_WRONLY);
1296 dup2(fd, 1);
1297 dup2(fd, 2);
1298 close(fd);
1300 execv(zsyncmake_path, zsyncmake_command);
1302 die("execv() should not return");
1303 } else {
1304 int exitstatus;
1306 if (waitpid(pid, &exitstatus, 0) == -1) {
1307 perror("waitpid() failed");
1308 exit(EXIT_FAILURE);
1311 if (WEXITSTATUS(exitstatus) != 0)
1312 die("zsyncmake command did not succeed");
1317 fprintf(stderr, "Success\n\n");
1318 fprintf(stderr, "Please consider submitting your AppImage to AppImageHub, the crowd-sourced\n");
1319 fprintf(stderr, "central directory of available AppImages, by opening a pull request\n");
1320 fprintf(stderr, "at https://github.com/AppImage/appimage.github.io\n");
1323 /* If the first argument is a regular file, then we assume that we should unpack it */
1324 if (g_file_test (remaining_args[0], G_FILE_TEST_IS_REGULAR)){
1325 fprintf (stdout, "%s is a file, assuming it is an AppImage and should be unpacked\n", remaining_args[0]);
1326 die("To be implemented");
1329 return 0;