Clarify special directories
[appimagekit/gsi.git] / appimagetool.c
blobd05cbc424eef205eeef12d1b46073012dded23a5
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 static gboolean guessupdateinformation = FALSE;
80 gchar *bintray_user = NULL;
81 gchar *bintray_repo = NULL;
82 gchar *sqfs_comp = "gzip";
83 gchar *exclude_file = NULL;
84 gchar *runtime_file = NULL;
85 gchar *sign_args = NULL;
87 // #####################################################################
89 static void die(const char *msg) {
90 fprintf(stderr, "%s\n", msg);
91 exit(1);
94 /* Function that prints the contents of a squashfs file
95 * using libsquashfuse (#include "squashfuse.h") */
96 int sfs_ls(char* image) {
97 sqfs_err err = SQFS_OK;
98 sqfs_traverse trv;
99 sqfs fs;
101 unsigned long fs_offset = get_elf_size(image);
103 if ((err = sqfs_open_image(&fs, image, fs_offset)))
104 die("sqfs_open_image error");
106 if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs))))
107 die("sqfs_traverse_open error");
108 while (sqfs_traverse_next(&trv, &err)) {
109 if (!trv.dir_end) {
110 printf("%s\n", trv.path);
113 if (err)
114 die("sqfs_traverse_next error");
115 sqfs_traverse_close(&trv);
117 sqfs_fd_close(fs.fd);
118 return 0;
121 /* Generate a squashfs filesystem using mksquashfs on the $PATH
122 * execlp(), execvp(), and execvpe() search on the $PATH */
123 int sfs_mksquashfs(char *source, char *destination, int offset) {
124 pid_t pid = fork();
126 if (pid == -1) {
127 // error, failed to fork()
128 return(-1);
129 } else if (pid > 0) {
130 int status;
131 waitpid(pid, &status, 0);
132 } else {
133 // we are the child
134 gchar *offset_string;
135 offset_string = g_strdup_printf("%i", offset);
137 char* args[32];
138 bool use_xz = strcmp(sqfs_comp, "xz") >= 0;
140 int i = 0;
141 args[i++] = "mksquashfs";
142 args[i++] = source;
143 args[i++] = destination;
144 args[i++] = "-offset";
145 args[i++] = offset_string;
146 args[i++] = "-comp";
148 if(use_xz)
149 args[i++] = "xz";
150 else
151 args[i++] = sqfs_comp;
153 args[i++] = "-root-owned";
154 args[i++] = "-noappend";
156 if(use_xz) {
157 // https://jonathancarter.org/2015/04/06/squashfs-performance-testing/ says:
158 // improved performance by using a 16384 block size with a sacrifice of around 3% more squashfs image space
159 args[i++] = "-Xdict-size";
160 args[i++] = "100%";
161 args[i++] = "-b";
162 args[i++] = "16384";
165 // check if ignore file exists and use it if possible
166 if(access(APPIMAGEIGNORE, F_OK) >= 0) {
167 printf("Including %s", APPIMAGEIGNORE);
168 args[i++] = "-wildcards";
169 args[i++] = "-ef";
171 // avoid warning: assignment discards ‘const’ qualifier
172 char buf[256];
173 strcpy(buf, APPIMAGEIGNORE);
174 args[i++] = buf;
177 // if an exclude file has been passed on the command line, should be used, too
178 if(exclude_file != 0 && strlen(exclude_file) > 0) {
179 if(access(exclude_file, F_OK) < 0) {
180 printf("WARNING: exclude file %s not found!", exclude_file);
181 return -1;
184 args[i++] = "-wildcards";
185 args[i++] = "-ef";
186 args[i++] = exclude_file;
189 args[i++] = 0;
191 execvp("mksquashfs", args);
193 perror("execlp"); // exec*() returns only on error
194 return -1; // exec never returns
196 return 0;
199 /* Validate desktop file using desktop-file-validate on the $PATH
200 * execlp(), execvp(), and execvpe() search on the $PATH */
201 int validate_desktop_file(char *file) {
202 int statval;
203 int child_pid;
204 child_pid = fork();
205 if(child_pid == -1)
207 printf("could not fork! \n");
208 return 1;
210 else if(child_pid == 0)
212 execlp("desktop-file-validate", "desktop-file-validate", file, NULL);
214 else
216 waitpid(child_pid, &statval, WUNTRACED | WCONTINUED);
217 if(WIFEXITED(statval)){
218 return(WEXITSTATUS(statval));
221 return -1;
224 /* Generate a squashfs filesystem
225 * The following would work if we link to mksquashfs.o after we renamed
226 * main() to mksquashfs_main() in mksquashfs.c but we don't want to actually do
227 * this because squashfs-tools is not under a permissive license
228 * i *nt sfs_mksquashfs(char *source, char *destination) {
229 * char *child_argv[5];
230 * child_argv[0] = NULL;
231 * child_argv[1] = source;
232 * child_argv[2] = destination;
233 * child_argv[3] = "-root-owned";
234 * child_argv[4] = "-noappend";
235 * mksquashfs_main(5, child_argv);
239 /* in-place modification of the string, and assuming the buffer pointed to by
240 * line is large enough to hold the resulting string*/
241 static void replacestr(char *line, const char *search, const char *replace)
243 char *sp = NULL;
245 if ((sp = strstr(line, search)) == NULL) {
246 return;
248 int search_len = strlen(search);
249 int replace_len = strlen(replace);
250 int tail_len = strlen(sp+search_len);
252 memmove(sp+replace_len,sp+search_len,tail_len+1);
253 memcpy(sp, replace, replace_len);
255 /* Do it recursively again until no more work to do */
257 if ((sp = strstr(line, search))) {
258 replacestr(line, search, replace);
262 int count_archs(bool* archs) {
263 int countArchs = 0;
264 int i;
265 for (i = 0; i < 4; i++) {
266 countArchs += archs[i];
268 return countArchs;
271 gchar* getArchName(bool* archs) {
272 if (archs[fARCH_i386])
273 return "i386";
274 else if (archs[fARCH_x86_64])
275 return "x86_64";
276 else if (archs[fARCH_arm])
277 return "ARM";
278 else if (archs[fARCH_aarch64])
279 return "ARM_aarch64";
280 else
281 return "all";
284 void extract_arch_from_text(gchar *archname, const gchar* sourcename, bool* archs) {
285 if (archname) {
286 archname = g_strstrip(archname);
287 if (archname) {
288 replacestr(archname, "-", "_");
289 replacestr(archname, " ", "_");
290 if (g_ascii_strncasecmp("i386", archname, 20) == 0
291 || g_ascii_strncasecmp("i486", archname, 20) == 0
292 || g_ascii_strncasecmp("i586", archname, 20) == 0
293 || g_ascii_strncasecmp("i686", archname, 20) == 0
294 || g_ascii_strncasecmp("intel_80386", archname, 20) == 0
295 || g_ascii_strncasecmp("intel_80486", archname, 20) == 0
296 || g_ascii_strncasecmp("intel_80586", archname, 20) == 0
297 || g_ascii_strncasecmp("intel_80686", archname, 20) == 0
299 archs[fARCH_i386] = 1;
300 if (verbose)
301 fprintf(stderr, "%s used for determining architecture i386\n", sourcename);
302 } else if (g_ascii_strncasecmp("x86_64", archname, 20) == 0) {
303 archs[fARCH_x86_64] = 1;
304 if (verbose)
305 fprintf(stderr, "%s used for determining architecture x86_64\n", sourcename);
306 } else if (g_ascii_strncasecmp("arm", archname, 20) == 0) {
307 archs[fARCH_arm] = 1;
308 if (verbose)
309 fprintf(stderr, "%s used for determining architecture ARM\n", sourcename);
310 } else if (g_ascii_strncasecmp("arm_aarch64", archname, 20) == 0) {
311 archs[fARCH_aarch64] = 1;
312 if (verbose)
313 fprintf(stderr, "%s used for determining architecture ARM aarch64\n", sourcename);
319 void guess_arch_of_file(const gchar *archfile, bool* archs) {
320 char line[PATH_MAX];
321 char command[PATH_MAX];
322 sprintf(command, "/usr/bin/file -L -N -b %s", archfile);
323 FILE* fp = popen(command, "r");
324 if (fp == NULL)
325 die("Failed to run file command");
326 fgets(line, sizeof (line) - 1, fp);
327 pclose(fp);
328 extract_arch_from_text(g_strsplit_set(line, ",", -1)[1], archfile, archs);
331 void find_arch(const gchar *real_path, const gchar *pattern, bool* archs) {
332 GDir *dir;
333 gchar *full_name;
334 dir = g_dir_open(real_path, 0, NULL);
335 if (dir != NULL) {
336 const gchar *entry;
337 while ((entry = g_dir_read_name(dir)) != NULL) {
338 full_name = g_build_filename(real_path, entry, NULL);
339 if (g_file_test(full_name, G_FILE_TEST_IS_DIR)) {
340 find_arch(full_name, pattern, archs);
341 } else if (g_file_test(full_name, G_FILE_TEST_IS_EXECUTABLE) || g_pattern_match_simple(pattern, entry) ) {
342 guess_arch_of_file(full_name, archs);
345 g_dir_close(dir);
347 else {
348 g_warning("%s: %s", real_path, g_strerror(errno));
352 gchar* find_first_matching_file_nonrecursive(const gchar *real_path, const gchar *pattern) {
353 GDir *dir;
354 gchar *full_name;
355 dir = g_dir_open(real_path, 0, NULL);
356 if (dir != NULL) {
357 const gchar *entry;
358 while ((entry = g_dir_read_name(dir)) != NULL) {
359 full_name = g_build_filename(real_path, entry, NULL);
360 if (g_file_test(full_name, G_FILE_TEST_IS_REGULAR)) {
361 if(g_pattern_match_simple(pattern, entry))
362 return(full_name);
365 g_dir_close(dir);
367 else {
368 g_warning("%s: %s", real_path, g_strerror(errno));
370 return NULL;
373 gchar* get_desktop_entry(GKeyFile *kf, char *key) {
374 gchar *value = g_key_file_get_string (kf, "Desktop Entry", key, NULL);
375 if (! value){
376 fprintf(stderr, "%s entry not found in desktop file\n", key);
378 return value;
381 bool readFile(char* filename, int* size, char** buffer) {
382 FILE* f = fopen(filename, "rb");
383 if (f==NULL) {
384 *buffer = 0;
385 *size = 0;
386 return false;
389 fseek(f, 0, SEEK_END);
390 long fsize = ftell(f);
391 fseek(f, 0, SEEK_SET);
393 char *indata = malloc(fsize);
394 fread(indata, fsize, 1, f);
395 fclose(f);
396 *size = (int)fsize;
397 *buffer = indata;
398 return TRUE;
401 // #####################################################################
403 static GOptionEntry entries[] =
405 { "list", 'l', 0, G_OPTION_ARG_NONE, &list, "List files in SOURCE AppImage", NULL },
406 { "updateinformation", 'u', 0, G_OPTION_ARG_STRING, &updateinformation, "Embed update information STRING; if zsyncmake is installed, generate zsync file", NULL },
407 { "guess", 'g', 0, G_OPTION_ARG_NONE, &guessupdateinformation, "Guess update information based on Travis CI environment variables", NULL },
408 { "bintray-user", 0, 0, G_OPTION_ARG_STRING, &bintray_user, "Bintray user name", NULL },
409 { "bintray-repo", 0, 0, G_OPTION_ARG_STRING, &bintray_repo, "Bintray repository", NULL },
410 { "version", 0, 0, G_OPTION_ARG_NONE, &version, "Show version number", NULL },
411 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Produce verbose output", NULL },
412 { "sign", 's', 0, G_OPTION_ARG_NONE, &sign, "Sign with gpg[2]", NULL },
413 { "comp", 0, 0, G_OPTION_ARG_STRING, &sqfs_comp, "Squashfs compression", NULL },
414 { "no-appstream", 'n', 0, G_OPTION_ARG_NONE, &no_appstream, "Do not check AppStream metadata", NULL },
415 { "exclude-file", 0, 0, G_OPTION_ARG_STRING, &exclude_file, _exclude_file_desc, NULL },
416 { "runtime-file", 0, 0, G_OPTION_ARG_STRING, &runtime_file, "Runtime file to use", NULL },
417 { "sign-args", 0, 0, G_OPTION_ARG_STRING, &sign_args, "Extra arguments to use when signing with gpg[2]", NULL},
418 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &remaining_args, NULL, NULL },
419 { 0,0,0,0,0,0,0 }
423 main (int argc, char *argv[])
425 /* Parse VERSION environment variable.
426 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
427 char* version_env;
428 version_env = getenv("VERSION");
430 /* Parse Travis CI environment variables.
431 * https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables
432 * TRAVIS_COMMIT: The commit that the current build is testing.
433 * TRAVIS_REPO_SLUG: The slug (in form: owner_name/repo_name) of the repository currently being built.
434 * TRAVIS_TAG: If the current build is for a git tag, this variable is set to the tag’s name.
435 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
436 // char* travis_commit;
437 // travis_commit = getenv("TRAVIS_COMMIT");
438 char* travis_repo_slug;
439 travis_repo_slug = getenv("TRAVIS_REPO_SLUG");
440 char* travis_tag;
441 travis_tag = getenv("TRAVIS_TAG");
442 char* travis_pull_request;
443 travis_pull_request = getenv("TRAVIS_PULL_REQUEST");
444 /* https://github.com/probonopd/uploadtool */
445 char* github_token;
446 github_token = getenv("GITHUB_TOKEN");
448 /* Parse OWD environment variable.
449 * If it is available then cd there. It is the original CWD prior to running AppRun */
450 char* owd_env = NULL;
451 owd_env = getenv("OWD");
452 if(NULL!=owd_env){
453 int ret;
454 ret = chdir(owd_env);
455 if (ret != 0){
456 fprintf(stderr, "Could not cd into %s\n", owd_env);
457 exit(1);
461 GError *error = NULL;
462 GOptionContext *context;
463 char command[PATH_MAX];
465 // initialize help text of argument
466 sprintf(_exclude_file_desc, "Uses given file as exclude file for mksquashfs, in addition to %s.", APPIMAGEIGNORE);
468 context = g_option_context_new ("SOURCE [DESTINATION] - Generate, extract, and inspect AppImages");
469 g_option_context_add_main_entries (context, entries, NULL);
470 // g_option_context_add_group (context, gtk_get_option_group (TRUE));
471 if (!g_option_context_parse (context, &argc, &argv, &error))
473 fprintf(stderr, "Option parsing failed: %s\n", error->message);
474 exit(1);
477 if(version){
478 fprintf(stderr,"Version: %s\n", VERSION_NUMBER);
479 exit(0);
482 if(!((0 == strcmp(sqfs_comp, "gzip")) || (0 ==strcmp(sqfs_comp, "xz"))))
483 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.");
484 /* Check for dependencies here. Better fail early if they are not present. */
485 if(! g_find_program_in_path ("mksquashfs"))
486 die("mksquashfs is missing but required, please install it");
487 if(! g_find_program_in_path ("desktop-file-validate"))
488 g_print("WARNING: desktop-file-validate is missing, please install it so that desktop files can be checked for potential errors\n");
489 if(! g_find_program_in_path ("zsyncmake"))
490 g_print("WARNING: zsyncmake is missing, please install it if you want to use binary delta updates\n");
491 if(! no_appstream)
492 if(! g_find_program_in_path ("appstreamcli"))
493 g_print("WARNING: appstreamcli is missing, please install it if you want to use AppStream metadata\n");
494 if(! g_find_program_in_path ("gpg2") && ! g_find_program_in_path ("gpg"))
495 g_print("WARNING: gpg2 or gpg is missing, please install it if you want to create digital signatures\n");
496 if(! g_find_program_in_path ("sha256sum") && ! g_find_program_in_path ("shasum"))
497 g_print("WARNING: sha256sum or shasum is missing, please install it if you want to create digital signatures\n");
499 if(!&remaining_args[0])
500 die("SOURCE is missing");
502 /* If in list mode */
503 if (list){
504 sfs_ls(remaining_args[0]);
505 exit(0);
508 /* If the first argument is a directory, then we assume that we should package it */
509 if (g_file_test (remaining_args[0], G_FILE_TEST_IS_DIR)){
510 char *destination;
511 char source[PATH_MAX];
512 realpath(remaining_args[0], source);
514 /* Check if *.desktop file is present in source AppDir */
515 gchar *desktop_file = find_first_matching_file_nonrecursive(source, "*.desktop");
516 if(desktop_file == NULL){
517 die("Desktop file not found, aborting");
519 if(verbose)
520 fprintf (stdout, "Desktop file: %s\n", desktop_file);
522 if(g_find_program_in_path ("desktop-file-validate")) {
523 if(validate_desktop_file(desktop_file) != 0){
524 fprintf(stderr, "ERROR: Desktop file contains errors. Please fix them. Please see\n");
525 fprintf(stderr, " https://standards.freedesktop.org/desktop-entry-spec/latest/\n");
526 die(" for more information.");
530 /* Read information from .desktop file */
531 GKeyFile *kf = g_key_file_new ();
532 if (!g_key_file_load_from_file (kf, desktop_file, 0, NULL))
533 die(".desktop file cannot be parsed");
535 if(verbose){
536 fprintf (stderr,"Name: %s\n", get_desktop_entry(kf, "Name"));
537 fprintf (stderr,"Icon: %s\n", get_desktop_entry(kf, "Icon"));
538 fprintf (stderr,"Exec: %s\n", get_desktop_entry(kf, "Exec"));
539 fprintf (stderr,"Comment: %s\n", get_desktop_entry(kf, "Comment"));
540 fprintf (stderr,"Type: %s\n", get_desktop_entry(kf, "Type"));
541 fprintf (stderr,"Categories: %s\n", get_desktop_entry(kf, "Categories"));
544 /* Determine the architecture */
545 bool archs[4] = {0, 0, 0, 0};
546 extract_arch_from_text(getenv("ARCH"), "Environmental variable ARCH", archs);
547 if (count_archs(archs) != 1) {
548 /* If no $ARCH variable is set check a file */
549 /* We use the next best .so that we can find to determine the architecture */
550 find_arch(source, "*.so.*", archs);
551 int countArchs = count_archs(archs);
552 if (countArchs != 1) {
553 if (countArchs < 1)
554 fprintf(stderr, "Unable to guess the architecture of the AppDir source directory \"%s\"\n", remaining_args[0]);
555 else
556 fprintf(stderr, "More than one architectures were found of the AppDir source directory \"%s\"\n", remaining_args[0]);
557 fprintf(stderr, "A valid architecture with the ARCH environmental variable should be provided\ne.g. ARCH=x86_64 %s", argv[0]),
558 die(" ...");
561 gchar* arch = getArchName(archs);
562 fprintf(stderr, "Using architecture %s\n", arch);
564 FILE *fp;
565 char app_name_for_filename[PATH_MAX];
566 sprintf(app_name_for_filename, "%s", get_desktop_entry(kf, "Name"));
567 replacestr(app_name_for_filename, " ", "_");
569 if(verbose)
570 fprintf (stderr,"App name for filename: %s\n", app_name_for_filename);
572 if (remaining_args[1]) {
573 destination = remaining_args[1];
574 } else {
575 /* No destination has been specified, to let's construct one
576 * TODO: Find out the architecture and use a $VERSION that might be around in the env */
577 char dest_path[PATH_MAX];
578 sprintf (dest_path, "%s-%s.AppImage", app_name_for_filename, arch);
580 if(verbose)
581 fprintf (stderr,"dest_path: %s\n", dest_path);
583 if (version_env!=NULL)
584 sprintf (dest_path, "%s-%s-%s.AppImage", app_name_for_filename, version_env, arch);
586 destination = dest_path;
587 replacestr(destination, " ", "_");
589 fprintf (stdout, "%s should be packaged as %s\n", source, destination);
590 /* Check if the Icon file is how it is expected */
591 gchar* icon_name = get_desktop_entry(kf, "Icon");
592 gchar* icon_file_path = NULL;
593 gchar* icon_file_png;
594 gchar* icon_file_svg;
595 gchar* icon_file_svgz;
596 gchar* icon_file_xpm;
597 icon_file_png = g_strdup_printf("%s/%s.png", source, icon_name);
598 icon_file_svg = g_strdup_printf("%s/%s.svg", source, icon_name);
599 icon_file_svgz = g_strdup_printf("%s/%s.svgz", source, icon_name);
600 icon_file_xpm = g_strdup_printf("%s/%s.xpm", source, icon_name);
601 if (g_file_test(icon_file_png, G_FILE_TEST_IS_REGULAR)) {
602 icon_file_path = icon_file_png;
603 } else if(g_file_test(icon_file_svg, G_FILE_TEST_IS_REGULAR)) {
604 icon_file_path = icon_file_svg;
605 } else if(g_file_test(icon_file_svgz, G_FILE_TEST_IS_REGULAR)) {
606 icon_file_path = icon_file_svgz;
607 } else if(g_file_test(icon_file_xpm, G_FILE_TEST_IS_REGULAR)) {
608 icon_file_path = icon_file_xpm;
609 } else {
610 fprintf (stderr, "%s{.png,.svg,.svgz,.xpm} defined in desktop file but not found\n", icon_name);
611 fprintf (stderr, "For example, you could put a 256x256 pixel png into\n");
612 gchar *icon_name_with_png = g_strconcat(icon_name, ".png", NULL);
613 gchar *example_path = g_build_filename(source, "/usr/share/icons/hicolor/256x256/apps/", icon_name_with_png, NULL);
614 fprintf (stderr, "%s\n", example_path);
615 exit(1);
618 /* Check if .DirIcon is present in source AppDir */
619 gchar *diricon_path = g_build_filename(source, ".DirIcon", NULL);
621 if (! g_file_test(diricon_path, G_FILE_TEST_EXISTS)){
622 fprintf (stderr, "Deleting pre-existing .DirIcon\n");
623 g_unlink(diricon_path);
625 if (! g_file_test(diricon_path, G_FILE_TEST_IS_REGULAR)){
626 fprintf (stderr, "Creating .DirIcon symlink based on information from desktop file\n");
627 int res = symlink(basename(icon_file_path), diricon_path);
628 if(res)
629 die("Could not symlink .DirIcon");
632 /* Check if AppStream upstream metadata is present in source AppDir */
633 if(! no_appstream){
634 char application_id[PATH_MAX];
635 sprintf (application_id, "%s", basename(desktop_file));
636 replacestr(application_id, ".desktop", ".appdata.xml");
637 gchar *appdata_path = g_build_filename(source, "/usr/share/metainfo/", application_id, NULL);
638 if (! g_file_test(appdata_path, G_FILE_TEST_IS_REGULAR)){
639 fprintf (stderr, "WARNING: AppStream upstream metadata is missing, please consider creating it\n");
640 fprintf (stderr, " in usr/share/metainfo/%s\n", application_id);
641 fprintf (stderr, " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps\n");
642 fprintf (stderr, " for more information.\n");
643 /* As a courtesy, generate one to be filled by the user */
644 if(g_find_program_in_path ("appstream-util")) {
645 gchar *appdata_dir = g_build_filename(source, "/usr/share/metainfo/", NULL);
646 g_mkdir_with_parents(appdata_dir, 0755);
647 sprintf (command, "%s appdata-from-desktop %s %s", g_find_program_in_path ("appstream-util"), desktop_file, appdata_path);
648 int ret = system(command);
649 if (ret != 0)
650 die("Failed to generate AppStream template");
651 fprintf (stderr, "AppStream template has been generated in in %s, please edit it\n", appdata_path);
652 exit(1);
654 } else {
655 fprintf (stderr, "AppStream upstream metadata found in usr/share/metainfo/%s\n", application_id);
656 /* Use ximion's appstreamcli to make sure that desktop file and appdata match together */
657 if(g_find_program_in_path ("appstreamcli")) {
658 sprintf (command, "%s validate-tree %s", g_find_program_in_path ("appstreamcli"), source);
659 int ret = system(command);
660 if (ret != 0)
661 die("Failed to validate AppStream information with appstreamcli");
663 /* It seems that hughsie's appstream-util does additional validations */
664 if(g_find_program_in_path ("appstream-util")) {
665 sprintf (command, "%s validate-relax %s", g_find_program_in_path ("appstream-util"), appdata_path);
666 int ret = system(command);
667 if (ret != 0)
668 die("Failed to validate AppStream information with appstream-util");
673 /* Upstream mksquashfs can currently not start writing at an offset,
674 * so we need a patched one. https://github.com/plougher/squashfs-tools/pull/13
675 * should hopefully change that. */
677 fprintf (stderr, "Generating squashfs...\n");
678 int size = 0;
679 char* data = NULL;
680 bool using_external_data = false;
681 if (runtime_file != NULL) {
682 if (!readFile(runtime_file, &size, &data))
683 die("Unable to load provided runtime file");
684 using_external_data = true;
685 } else {
686 #ifdef HAVE_BINARY_RUNTIME
687 /* runtime is embedded into this executable
688 * http://stupefydeveloper.blogspot.de/2008/08/cc-embed-binary-data-into-elf.html */
689 size = (int)((void *)&_binary_runtime_end - (void *)&_binary_runtime_start);
690 data = (char *)&_binary_runtime_start;
691 #else
692 die("No runtime file was provided");
693 #endif
695 if (verbose)
696 printf("Size of the embedded runtime: %d bytes\n", size);
698 int result = sfs_mksquashfs(source, destination, size);
699 if(result != 0)
700 die("sfs_mksquashfs error");
702 fprintf (stderr, "Embedding ELF...\n");
703 FILE *fpdst = fopen(destination, "rb+");
704 if (fpdst == NULL) {
705 die("Not able to open the AppImage for writing, aborting");
708 fseek(fpdst, 0, SEEK_SET);
709 fwrite(data, size, 1, fpdst);
710 fclose(fpdst);
711 if (using_external_data)
712 free(data);
714 fprintf (stderr, "Marking the AppImage as executable...\n");
715 if (chmod (destination, 0755) < 0) {
716 printf("Could not set executable bit, aborting\n");
717 exit(1);
720 if(bintray_user != NULL){
721 if(bintray_repo != NULL){
722 char buf[1024];
723 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);
724 updateinformation = buf;
725 printf("%s\n", updateinformation);
729 /* If the user has not provided update information but we know this is a Travis CI build,
730 * then fill in update information based on TRAVIS_REPO_SLUG */
731 if(guessupdateinformation){
732 if(!travis_repo_slug){
733 printf("Cannot guess update information since $TRAVIS_REPO_SLUG is missing\n");
734 } else if(!github_token) {
735 printf("Will not guess update information since $GITHUB_TOKEN is missing,\n");
736 if(0 != strcmp(travis_pull_request, "false")){
737 printf("please set it in the Travis CI Repository Settings for this project.\n");
738 printf("You can get one from https://github.com/settings/tokens\n");
739 } else {
740 printf("which is expected since this is a pull request\n");
742 } else {
743 gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake");
744 if(zsyncmake_path){
745 char buf[1024];
746 gchar **parts = g_strsplit (travis_repo_slug, "/", 2);
747 /* https://github.com/AppImage/AppImageSpec/blob/master/draft.md#github-releases
748 * gh-releases-zsync|probono|AppImages|latest|Subsurface*-x86_64.AppImage.zsync */
749 gchar *channel = "continuous";
750 if(travis_tag != NULL){
751 if((strcmp(travis_tag, "") != 0) && (strcmp(travis_tag, "continuous") != 0)) {
752 channel = "latest";
755 sprintf(buf, "gh-releases-zsync|%s|%s|%s|%s*-%s.AppImage.zsync", parts[0], parts[1], channel, app_name_for_filename, arch);
756 updateinformation = buf;
757 printf("Guessing update information based on $TRAVIS_TAG=%s and $TRAVIS_REPO_SLUG=%s\n", travis_tag, travis_repo_slug);
758 printf("%s\n", updateinformation);
759 } else {
760 printf("Will not guess update information since zsyncmake is missing\n");
765 /* If updateinformation was provided, then we check and embed it */
766 if(updateinformation != NULL){
767 if(!g_str_has_prefix(updateinformation,"zsync|"))
768 if(!g_str_has_prefix(updateinformation,"bintray-zsync|"))
769 if(!g_str_has_prefix(updateinformation,"gh-releases-zsync|"))
770 die("The provided updateinformation is not in a recognized format");
772 gchar **ui_type = g_strsplit_set(updateinformation, "|", -1);
774 if(verbose)
775 printf("updateinformation type: %s\n", ui_type[0]);
776 /* TODO: Further checking of the updateinformation */
779 unsigned long ui_offset = 0;
780 unsigned long ui_length = 0;
781 get_elf_section_offset_and_lenghth(destination, ".upd_info", &ui_offset, &ui_length);
782 if(verbose) {
783 printf("ui_offset: %lu\n", ui_offset);
784 printf("ui_length: %lu\n", ui_length);
786 if(ui_offset == 0) {
787 die("Could not determine offset for updateinformation");
788 } else {
789 if(strlen(updateinformation)>ui_length)
790 die("updateinformation does not fit into segment, aborting");
791 FILE *fpdst2 = fopen(destination, "r+");
792 if (fpdst2 == NULL)
793 die("Not able to open the destination file for writing, aborting");
794 fseek(fpdst2, ui_offset, SEEK_SET);
795 // fseek(fpdst2, ui_offset, SEEK_SET);
796 // fwrite(0x00, 1, 1024, fpdst); // FIXME: Segfaults; why?
797 // fseek(fpdst, ui_offset, SEEK_SET);
798 fwrite(updateinformation, strlen(updateinformation), 1, fpdst2);
799 fclose(fpdst2);
803 if(sign){
804 bool using_gpg = FALSE;
805 bool using_shasum = FALSE;
806 /* The user has indicated that he wants to sign */
807 gchar *gpg2_path = g_find_program_in_path ("gpg2");
808 if (!gpg2_path) {
809 gpg2_path = g_find_program_in_path ("gpg");
810 using_gpg = TRUE;
812 gchar *sha256sum_path = g_find_program_in_path ("sha256sum");
813 if (!sha256sum_path) {
814 sha256sum_path = g_find_program_in_path ("shasum");
815 using_shasum = 1;
817 if(!gpg2_path){
818 fprintf (stderr, "gpg2 or gpg is not installed, cannot sign\n");
820 else if(!sha256sum_path) {
821 fprintf(stderr, "sha256sum or shasum is not installed, cannot sign\n");
822 } else {
823 fprintf(stderr, "%s and %s are installed and user requested to sign, "
824 "hence signing\n", using_gpg ? "gpg" : "gpg2",
825 using_shasum ? "shasum" : "sha256sum");
826 char *digestfile;
827 digestfile = br_strcat(destination, ".digest");
828 char *ascfile;
829 ascfile = br_strcat(destination, ".digest.asc");
830 if (g_file_test (digestfile, G_FILE_TEST_IS_REGULAR))
831 unlink(digestfile);
832 if (using_shasum)
833 sprintf (command, "%s -a256 %s", sha256sum_path, destination);
834 else
835 sprintf (command, "%s %s", sha256sum_path, destination);
836 if(verbose)
837 fprintf (stderr, "%s\n", command);
838 fp = popen(command, "r");
839 if (fp == NULL)
840 die(using_shasum ? "shasum command did not succeed" : "sha256sum command did not succeed");
841 char output[1024];
842 fgets(output, sizeof (output) - 1, fp);
843 if (verbose)
844 printf("%s: %s\n", using_shasum ? "shasum" : "sha256sum",
845 g_strsplit_set(output, " ", -1)[0]);
846 FILE *fpx = fopen(digestfile, "w");
847 if (fpx != NULL)
849 fputs(g_strsplit_set(output, " ", -1)[0], fpx);
850 fclose(fpx);
852 int shasum_exit_status = pclose(fp);
853 if(WEXITSTATUS(shasum_exit_status) != 0)
854 die(using_shasum ? "shasum command did not succeed" : "sha256sum command did not succeed");
855 if (g_file_test (ascfile, G_FILE_TEST_IS_REGULAR))
856 unlink(ascfile);
857 sprintf (command, "%s --detach-sign --armor %s %s", gpg2_path, sign_args ? sign_args : "", digestfile);
858 if(verbose)
859 fprintf (stderr, "%s\n", command);
860 fp = popen(command, "r");
861 int gpg_exit_status = pclose(fp);
862 if(WEXITSTATUS(gpg_exit_status) != 0) {
863 fprintf (stderr, "ERROR: %s command did not succeed, could not sign, continuing\n", using_gpg ? "gpg" : "gpg2");
864 } else {
865 unsigned long sig_offset = 0;
866 unsigned long sig_length = 0;
867 get_elf_section_offset_and_lenghth(destination, ".sha256_sig", &sig_offset, &sig_length);
868 if(verbose) {
869 printf("sig_offset: %lu\n", sig_offset);
870 printf("sig_length: %lu\n", sig_length);
872 if(sig_offset == 0) {
873 die("Could not determine offset for signature");
874 } else {
875 FILE *fpdst3 = fopen(destination, "r+");
876 if (fpdst3 == NULL)
877 die("Not able to open the destination file for writing, aborting");
878 // if(strlen(updateinformation)>sig_length)
879 // die("signature does not fit into segment, aborting");
880 fseek(fpdst3, sig_offset, SEEK_SET);
881 FILE *fpsrc2 = fopen(ascfile, "rb");
882 if (fpsrc2 == NULL) {
883 die("Not able to open the asc file for reading, aborting");
885 char byte;
886 while (!feof(fpsrc2))
888 fread(&byte, sizeof(char), 1, fpsrc2);
889 fwrite(&byte, sizeof(char), 1, fpdst3);
891 fclose(fpsrc2);
892 fclose(fpdst3);
894 if (g_file_test (ascfile, G_FILE_TEST_IS_REGULAR))
895 unlink(ascfile);
896 if (g_file_test (digestfile, G_FILE_TEST_IS_REGULAR))
897 unlink(digestfile);
902 /* If updateinformation was provided, then we also generate the zsync file (after having signed the AppImage) */
903 if(updateinformation != NULL){
904 gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake");
905 if(!zsyncmake_path){
906 fprintf (stderr, "zsyncmake is not installed/bundled, skipping\n");
907 } else {
908 fprintf (stderr, "zsyncmake is available and updateinformation is provided, "
909 "hence generating zsync file\n");
910 sprintf (command, "%s %s -u %s", zsyncmake_path, destination, basename(destination));
911 if(verbose)
912 fprintf (stderr, "%s\n", command);
913 fp = popen(command, "r");
914 if (fp == NULL)
915 die("Failed to run zsyncmake command");
916 int exitstatus = pclose(fp);
917 if (WEXITSTATUS(exitstatus) != 0)
918 die("zsyncmake command did not succeed");
922 fprintf (stderr, "Success\n\n");
923 fprintf (stderr, "Please consider submitting your AppImage to AppImageHub, the crowd-sourced\n");
924 fprintf (stderr, "central directory of available AppImages, by opening a pull request\n");
925 fprintf (stderr, "at https://github.com/AppImage/appimage.github.io\n");
928 /* If the first argument is a regular file, then we assume that we should unpack it */
929 if (g_file_test (remaining_args[0], G_FILE_TEST_IS_REGULAR)){
930 fprintf (stdout, "%s is a file, assuming it is an AppImage and should be unpacked\n", remaining_args[0]);
931 die("To be implemented");
934 return 0;