1 /**************************************************************************
3 * Copyright (c) 2004-17 Simon Peter
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
25 **************************************************************************/
27 #ident "AppImage by Simon Peter, http://appimage.org/"
30 #include <glib/gstdio.h>
38 #include "squashfuse.h"
40 #include <sys/types.h>
52 #include "getsection.h"
54 extern int _binary_runtime_start
;
55 extern int _binary_runtime_end
;
58 static gboolean list
= FALSE
;
59 static gboolean verbose
= FALSE
;
60 static gboolean version
= FALSE
;
61 static gboolean sign
= FALSE
;
62 static gboolean no_appstream
= FALSE
;
63 gchar
**remaining_args
= NULL
;
64 gchar
*updateinformation
= NULL
;
65 gchar
*bintray_user
= NULL
;
66 gchar
*bintray_repo
= NULL
;
67 gchar
*sqfs_comp
= "gzip";
69 // #####################################################################
71 static void die(const char *msg
) {
72 fprintf(stderr
, "%s\n", msg
);
76 /* Function that prints the contents of a squashfs file
77 * using libsquashfuse (#include "squashfuse.h") */
78 int sfs_ls(char* image
) {
79 sqfs_err err
= SQFS_OK
;
83 unsigned long fs_offset
= get_elf_size(image
);
85 if ((err
= sqfs_open_image(&fs
, image
, fs_offset
)))
86 die("sqfs_open_image error");
88 if ((err
= sqfs_traverse_open(&trv
, &fs
, sqfs_inode_root(&fs
))))
89 die("sqfs_traverse_open error");
90 while (sqfs_traverse_next(&trv
, &err
)) {
92 printf("%s\n", trv
.path
);
96 die("sqfs_traverse_next error");
97 sqfs_traverse_close(&trv
);
103 /* Generate a squashfs filesystem using mksquashfs on the $PATH
104 * execlp(), execvp(), and execvpe() search on the $PATH */
105 int sfs_mksquashfs(char *source
, char *destination
, int offset
) {
109 // error, failed to fork()
111 } else if (pid
> 0) {
113 waitpid(pid
, &status
, 0);
116 gchar
*offset_string
;
117 offset_string
= g_strdup_printf("%i", offset
);
118 if(0==strcmp("xz", sqfs_comp
))
120 // https://jonathancarter.org/2015/04/06/squashfs-performance-testing/ says:
121 // improved performance by using a 16384 block size with a sacrifice of around 3% more squashfs image space
122 execlp("mksquashfs", "mksquashfs", source
, destination
, "-offset", offset_string
, "-comp", "xz", "-root-owned", "-noappend", "-Xdict-size", "100%", "-b", "16384", "-no-xattrs", "-root-owned", NULL
);
124 execlp("mksquashfs", "mksquashfs", source
, destination
, "-offset", offset_string
, "-comp", sqfs_comp
, "-root-owned", "-noappend", "-no-xattrs", "-root-owned", NULL
);
126 perror("execlp"); // execlp() returns only on error
127 return(-1); // exec never returns
132 /* Generate a squashfs filesystem
133 * The following would work if we link to mksquashfs.o after we renamed
134 * main() to mksquashfs_main() in mksquashfs.c but we don't want to actually do
135 * this because squashfs-tools is not under a permissive license
136 * i *nt sfs_mksquashfs(char *source, char *destination) {
137 * char *child_argv[5];
138 * child_argv[0] = NULL;
139 * child_argv[1] = source;
140 * child_argv[2] = destination;
141 * child_argv[3] = "-root-owned";
142 * child_argv[4] = "-noappend";
143 * mksquashfs_main(5, child_argv);
147 gchar
* find_first_matching_file(const gchar
*real_path
, const gchar
*pattern
) {
151 dir
= g_dir_open(real_path
, 0, NULL
);
154 while ((entry
= g_dir_read_name(dir
)) != NULL
) {
155 full_name
= g_build_filename(real_path
, entry
, NULL
);
156 if (! g_file_test(full_name
, G_FILE_TEST_IS_DIR
)) {
157 if(g_pattern_match_simple(pattern
, entry
))
161 resulting
= find_first_matching_file(full_name
, pattern
);
169 g_warning("%s: %s", real_path
, g_strerror(errno
));
174 gchar
* find_first_matching_file_nonrecursive(const gchar
*real_path
, const gchar
*pattern
) {
177 dir
= g_dir_open(real_path
, 0, NULL
);
180 while ((entry
= g_dir_read_name(dir
)) != NULL
) {
181 full_name
= g_build_filename(real_path
, entry
, NULL
);
182 if (g_file_test(full_name
, G_FILE_TEST_IS_REGULAR
)) {
183 if(g_pattern_match_simple(pattern
, entry
))
190 g_warning("%s: %s", real_path
, g_strerror(errno
));
195 gchar
* get_desktop_entry(GKeyFile
*kf
, char *key
) {
196 gchar
*value
= g_key_file_get_string (kf
, "Desktop Entry", key
, NULL
);
198 fprintf(stderr
, "%s entry not found in desktop file\n", key
);
203 /* in-place modification of the string, and assuming the buffer pointed to by
204 * line is large enough to hold the resulting string*/
205 static void replacestr(char *line
, const char *search
, const char *replace
)
209 if ((sp
= strstr(line
, search
)) == NULL
) {
212 int search_len
= strlen(search
);
213 int replace_len
= strlen(replace
);
214 int tail_len
= strlen(sp
+search_len
);
216 memmove(sp
+replace_len
,sp
+search_len
,tail_len
+1);
217 memcpy(sp
, replace
, replace_len
);
219 /* Do it recursively again until no more work to do */
221 if ((sp
= strstr(line
, search
))) {
222 replacestr(line
, search
, replace
);
226 // #####################################################################
228 static GOptionEntry entries
[] =
230 { "list", 'l', 0, G_OPTION_ARG_NONE
, &list
, "List files in SOURCE AppImage", NULL
},
231 { "updateinformation", 'u', 0, G_OPTION_ARG_STRING
, &updateinformation
, "Embed update information STRING; if zsyncmake is installed, generate zsync file", NULL
},
232 { "bintray-user", 0, 0, G_OPTION_ARG_STRING
, &bintray_user
, "Bintray user name", NULL
},
233 { "bintray-repo", 0, 0, G_OPTION_ARG_STRING
, &bintray_repo
, "Bintray repository", NULL
},
234 { "version", 0, 0, G_OPTION_ARG_NONE
, &version
, "Show version number", NULL
},
235 { "verbose", 'v', 0, G_OPTION_ARG_NONE
, &verbose
, "Produce verbose output", NULL
},
236 { "sign", 's', 0, G_OPTION_ARG_NONE
, &sign
, "Sign with gpg2", NULL
},
237 { "comp", 0, 0, G_OPTION_ARG_STRING
, &sqfs_comp
, "Squashfs compression", NULL
},
238 { "no-appstream", 'n', 0, G_OPTION_ARG_NONE
, &no_appstream
, "Do not check AppStream metadata", NULL
},
239 { G_OPTION_REMAINING
, 0, 0, G_OPTION_ARG_FILENAME_ARRAY
, &remaining_args
, NULL
, NULL
},
244 main (int argc
, char *argv
[])
246 /* Parse VERSION environment variable.
247 * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */
249 version_env
= getenv("VERSION");
251 /* Parse OWD environment variable.
252 * If it is available then cd there. It is the original CWD prior to running AppRun */
253 char* owd_env
= NULL
;
254 owd_env
= getenv("OWD");
257 ret
= chdir(owd_env
);
259 fprintf(stderr
, "Could not cd into %s\n", owd_env
);
265 GError
*error
= NULL
;
266 GOptionContext
*context
;
267 char command
[PATH_MAX
];
269 context
= g_option_context_new ("SOURCE [DESTINATION] - Generate, extract, and inspect AppImages");
270 g_option_context_add_main_entries (context
, entries
, NULL
);
271 // g_option_context_add_group (context, gtk_get_option_group (TRUE));
272 if (!g_option_context_parse (context
, &argc
, &argv
, &error
))
274 fprintf(stderr
, "Option parsing failed: %s\n", error
->message
);
279 fprintf(stderr
,"Version: %s\n", VERSION_NUMBER
);
283 if(!((0 == strcmp(sqfs_comp
, "gzip")) || (0 ==strcmp(sqfs_comp
, "xz"))))
284 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.");
285 /* Check for dependencies here. Better fail early if they are not present. */
286 if(! g_find_program_in_path ("mksquashfs"))
287 die("mksquashfs is missing but required, please install it");
288 if(! g_find_program_in_path ("zsyncmake"))
289 g_print("WARNING: zsyncmake is missing, please install it if you want to use binary delta updates\n");
291 if(! g_find_program_in_path ("appstreamcli"))
292 g_print("WARNING: appstreamcli is missing, please install it if you want to use AppStream metadata\n");
293 if(! g_find_program_in_path ("gpg2"))
294 g_print("WARNING: gpg2 is missing, please install it if you want to create digital signatures\n");
295 if(! g_find_program_in_path ("sha256sum"))
296 g_print("WARNING: sha256sum is missing, please install it if you want to create digital signatures\n");
298 if(!&remaining_args
[0])
299 die("SOURCE is missing");
301 /* If in list mode */
303 sfs_ls(remaining_args
[0]);
307 /* If the first argument is a directory, then we assume that we should package it */
308 if (g_file_test (remaining_args
[0], G_FILE_TEST_IS_DIR
)){
310 char source
[PATH_MAX
];
311 realpath(remaining_args
[0], source
);
313 /* Check if *.desktop file is present in source AppDir */
314 gchar
*desktop_file
= find_first_matching_file_nonrecursive(source
, "*.desktop");
315 if(desktop_file
== NULL
){
316 die("$ID.desktop file not found");
319 fprintf (stdout
, "Desktop file: %s\n", desktop_file
);
321 /* Read information from .desktop file */
322 GKeyFile
*kf
= g_key_file_new ();
323 if (!g_key_file_load_from_file (kf
, desktop_file
, 0, NULL
))
324 die(".desktop file cannot be parsed");
327 fprintf (stderr
,"Name: %s\n", get_desktop_entry(kf
, "Name"));
328 fprintf (stderr
,"Icon: %s\n", get_desktop_entry(kf
, "Icon"));
329 fprintf (stderr
,"Exec: %s\n", get_desktop_entry(kf
, "Exec"));
330 fprintf (stderr
,"Comment: %s\n", get_desktop_entry(kf
, "Comment"));
331 fprintf (stderr
,"Type: %s\n", get_desktop_entry(kf
, "Type"));
332 fprintf (stderr
,"Categories: %s\n", get_desktop_entry(kf
, "Categories"));
335 /* Determine the architecture */
336 gchar
*arch
= getenv("ARCH");
338 /* If no $ARCH variable is set check a file */
340 gchar
*archfile
= NULL
;
341 /* We use the next best .so that we can find to determine the architecture */
342 archfile
= find_first_matching_file(source
, "*.so.*");
345 /* If we found no .so we try to guess the main executable - this might be a script though */
346 // char guessed_bin_path[PATH_MAX];
347 // sprintf (guessed_bin_path, "%s/usr/bin/%s", source, g_strsplit_set(get_desktop_entry(kf, "Exec"), " ", -1)[0]);
348 // archfile = guessed_bin_path;
349 archfile
= "/proc/self/exe";
352 fprintf (stderr
,"File used for determining architecture: %s\n", archfile
);
355 char command
[PATH_MAX
];
356 sprintf (command
, "/usr/bin/file -L -N -b %s", archfile
);
357 fp
= popen(command
, "r");
359 die("Failed to run file command");
360 fgets(line
, sizeof(line
)-1, fp
);
361 arch
= g_strstrip(g_strsplit_set(line
, ",", -1)[1]);
362 replacestr(arch
, "-", "_");
363 fprintf (stderr
,"Arch: %s\n", arch
+1);
368 printf("The architecture could not be determined, assuming 'all'\n");
373 char app_name_for_filename
[PATH_MAX
];
374 sprintf(app_name_for_filename
, "%s", get_desktop_entry(kf
, "Name"));
375 replacestr(app_name_for_filename
, " ", "_");
378 fprintf (stderr
,"App name for filename: %s\n", app_name_for_filename
);
380 if (remaining_args
[1]) {
381 destination
= remaining_args
[1];
383 /* No destination has been specified, to let's construct one
384 * TODO: Find out the architecture and use a $VERSION that might be around in the env */
385 char dest_path
[PATH_MAX
];
386 sprintf (dest_path
, "%s-%s.AppImage", app_name_for_filename
, arch
);
389 fprintf (stderr
,"dest_path: %s\n", dest_path
);
391 if (version_env
!=NULL
)
392 sprintf (dest_path
, "%s-%s-%s.AppImage", app_name_for_filename
, version_env
, arch
);
394 destination
= dest_path
;
395 replacestr(destination
, " ", "_");
397 // destination = basename(br_strcat(source, ".AppImage"));
398 fprintf (stdout
, "DESTINATION not specified, so assuming %s\n", destination
);
400 fprintf (stdout
, "%s should be packaged as %s\n", source
, destination
);
401 /* Check if the Icon file is how it is expected */
402 gchar
* icon_name
= get_desktop_entry(kf
, "Icon");
403 gchar
* icon_file_path
= NULL
;
404 gchar
* icon_file_png
;
405 gchar
* icon_file_svg
;
406 gchar
* icon_file_svgz
;
407 gchar
* icon_file_xpm
;
408 icon_file_png
= g_strdup_printf("%s/%s.png", source
, icon_name
);
409 icon_file_svg
= g_strdup_printf("%s/%s.svg", source
, icon_name
);
410 icon_file_svgz
= g_strdup_printf("%s/%s.svgz", source
, icon_name
);
411 icon_file_xpm
= g_strdup_printf("%s/%s.xpm", source
, icon_name
);
412 if (g_file_test(icon_file_png
, G_FILE_TEST_IS_REGULAR
)) {
413 icon_file_path
= icon_file_png
;
414 } else if(g_file_test(icon_file_svg
, G_FILE_TEST_IS_REGULAR
)) {
415 icon_file_path
= icon_file_svg
;
416 } else if(g_file_test(icon_file_svgz
, G_FILE_TEST_IS_REGULAR
)) {
417 icon_file_path
= icon_file_svgz
;
418 } else if(g_file_test(icon_file_xpm
, G_FILE_TEST_IS_REGULAR
)) {
419 icon_file_path
= icon_file_xpm
;
421 fprintf (stderr
, "%s{.png,.svg,.svgz,.xpm} not present but defined in desktop file\n", icon_name
);
425 /* Check if .DirIcon is present in source AppDir */
426 gchar
*diricon_path
= g_build_filename(source
, ".DirIcon", NULL
);
428 if (! g_file_test(diricon_path
, G_FILE_TEST_EXISTS
)){
429 fprintf (stderr
, "Deleting pre-existing .DirIcon\n");
430 g_unlink(diricon_path
);
432 if (! g_file_test(diricon_path
, G_FILE_TEST_IS_REGULAR
)){
433 fprintf (stderr
, "Creating .DirIcon symlink based on information from desktop file\n");
434 int res
= symlink(basename(icon_file_path
), diricon_path
);
436 die("Could not symlink .DirIcon");
439 /* Check if AppStream upstream metadata is present in source AppDir */
441 char application_id
[PATH_MAX
];
442 sprintf (application_id
, "%s", basename(desktop_file
));
443 replacestr(application_id
, ".desktop", ".appdata.xml");
444 gchar
*appdata_path
= g_build_filename(source
, "/usr/share/metainfo/", application_id
, NULL
);
445 if (! g_file_test(appdata_path
, G_FILE_TEST_IS_REGULAR
)){
446 fprintf (stderr
, "WARNING: AppStream upstream metadata is missing, please consider creating it\n");
447 fprintf (stderr
, " in usr/share/metainfo/%s\n", application_id
);
448 fprintf (stderr
, " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps\n");
449 fprintf (stderr
, " for more information.\n");
450 /* As a courtesy, generate one to be filled by the user */
451 if(g_find_program_in_path ("appstream-util")) {
452 gchar
*appdata_dir
= g_build_filename(source
, "/usr/share/metainfo/", NULL
);
453 g_mkdir_with_parents(appdata_dir
, 0755);
454 sprintf (command
, "%s appdata-from-desktop %s %s", g_find_program_in_path ("appstream-util"), desktop_file
, appdata_path
);
455 int ret
= system(command
);
457 die("Failed to generate AppStream template");
458 fprintf (stderr
, "AppStream template has been generated in in %s, please edit it\n", appdata_path
);
462 fprintf (stderr
, "AppStream upstream metadata found in usr/share/metainfo/%s\n", application_id
);
463 /* Use ximion's appstreamcli to make sure that desktop file and appdata match together */
464 if(g_find_program_in_path ("appstreamcli")) {
465 sprintf (command
, "%s validate-tree %s", g_find_program_in_path ("appstreamcli"), source
);
466 int ret
= system(command
);
468 die("Failed to validate AppStream information with appstreamcli");
470 /* It seems that hughsie's appstream-util does additional validations */
471 if(g_find_program_in_path ("appstream-util")) {
472 sprintf (command
, "%s validate-relax %s", g_find_program_in_path ("appstream-util"), appdata_path
);
473 int ret
= system(command
);
475 die("Failed to validate AppStream information with appstream-util");
480 /* Upstream mksquashfs can currently not start writing at an offset,
481 * so we need a patched one. https://github.com/plougher/squashfs-tools/pull/13
482 * should hopefully change that. */
484 fprintf (stderr
, "Generating squashfs...\n");
485 /* runtime is embedded into this executable
486 * http://stupefydeveloper.blogspot.de/2008/08/cc-embed-binary-data-into-elf.html */
487 int size
= (int)((void *)&_binary_runtime_end
- (void *)&_binary_runtime_start
);
488 char *data
= (char *)&_binary_runtime_start
;
490 printf("Size of the embedded runtime: %d bytes\n", size
);
492 int result
= sfs_mksquashfs(source
, destination
, size
);
494 die("sfs_mksquashfs error");
496 fprintf (stderr
, "Embedding ELF...\n");
497 FILE *fpdst
= fopen(destination
, "rb+");
499 die("Not able to open the AppImage for writing, aborting");
502 fseek(fpdst
, 0, SEEK_SET
);
503 fwrite(data
, size
, 1, fpdst
);
506 fprintf (stderr
, "Marking the AppImage as executable...\n");
507 if (chmod (destination
, 0755) < 0) {
508 printf("Could not set executable bit, aborting\n");
512 if(bintray_user
!= NULL
){
513 if(bintray_repo
!= NULL
){
515 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
);
516 updateinformation
= buf
;
517 printf("%s\n", updateinformation
);
521 /* If updateinformation was provided, then we check and embed it */
522 if(updateinformation
!= NULL
){
523 if(!g_str_has_prefix(updateinformation
,"zsync|"))
524 if(!g_str_has_prefix(updateinformation
,"bintray-zsync|"))
525 die("The provided updateinformation is not in a recognized format");
527 gchar
**ui_type
= g_strsplit_set(updateinformation
, "|", -1);
530 printf("updateinformation type: %s\n", ui_type
[0]);
531 /* TODO: Further checking of the updateinformation */
534 unsigned long ui_offset
= 0;
535 unsigned long ui_length
= 0;
536 get_elf_section_offset_and_lenghth(destination
, ".upd_info", &ui_offset
, &ui_length
);
538 printf("ui_offset: %lu\n", ui_offset
);
539 printf("ui_length: %lu\n", ui_length
);
542 die("Could not determine offset for updateinformation");
544 if(strlen(updateinformation
)>ui_length
)
545 die("updateinformation does not fit into segment, aborting");
546 FILE *fpdst2
= fopen(destination
, "r+");
548 die("Not able to open the destination file for writing, aborting");
549 fseek(fpdst2
, ui_offset
, SEEK_SET
);
550 // fseek(fpdst2, ui_offset, SEEK_SET);
551 // fwrite(0x00, 1, 1024, fpdst); // FIXME: Segfaults; why?
552 // fseek(fpdst, ui_offset, SEEK_SET);
553 fwrite(updateinformation
, strlen(updateinformation
), 1, fpdst2
);
559 /* The user has indicated that he wants to sign */
560 gchar
*gpg2_path
= g_find_program_in_path ("gpg2");
561 gchar
*sha256sum_path
= g_find_program_in_path ("sha256sum");
563 fprintf (stderr
, "gpg2 is not installed, cannot sign\n");
565 else if(!sha256sum_path
){
566 fprintf (stderr
, "sha256sum is not installed, cannot sign\n");
568 fprintf (stderr
, "gpg2 and sha256sum are installed and user requested to sign, "
571 digestfile
= br_strcat(destination
, ".digest");
573 ascfile
= br_strcat(destination
, ".digest.asc");
574 if (g_file_test (digestfile
, G_FILE_TEST_IS_REGULAR
))
576 sprintf (command
, "%s %s", sha256sum_path
, destination
);
578 fprintf (stderr
, "%s\n", command
);
579 fp
= popen(command
, "r");
581 die("sha256sum command did not succeed");
583 fgets(output
, sizeof(output
)-1, fp
);
585 printf("sha256sum: %s\n", g_strsplit_set(output
, " ", -1)[0]);
586 FILE *fpx
= fopen(digestfile
, "w");
589 fputs(g_strsplit_set(output
, " ", -1)[0], fpx
);
592 if(WEXITSTATUS(pclose(fp
)) != 0)
593 die("sha256sum command did not succeed");
594 if (g_file_test (ascfile
, G_FILE_TEST_IS_REGULAR
))
596 sprintf (command
, "%s --detach-sign --armor %s", gpg2_path
, digestfile
);
598 fprintf (stderr
, "%s\n", command
);
599 fp
= popen(command
, "r");
600 if(WEXITSTATUS(pclose(fp
)) != 0) {
601 fprintf (stderr
, "ERROR: gpg2 command did not succeed, could not sign, continuing\n");
603 unsigned long sig_offset
= 0;
604 unsigned long sig_length
= 0;
605 get_elf_section_offset_and_lenghth(destination
, ".sha256_sig", &sig_offset
, &sig_length
);
607 printf("sig_offset: %lu\n", sig_offset
);
608 printf("sig_length: %lu\n", sig_length
);
610 if(sig_offset
== 0) {
611 die("Could not determine offset for signature");
613 FILE *fpdst3
= fopen(destination
, "r+");
615 die("Not able to open the destination file for writing, aborting");
616 // if(strlen(updateinformation)>sig_length)
617 // die("signature does not fit into segment, aborting");
618 fseek(fpdst3
, sig_offset
, SEEK_SET
);
619 FILE *fpsrc2
= fopen(ascfile
, "rb");
620 if (fpsrc2
== NULL
) {
621 die("Not able to open the asc file for reading, aborting");
624 while (!feof(fpsrc2
))
626 fread(&byte
, sizeof(char), 1, fpsrc2
);
627 fwrite(&byte
, sizeof(char), 1, fpdst3
);
632 if (g_file_test (ascfile
, G_FILE_TEST_IS_REGULAR
))
634 if (g_file_test (digestfile
, G_FILE_TEST_IS_REGULAR
))
640 /* If updateinformation was provided, then we also generate the zsync file (after having signed the AppImage) */
641 if(updateinformation
!= NULL
){
642 gchar
*zsyncmake_path
= g_find_program_in_path ("zsyncmake");
644 fprintf (stderr
, "zsyncmake is not installed, skipping\n");
646 fprintf (stderr
, "zsyncmake is installed and updateinformation is provided, "
647 "hence generating zsync file\n");
648 sprintf (command
, "%s %s -u %s", zsyncmake_path
, destination
, basename(destination
));
650 fprintf (stderr
, "%s\n", command
);
651 fp
= popen(command
, "r");
653 die("Failed to run zsyncmake command");
657 fprintf (stderr
, "Success\n");
660 /* If the first argument is a regular file, then we assume that we should unpack it */
661 if (g_file_test (remaining_args
[0], G_FILE_TEST_IS_REGULAR
)){
662 fprintf (stdout
, "%s is a file, assuming it is an AppImage and should be unpacked\n", remaining_args
[0]);
663 die("To be implemented");