diff --git a/.gitignore b/.gitignore index 15fb151..138dd75 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ cmake-build-*/ *.*swp* *.bak *.AppImage* +.vscode/ \ No newline at end of file diff --git a/ci/build.sh b/ci/build.sh index e7b06b9..951a0b2 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -41,7 +41,7 @@ cp "$(which mksquashfs)" AppDir/usr/bin cp "$repo_root"/resources/AppRun.sh AppDir/AppRun chmod +x AppDir/AppRun -wget https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-"$ARCH" +# wget https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-"$ARCH" pushd AppDir ln -s usr/share/applications/appimagetool.desktop . @@ -52,6 +52,7 @@ popd find AppDir cat AppDir/appimagetool.desktop -AppDir/AppRun --runtime-file runtime-"$ARCH" AppDir +# AppDir/AppRun --runtime-file runtime-"$ARCH" AppDir --verbose +AppDir/AppRun AppDir --verbose mv appimagetool-*.AppImage "$old_cwd" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e9365a7..f21186f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,13 +1,23 @@ add_executable(appimagetool - appimagetool.c + appimagetool.cpp + appimagetool_fetch_runtime.cpp + appimagetool_fetch_runtime.h appimagetool_sign.c - appimagetool_fetch_runtime.c - hexlify.c - elf.c + appimagetool_sign.h digest.c + elf.c + hexlify.c + light_byteswap.h + light_elf.h md5.c + md5.h + util.h ) +# Ensure that the C compiler, not the C++ compiler, is used for compiling C files +set_source_files_properties(digest.c elf.c hexlify.c md5.c appimagetool_sign.c PROPERTIES LANGUAGE C) +set(CMAKE_C_COMPILER gcc) + # trick: list libraries on which imported static ones depend on in the PUBLIC section # CMake then adds them after the PRIVATE ones in the linker command target_link_libraries(appimagetool @@ -19,6 +29,7 @@ target_link_libraries(appimagetool PkgConfig::libcurl ) + target_compile_definitions(appimagetool PRIVATE -D_FILE_OFFSET_BITS=64 PRIVATE -DGIT_VERSION="${GIT_VERSION}" @@ -49,3 +60,4 @@ install( FILES "${PROJECT_SOURCE_DIR}/resources/appimagetool.appdata.xml" DESTINATION share/metainfo ) + diff --git a/src/appimagetool.c b/src/appimagetool.cpp similarity index 68% rename from src/appimagetool.c rename to src/appimagetool.cpp index 74f5d15..0de29fa 100644 --- a/src/appimagetool.c +++ b/src/appimagetool.cpp @@ -32,8 +32,10 @@ #include +// TODO: Remove glib dependency in favor of C++ #include #include +#include #include #include @@ -57,6 +59,15 @@ #include "appimagetool_fetch_runtime.h" #include "appimagetool_sign.h" +#include "elf.h" + +// C++ +#include +#include +#include +#include +#include +#include typedef enum { fARCH_i686, @@ -68,15 +79,15 @@ typedef enum { static gchar const APPIMAGEIGNORE[] = ".appimageignore"; static char _exclude_file_desc[256]; -static gboolean list = FALSE; -static gboolean verbose = FALSE; -static gboolean showVersionOnly = FALSE; -static gboolean sign = FALSE; -static gboolean no_appstream = FALSE; +static bool list = false; +static bool verbose = false; +static bool showVersionOnly = false; +static bool sign = false; +static bool no_appstream = false; gchar **remaining_args = NULL; gchar *updateinformation = NULL; -static gboolean guess_update_information = FALSE; -gchar *sqfs_comp = NULL; +static bool guess_update_information = false; +std::string sqfs_comp = "zstd"; gchar **sqfs_opts = NULL; gchar *exclude_file = NULL; gchar *runtime_file = NULL; @@ -86,7 +97,7 @@ gchar *pathToMksquashfs = NULL; // ##################################################################### static void die(const char *msg) { - fprintf(stderr, "%s\n", msg); + std::cerr << msg << std::endl; exit(1); } @@ -109,69 +120,65 @@ int sfs_mksquashfs(char *source, char *destination, int offset) { int retcode = WEXITSTATUS(status); if (retcode) { - fprintf(stderr, "mksquashfs (pid %d) exited with code %d\n", pid, retcode); + std::cerr << "mksquashfs (pid " << pid << ") exited with code " << retcode << std::endl; return(-1); } return 0; } else { // we are the child - gchar* offset_string; - offset_string = g_strdup_printf("%i", offset); + std::string offset_string = std::to_string(offset); - guint sqfs_opts_len = sqfs_opts ? g_strv_length(sqfs_opts) : 0; + unsigned int sqfs_opts_len = sqfs_opts ? g_strv_length(sqfs_opts) : 0; int max_num_args = sqfs_opts_len + 22; - char* args[max_num_args]; + std::vector args; - int i = 0; -#ifndef AUXILIARY_FILES_DESTINATION - args[i++] = "mksquashfs"; -#else - args[i++] = pathToMksquashfs; -#endif - args[i++] = source; - args[i++] = destination; - args[i++] = "-offset"; - args[i++] = offset_string; + #ifndef AUXILIARY_FILES_DESTINATION + args.push_back("mksquashfs"); + #else + args.push_back(pathToMksquashfs); + #endif + args.push_back(source); + args.push_back(destination); + args.push_back("-offset"); + args.push_back(offset_string); - if (sqfs_comp == NULL) { - sqfs_comp = "zstd"; - } + int i = args.size(); - args[i++] = "-comp"; - args[i++] = sqfs_comp; + args.push_back("-comp"); + args.push_back(sqfs_comp); - args[i++] = "-root-owned"; - args[i++] = "-noappend"; + args.push_back("-root-owned"); + args.push_back("-noappend"); // compression-specific optimization - if (strcmp(sqfs_comp, "xz") == 0) { + if (sqfs_comp == "xz") { // https://jonathancarter.org/2015/04/06/squashfs-performance-testing/ says: // improved performance by using a 16384 block size with a sacrifice of around 3% more squashfs image space - args[i++] = "-Xdict-size"; - args[i++] = "100%"; - args[i++] = "-b"; - args[i++] = "16384"; - } else if (strcmp(sqfs_comp, "zstd") == 0) { + args.push_back("-Xdict-size"); + args.push_back("100%"); + args.push_back("-b"); + args.push_back("16384"); + } else if (sqfs_comp == "zstd") { /* * > Build with 1MiB block size * > Using a bigger block size than mksquashfs's default improves read speed and can produce smaller AppImages as well * -- https://github.com/probonopd/go-appimage/commit/c4a112e32e8c2c02d1d388c8fa45a9222a529af3 */ - args[i++] = "-b"; - args[i++] = "1M"; + args.push_back("-b"); + args.push_back("1M"); } // check if ignore file exists and use it if possible if (access(APPIMAGEIGNORE, F_OK) >= 0) { printf("Including %s", APPIMAGEIGNORE); - args[i++] = "-wildcards"; - args[i++] = "-ef"; + args.push_back("-wildcards"); + args.push_back("-ef"); // avoid warning: assignment discards ‘const’ qualifier char* buf = strdup(APPIMAGEIGNORE); - args[i++] = buf; + args.push_back(buf); } // if an exclude file has been passed on the command line, should be used, too @@ -181,40 +188,35 @@ int sfs_mksquashfs(char *source, char *destination, int offset) { return -1; } - args[i++] = "-wildcards"; - args[i++] = "-ef"; - args[i++] = exclude_file; + args.push_back("-wildcards"); + args.push_back("-ef"); + args.push_back(exclude_file); + } - args[i++] = "-mkfs-time"; - args[i++] = "0"; + args.push_back("-mkfs-time"); + args.push_back("0"); - for (guint sqfs_opts_idx = 0; sqfs_opts_idx < sqfs_opts_len; ++sqfs_opts_idx) { + for (unsigned int sqfs_opts_idx = 0; sqfs_opts_idx < sqfs_opts_len; ++sqfs_opts_idx) { args[i++] = sqfs_opts[sqfs_opts_idx]; } - - args[i++] = 0; - - if (verbose) { - printf("mksquashfs commandline: "); - for (char** t = args; *t != 0; t++) { - printf("%s ", *t); + // Build the command string + std::stringstream cmdStream; + for (const auto& arg : args) { + // Quote each argument to handle spaces and special characters + cmdStream << "\"" << arg << "\" "; + } + if (verbose) { + std::cout << "Executing command: " << cmdStream.str() << std::endl; + } + if (std::system(cmdStream.str().c_str()) != 0) { + std::cerr << "ERROR: Failed to execute command: " << cmdStream.str() << std::endl; + exit(1); + } } - printf("\n"); + return 0; } -#ifndef AUXILIARY_FILES_DESTINATION - execvp("mksquashfs", args); - perror("execvp(\"mksquashfs\") failed"); -#else - execvp(pathToMksquashfs, args); - fprintf(stderr, "execvp(\"%s\") failed: %s\n", pathToMksquashfs, strerror(errno)); -#endif - return -1; // exec never returns - } - return 0; -} - /* Validate desktop file using desktop-file-validate on the $PATH * execlp(), execvp(), and execvpe() search on the $PATH */ int validate_desktop_file(char *file) { @@ -287,7 +289,7 @@ int count_archs(bool* archs) { return countArchs; } -gchar* archToName(fARCH arch) { +std::string archToName(fARCH arch) { switch (arch) { case fARCH_aarch64: return "aarch64"; @@ -300,7 +302,7 @@ gchar* archToName(fARCH arch) { } } -gchar* getArchName(bool* archs) { +std::string getArchName(bool* archs) { if (archs[fARCH_i686]) return archToName(fARCH_i686); else if (archs[fARCH_x86_64]) @@ -313,62 +315,66 @@ gchar* getArchName(bool* archs) { return "all"; } -void extract_arch_from_e_machine_field(int16_t e_machine, const gchar* sourcename, bool* archs) { +void extract_arch_from_e_machine_field(int16_t e_machine, const std::string sourcename, bool* archs) { if (e_machine == 3) { archs[fARCH_i686] = 1; if(verbose) - fprintf(stderr, "%s used for determining architecture %s\n", sourcename, archToName(fARCH_i686)); + std::cout << sourcename << " used for determining architecture " << archToName(fARCH_i686) << std::endl; } if (e_machine == 62) { archs[fARCH_x86_64] = 1; if(verbose) - fprintf(stderr, "%s used for determining architecture %s\n", sourcename, archToName(fARCH_x86_64)); + std::cout << sourcename << " used for determining architecture " << archToName(fARCH_x86_64) << std::endl; } if (e_machine == 40) { archs[fARCH_armhf] = 1; if(verbose) - fprintf(stderr, "%s used for determining architecture %s\n", sourcename, archToName(fARCH_armhf)); + std::cout << sourcename << " used for determining architecture " << archToName(fARCH_armhf) << std::endl; } if (e_machine == 183) { archs[fARCH_aarch64] = 1; if(verbose) - fprintf(stderr, "%s used for determining architecture %s\n", sourcename, archToName(fARCH_aarch64)); + std::cout << sourcename << " used for determining architecture " << archToName(fARCH_aarch64) << std::endl; } } -void extract_arch_from_text(gchar *archname, const gchar* sourcename, bool* archs) { - if (archname) { - archname = g_strstrip(archname); - if (archname) { - replacestr(archname, "-", "_"); - replacestr(archname, " ", "_"); - if (g_ascii_strncasecmp("i386", archname, 20) == 0 - || g_ascii_strncasecmp("i486", archname, 20) == 0 - || g_ascii_strncasecmp("i586", archname, 20) == 0 - || g_ascii_strncasecmp("i686", archname, 20) == 0 - || g_ascii_strncasecmp("intel_80386", archname, 20) == 0 - || g_ascii_strncasecmp("intel_80486", archname, 20) == 0 - || g_ascii_strncasecmp("intel_80586", archname, 20) == 0 - || g_ascii_strncasecmp("intel_80686", archname, 20) == 0 - ) { +void extract_arch_from_text(std::string archname, const std::string sourcename, bool* archs) { + if (!archname.empty()) { + // Trim whitespace + archname.erase(archname.begin(), std::find_if(archname.begin(), archname.end(), [](int ch) { + return !std::isspace(ch); + })); + if (!archname.empty()) { + // Replace all dashes and spaces with underscores using C++11 + std::replace(archname.begin(), archname.end(), '-', '_'); + std::replace(archname.begin(), archname.end(), ' ', '_'); + + if (archname.substr(0, 4) == "i386" + || archname.substr(0, 4) == "i486" + || archname.substr(0, 4) == "i586" + || archname.substr(0, 4) == "i686" + || archname.substr(0, 14) == "intel_80386" + || archname.substr(0, 14) == "intel_80486" + || archname.substr(0, 14) == "intel_80586" + || archname.substr(0, 14) == "intel_80686") { archs[fARCH_i686] = 1; if (verbose) - fprintf(stderr, "%s used for determining architecture i386\n", sourcename); - } else if (g_ascii_strncasecmp("x86_64", archname, 20) == 0) { + std::cout << sourcename << " used for determining architecture i386" << std::endl; + } else if (archname.substr(0, 5) == "x86_64") { archs[fARCH_x86_64] = 1; if (verbose) - fprintf(stderr, "%s used for determining architecture x86_64\n", sourcename); - } else if (g_ascii_strncasecmp("arm", archname, 20) == 0) { + std::cout << sourcename << " used for determining architecture x86_64" << std::endl; + } else if (archname.substr(0, 3) == "arm") { archs[fARCH_armhf] = 1; if (verbose) - fprintf(stderr, "%s used for determining architecture ARM\n", sourcename); - } else if (g_ascii_strncasecmp("arm_aarch64", archname, 20) == 0) { + std::cout << sourcename << " used for determining architecture ARM" << std::endl; + } else if (archname.substr(0, 11) == "arm_aarch64") { archs[fARCH_aarch64] = 1; if (verbose) - fprintf(stderr, "%s used for determining architecture ARM aarch64\n", sourcename); + std::cerr << sourcename << " used for determining architecture ARM aarch64\n"; } } } @@ -438,7 +444,7 @@ gchar* find_first_matching_file_nonrecursive(const gchar *real_path, const gchar gchar* get_desktop_entry(GKeyFile *kf, char *key) { gchar *value = g_key_file_get_string (kf, "Desktop Entry", key, NULL); if (! value){ - fprintf(stderr, "%s entry not found in desktop file\n", key); + std::cout << key << " entry not found in desktop file" << std::endl; } return value; } @@ -455,12 +461,12 @@ bool readFile(char* filename, size_t* size, char** buffer) { long fsize = ftell(f); fseek(f, 0, SEEK_SET); - char *indata = malloc(fsize); + char *indata = (char*) malloc(fsize); fread(indata, fsize, 1, f); fclose(f); *size = (int)fsize; *buffer = indata; - return TRUE; + return true; } /* run a command outside the current appimage, block environs like LD_LIBRARY_PATH */ @@ -515,7 +521,7 @@ static GOptionEntry entries[] = { "runtime-file", 0, 0, G_OPTION_ARG_STRING, &runtime_file, "Runtime file to use", NULL }, { "sign-key", 0, 0, G_OPTION_ARG_STRING, &sign_key, "Key ID to use for gpg[2] signatures", NULL}, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &remaining_args, NULL, NULL }, - { 0,0,0,0,0,0,0 } + { 0,0,0, G_OPTION_ARG_NONE, 0,0,0 }, }; int @@ -530,26 +536,47 @@ main (int argc, char *argv[]) * We cannot use g_environ_getenv (g_get_environ() since it is too new for CentOS 6 */ // char* travis_commit; // travis_commit = getenv("TRAVIS_COMMIT"); - char* travis_repo_slug; - travis_repo_slug = getenv("TRAVIS_REPO_SLUG"); - char* travis_tag; - travis_tag = getenv("TRAVIS_TAG"); - char* travis_pull_request; - travis_pull_request = getenv("TRAVIS_PULL_REQUEST"); + std::string travis_repo_slug; + char* travis_repo_slug_cstr = getenv("TRAVIS_REPO_SLUG"); + if (travis_repo_slug_cstr != NULL) { + travis_repo_slug = std::string(travis_repo_slug_cstr); + } + std::string travis_tag; + char* travis_tag_cstr = getenv("TRAVIS_TAG"); + if (travis_tag_cstr != NULL) { + travis_tag = std::string(travis_tag_cstr); + } + std::string travis_pull_request; + char* travis_pull_request_cstr = getenv("TRAVIS_PULL_REQUEST"); + if (travis_pull_request_cstr != NULL) { + travis_pull_request = std::string(travis_pull_request_cstr); + } /* https://github.com/probonopd/uploadtool */ - char* github_token; - github_token = getenv("GITHUB_TOKEN"); + std::string github_token; + char* github_token_cstr = getenv("GITHUB_TOKEN"); + if (github_token_cstr != NULL) { + github_token = std::string(github_token_cstr); + } /* Parse GitLab CI environment variables. * https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables * echo "${CI_PROJECT_URL}/-/jobs/artifacts/${CI_COMMIT_REF_NAME}/raw/QtQuickApp-x86_64.AppImage?job=${CI_JOB_NAME}" */ - char* CI_PROJECT_URL; - CI_PROJECT_URL = getenv("CI_PROJECT_URL"); - char* CI_COMMIT_REF_NAME; - CI_COMMIT_REF_NAME = getenv("CI_COMMIT_REF_NAME"); // The branch or tag name for which project is built - char* CI_JOB_NAME; - CI_JOB_NAME = getenv("CI_JOB_NAME"); // The name of the job as defined in .gitlab-ci.yml + std::string CI_PROJECT_URL; + char* CI_PROJECT_URL_cstr = getenv("CI_PROJECT_URL"); + if (CI_PROJECT_URL_cstr != NULL) { + CI_PROJECT_URL = std::string(CI_PROJECT_URL_cstr); + } + std::string CI_COMMIT_REF_NAME; + char* CI_COMMIT_REF_NAME_cstr = getenv("CI_COMMIT_REF_NAME"); + if (CI_COMMIT_REF_NAME_cstr != NULL) { + CI_COMMIT_REF_NAME = std::string(CI_COMMIT_REF_NAME_cstr); + } + std::string CI_JOB_NAME; + char* CI_JOB_NAME_cstr = getenv("CI_JOB_NAME"); + if (CI_JOB_NAME_cstr != NULL) { + CI_JOB_NAME = std::string(CI_JOB_NAME_cstr); + } /* Parse OWD environment variable. * If it is available then cd there. It is the original CWD prior to running AppRun */ @@ -559,7 +586,7 @@ main (int argc, char *argv[]) int ret; ret = chdir(owd_env); if (ret != 0){ - fprintf(stderr, "Could not cd into %s\n", owd_env); + std::cerr << "Could not cd into " << owd_env << std::endl; exit(1); } } @@ -575,15 +602,11 @@ main (int argc, char *argv[]) // g_option_context_add_group (context, gtk_get_option_group (TRUE)); if (!g_option_context_parse (context, &argc, &argv, &error)) { - fprintf(stderr, "Option parsing failed: %s\n", error->message); + std::cerr << "Option parsing failed: " << error->message << std::endl; exit(1); } - fprintf( - stderr, - "appimagetool, %s (git version %s), build %s built on %s\n", - RELEASE_NAME, GIT_VERSION, BUILD_NUMBER, BUILD_DATE - ); + std::cerr << "appimagetool, " << RELEASE_NAME << " (git version " << GIT_VERSION << "), build " << BUILD_NUMBER << " built on " << BUILD_DATE << std::endl; // always show version, but exit immediately if only the version number was requested if (showVersionOnly) @@ -659,17 +682,17 @@ main (int argc, char *argv[]) // g_spawn_command_line_sync might have set error already, in that case we don't want to overwrite if (error != NULL || !g_spawn_check_exit_status(exit_status, &error)) { if (error == NULL) { - g_printerr("Failed to run 'git rev-parse --short HEAD, but failed to interpret GLib error state: %d\n", exit_status); + std::cerr << "Failed to run 'git rev-parse --short HEAD, but failed to interpret GLib error state: " << exit_status << std::endl; } else { - g_printerr("Failed to run 'git rev-parse --short HEAD: %s (code %d)\n", error->message, error->code); + std::cerr << "Failed to run 'git rev-parse --short HEAD: " << error->message << " (code " << error->code << ")" << std::endl; } } else { version_env = g_strstrip(out); if (version_env != NULL) { - g_printerr("NOTE: Using the output of 'git rev-parse --short HEAD' as the version:\n"); - g_printerr(" %s\n", version_env); - g_printerr(" Please set the $VERSION environment variable if this is not intended\n"); + std::cerr << "NOTE: Using the output of 'git rev-parse --short HEAD' as the version:" << std::endl; + std::cerr << " " << version_env << std::endl; + std::cerr << " Please set the $VERSION environment variable if this is not intended" << std::endl; } } } @@ -688,35 +711,36 @@ main (int argc, char *argv[]) die("Desktop file not found, aborting"); } if(verbose) - fprintf (stdout, "Desktop file: %s\n", desktop_file); + std::cout << "Desktop file: " << desktop_file << std::endl; if(g_find_program_in_path ("desktop-file-validate")) { if(validate_desktop_file(desktop_file) != 0){ - fprintf(stderr, "ERROR: Desktop file contains errors. Please fix them. Please see\n"); - fprintf(stderr, " https://standards.freedesktop.org/desktop-entry-spec/1.0/n"); + std::cerr << "ERROR: Desktop file contains errors. Please fix them. Please see" << std::endl; + std::cerr << " https://standards.freedesktop.org/desktop-entry-spec/1.0/n" << std::endl; die(" for more information."); } } /* Read information from .desktop file */ GKeyFile *kf = g_key_file_new (); - if (!g_key_file_load_from_file (kf, desktop_file, G_KEY_FILE_KEEP_TRANSLATIONS | G_KEY_FILE_KEEP_COMMENTS, NULL)) + GKeyFileFlags flags = (GKeyFileFlags) (G_KEY_FILE_KEEP_TRANSLATIONS | G_KEY_FILE_KEEP_COMMENTS); + if (!g_key_file_load_from_file (kf, desktop_file, flags, NULL)) die(".desktop file cannot be parsed"); if (!get_desktop_entry(kf, "Categories")) die(".desktop file is missing a Categories= key"); - if(verbose){ - fprintf (stderr,"Name: %s\n", get_desktop_entry(kf, "Name")); - fprintf (stderr,"Icon: %s\n", get_desktop_entry(kf, "Icon")); - fprintf (stderr,"Exec: %s\n", get_desktop_entry(kf, "Exec")); - fprintf (stderr,"Comment: %s\n", get_desktop_entry(kf, "Comment")); - fprintf (stderr,"Type: %s\n", get_desktop_entry(kf, "Type")); - fprintf (stderr,"Categories: %s\n", get_desktop_entry(kf, "Categories")); + std::cerr << "Name: " << get_desktop_entry(kf, "Name") << std::endl; + std::cerr << "Icon: " << get_desktop_entry(kf, "Icon") << std::endl; + std::cerr << "Exec: " << get_desktop_entry(kf, "Exec") << std::endl; + std::cerr << "Comment: " << get_desktop_entry(kf, "Comment") << std::endl; + std::cerr << "Type: " << get_desktop_entry(kf, "Type") << std::endl; + std::cerr << "Categories: " << get_desktop_entry(kf, "Categories") << std::endl; } /* Determine the architecture */ bool archs[4] = {0, 0, 0, 0}; - extract_arch_from_text(getenv("ARCH"), "Environmental variable ARCH", archs); + std::string arch_str = getenv("ARCH") ? getenv("ARCH") : ""; + extract_arch_from_text(arch_str.c_str(), "Environmental variable ARCH", archs); if (count_archs(archs) != 1) { /* If no $ARCH variable is set check a file */ /* We use the next best .so that we can find to determine the architecture */ @@ -724,21 +748,21 @@ main (int argc, char *argv[]) int countArchs = count_archs(archs); if (countArchs != 1) { if (countArchs < 1) - fprintf(stderr, "Unable to guess the architecture of the AppDir source directory \"%s\"\n", remaining_args[0]); + std::cerr << "Unable to guess the architecture of the AppDir source directory \"" << remaining_args[0] << "\"" << std::endl; else - fprintf(stderr, "More than one architectures were found of the AppDir source directory \"%s\"\n", remaining_args[0]); - fprintf(stderr, "A valid architecture with the ARCH environmental variable should be provided\ne.g. ARCH=x86_64 %s", argv[0]), + std::cerr << "More than one architectures were found of the AppDir source directory \"" << remaining_args[0] << "\"" << std::endl; + std::cerr << "A valid architecture with the ARCH environmental variable should be provided\ne.g. ARCH=x86_64 " << argv[0] << std::endl; die(" ..."); } } - gchar* arch = getArchName(archs); - fprintf(stderr, "Using architecture %s\n", arch); + std::string arch = getArchName(archs); + std::cerr << "Using architecture " << arch << std::endl; char app_name_for_filename[PATH_MAX]; { const char* const env_app_name = getenv("APPIMAGETOOL_APP_NAME"); if (env_app_name != NULL) { - fprintf(stderr, "Using user-specified app name: %s\n", env_app_name); + std::cerr << "Using user-specified app name: " << env_app_name << std::endl; strncpy(app_name_for_filename, env_app_name, PATH_MAX); } else { const gchar* const desktop_file_app_name = get_desktop_entry(kf, "Name"); @@ -746,7 +770,7 @@ main (int argc, char *argv[]) replacestr(app_name_for_filename, " ", "_"); if (verbose) { - fprintf(stderr, "Using app name extracted from desktop file: %s\n", app_name_for_filename); + std::cerr << "Using app name extracted from desktop file: " << app_name_for_filename << std::endl; } } } @@ -774,12 +798,12 @@ main (int argc, char *argv[]) g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, "X-AppImage-Version", version_env); if (!g_key_file_save_to_file(kf, desktop_file, NULL)) { - fprintf(stderr, "Could not save modified desktop file\n"); + std::cerr << "Could not save modified desktop file" << std::endl; exit(1); } } - fprintf (stdout, "%s should be packaged as %s\n", source, destination); + std::cout << source << " should be packaged as " << destination << std::endl; /* Check if the Icon file is how it is expected */ gchar* icon_name = get_desktop_entry(kf, "Icon"); gchar* icon_file_path = NULL; @@ -796,11 +820,11 @@ main (int argc, char *argv[]) } else if(g_file_test(icon_file_xpm, G_FILE_TEST_IS_REGULAR)) { icon_file_path = icon_file_xpm; } else { - fprintf (stderr, "%s{.png,.svg,.xpm} defined in desktop file but not found\n", icon_name); - fprintf (stderr, "For example, you could put a 256x256 pixel png into\n"); + std::cerr << icon_name << "{.png,.svg,.xpm} defined in desktop file but not found" << std::endl; + std::cerr << "For example, you could put a 256x256 pixel png into" << std::endl; gchar *icon_name_with_png = g_strconcat(icon_name, ".png", NULL); gchar *example_path = g_build_filename(source, "/", icon_name_with_png, NULL); - fprintf (stderr, "%s\n", example_path); + std::cerr << example_path << std::endl; exit(1); } @@ -808,11 +832,11 @@ main (int argc, char *argv[]) gchar *diricon_path = g_build_filename(source, ".DirIcon", NULL); if (! g_file_test(diricon_path, G_FILE_TEST_EXISTS)){ - fprintf (stderr, "Deleting pre-existing .DirIcon\n"); + std::cerr << "Deleting pre-existing .DirIcon" << std::endl; g_unlink(diricon_path); } if (! g_file_test(diricon_path, G_FILE_TEST_IS_REGULAR)){ - fprintf (stderr, "Creating .DirIcon symlink based on information from desktop file\n"); + std::cerr << "Creating .DirIcon symlink based on information from desktop file" << std::endl; int res = symlink(basename(icon_file_path), diricon_path); if(res) die("Could not symlink .DirIcon"); @@ -825,12 +849,12 @@ main (int argc, char *argv[]) replacestr(application_id, ".desktop", ".appdata.xml"); gchar *appdata_path = g_build_filename(source, "/usr/share/metainfo/", application_id, NULL); if (! g_file_test(appdata_path, G_FILE_TEST_IS_REGULAR)){ - fprintf (stderr, "WARNING: AppStream upstream metadata is missing, please consider creating it\n"); - fprintf (stderr, " in usr/share/metainfo/%s\n", application_id); - fprintf (stderr, " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps\n"); - fprintf (stderr, " for more information or use the generator at http://output.jsbin.com/qoqukof.\n"); - } else { - fprintf (stderr, "AppStream upstream metadata found in usr/share/metainfo/%s\n", application_id); + std::cerr << "WARNING: AppStream upstream metadata is missing, please consider creating it" << std::endl; + std::cerr << " in usr/share/metainfo/" << application_id << std::endl; + std::cerr << " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps" << std::endl; + std::cerr << " for more information or use the generator at http://output.jsbin.com/qoqukof." << std::endl; + } else { + std::cerr << "AppStream upstream metadata found in usr/share/metainfo/" << application_id << std::endl; /* Use ximion's appstreamcli to make sure that desktop file and appdata match together */ if(g_find_program_in_path ("appstreamcli")) { char *args[] = { @@ -866,7 +890,7 @@ main (int argc, char *argv[]) * so we need a patched one. https://github.com/plougher/squashfs-tools/pull/13 * should hopefully change that. */ - fprintf (stderr, "Generating squashfs...\n"); + std::cerr << "Loading runtime file..." << std::endl; size_t size = 0; char* data = NULL; // TODO: just write to the output file directly, we don't really need a memory buffer @@ -880,13 +904,14 @@ main (int argc, char *argv[]) } } if (verbose) - printf("Size of the embedded runtime: %d bytes\n", size); - + std::cout << "Size of the embedded runtime: " << size << " bytes" << std::endl; + + std::cerr << "Generating squashfs..." << std::endl; int result = sfs_mksquashfs(source, destination, size); if(result != 0) die("sfs_mksquashfs error"); - fprintf (stderr, "Embedding ELF...\n"); + std::cerr << "Embedding ELF..." << std::endl; FILE *fpdst = fopen(destination, "rb+"); if (fpdst == NULL) { die("Not able to open the AppImage for writing, aborting"); @@ -898,19 +923,21 @@ main (int argc, char *argv[]) // TODO: avoid memory buffer (see above) free(data); - fprintf (stderr, "Marking the AppImage as executable...\n"); + std::cerr << "Marking the AppImage as executable..." << std::endl; if (chmod (destination, 0755) < 0) { - printf("Could not set executable bit, aborting\n"); + std::cout << "Could not set executable bit, aborting" << std::endl; exit(1); } + + std::string update_info; /* If the user has not provided update information but we know this is a Travis CI build, * then fill in update information based on TRAVIS_REPO_SLUG */ if(guess_update_information){ - if(travis_repo_slug){ - if(!github_token) { + if (!travis_repo_slug.empty()) { + if(github_token.empty()) { printf("Will not guess update information since $GITHUB_TOKEN is missing,\n"); - if(0 != strcmp(travis_pull_request, "false")){ + if(travis_pull_request != "false"){ printf("please set it in the Travis CI Repository Settings for this project.\n"); printf("You can get one from https://github.com/settings/tokens\n"); } else { @@ -920,52 +947,58 @@ main (int argc, char *argv[]) gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake"); if(zsyncmake_path){ char buf[1024]; - gchar **parts = g_strsplit (travis_repo_slug, "/", 2); + std::string delimiter = "/"; + std::string owner = travis_repo_slug.substr(0, travis_repo_slug.find(delimiter)); + std::string repo = travis_repo_slug.substr(travis_repo_slug.find(delimiter) + 1); /* https://github.com/AppImage/AppImageSpec/blob/master/draft.md#github-releases * gh-releases-zsync|probono|AppImages|latest|Subsurface*-x86_64.AppImage.zsync */ gchar *channel = "continuous"; - if(travis_tag != NULL){ - if((strcmp(travis_tag, "") != 0) && (strcmp(travis_tag, "continuous") != 0)) { - channel = "latest"; - } + if ((!travis_tag.empty()) && (travis_tag != "continuous")) { + channel = "latest"; } - sprintf(buf, "gh-releases-zsync|%s|%s|%s|%s*-%s.AppImage.zsync", parts[0], parts[1], channel, app_name_for_filename, arch); + std::ostringstream oss; + oss << "gh-releases-zsync|" << owner << "|" << repo << "|" << channel << "|" << app_name_for_filename << "*-" << arch << ".AppImage.zsync"; + update_info = oss.str(); updateinformation = buf; - printf("Guessing update information based on $TRAVIS_TAG=%s and $TRAVIS_REPO_SLUG=%s\n", travis_tag, travis_repo_slug); - printf("%s\n", updateinformation); + std::cout << "Guessing update information based on $TRAVIS_TAG=" << travis_tag << " and $TRAVIS_REPO_SLUG=" << travis_repo_slug << std::endl; + std::cout << updateinformation << std::endl; } else { printf("Will not guess update information since zsyncmake is missing\n"); } } - } else if(CI_COMMIT_REF_NAME){ + } else if(! CI_COMMIT_REF_NAME.empty()){ // ${CI_PROJECT_URL}/-/jobs/artifacts/${CI_COMMIT_REF_NAME}/raw/QtQuickApp-x86_64.AppImage?job=${CI_JOB_NAME} gchar *zsyncmake_path = g_find_program_in_path ("zsyncmake"); - if(zsyncmake_path){ - char buf[1024]; - sprintf(buf, "zsync|%s/-/jobs/artifacts/%s/raw/%s-%s.AppImage.zsync?job=%s", CI_PROJECT_URL, CI_COMMIT_REF_NAME, app_name_for_filename, arch, CI_JOB_NAME); - updateinformation = buf; - printf("Guessing update information based on $CI_COMMIT_REF_NAME=%s and $CI_JOB_NAME=%s\n", CI_COMMIT_REF_NAME, CI_JOB_NAME); - printf("%s\n", updateinformation); + if (zsyncmake_path) { + update_info = "zsync|" + CI_PROJECT_URL + "/-/jobs/artifacts/" + CI_COMMIT_REF_NAME + "/raw/" + app_name_for_filename + "-" + arch + ".AppImage.zsync?job=" + CI_JOB_NAME; + std::cout << "Guessing update information based on $CI_COMMIT_REF_NAME=" << CI_COMMIT_REF_NAME << " and $CI_JOB_NAME=" << CI_JOB_NAME << std::endl; + std::cout << update_info << std::endl; } else { - printf("Will not guess update information since zsyncmake is missing\n"); + std::cout << "Will not guess update information since zsyncmake is missing\n"; } } } /* If updateinformation was provided, then we check and embed it */ - if(updateinformation != NULL){ - if(!g_str_has_prefix(updateinformation,"zsync|")) - if(!g_str_has_prefix(updateinformation,"gh-releases-zsync|")) - if(!g_str_has_prefix(updateinformation,"pling-v1-zsync|")) - die("The provided updateinformation is not in a recognized format"); + if(!update_info.empty()){ + if(update_info.find("zsync|") != 0) + if(update_info.find("gh-releases-zsync|") != 0) + if(update_info.find("pling-v1-zsync|") != 0) + die("The provided update information is not in a recognized format"); - gchar **ui_type = g_strsplit_set(updateinformation, "|", -1); + std::string ui_type; + for (int i = 0; i < update_info.length(); i++) { + if (update_info[i] == '|') { + break; + } + ui_type += update_info[i]; + } - if(verbose) - printf("updateinformation type: %s\n", ui_type[0]); + if(verbose) { + std::cout << "updateinformation type: " << ui_type << std::endl; + } /* TODO: Further checking of the updateinformation */ - - + unsigned long ui_offset = 0; unsigned long ui_length = 0; @@ -976,8 +1009,8 @@ main (int argc, char *argv[]) } if(verbose) { - printf("ui_offset: %lu\n", ui_offset); - printf("ui_length: %lu\n", ui_length); + std::cout << "ui_offset: " << ui_offset << std::endl; + std::cout << "ui_length: " << ui_length << std::endl; } if(ui_offset == 0) { die("Could not determine offset for updateinformation"); @@ -998,7 +1031,7 @@ main (int argc, char *argv[]) // calculate and embed MD5 digest { - fprintf(stderr, "Embedding MD5 digest\n"); + std::cerr << "Embedding MD5 digest" << std::endl; unsigned long digest_md5_offset = 0; unsigned long digest_md5_length = 0; @@ -1012,12 +1045,8 @@ main (int argc, char *argv[]) static const unsigned long section_size = 16; if (digest_md5_length < section_size) { - fprintf( - stderr, - ".digest_md5 section in runtime's ELF header is too small" - "(found %lu bytes, minimum required: %lu bytes)\n", - digest_md5_length, section_size - ); + std::cerr << ".digest_md5 section in runtime's ELF header is too small" + << "(found " << digest_md5_length << " bytes, minimum required: " << section_size << " bytes)" << std::endl; exit(1); } @@ -1056,36 +1085,36 @@ main (int argc, char *argv[]) if (updateinformation != NULL) { gchar* zsyncmake_path = g_find_program_in_path("zsyncmake"); if (!zsyncmake_path) { - fprintf(stderr, "zsyncmake is not installed/bundled, skipping\n"); + std::cerr << "zsyncmake is not installed/bundled, skipping\n"; } else { - fprintf(stderr, "zsyncmake is available and updateinformation is provided, " - "hence generating zsync file\n"); + std::cerr << "zsyncmake is available and updateinformation is provided, " + "hence generating zsync file\n"; const gchar* const zsyncmake_command[] = {zsyncmake_path, destination, "-u", basename(destination), NULL}; if (verbose) { - fprintf(stderr, "Running zsyncmake process: "); + std::cerr << "Running zsyncmake process: "; for (gint j = 0; j < (sizeof(zsyncmake_command) / sizeof(char*) - 1); ++j) { - fprintf(stderr, "'%s' ", zsyncmake_command[j]); + std::cerr << "'" << zsyncmake_command[j] << "' "; } - fprintf(stderr, "\n"); + std::cerr << "\n"; } GSubprocessFlags flags = G_SUBPROCESS_FLAGS_NONE; if (!verbose) { - flags = G_SUBPROCESS_FLAGS_STDERR_SILENCE | G_SUBPROCESS_FLAGS_STDOUT_SILENCE; + flags = static_cast(G_SUBPROCESS_FLAGS_STDERR_SILENCE | G_SUBPROCESS_FLAGS_STDOUT_SILENCE); } - + GSubprocess* proc = g_subprocess_newv(zsyncmake_command, flags, &error); if (proc == NULL) { - fprintf(stderr, "ERROR: failed to create zsyncmake process: %s\n", error->message); + std::cerr << "ERROR: failed to create zsyncmake process: " << error->message << "\n"; exit(1); } if (!g_subprocess_wait_check(proc, NULL, &error)) { - fprintf(stderr, "ERROR: zsyncmake returned abnormal exit code: %s\n", error->message); + std::cerr << "ERROR: zsyncmake returned abnormal exit code: " << error->message << "\n"; g_object_unref(proc); exit(1); } @@ -1094,19 +1123,17 @@ main (int argc, char *argv[]) } } - fprintf(stderr, "Success\n\n"); - fprintf(stderr, "Please consider submitting your AppImage to AppImageHub, the crowd-sourced\n"); - fprintf(stderr, "central directory of available AppImages, by opening a pull request\n"); - fprintf(stderr, "at https://github.com/AppImage/appimage.github.io\n"); + std::string absolute_path = std::string(realpath(destination, NULL)); + if (!absolute_path.empty()) { + std::cerr << "Success: " << absolute_path << std::endl; + std::cerr << "Please consider submitting your AppImage to AppImageHub, the crowd-sourced\n"; + std::cerr << "central directory of available AppImages, by opening a pull request\n"; + std::cerr << "at https://github.com/AppImage/appimage.github.io\n"; + return 0; + } - return 0; - } else if (g_file_test(remaining_args[0], G_FILE_TEST_IS_REGULAR)) { - /* If the first argument is a regular file, then we assume that we should unpack it */ - fprintf(stdout, "%s is a file, assuming it is an AppImage and should be unpacked\n", remaining_args[0]); - die("To be implemented"); - return 1; } else { - fprintf(stderr, "Error: no such file or directory: %s\n", remaining_args[0]); + std::cerr << "Error: no such file or directory: " << remaining_args[0] << "\n"; return 1; } diff --git a/src/appimagetool_fetch_runtime.c b/src/appimagetool_fetch_runtime.c deleted file mode 100644 index fa224d0..0000000 --- a/src/appimagetool_fetch_runtime.c +++ /dev/null @@ -1,163 +0,0 @@ -// need to define this to enable asprintf -#define _GNU_SOURCE -#include -#include - -#include -#include -#include -#include - -#include "appimagetool_fetch_runtime.h" - -bool fetch_runtime(char* arch, size_t* size, char** buffer, bool verbose) { - // not the cleanest approach to globally init curl here, but this method shouldn't be called more than once anyway - curl_global_init(CURL_GLOBAL_ALL); - - // should be plenty big for the URL - char url[1024]; - int url_size = snprintf(url, sizeof(url), "https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-%s", arch); - if (url_size <= 0 || url_size >= sizeof(url)) { - fprintf(stderr, "Failed to generate runtime URL\n"); - curl_global_cleanup(); - return false; - } - - fprintf(stderr, "Downloading runtime file from %s\n", url); - - char curl_error_buf[CURL_ERROR_SIZE]; - CURL* handle = NULL; - // should also be plenty big for the redirect target - char effective_url[sizeof(url)]; - int success = -1L; - - curl_off_t content_length = -1; - - // first, we perform a HEAD request to determine the required buffer size to write the file to - // of course, this assumes that a) GitHub sends a Content-Length header and b) that it is correct and will be in - // the GET request, too - // we store the URL we are redirected to (which probably lies on some AWS and is unique to that file) for use in - // the GET request, which should ensure we really download the file whose size we check now - handle = curl_easy_init(); - - if (handle == NULL) { - fprintf(stderr, "Failed to initialize libcurl\n"); - curl_global_cleanup(); - return false; - } - - curl_easy_setopt(handle, CURLOPT_URL, url); - curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L); - // should be plenty for GitHub - curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 12L); - curl_easy_setopt(handle, CURLOPT_NOBODY, 1L); - curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, curl_error_buf); - if (verbose) { - curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L); - } - - success = curl_easy_perform(handle); - - if (success == CURLE_OK) { - // we want to clean up the handle before we can use effective_url - char* temp; - if (curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &temp) == CURLE_OK) { - strncpy(effective_url, temp, sizeof(effective_url) - 1); - // just a little sanity check - if (strlen(effective_url) != strlen(temp)) { - fprintf(stderr, "Failed to copy effective URL\n"); - // delegate cleanup - effective_url[0] = '\0'; - } else if (strcmp(url, effective_url) != 0) { - fprintf(stderr, "Redirected to %s\n", effective_url); - } - } else { - fprintf(stderr, "Error: failed to determine effective URL\n"); - // we recycle the cleanup call below and check whether effective_url was set to anything meaningful below - } - - if (curl_easy_getinfo(handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &content_length) == CURLE_OK) { - fprintf(stderr, "Downloading runtime binary of size %" CURL_FORMAT_CURL_OFF_T "\n", content_length); - } else { - fprintf(stderr, "Error: no Content-Length header sent by GitHub\n"); - // we recycle the cleanup call below and check whether content_length was set to anything meaningful below - } - } else { - fprintf(stderr, "HEAD request to %s failed: %s\n", url, curl_error_buf); - } - - curl_easy_cleanup(handle); - handle = NULL; - - if (success != CURLE_OK || strlen(effective_url) == 0 || content_length <= 0) { - curl_global_cleanup(); - return false; - } - - // now that we know the required buffer size, we allocate a suitable in-memory buffer and perform the GET request - // we allocate our own so that we don't have to use fread(...) to get the data - char raw_buffer[content_length]; - FILE* file_buffer = fmemopen(raw_buffer, sizeof(raw_buffer), "w+b"); - setbuf(file_buffer, NULL); - - if (file_buffer == NULL) { - fprintf(stderr, "fmemopen failed: %s\n", strerror(errno)); - curl_global_cleanup(); - return false; - } - - handle = curl_easy_init(); - - if (handle == NULL) { - fprintf(stderr, "Failed to initialize libcurl\n"); - curl_global_cleanup(); - return false; - } - - // note: we should not need any more redirects - curl_easy_setopt(handle, CURLOPT_URL, effective_url); - curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void*) file_buffer); - curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, curl_error_buf); - if (verbose) { - curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L); - } - - success = curl_easy_perform(handle); - - int get_content_length; - - if (success != CURLE_OK) { - fprintf(stderr, "GET request to %s failed: %s\n", effective_url, curl_error_buf); - curl_easy_cleanup(handle); - curl_global_cleanup(); - return false; - } else { - if (curl_easy_getinfo(handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &get_content_length) != CURLE_OK) { - fprintf(stderr, "Error: no Content-Length header sent by GitHub\n"); - // we recycle the cleanup call below and check whether content_length was set to anything meaningful below - } - } - - curl_easy_cleanup(handle); - handle = NULL; - - // done with libcurl - curl_global_cleanup(); - - if (get_content_length != content_length) { - fprintf(stderr, "Downloading runtime binary of size %" CURL_FORMAT_CURL_OFF_T "\n", content_length); - } - - *size = content_length; - - *buffer = (char*) calloc(content_length + 1, 1); - - if (*buffer == NULL) { - fprintf(stderr, "Failed to allocate buffer\n"); - return false; - } - - memcpy((void* ) *buffer, raw_buffer, sizeof(raw_buffer)); - - return true; -} diff --git a/src/appimagetool_fetch_runtime.cpp b/src/appimagetool_fetch_runtime.cpp new file mode 100644 index 0000000..8b621bc --- /dev/null +++ b/src/appimagetool_fetch_runtime.cpp @@ -0,0 +1,164 @@ +#include +#include +#include + +#include +#include + +#include "appimagetool_fetch_runtime.h" + +#include +#include + +enum class RequestType { + GET, + HEAD, +}; + +class CurlResponse { +private: + bool _success; + std::string _effectiveUrl; + curl_off_t _contentLength; + std::vector _data; + +public: + CurlResponse(bool success, curl_off_t contentLength, std::vector data) + : _success(success) + , _contentLength(contentLength) + , _data(std::move(data)) {} + + bool success() { + return _success; + } + + curl_off_t contentLength() { + return _contentLength; + } + + std::vector data() { + return _data; + } +}; + +class CurlException : public std::runtime_error { +public: + CurlException(CURLcode code, const std::string& errorMessage) : std::runtime_error(std::string(curl_easy_strerror(code)) + ": " + errorMessage) {} +}; + +class CurlRequest { +private: + CURL* _handle; + std::vector _buffer; + std::vector _errorBuffer; + + static size_t writeStuff(char* data, size_t size, size_t nmemb, void* this_ptr) { + const auto bytes = size * nmemb; + std::copy(data, data + bytes, std::back_inserter(static_cast(this_ptr)->_buffer)); + return bytes; + } + + void checkForCurlError(CURLcode code) { + if (code != CURLE_OK) { + throw CurlException(code, _errorBuffer.data()); + } + } + +public: + CurlRequest(const std::string& url, RequestType requestType) : _errorBuffer(CURL_ERROR_SIZE) { + // not the cleanest approach to globally init curl here, but this method shouldn't be called more than once anyway + curl_global_init(CURL_GLOBAL_ALL); + + this->_handle = curl_easy_init(); + if (_handle == nullptr) { + throw std::runtime_error("Failed to initialize libcurl\n"); + } + + checkForCurlError(curl_easy_setopt(this->_handle, CURLOPT_URL, url.c_str())); + + switch (requestType) { + case RequestType::GET: + break; + case RequestType::HEAD: + checkForCurlError(curl_easy_setopt(this->_handle, CURLOPT_NOBODY, 1L)); + break; + } + + // default parameters + checkForCurlError(curl_easy_setopt(_handle, CURLOPT_FOLLOWLOCATION, 1L)); + checkForCurlError(curl_easy_setopt(_handle, CURLOPT_MAXREDIRS, 12L)); + + checkForCurlError(curl_easy_setopt(_handle, CURLOPT_WRITEFUNCTION, CurlRequest::writeStuff)); + checkForCurlError(curl_easy_setopt(_handle, CURLOPT_WRITEDATA, static_cast(this))); + + checkForCurlError(curl_easy_setopt(_handle, CURLOPT_ERRORBUFFER, _errorBuffer.data())); + } + + ~CurlRequest() { + curl_global_cleanup(); + curl_easy_cleanup(this->_handle); + } + + void setVerbose(bool verbose) { + checkForCurlError(curl_easy_setopt(this->_handle, CURLOPT_VERBOSE, verbose ? 1L : 0L)); + } + + CurlResponse perform() { + auto result = curl_easy_perform(this->_handle); + + curl_off_t contentLength; + checkForCurlError(curl_easy_getinfo(_handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &contentLength)); + + return {result == CURLE_OK, contentLength, _buffer}; + } +}; + +#include +#include +#include + +#include "appimagetool_fetch_runtime.h" + +bool fetch_runtime(std::string arch, size_t* size, char** buffer, bool verbose) { + std::ostringstream urlstream; + urlstream << "https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-" << arch; + auto url = urlstream.str(); + + std::cerr << "Downloading runtime file from " << url << std::endl; + + CurlRequest request(url, RequestType::GET); + + if (verbose) { + request.setVerbose(true); + } + + auto response = request.perform(); + + std::cerr << "Downloaded runtime binary of size " << response.contentLength() << std::endl; + + if (!response.success()) { + return false; + } + + auto runtimeData = response.data(); + + if (runtimeData.size() != response.contentLength()) { + std::cerr << "Error: downloaded data size of " << runtimeData.size() + << " does not match content-length of " << response.contentLength() << std::endl; + return false; + } + + *size = response.contentLength(); + + *buffer = new char[response.contentLength() + 1]; + + if (*buffer == nullptr) { + std::cerr << "Failed to allocate buffer" << std::endl; + return false; + } + + std::memcpy(*buffer, runtimeData.data(), response.contentLength()); + (*buffer)[response.contentLength()] = '\0'; + + return true; +} diff --git a/src/appimagetool_fetch_runtime.h b/src/appimagetool_fetch_runtime.h index 4110f9b..5c0dc6f 100644 --- a/src/appimagetool_fetch_runtime.h +++ b/src/appimagetool_fetch_runtime.h @@ -1,7 +1,15 @@ #pragma once +// C++ +#include +#include +#include + /** * Download runtime from GitHub into a buffer. * This function allocates a buffer of the right size internally, which needs to be cleaned up by the caller. */ -bool fetch_runtime(char* arch, size_t* size, char** buffer, bool verbose); + +bool fetch_runtime(std::string arch, size_t *size, char **buffer, bool verbose); + + diff --git a/src/appimagetool_sign.c b/src/appimagetool_sign.c index 0a3e1cd..ad6fdf9 100644 --- a/src/appimagetool_sign.c +++ b/src/appimagetool_sign.c @@ -1,3 +1,7 @@ +#ifdef __cplusplus +extern "C" { +#endif + #include #include #include @@ -471,3 +475,6 @@ bool init_gcrypt() { return true; } +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/appimagetool_sign.h b/src/appimagetool_sign.h index 59e860b..829812e 100644 --- a/src/appimagetool_sign.h +++ b/src/appimagetool_sign.h @@ -1,4 +1,10 @@ -#pragma once +#ifndef APPIMAGETOOL_SIGN_H +#define APPIMAGETOOL_SIGN_H + +#ifdef __cplusplus +extern "C" { +#endif + bool sign_appimage(char* appimage_filename, char* key_id, bool verbose); @@ -25,3 +31,9 @@ void gpg_release_resources(); * @return true on success, false otherwise */ bool init_gcrypt(); + +#ifdef __cplusplus +} +#endif + +#endif /* APPIMAGETOOL_SIGN_H */ \ No newline at end of file diff --git a/src/elf.c b/src/elf.c index aed9e89..75cbba1 100644 --- a/src/elf.c +++ b/src/elf.c @@ -1,3 +1,7 @@ +#ifdef __cplusplus +extern "C" { +#endif + #include #include #include @@ -12,7 +16,6 @@ #include "light_elf.h" #include "light_byteswap.h" - typedef Elf32_Nhdr Elf_Nhdr; static char *fname; @@ -125,13 +128,13 @@ bool appimage_get_elf_section_offset_and_length(const char* fname, const char* s int fd = open(fname, O_RDONLY); size_t map_size = (size_t) lseek(fd, 0, SEEK_END); - data = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, 0); + data = (uint8_t*) mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, 0); close(fd); // this trick works as both 32 and 64 bit ELF files start with the e_ident[EI_NINDENT] section - unsigned char class = data[EI_CLASS]; + uint8_t elf_class = data[EI_CLASS]; - if (class == ELFCLASS32) { + if (elf_class == ELFCLASS32) { Elf32_Ehdr* elf; Elf32_Shdr* shdr; @@ -145,7 +148,7 @@ bool appimage_get_elf_section_offset_and_length(const char* fname, const char* s *length = shdr[i].sh_size; } } - } else if (class == ELFCLASS64) { + } else if (elf_class == ELFCLASS64) { Elf64_Ehdr* elf; Elf64_Shdr* shdr; @@ -177,7 +180,7 @@ char* read_file_offset_length(const char* fname, unsigned long offset, unsigned fseek(f, offset, SEEK_SET); - char* buffer = calloc(length + 1, sizeof(char)); + char* buffer = (char*) calloc(length + 1, sizeof(char)); fread(buffer, length, sizeof(char), f); fclose(f); @@ -214,3 +217,7 @@ int appimage_print_binary(const char* fname, unsigned long offset, unsigned long return 0; } + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/elf.h b/src/elf.h new file mode 100644 index 0000000..c5e27a1 --- /dev/null +++ b/src/elf.h @@ -0,0 +1,14 @@ +#ifndef ELF_H +#define ELF_H + +#include + +bool appimage_get_elf_section_offset_and_length(const char* fname, const char* section_name, unsigned long* offset, unsigned long* length); + +char* read_file_offset_length(const char* fname, unsigned long offset, unsigned long length); + +int appimage_print_hex(const char* fname, unsigned long offset, unsigned long length); + +int appimage_print_binary(const char* fname, unsigned long offset, unsigned long length); + +#endif /* ELF_H */ \ No newline at end of file diff --git a/src/util.h b/src/util.h index 953e9da..03799b1 100644 --- a/src/util.h +++ b/src/util.h @@ -1,3 +1,11 @@ +#ifdef __cplusplus +extern "C" { +#endif + char* appimage_hexlify(const char* bytes, const size_t numBytes); bool appimage_get_elf_section_offset_and_length(const char* fname, const char* section_name, unsigned long* offset, unsigned long* length); bool appimage_type2_digest_md5(const char* path, char* digest); + +#ifdef __cplusplus +} +#endif \ No newline at end of file