├── .mailmap ├── doc └── libautoupdate.gif ├── AUTHORS ├── .travis.yml ├── src ├── inflate.c ├── autoupdate_internal.h ├── tmpf.c ├── exepath.c ├── utils.c └── autoupdate.c ├── libautoupdate.gyp ├── .gitignore ├── include └── autoupdate.h ├── CMakeLists.txt ├── appveyor.yml ├── LICENSE ├── tests └── main.c └── README.md /.mailmap: -------------------------------------------------------------------------------- 1 | Minqi Pan -------------------------------------------------------------------------------- /doc/libautoupdate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmq20/libautoupdate/HEAD/doc/libautoupdate.gif -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Authors ordered by first contribution. 2 | 3 | Minqi Pan 4 | Venkat Ram 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | os: 4 | - linux 5 | - osx 6 | 7 | compiler: 8 | - gcc 9 | - clang 10 | 11 | script: 12 | - cmake --version 13 | - mkdir build && cd build 14 | - cmake -DBUILD_TESTS=ON .. || exit $? 15 | - cmake --build . || exit $? 16 | -------------------------------------------------------------------------------- /src/inflate.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Minqi Pan et al. 3 | * 4 | * This file is part of libautoupdate, distributed under the MIT License 5 | * For full terms see the included LICENSE file 6 | */ 7 | 8 | #include "autoupdate.h" 9 | #include "autoupdate_internal.h" 10 | -------------------------------------------------------------------------------- /libautoupdate.gyp: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Minqi Pan et al. 2 | # 3 | # This file is part of libautoupdate, distributed under the MIT License 4 | # For full terms see the included LICENSE file 5 | 6 | { 7 | 'targets': [ 8 | { 9 | 'target_name': 'libautoupdate', 10 | 'type': 'static_library', 11 | 'sources': [ 12 | 'include/autoupdate.h', 13 | 'src/autoupdate.c', 14 | 'src/autoupdate_internal.h', 15 | 'src/exepath.c', 16 | 'src/inflate.c', 17 | 'src/tmpf.c', 18 | 'src/utils.c', 19 | ], 20 | 'include_dirs': [ 21 | 'include', 22 | '../zlib', 23 | ], 24 | }, 25 | ], 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | 3 | # Prerequisites 4 | *.d 5 | 6 | # Object files 7 | *.o 8 | *.ko 9 | *.obj 10 | *.elf 11 | 12 | # Linker output 13 | *.ilk 14 | *.map 15 | *.exp 16 | 17 | # Precompiled Headers 18 | *.gch 19 | *.pch 20 | 21 | # Libraries 22 | *.lib 23 | *.a 24 | *.la 25 | *.lo 26 | 27 | # Shared objects (inc. Windows DLLs) 28 | *.dll 29 | *.so 30 | *.so.* 31 | *.dylib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | *.i*86 38 | *.x86_64 39 | *.hex 40 | 41 | # Debug files 42 | *.dSYM/ 43 | *.su 44 | *.idb 45 | *.pdb 46 | 47 | # Kernel Module Compile Results 48 | *.mod* 49 | *.cmd 50 | .tmp_versions/ 51 | modules.order 52 | Module.symvers 53 | Mkfile.old 54 | dkms.conf 55 | -------------------------------------------------------------------------------- /include/autoupdate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Minqi Pan et al. 3 | * 4 | * This file is part of libautoupdate, distributed under the MIT License 5 | * For full terms see the included LICENSE file 6 | */ 7 | 8 | #ifndef AUTOUPDATE_H_8C141CA2 9 | #define AUTOUPDATE_H_8C141CA2 10 | 11 | #ifdef _WIN32 12 | 13 | #include 14 | int autoupdate( 15 | int argc, 16 | wchar_t *wargv[], 17 | const char *host, 18 | const char *port, 19 | const char *path, 20 | const char *current, 21 | short force 22 | ); 23 | 24 | #else 25 | 26 | #include 27 | int autoupdate( 28 | int argc, 29 | char *argv[], 30 | const char *host, 31 | uint16_t port, 32 | const char *path, 33 | const char *current, 34 | short force 35 | ); 36 | 37 | #endif // _WIN32 38 | 39 | #endif /* end of include guard: AUTOUPDATE_H_8C141CA2 */ 40 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Minqi Pan et al. 2 | # 3 | # This file is part of libautoupdate, distributed under the MIT License 4 | # For full terms see the included LICENSE file 5 | 6 | PROJECT(libautoupdate C) 7 | CMAKE_MINIMUM_REQUIRED(VERSION 2.6) 8 | 9 | FIND_PACKAGE(ZLIB) 10 | 11 | INCLUDE_DIRECTORIES(src include ${ZLIB_INCLUDE_DIR}) 12 | FILE(GLOB SRC_H include/autoupdate.h) 13 | FILE(GLOB SRC_AUTOUPDATE src/*.c src/*.h) 14 | ADD_LIBRARY(autoupdate ${SRC_H} ${SRC_AUTOUPDATE}) 15 | 16 | IF(BUILD_TESTS) 17 | ENABLE_TESTING() 18 | ADD_TEST(autoupdate_tests autoupdate_tests --help) 19 | FILE(GLOB SRC_TEST tests/*.c) 20 | ADD_EXECUTABLE(autoupdate_tests ${SRC_TEST}) 21 | TARGET_LINK_LIBRARIES(autoupdate_tests autoupdate ${ZLIB_LIBRARIES}) 22 | if(WIN32) 23 | TARGET_LINK_LIBRARIES(autoupdate_tests shlwapi.lib Ws2_32.lib) 24 | endif() 25 | ENDIF() 26 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | 3 | environment: 4 | matrix: 5 | - GENERATOR: "Visual Studio 14 2015 Win64" 6 | ARCH: 64 7 | PlatformToolset: v140 8 | Platform: x64 9 | - GENERATOR: "Visual Studio 12 2013 Win64" 10 | ARCH: 64 11 | PlatformToolset: v120 12 | Platform: x64 13 | 14 | build_script: 15 | - ps: | 16 | nuget install zlib.$env:PlatformToolset.windesktop.msvcstl.dyn.rt-dyn 17 | 18 | cmake --version 19 | 20 | mkdir build 21 | 22 | cd build 23 | 24 | cmake -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release -G"$env:GENERATOR" -DZLIB_INCLUDE_DIR:PATH=C:\projects\libautoupdate\zlib.$env:PlatformToolset.windesktop.msvcstl.dyn.rt-dyn.1.2.8.8\build\native\include -DZLIB_LIBRARY_RELEASE:FILEPATH=C:\projects\libautoupdate\zlib.$env:PlatformToolset.windesktop.msvcstl.dyn.rt-dyn.1.2.8.8\lib\native\$env:PlatformToolset\windesktop\msvcstl\dyn\rt-dyn\$env:Platform\RelWithDebInfo\zlib.lib .. 25 | 26 | cmake --build . --config Release 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Minqi Pan et al. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/autoupdate_internal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Minqi Pan et al. 3 | * 4 | * This file is part of libautoupdate, distributed under the MIT License 5 | * For full terms see the included LICENSE file 6 | */ 7 | 8 | #ifndef AUTOUPDATE_INTERNAL_H_A40E122A 9 | #define AUTOUPDATE_INTERNAL_H_A40E122A 10 | 11 | #include 12 | #include 13 | 14 | #ifdef _WIN32 15 | 16 | #define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) ) 17 | 18 | PACK( 19 | struct ZIPLocalFileHeader 20 | { 21 | uint32_t signature; 22 | uint16_t versionNeededToExtract; // unsupported 23 | uint16_t generalPurposeBitFlag; // unsupported 24 | uint16_t compressionMethod; 25 | uint16_t lastModFileTime; 26 | uint16_t lastModFileDate; 27 | uint32_t crc32; 28 | uint32_t compressedSize; 29 | uint32_t uncompressedSize; 30 | uint16_t fileNameLength; 31 | uint16_t extraFieldLength; // unsupported 32 | }); 33 | 34 | wchar_t* autoupdate_tmpdir(); 35 | wchar_t* autoupdate_tmpf(wchar_t *tmpdir, const char *ext_name); 36 | short autoupdate_should_proceed_24_hours(int argc, wchar_t *wargv[], short will_write); 37 | 38 | #else 39 | 40 | char* autoupdate_tmpdir(); 41 | char* autoupdate_tmpf(char *tmpdir, const char *ext_name); 42 | short autoupdate_should_proceed_24_hours(int argc, char *argv[], short will_write); 43 | 44 | #endif // _WIN32 45 | 46 | short autoupdate_should_proceed(); 47 | int autoupdate_exepath(char* buffer, size_t* size); 48 | 49 | #endif /* end of include guard: AUTOUPDATE_INTERNAL_H_A40E122A */ 50 | -------------------------------------------------------------------------------- /tests/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Minqi Pan et al. 3 | * 4 | * This file is part of libautoupdate, distributed under the MIT License 5 | * For full terms see the included LICENSE file 6 | */ 7 | 8 | #include "autoupdate.h" 9 | #include "autoupdate_internal.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #ifdef _WIN32 21 | #include 22 | #include 23 | #endif 24 | 25 | #ifdef __linux__ 26 | #include 27 | #endif 28 | 29 | #define EXPECT(condition) expect(condition, __FILE__, __LINE__) 30 | 31 | static void expect(short condition, const char *file, int line) 32 | { 33 | if (condition) { 34 | fprintf(stderr, "."); 35 | } 36 | else { 37 | fprintf(stderr, "x"); 38 | fprintf(stderr, "\nFAILED: %s line %d\n", file, line); 39 | exit(1); 40 | } 41 | fflush(stderr); 42 | } 43 | 44 | #ifdef _WIN32 45 | int main(int argc, wchar_t *wargv[]) 46 | #else 47 | int main(int argc, char *argv[]) 48 | #endif 49 | { 50 | int ret; 51 | struct stat statbuf; 52 | size_t exec_path_len; 53 | char* exec_path; 54 | 55 | // test autoupdate_exepath 56 | #ifdef _WIN32 57 | exec_path_len = 2 * MAX_PATH; 58 | #else 59 | exec_path_len = 2 * PATH_MAX; 60 | #endif 61 | exec_path = malloc(exec_path_len); 62 | ret = autoupdate_exepath(exec_path, &exec_path_len); 63 | EXPECT(0 == ret); 64 | 65 | ret = stat(exec_path, &statbuf); 66 | EXPECT(0 == ret); 67 | EXPECT(S_IFREG == (S_IFMT & statbuf.st_mode)); 68 | 69 | // test autoupdate_should_proceed() 70 | autoupdate_should_proceed(); 71 | 72 | // test autoupdate_should_proceed_24_hours() 73 | #ifdef _WIN32 74 | autoupdate_should_proceed_24_hours(argc, wargv, 0); 75 | autoupdate_should_proceed_24_hours(argc, wargv, 1); 76 | autoupdate_should_proceed_24_hours(argc, wargv, 0); 77 | autoupdate_should_proceed_24_hours(argc, wargv, 1); 78 | #else 79 | autoupdate_should_proceed_24_hours(argc, argv, 0); 80 | autoupdate_should_proceed_24_hours(argc, argv, 1); 81 | autoupdate_should_proceed_24_hours(argc, argv, 0); 82 | autoupdate_should_proceed_24_hours(argc, argv, 1); 83 | #endif 84 | 85 | // test autoupdate() 86 | #ifdef _WIN32 87 | autoupdate( 88 | argc, 89 | wargv, 90 | "enclose.io", 91 | "80", 92 | "/rubyc/rubyc-x64.zip", 93 | "---^_^---", 94 | 1 95 | ); 96 | #endif 97 | 98 | #ifdef __linux__ 99 | autoupdate( 100 | argc, 101 | argv, 102 | "enclose.io", 103 | 80, 104 | "/rubyc/rubyc-linux-x64.gz", 105 | "---^_^---", 106 | 1 107 | ); 108 | #endif 109 | 110 | #ifdef __APPLE__ 111 | autoupdate( 112 | argc, 113 | argv, 114 | "enclose.io", 115 | 80, 116 | "/rubyc/rubyc-darwin-x64.gz", 117 | "---^_^---", 118 | 1 119 | ); 120 | #endif 121 | // should never reach this point 122 | return 1; 123 | } 124 | -------------------------------------------------------------------------------- /src/tmpf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Minqi Pan et al. 3 | * 4 | * This file is part of libautoupdate, distributed under the MIT License 5 | * For full terms see the included LICENSE file 6 | */ 7 | 8 | #include "autoupdate.h" 9 | #include "autoupdate_internal.h" 10 | 11 | #include 12 | #include 13 | 14 | #ifdef _WIN32 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | wchar_t* autoupdate_tmpdir() 22 | { 23 | const int squash_win32_buf_sz = 32767; 24 | wchar_t squash_win32_buf[32767 + 1]; 25 | DWORD length; 26 | 27 | length = GetEnvironmentVariableW(L"TEMP", squash_win32_buf, squash_win32_buf_sz); 28 | if (length) { 29 | goto out; 30 | } 31 | length = GetEnvironmentVariableW(L"TMP", squash_win32_buf, squash_win32_buf_sz); 32 | if (length) { 33 | goto out; 34 | } 35 | length = GetEnvironmentVariableW(L"SystemRoot", squash_win32_buf, squash_win32_buf_sz); 36 | if (!length) { 37 | length = GetEnvironmentVariableW(L"windir", squash_win32_buf, squash_win32_buf_sz); 38 | } 39 | if (length) { 40 | if (length + 5 >= squash_win32_buf_sz) { 41 | return NULL; 42 | } 43 | squash_win32_buf[length] = L'\\'; 44 | squash_win32_buf[length + 1] = L't'; 45 | squash_win32_buf[length + 2] = L'e'; 46 | squash_win32_buf[length + 3] = L'm'; 47 | squash_win32_buf[length + 4] = L'p'; 48 | squash_win32_buf[length + 5] = 0; 49 | length += 5; 50 | goto out; 51 | } 52 | return NULL; 53 | out: 54 | if (length >= 2 && L'\\' == squash_win32_buf[length - 1] && L':' != squash_win32_buf[length - 2]) { 55 | squash_win32_buf[length - 1] = 0; 56 | length -= 1; 57 | } 58 | return wcsdup(squash_win32_buf); 59 | } 60 | 61 | wchar_t* autoupdate_tmpf(wchar_t *tmpdir, const char *ext_name) 62 | { 63 | const int squash_win32_buf_sz = 32767; 64 | wchar_t squash_win32_buf[32767 + 1]; 65 | size_t curlen, size_ret; 66 | int try_cnt = 0; 67 | srand(time(NULL) * getpid()); 68 | squash_win32_buf[squash_win32_buf_sz] = 0; 69 | while (try_cnt < 3) { 70 | squash_win32_buf[0] = 0; 71 | assert(0 == wcslen(squash_win32_buf)); 72 | wcsncat(squash_win32_buf + wcslen(squash_win32_buf), tmpdir, squash_win32_buf_sz - wcslen(squash_win32_buf)); 73 | wcsncat(squash_win32_buf + wcslen(squash_win32_buf), L"\\libautoupdate-", squash_win32_buf_sz - wcslen(squash_win32_buf)); 74 | // up to 33 characters for _itoa 75 | if (squash_win32_buf_sz - wcslen(squash_win32_buf) <= 33) { 76 | return NULL; 77 | } 78 | _itow(rand(), squash_win32_buf + wcslen(squash_win32_buf), 10); 79 | if (ext_name) { 80 | wcsncat(squash_win32_buf + wcslen(squash_win32_buf), L".", squash_win32_buf_sz - wcslen(squash_win32_buf)); 81 | } 82 | if (ext_name) { 83 | curlen = wcslen(squash_win32_buf); 84 | size_ret = mbstowcs((wchar_t*)(squash_win32_buf) + curlen, ext_name, squash_win32_buf_sz - curlen); 85 | if ((size_t)-1 == size_ret) { 86 | return NULL; 87 | } 88 | *((wchar_t*)(squash_win32_buf) + curlen + size_ret) = 0; 89 | } 90 | if (!PathFileExistsW(squash_win32_buf)) { 91 | return wcsdup(squash_win32_buf); 92 | } 93 | ++try_cnt; 94 | } 95 | return NULL; 96 | } 97 | 98 | #else 99 | 100 | #include 101 | #include 102 | #include 103 | #include 104 | #include 105 | 106 | char* autoupdate_tmpdir() 107 | { 108 | char *try_try; 109 | size_t length; 110 | try_try = getenv("TMPDIR"); 111 | if (try_try) { 112 | goto out; 113 | } 114 | try_try = getenv("TMP"); 115 | if (try_try) { 116 | goto out; 117 | } 118 | try_try = getenv("TEMP"); 119 | if (try_try) { 120 | goto out; 121 | } 122 | try_try = "/tmp"; 123 | out: 124 | try_try = strdup(try_try); 125 | length = strlen(try_try); 126 | if (length >= 2 && '/' == try_try[length - 1]) { 127 | try_try[length - 1] = 0; 128 | } 129 | return try_try; 130 | } 131 | 132 | char* autoupdate_tmpf(char *tmpdir, const char *ext_name) 133 | { 134 | const int squash_buf_sz = 32767; 135 | char squash_buf[squash_buf_sz + 1]; 136 | int ret, try_cnt = 0; 137 | struct stat statbuf; 138 | 139 | srand(time(NULL) * getpid()); 140 | while (try_cnt < 3) { 141 | if (ext_name) { 142 | ret = snprintf(squash_buf, squash_buf_sz, "%s/libautoupdate-%d.%s", tmpdir, rand(), ext_name); 143 | } else { 144 | ret = snprintf(squash_buf, squash_buf_sz, "%s/libautoupdate-%d", tmpdir, rand()); 145 | } 146 | if (-1 == ret) { 147 | return NULL; 148 | } 149 | if (-1 == stat(squash_buf, &statbuf)) { 150 | return strdup(squash_buf); 151 | } 152 | ++try_cnt; 153 | } 154 | return NULL; 155 | } 156 | 157 | #endif // _WIN32 158 | -------------------------------------------------------------------------------- /src/exepath.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Minqi Pan et al. 3 | * 4 | * This file is part of libautoupdate, distributed under the MIT License 5 | * For full terms see the included LICENSE file 6 | */ 7 | 8 | /* 9 | * autoupdate_exepath is derived from uv_exepath of libuv. 10 | * libuv is licensed for use as follows: 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to 14 | * deal in the Software without restriction, including without limitation the 15 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 16 | * sell copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 28 | * IN THE SOFTWARE. 29 | */ 30 | 31 | #include "autoupdate.h" 32 | #include "autoupdate_internal.h" 33 | 34 | #ifdef _WIN32 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | int autoupdate_exepath(char* buffer, size_t* size_ptr) { 46 | int utf8_len, utf16_buffer_len, utf16_len; 47 | WCHAR* utf16_buffer; 48 | int err; 49 | 50 | if (buffer == NULL || size_ptr == NULL || *size_ptr == 0) { 51 | return -1; 52 | } 53 | 54 | if (*size_ptr > 32768) { 55 | /* Windows paths can never be longer than this. */ 56 | utf16_buffer_len = 32768; 57 | } else { 58 | utf16_buffer_len = (int) *size_ptr; 59 | } 60 | 61 | utf16_buffer = (WCHAR*) malloc(sizeof(WCHAR) * utf16_buffer_len); 62 | if (!utf16_buffer) { 63 | return -1; 64 | } 65 | 66 | /* Get the path as UTF-16. */ 67 | utf16_len = GetModuleFileNameW(NULL, utf16_buffer, utf16_buffer_len); 68 | if (utf16_len <= 0) { 69 | err = GetLastError(); 70 | goto error; 71 | } 72 | 73 | /* utf16_len contains the length, *not* including the terminating null. */ 74 | utf16_buffer[utf16_len] = L'\0'; 75 | 76 | /* Convert to UTF-8 */ 77 | utf8_len = WideCharToMultiByte(CP_UTF8, 78 | 0, 79 | utf16_buffer, 80 | -1, 81 | buffer, 82 | (int) *size_ptr, 83 | NULL, 84 | NULL); 85 | if (utf8_len == 0) { 86 | err = GetLastError(); 87 | goto error; 88 | } 89 | 90 | free(utf16_buffer); 91 | 92 | /* utf8_len *does* include the terminating null at this point, but the */ 93 | /* returned size shouldn't. */ 94 | *size_ptr = utf8_len - 1; 95 | return 0; 96 | 97 | error: 98 | free(utf16_buffer); 99 | return -1; 100 | } 101 | 102 | #endif 103 | 104 | #ifdef __linux__ 105 | #include 106 | 107 | int autoupdate_exepath(char* buffer, size_t* size) { 108 | ssize_t n; 109 | 110 | if (buffer == NULL || size == NULL || *size == 0) 111 | return -1; 112 | 113 | n = *size - 1; 114 | if (n > 0) 115 | n = readlink("/proc/self/exe", buffer, n); 116 | 117 | if (n == -1) 118 | return -1; 119 | 120 | buffer[n] = '\0'; 121 | *size = n; 122 | 123 | return 0; 124 | } 125 | #endif 126 | 127 | #ifdef __APPLE__ 128 | #include 129 | #include 130 | #include 131 | #include 132 | #include // PATH_MAX 133 | 134 | int autoupdate_exepath(char* buffer, size_t* size) { 135 | /* realpath(exepath) may be > PATH_MAX so double it to be on the safe side. */ 136 | char abspath[PATH_MAX * 2 + 1]; 137 | char exepath[PATH_MAX + 1]; 138 | uint32_t exepath_size; 139 | size_t abspath_size; 140 | 141 | if (buffer == NULL || size == NULL || *size == 0) 142 | return -1; 143 | 144 | exepath_size = sizeof(exepath); 145 | if (_NSGetExecutablePath(exepath, &exepath_size)) 146 | return -1; 147 | 148 | if (realpath(exepath, abspath) != abspath) 149 | return -1; 150 | 151 | abspath_size = strlen(abspath); 152 | if (abspath_size == 0) 153 | return -1; 154 | 155 | *size -= 1; 156 | if (*size > abspath_size) 157 | *size = abspath_size; 158 | 159 | memcpy(buffer, abspath, *size); 160 | buffer[*size] = '\0'; 161 | 162 | return 0; 163 | } 164 | #endif 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Libautoupdate 2 | 3 | Cross-platform C library that enables your application to auto-update itself in place. 4 | 5 | [![Build Status](https://travis-ci.org/pmq20/libautoupdate.svg?branch=master)](https://travis-ci.org/pmq20/libautoupdate) 6 | [![Build status](https://ci.appveyor.com/api/projects/status/sjdyfwd768lh187f/branch/master?svg=true)](https://ci.appveyor.com/project/pmq20/libautoupdate/branch/master) 7 | 8 | ![Terminal simulation of a simple auto-update](https://github.com/pmq20/libautoupdate/raw/master/doc/libautoupdate.gif) 9 | 10 | ## API 11 | 12 | There is only one public API, i.e. `autoupdate()`. 13 | 14 | ```C 15 | int autoupdate(argc, argv, host, port, path, current) 16 | ``` 17 | 18 | It accepts the following arguments: 19 | 20 | - the 1st and 2nd arguments are the same as those passed to `main()` 21 | - `host` is the host name of the update server to communicate with 22 | - `port` is the port of the server, which is a string on Windows and a 16-bit integer on macOS / Linux 23 | - `path` is the paramater passed to the HTTP/1.0 HEAD request of the Round 1 24 | - `current` is the current version string to be compared with what is returned from the server 25 | - a new version is considered detected if this string is not a substring of the server's reply 26 | 27 | It never returns if a new version was detected and auto-update was successfully performed. 28 | Otherwise, it returns one of the following integers to indicate the situation: 29 | 30 | | Return Value | Indication | 31 | |:--------------:|---------------------------------------------------------------------------------------------| 32 | | 0 | Latest version confirmed. No need to update | 33 | | 1 | Auto-update shall not proceed due to environment variable `CI` being set | 34 | | 2 | Auto-update process failed prematurely and detailed errors are printed to stderr | 35 | | 3 | Failed to restart after replacing itself with the new version | 36 | | 4 | Auto-update shall not proceed due to being already checked in the last 24 hours | 37 | 38 | ## Communication 39 | 40 | ### Round 1 41 | 42 | Libautoupdate first makes a HTTP/1.0 HEAD request to the server, in order to peek the latest version string. 43 | 44 | Libautoupdate -- HTTP/1.0 HEAD request --> Server 45 | 46 | The server is expected to repond with `HTTP 302 Found` and provide a `Location` header. 47 | 48 | It then compares the content of `Location` header with the current version string. 49 | It proceeds to Round 2 if the current version string is NOT a sub-string of the `Location` header. 50 | 51 | ### Round 2 52 | 53 | Libautoupdate makes a full HTTP/1.0 GET request to the `Location` header of the last round. 54 | 55 | Libautoupdate -- HTTP/1.0 GET request --> Server 56 | 57 | The server is expected to respond with `200 OK` transferring the new release itself. 58 | 59 | Based on the `Content-Type` header received, an addtional inflation operation might be performed: 60 | - `Content-Type: application/x-gzip`: Gzip Inflation is performed 61 | - `Content-Type: application/zip`: Deflate compression is assumed and the first file is inflated and used 62 | 63 | ## Self-replacing 64 | 65 | After 2 rounds of communication with the server, 66 | libautoupdate will then proceeds with a self-replacing process, 67 | i.e. the program replaces itself in-place with the help of the system temporary directory, 68 | after which it restarts itself with the new release. 69 | 70 | ## Examples 71 | 72 | Just call `autoupdate()` at the beginning of your `main()`, 73 | before all actual logic of your application. 74 | See the following code for examples. 75 | 76 | ### Windows 77 | 78 | ```C 79 | #include 80 | 81 | int wmain(int argc, wchar_t *wargv[]) 82 | { 83 | int autoupdate_result; 84 | 85 | autoupdate_result = autoupdate( 86 | argc, 87 | wargv, 88 | "enclose.io", 89 | "80", 90 | "/nodec/nodec-x64.exe" 91 | "v1.1.0" 92 | ); 93 | 94 | /* 95 | actual logic of your application 96 | ... 97 | */ 98 | } 99 | ``` 100 | 101 | ### macOS / Linux 102 | 103 | ```C 104 | #include 105 | 106 | int main(int argc, char *argv[]) 107 | { 108 | int autoupdate_result; 109 | 110 | autoupdate_result = autoupdate( 111 | argc, 112 | argv, 113 | "enclose.io", 114 | 80, 115 | "/nodec/nodec-darwin-x64" 116 | "v1.1.0" 117 | ); 118 | 119 | /* 120 | actual logic of your application 121 | ... 122 | */ 123 | } 124 | ``` 125 | 126 | ## Hints 127 | 128 | - Set environment variable `CI=true` will prevent auto-updating 129 | - Remove the file `~/.libautoupdate` will remove the once-per-24-hours check limit 130 | 131 | ## To-do 132 | 133 | - Cater to bad network connection situations 134 | - Print more information about the new version 135 | - Use better error messages when the user did not have permissions to move the new version into the destination directory 136 | - Move the old binary to the system temporary directory, yet not deleting it. 137 | - The Operating System will delete it when restarted/tmpdir-full 138 | - Add facility to restore/rollback to the old file once the new version went wrong 139 | 140 | ## Authors 141 | 142 | [Minqi Pan et al.](https://raw.githubusercontent.com/pmq20/libautoupdate/master/AUTHORS) 143 | 144 | ## License 145 | 146 | [MIT](https://raw.githubusercontent.com/pmq20/libautoupdate/master/LICENSE) 147 | 148 | ## See Also 149 | 150 | - [Node.js Packer](https://github.com/pmq20/node-packer): Packing your Node.js application into a single executable. 151 | - [Ruby Packer](https://github.com/pmq20/ruby-packer): Packing your Ruby application into a single executable. 152 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Minqi Pan et al. 3 | * 4 | * This file is part of libautoupdate, distributed under the MIT License 5 | * For full terms see the included LICENSE file 6 | */ 7 | 8 | #include "autoupdate.h" 9 | #include "autoupdate_internal.h" 10 | #include 11 | 12 | #ifdef _WIN32 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | short autoupdate_should_proceed() 26 | { 27 | TCHAR lpBuffer[32767 + 1]; 28 | if (0 == GetEnvironmentVariable("CI", lpBuffer, 32767)) { 29 | return 1; 30 | } else { 31 | return 0; 32 | } 33 | } 34 | 35 | #else 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | short autoupdate_should_proceed() 47 | { 48 | if (NULL == getenv("CI")) { 49 | return 1; 50 | } else { 51 | return 0; 52 | } 53 | } 54 | 55 | #endif // _WIN32 56 | 57 | #ifdef _WIN32 58 | short autoupdate_should_proceed_24_hours(int argc, wchar_t *wargv[], short will_write) 59 | { 60 | const KNOWNFOLDERID rfid = FOLDERID_Profile; 61 | PWSTR ppszPath = NULL; 62 | HRESULT hret; 63 | wchar_t filepath[2 * 32768]; 64 | const wchar_t *filename = L"\\.libautoupdate"; 65 | size_t exec_path_len = 2 * 32768; 66 | char exec_path[2 * 32768]; 67 | #else 68 | short autoupdate_should_proceed_24_hours(int argc, char *argv[], short will_write) 69 | { 70 | char *filepath = NULL; 71 | const char *filename = "/.libautoupdate"; 72 | size_t exec_path_len = 2 * PATH_MAX; 73 | char exec_path[2 * PATH_MAX]; 74 | struct passwd *pw; 75 | const char *homedir; 76 | #endif // _WIN32 77 | short has_written = 0; 78 | time_t time_now; 79 | long item_time; 80 | char *item_string = NULL; 81 | char *item_space; 82 | char *cursor; 83 | char *string = NULL; 84 | char *string0 = NULL; 85 | long fsize; 86 | FILE *f = NULL; 87 | int ret; 88 | size_t size_t_ret; 89 | 90 | if (autoupdate_exepath(exec_path, &exec_path_len) != 0) { 91 | #ifdef _WIN32 92 | goto exit; 93 | #else 94 | if (!argv[0]) { 95 | goto exit; 96 | } 97 | assert(strlen(argv[0]) < 2 * PATH_MAX); 98 | memcpy(exec_path, argv[0], strlen(argv[0])); 99 | #endif 100 | } 101 | 102 | time_now = time(NULL); 103 | if ((time_t)-1 == time_now) { 104 | goto exit; 105 | } 106 | #ifdef _WIN32 107 | hret = SHGetKnownFolderPath( 108 | &rfid, 109 | 0, 110 | NULL, 111 | &ppszPath 112 | ); 113 | if (S_OK != hret) { 114 | goto exit; 115 | } 116 | memcpy(filepath, ppszPath, wcslen(ppszPath) * sizeof(wchar_t)); 117 | memcpy(filepath + wcslen(ppszPath), filename, wcslen(filename) * sizeof(wchar_t)); 118 | filepath[wcslen(ppszPath) + wcslen(filename)] = 0; 119 | f = _wfopen(filepath, L"rb"); 120 | #else 121 | pw = getpwuid(getuid()); 122 | if (NULL == pw) { 123 | goto exit; 124 | } 125 | homedir = pw->pw_dir; 126 | if (NULL == homedir) { 127 | goto exit; 128 | } 129 | filepath = malloc(strlen(homedir) + strlen(filename) + 1); 130 | if (NULL == filepath) { 131 | goto exit; 132 | } 133 | memcpy(filepath, homedir, strlen(homedir)); 134 | memcpy(filepath + strlen(homedir), filename, strlen(filename)); 135 | filepath[strlen(homedir) + strlen(filename)] = 0; 136 | f = fopen(filepath, "rb"); 137 | #endif // _WIN32 138 | if (NULL == f) { 139 | if (will_write) { 140 | string0 = NULL; 141 | goto write; 142 | } else { 143 | goto exit; 144 | } 145 | } 146 | ret = fseek(f, 0, SEEK_END); 147 | if (0 != ret) { 148 | goto exit; 149 | } 150 | fsize = ftell(f); 151 | if (fsize <= 0) { 152 | goto exit; 153 | } 154 | ret = fseek(f, 0, SEEK_SET); 155 | if (0 != ret) { 156 | goto exit; 157 | } 158 | 159 | string = malloc(fsize + 1); 160 | if (NULL == string) { 161 | goto exit; 162 | } 163 | string0 = string; 164 | size_t_ret = fread(string, fsize, 1, f); 165 | if (1 != size_t_ret) { 166 | goto exit; 167 | } 168 | string[fsize] = 0; 169 | ret = fclose(f); 170 | if (0 != ret) { 171 | goto exit; 172 | } 173 | f = NULL; 174 | string[fsize] = 0; 175 | while (string < string0 + fsize) { 176 | cursor = strchr(string, '\n'); 177 | if (!cursor) { 178 | if (will_write) { 179 | string0 = NULL; 180 | goto write; 181 | } else { 182 | goto exit; 183 | } 184 | } 185 | *cursor = 0; 186 | item_space = strchr(string, ' '); 187 | if (!item_space) { 188 | goto exit; 189 | } 190 | *item_space = 0; 191 | item_time = atol(string); 192 | item_string = item_space + 1; 193 | if (exec_path_len == cursor - item_string && 0 == memcmp(item_string, exec_path, exec_path_len)) { 194 | if (will_write) { 195 | if (item_time >= 1000000000 && time_now >= 1000000000) { 196 | has_written = 1; 197 | #ifdef _WIN32 198 | _ltoa(time_now, string, 10); 199 | #else 200 | ret = sprintf(string, "%ld", time_now); 201 | #endif // _WIN32 202 | string[10] = ' '; 203 | *cursor = '\n'; 204 | break; 205 | } 206 | } else if (time_now - item_time < 24 * 3600) { 207 | return 0; 208 | } 209 | } 210 | *item_space = ' '; 211 | *cursor = '\n'; 212 | string = cursor + 1; 213 | } 214 | write: 215 | if (will_write) { 216 | #ifdef _WIN32 217 | f = _wfopen(filepath, L"wb"); 218 | #else 219 | f = fopen(filepath, "wb"); 220 | #endif // _WIN32 221 | if (NULL == f) { 222 | goto exit; 223 | } 224 | if (string0) { 225 | ret = fwrite(string0, fsize, 1, f); 226 | if (1 != ret) { 227 | goto exit; 228 | } 229 | } 230 | if (!has_written) { 231 | char writting[20]; 232 | #ifdef _WIN32 233 | _ltoa(time_now, writting, 10); 234 | ret = fwrite(writting, strlen(writting), 1, f); 235 | if (1 != ret) { 236 | goto exit; 237 | } 238 | ret = fwrite(" ", 1, 1, f); 239 | if (1 != ret) { 240 | goto exit; 241 | } 242 | #else 243 | ret = sprintf(writting, "%ld ", time_now); 244 | ret = fwrite(writting, strlen(writting), 1, f); 245 | if (1 != ret) { 246 | goto exit; 247 | } 248 | #endif // _WIN32 249 | ret = fwrite(exec_path, exec_path_len, 1, f); 250 | if (1 != ret) { 251 | goto exit; 252 | } 253 | ret = fwrite("\n", 1, 1, f); 254 | if (1 != ret) { 255 | goto exit; 256 | } 257 | } 258 | } 259 | 260 | exit: 261 | if (f) { 262 | fclose(f); 263 | } 264 | if (string0) { 265 | free(string0); 266 | } 267 | #ifdef _WIN32 268 | if (ppszPath) { 269 | CoTaskMemFree(ppszPath); 270 | } 271 | #else 272 | if (filepath) { 273 | free(filepath); 274 | } 275 | #endif 276 | return 1; 277 | } 278 | -------------------------------------------------------------------------------- /src/autoupdate.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Minqi Pan et al. 3 | * 4 | * This file is part of libautoupdate, distributed under the MIT License 5 | * For full terms see the included LICENSE file 6 | */ 7 | 8 | #include "autoupdate.h" 9 | #include "autoupdate_internal.h" 10 | #include "zlib.h" 11 | 12 | #ifdef _WIN32 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include /* exit */ 22 | #include 23 | 24 | int autoupdate( 25 | int argc, 26 | wchar_t *wargv[], 27 | const char *host, 28 | const char *port, 29 | const char *path, 30 | const char *current, 31 | short force 32 | ) 33 | { 34 | WSADATA wsaData; 35 | 36 | if (!force && !autoupdate_should_proceed()) { 37 | return 1; 38 | } 39 | 40 | if (!force && !autoupdate_should_proceed_24_hours(argc, wargv, 0)) { 41 | return 4; 42 | } 43 | 44 | // Initialize Winsock 45 | int iResult = WSAStartup(MAKEWORD(2,2), &wsaData); 46 | if (iResult != 0) { 47 | fprintf(stderr, "Auto-update Failed: WSAStartup failed with %d\n", iResult); 48 | return 2; 49 | } 50 | 51 | struct addrinfo *result = NULL, 52 | *ptr = NULL, 53 | hints; 54 | 55 | ZeroMemory(&hints, sizeof(hints)); 56 | hints.ai_family = AF_UNSPEC; 57 | hints.ai_socktype = SOCK_STREAM; 58 | hints.ai_protocol = IPPROTO_TCP; 59 | 60 | // Resolve the server address and port 61 | iResult = getaddrinfo(host, port, &hints, &result); 62 | if (iResult != 0) { 63 | fprintf(stderr, "Auto-update Failed: getaddrinfo failed with %d\n", iResult); 64 | WSACleanup(); 65 | return 2; 66 | } 67 | 68 | SOCKET ConnectSocket = INVALID_SOCKET; 69 | 70 | // Attempt to connect to the first address returned by 71 | // the call to getaddrinfo 72 | ptr = result; 73 | 74 | // Create a SOCKET for connecting to server 75 | ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, 76 | ptr->ai_protocol); 77 | 78 | if (ConnectSocket == INVALID_SOCKET) { 79 | fprintf(stderr, "Auto-update Failed: Error at socket() with %d\n", WSAGetLastError()); 80 | freeaddrinfo(result); 81 | WSACleanup(); 82 | return 2; 83 | } 84 | 85 | // Connect to server. 86 | iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); 87 | if (iResult == SOCKET_ERROR) { 88 | closesocket(ConnectSocket); 89 | ConnectSocket = INVALID_SOCKET; 90 | } 91 | freeaddrinfo(result); 92 | if (ConnectSocket == INVALID_SOCKET) { 93 | fprintf(stderr, "Auto-update Failed: connect failed on %s and port %s\n", host, port); 94 | WSACleanup(); 95 | return 2; 96 | } 97 | if (5 != send(ConnectSocket, "HEAD ", 5, 0) || 98 | strlen(path) != send(ConnectSocket, path, strlen(path), 0) || 99 | 11 != send(ConnectSocket, " HTTP/1.0\r\n", 11, 0) || 100 | 6 != send(ConnectSocket, "Host: ", 6, 0) || 101 | strlen(host) != send(ConnectSocket, host, strlen(host), 0) || 102 | 4 != send(ConnectSocket, "\r\n\r\n", 4, 0)) { 103 | fprintf(stderr, "Auto-update Failed: send failed with %d\n", WSAGetLastError()); 104 | closesocket(ConnectSocket); 105 | WSACleanup(); 106 | return 2; 107 | } 108 | 109 | char response[1024 * 10 + 1]; // 10KB 110 | int bytes, total; 111 | total = sizeof(response) - 2; 112 | long long received = 0; 113 | do { 114 | bytes = recv(ConnectSocket, response + received, total - received, 0); 115 | if (bytes < 0) { 116 | fprintf(stderr, "Auto-update Failed: recv failed with %d\n", WSAGetLastError()); 117 | closesocket(ConnectSocket); 118 | WSACleanup(); 119 | return 2; 120 | } 121 | if (bytes == 0) { 122 | /* EOF */ 123 | *(response + received) = 0; 124 | break; 125 | } 126 | received += bytes; 127 | } while (received < total); 128 | if (received == total) { 129 | fprintf(stderr, "Auto-update Failed: read causes buffer full\n"); 130 | closesocket(ConnectSocket); 131 | WSACleanup(); 132 | return 2; 133 | } 134 | 135 | // shutdown the connection for sending since no more data will be sent 136 | // the client can still use the ConnectSocket for receiving data 137 | iResult = shutdown(ConnectSocket, SD_SEND); 138 | if (iResult == SOCKET_ERROR) { 139 | fprintf(stderr, "Auto-update Failed: shutdown failed with %d\n", WSAGetLastError()); 140 | closesocket(ConnectSocket); 141 | WSACleanup(); 142 | return 2; 143 | } 144 | 145 | assert(received < total); 146 | size_t len = strlen(response); 147 | short again_302 = 0; 148 | parse_location_header: 149 | assert(len <= total); 150 | char *new_line = NULL; 151 | char *found = NULL; 152 | size_t i = 0; 153 | response[sizeof(response) - 1] = 0; 154 | while (i < len) { 155 | new_line = strstr(response + i, "\r\n"); 156 | if (NULL == new_line) { 157 | break; 158 | } 159 | *new_line = 0; 160 | if (0 == strncmp(response + i, "Location: ", 10)) { 161 | found = response + i + 10; 162 | break; 163 | } 164 | *new_line = '\r'; 165 | i = new_line - response + 2; 166 | } 167 | if (!found) { 168 | fprintf(stderr, "Auto-update Failed: failed to find a Location header\n"); 169 | return 2; 170 | } 171 | if (!again_302) { 172 | if (strstr(found, current)) { 173 | /* Latest version confirmed. No need to update */ 174 | autoupdate_should_proceed_24_hours(argc, wargv, 1); 175 | return 0; 176 | } else { 177 | fprintf(stderr, "Hint: to disable auto-update, run with environment variable CI=true\n"); 178 | fflush(stderr); 179 | } 180 | } 181 | 182 | char *url = found; 183 | fprintf(stderr, "Downloading update from %s\n", url); 184 | fflush(stderr); 185 | 186 | char *host2; 187 | char *port2 = "80"; 188 | if (strlen(url) >= 8 && 0 == strncmp("https://", url, 8)) { 189 | host2 = url + 8; 190 | } else if (strlen(url) >= 7 && 0 == strncmp("http://", url, 7)) { 191 | host2 = url + 7; 192 | } else { 193 | fprintf(stderr, "Auto-update Failed: failed to find http:// or https:// at the beginning of URL %s\n", url); 194 | return 2; 195 | } 196 | char *found_slash = strchr(host2, '/'); 197 | char *request_path; 198 | if (NULL == found_slash) { 199 | request_path = "/"; 200 | } else { 201 | request_path = found_slash; 202 | *found_slash = 0; 203 | } 204 | 205 | result = NULL; 206 | ptr = NULL; 207 | ZeroMemory(&hints, sizeof(hints)); 208 | hints.ai_family = AF_UNSPEC; 209 | hints.ai_socktype = SOCK_STREAM; 210 | hints.ai_protocol = IPPROTO_TCP; 211 | 212 | // Resolve the server address and port 213 | iResult = getaddrinfo(host2, port2, &hints, &result); 214 | if (iResult != 0) { 215 | fprintf(stderr, "Auto-update Failed: getaddrinfo failed with %d\n", iResult); 216 | WSACleanup(); 217 | return 2; 218 | } 219 | 220 | ConnectSocket = INVALID_SOCKET; 221 | 222 | // Attempt to connect to the first address returned by 223 | // the call to getaddrinfo 224 | ptr = result; 225 | 226 | // Create a SOCKET for connecting to server 227 | ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); 228 | 229 | if (ConnectSocket == INVALID_SOCKET) { 230 | fprintf(stderr, "Auto-update Failed: Error at socket() with %d\n", WSAGetLastError()); 231 | freeaddrinfo(result); 232 | WSACleanup(); 233 | return 2; 234 | } 235 | // Connect to server. 236 | iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); 237 | if (iResult == SOCKET_ERROR) { 238 | closesocket(ConnectSocket); 239 | ConnectSocket = INVALID_SOCKET; 240 | } 241 | freeaddrinfo(result); 242 | if (ConnectSocket == INVALID_SOCKET) { 243 | fprintf(stderr, "Auto-update Failed: connect failed on %s and port %s\n", host2, port2); 244 | WSACleanup(); 245 | return 2; 246 | } 247 | if (NULL != found_slash) { 248 | *found_slash = '/'; 249 | } 250 | if (4 != send(ConnectSocket, "GET ", 4, 0) || 251 | strlen(request_path) != send(ConnectSocket, request_path, strlen(request_path), 0) || 252 | 11 != send(ConnectSocket, " HTTP/1.0\r\n", 11, 0)) { 253 | fprintf(stderr, "Auto-update Failed: send failed with %d\n", WSAGetLastError()); 254 | closesocket(ConnectSocket); 255 | WSACleanup(); 256 | return 2; 257 | } 258 | if (NULL != found_slash) { 259 | *found_slash = 0; 260 | } 261 | if (6 != send(ConnectSocket, "Host: ", 6, 0) || 262 | strlen(host2) != send(ConnectSocket, host2, strlen(host2), 0) || 263 | 4 != send(ConnectSocket, "\r\n\r\n", 4, 0)) { 264 | fprintf(stderr, "Auto-update Failed: send failed with %d\n", WSAGetLastError()); 265 | closesocket(ConnectSocket); 266 | WSACleanup(); 267 | return 2; 268 | } 269 | 270 | // Read the header 271 | total = sizeof(response) - 2; 272 | response[sizeof(response) - 1] = 0; 273 | received = 0; 274 | char *header_end = NULL; 275 | do { 276 | bytes = recv(ConnectSocket, response + received, total - received, 0); 277 | if (bytes < 0) { 278 | fprintf(stderr, "Auto-update Failed: recv failed with %d\n", WSAGetLastError()); 279 | closesocket(ConnectSocket); 280 | WSACleanup(); 281 | return 2; 282 | } 283 | if (bytes == 0) { 284 | /* EOF */ 285 | *(response + received) = 0; 286 | break; 287 | } 288 | *(response + received + bytes) = 0; 289 | header_end = strstr(response + received, "\r\n\r\n"); 290 | received += bytes; 291 | if (header_end) { 292 | break; 293 | } 294 | } while (received < total); 295 | if (NULL == header_end) { 296 | fprintf(stderr, "Auto-update Failed: failed to find the end of the response header\n"); 297 | closesocket(ConnectSocket); 298 | WSACleanup(); 299 | return 2; 300 | } 301 | assert(received <= total); 302 | 303 | // Possible new 302 304 | if (received > 13 && ( 305 | 0 == strncmp(response, "HTTP/1.1 302 ", 13) || 306 | 0 == strncmp(response, "HTTP/1.0 302 ", 13))) { 307 | len = received; 308 | again_302 = 1; 309 | goto parse_location_header; 310 | } 311 | 312 | // Parse the header 313 | len = received; 314 | assert(len <= total); 315 | new_line = NULL; 316 | long long found_length = -1; 317 | i = 0; 318 | response[sizeof(response) - 1] = 0; 319 | while (i < len) { 320 | new_line = strstr(response + i, "\r\n"); 321 | if (NULL == new_line) { 322 | break; 323 | } 324 | *new_line = 0; 325 | if (0 == strncmp(response + i, "Content-Length: ", 16)) { 326 | found_length = atoll(response + i + 16); 327 | break; 328 | } 329 | *new_line = '\r'; 330 | i = new_line - response + 2; 331 | } 332 | if (-1 == found_length) { 333 | fprintf(stderr, "Auto-update Failed: failed to find a Content-Length header\n"); 334 | closesocket(ConnectSocket); 335 | WSACleanup(); 336 | return 2; 337 | } 338 | if (0 == found_length) { 339 | fprintf(stderr, "Auto-update Failed: found a Content-Length header of zero\n"); 340 | closesocket(ConnectSocket); 341 | WSACleanup(); 342 | return 2; 343 | } 344 | assert(found_length > 0); 345 | // Read the body 346 | // header_end -> \r\n\r\n 347 | assert(header_end); 348 | assert(header_end + 4 <= response + received); 349 | // put the rest of over-read content when reading header 350 | size_t the_rest = response + received - (header_end + 4); 351 | char *body_buffer = (char *)(malloc(found_length)); 352 | if (NULL == body_buffer) { 353 | fprintf(stderr, "Auto-update Failed: Insufficient memory\n"); 354 | closesocket(ConnectSocket); 355 | WSACleanup(); 356 | return 2; 357 | } 358 | memcpy(body_buffer, (header_end + 4), the_rest); 359 | char *body_buffer_ptr = body_buffer + the_rest; 360 | char *body_buffer_end = body_buffer + found_length; 361 | // read the remaining body 362 | received = the_rest; 363 | fprintf(stderr, "\r%lld / %lld bytes finished (%lld%%)", received, found_length, received*100LL/found_length); 364 | fflush(stderr); 365 | while (received < found_length) { 366 | size_t space = 100 * 1024; 367 | if (space > body_buffer_end - body_buffer_ptr) { 368 | space = body_buffer_end - body_buffer_ptr; 369 | } 370 | bytes = recv(ConnectSocket, body_buffer_ptr, space, 0); 371 | if (bytes < 0) { 372 | fprintf(stderr, "Auto-update Failed: read failed\n"); 373 | free(body_buffer); 374 | closesocket(ConnectSocket); 375 | WSACleanup(); 376 | return 2; 377 | } 378 | if (bytes == 0) { 379 | /* EOF */ 380 | break; 381 | } 382 | received += bytes; 383 | body_buffer_ptr += bytes; 384 | fprintf(stderr, "\r%lld / %lld bytes finished (%lld%%)", received, found_length, received*100LL/found_length); 385 | fflush(stderr); 386 | } 387 | if (received != found_length) { 388 | assert(received < found_length); 389 | fprintf(stderr, "Auto-update Failed: prematurely reached EOF after reading %lld bytes\n", received); 390 | closesocket(ConnectSocket); 391 | WSACleanup(); 392 | free(body_buffer); 393 | return 2; 394 | } 395 | fprintf(stderr, "\n"); 396 | fflush(stderr); 397 | // shutdown the connection for sending since no more data will be sent 398 | // the client can still use the ConnectSocket for receiving data 399 | iResult = shutdown(ConnectSocket, SD_SEND); 400 | if (iResult == SOCKET_ERROR) { 401 | fprintf(stderr, "Auto-update Failed: shutdown failed with %d\n", WSAGetLastError()); 402 | closesocket(ConnectSocket); 403 | WSACleanup(); 404 | return 2; 405 | } 406 | // Inflate to a file 407 | fprintf(stderr, "Inflating"); 408 | fflush(stderr); 409 | struct ZIPLocalFileHeader *h = (struct ZIPLocalFileHeader *)body_buffer; 410 | if (!(0x04034b50 == h->signature && 8 == h->compressionMethod)) { 411 | fprintf(stderr, "Auto-update Failed: We only support a zip file containing " 412 | "one Deflate compressed file for the moment.\n" 413 | "Pull requests are welcome on GitHub at " 414 | "https://github.com/pmq20/libautoupdate\n"); 415 | } 416 | // skip the Local File Header 417 | unsigned full_length = found_length - sizeof(struct ZIPLocalFileHeader) - h->fileNameLength; 418 | unsigned half_length = full_length / 2; 419 | unsigned uncompLength = full_length; 420 | 421 | /* windowBits is passed < 0 to tell that there is no zlib header. 422 | * Note that in this case inflate *requires* an extra "dummy" byte 423 | * after the compressed stream in order to complete decompression and 424 | * return Z_STREAM_END. 425 | */ 426 | char* uncomp = (char*)calloc(sizeof(char), uncompLength + 1); 427 | if (NULL == uncomp) { 428 | fprintf(stderr, "Auto-update Failed: Insufficient memory\n"); 429 | free(body_buffer); 430 | return 2; 431 | } 432 | 433 | z_stream strm; 434 | strm.next_in = (Bytef *)(body_buffer + sizeof(struct ZIPLocalFileHeader) + h->fileNameLength); 435 | strm.avail_in = found_length; 436 | strm.total_out = 0; 437 | strm.zalloc = Z_NULL; 438 | strm.zfree = Z_NULL; 439 | 440 | short done = 0; 441 | 442 | if (inflateInit2(&strm, -MAX_WBITS) != Z_OK) { 443 | free(uncomp); 444 | free(body_buffer); 445 | fprintf(stderr, "Auto-update Failed: inflateInit2 failed\n"); 446 | return 2; 447 | } 448 | 449 | while (!done) { 450 | // If our output buffer is too small 451 | if (strm.total_out >= uncompLength) { 452 | // Increase size of output buffer 453 | char* uncomp2 = (char*)calloc(sizeof(char), uncompLength + half_length + 1); 454 | if (NULL == uncomp2) { 455 | free(uncomp); 456 | free(body_buffer); 457 | fprintf(stderr, "Auto-update Failed: calloc failed\n"); 458 | return 2; 459 | } 460 | memcpy(uncomp2, uncomp, uncompLength); 461 | uncompLength += half_length; 462 | free(uncomp); 463 | uncomp = uncomp2; 464 | } 465 | 466 | strm.next_out = (Bytef *)(uncomp + strm.total_out); 467 | strm.avail_out = uncompLength - strm.total_out; 468 | 469 | // Inflate another chunk. 470 | int err = inflate(&strm, Z_SYNC_FLUSH); 471 | if (err == Z_STREAM_END) { 472 | done = 1; 473 | } 474 | else if (err != Z_OK) { 475 | fprintf(stderr, "Auto-update Failed: inflate failed with %d\n", err); 476 | free(uncomp); 477 | free(body_buffer); 478 | return 2; 479 | } 480 | } 481 | 482 | if (inflateEnd(&strm) != Z_OK) { 483 | fprintf(stderr, "Auto-update Failed: inflateInit2 failed\n"); 484 | free(uncomp); 485 | free(body_buffer); 486 | return 2; 487 | } 488 | 489 | wchar_t *tmpdir = autoupdate_tmpdir(); 490 | if (NULL == tmpdir) { 491 | fprintf(stderr, "Auto-update Failed: no temporary folder found\n"); 492 | free(uncomp); 493 | free(body_buffer); 494 | return 2; 495 | } 496 | /* Windows paths can never be longer than this. */ 497 | const size_t exec_path_len = 32768; 498 | wchar_t exec_path[32768]; 499 | DWORD utf16_len = GetModuleFileNameW(NULL, exec_path, exec_path_len); 500 | if (0 == utf16_len) { 501 | fprintf(stderr, "Auto-update Failed: GetModuleFileNameW failed with GetLastError=%d\n", GetLastError()); 502 | free((void*)(tmpdir)); 503 | free(uncomp); 504 | free(body_buffer); 505 | return 2; 506 | } 507 | if (tmpdir[0] != exec_path[0]) { 508 | free((void*)(tmpdir)); 509 | tmpdir = wcsdup(exec_path); 510 | wchar_t *backslash = wcsrchr(tmpdir, L'\\'); 511 | if (NULL == backslash) { 512 | fprintf(stderr, "Auto-update Failed: Cannot find an approriate tmpdir with %S\n", tmpdir); 513 | free((void*)(tmpdir)); 514 | free(uncomp); 515 | free(body_buffer); 516 | return 2; 517 | } 518 | *backslash = 0; 519 | } 520 | wchar_t *tmpf = autoupdate_tmpf(tmpdir, "exe"); 521 | if (NULL == tmpf) { 522 | fprintf(stderr, "Auto-update Failed: no temporary file available\n"); 523 | free((void*)(tmpdir)); 524 | free(uncomp); 525 | free(body_buffer); 526 | return 2; 527 | } 528 | FILE *fp = _wfopen(tmpf, L"wb"); 529 | if (NULL == fp) { 530 | fprintf(stderr, "Auto-update Failed: cannot open temporary file %S\n", tmpf); 531 | free((void*)(tmpdir)); 532 | free((void*)(tmpf)); 533 | free(uncomp); 534 | free(body_buffer); 535 | return 2; 536 | } 537 | fprintf(stderr, " to %S\n", tmpf); 538 | size_t fwrite_ret = fwrite(uncomp, sizeof(char), strm.total_out, fp); 539 | if (fwrite_ret != strm.total_out) { 540 | fprintf(stderr, "Auto-update Failed: fwrite failed %S\n", tmpf); 541 | fclose(fp); 542 | DeleteFileW(tmpf); 543 | free((void*)(tmpdir)); 544 | free((void*)(tmpf)); 545 | free(uncomp); 546 | free(body_buffer); 547 | return 2; 548 | } 549 | fclose(fp); 550 | free(uncomp); 551 | free(body_buffer); 552 | // Backup 553 | wchar_t *selftmpf = autoupdate_tmpf(tmpdir, "exe"); 554 | if (NULL == selftmpf) { 555 | fprintf(stderr, "Auto-update Failed: no temporary file available\n"); 556 | DeleteFileW(tmpf); 557 | free((void*)(tmpdir)); 558 | free((void*)(tmpf)); 559 | return 2; 560 | } 561 | fprintf(stderr, "Moving the old version from %S to %S\n", exec_path, selftmpf); 562 | BOOL ret = MoveFileExW(exec_path, selftmpf, MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH); 563 | if (!ret) { 564 | fprintf(stderr, "Auto-update Failed: MoveFileW failed with GetLastError=%d\n", GetLastError()); 565 | DeleteFileW(tmpf); 566 | free((void*)(tmpdir)); 567 | free((void*)(tmpf)); 568 | free((void*)(selftmpf)); 569 | return 2; 570 | } 571 | // Move the new version into the original place 572 | fprintf(stderr, "Moving the new version from %S to %S \n", tmpf, exec_path); 573 | ret = MoveFileExW(tmpf, exec_path, MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH); 574 | if (!ret) { 575 | fprintf(stderr, "Auto-update Failed: MoveFileW failed with GetLastError=%d\n", GetLastError()); 576 | DeleteFileW(tmpf); 577 | free((void*)(tmpdir)); 578 | free((void*)(tmpf)); 579 | free((void*)(selftmpf)); 580 | return 2; 581 | } 582 | // Restarting 583 | fprintf(stderr, "Restarting...\n"); 584 | fflush(stderr); 585 | STARTUPINFO si; 586 | PROCESS_INFORMATION pi; 587 | ZeroMemory(&si, sizeof(si)); 588 | si.cb = sizeof(si); 589 | ZeroMemory(&pi, sizeof(pi)); 590 | ret = CreateProcess( 591 | NULL, // No module name (use command line) 592 | GetCommandLine(), // Command line 593 | NULL, // Process handle not inheritable 594 | NULL, // Thread handle not inheritable 595 | FALSE, // Set handle inheritance to FALSE 596 | 0, // No creation flags 597 | NULL, // Use parent's environment block 598 | NULL, // Use parent's starting directory 599 | &si, // Pointer to STARTUPINFO structure 600 | &pi // Pointer to PROCESS_INFORMATION structure 601 | ); 602 | if (!ret) { 603 | fprintf(stderr, "Auto-update Failed: CreateProcess failed with GetLastError=%d\n", GetLastError()); 604 | DeleteFileW(tmpf); 605 | free((void*)(tmpdir)); 606 | free((void*)(tmpf)); 607 | free((void*)(selftmpf)); 608 | return 3; 609 | } 610 | // Wait until child process exits. 611 | WaitForSingleObject(pi.hProcess, INFINITE); 612 | // Close process and thread handles. 613 | CloseHandle(pi.hProcess); 614 | CloseHandle(pi.hThread); 615 | fprintf(stderr, "Deleting %S\n", selftmpf); 616 | fflush(stderr); 617 | _wexeclp(L"cmd", L"cmd", L"/c", L"ping", L"127.0.0.1", L"-n", L"3", L">nul", L"&", L"del", selftmpf, NULL); 618 | // we should never reach here 619 | assert(0); 620 | return 3; 621 | } 622 | 623 | #else 624 | 625 | #include 626 | #include /* printf, sprintf */ 627 | #include /* exit */ 628 | #include /* read, write, close */ 629 | #include /* memcpy, memset */ 630 | #include /* socket, connect */ 631 | #include /* struct sockaddr_in, struct sockaddr */ 632 | #include /* struct hostent, gethostbyname */ 633 | #include 634 | #include 635 | #include /* PATH_MAX */ 636 | #include /* struct stat */ 637 | #include 638 | 639 | int autoupdate( 640 | int argc, 641 | char *argv[], 642 | const char *host, 643 | uint16_t port, 644 | const char *path, 645 | const char *current, 646 | short force 647 | ) 648 | { 649 | struct hostent *server; 650 | struct sockaddr_in serv_addr; 651 | int sockfd, bytes, total; 652 | char response[1024 * 10 + 1]; // 10KB 653 | 654 | if (!force && !autoupdate_should_proceed()) { 655 | return 1; 656 | } 657 | 658 | if (!force && !autoupdate_should_proceed_24_hours(argc, argv, 0)) { 659 | return 4; 660 | } 661 | 662 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 663 | if (sockfd < 0) { 664 | fprintf(stderr, "Auto-update Failed: socket creation failed\n"); 665 | return 2; 666 | } 667 | server = gethostbyname(host); 668 | if (server == NULL) { 669 | close(sockfd); 670 | fprintf(stderr, "Auto-update Failed: gethostbyname failed for %s\n", host); 671 | return 2; 672 | } 673 | memset(&serv_addr, 0, sizeof(serv_addr)); 674 | serv_addr.sin_family = AF_INET; 675 | serv_addr.sin_port = htons(port); 676 | memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length); 677 | if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { 678 | close(sockfd); 679 | fprintf(stderr, "Auto-update Failed: connect failed on %s and port %d\n", host, port); 680 | return 2; 681 | } 682 | if (5 != write(sockfd, "HEAD ", 5) || 683 | strlen(path) != write(sockfd, path, strlen(path)) || 684 | 11 != write(sockfd, " HTTP/1.0\r\n", 11) || 685 | 6 != write(sockfd, "Host: ", 6) || 686 | strlen(host) != write(sockfd, host, strlen(host)) || 687 | 4 != write(sockfd, "\r\n\r\n", 4)) { 688 | close(sockfd); 689 | fprintf(stderr, "Auto-update Failed: write failed\n"); 690 | return 2; 691 | } 692 | total = sizeof(response) - 2; 693 | long long received = 0; 694 | do { 695 | bytes = read(sockfd, response + received, total - received); 696 | if (bytes < 0) { 697 | close(sockfd); 698 | fprintf(stderr, "Auto-update Failed: read failed\n"); 699 | return 2; 700 | } 701 | if (bytes == 0) { 702 | /* EOF */ 703 | *(response + received) = 0; 704 | break; 705 | } 706 | received += bytes; 707 | } while (received < total); 708 | if (received == total) { 709 | close(sockfd); 710 | fprintf(stderr, "Auto-update Failed: read causes buffer full\n"); 711 | return 2; 712 | } 713 | close(sockfd); 714 | assert(received < total); 715 | size_t len = strlen(response); 716 | short again_302 = 0; 717 | parse_location_header: 718 | assert(len <= total); 719 | char *new_line = NULL; 720 | char *found = NULL; 721 | size_t i = 0; 722 | response[sizeof(response) - 1] = 0; 723 | while (i < len) { 724 | new_line = strstr(response + i, "\r\n"); 725 | if (NULL == new_line) { 726 | break; 727 | } 728 | *new_line = 0; 729 | if (0 == strncmp(response + i, "Location: ", 10)) { 730 | found = response + i + 10; 731 | break; 732 | } 733 | *new_line = '\r'; 734 | i = new_line - response + 2; 735 | } 736 | if (!found) { 737 | fprintf(stderr, "Auto-update Failed: failed to find a Location header\n"); 738 | return 2; 739 | } 740 | if (!again_302) { 741 | if (strstr(found, current)) { 742 | /* Latest version confirmed. No need to update */ 743 | autoupdate_should_proceed_24_hours(argc, argv, 1); 744 | return 0; 745 | } else { 746 | fprintf(stderr, "Hint: to disable auto-update, run with environment variable CI=true\n"); 747 | fflush(stderr); 748 | } 749 | } 750 | 751 | char *url = found; 752 | fprintf(stderr, "Downloading update from %s\n", url); 753 | fflush(stderr); 754 | 755 | char *host2; 756 | uint16_t port2 = 80; 757 | if (strlen(url) >= 8 && 0 == strncmp("https://", url, 8)) { 758 | host2 = url + 8; 759 | } else if (strlen(url) >= 7 && 0 == strncmp("http://", url, 7)) { 760 | host2 = url + 7; 761 | } else { 762 | fprintf(stderr, "Auto-update Failed: failed to find http:// or https:// at the beginning of URL %s\n", url); 763 | return 2; 764 | } 765 | char *found_slash = strchr(host2, '/'); 766 | char *request_path; 767 | if (NULL == found_slash) { 768 | request_path = "/"; 769 | } else { 770 | request_path = found_slash; 771 | *found_slash = 0; 772 | } 773 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 774 | if (sockfd < 0) { 775 | fprintf(stderr, "Auto-update Failed: socket creation failed\n"); 776 | return 2; 777 | } 778 | server = gethostbyname(host2); 779 | if (server == NULL) { 780 | close(sockfd); 781 | fprintf(stderr, "Auto-update Failed: gethostbyname failed for %s\n", host2); 782 | return 2; 783 | } 784 | memset(&serv_addr, 0, sizeof(serv_addr)); 785 | serv_addr.sin_family = AF_INET; 786 | serv_addr.sin_port = htons(port2); 787 | memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length); 788 | if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { 789 | close(sockfd); 790 | fprintf(stderr, "Auto-update Failed: connect failed on %s and port %d\n", host2, port2); 791 | return 2; 792 | } 793 | if (NULL != found_slash) { 794 | *found_slash = '/'; 795 | } 796 | if (4 != write(sockfd, "GET ", 4) || 797 | strlen(request_path) != write(sockfd, request_path, strlen(request_path)) || 798 | 11 != write(sockfd, " HTTP/1.0\r\n", 11)) { 799 | close(sockfd); 800 | fprintf(stderr, "Auto-update Failed: write failed\n"); 801 | return 2; 802 | } 803 | if (NULL != found_slash) { 804 | *found_slash = 0; 805 | } 806 | if (6 != write(sockfd, "Host: ", 6) || 807 | strlen(host2) != write(sockfd, host2, strlen(host2)) || 808 | 4 != write(sockfd, "\r\n\r\n", 4)) { 809 | close(sockfd); 810 | fprintf(stderr, "Auto-update Failed: write failed\n"); 811 | return 2; 812 | } 813 | 814 | // Read the header 815 | total = sizeof(response) - 2; 816 | response[sizeof(response) - 1] = 0; 817 | received = 0; 818 | char *header_end = NULL; 819 | do { 820 | bytes = read(sockfd, response + received, total - received); 821 | if (bytes < 0) { 822 | close(sockfd); 823 | fprintf(stderr, "Auto-update Failed: read failed\n"); 824 | return 2; 825 | } 826 | if (bytes == 0) { 827 | /* EOF */ 828 | *(response + received) = 0; 829 | break; 830 | } 831 | *(response + received + bytes) = 0; 832 | header_end = strstr(response + received, "\r\n\r\n"); 833 | received += bytes; 834 | if (header_end) { 835 | break; 836 | } 837 | } while (received < total); 838 | if (NULL == header_end) { 839 | close(sockfd); 840 | fprintf(stderr, "Auto-update Failed: failed to find the end of the response header\n"); 841 | return 2; 842 | } 843 | assert(received <= total); 844 | 845 | // Possible new 302 846 | if (received > 13 && ( 847 | 0 == strncmp(response, "HTTP/1.1 302 ", 13) || 848 | 0 == strncmp(response, "HTTP/1.0 302 ", 13))) { 849 | len = received; 850 | again_302 = 1; 851 | goto parse_location_header; 852 | } 853 | 854 | // Parse the header 855 | len = received; 856 | assert(len <= total); 857 | new_line = NULL; 858 | long long found_length = -1; 859 | i = 0; 860 | response[sizeof(response) - 1] = 0; 861 | while (i < len) { 862 | new_line = strstr(response + i, "\r\n"); 863 | if (NULL == new_line) { 864 | break; 865 | } 866 | *new_line = 0; 867 | if (0 == strncmp(response + i, "Content-Length: ", 16)) { 868 | found_length = atoll(response + i + 16); 869 | break; 870 | } 871 | *new_line = '\r'; 872 | i = new_line - response + 2; 873 | } 874 | if (-1 == found_length) { 875 | close(sockfd); 876 | fprintf(stderr, "Auto-update Failed: failed to find a Content-Length header\n"); 877 | return 2; 878 | } 879 | if (0 == found_length) { 880 | close(sockfd); 881 | fprintf(stderr, "Auto-update Failed: found a Content-Length header of zero\n"); 882 | return 2; 883 | } 884 | assert(found_length > 0); 885 | // Read the body 886 | // header_end -> \r\n\r\n 887 | assert(header_end); 888 | assert(header_end + 4 <= response + received); 889 | // put the rest of over-read content when reading header 890 | size_t the_rest = response + received - (header_end + 4); 891 | char *body_buffer = (char *)(malloc(found_length)); 892 | if (NULL == body_buffer) { 893 | close(sockfd); 894 | fprintf(stderr, "Auto-update Failed: Insufficient memory\n"); 895 | return 2; 896 | } 897 | memcpy(body_buffer, (header_end + 4), the_rest); 898 | char *body_buffer_ptr = body_buffer + the_rest; 899 | char *body_buffer_end = body_buffer + found_length; 900 | // read the remaining body 901 | received = the_rest; 902 | fprintf(stderr, "\r%lld / %lld bytes finished (%lld%%)", received, found_length, received*100LL/found_length); 903 | fflush(stderr); 904 | while (received < found_length) { 905 | size_t space = 100 * 1024; 906 | if (space > body_buffer_end - body_buffer_ptr) { 907 | space = body_buffer_end - body_buffer_ptr; 908 | } 909 | bytes = read(sockfd, body_buffer_ptr, space); 910 | if (bytes < 0) { 911 | fprintf(stderr, "Auto-update Failed: read failed\n"); 912 | free(body_buffer); 913 | close(sockfd); 914 | return 2; 915 | } 916 | if (bytes == 0) { 917 | /* EOF */ 918 | break; 919 | } 920 | received += bytes; 921 | body_buffer_ptr += bytes; 922 | fprintf(stderr, "\r%lld / %lld bytes finished (%lld%%)", received, found_length, received*100LL/found_length); 923 | fflush(stderr); 924 | } 925 | if (received != found_length) { 926 | assert(received < found_length); 927 | fprintf(stderr, "Auto-update Failed: prematurely reached EOF after reading %lld bytes\n", received); 928 | close(sockfd); 929 | free(body_buffer); 930 | return 2; 931 | } 932 | fprintf(stderr, "\n"); 933 | fflush(stderr); 934 | close(sockfd); 935 | // Inflate to a file 936 | fprintf(stderr, "Inflating"); 937 | fflush(stderr); 938 | unsigned full_length = found_length; 939 | unsigned half_length = found_length / 2; 940 | unsigned uncompLength = full_length; 941 | char* uncomp = (char*) calloc( sizeof(char), uncompLength ); 942 | if (NULL == uncomp) { 943 | fprintf(stderr, "Auto-update Failed: Insufficient memory\n"); 944 | free(body_buffer); 945 | return 2; 946 | } 947 | 948 | z_stream strm; 949 | strm.next_in = (Bytef *)body_buffer; 950 | strm.avail_in = found_length; 951 | strm.total_out = 0; 952 | strm.zalloc = Z_NULL; 953 | strm.zfree = Z_NULL; 954 | 955 | short done = 0; 956 | 957 | if (inflateInit2(&strm, (16+MAX_WBITS)) != Z_OK) { 958 | free(uncomp); 959 | free(body_buffer); 960 | fprintf(stderr, "Auto-update Failed: inflateInit2 failed\n"); 961 | return 2; 962 | } 963 | 964 | while (!done) { 965 | // If our output buffer is too small 966 | if (strm.total_out >= uncompLength ) { 967 | // Increase size of output buffer 968 | char* uncomp2 = (char*) calloc( sizeof(char), uncompLength + half_length ); 969 | if (NULL == uncomp2) { 970 | free(uncomp); 971 | free(body_buffer); 972 | fprintf(stderr, "Auto-update Failed: calloc failed\n"); 973 | return 2; 974 | } 975 | memcpy( uncomp2, uncomp, uncompLength ); 976 | uncompLength += half_length ; 977 | free( uncomp ); 978 | uncomp = uncomp2 ; 979 | } 980 | 981 | strm.next_out = (Bytef *) (uncomp + strm.total_out); 982 | strm.avail_out = uncompLength - strm.total_out; 983 | 984 | // Inflate another chunk. 985 | int err = inflate(&strm, Z_SYNC_FLUSH); 986 | if (err == Z_STREAM_END) { 987 | done = 1; 988 | } 989 | else if (err != Z_OK) { 990 | fprintf(stderr, "Auto-update Failed: inflate failed with %d\n", err); 991 | free(uncomp); 992 | free(body_buffer); 993 | return 2; 994 | } 995 | } 996 | 997 | if (inflateEnd (&strm) != Z_OK) { 998 | fprintf(stderr, "Auto-update Failed: inflateInit2 failed\n"); 999 | free(uncomp); 1000 | free(body_buffer); 1001 | return 2; 1002 | } 1003 | 1004 | char *tmpdir = autoupdate_tmpdir(); 1005 | if (NULL == tmpdir) { 1006 | fprintf(stderr, "Auto-update Failed: no temporary folder found\n"); 1007 | free(uncomp); 1008 | free(body_buffer); 1009 | return 2; 1010 | } 1011 | char *tmpf = autoupdate_tmpf(tmpdir, NULL); 1012 | if (NULL == tmpf) { 1013 | fprintf(stderr, "Auto-update Failed: no temporary file available\n"); 1014 | free((void*)(tmpdir)); 1015 | free(uncomp); 1016 | free(body_buffer); 1017 | return 2; 1018 | } 1019 | FILE *fp = fopen(tmpf, "wb"); 1020 | if (NULL == fp) { 1021 | fprintf(stderr, "Auto-update Failed: cannot open temporary file %s\n", tmpf); 1022 | free((void*)(tmpdir)); 1023 | free((void*)(tmpf)); 1024 | free(uncomp); 1025 | free(body_buffer); 1026 | return 2; 1027 | } 1028 | fprintf(stderr, " to %s\n", tmpf); 1029 | size_t fwrite_ret = fwrite(uncomp, sizeof(char), strm.total_out, fp); 1030 | if (fwrite_ret != strm.total_out) { 1031 | fprintf(stderr, "Auto-update Failed: fwrite failed %s\n", tmpf); 1032 | fclose(fp); 1033 | unlink(tmpf); 1034 | free((void*)(tmpdir)); 1035 | free((void*)(tmpf)); 1036 | free(uncomp); 1037 | free(body_buffer); 1038 | return 2; 1039 | } 1040 | fclose(fp); 1041 | free(uncomp); 1042 | free(body_buffer); 1043 | // chmod 1044 | size_t exec_path_len = 2 * PATH_MAX; 1045 | char* exec_path = (char*)(malloc(exec_path_len)); 1046 | if (NULL == exec_path) { 1047 | fprintf(stderr, "Auto-update Failed: Insufficient memory allocating exec_path\n"); 1048 | free((void*)(tmpdir)); 1049 | free((void*)(tmpf)); 1050 | unlink(tmpf); 1051 | return 2; 1052 | } 1053 | if (autoupdate_exepath(exec_path, &exec_path_len) != 0) { 1054 | if (!argv[0]) { 1055 | fprintf(stderr, "Auto-update Failed: missing argv[0]\n"); 1056 | free((void*)(tmpdir)); 1057 | free((void*)(tmpf)); 1058 | unlink(tmpf); 1059 | return 2; 1060 | } 1061 | assert(strlen(argv[0]) < 2 * PATH_MAX); 1062 | memcpy(exec_path, argv[0], strlen(argv[0])); 1063 | } 1064 | struct stat current_st; 1065 | int ret = stat(exec_path, ¤t_st); 1066 | if (0 != ret) { 1067 | fprintf(stderr, "Auto-update Failed: stat failed for %s\n", exec_path); 1068 | free(exec_path); 1069 | free((void*)(tmpdir)); 1070 | free((void*)(tmpf)); 1071 | unlink(tmpf); 1072 | return 2; 1073 | } 1074 | ret = chmod(tmpf, current_st.st_mode | S_IXUSR); 1075 | if (0 != ret) { 1076 | fprintf(stderr, "Auto-update Failed: chmod failed for %s\n", tmpf); 1077 | free(exec_path); 1078 | free((void*)(tmpdir)); 1079 | free((void*)(tmpf)); 1080 | unlink(tmpf); 1081 | return 2; 1082 | } 1083 | // Move the new version into the original place 1084 | fprintf(stderr, "Moving the new version from %s to %s\n", tmpf, exec_path); 1085 | ret = rename(tmpf, exec_path); 1086 | if (0 != ret) { 1087 | fprintf(stderr, "Auto-update Failed: failed calling rename %s to %s\n", tmpf, exec_path); 1088 | free(exec_path); 1089 | free((void*)(tmpdir)); 1090 | free((void*)(tmpf)); 1091 | unlink(tmpf); 1092 | return 2; 1093 | } 1094 | fprintf(stderr, "Restarting...\n"); 1095 | ret = execv(exec_path, argv); 1096 | // we should not reach this point 1097 | fprintf(stderr, "Auto-update Failed: execv failed with %d (errno %d)\n", ret, errno); 1098 | free(exec_path); 1099 | free((void*)(tmpdir)); 1100 | free((void*)(tmpf)); 1101 | unlink(tmpf); 1102 | return 3; 1103 | } 1104 | 1105 | #endif // _WIN32 1106 | --------------------------------------------------------------------------------