Update appimagetool.c
[appimagekit/gsi.git] / appimagetool.c
blobe5b73d25b4f733d5de07c28cefa272afa72acd85
1 /**************************************************************************
2 *
3 * Copyright (c) 2004-17 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 #include <glib.h>
30 #include <glib/gstdio.h>
31 #include <stdlib.h>
33 #include <stdio.h>
34 #include <argp.h>
36 #include <stdlib.h>
37 #include <fcntl.h>
38 #include "squashfuse.h"
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <sys/wait.h>
44 #include "binreloc.h"
46 #include <libgen.h>
48 #include <unistd.h>
49 #include <string.h>
50 #include <limits.h>
51 #include <stdbool.h>
53 #include "elf.h"
54 #include "getsection.h"
56 #ifdef __linux__
57 #define HAVE_BINARY_RUNTIME
58 extern int _binary_runtime_start;
59 extern int _binary_runtime_end;
60 #endif
62 enum fARCH {
63 fARCH_i386,
64 fARCH_x86_64,
65 fARCH_arm,
66 fARCH_aarch64
69 static gchar const APPIMAGEIGNORE[] = ".appimageignore";
70 static char _exclude_file_desc[256];
72 static gboolean list = FALSE;
73 static gboolean verbose = FALSE;
74 static gboolean version = FALSE;
75 static gboolean sign = FALSE;
76 static gboolean no_appstream = FALSE;
77 gchar **remaining_args = NULL;
78 gchar *updateinformation = NULL;
79 gchar *bintray_user = NULL;
80 gchar *bintray_repo = NULL;
81 gchar *sqfs_comp = "gzip";
82 gchar *exclude_file = NULL;
83 gchar *runtime_file = NULL;
84 gchar *sign_args = NULL;
86 // #####################################################################
88 static void die(const char *msg) {
89 fprintf(stderr, "%s\n", msg);
90 exit(1);
93 /* Function that prints the contents of a squashfs file
94 * using libsquashfuse (#include "squashfuse.h") */
95 int sfs_ls(char* image) {
96 sqfs_err err = SQFS_OK;
97 sqfs_traverse trv;
98 sqfs fs;
100 unsigned long fs_offset = get_elf_size(image);
102 if ((err = sqfs_open_image(&fs, image, fs_offset)))
103 die("sqfs_open_image error");
105 if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs))))
106 die("sqfs_traverse_open error");
107 while (sqfs_traverse_next(&trv, &err)) {
108 if (!trv.dir_end) {
109 printf("%s\n", trv.path);
112 if (err)
113 die("sqfs_traverse_next error");
114 sqfs_traverse_close(&trv);
116 sqfs_fd_close(fs.fd);
117 return 0;
120 /* Generate a squashfs filesystem using mksquashfs on the $PATH
121 * execlp(), execvp(), and execvpe() search on the $PATH */
122 int sfs_mksquashfs(char *source, char *destination, int offset) {
123 pid_t pid = fork();
125 if (pid == -1) {
126 // error, failed to fork()
127 return(-1);
128 } else if (pid > 0) {
129 int status;
130 waitpid(pid, &status, 0);
131 } else {
132 // we are the child
133 gchar *offset_string;
134 offset_string = g_strdup_printf("%i", offset);
136 char* args[32];
137 bool use_xz = strcmp(sqfs_comp, "xz") >= 0;
139 int i = 0;
140 args[i++] = "mksquashfs";
141 args[i++] = source;
142 args[i++] = destination;
143 args[i++] = "-offset";
144 args[i++] = offset_string;
145 args[i++] = "-comp";
147 if(use_xz)
148 args[i++] = "xz";
149 else
150 args[i++] = sqfs_comp;
152 args[i++] = "-root-owned";
153 args[i++] = "-noappend";
155 if(use_xz) {
156 // https://jonathancarter.org/2015/04/06/squashfs-performance-testing/ says:
157 // improved performance by using a 16384 block size with a sacrifice of around 3% more squashfs image space
158 args[i++] = "-Xdict-size";
159 args[i++] = "100%";
160 args[i++] = "-b";
161 args[i++] = "16384";
164 // check if ignore file exists and use it if possible
165 if(access(APPIMAGEIGNORE, F_OK) >= 0) {
166 printf("Including %s", APPIMAGEIGNORE);
167 args[i++] = "-wildcards";
168 args[i++] = "-ef";
170 // avoid warning: assignment discards ‘const’ qualifier
171 char buf[256];
172 strcpy(buf, APPIMAGEIGNORE);
173 args[i++] = buf;
176 // if an exclude file has been passed on the command line, should be used, too
177 if(exclude_file != 0 && strlen(exclude_file) > 0) {
178 if(access(exclude_file, F_OK) < 0) {
179 printf("WARNING: exclude file %s not found!", exclude_file);
180 return -1;
183 args[i++] = "-wildcards";
184 args[i++] = "-ef";
185 args[i++] = exclude_file;
188 args[i++] = 0;
190 execvp("mksquashfs", args);
192 perror("execlp"); // exec*() returns only on error
193 return -1; // exec never returns
195 return 0;
198 /* Validate desktop file using desktop-file-validate on the $PATH
199 * execlp(), execvp(), and execvpe() search on the $PATH */
200 int validate_desktop_file(char *file) {
201 int number, statval;
202 int child_pid;
203 child_pid = fork();
204 if(child_pid == -1)
206 printf("could not fork! \n");
207 return 1;
209 else if(child_pid == 0)
211 execlp("desktop-file-validate", "desktop-file-validate", file, NULL);
213 else
215 waitpid(child_pid, &statval, WUNTRACED | WCONTINUED);
216 if(WIFEXITED(statval)){
217 return(WEXITSTATUS(statval));
220 return -1;
223 /* Generate a squashfs filesystem
224 * The following would work if we link to mksquashfs.o after we renamed
225 * main() to mksquashfs_main() in mksquashfs.c but we don't want to actually do
226 * this because squashfs-tools is not under a permissive license
227 * i *nt sfs_mksquashfs(char *source, char *destination) {
228 * char *child_argv[5];
229 * child_argv[0] = NULL;
230 * child_argv[1] = source;
231 * child_argv[2] = destination;
232 * child_argv[3] = "-root-owned";
233 * child_argv[4] = "-noappend";
234 * mksquashfs_main(5, child_argv);
238 /* in-place modification of the string, and assuming the buffer pointed to by
239 * line is large enough to hold the resulting string*/
240 static void replacestr(char *line, const char *search, const char *replace)
242 char *sp = NULL;
244 if ((sp = strstr(line, search)) == NULL) {
245 return;
247 int search_len = strlen(search);
248 int replace_len = strlen(replace);
249 int tail_len = strlen(sp+search_len);
251 memmove(sp+replace_len,sp+search_len,tail_len+1);
252 memcpy(sp, replace, replace_len);
254 /* Do it recursively again until no more work to do */
256 if ((sp = strstr(line, search))) {
257 replacestr(line, search, replace);
261 int count_archs(bool* archs) {
262 int countArchs = 0;
263 int i;
264 for (i = 0; i < 4; i++) {
265 countArchs += archs[i];
267 return countArchs;
270 gchar* getArchName(bool* archs) {
271 if (archs[fARCH_i386])
272 return "i386";
273 else if (archs[fARCH_x86_64])
274 return "x86_64";
275 else if (archs[fARCH_arm])
276 return "ARM";
277 else if (archs[fARCH_aarch64])
278 return "ARM_aarch64";
279 else
280 return "all";
283 void extract_arch_from_text(gchar *archname, const gchar* sourcename, bool* archs) {
284 if (archname) {
285 archname = g_strstrip(archname);
286 if (archname) {
287 replacestr(archname, "-", "_");
288 replacestr(archname, " ", "_");
289 if (g_ascii_strncasecmp("i386", archname, 20) == 0
290 || g_ascii_strncasecmp("i486", archname, 20) == 0
291 || g_ascii_strncasecmp("i586", archname, 20) == 0
292 || g_ascii_strncasecmp("i686", archname, 20) == 0
293 || g_ascii_strncasecmp("intel_80386", archname, 20) == 0
294 || g_ascii_strncasecmp("intel_80486", archname, 20) == 0
295 || g_ascii_strncasecmp("intel_80586", archname, 20) == 0
296 || g_ascii_strncasecmp("intel_80686", archname, 20) == 0
298 archs[fARCH_i386] = 1;
299 if (verbose)
300 fprintf(stderr, "%s used for determining architecture i386\n", sourcename);
301 } else if (g_ascii_strncasecmp("x86_64", archname, 20) == 0) {
302 archs[fARCH_x86_64] = 1;
303 if (verbose)
304 fprintf(stderr, "%s used for determining architecture x86_64\n", sourcename);
305 } else if (g_ascii_strncasecmp("arm", archname, 20) == 0) {
306 archs[fARCH_arm] = 1;
307 if (verbose)
308 fprintf(stderr, "%s used for determining architecture ARM\n", sourcename);
309 } else if (g_ascii_strncasecmp("arm_aarch64", archname, 20) == 0) {
310 archs[fARCH_aarch64] = 1;
311 if (verbose)
312 fprintf(stderr, "%s used for determining architecture ARM aarch64\n", sourcename);
318 void guess_arch_of_file(const gchar *archfile, bool* archs) {
319 char line[PATH_MAX];
320 char command[PATH_MAX];
321 sprintf(command, "/usr/bin/file -L -N -b %s", archfile);
322 FILE* fp = popen(command, "r");
323 if (fp == NULL)
324 die("Failed to run file command");
325 fgets(line, sizeof (line) - 1, fp);
326 pclose(fp);
327 extract_arch_from_text(g_strsplit_set(line, ",", -1)[1], archfile, archs);
330 void find_arch(const gchar *real_path, const gchar *pattern, bool* archs) {
331 GDir *dir;
332 gchar *full_name;
333 dir = g_dir_open(real_path, 0, NULL);
334 if (dir != NULL) {
335 const gchar *entry;
336 while ((entry = g_dir_read_name(dir)) != NULL) {
337 full_name = g_build_filename(real_path, entry, NULL);
338 if (g_file_test(full_name, G_FILE_TEST_IS_DIR)) {
339 find_arch(full_name, pattern, archs);
340 } else if (g_file_test(full_name, G_FILE_TEST_IS_EXECUTABLE) || g_pattern_match_simple(pattern, entry) ) {
341 guess_arch_of_file(full_name, archs);
344 g_dir_close(dir);
346 else {
347 g_warning("%s: %s", real_path, g_strerror(errno));
351 gchar* find_first_matching_file_nonrecursive(const gchar *real_path, const gchar *pattern) {
352 GDir *dir;
353 gchar *full_name;
354 dir = g_dir_open(real_path, 0, NULL);
355 if (dir != NULL) {
356 const gchar *entry;
357 while ((entry = g_dir_read_name(dir)) != NULL) {
358 full_name = g_build_filename(real_path, entry, NULL);
359 if (g_file_test(full_name, G_FILE_TEST_IS_REGULAR)) {
360 if(g_pattern_match_simple(pattern, entry))
361 return(full_name);
364 g_dir_close(dir);
366 else {
367 g_warning("%s: %s", real_path, g_strerror(errno));
369 return NULL;
372 gchar* get_desktop_entry(GKeyFile *kf, char *key) {
373 gchar *value = g_key_file_get_string (kf, "Desktop Entry", key, NULL);
374 if (! value){
375 fprintf(stderr, "%s entry not found in desktop file\n", key);
377 return value;
380 bool readFile(char* filename, int* size, char** buffer) {
381 FILE* f = fopen(filename, "rb");
382 if (f==NULL) {
383 *buffer = 0;
384 *size = 0;
385 return false;
388 fseek(f, 0, SEEK_END);
389 long fsize = ftell(f);
390 fseek(f, 0, SEEK_SET);
392 char *indata = malloc(fsize);
393 fread(indata, fsize, 1, f);
394 fclose(f);
395 *size = (int)fsize;
396 *buffer = indata;
397 return TRUE;
400 // #####################################################################
402 static GOptionEntry entries[] =
404 { "list", 'l', 0, G_OPTION_ARG_NONE, &list, "List files in SOURCE AppImage", NULL },
405 { "updateinformation", 'u', 0, G_OPTION_ARG_STRING, &updateinformation, "Embed update information STRING; if zsyncmake is installed, generate zsync file", NULL },
406 { "bintray-user", 0, 0, G_OPTION_ARG_STRING, &bintray_user, "Bintray user name", NULL },
407 { "bintray-repo", 0, 0, G_OPTION_ARG_STRING, &bintray_repo, "Bintray repository", NULL },
408 { "version", 0, 0, G_OPTION_ARG_NONE, &version, "Show version number", NULL },
409 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Produce verbose output", NULL },
410 { "sign", 's', 0, G_OPTION_ARG_NONE, &sign, "Sign with gpg[2]", NULL },
411 { "comp", 0, 0, G_OPTION_ARG_STRING, &sqfs_comp, "Squashfs compression", NULL },
412 { "no-appstream", 'n', 0, G_OPTION_ARG_NONE, &no_appstream, "Do not check AppStream metadata", NULL },
413 { "exclude-file", 0, 0, G_OPTION_ARG_STRING, &exclude_file, _exclude_file_desc, NULL },
414 { "runtime-file", 0, 0, G_OPTION_ARG_STRING, &runtime_file, "Runtime file to use", NULL },
415 { "sign-args", 0, 0, G_OPTION_ARG_STRING, &sign_args, "Extra arguments to use when signing with gpg[2]", NULL},
416 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &remaining_args, NULL, NULL },
417 { 0,0,0,0,0,0,0 }
421 main (int argc, char *argv[])
423 /* Parse VERSION environment variable.
424 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
425 char* version_env;
426 version_env = getenv("VERSION");
428 /* Parse Travis CI environment variables.
429 * https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
430 * TRAVIS_COMMIT: The commit that the current build is testing.
431 * TRAVIS_REPO_SLUG: The slug (in form: owner_name/repo_name) of the repository currently being built.
432 * TRAVIS_TAG: If the current build is for a git tag, this variable is set to the tag’s name.
433 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
434 char* travis_commit;
435 travis_commit = getenv("TRAVIS_COMMIT");
436 char* travis_repo_slug;
437 travis_repo_slug = getenv("TRAVIS_REPO_SLUG");
438 char* travis_tag;
439 travis_tag = getenv("TRAVIS_TAG");
441 /* Parse OWD environment variable.
442 * If it is available then cd there. It is the original CWD prior to running AppRun */
443 char* owd_env = NULL;
444 owd_env = getenv("OWD");
445 if(NULL!=owd_env){
446 int ret;
447 ret = chdir(owd_env);
448 if (ret != 0){
449 fprintf(stderr, "Could not cd into %s\n", owd_env);
450 exit(1);
455 GError *error = NULL;
456 GOptionContext *context;
457 char command[PATH_MAX];
459 // initialize help text of argument
460 sprintf(_exclude_file_desc, "Uses given file as exclude file for mksquashfs, in addition to %s.", APPIMAGEIGNORE);
462 context = g_option_context_new ("SOURCE [DESTINATION] - Generate, extract, and inspect AppImages");
463 g_option_context_add_main_entries (context, entries, NULL);
464 // g_option_context_add_group (context, gtk_get_option_group (TRUE));
465 if (!g_option_context_parse (context, &argc, &argv, &error))
467 fprintf(stderr, "Option parsing failed: %s\n", error->message);
468 exit(1);
471 if(version){
472 fprintf(stderr,"Version: %s\n", VERSION_NUMBER);
473 exit(0);
476 if(!((0 == strcmp(sqfs_comp, "gzip")) || (0 ==strcmp(sqfs_comp, "xz"))))
477 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.");
478 /* Check for dependencies here. Better fail early if they are not present. */
479 if(! g_find_program_in_path ("mksquashfs"))
480 die("mksquashfs is missing but required, please install it");
481 if(! g_find_program_in_path ("desktop-file-validate"))
482 g_print("WARNING: desktop-file-validate is missing, please install it so that desktop files can be checked for potential errors\n");
483 if(! g_find_program_in_path ("zsyncmake"))
484 g_print("WARNING: zsyncmake is missing, please install it if you want to use binary delta updates\n");
485 if(! no_appstream)
486 if(! g_find_program_in_path ("appstreamcli"))
487 g_print("WARNING: appstreamcli is missing, please install it if you want to use AppStream metadata\n");
488 if(! g_find_program_in_path ("gpg2") && ! g_find_program_in_path ("gpg"))
489 g_print("WARNING: gpg2 or gpg is missing, please install it if you want to create digital signatures\n");
490 if(! g_find_program_in_path ("sha256sum") && ! g_find_program_in_path ("shasum"))
491 g_print("WARNING: sha256sum or shasum is missing, please install it if you want to create digital signatures\n");
493 if(!&remaining_args[0])
494 die("SOURCE is missing");
496 /* If in list mode */
497 if (list){
498 sfs_ls(remaining_args[0]);
499 exit(0);
502 /* If the first argument is a directory, then we assume that we should package it */
503 if (g_file_test (remaining_args[0], G_FILE_TEST_IS_DIR)){
504 char *destination;
505 char source[PATH_MAX];
506 realpath(remaining_args[0], source);
508 /* Check if *.desktop file is present in source AppDir */
509 gchar *desktop_file = find_first_matching_file_nonrecursive(source, "*.desktop");
510 if(desktop_file == NULL){
511 die("Desktop file not found, aborting");
513 if(verbose)
514 fprintf (stdout, "Desktop file: %s\n", desktop_file);
516 if(g_find_program_in_path ("desktop-file-validate")) {
517 if(validate_desktop_file(desktop_file) != 0){
518 fprintf(stderr, "ERROR: Desktop file contains errors. Please fix them. Please see\n");
519 fprintf(stderr, " https://standards.freedesktop.org/desktop-entry-spec/latest/\n");
520 die(" for more information.");
524 /* Read information from .desktop file */
525 GKeyFile *kf = g_key_file_new ();
526 if (!g_key_file_load_from_file (kf, desktop_file, 0, NULL))
527 die(".desktop file cannot be parsed");
529 if(verbose){
530 fprintf (stderr,"Name: %s\n", get_desktop_entry(kf, "Name"));
531 fprintf (stderr,"Icon: %s\n", get_desktop_entry(kf, "Icon"));
532 fprintf (stderr,"Exec: %s\n", get_desktop_entry(kf, "Exec"));
533 fprintf (stderr,"Comment: %s\n", get_desktop_entry(kf, "Comment"));
534 fprintf (stderr,"Type: %s\n", get_desktop_entry(kf, "Type"));
535 fprintf (stderr,"Categories: %s\n", get_desktop_entry(kf, "Categories"));
538 /* Determine the architecture */
539 bool archs[4] = {0, 0, 0, 0};
540 extract_arch_from_text(getenv("ARCH"), "Environmental variable ARCH", archs);
541 if (count_archs(archs) != 1) {
542 /* If no $ARCH variable is set check a file */
543 /* We use the next best .so that we can find to determine the architecture */
544 find_arch(source, "*.so.*", archs);
545 int countArchs = count_archs(archs);
546 if (countArchs != 1) {
547 if (countArchs < 1)
548 fprintf(stderr, "Unable to guess the architecture of the AppDir source directory \"%s\"\n", remaining_args[0]);
549 else
550 fprintf(stderr, "More than one architectures were found of the AppDir source directory \"%s\"\n", remaining_args[0]);
551 fprintf(stderr, "A valid architecture with the ARCH environmental variable should be provided\ne.g. ARCH=x86_64 %s", argv[0]),
552 die(" ...");
555 gchar* arch = getArchName(archs);
556 fprintf(stderr, "Using architecture %s\n", arch);
558 FILE *fp;
559 char app_name_for_filename[PATH_MAX];
560 sprintf(app_name_for_filename, "%s", get_desktop_entry(kf, "Name"));
561 replacestr(app_name_for_filename, " ", "_");
563 if(verbose)
564 fprintf (stderr,"App name for filename: %s\n", app_name_for_filename);
566 if (remaining_args[1]) {
567 destination = remaining_args[1];
568 } else {
569 /* No destination has been specified, to let's construct one
570 * TODO: Find out the architecture and use a $VERSION that might be around in the env */
571 char dest_path[PATH_MAX];
572 sprintf (dest_path, "%s-%s.AppImage", app_name_for_filename, arch);
574 if(verbose)
575 fprintf (stderr,"dest_path: %s\n", dest_path);
577 if (version_env!=NULL)
578 sprintf (dest_path, "%s-%s-%s.AppImage", app_name_for_filename, version_env, arch);
580 destination = dest_path;
581 replacestr(destination, " ", "_");
583 fprintf (stdout, "%s should be packaged as %s\n", source, destination);
584 /* Check if the Icon file is how it is expected */
585 gchar* icon_name = get_desktop_entry(kf, "Icon");
586 gchar* icon_file_path = NULL;
587 gchar* icon_file_png;
588 gchar* icon_file_svg;
589 gchar* icon_file_svgz;
590 gchar* icon_file_xpm;
591 icon_file_png = g_strdup_printf("%s/%s.png", source, icon_name);
592 icon_file_svg = g_strdup_printf("%s/%s.svg", source, icon_name);
593 icon_file_svgz = g_strdup_printf("%s/%s.svgz", source, icon_name);
594 icon_file_xpm = g_strdup_printf("%s/%s.xpm", source, icon_name);
595 if (g_file_test(icon_file_png, G_FILE_TEST_IS_REGULAR)) {
596 icon_file_path = icon_file_png;
597 } else if(g_file_test(icon_file_svg, G_FILE_TEST_IS_REGULAR)) {
598 icon_file_path = icon_file_svg;
599 } else if(g_file_test(icon_file_svgz, G_FILE_TEST_IS_REGULAR)) {
600 icon_file_path = icon_file_svgz;
601 } else if(g_file_test(icon_file_xpm, G_FILE_TEST_IS_REGULAR)) {
602 icon_file_path = icon_file_xpm;
603 } else {
604 fprintf (stderr, "%s{.png,.svg,.svgz,.xpm} not present but defined in desktop file\n", icon_name);
605 exit(1);
608 /* Check if .DirIcon is present in source AppDir */
609 gchar *diricon_path = g_build_filename(source, ".DirIcon", NULL);
611 if (! g_file_test(diricon_path, G_FILE_TEST_EXISTS)){
612 fprintf (stderr, "Deleting pre-existing .DirIcon\n");
613 g_unlink(diricon_path);
615 if (! g_file_test(diricon_path, G_FILE_TEST_IS_REGULAR)){
616 fprintf (stderr, "Creating .DirIcon symlink based on information from desktop file\n");
617 int res = symlink(basename(icon_file_path), diricon_path);
618 if(res)
619 die("Could not symlink .DirIcon");
622 /* Check if AppStream upstream metadata is present in source AppDir */
623 if(! no_appstream){
624 char application_id[PATH_MAX];
625 sprintf (application_id, "%s", basename(desktop_file));
626 replacestr(application_id, ".desktop", ".appdata.xml");
627 gchar *appdata_path = g_build_filename(source, "/usr/share/metainfo/", application_id, NULL);
628 if (! g_file_test(appdata_path, G_FILE_TEST_IS_REGULAR)){
629 fprintf (stderr, "WARNING: AppStream upstream metadata is missing, please consider creating it\n");
630 fprintf (stderr, " in usr/share/metainfo/%s\n", application_id);
631 fprintf (stderr, " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps\n");
632 fprintf (stderr, " for more information.\n");
633 /* As a courtesy, generate one to be filled by the user */
634 if(g_find_program_in_path ("appstream-util")) {
635 gchar *appdata_dir = g_build_filename(source, "/usr/share/metainfo/", NULL);
636 g_mkdir_with_parents(appdata_dir, 0755);
637 sprintf (command, "%s appdata-from-desktop %s %s", g_find_program_in_path ("appstream-util"), desktop_file, appdata_path);
638 int ret = system(command);
639 if (ret != 0)
640 die("Failed to generate AppStream template");
641 fprintf (stderr, "AppStream template has been generated in in %s, please edit it\n", appdata_path);
642 exit(1);
644 } else {
645 fprintf (stderr, "AppStream upstream metadata found in usr/share/metainfo/%s\n", application_id);
646 /* Use ximion's appstreamcli to make sure that desktop file and appdata match together */
647 if(g_find_program_in_path ("appstreamcli")) {
648 sprintf (command, "%s validate-tree %s", g_find_program_in_path ("appstreamcli"), source);
649 int ret = system(command);
650 if (ret != 0)
651 die("Failed to validate AppStream information with appstreamcli");
653 /* It seems that hughsie's appstream-util does additional validations */
654 if(g_find_program_in_path ("appstream-util")) {
655 sprintf (command, "%s validate-relax %s", g_find_program_in_path ("appstream-util"), appdata_path);
656 int ret = system(command);
657 if (ret != 0)
658 die("Failed to validate AppStream information with appstream-util");
663 /* Upstream mksquashfs can currently not start writing at an offset,
664 * so we need a patched one. https://github.com/plougher/squashfs-tools/pull/13
665 * should hopefully change that. */
667 fprintf (stderr, "Generating squashfs...\n");
668 int size = 0;
669 char* data = NULL;
670 bool using_external_data = false;
671 if (runtime_file != NULL) {
672 if (!readFile(runtime_file, &size, &data))
673 die("Unable to load provided runtime file");
674 using_external_data = true;
675 } else {
676 #ifdef HAVE_BINARY_RUNTIME
677 /* runtime is embedded into this executable
678 * http://stupefydeveloper.blogspot.de/2008/08/cc-embed-binary-data-into-elf.html */
679 size = (int)((void *)&_binary_runtime_end - (void *)&_binary_runtime_start);
680 data = (char *)&_binary_runtime_start;
681 #else
682 die("No runtime file was provided");
683 #endif
685 if (verbose)
686 printf("Size of the embedded runtime: %d bytes\n", size);
688 int result = sfs_mksquashfs(source, destination, size);
689 if(result != 0)
690 die("sfs_mksquashfs error");
692 fprintf (stderr, "Embedding ELF...\n");
693 FILE *fpdst = fopen(destination, "rb+");
694 if (fpdst == NULL) {
695 die("Not able to open the AppImage for writing, aborting");
698 fseek(fpdst, 0, SEEK_SET);
699 fwrite(data, size, 1, fpdst);
700 fclose(fpdst);
701 if (using_external_data)
702 free(data);
704 fprintf (stderr, "Marking the AppImage as executable...\n");
705 if (chmod (destination, 0755) < 0) {
706 printf("Could not set executable bit, aborting\n");
707 exit(1);
710 if(bintray_user != NULL){
711 if(bintray_repo != NULL){
712 char buf[1024];
713 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);
714 updateinformation = buf;
715 printf("%s\n", updateinformation);
719 /* If the user has not provided update information but we know this is a Travis CI build,
720 * then fill in update information based on TRAVIS_REPO_SLUG */
721 if(updateinformation == NULL){
722 if(travis_repo_slug != NULL){
723 char buf[1024];
724 gchar **parts = g_strsplit (travis_repo_slug, ",", 2);
725 /* https://github.com/AppImage/AppImageSpec/blob/master/draft.md#github-releases
726 * gh-releases-zsync|probono|AppImages|latest|Subsurface-*-x86_64.AppImage.zsync */
727 sprintf(buf, "gh-releases-zsync|%s|%s|latest|%s-_*-%s.AppImage.zsync", parts[0], parts[1], app_name_for_filename, arch);
728 updateinformation = buf;
729 printf("As a courtesy, automatically embedding update information based on $TRAVIS_REPO_SLUG=%s\n", travis_repo_slug);
730 printf("%s\n", updateinformation);
734 /* If updateinformation was provided, then we check and embed it */
735 if(updateinformation != NULL){
736 if(!g_str_has_prefix(updateinformation,"zsync|"))
737 if(!g_str_has_prefix(updateinformation,"bintray-zsync|"))
738 if(!g_str_has_prefix(updateinformation,"gh-releases-zsync|"))
739 die("The provided updateinformation is not in a recognized format");
741 gchar **ui_type = g_strsplit_set(updateinformation, "|", -1);
743 if(verbose)
744 printf("updateinformation type: %s\n", ui_type[0]);
745 /* TODO: Further checking of the updateinformation */
748 unsigned long ui_offset = 0;
749 unsigned long ui_length = 0;
750 get_elf_section_offset_and_lenghth(destination, ".upd_info", &ui_offset, &ui_length);
751 if(verbose) {
752 printf("ui_offset: %lu\n", ui_offset);
753 printf("ui_length: %lu\n", ui_length);
755 if(ui_offset == 0) {
756 die("Could not determine offset for updateinformation");
757 } else {
758 if(strlen(updateinformation)>ui_length)
759 die("updateinformation does not fit into segment, aborting");
760 FILE *fpdst2 = fopen(destination, "r+");
761 if (fpdst2 == NULL)
762 die("Not able to open the destination file for writing, aborting");
763 fseek(fpdst2, ui_offset, SEEK_SET);
764 // fseek(fpdst2, ui_offset, SEEK_SET);
765 // fwrite(0x00, 1, 1024, fpdst); // FIXME: Segfaults; why?
766 // fseek(fpdst, ui_offset, SEEK_SET);
767 fwrite(updateinformation, strlen(updateinformation), 1, fpdst2);
768 fclose(fpdst2);
772 if(sign){
773 bool using_gpg = FALSE;
774 bool using_shasum = FALSE;
775 /* The user has indicated that he wants to sign */
776 gchar *gpg2_path = g_find_program_in_path ("gpg2");
777 if (!gpg2_path) {
778 gpg2_path = g_find_program_in_path ("gpg");
779 using_gpg = TRUE;
781 gchar *sha256sum_path = g_find_program_in_path ("sha256sum");
782 if (!sha256sum_path) {
783 sha256sum_path = g_find_program_in_path ("shasum");
784 using_shasum = 1;
786 if(!gpg2_path){
787 fprintf (stderr, "gpg2 or gpg is not installed, cannot sign\n");
789 else if(!sha256sum_path) {
790 fprintf(stderr, "sha256sum or shasum is not installed, cannot sign\n");
791 } else {
792 fprintf(stderr, "%s and %s are installed and user requested to sign, "
793 "hence signing\n", using_gpg ? "gpg" : "gpg2",
794 using_shasum ? "shasum" : "sha256sum");
795 char *digestfile;
796 digestfile = br_strcat(destination, ".digest");
797 char *ascfile;
798 ascfile = br_strcat(destination, ".digest.asc");
799 if (g_file_test (digestfile, G_FILE_TEST_IS_REGULAR))
800 unlink(digestfile);
801 if (using_shasum)
802 sprintf (command, "%s -a256 %s", sha256sum_path, destination);
803 else
804 sprintf (command, "%s %s", sha256sum_path, destination);
805 if(verbose)
806 fprintf (stderr, "%s\n", command);
807 fp = popen(command, "r");
808 if (fp == NULL)
809 die(using_shasum ? "shasum command did not succeed" : "sha256sum command did not succeed");
810 char output[1024];
811 fgets(output, sizeof (output) - 1, fp);
812 if (verbose)
813 printf("%s: %s\n", using_shasum ? "shasum" : "sha256sum",
814 g_strsplit_set(output, " ", -1)[0]);
815 FILE *fpx = fopen(digestfile, "w");
816 if (fpx != NULL)
818 fputs(g_strsplit_set(output, " ", -1)[0], fpx);
819 fclose(fpx);
821 int shasum_exit_status = pclose(fp);
822 if(WEXITSTATUS(shasum_exit_status) != 0)
823 die(using_shasum ? "shasum command did not succeed" : "sha256sum command did not succeed");
824 if (g_file_test (ascfile, G_FILE_TEST_IS_REGULAR))
825 unlink(ascfile);
826 sprintf (command, "%s --detach-sign --armor %s %s", gpg2_path, sign_args ? sign_args : "", digestfile);
827 if(verbose)
828 fprintf (stderr, "%s\n", command);
829 fp = popen(command, "r");
830 int gpg_exit_status = pclose(fp);
831 if(WEXITSTATUS(gpg_exit_status) != 0) {
832 fprintf (stderr, "ERROR: %s command did not succeed, could not sign, continuing\n", using_gpg ? "gpg" : "gpg2");
833 } else {
834 unsigned long sig_offset = 0;
835 unsigned long sig_length = 0;
836 get_elf_section_offset_and_lenghth(destination, ".sha256_sig", &sig_offset, &sig_length);
837 if(verbose) {
838 printf("sig_offset: %lu\n", sig_offset);
839 printf("sig_length: %lu\n", sig_length);
841 if(sig_offset == 0) {
842 die("Could not determine offset for signature");
843 } else {
844 FILE *fpdst3 = fopen(destination, "r+");
845 if (fpdst3 == NULL)
846 die("Not able to open the destination file for writing, aborting");
847 // if(strlen(updateinformation)>sig_length)
848 // die("signature does not fit into segment, aborting");
849 fseek(fpdst3, sig_offset, SEEK_SET);
850 FILE *fpsrc2 = fopen(ascfile, "rb");
851 if (fpsrc2 == NULL) {
852 die("Not able to open the asc file for reading, aborting");
854 char byte;
855 while (!feof(fpsrc2))
857 fread(&byte, sizeof(char), 1, fpsrc2);
858 fwrite(&byte, sizeof(char), 1, fpdst3);
860 fclose(fpsrc2);
861 fclose(fpdst3);
863 if (g_file_test (ascfile, G_FILE_TEST_IS_REGULAR))
864 unlink(ascfile);
865 if (g_file_test (digestfile, G_FILE_TEST_IS_REGULAR))
866 unlink(digestfile);
871 /* If updateinformation was provided, then we also generate the zsync file (after having signed the AppImage) */
872 if(updateinformation != NULL){
873 gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake");
874 if(!zsyncmake_path){
875 fprintf (stderr, "zsyncmake is not installed/bundled, skipping\n");
876 } else {
877 fprintf (stderr, "zsyncmake is bundled and updateinformation is provided, "
878 "hence generating zsync file\n");
879 sprintf (command, "%s %s -u %s", zsyncmake_path, destination, basename(destination));
880 if(verbose)
881 fprintf (stderr, "%s\n", command);
882 fp = popen(command, "r");
883 if (fp == NULL)
884 die("Failed to run zsyncmake command");
885 int exitstatus = pclose(fp);
886 if (WEXITSTATUS(exitstatus) != 0)
887 die("zsyncmake command did not succeed");
891 fprintf (stderr, "Success\n\n");
892 fprintf (stderr, "Please consider submitting your AppImage to AppImageHub, the crowd-sourced\n");
893 fprintf (stderr, "central directory of available AppImages, by opening a pull request\n");
894 fprintf (stderr, "at https://github.com/AppImage/appimage.github.io\n");
897 /* If the first argument is a regular file, then we assume that we should unpack it */
898 if (g_file_test (remaining_args[0], G_FILE_TEST_IS_REGULAR)){
899 fprintf (stdout, "%s is a file, assuming it is an AppImage and should be unpacked\n", remaining_args[0]);
900 die("To be implemented");
903 return 0;