Stefan Reinauer | dfd556c | 2019-11-20 17:57:40 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019 Google LLC |
| 3 | * |
| 4 | * This program is free software; you can redistribute it and/or modify |
| 5 | * it under the terms of the GNU General Public License as published by |
| 6 | * the Free Software Foundation; version 2 of the License. |
| 7 | * |
| 8 | * This program is distributed in the hope that it will be useful, |
| 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | * GNU General Public License for more details. |
| 12 | */ |
| 13 | |
| 14 | #include <stdio.h> |
| 15 | #include <stdlib.h> |
| 16 | #include <string.h> |
| 17 | #include <unistd.h> |
| 18 | #include <curl/curl.h> |
| 19 | #include "em100.h" |
| 20 | |
| 21 | /* For higher availability these binaries are hosted on |
| 22 | * Google Drive for convenience. You can create them by yourself |
| 23 | * from the installer tar ball. |
| 24 | * |
| 25 | * TODO: some sort of MD5 check / update check. |
| 26 | */ |
| 27 | const char *firmware_id = "1UmzGZbRkF9duwTLPi467EyfIZ6EhnMKA"; |
| 28 | const char *firmware_name = "firmware.tar.xz"; |
| 29 | |
| 30 | const char *configs_id = "19jT6kNYV1TE6WNx6lUkgH0TYyKbxXcd4"; |
| 31 | const char *configs_name = "configs.tar.xz"; |
| 32 | |
| 33 | const char *version_id = "1YC755W_c4nRN4qVgosegFrvfyWllqb0b"; |
| 34 | const char *version_name = "VERSION"; |
| 35 | |
| 36 | #define TIMEOPT CURLINFO_TOTAL_TIME_T |
| 37 | #define MINIMAL_PROGRESS_FUNCTIONALITY_INTERVAL 3000000 |
| 38 | |
| 39 | static int xferinfo(void *p __unused, |
| 40 | curl_off_t dltotal __unused, curl_off_t dlnow __unused, |
| 41 | curl_off_t ultotal __unused, curl_off_t ulnow __unused) |
| 42 | { |
| 43 | /* Google Drive API transfers no Content-Length, so |
| 44 | * instead of bloating this with Range: hacks, let's |
| 45 | * just print a spinning wheel. |
| 46 | */ |
| 47 | static int pos = 0; |
| 48 | char cursor[4] = { '/', '-', '\\', '|' }; |
| 49 | printf("%c\b", cursor[pos]); |
| 50 | fflush(stdout); |
| 51 | pos = (pos + 1) % 4; |
| 52 | return 0; |
| 53 | } |
| 54 | |
| 55 | static size_t write_data(void *ptr, size_t size, size_t nmemb, |
| 56 | void *stream) |
| 57 | { |
| 58 | return fwrite(ptr, size, nmemb, (FILE *) stream); |
| 59 | } |
| 60 | |
| 61 | static int curl_get(const char *id, const char *filename, int progress) |
| 62 | { |
| 63 | FILE *file; |
| 64 | CURLcode res; |
| 65 | #define URL_BUFFER_SIZE 1024 |
| 66 | char url[URL_BUFFER_SIZE] = |
| 67 | "https://drive.google.com/uc?export=download&id="; |
| 68 | |
| 69 | file = fopen(filename, "wb"); |
| 70 | if (!file) { |
| 71 | perror(filename); |
| 72 | return -1; |
| 73 | } |
| 74 | |
| 75 | CURL *curl = curl_easy_init(); |
| 76 | if (!curl) { |
| 77 | printf("CURL failed.\n"); |
| 78 | fclose(file); |
| 79 | return -1; |
| 80 | } |
| 81 | |
| 82 | strncat(url, id, URL_BUFFER_SIZE - 1); |
| 83 | |
| 84 | /* Set URL to GET here */ |
| 85 | curl_easy_setopt(curl, CURLOPT_URL, url); |
| 86 | #ifdef DEBUG |
| 87 | /* Switch on protocol/debug output for testing */ |
| 88 | curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); |
| 89 | #endif |
| 90 | |
| 91 | /* Some servers don't like requests without a user-agent field. */ |
| 92 | curl_easy_setopt(curl, CURLOPT_USERAGENT, "em100-agent/1.0"); |
| 93 | |
| 94 | /* Write callback to write the data to disk */ |
| 95 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); |
| 96 | |
| 97 | /* Write data to this file handle */ |
| 98 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); |
| 99 | |
| 100 | if (progress) { |
| 101 | /* Simple progress indicator function */ |
| 102 | curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferinfo); |
| 103 | |
| 104 | /* Enable progress indicator */ |
| 105 | curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); |
| 106 | } |
| 107 | |
| 108 | /* Follow redirections (as used by Google Drive) */ |
| 109 | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION , 1L); |
| 110 | |
| 111 | /* Fetch the file */ |
| 112 | res = curl_easy_perform(curl); |
| 113 | |
| 114 | /* Close file */ |
| 115 | fclose(file); |
| 116 | |
| 117 | /* Clean up */ |
| 118 | curl_easy_cleanup(curl); |
| 119 | |
| 120 | return 0; |
| 121 | } |
| 122 | |
| 123 | void download(const char *name, const char *id) |
| 124 | { |
| 125 | char *filename = get_em100_file(name); |
| 126 | printf("Downloading %s: ", name); |
| 127 | if (curl_get(id, filename, 1)) |
| 128 | printf("FAILED.\n"); |
| 129 | else |
| 130 | printf("OK\n"); |
| 131 | free(filename); |
| 132 | } |
| 133 | |
| 134 | int update_all_files(void) |
| 135 | { |
Stefan Reinauer | bb6415a | 2019-12-04 17:52:13 -0800 | [diff] [blame] | 136 | long old_time = 0, new_time = 0; |
Stefan Reinauer | dfd556c | 2019-11-20 17:57:40 -0800 | [diff] [blame] | 137 | char old_version[256] = "<unknown>", new_version[256] = "<unknown>"; |
| 138 | |
| 139 | /* Read existing version and timestamp */ |
| 140 | char *my_version_name = get_em100_file(version_name); |
| 141 | FILE *old = fopen(my_version_name, "r"); |
Stefan Reinauer | bb6415a | 2019-12-04 17:52:13 -0800 | [diff] [blame] | 142 | if (old) { |
| 143 | if (fscanf(old, "Time: %ld\nVersion: %255s\n", |
| 144 | &old_time, old_version) != 2) |
| 145 | printf("Parse error in %s.\n", my_version_name); |
| 146 | fclose(old); |
Stefan Reinauer | dfd556c | 2019-11-20 17:57:40 -0800 | [diff] [blame] | 147 | } |
| 148 | |
Stefan Reinauer | dfd556c | 2019-11-20 17:57:40 -0800 | [diff] [blame] | 149 | free(my_version_name); |
Stefan Reinauer | dfd556c | 2019-11-20 17:57:40 -0800 | [diff] [blame] | 150 | |
| 151 | /* Read upstream version and timestamp */ |
| 152 | char *tmp_version = get_em100_file(".VERSION.new"); |
| 153 | if (curl_get(version_id, tmp_version, 0)) { |
| 154 | printf("FAILED.\n"); |
| 155 | } |
| 156 | FILE *new = fopen(tmp_version, "r"); |
| 157 | if (!new) { |
| 158 | free(tmp_version); |
| 159 | return 1; |
| 160 | } |
Stefan Reinauer | d565ea8 | 2019-12-02 11:59:16 -0800 | [diff] [blame] | 161 | if (fscanf(new, "Time: %ld\nVersion: %255s\n", |
Stefan Reinauer | dfd556c | 2019-11-20 17:57:40 -0800 | [diff] [blame] | 162 | &new_time, new_version) != 2) |
| 163 | printf("Parse error in upstream VERSION.\n"); |
| 164 | fclose(new); |
| 165 | unlink(tmp_version); |
| 166 | free(tmp_version); |
| 167 | |
| 168 | /* Compare time stamps and bail out if we have the latest version */ |
| 169 | if (old_time >= new_time) { |
| 170 | printf("Current version: %s. No newer version available.\n", old_version); |
| 171 | return 0; |
| 172 | } |
| 173 | |
Stefan Reinauer | dfd556c | 2019-11-20 17:57:40 -0800 | [diff] [blame] | 174 | /* Download everything */ |
Stefan Reinauer | bb6415a | 2019-12-04 17:52:13 -0800 | [diff] [blame] | 175 | if (old_time == 0) |
| 176 | printf("Downloading latest version: %s\n", new_version); |
| 177 | else |
| 178 | printf("Update available: %s (installed: %s)\n", new_version, old_version); |
Stefan Reinauer | dfd556c | 2019-11-20 17:57:40 -0800 | [diff] [blame] | 179 | download(version_name, version_id); |
| 180 | download(configs_name, configs_id); |
| 181 | download(firmware_name, firmware_id); |
| 182 | |
| 183 | return 0; |
| 184 | } |