feat: Allow www.pling.com and www.appimagehub.com as update source
[appimagekit.git] / src / appimagetool.c
blobb9939253eb55b41f31b9d6b2e6470345c2c6a8ac
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 <fcntl.h>
41 #include "squashfuse.h"
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/wait.h>
47 #include "binreloc.h"
49 #include <libgen.h>
51 #include <unistd.h>
52 #include <string.h>
53 #include <limits.h>
54 #include <stdbool.h>
56 #include "appimage/appimage.h"
58 #ifdef __linux__
59 #define HAVE_BINARY_RUNTIME
60 extern char runtime[];
61 extern unsigned int runtime_len;
62 #endif
64 enum fARCH {
65 fARCH_i386,
66 fARCH_x86_64,
67 fARCH_arm,
68 fARCH_aarch64
71 static gchar const APPIMAGEIGNORE[] = ".appimageignore";
72 static char _exclude_file_desc[256];
74 static gboolean list = FALSE;
75 static gboolean verbose = FALSE;
76 static gboolean showVersionOnly = FALSE;
77 static gboolean sign = FALSE;
78 static gboolean no_appstream = FALSE;
79 gchar **remaining_args = NULL;
80 gchar *updateinformation = NULL;
81 static gboolean guess_update_information = FALSE;
82 gchar *bintray_user = NULL;
83 gchar *bintray_repo = NULL;
84 gchar *sqfs_comp = "gzip";
85 gchar *exclude_file = NULL;
86 gchar *runtime_file = NULL;
87 gchar *sign_args = NULL;
88 gchar *sign_key = NULL;
89 gchar *pathToMksquashfs = NULL;
91 // #####################################################################
93 static void die(const char *msg) {
94 fprintf(stderr, "%s\n", msg);
95 exit(1);
98 /* Function that prints the contents of a squashfs file
99 * using libsquashfuse (#include "squashfuse.h") */
100 int sfs_ls(char* image) {
101 sqfs_err err = SQFS_OK;
102 sqfs_traverse trv;
103 sqfs fs;
105 ssize_t fs_offset = appimage_get_elf_size(image);
107 // error check
108 if (fs_offset < 0)
109 die("failed to read elf size");
111 if ((err = sqfs_open_image(&fs, image, fs_offset)))
112 die("sqfs_open_image error");
114 if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs))))
115 die("sqfs_traverse_open error");
116 while (sqfs_traverse_next(&trv, &err)) {
117 if (!trv.dir_end) {
118 printf("%s\n", trv.path);
121 if (err)
122 die("sqfs_traverse_next error");
123 sqfs_traverse_close(&trv);
125 sqfs_fd_close(fs.fd);
126 return 0;
129 /* Generate a squashfs filesystem using mksquashfs on the $PATH
130 * execlp(), execvp(), and execvpe() search on the $PATH */
131 int sfs_mksquashfs(char *source, char *destination, int offset) {
132 pid_t pid = fork();
134 if (pid == -1) {
135 // error, failed to fork()
136 return(-1);
137 } else if (pid > 0) {
138 int status;
139 waitpid(pid, &status, 0);
140 } else {
141 // we are the child
142 gchar* offset_string;
143 offset_string = g_strdup_printf("%i", offset);
145 char* args[32];
146 bool use_xz = strcmp(sqfs_comp, "xz") >= 0;
148 int i = 0;
149 #ifndef AUXILIARY_FILES_DESTINATION
150 args[i++] = "mksquashfs";
151 #else
152 args[i++] = pathToMksquashfs;
153 #endif
154 args[i++] = source;
155 args[i++] = destination;
156 args[i++] = "-offset";
157 args[i++] = offset_string;
158 args[i++] = "-comp";
160 if (use_xz)
161 args[i++] = "xz";
162 else
163 args[i++] = sqfs_comp;
165 args[i++] = "-root-owned";
166 args[i++] = "-noappend";
168 if (use_xz) {
169 // https://jonathancarter.org/2015/04/06/squashfs-performance-testing/ says:
170 // improved performance by using a 16384 block size with a sacrifice of around 3% more squashfs image space
171 args[i++] = "-Xdict-size";
172 args[i++] = "100%";
173 args[i++] = "-b";
174 args[i++] = "16384";
177 // check if ignore file exists and use it if possible
178 if (access(APPIMAGEIGNORE, F_OK) >= 0) {
179 printf("Including %s", APPIMAGEIGNORE);
180 args[i++] = "-wildcards";
181 args[i++] = "-ef";
183 // avoid warning: assignment discards ‘const’ qualifier
184 char* buf = strdup(APPIMAGEIGNORE);
185 args[i++] = buf;
188 // if an exclude file has been passed on the command line, should be used, too
189 if (exclude_file != 0 && strlen(exclude_file) > 0) {
190 if (access(exclude_file, F_OK) < 0) {
191 printf("WARNING: exclude file %s not found!", exclude_file);
192 return -1;
195 args[i++] = "-wildcards";
196 args[i++] = "-ef";
197 args[i++] = exclude_file;
200 args[i++] = "-mkfs-time";
201 args[i++] = "0";
203 args[i++] = 0;
205 if (verbose) {
206 printf("mksquashfs commandline: ");
207 for (char** t = args; *t != 0; t++) {
208 printf("%s ", *t);
210 printf("\n");
213 #ifndef AUXILIARY_FILES_DESTINATION
214 execvp("mksquashfs", args);
215 #else
216 execvp(pathToMksquashfs, args);
217 #endif
219 perror("execlp"); // exec*() returns only on error
220 return -1; // exec never returns
222 return 0;
225 /* Validate desktop file using desktop-file-validate on the $PATH
226 * execlp(), execvp(), and execvpe() search on the $PATH */
227 int validate_desktop_file(char *file) {
228 int statval;
229 int child_pid;
230 child_pid = fork();
231 if(child_pid == -1)
233 printf("could not fork! \n");
234 return 1;
236 else if(child_pid == 0)
238 execlp("desktop-file-validate", "desktop-file-validate", file, NULL);
240 else
242 waitpid(child_pid, &statval, WUNTRACED | WCONTINUED);
243 if(WIFEXITED(statval)){
244 return(WEXITSTATUS(statval));
247 return -1;
250 /* Generate a squashfs filesystem
251 * The following would work if we link to mksquashfs.o after we renamed
252 * main() to mksquashfs_main() in mksquashfs.c but we don't want to actually do
253 * this because squashfs-tools is not under a permissive license
254 * i *nt sfs_mksquashfs(char *source, char *destination) {
255 * char *child_argv[5];
256 * child_argv[0] = NULL;
257 * child_argv[1] = source;
258 * child_argv[2] = destination;
259 * child_argv[3] = "-root-owned";
260 * child_argv[4] = "-noappend";
261 * mksquashfs_main(5, child_argv);
265 /* in-place modification of the string, and assuming the buffer pointed to by
266 * line is large enough to hold the resulting string*/
267 static void replacestr(char *line, const char *search, const char *replace)
269 char *sp = NULL;
271 if ((sp = strstr(line, search)) == NULL) {
272 return;
274 int search_len = strlen(search);
275 int replace_len = strlen(replace);
276 int tail_len = strlen(sp+search_len);
278 memmove(sp+replace_len,sp+search_len,tail_len+1);
279 memcpy(sp, replace, replace_len);
281 /* Do it recursively again until no more work to do */
283 if ((sp = strstr(line, search))) {
284 replacestr(line, search, replace);
288 int count_archs(bool* archs) {
289 int countArchs = 0;
290 int i;
291 for (i = 0; i < 4; i++) {
292 countArchs += archs[i];
294 return countArchs;
297 gchar* getArchName(bool* archs) {
298 if (archs[fARCH_i386])
299 return "i386";
300 else if (archs[fARCH_x86_64])
301 return "x86_64";
302 else if (archs[fARCH_arm])
303 return "armhf";
304 else if (archs[fARCH_aarch64])
305 return "aarch64";
306 else
307 return "all";
310 void extract_arch_from_e_machine_field(int16_t e_machine, const gchar* sourcename, bool* archs) {
311 if (e_machine == 3) {
312 archs[fARCH_i386] = 1;
313 if(verbose)
314 fprintf(stderr, "%s used for determining architecture i386\n", sourcename);
317 if (e_machine == 62) {
318 archs[fARCH_x86_64] = 1;
319 if(verbose)
320 fprintf(stderr, "%s used for determining architecture x86_64\n", sourcename);
323 if (e_machine == 40) {
324 archs[fARCH_arm] = 1;
325 if(verbose)
326 fprintf(stderr, "%s used for determining architecture armhf\n", sourcename);
329 if (e_machine == 183) {
330 archs[fARCH_aarch64] = 1;
331 if(verbose)
332 fprintf(stderr, "%s used for determining architecture aarch64\n", sourcename);
336 void extract_arch_from_text(gchar *archname, const gchar* sourcename, bool* archs) {
337 if (archname) {
338 archname = g_strstrip(archname);
339 if (archname) {
340 replacestr(archname, "-", "_");
341 replacestr(archname, " ", "_");
342 if (g_ascii_strncasecmp("i386", archname, 20) == 0
343 || g_ascii_strncasecmp("i486", archname, 20) == 0
344 || g_ascii_strncasecmp("i586", archname, 20) == 0
345 || g_ascii_strncasecmp("i686", archname, 20) == 0
346 || g_ascii_strncasecmp("intel_80386", archname, 20) == 0
347 || g_ascii_strncasecmp("intel_80486", archname, 20) == 0
348 || g_ascii_strncasecmp("intel_80586", archname, 20) == 0
349 || g_ascii_strncasecmp("intel_80686", archname, 20) == 0
351 archs[fARCH_i386] = 1;
352 if (verbose)
353 fprintf(stderr, "%s used for determining architecture i386\n", sourcename);
354 } else if (g_ascii_strncasecmp("x86_64", archname, 20) == 0) {
355 archs[fARCH_x86_64] = 1;
356 if (verbose)
357 fprintf(stderr, "%s used for determining architecture x86_64\n", sourcename);
358 } else if (g_ascii_strncasecmp("arm", archname, 20) == 0) {
359 archs[fARCH_arm] = 1;
360 if (verbose)
361 fprintf(stderr, "%s used for determining architecture ARM\n", sourcename);
362 } else if (g_ascii_strncasecmp("arm_aarch64", archname, 20) == 0) {
363 archs[fARCH_aarch64] = 1;
364 if (verbose)
365 fprintf(stderr, "%s used for determining architecture ARM aarch64\n", sourcename);
371 int16_t read_elf_e_machine_field(const gchar* file_path) {
372 int16_t e_machine = 0x00;
373 FILE* file = 0;
374 file = fopen(file_path, "rb");
375 if (file) {
376 fseek(file, 0x12, SEEK_SET);
377 fgets((char*) (&e_machine), 0x02, file);
378 fclose(file);
381 return e_machine;
384 void guess_arch_of_file(const gchar *archfile, bool* archs) {
385 int16_t e_machine_field = read_elf_e_machine_field(archfile);
386 extract_arch_from_e_machine_field(e_machine_field, archfile, archs);
389 void find_arch(const gchar *real_path, const gchar *pattern, bool* archs) {
390 GDir *dir;
391 gchar *full_name;
392 dir = g_dir_open(real_path, 0, NULL);
393 if (dir != NULL) {
394 const gchar *entry;
395 while ((entry = g_dir_read_name(dir)) != NULL) {
396 full_name = g_build_filename(real_path, entry, NULL);
397 if (g_file_test(full_name, G_FILE_TEST_IS_SYMLINK)) {
398 } else if (g_file_test(full_name, G_FILE_TEST_IS_DIR)) {
399 find_arch(full_name, pattern, archs);
400 } else if (g_file_test(full_name, G_FILE_TEST_IS_EXECUTABLE) || g_pattern_match_simple(pattern, entry) ) {
401 guess_arch_of_file(full_name, archs);
404 g_dir_close(dir);
406 else {
407 g_warning("%s: %s", real_path, g_strerror(errno));
411 gchar* find_first_matching_file_nonrecursive(const gchar *real_path, const gchar *pattern) {
412 GDir *dir;
413 gchar *full_name;
414 dir = g_dir_open(real_path, 0, NULL);
415 if (dir != NULL) {
416 const gchar *entry;
417 while ((entry = g_dir_read_name(dir)) != NULL) {
418 full_name = g_build_filename(real_path, entry, NULL);
419 if (g_file_test(full_name, G_FILE_TEST_IS_REGULAR)) {
420 if(g_pattern_match_simple(pattern, entry))
421 return(full_name);
424 g_dir_close(dir);
426 else {
427 g_warning("%s: %s", real_path, g_strerror(errno));
429 return NULL;
432 gchar* get_desktop_entry(GKeyFile *kf, char *key) {
433 gchar *value = g_key_file_get_string (kf, "Desktop Entry", key, NULL);
434 if (! value){
435 fprintf(stderr, "%s entry not found in desktop file\n", key);
437 return value;
440 bool readFile(char* filename, int* size, char** buffer) {
441 FILE* f = fopen(filename, "rb");
442 if (f==NULL) {
443 *buffer = 0;
444 *size = 0;
445 return false;
448 fseek(f, 0, SEEK_END);
449 long fsize = ftell(f);
450 fseek(f, 0, SEEK_SET);
452 char *indata = malloc(fsize);
453 fread(indata, fsize, 1, f);
454 fclose(f);
455 *size = (int)fsize;
456 *buffer = indata;
457 return TRUE;
460 /* run a command outside the current appimage, block environs like LD_LIBRARY_PATH */
461 int run_external(const char *filename, char *const argv []) {
462 int pid = fork();
463 if (pid < 0) {
464 g_print("run_external: fork failed");
465 // fork failed
466 exit(1);
467 } else if (pid == 0) {
468 // blocks env defined in resources/AppRun
469 unsetenv("LD_LIBRARY_PATH");
470 unsetenv("PYTHONPATH");
471 unsetenv("XDG_DATA_DIRS");
472 unsetenv("PERLLIB");
473 unsetenv("GSETTINGS_SCHEMA_DIR");
474 unsetenv("QT_PLUGIN_PATH");
475 // runs command
476 execv(filename, argv);
477 // execv(3) returns, indicating error
478 g_print("run_external: subprocess execv(3) got error %s", g_strerror(errno));
479 exit(1);
480 } else {
481 int wstatus;
482 if (waitpid(pid, &wstatus, 0) == -1) {
483 g_print("run_external: wait failed");
484 return -1;
486 if (WIFEXITED(wstatus) && (WEXITSTATUS(wstatus) == 0)) {
487 return 0;
488 } else {
489 g_print("run_external: subprocess exited with status %d", WEXITSTATUS(wstatus));
490 return 1;
495 // #####################################################################
497 static GOptionEntry entries[] =
499 { "list", 'l', 0, G_OPTION_ARG_NONE, &list, "List files in SOURCE AppImage", NULL },
500 { "updateinformation", 'u', 0, G_OPTION_ARG_STRING, &updateinformation, "Embed update information STRING; if zsyncmake is installed, generate zsync file", NULL },
501 { "guess", 'g', 0, G_OPTION_ARG_NONE, &guess_update_information, "Guess update information based on Travis CI or GitLab environment variables", NULL },
502 { "bintray-user", 0, 0, G_OPTION_ARG_STRING, &bintray_user, "Bintray user name", NULL },
503 { "bintray-repo", 0, 0, G_OPTION_ARG_STRING, &bintray_repo, "Bintray repository", NULL },
504 { "version", 0, 0, G_OPTION_ARG_NONE, &showVersionOnly, "Show version number", NULL },
505 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Produce verbose output", NULL },
506 { "sign", 's', 0, G_OPTION_ARG_NONE, &sign, "Sign with gpg[2]", NULL },
507 { "comp", 0, 0, G_OPTION_ARG_STRING, &sqfs_comp, "Squashfs compression", NULL },
508 { "no-appstream", 'n', 0, G_OPTION_ARG_NONE, &no_appstream, "Do not check AppStream metadata", NULL },
509 { "exclude-file", 0, 0, G_OPTION_ARG_STRING, &exclude_file, _exclude_file_desc, NULL },
510 { "runtime-file", 0, 0, G_OPTION_ARG_STRING, &runtime_file, "Runtime file to use", NULL },
511 { "sign-key", 0, 0, G_OPTION_ARG_STRING, &sign_key, "Key ID to use for gpg[2] signatures", NULL},
512 { "sign-args", 0, 0, G_OPTION_ARG_STRING, &sign_args, "Extra arguments to use when signing with gpg[2]", NULL},
513 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &remaining_args, NULL, NULL },
514 { 0,0,0,0,0,0,0 }
518 main (int argc, char *argv[])
521 /* Parse Travis CI environment variables.
522 * https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
523 * TRAVIS_COMMIT: The commit that the current build is testing.
524 * TRAVIS_REPO_SLUG: The slug (in form: owner_name/repo_name) of the repository currently being built.
525 * TRAVIS_TAG: If the current build is for a git tag, this variable is set to the tag’s name.
526 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
527 // char* travis_commit;
528 // travis_commit = getenv("TRAVIS_COMMIT");
529 char* travis_repo_slug;
530 travis_repo_slug = getenv("TRAVIS_REPO_SLUG");
531 char* travis_tag;
532 travis_tag = getenv("TRAVIS_TAG");
533 char* travis_pull_request;
534 travis_pull_request = getenv("TRAVIS_PULL_REQUEST");
535 /* https://github.com/probonopd/uploadtool */
536 char* github_token;
537 github_token = getenv("GITHUB_TOKEN");
539 /* Parse GitLab CI environment variables.
540 * https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables
541 * echo "${CI_PROJECT_URL}/-/jobs/artifacts/${CI_COMMIT_REF_NAME}/raw/QtQuickApp-x86_64.AppImage?job=${CI_JOB_NAME}"
543 char* CI_PROJECT_URL;
544 CI_PROJECT_URL = getenv("CI_PROJECT_URL");
545 char* CI_COMMIT_REF_NAME;
546 CI_COMMIT_REF_NAME = getenv("CI_COMMIT_REF_NAME"); // The branch or tag name for which project is built
547 char* CI_JOB_NAME;
548 CI_JOB_NAME = getenv("CI_JOB_NAME"); // The name of the job as defined in .gitlab-ci.yml
550 /* Parse OWD environment variable.
551 * If it is available then cd there. It is the original CWD prior to running AppRun */
552 char* owd_env = NULL;
553 owd_env = getenv("OWD");
554 if(NULL!=owd_env){
555 int ret;
556 ret = chdir(owd_env);
557 if (ret != 0){
558 fprintf(stderr, "Could not cd into %s\n", owd_env);
559 exit(1);
563 GError *error = NULL;
564 GOptionContext *context;
565 char command[PATH_MAX];
567 // initialize help text of argument
568 sprintf(_exclude_file_desc, "Uses given file as exclude file for mksquashfs, in addition to %s.", APPIMAGEIGNORE);
570 context = g_option_context_new ("SOURCE [DESTINATION] - Generate, extract, and inspect AppImages");
571 g_option_context_add_main_entries (context, entries, NULL);
572 // g_option_context_add_group (context, gtk_get_option_group (TRUE));
573 if (!g_option_context_parse (context, &argc, &argv, &error))
575 fprintf(stderr, "Option parsing failed: %s\n", error->message);
576 exit(1);
579 fprintf(
580 stderr,
581 "appimagetool, %s (commit %s), build %s built on %s\n",
582 RELEASE_NAME, GIT_COMMIT, BUILD_NUMBER, BUILD_DATE
585 // always show version, but exit immediately if only the version number was requested
586 if (showVersionOnly)
587 exit(0);
589 if(!((0 == strcmp(sqfs_comp, "gzip")) || (0 ==strcmp(sqfs_comp, "xz"))))
590 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.");
591 /* Check for dependencies here. Better fail early if they are not present. */
592 if(! g_find_program_in_path ("file"))
593 die("file command is missing but required, please install it");
594 #ifndef AUXILIARY_FILES_DESTINATION
595 if(! g_find_program_in_path ("mksquashfs"))
596 die("mksquashfs command is missing but required, please install it");
597 #else
599 // build path relative to appimagetool binary
600 char *appimagetoolDirectory = dirname(realpath("/proc/self/exe", NULL));
601 if (!appimagetoolDirectory) {
602 g_print("Could not access /proc/self/exe\n");
603 exit(1);
606 pathToMksquashfs = g_build_filename(appimagetoolDirectory, "..", AUXILIARY_FILES_DESTINATION, "mksquashfs", NULL);
608 if (!g_file_test(pathToMksquashfs, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_EXECUTABLE)) {
609 g_printf("No such file or directory: %s\n", pathToMksquashfs);
610 g_free(pathToMksquashfs);
611 exit(1);
614 #endif
615 if(! g_find_program_in_path ("desktop-file-validate"))
616 die("desktop-file-validate command is missing, please install it");
617 if(! g_find_program_in_path ("zsyncmake"))
618 g_print("WARNING: zsyncmake command is missing, please install it if you want to use binary delta updates\n");
619 if(! no_appstream)
620 if(! g_find_program_in_path ("appstreamcli"))
621 g_print("WARNING: appstreamcli command is missing, please install it if you want to use AppStream metadata\n");
622 if(! g_find_program_in_path ("gpg2") && ! g_find_program_in_path ("gpg"))
623 g_print("WARNING: gpg2 or gpg command is missing, please install it if you want to create digital signatures\n");
624 if(! g_find_program_in_path ("sha256sum") && ! g_find_program_in_path ("shasum"))
625 g_print("WARNING: sha256sum or shasum command is missing, please install it if you want to create digital signatures\n");
627 if(!&remaining_args[0])
628 die("SOURCE is missing");
630 /* If in list mode */
631 if (list){
632 sfs_ls(remaining_args[0]);
633 exit(0);
636 /* If the first argument is a directory, then we assume that we should package it */
637 if (g_file_test(remaining_args[0], G_FILE_TEST_IS_DIR)) {
638 /* Parse VERSION environment variable.
639 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6
640 * Also, if VERSION is not set and -g is called and if git is on the path, use
641 * git rev-parse --short HEAD
642 * TODO: Might also want to somehow make use of
643 * git rev-parse --abbrev-ref HEAD
644 * git log -1 --format=%ci */
645 gchar* version_env = getenv("VERSION");
647 if (guess_update_information) {
648 char* gitPath = g_find_program_in_path("git");
650 if (gitPath != NULL) {
651 if (version_env == NULL) {
652 GError* error = NULL;
653 gchar* out = NULL;
655 char command_line[] = "git rev-parse --short HEAD";
657 // *not* the exit code! must be interpreted via g_spawn_check_exit_status!
658 int exit_status = -1;
660 // g_spawn_command_line_sync returns whether the program succeeded
661 // its return value is buggy, hence we're using g_spawn_check_exit_status to check for errors
662 g_spawn_command_line_sync(command_line, &out, NULL, &exit_status, &error);
664 // g_spawn_command_line_sync might have set error already, in that case we don't want to overwrite
665 if (error != NULL || !g_spawn_check_exit_status(exit_status, &error)) {
666 if (error == NULL) {
667 g_printerr("Failed to run 'git rev-parse --short HEAD, but failed to interpret GLib error state: %d\n", exit_status);
668 } else {
669 g_printerr("Failed to run 'git rev-parse --short HEAD: %s (code %d)\n", error->message, error->code);
671 } else {
672 version_env = g_strstrip(out);
674 if (version_env != NULL) {
675 g_printerr("NOTE: Using the output of 'git rev-parse --short HEAD' as the version:\n");
676 g_printerr(" %s\n", version_env);
677 g_printerr(" Please set the $VERSION environment variable if this is not intended\n");
683 free(gitPath);
686 char *destination;
687 char source[PATH_MAX];
688 realpath(remaining_args[0], source);
690 /* Check if *.desktop file is present in source AppDir */
691 gchar *desktop_file = find_first_matching_file_nonrecursive(source, "*.desktop");
692 if(desktop_file == NULL){
693 die("Desktop file not found, aborting");
695 if(verbose)
696 fprintf (stdout, "Desktop file: %s\n", desktop_file);
698 if(g_find_program_in_path ("desktop-file-validate")) {
699 if(validate_desktop_file(desktop_file) != 0){
700 fprintf(stderr, "ERROR: Desktop file contains errors. Please fix them. Please see\n");
701 fprintf(stderr, " https://standards.freedesktop.org/desktop-entry-spec/1.0/n");
702 die(" for more information.");
706 /* Read information from .desktop file */
707 GKeyFile *kf = g_key_file_new ();
708 if (!g_key_file_load_from_file (kf, desktop_file, G_KEY_FILE_KEEP_TRANSLATIONS | G_KEY_FILE_KEEP_COMMENTS, NULL))
709 die(".desktop file cannot be parsed");
710 if (!get_desktop_entry(kf, "Categories"))
711 die(".desktop file is missing a Categories= key");
713 if(verbose){
714 fprintf (stderr,"Name: %s\n", get_desktop_entry(kf, "Name"));
715 fprintf (stderr,"Icon: %s\n", get_desktop_entry(kf, "Icon"));
716 fprintf (stderr,"Exec: %s\n", get_desktop_entry(kf, "Exec"));
717 fprintf (stderr,"Comment: %s\n", get_desktop_entry(kf, "Comment"));
718 fprintf (stderr,"Type: %s\n", get_desktop_entry(kf, "Type"));
719 fprintf (stderr,"Categories: %s\n", get_desktop_entry(kf, "Categories"));
722 /* Determine the architecture */
723 bool archs[4] = {0, 0, 0, 0};
724 extract_arch_from_text(getenv("ARCH"), "Environmental variable ARCH", archs);
725 if (count_archs(archs) != 1) {
726 /* If no $ARCH variable is set check a file */
727 /* We use the next best .so that we can find to determine the architecture */
728 find_arch(source, "*.so.*", archs);
729 int countArchs = count_archs(archs);
730 if (countArchs != 1) {
731 if (countArchs < 1)
732 fprintf(stderr, "Unable to guess the architecture of the AppDir source directory \"%s\"\n", remaining_args[0]);
733 else
734 fprintf(stderr, "More than one architectures were found of the AppDir source directory \"%s\"\n", remaining_args[0]);
735 fprintf(stderr, "A valid architecture with the ARCH environmental variable should be provided\ne.g. ARCH=x86_64 %s", argv[0]),
736 die(" ...");
739 gchar* arch = getArchName(archs);
740 fprintf(stderr, "Using architecture %s\n", arch);
742 FILE *fp;
743 char app_name_for_filename[PATH_MAX];
744 sprintf(app_name_for_filename, "%s", get_desktop_entry(kf, "Name"));
745 replacestr(app_name_for_filename, " ", "_");
747 if(verbose)
748 fprintf (stderr,"App name for filename: %s\n", app_name_for_filename);
750 if (remaining_args[1]) {
751 destination = remaining_args[1];
752 } else {
753 /* No destination has been specified, to let's construct one
754 * TODO: Find out the architecture and use a $VERSION that might be around in the env */
755 char dest_path[PATH_MAX];
757 // if $VERSION is specified, we embed it into the filename
758 if (version_env != NULL) {
759 sprintf(dest_path, "%s-%s-%s.AppImage", app_name_for_filename, version_env, arch);
760 } else {
761 sprintf(dest_path, "%s-%s.AppImage", app_name_for_filename, arch);
764 destination = strdup(dest_path);
765 replacestr(destination, " ", "_");
768 // if $VERSION is specified, we embed its value into the desktop file
769 if (version_env != NULL) {
770 g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, "X-AppImage-Version", version_env);
772 if (!g_key_file_save_to_file(kf, desktop_file, NULL)) {
773 fprintf(stderr, "Could not save modified desktop file\n");
774 exit(1);
778 fprintf (stdout, "%s should be packaged as %s\n", source, destination);
779 /* Check if the Icon file is how it is expected */
780 gchar* icon_name = get_desktop_entry(kf, "Icon");
781 gchar* icon_file_path = NULL;
782 gchar* icon_file_png;
783 gchar* icon_file_svg;
784 gchar* icon_file_xpm;
785 icon_file_png = g_strdup_printf("%s/%s.png", source, icon_name);
786 icon_file_svg = g_strdup_printf("%s/%s.svg", source, icon_name);
787 icon_file_xpm = g_strdup_printf("%s/%s.xpm", source, icon_name);
788 if (g_file_test(icon_file_png, G_FILE_TEST_IS_REGULAR)) {
789 icon_file_path = icon_file_png;
790 } else if(g_file_test(icon_file_svg, G_FILE_TEST_IS_REGULAR)) {
791 icon_file_path = icon_file_svg;
792 } else if(g_file_test(icon_file_xpm, G_FILE_TEST_IS_REGULAR)) {
793 icon_file_path = icon_file_xpm;
794 } else {
795 fprintf (stderr, "%s{.png,.svg,.xpm} defined in desktop file but not found\n", icon_name);
796 fprintf (stderr, "For example, you could put a 256x256 pixel png into\n");
797 gchar *icon_name_with_png = g_strconcat(icon_name, ".png", NULL);
798 gchar *example_path = g_build_filename(source, "/", icon_name_with_png, NULL);
799 fprintf (stderr, "%s\n", example_path);
800 exit(1);
803 /* Check if .DirIcon is present in source AppDir */
804 gchar *diricon_path = g_build_filename(source, ".DirIcon", NULL);
806 if (! g_file_test(diricon_path, G_FILE_TEST_EXISTS)){
807 fprintf (stderr, "Deleting pre-existing .DirIcon\n");
808 g_unlink(diricon_path);
810 if (! g_file_test(diricon_path, G_FILE_TEST_IS_REGULAR)){
811 fprintf (stderr, "Creating .DirIcon symlink based on information from desktop file\n");
812 int res = symlink(basename(icon_file_path), diricon_path);
813 if(res)
814 die("Could not symlink .DirIcon");
817 /* Check if AppStream upstream metadata is present in source AppDir */
818 if(! no_appstream){
819 char application_id[PATH_MAX];
820 sprintf (application_id, "%s", basename(desktop_file));
821 replacestr(application_id, ".desktop", ".appdata.xml");
822 gchar *appdata_path = g_build_filename(source, "/usr/share/metainfo/", application_id, NULL);
823 if (! g_file_test(appdata_path, G_FILE_TEST_IS_REGULAR)){
824 fprintf (stderr, "WARNING: AppStream upstream metadata is missing, please consider creating it\n");
825 fprintf (stderr, " in usr/share/metainfo/%s\n", application_id);
826 fprintf (stderr, " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps\n");
827 fprintf (stderr, " for more information or use the generator at http://output.jsbin.com/qoqukof.\n");
828 } else {
829 fprintf (stderr, "AppStream upstream metadata found in usr/share/metainfo/%s\n", application_id);
830 /* Use ximion's appstreamcli to make sure that desktop file and appdata match together */
831 if(g_find_program_in_path ("appstreamcli")) {
832 char *args[] = {
833 "appstreamcli",
834 "validate-tree",
835 source,
836 NULL
838 g_print("Trying to validate AppStream information with the appstreamcli tool\n");
839 g_print("In case of issues, please refer to https://github.com/ximion/appstream\n");
840 int ret = run_external(g_find_program_in_path ("appstreamcli"), args);
841 if (ret != 0)
842 die("Failed to validate AppStream information with appstreamcli");
844 /* It seems that hughsie's appstream-util does additional validations */
845 if(g_find_program_in_path ("appstream-util")) {
846 char *args[] = {
847 "appstream-util",
848 "validate-relax",
849 appdata_path,
850 NULL
852 g_print("Trying to validate AppStream information with the appstream-util tool\n");
853 g_print("In case of issues, please refer to https://github.com/hughsie/appstream-glib\n");
854 int ret = run_external(g_find_program_in_path ("appstream-util"), args);
855 if (ret != 0)
856 die("Failed to validate AppStream information with appstream-util");
861 /* Upstream mksquashfs can currently not start writing at an offset,
862 * so we need a patched one. https://github.com/plougher/squashfs-tools/pull/13
863 * should hopefully change that. */
865 fprintf (stderr, "Generating squashfs...\n");
866 int size = 0;
867 char* data = NULL;
868 bool using_external_data = false;
869 if (runtime_file != NULL) {
870 if (!readFile(runtime_file, &size, &data))
871 die("Unable to load provided runtime file");
872 using_external_data = true;
873 } else {
874 #ifdef HAVE_BINARY_RUNTIME
875 /* runtime is embedded into this executable
876 * http://stupefydeveloper.blogspot.de/2008/08/cc-embed-binary-data-into-elf.html */
877 size = runtime_len;
878 data = runtime;
879 #else
880 die("No runtime file was provided");
881 #endif
883 if (verbose)
884 printf("Size of the embedded runtime: %d bytes\n", size);
886 int result = sfs_mksquashfs(source, destination, size);
887 if(result != 0)
888 die("sfs_mksquashfs error");
890 fprintf (stderr, "Embedding ELF...\n");
891 FILE *fpdst = fopen(destination, "rb+");
892 if (fpdst == NULL) {
893 die("Not able to open the AppImage for writing, aborting");
896 fseek(fpdst, 0, SEEK_SET);
897 fwrite(data, size, 1, fpdst);
898 fclose(fpdst);
899 if (using_external_data)
900 free(data);
902 fprintf (stderr, "Marking the AppImage as executable...\n");
903 if (chmod (destination, 0755) < 0) {
904 printf("Could not set executable bit, aborting\n");
905 exit(1);
908 if(bintray_user != NULL){
909 if(bintray_repo != NULL){
910 char buf[1024];
911 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);
912 updateinformation = buf;
913 printf("%s\n", updateinformation);
917 /* If the user has not provided update information but we know this is a Travis CI build,
918 * then fill in update information based on TRAVIS_REPO_SLUG */
919 if(guess_update_information){
920 if(travis_repo_slug){
921 if(!github_token) {
922 printf("Will not guess update information since $GITHUB_TOKEN is missing,\n");
923 if(0 != strcmp(travis_pull_request, "false")){
924 printf("please set it in the Travis CI Repository Settings for this project.\n");
925 printf("You can get one from https://github.com/settings/tokens\n");
926 } else {
927 printf("which is expected since this is a pull request\n");
929 } else {
930 gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake");
931 if(zsyncmake_path){
932 char buf[1024];
933 gchar **parts = g_strsplit (travis_repo_slug, "/", 2);
934 /* https://github.com/AppImage/AppImageSpec/blob/master/draft.md#github-releases
935 * gh-releases-zsync|probono|AppImages|latest|Subsurface*-x86_64.AppImage.zsync */
936 gchar *channel = "continuous";
937 if(travis_tag != NULL){
938 if((strcmp(travis_tag, "") != 0) && (strcmp(travis_tag, "continuous") != 0)) {
939 channel = "latest";
942 sprintf(buf, "gh-releases-zsync|%s|%s|%s|%s*-%s.AppImage.zsync", parts[0], parts[1], channel, app_name_for_filename, arch);
943 updateinformation = buf;
944 printf("Guessing update information based on $TRAVIS_TAG=%s and $TRAVIS_REPO_SLUG=%s\n", travis_tag, travis_repo_slug);
945 printf("%s\n", updateinformation);
946 } else {
947 printf("Will not guess update information since zsyncmake is missing\n");
950 } else if(CI_COMMIT_REF_NAME){
951 // ${CI_PROJECT_URL}/-/jobs/artifacts/${CI_COMMIT_REF_NAME}/raw/QtQuickApp-x86_64.AppImage?job=${CI_JOB_NAME}
952 gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake");
953 if(zsyncmake_path){
954 char buf[1024];
955 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);
956 updateinformation = buf;
957 printf("Guessing update information based on $CI_COMMIT_REF_NAME=%s and $CI_JOB_NAME=%s\n", CI_COMMIT_REF_NAME, CI_JOB_NAME);
958 printf("%s\n", updateinformation);
959 } else {
960 printf("Will not guess update information since zsyncmake is missing\n");
965 /* If updateinformation was provided, then we check and embed it */
966 if(updateinformation != NULL){
967 if(!g_str_has_prefix(updateinformation,"zsync|"))
968 if(!g_str_has_prefix(updateinformation,"bintray-zsync|"))
969 if(!g_str_has_prefix(updateinformation,"gh-releases-zsync|"))
970 if(!g_str_has_prefix(updateinformation,"pling-v1-zsync|"))
971 die("The provided updateinformation is not in a recognized format");
973 gchar **ui_type = g_strsplit_set(updateinformation, "|", -1);
975 if(verbose)
976 printf("updateinformation type: %s\n", ui_type[0]);
977 /* TODO: Further checking of the updateinformation */
980 unsigned long ui_offset = 0;
981 unsigned long ui_length = 0;
983 bool rv = appimage_get_elf_section_offset_and_length(destination, ".upd_info", &ui_offset, &ui_length);
985 if (!rv || ui_offset == 0 || ui_length == 0) {
986 die("Could not find section .upd_info in runtime");
989 if(verbose) {
990 printf("ui_offset: %lu\n", ui_offset);
991 printf("ui_length: %lu\n", ui_length);
993 if(ui_offset == 0) {
994 die("Could not determine offset for updateinformation");
995 } else {
996 if(strlen(updateinformation)>ui_length)
997 die("updateinformation does not fit into segment, aborting");
998 FILE *fpdst2 = fopen(destination, "r+");
999 if (fpdst2 == NULL)
1000 die("Not able to open the destination file for writing, aborting");
1001 fseek(fpdst2, ui_offset, SEEK_SET);
1002 // fseek(fpdst2, ui_offset, SEEK_SET);
1003 // fwrite(0x00, 1, 1024, fpdst); // FIXME: Segfaults; why?
1004 // fseek(fpdst, ui_offset, SEEK_SET);
1005 fwrite(updateinformation, strlen(updateinformation), 1, fpdst2);
1006 fclose(fpdst2);
1010 // calculate and embed MD5 digest
1012 fprintf(stderr, "Embedding MD5 digest\n");
1014 unsigned long digest_md5_offset = 0;
1015 unsigned long digest_md5_length = 0;
1017 bool rv = appimage_get_elf_section_offset_and_length(destination, ".digest_md5", &digest_md5_offset, &digest_md5_length);
1019 if (!rv || digest_md5_offset == 0 || digest_md5_length == 0) {
1020 die("Could not find section .digest_md5 in runtime");
1023 static const unsigned long section_size = 16;
1025 if (digest_md5_length < section_size) {
1026 fprintf(
1027 stderr,
1028 ".digest_md5 section in runtime's ELF header is too small"
1029 "(found %lu bytes, minimum required: %lu bytes)\n",
1030 digest_md5_length, section_size
1032 exit(1);
1035 char digest_buffer[section_size];
1037 if (!appimage_type2_digest_md5(destination, digest_buffer)) {
1038 die("Failed to calculate MD5 digest");
1041 FILE* destinationfp = fopen(destination, "r+");
1043 if (destinationfp == NULL) {
1044 die("Failed to open AppImage for updating");
1047 if (fseek(destinationfp, digest_md5_offset, SEEK_SET) != 0) {
1048 fclose(destinationfp);
1049 die("Failed to embed MD5 digest: could not seek to section offset");
1052 if (fwrite(digest_buffer, sizeof(char), section_size, destinationfp) != section_size) {
1053 fclose(destinationfp);
1054 die("Failed to embed MD5 digest: write failed");
1057 fclose(destinationfp);
1060 if (sign) {
1061 bool using_gpg = FALSE;
1062 bool using_shasum = FALSE;
1064 /* The user has indicated that he wants to sign */
1065 gchar* gpg2_path = g_find_program_in_path("gpg2");
1067 if (!gpg2_path) {
1068 gpg2_path = g_find_program_in_path("gpg");
1069 using_gpg = TRUE;
1072 gchar* sha256sum_path = g_find_program_in_path("sha256sum");
1074 if (!sha256sum_path) {
1075 sha256sum_path = g_find_program_in_path("shasum");
1076 using_shasum = 1;
1079 if (!gpg2_path) {
1080 fprintf(stderr, "gpg2 or gpg is not installed, cannot sign\n");
1081 } else if (!sha256sum_path) {
1082 fprintf(stderr, "sha256sum or shasum is not installed, cannot sign\n");
1083 } else {
1084 fprintf(stderr, "%s and %s are installed and user requested to sign, "
1085 "hence signing\n", using_gpg ? "gpg" : "gpg2",
1086 using_shasum ? "shasum" : "sha256sum");
1088 char* digestfile;
1089 digestfile = br_strcat(destination, ".digest");
1091 char* ascfile;
1092 ascfile = br_strcat(destination, ".digest.asc");
1094 if (g_file_test(digestfile, G_FILE_TEST_IS_REGULAR))
1095 unlink(digestfile);
1097 if (using_shasum)
1098 sprintf(command, "%s -a256 %s", sha256sum_path, destination);
1099 else
1100 sprintf(command, "%s %s", sha256sum_path, destination);
1102 if (verbose)
1103 fprintf(stderr, "%s\n", command);
1105 fp = popen(command, "r");
1107 if (fp == NULL)
1108 die(using_shasum ? "shasum command did not succeed" : "sha256sum command did not succeed");
1110 char output[1024];
1112 fgets(output, sizeof(output) - 1, fp);
1115 if (pclose(fp) != 0)
1116 die(using_shasum ? "shasum command did not succeed" : "sha256sum command did not succeed");
1118 // let's use a new scope to avoid polluting the scope too much with temporary variables
1120 // the output of sha256sum is always <hash> <filename>
1121 // we have to split the string, the first item contains the hash then
1122 gchar** split_result = g_strsplit_set(output, " ", -1);
1124 // extract the first item reference, doesn't have to be free-d
1125 const gchar* digest_only = split_result[0];
1127 // print hash which is later signed for debugging purposes
1128 printf("Signing using SHA256 digest: %s\n", digest_only);
1130 FILE* fpx = fopen(digestfile, "w");
1132 if (fpx != NULL) {
1133 fputs(digest_only, fpx);
1134 fclose(fpx);
1137 g_strfreev(split_result);
1140 fp = NULL;
1142 if (g_file_test(ascfile, G_FILE_TEST_IS_REGULAR))
1143 unlink(ascfile);
1145 char* key_arg = NULL;
1147 if (sign_key && strlen(sign_key) > 0) {
1148 key_arg = calloc(sizeof(char), strlen(sign_key) + strlen("--local-user ''"));
1150 if (key_arg == NULL)
1151 die("malloc() failed");
1153 strcpy(key_arg, "--local-user '");
1154 strcat(key_arg, sign_key);
1155 strcat(key_arg, "'");
1158 sprintf(command,
1159 "%s --batch --detach-sign --armor %s %s %s",
1160 gpg2_path, key_arg ? key_arg : "", sign_args ? sign_args : "", digestfile
1163 free(key_arg);
1164 key_arg = NULL;
1166 if (verbose)
1167 fprintf(stderr, "%s\n", command);
1169 fp = popen(command, "r");
1171 if (pclose(fp) != 0) {
1172 fprintf(stderr, "ERROR: %s command did not succeed, could not sign, continuing\n", using_gpg ? "gpg" : "gpg2");
1173 } else {
1175 fp = NULL;
1177 FILE* destinationfp = fopen(destination, "r+");
1179 // calculate signature
1181 unsigned long sig_offset = 0;
1182 unsigned long sig_length = 0;
1184 bool rv = appimage_get_elf_section_offset_and_length(destination, ".sha256_sig", &sig_offset,
1185 &sig_length);
1187 if (!rv || sig_offset == 0 || sig_length == 0) {
1188 die("Could not find section .sha256_sig in runtime");
1191 if (verbose) {
1192 printf("sig_offset: %lu\n", sig_offset);
1193 printf("sig_length: %lu\n", sig_length);
1196 if (sig_offset == 0) {
1197 die("Could not determine offset for signature");
1200 if (destinationfp == NULL)
1201 die("Not able to open the destination file for writing, aborting");
1203 // if(strlen(updateinformation)>sig_length)
1204 // die("signature does not fit into segment, aborting");
1206 fseek(destinationfp, sig_offset, SEEK_SET);
1208 FILE* ascfilefp = fopen(ascfile, "rb");
1210 if (ascfilefp == NULL) {
1211 die("Not able to open the asc file for reading, aborting");
1214 static const int bufsize = 1024;
1215 char buffer[bufsize];
1217 size_t totalBytesRead = 0;
1219 while (!feof(ascfilefp)) {
1220 size_t bytesRead = fread(buffer, sizeof(char), bufsize, ascfilefp);
1221 totalBytesRead += bytesRead;
1223 if (totalBytesRead > sig_length) {
1224 die("Error: cannot embed key in AppImage: size exceeds reserved ELF section size");
1227 size_t bytesWritten = fwrite(buffer, sizeof(char), bytesRead, destinationfp);
1229 if (bytesRead != bytesWritten) {
1230 char message[128];
1231 sprintf(message, "Bytes read and written differ: %lu != %lu", (long unsigned) bytesRead,
1232 (long unsigned) bytesWritten);
1233 die(message);
1237 fclose(ascfilefp);
1238 if (g_file_test(digestfile, G_FILE_TEST_IS_REGULAR))
1239 unlink(digestfile);
1241 if (sign_key == NULL || strlen(sign_key) > 0) {
1242 // read which key was used to sign from signature
1243 sprintf(command, "%s --batch --list-packets %s", gpg2_path, ascfile);
1245 fp = popen(command, "r");
1247 if (fp == NULL)
1248 die("Failed to call gpg[2] to detect signature's key ID");
1250 while (!feof(fp)) {
1251 size_t bytesRead = fread(buffer, sizeof(char), bufsize, fp);
1253 char* keyid_pos = strstr(buffer, "keyid");
1255 if (keyid_pos == NULL)
1256 continue;
1258 char* keyIDBegin = keyid_pos + strlen("keyid ");
1259 char* endOfKeyID = strstr(keyIDBegin, "\n");
1261 sign_key = calloc(endOfKeyID - keyIDBegin, sizeof(char));
1262 memcpy(sign_key, keyIDBegin, endOfKeyID - keyIDBegin);
1265 // read rest of process input to avoid broken pipe error
1266 while (!feof(fp)) {
1267 fread(buffer, sizeof(char), bufsize, fp);
1270 int retval = pclose(fp);
1271 fp = NULL;
1273 if (retval != 0)
1274 die("Failed to call gpg[2] to detect signature's key ID");
1277 if (g_file_test(ascfile, G_FILE_TEST_IS_REGULAR))
1278 unlink(ascfile);
1281 // export key and write into section
1283 sprintf(command, "%s --batch --export --armor %s", gpg2_path, sign_key);
1285 unsigned long key_offset = 0, key_length = 0;
1287 bool rv = appimage_get_elf_section_offset_and_length(destination, ".sig_key", &key_offset, &key_length);
1289 if (verbose) {
1290 printf("key_offset: %lu\n", key_offset);
1291 printf("key_length: %lu\n", key_length);
1294 if (!rv || key_offset == 0 || key_length == 0) {
1295 die("Could not find section .sig_key in runtime");
1298 fseek(destinationfp, key_offset, SEEK_SET);
1300 fp = popen(command, "r");
1302 if (fp == NULL)
1303 die("Failed to call gpg[2] to export the signature key");
1305 static const int bufsize = 1024;
1306 char buffer[bufsize];
1308 size_t totalBytesRead = 0;
1309 while (!feof(fp)) {
1310 size_t bytesRead = fread(buffer, sizeof(char), bufsize, fp);
1311 totalBytesRead += bytesRead;
1313 if (totalBytesRead > key_length) {
1314 // read rest of process input to avoid broken pipe error
1315 while (!feof(fp)) {
1316 fread(buffer, sizeof(char), bufsize, fp);
1319 pclose(fp);
1320 die("Error: cannot embed key in AppImage: size exceeds reserved ELF section size");
1323 size_t bytesWritten = fwrite(buffer, sizeof(char), bytesRead, destinationfp);
1325 if (bytesRead != bytesWritten) {
1326 char message[128];
1327 sprintf(message, "Error: Bytes read and written differ: %lu != %lu",
1328 (long unsigned) bytesRead, (long unsigned) bytesWritten);
1329 die(message);
1333 int exportexitcode = pclose(fp);
1334 fp = NULL;
1336 if (exportexitcode != 0) {
1337 char message[128];
1338 sprintf(message, "GPG key export failed: exit code %d", exportexitcode);
1339 die(message);
1342 fclose(destinationfp);
1348 /* If updateinformation was provided, then we also generate the zsync file (after having signed the AppImage) */
1349 if (updateinformation != NULL) {
1350 gchar* zsyncmake_path = g_find_program_in_path("zsyncmake");
1351 if (!zsyncmake_path) {
1352 fprintf(stderr, "zsyncmake is not installed/bundled, skipping\n");
1353 } else {
1354 fprintf(stderr, "zsyncmake is available and updateinformation is provided, "
1355 "hence generating zsync file\n");
1357 int pid = fork();
1359 if (verbose)
1360 fprintf(stderr, "%s %s -u %s", zsyncmake_path, destination, basename(destination));
1362 if (pid == -1) {
1363 // fork error
1364 die("fork() failed");
1365 } else if (pid == 0) {
1366 char* zsyncmake_command[] = {zsyncmake_path, destination, "-u", basename(destination), NULL};
1368 // redirect stdout/stderr to /dev/null
1369 int fd = open("/dev/null", O_WRONLY);
1371 dup2(fd, 1);
1372 dup2(fd, 2);
1373 close(fd);
1375 execv(zsyncmake_path, zsyncmake_command);
1377 die("execv() should not return");
1378 } else {
1379 int exitstatus;
1381 if (waitpid(pid, &exitstatus, 0) == -1) {
1382 perror("waitpid() failed");
1383 exit(EXIT_FAILURE);
1386 if (WEXITSTATUS(exitstatus) != 0)
1387 die("zsyncmake command did not succeed");
1392 fprintf(stderr, "Success\n\n");
1393 fprintf(stderr, "Please consider submitting your AppImage to AppImageHub, the crowd-sourced\n");
1394 fprintf(stderr, "central directory of available AppImages, by opening a pull request\n");
1395 fprintf(stderr, "at https://github.com/AppImage/appimage.github.io\n");
1397 return 0;
1398 } else if (g_file_test(remaining_args[0], G_FILE_TEST_IS_REGULAR)) {
1399 /* If the first argument is a regular file, then we assume that we should unpack it */
1400 fprintf(stdout, "%s is a file, assuming it is an AppImage and should be unpacked\n", remaining_args[0]);
1401 die("To be implemented");
1402 return 1;
1403 } else {
1404 fprintf(stderr, "Error: no such file or directory: %s\n", remaining_args[0]);
1405 return 1;
1408 // should never be reached
1409 return 1;