Merge pull request #475 from th-otto/PR-2
[appimagekit/gsi.git] / appimagetool.c
blob7163bcb9728616c46c7ee10daba4d47b8d4ae5c0
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>
51 #include "elf.h"
52 #include "getsection.h"
54 extern int _binary_runtime_start;
55 extern int _binary_runtime_end;
57 static gchar const APPIMAGEIGNORE[] = ".appimageignore";
58 static char _exclude_file_desc[256];
60 static gboolean list = FALSE;
61 static gboolean verbose = FALSE;
62 static gboolean version = FALSE;
63 static gboolean sign = FALSE;
64 static gboolean no_appstream = FALSE;
65 gchar **remaining_args = NULL;
66 gchar *updateinformation = NULL;
67 gchar *bintray_user = NULL;
68 gchar *bintray_repo = NULL;
69 gchar *sqfs_comp = "gzip";
70 gchar *exclude_file = NULL;
72 // #####################################################################
74 static void die(const char *msg) {
75 fprintf(stderr, "%s\n", msg);
76 exit(1);
79 /* Function that prints the contents of a squashfs file
80 * using libsquashfuse (#include "squashfuse.h") */
81 int sfs_ls(char* image) {
82 sqfs_err err = SQFS_OK;
83 sqfs_traverse trv;
84 sqfs fs;
86 unsigned long fs_offset = get_elf_size(image);
88 if ((err = sqfs_open_image(&fs, image, fs_offset)))
89 die("sqfs_open_image error");
91 if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs))))
92 die("sqfs_traverse_open error");
93 while (sqfs_traverse_next(&trv, &err)) {
94 if (!trv.dir_end) {
95 printf("%s\n", trv.path);
98 if (err)
99 die("sqfs_traverse_next error");
100 sqfs_traverse_close(&trv);
102 sqfs_fd_close(fs.fd);
103 return 0;
106 /* Generate a squashfs filesystem using mksquashfs on the $PATH
107 * execlp(), execvp(), and execvpe() search on the $PATH */
108 int sfs_mksquashfs(char *source, char *destination, int offset) {
109 pid_t pid = fork();
111 if (pid == -1) {
112 // error, failed to fork()
113 return(-1);
114 } else if (pid > 0) {
115 int status;
116 waitpid(pid, &status, 0);
117 } else {
118 // we are the child
119 gchar *offset_string;
120 offset_string = g_strdup_printf("%i", offset);
122 char* args[32];
123 bool use_xz = strcmp(sqfs_comp, "xz") >= 0;
125 int i = 0;
126 args[i++] = "mksquashfs";
127 args[i++] = source;
128 args[i++] = destination;
129 args[i++] = "-offset";
130 args[i++] = offset_string;
131 args[i++] = "-comp";
133 if(use_xz)
134 args[i++] = "xz";
135 else
136 args[i++] = sqfs_comp;
138 args[i++] = "-root-owned";
139 args[i++] = "-noappend";
141 if(use_xz) {
142 // https://jonathancarter.org/2015/04/06/squashfs-performance-testing/ says:
143 // improved performance by using a 16384 block size with a sacrifice of around 3% more squashfs image space
144 args[i++] = "-Xdict-size";
145 args[i++] = "100%";
146 args[i++] = "-b";
147 args[i++] = "16384";
150 // check if ignore file exists and use it if possible
151 if(access(APPIMAGEIGNORE, F_OK) >= 0) {
152 printf("Including %s", APPIMAGEIGNORE);
153 args[i++] = "-wildcards";
154 args[i++] = "-ef";
156 // avoid warning: assignment discards ‘const’ qualifier
157 char buf[256];
158 strcpy(buf, APPIMAGEIGNORE);
159 args[i++] = buf;
162 // if an exclude file has been passed on the command line, should be used, too
163 if(exclude_file != 0 && strlen(exclude_file) > 0) {
164 if(access(exclude_file, F_OK) < 0) {
165 printf("WARNING: exclude file %s not found!", exclude_file);
166 return -1;
169 args[i++] = "-wildcards";
170 args[i++] = "-ef";
171 args[i++] = exclude_file;
174 args[i++] = 0;
176 execvp("mksquashfs", args);
178 perror("execlp"); // exec*() returns only on error
179 return -1; // exec never returns
181 return 0;
184 /* Validate desktop file using desktop-file-validate on the $PATH
185 * execlp(), execvp(), and execvpe() search on the $PATH */
186 int validate_desktop_file(char *file) {
187 int number, statval;
188 int child_pid;
189 child_pid = fork();
190 if(child_pid == -1)
192 printf("could not fork! \n");
193 return 1;
195 else if(child_pid == 0)
197 execlp("desktop-file-validate", "desktop-file-validate", file, NULL);
199 else
201 waitpid(child_pid, &statval, WUNTRACED | WCONTINUED);
202 if(WIFEXITED(statval)){
203 return(WEXITSTATUS(statval));
206 return -1;
209 /* Generate a squashfs filesystem
210 * The following would work if we link to mksquashfs.o after we renamed
211 * main() to mksquashfs_main() in mksquashfs.c but we don't want to actually do
212 * this because squashfs-tools is not under a permissive license
213 * i *nt sfs_mksquashfs(char *source, char *destination) {
214 * char *child_argv[5];
215 * child_argv[0] = NULL;
216 * child_argv[1] = source;
217 * child_argv[2] = destination;
218 * child_argv[3] = "-root-owned";
219 * child_argv[4] = "-noappend";
220 * mksquashfs_main(5, child_argv);
224 gchar* find_first_matching_file(const gchar *real_path, const gchar *pattern) {
225 GDir *dir;
226 gchar *full_name;
227 gchar *resulting;
228 dir = g_dir_open(real_path, 0, NULL);
229 if (dir != NULL) {
230 const gchar *entry;
231 while ((entry = g_dir_read_name(dir)) != NULL) {
232 full_name = g_build_filename(real_path, entry, NULL);
233 if (! g_file_test(full_name, G_FILE_TEST_IS_DIR)) {
234 if(g_pattern_match_simple(pattern, entry))
235 return(full_name);
237 else {
238 resulting = find_first_matching_file(full_name, pattern);
239 if(resulting)
240 return(resulting);
243 g_dir_close(dir);
245 else {
246 g_warning("%s: %s", real_path, g_strerror(errno));
248 return NULL;
251 gchar* find_first_matching_file_nonrecursive(const gchar *real_path, const gchar *pattern) {
252 GDir *dir;
253 gchar *full_name;
254 dir = g_dir_open(real_path, 0, NULL);
255 if (dir != NULL) {
256 const gchar *entry;
257 while ((entry = g_dir_read_name(dir)) != NULL) {
258 full_name = g_build_filename(real_path, entry, NULL);
259 if (g_file_test(full_name, G_FILE_TEST_IS_REGULAR)) {
260 if(g_pattern_match_simple(pattern, entry))
261 return(full_name);
264 g_dir_close(dir);
266 else {
267 g_warning("%s: %s", real_path, g_strerror(errno));
269 return NULL;
272 gchar* get_desktop_entry(GKeyFile *kf, char *key) {
273 gchar *value = g_key_file_get_string (kf, "Desktop Entry", key, NULL);
274 if (! value){
275 fprintf(stderr, "%s entry not found in desktop file\n", key);
277 return value;
280 /* in-place modification of the string, and assuming the buffer pointed to by
281 * line is large enough to hold the resulting string*/
282 static void replacestr(char *line, const char *search, const char *replace)
284 char *sp = NULL;
286 if ((sp = strstr(line, search)) == NULL) {
287 return;
289 int search_len = strlen(search);
290 int replace_len = strlen(replace);
291 int tail_len = strlen(sp+search_len);
293 memmove(sp+replace_len,sp+search_len,tail_len+1);
294 memcpy(sp, replace, replace_len);
296 /* Do it recursively again until no more work to do */
298 if ((sp = strstr(line, search))) {
299 replacestr(line, search, replace);
303 // #####################################################################
305 static GOptionEntry entries[] =
307 { "list", 'l', 0, G_OPTION_ARG_NONE, &list, "List files in SOURCE AppImage", NULL },
308 { "updateinformation", 'u', 0, G_OPTION_ARG_STRING, &updateinformation, "Embed update information STRING; if zsyncmake is installed, generate zsync file", NULL },
309 { "bintray-user", 0, 0, G_OPTION_ARG_STRING, &bintray_user, "Bintray user name", NULL },
310 { "bintray-repo", 0, 0, G_OPTION_ARG_STRING, &bintray_repo, "Bintray repository", NULL },
311 { "version", 0, 0, G_OPTION_ARG_NONE, &version, "Show version number", NULL },
312 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Produce verbose output", NULL },
313 { "sign", 's', 0, G_OPTION_ARG_NONE, &sign, "Sign with gpg2", NULL },
314 { "comp", 0, 0, G_OPTION_ARG_STRING, &sqfs_comp, "Squashfs compression", NULL },
315 { "no-appstream", 'n', 0, G_OPTION_ARG_NONE, &no_appstream, "Do not check AppStream metadata", NULL },
316 { "exclude-file", 0, 0, G_OPTION_ARG_STRING, &exclude_file, _exclude_file_desc, NULL },
317 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &remaining_args, NULL, NULL },
318 { 0,0,0,0,0,0,0 }
322 main (int argc, char *argv[])
324 /* Parse VERSION environment variable.
325 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
326 char* version_env;
327 version_env = getenv("VERSION");
329 /* Parse OWD environment variable.
330 * If it is available then cd there. It is the original CWD prior to running AppRun */
331 char* owd_env = NULL;
332 owd_env = getenv("OWD");
333 if(NULL!=owd_env){
334 int ret;
335 ret = chdir(owd_env);
336 if (ret != 0){
337 fprintf(stderr, "Could not cd into %s\n", owd_env);
338 exit(1);
343 GError *error = NULL;
344 GOptionContext *context;
345 char command[PATH_MAX];
347 // initialize help text of argument
348 sprintf(_exclude_file_desc, "Uses given file as exclude file for mksquashfs, in addition to %s.", APPIMAGEIGNORE);
350 context = g_option_context_new ("SOURCE [DESTINATION] - Generate, extract, and inspect AppImages");
351 g_option_context_add_main_entries (context, entries, NULL);
352 // g_option_context_add_group (context, gtk_get_option_group (TRUE));
353 if (!g_option_context_parse (context, &argc, &argv, &error))
355 fprintf(stderr, "Option parsing failed: %s\n", error->message);
356 exit(1);
359 if(version){
360 fprintf(stderr,"Version: %s\n", VERSION_NUMBER);
361 exit(0);
364 if(!((0 == strcmp(sqfs_comp, "gzip")) || (0 ==strcmp(sqfs_comp, "xz"))))
365 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.");
366 /* Check for dependencies here. Better fail early if they are not present. */
367 if(! g_find_program_in_path ("mksquashfs"))
368 die("mksquashfs is missing but required, please install it");
369 if(! g_find_program_in_path ("desktop-file-validate"))
370 g_print("WARNING: desktop-file-validate is missing, please install it so that desktop files can be checked for potential errors\n");
371 if(! g_find_program_in_path ("zsyncmake"))
372 g_print("WARNING: zsyncmake is missing, please install it if you want to use binary delta updates\n");
373 if(! no_appstream)
374 if(! g_find_program_in_path ("appstreamcli"))
375 g_print("WARNING: appstreamcli is missing, please install it if you want to use AppStream metadata\n");
376 if(! g_find_program_in_path ("gpg2"))
377 g_print("WARNING: gpg2 is missing, please install it if you want to create digital signatures\n");
378 if(! g_find_program_in_path ("sha256sum"))
379 g_print("WARNING: sha256sum is missing, please install it if you want to create digital signatures\n");
381 if(!&remaining_args[0])
382 die("SOURCE is missing");
384 /* If in list mode */
385 if (list){
386 sfs_ls(remaining_args[0]);
387 exit(0);
390 /* If the first argument is a directory, then we assume that we should package it */
391 if (g_file_test (remaining_args[0], G_FILE_TEST_IS_DIR)){
392 char *destination;
393 char source[PATH_MAX];
394 realpath(remaining_args[0], source);
396 /* Check if *.desktop file is present in source AppDir */
397 gchar *desktop_file = find_first_matching_file_nonrecursive(source, "*.desktop");
398 if(desktop_file == NULL){
399 die("Desktop file not found, aborting");
401 if(verbose)
402 fprintf (stdout, "Desktop file: %s\n", desktop_file);
404 if(g_find_program_in_path ("desktop-file-validate")) {
405 if(validate_desktop_file(desktop_file) != 0){
406 fprintf(stderr, "ERROR: Desktop file contains errors. Please fix them. Please see\n");
407 fprintf(stderr, " https://standards.freedesktop.org/desktop-entry-spec/latest/\n");
408 die(" for more information.");
412 /* Read information from .desktop file */
413 GKeyFile *kf = g_key_file_new ();
414 if (!g_key_file_load_from_file (kf, desktop_file, 0, NULL))
415 die(".desktop file cannot be parsed");
417 if(verbose){
418 fprintf (stderr,"Name: %s\n", get_desktop_entry(kf, "Name"));
419 fprintf (stderr,"Icon: %s\n", get_desktop_entry(kf, "Icon"));
420 fprintf (stderr,"Exec: %s\n", get_desktop_entry(kf, "Exec"));
421 fprintf (stderr,"Comment: %s\n", get_desktop_entry(kf, "Comment"));
422 fprintf (stderr,"Type: %s\n", get_desktop_entry(kf, "Type"));
423 fprintf (stderr,"Categories: %s\n", get_desktop_entry(kf, "Categories"));
426 /* Determine the architecture */
427 gchar *arch = getenv("ARCH");
428 FILE *fp;
429 /* If no $ARCH variable is set check a file */
430 if (!arch) {
431 gchar *archfile = NULL;
432 /* We use the next best .so that we can find to determine the architecture */
433 archfile = find_first_matching_file(source, "*.so.*");
434 if(!archfile)
436 /* If we found no .so we try to guess the main executable - this might be a script though */
437 // char guessed_bin_path[PATH_MAX];
438 // sprintf (guessed_bin_path, "%s/usr/bin/%s", source, g_strsplit_set(get_desktop_entry(kf, "Exec"), " ", -1)[0]);
439 // archfile = guessed_bin_path;
440 archfile = "/proc/self/exe";
442 if(verbose)
443 fprintf (stderr,"File used for determining architecture: %s\n", archfile);
445 char line[PATH_MAX];
446 char command[PATH_MAX];
447 sprintf (command, "/usr/bin/file -L -N -b %s", archfile);
448 fp = popen(command, "r");
449 if (fp == NULL)
450 die("Failed to run file command");
451 fgets(line, sizeof(line)-1, fp);
452 arch = g_strstrip(g_strsplit_set(line, ",", -1)[1]);
453 replacestr(arch, "-", "_");
454 fprintf (stderr,"Arch: %s\n", arch+1);
455 pclose(fp);
457 if(!arch)
459 printf("The architecture could not be determined, assuming 'all'\n");
460 arch="all";
464 char app_name_for_filename[PATH_MAX];
465 sprintf(app_name_for_filename, "%s", get_desktop_entry(kf, "Name"));
466 replacestr(app_name_for_filename, " ", "_");
468 if(verbose)
469 fprintf (stderr,"App name for filename: %s\n", app_name_for_filename);
471 if (remaining_args[1]) {
472 destination = remaining_args[1];
473 } else {
474 /* No destination has been specified, to let's construct one
475 * TODO: Find out the architecture and use a $VERSION that might be around in the env */
476 char dest_path[PATH_MAX];
477 sprintf (dest_path, "%s-%s.AppImage", app_name_for_filename, arch);
479 if(verbose)
480 fprintf (stderr,"dest_path: %s\n", dest_path);
482 if (version_env!=NULL)
483 sprintf (dest_path, "%s-%s-%s.AppImage", app_name_for_filename, version_env, arch);
485 destination = dest_path;
486 replacestr(destination, " ", "_");
488 fprintf (stdout, "%s should be packaged as %s\n", source, destination);
489 /* Check if the Icon file is how it is expected */
490 gchar* icon_name = get_desktop_entry(kf, "Icon");
491 gchar* icon_file_path = NULL;
492 gchar* icon_file_png;
493 gchar* icon_file_svg;
494 gchar* icon_file_svgz;
495 gchar* icon_file_xpm;
496 icon_file_png = g_strdup_printf("%s/%s.png", source, icon_name);
497 icon_file_svg = g_strdup_printf("%s/%s.svg", source, icon_name);
498 icon_file_svgz = g_strdup_printf("%s/%s.svgz", source, icon_name);
499 icon_file_xpm = g_strdup_printf("%s/%s.xpm", source, icon_name);
500 if (g_file_test(icon_file_png, G_FILE_TEST_IS_REGULAR)) {
501 icon_file_path = icon_file_png;
502 } else if(g_file_test(icon_file_svg, G_FILE_TEST_IS_REGULAR)) {
503 icon_file_path = icon_file_svg;
504 } else if(g_file_test(icon_file_svgz, G_FILE_TEST_IS_REGULAR)) {
505 icon_file_path = icon_file_svgz;
506 } else if(g_file_test(icon_file_xpm, G_FILE_TEST_IS_REGULAR)) {
507 icon_file_path = icon_file_xpm;
508 } else {
509 fprintf (stderr, "%s{.png,.svg,.svgz,.xpm} not present but defined in desktop file\n", icon_name);
510 exit(1);
513 /* Check if .DirIcon is present in source AppDir */
514 gchar *diricon_path = g_build_filename(source, ".DirIcon", NULL);
516 if (! g_file_test(diricon_path, G_FILE_TEST_EXISTS)){
517 fprintf (stderr, "Deleting pre-existing .DirIcon\n");
518 g_unlink(diricon_path);
520 if (! g_file_test(diricon_path, G_FILE_TEST_IS_REGULAR)){
521 fprintf (stderr, "Creating .DirIcon symlink based on information from desktop file\n");
522 int res = symlink(basename(icon_file_path), diricon_path);
523 if(res)
524 die("Could not symlink .DirIcon");
527 /* Check if AppStream upstream metadata is present in source AppDir */
528 if(! no_appstream){
529 char application_id[PATH_MAX];
530 sprintf (application_id, "%s", basename(desktop_file));
531 replacestr(application_id, ".desktop", ".appdata.xml");
532 gchar *appdata_path = g_build_filename(source, "/usr/share/metainfo/", application_id, NULL);
533 if (! g_file_test(appdata_path, G_FILE_TEST_IS_REGULAR)){
534 fprintf (stderr, "WARNING: AppStream upstream metadata is missing, please consider creating it\n");
535 fprintf (stderr, " in usr/share/metainfo/%s\n", application_id);
536 fprintf (stderr, " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps\n");
537 fprintf (stderr, " for more information.\n");
538 /* As a courtesy, generate one to be filled by the user */
539 if(g_find_program_in_path ("appstream-util")) {
540 gchar *appdata_dir = g_build_filename(source, "/usr/share/metainfo/", NULL);
541 g_mkdir_with_parents(appdata_dir, 0755);
542 sprintf (command, "%s appdata-from-desktop %s %s", g_find_program_in_path ("appstream-util"), desktop_file, appdata_path);
543 int ret = system(command);
544 if (ret != 0)
545 die("Failed to generate AppStream template");
546 fprintf (stderr, "AppStream template has been generated in in %s, please edit it\n", appdata_path);
547 exit(1);
549 } else {
550 fprintf (stderr, "AppStream upstream metadata found in usr/share/metainfo/%s\n", application_id);
551 /* Use ximion's appstreamcli to make sure that desktop file and appdata match together */
552 if(g_find_program_in_path ("appstreamcli")) {
553 sprintf (command, "%s validate-tree %s", g_find_program_in_path ("appstreamcli"), source);
554 int ret = system(command);
555 if (ret != 0)
556 die("Failed to validate AppStream information with appstreamcli");
558 /* It seems that hughsie's appstream-util does additional validations */
559 if(g_find_program_in_path ("appstream-util")) {
560 sprintf (command, "%s validate-relax %s", g_find_program_in_path ("appstream-util"), appdata_path);
561 int ret = system(command);
562 if (ret != 0)
563 die("Failed to validate AppStream information with appstream-util");
568 /* Upstream mksquashfs can currently not start writing at an offset,
569 * so we need a patched one. https://github.com/plougher/squashfs-tools/pull/13
570 * should hopefully change that. */
572 fprintf (stderr, "Generating squashfs...\n");
573 /* runtime is embedded into this executable
574 * http://stupefydeveloper.blogspot.de/2008/08/cc-embed-binary-data-into-elf.html */
575 int size = (int)((void *)&_binary_runtime_end - (void *)&_binary_runtime_start);
576 char *data = (char *)&_binary_runtime_start;
577 if (verbose)
578 printf("Size of the embedded runtime: %d bytes\n", size);
580 int result = sfs_mksquashfs(source, destination, size);
581 if(result != 0)
582 die("sfs_mksquashfs error");
584 fprintf (stderr, "Embedding ELF...\n");
585 FILE *fpdst = fopen(destination, "rb+");
586 if (fpdst == NULL) {
587 die("Not able to open the AppImage for writing, aborting");
590 fseek(fpdst, 0, SEEK_SET);
591 fwrite(data, size, 1, fpdst);
592 fclose(fpdst);
594 fprintf (stderr, "Marking the AppImage as executable...\n");
595 if (chmod (destination, 0755) < 0) {
596 printf("Could not set executable bit, aborting\n");
597 exit(1);
600 if(bintray_user != NULL){
601 if(bintray_repo != NULL){
602 char buf[1024];
603 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);
604 updateinformation = buf;
605 printf("%s\n", updateinformation);
609 /* If updateinformation was provided, then we check and embed it */
610 if(updateinformation != NULL){
611 if(!g_str_has_prefix(updateinformation,"zsync|"))
612 if(!g_str_has_prefix(updateinformation,"bintray-zsync|"))
613 if(!g_str_has_prefix(updateinformation,"gh-releases-zsync|"))
614 die("The provided updateinformation is not in a recognized format");
616 gchar **ui_type = g_strsplit_set(updateinformation, "|", -1);
618 if(verbose)
619 printf("updateinformation type: %s\n", ui_type[0]);
620 /* TODO: Further checking of the updateinformation */
623 unsigned long ui_offset = 0;
624 unsigned long ui_length = 0;
625 get_elf_section_offset_and_lenghth(destination, ".upd_info", &ui_offset, &ui_length);
626 if(verbose) {
627 printf("ui_offset: %lu\n", ui_offset);
628 printf("ui_length: %lu\n", ui_length);
630 if(ui_offset == 0) {
631 die("Could not determine offset for updateinformation");
632 } else {
633 if(strlen(updateinformation)>ui_length)
634 die("updateinformation does not fit into segment, aborting");
635 FILE *fpdst2 = fopen(destination, "r+");
636 if (fpdst2 == NULL)
637 die("Not able to open the destination file for writing, aborting");
638 fseek(fpdst2, ui_offset, SEEK_SET);
639 // fseek(fpdst2, ui_offset, SEEK_SET);
640 // fwrite(0x00, 1, 1024, fpdst); // FIXME: Segfaults; why?
641 // fseek(fpdst, ui_offset, SEEK_SET);
642 fwrite(updateinformation, strlen(updateinformation), 1, fpdst2);
643 fclose(fpdst2);
647 if(sign){
648 /* The user has indicated that he wants to sign */
649 gchar *gpg2_path = g_find_program_in_path ("gpg2");
650 gchar *sha256sum_path = g_find_program_in_path ("sha256sum");
651 if(!gpg2_path){
652 fprintf (stderr, "gpg2 is not installed, cannot sign\n");
654 else if(!sha256sum_path){
655 fprintf (stderr, "sha256sum is not installed, cannot sign\n");
656 } else {
657 fprintf (stderr, "gpg2 and sha256sum are installed and user requested to sign, "
658 "hence signing\n");
659 char *digestfile;
660 digestfile = br_strcat(destination, ".digest");
661 char *ascfile;
662 ascfile = br_strcat(destination, ".digest.asc");
663 if (g_file_test (digestfile, G_FILE_TEST_IS_REGULAR))
664 unlink(digestfile);
665 sprintf (command, "%s %s", sha256sum_path, destination);
666 if(verbose)
667 fprintf (stderr, "%s\n", command);
668 fp = popen(command, "r");
669 if (fp == NULL)
670 die("sha256sum command did not succeed");
671 char output[1024];
672 fgets(output, sizeof(output)-1, fp);
673 if(verbose)
674 printf("sha256sum: %s\n", g_strsplit_set(output, " ", -1)[0]);
675 FILE *fpx = fopen(digestfile, "w");
676 if (fpx != NULL)
678 fputs(g_strsplit_set(output, " ", -1)[0], fpx);
679 fclose(fpx);
681 int exitstatus = pclose(fp);
682 if(WEXITSTATUS(exitstatus) != 0)
683 die("sha256sum command did not succeed");
684 if (g_file_test (ascfile, G_FILE_TEST_IS_REGULAR))
685 unlink(ascfile);
686 sprintf (command, "%s --detach-sign --armor %s", gpg2_path, digestfile);
687 if(verbose)
688 fprintf (stderr, "%s\n", command);
689 fp = popen(command, "r");
690 exitstatus = pclose(fp);
691 if(WEXITSTATUS(exitstatus) != 0) {
692 fprintf (stderr, "ERROR: gpg2 command did not succeed, could not sign, continuing\n");
693 } else {
694 unsigned long sig_offset = 0;
695 unsigned long sig_length = 0;
696 get_elf_section_offset_and_lenghth(destination, ".sha256_sig", &sig_offset, &sig_length);
697 if(verbose) {
698 printf("sig_offset: %lu\n", sig_offset);
699 printf("sig_length: %lu\n", sig_length);
701 if(sig_offset == 0) {
702 die("Could not determine offset for signature");
703 } else {
704 FILE *fpdst3 = fopen(destination, "r+");
705 if (fpdst3 == NULL)
706 die("Not able to open the destination file for writing, aborting");
707 // if(strlen(updateinformation)>sig_length)
708 // die("signature does not fit into segment, aborting");
709 fseek(fpdst3, sig_offset, SEEK_SET);
710 FILE *fpsrc2 = fopen(ascfile, "rb");
711 if (fpsrc2 == NULL) {
712 die("Not able to open the asc file for reading, aborting");
714 char byte;
715 while (!feof(fpsrc2))
717 fread(&byte, sizeof(char), 1, fpsrc2);
718 fwrite(&byte, sizeof(char), 1, fpdst3);
720 fclose(fpsrc2);
721 fclose(fpdst3);
723 if (g_file_test (ascfile, G_FILE_TEST_IS_REGULAR))
724 unlink(ascfile);
725 if (g_file_test (digestfile, G_FILE_TEST_IS_REGULAR))
726 unlink(digestfile);
731 /* If updateinformation was provided, then we also generate the zsync file (after having signed the AppImage) */
732 if(updateinformation != NULL){
733 gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake");
734 if(!zsyncmake_path){
735 fprintf (stderr, "zsyncmake is not installed, skipping\n");
736 } else {
737 fprintf (stderr, "zsyncmake is installed and updateinformation is provided, "
738 "hence generating zsync file\n");
739 sprintf (command, "%s %s -u %s", zsyncmake_path, destination, basename(destination));
740 if(verbose)
741 fprintf (stderr, "%s\n", command);
742 fp = popen(command, "r");
743 if (fp == NULL)
744 die("Failed to run zsyncmake command");
745 int exitstatus = pclose(fp);
746 if (WEXITSTATUS(exitstatus) != 0)
747 die("zsyncmake command did not succeed");
751 fprintf (stderr, "Success\n");
754 /* If the first argument is a regular file, then we assume that we should unpack it */
755 if (g_file_test (remaining_args[0], G_FILE_TEST_IS_REGULAR)){
756 fprintf (stdout, "%s is a file, assuming it is an AppImage and should be unpacked\n", remaining_args[0]);
757 die("To be implemented");
760 return 0;