Repository: pmq20/libautoupdate Branch: master Commit: 006f02c6b350 Files: 17 Total size: 56.8 KB Directory structure: gitextract_b1h5ve2y/ ├── .gitignore ├── .mailmap ├── .travis.yml ├── AUTHORS ├── CMakeLists.txt ├── LICENSE ├── README.md ├── appveyor.yml ├── include/ │ └── autoupdate.h ├── libautoupdate.gyp ├── src/ │ ├── autoupdate.c │ ├── autoupdate_internal.h │ ├── exepath.c │ ├── inflate.c │ ├── tmpf.c │ └── utils.c └── tests/ └── main.c ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /build/ # Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf ================================================ FILE: .mailmap ================================================ Minqi Pan ================================================ FILE: .travis.yml ================================================ language: c os: - linux - osx compiler: - gcc - clang script: - cmake --version - mkdir build && cd build - cmake -DBUILD_TESTS=ON .. || exit $? - cmake --build . || exit $? ================================================ FILE: AUTHORS ================================================ # Authors ordered by first contribution. Minqi Pan Venkat Ram ================================================ FILE: CMakeLists.txt ================================================ # Copyright (c) 2020 Minqi Pan et al. # # This file is part of libautoupdate, distributed under the MIT License # For full terms see the included LICENSE file PROJECT(libautoupdate C) CMAKE_MINIMUM_REQUIRED(VERSION 2.6) FIND_PACKAGE(ZLIB) INCLUDE_DIRECTORIES(src include ${ZLIB_INCLUDE_DIR}) FILE(GLOB SRC_H include/autoupdate.h) FILE(GLOB SRC_AUTOUPDATE src/*.c src/*.h) ADD_LIBRARY(autoupdate ${SRC_H} ${SRC_AUTOUPDATE}) IF(BUILD_TESTS) ENABLE_TESTING() ADD_TEST(autoupdate_tests autoupdate_tests --help) FILE(GLOB SRC_TEST tests/*.c) ADD_EXECUTABLE(autoupdate_tests ${SRC_TEST}) TARGET_LINK_LIBRARIES(autoupdate_tests autoupdate ${ZLIB_LIBRARIES}) if(WIN32) TARGET_LINK_LIBRARIES(autoupdate_tests shlwapi.lib Ws2_32.lib) endif() ENDIF() ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Minqi Pan et al. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Libautoupdate Cross-platform C library that enables your application to auto-update itself in place. [![Build Status](https://travis-ci.org/pmq20/libautoupdate.svg?branch=master)](https://travis-ci.org/pmq20/libautoupdate) [![Build status](https://ci.appveyor.com/api/projects/status/sjdyfwd768lh187f/branch/master?svg=true)](https://ci.appveyor.com/project/pmq20/libautoupdate/branch/master) ![Terminal simulation of a simple auto-update](https://github.com/pmq20/libautoupdate/raw/master/doc/libautoupdate.gif) ## API There is only one public API, i.e. `autoupdate()`. ```C int autoupdate(argc, argv, host, port, path, current) ``` It accepts the following arguments: - the 1st and 2nd arguments are the same as those passed to `main()` - `host` is the host name of the update server to communicate with - `port` is the port of the server, which is a string on Windows and a 16-bit integer on macOS / Linux - `path` is the paramater passed to the HTTP/1.0 HEAD request of the Round 1 - `current` is the current version string to be compared with what is returned from the server - a new version is considered detected if this string is not a substring of the server's reply It never returns if a new version was detected and auto-update was successfully performed. Otherwise, it returns one of the following integers to indicate the situation: | Return Value | Indication | |:--------------:|---------------------------------------------------------------------------------------------| | 0 | Latest version confirmed. No need to update | | 1 | Auto-update shall not proceed due to environment variable `CI` being set | | 2 | Auto-update process failed prematurely and detailed errors are printed to stderr | | 3 | Failed to restart after replacing itself with the new version | | 4 | Auto-update shall not proceed due to being already checked in the last 24 hours | ## Communication ### Round 1 Libautoupdate first makes a HTTP/1.0 HEAD request to the server, in order to peek the latest version string. Libautoupdate -- HTTP/1.0 HEAD request --> Server The server is expected to repond with `HTTP 302 Found` and provide a `Location` header. It then compares the content of `Location` header with the current version string. It proceeds to Round 2 if the current version string is NOT a sub-string of the `Location` header. ### Round 2 Libautoupdate makes a full HTTP/1.0 GET request to the `Location` header of the last round. Libautoupdate -- HTTP/1.0 GET request --> Server The server is expected to respond with `200 OK` transferring the new release itself. Based on the `Content-Type` header received, an addtional inflation operation might be performed: - `Content-Type: application/x-gzip`: Gzip Inflation is performed - `Content-Type: application/zip`: Deflate compression is assumed and the first file is inflated and used ## Self-replacing After 2 rounds of communication with the server, libautoupdate will then proceeds with a self-replacing process, i.e. the program replaces itself in-place with the help of the system temporary directory, after which it restarts itself with the new release. ## Examples Just call `autoupdate()` at the beginning of your `main()`, before all actual logic of your application. See the following code for examples. ### Windows ```C #include int wmain(int argc, wchar_t *wargv[]) { int autoupdate_result; autoupdate_result = autoupdate( argc, wargv, "enclose.io", "80", "/nodec/nodec-x64.exe" "v1.1.0" ); /* actual logic of your application ... */ } ``` ### macOS / Linux ```C #include int main(int argc, char *argv[]) { int autoupdate_result; autoupdate_result = autoupdate( argc, argv, "enclose.io", 80, "/nodec/nodec-darwin-x64" "v1.1.0" ); /* actual logic of your application ... */ } ``` ## Hints - Set environment variable `CI=true` will prevent auto-updating - Remove the file `~/.libautoupdate` will remove the once-per-24-hours check limit ## To-do - Cater to bad network connection situations - Print more information about the new version - Use better error messages when the user did not have permissions to move the new version into the destination directory - Move the old binary to the system temporary directory, yet not deleting it. - The Operating System will delete it when restarted/tmpdir-full - Add facility to restore/rollback to the old file once the new version went wrong ## Authors [Minqi Pan et al.](https://raw.githubusercontent.com/pmq20/libautoupdate/master/AUTHORS) ## License [MIT](https://raw.githubusercontent.com/pmq20/libautoupdate/master/LICENSE) ## See Also - [Node.js Packer](https://github.com/pmq20/node-packer): Packing your Node.js application into a single executable. - [Ruby Packer](https://github.com/pmq20/ruby-packer): Packing your Ruby application into a single executable. ================================================ FILE: appveyor.yml ================================================ version: '{build}' environment: matrix: - GENERATOR: "Visual Studio 14 2015 Win64" ARCH: 64 PlatformToolset: v140 Platform: x64 - GENERATOR: "Visual Studio 12 2013 Win64" ARCH: 64 PlatformToolset: v120 Platform: x64 build_script: - ps: | nuget install zlib.$env:PlatformToolset.windesktop.msvcstl.dyn.rt-dyn cmake --version mkdir build cd build 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 .. cmake --build . --config Release ================================================ FILE: include/autoupdate.h ================================================ /* * Copyright (c) 2020 Minqi Pan et al. * * This file is part of libautoupdate, distributed under the MIT License * For full terms see the included LICENSE file */ #ifndef AUTOUPDATE_H_8C141CA2 #define AUTOUPDATE_H_8C141CA2 #ifdef _WIN32 #include int autoupdate( int argc, wchar_t *wargv[], const char *host, const char *port, const char *path, const char *current, short force ); #else #include int autoupdate( int argc, char *argv[], const char *host, uint16_t port, const char *path, const char *current, short force ); #endif // _WIN32 #endif /* end of include guard: AUTOUPDATE_H_8C141CA2 */ ================================================ FILE: libautoupdate.gyp ================================================ # Copyright (c) 2020 Minqi Pan et al. # # This file is part of libautoupdate, distributed under the MIT License # For full terms see the included LICENSE file { 'targets': [ { 'target_name': 'libautoupdate', 'type': 'static_library', 'sources': [ 'include/autoupdate.h', 'src/autoupdate.c', 'src/autoupdate_internal.h', 'src/exepath.c', 'src/inflate.c', 'src/tmpf.c', 'src/utils.c', ], 'include_dirs': [ 'include', '../zlib', ], }, ], } ================================================ FILE: src/autoupdate.c ================================================ /* * Copyright (c) 2020 Minqi Pan et al. * * This file is part of libautoupdate, distributed under the MIT License * For full terms see the included LICENSE file */ #include "autoupdate.h" #include "autoupdate_internal.h" #include "zlib.h" #ifdef _WIN32 #include #include #include #include #include #include #include #include /* exit */ #include int autoupdate( int argc, wchar_t *wargv[], const char *host, const char *port, const char *path, const char *current, short force ) { WSADATA wsaData; if (!force && !autoupdate_should_proceed()) { return 1; } if (!force && !autoupdate_should_proceed_24_hours(argc, wargv, 0)) { return 4; } // Initialize Winsock int iResult = WSAStartup(MAKEWORD(2,2), &wsaData); if (iResult != 0) { fprintf(stderr, "Auto-update Failed: WSAStartup failed with %d\n", iResult); return 2; } struct addrinfo *result = NULL, *ptr = NULL, hints; ZeroMemory(&hints, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; // Resolve the server address and port iResult = getaddrinfo(host, port, &hints, &result); if (iResult != 0) { fprintf(stderr, "Auto-update Failed: getaddrinfo failed with %d\n", iResult); WSACleanup(); return 2; } SOCKET ConnectSocket = INVALID_SOCKET; // Attempt to connect to the first address returned by // the call to getaddrinfo ptr = result; // Create a SOCKET for connecting to server ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (ConnectSocket == INVALID_SOCKET) { fprintf(stderr, "Auto-update Failed: Error at socket() with %d\n", WSAGetLastError()); freeaddrinfo(result); WSACleanup(); return 2; } // Connect to server. iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); if (iResult == SOCKET_ERROR) { closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; } freeaddrinfo(result); if (ConnectSocket == INVALID_SOCKET) { fprintf(stderr, "Auto-update Failed: connect failed on %s and port %s\n", host, port); WSACleanup(); return 2; } if (5 != send(ConnectSocket, "HEAD ", 5, 0) || strlen(path) != send(ConnectSocket, path, strlen(path), 0) || 11 != send(ConnectSocket, " HTTP/1.0\r\n", 11, 0) || 6 != send(ConnectSocket, "Host: ", 6, 0) || strlen(host) != send(ConnectSocket, host, strlen(host), 0) || 4 != send(ConnectSocket, "\r\n\r\n", 4, 0)) { fprintf(stderr, "Auto-update Failed: send failed with %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 2; } char response[1024 * 10 + 1]; // 10KB int bytes, total; total = sizeof(response) - 2; long long received = 0; do { bytes = recv(ConnectSocket, response + received, total - received, 0); if (bytes < 0) { fprintf(stderr, "Auto-update Failed: recv failed with %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 2; } if (bytes == 0) { /* EOF */ *(response + received) = 0; break; } received += bytes; } while (received < total); if (received == total) { fprintf(stderr, "Auto-update Failed: read causes buffer full\n"); closesocket(ConnectSocket); WSACleanup(); return 2; } // shutdown the connection for sending since no more data will be sent // the client can still use the ConnectSocket for receiving data iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { fprintf(stderr, "Auto-update Failed: shutdown failed with %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 2; } assert(received < total); size_t len = strlen(response); short again_302 = 0; parse_location_header: assert(len <= total); char *new_line = NULL; char *found = NULL; size_t i = 0; response[sizeof(response) - 1] = 0; while (i < len) { new_line = strstr(response + i, "\r\n"); if (NULL == new_line) { break; } *new_line = 0; if (0 == strncmp(response + i, "Location: ", 10)) { found = response + i + 10; break; } *new_line = '\r'; i = new_line - response + 2; } if (!found) { fprintf(stderr, "Auto-update Failed: failed to find a Location header\n"); return 2; } if (!again_302) { if (strstr(found, current)) { /* Latest version confirmed. No need to update */ autoupdate_should_proceed_24_hours(argc, wargv, 1); return 0; } else { fprintf(stderr, "Hint: to disable auto-update, run with environment variable CI=true\n"); fflush(stderr); } } char *url = found; fprintf(stderr, "Downloading update from %s\n", url); fflush(stderr); char *host2; char *port2 = "80"; if (strlen(url) >= 8 && 0 == strncmp("https://", url, 8)) { host2 = url + 8; } else if (strlen(url) >= 7 && 0 == strncmp("http://", url, 7)) { host2 = url + 7; } else { fprintf(stderr, "Auto-update Failed: failed to find http:// or https:// at the beginning of URL %s\n", url); return 2; } char *found_slash = strchr(host2, '/'); char *request_path; if (NULL == found_slash) { request_path = "/"; } else { request_path = found_slash; *found_slash = 0; } result = NULL; ptr = NULL; ZeroMemory(&hints, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; // Resolve the server address and port iResult = getaddrinfo(host2, port2, &hints, &result); if (iResult != 0) { fprintf(stderr, "Auto-update Failed: getaddrinfo failed with %d\n", iResult); WSACleanup(); return 2; } ConnectSocket = INVALID_SOCKET; // Attempt to connect to the first address returned by // the call to getaddrinfo ptr = result; // Create a SOCKET for connecting to server ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (ConnectSocket == INVALID_SOCKET) { fprintf(stderr, "Auto-update Failed: Error at socket() with %d\n", WSAGetLastError()); freeaddrinfo(result); WSACleanup(); return 2; } // Connect to server. iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); if (iResult == SOCKET_ERROR) { closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; } freeaddrinfo(result); if (ConnectSocket == INVALID_SOCKET) { fprintf(stderr, "Auto-update Failed: connect failed on %s and port %s\n", host2, port2); WSACleanup(); return 2; } if (NULL != found_slash) { *found_slash = '/'; } if (4 != send(ConnectSocket, "GET ", 4, 0) || strlen(request_path) != send(ConnectSocket, request_path, strlen(request_path), 0) || 11 != send(ConnectSocket, " HTTP/1.0\r\n", 11, 0)) { fprintf(stderr, "Auto-update Failed: send failed with %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 2; } if (NULL != found_slash) { *found_slash = 0; } if (6 != send(ConnectSocket, "Host: ", 6, 0) || strlen(host2) != send(ConnectSocket, host2, strlen(host2), 0) || 4 != send(ConnectSocket, "\r\n\r\n", 4, 0)) { fprintf(stderr, "Auto-update Failed: send failed with %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 2; } // Read the header total = sizeof(response) - 2; response[sizeof(response) - 1] = 0; received = 0; char *header_end = NULL; do { bytes = recv(ConnectSocket, response + received, total - received, 0); if (bytes < 0) { fprintf(stderr, "Auto-update Failed: recv failed with %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 2; } if (bytes == 0) { /* EOF */ *(response + received) = 0; break; } *(response + received + bytes) = 0; header_end = strstr(response + received, "\r\n\r\n"); received += bytes; if (header_end) { break; } } while (received < total); if (NULL == header_end) { fprintf(stderr, "Auto-update Failed: failed to find the end of the response header\n"); closesocket(ConnectSocket); WSACleanup(); return 2; } assert(received <= total); // Possible new 302 if (received > 13 && ( 0 == strncmp(response, "HTTP/1.1 302 ", 13) || 0 == strncmp(response, "HTTP/1.0 302 ", 13))) { len = received; again_302 = 1; goto parse_location_header; } // Parse the header len = received; assert(len <= total); new_line = NULL; long long found_length = -1; i = 0; response[sizeof(response) - 1] = 0; while (i < len) { new_line = strstr(response + i, "\r\n"); if (NULL == new_line) { break; } *new_line = 0; if (0 == strncmp(response + i, "Content-Length: ", 16)) { found_length = atoll(response + i + 16); break; } *new_line = '\r'; i = new_line - response + 2; } if (-1 == found_length) { fprintf(stderr, "Auto-update Failed: failed to find a Content-Length header\n"); closesocket(ConnectSocket); WSACleanup(); return 2; } if (0 == found_length) { fprintf(stderr, "Auto-update Failed: found a Content-Length header of zero\n"); closesocket(ConnectSocket); WSACleanup(); return 2; } assert(found_length > 0); // Read the body // header_end -> \r\n\r\n assert(header_end); assert(header_end + 4 <= response + received); // put the rest of over-read content when reading header size_t the_rest = response + received - (header_end + 4); char *body_buffer = (char *)(malloc(found_length)); if (NULL == body_buffer) { fprintf(stderr, "Auto-update Failed: Insufficient memory\n"); closesocket(ConnectSocket); WSACleanup(); return 2; } memcpy(body_buffer, (header_end + 4), the_rest); char *body_buffer_ptr = body_buffer + the_rest; char *body_buffer_end = body_buffer + found_length; // read the remaining body received = the_rest; fprintf(stderr, "\r%lld / %lld bytes finished (%lld%%)", received, found_length, received*100LL/found_length); fflush(stderr); while (received < found_length) { size_t space = 100 * 1024; if (space > body_buffer_end - body_buffer_ptr) { space = body_buffer_end - body_buffer_ptr; } bytes = recv(ConnectSocket, body_buffer_ptr, space, 0); if (bytes < 0) { fprintf(stderr, "Auto-update Failed: read failed\n"); free(body_buffer); closesocket(ConnectSocket); WSACleanup(); return 2; } if (bytes == 0) { /* EOF */ break; } received += bytes; body_buffer_ptr += bytes; fprintf(stderr, "\r%lld / %lld bytes finished (%lld%%)", received, found_length, received*100LL/found_length); fflush(stderr); } if (received != found_length) { assert(received < found_length); fprintf(stderr, "Auto-update Failed: prematurely reached EOF after reading %lld bytes\n", received); closesocket(ConnectSocket); WSACleanup(); free(body_buffer); return 2; } fprintf(stderr, "\n"); fflush(stderr); // shutdown the connection for sending since no more data will be sent // the client can still use the ConnectSocket for receiving data iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { fprintf(stderr, "Auto-update Failed: shutdown failed with %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 2; } // Inflate to a file fprintf(stderr, "Inflating"); fflush(stderr); struct ZIPLocalFileHeader *h = (struct ZIPLocalFileHeader *)body_buffer; if (!(0x04034b50 == h->signature && 8 == h->compressionMethod)) { fprintf(stderr, "Auto-update Failed: We only support a zip file containing " "one Deflate compressed file for the moment.\n" "Pull requests are welcome on GitHub at " "https://github.com/pmq20/libautoupdate\n"); } // skip the Local File Header unsigned full_length = found_length - sizeof(struct ZIPLocalFileHeader) - h->fileNameLength; unsigned half_length = full_length / 2; unsigned uncompLength = full_length; /* windowBits is passed < 0 to tell that there is no zlib header. * Note that in this case inflate *requires* an extra "dummy" byte * after the compressed stream in order to complete decompression and * return Z_STREAM_END. */ char* uncomp = (char*)calloc(sizeof(char), uncompLength + 1); if (NULL == uncomp) { fprintf(stderr, "Auto-update Failed: Insufficient memory\n"); free(body_buffer); return 2; } z_stream strm; strm.next_in = (Bytef *)(body_buffer + sizeof(struct ZIPLocalFileHeader) + h->fileNameLength); strm.avail_in = found_length; strm.total_out = 0; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; short done = 0; if (inflateInit2(&strm, -MAX_WBITS) != Z_OK) { free(uncomp); free(body_buffer); fprintf(stderr, "Auto-update Failed: inflateInit2 failed\n"); return 2; } while (!done) { // If our output buffer is too small if (strm.total_out >= uncompLength) { // Increase size of output buffer char* uncomp2 = (char*)calloc(sizeof(char), uncompLength + half_length + 1); if (NULL == uncomp2) { free(uncomp); free(body_buffer); fprintf(stderr, "Auto-update Failed: calloc failed\n"); return 2; } memcpy(uncomp2, uncomp, uncompLength); uncompLength += half_length; free(uncomp); uncomp = uncomp2; } strm.next_out = (Bytef *)(uncomp + strm.total_out); strm.avail_out = uncompLength - strm.total_out; // Inflate another chunk. int err = inflate(&strm, Z_SYNC_FLUSH); if (err == Z_STREAM_END) { done = 1; } else if (err != Z_OK) { fprintf(stderr, "Auto-update Failed: inflate failed with %d\n", err); free(uncomp); free(body_buffer); return 2; } } if (inflateEnd(&strm) != Z_OK) { fprintf(stderr, "Auto-update Failed: inflateInit2 failed\n"); free(uncomp); free(body_buffer); return 2; } wchar_t *tmpdir = autoupdate_tmpdir(); if (NULL == tmpdir) { fprintf(stderr, "Auto-update Failed: no temporary folder found\n"); free(uncomp); free(body_buffer); return 2; } /* Windows paths can never be longer than this. */ const size_t exec_path_len = 32768; wchar_t exec_path[32768]; DWORD utf16_len = GetModuleFileNameW(NULL, exec_path, exec_path_len); if (0 == utf16_len) { fprintf(stderr, "Auto-update Failed: GetModuleFileNameW failed with GetLastError=%d\n", GetLastError()); free((void*)(tmpdir)); free(uncomp); free(body_buffer); return 2; } if (tmpdir[0] != exec_path[0]) { free((void*)(tmpdir)); tmpdir = wcsdup(exec_path); wchar_t *backslash = wcsrchr(tmpdir, L'\\'); if (NULL == backslash) { fprintf(stderr, "Auto-update Failed: Cannot find an approriate tmpdir with %S\n", tmpdir); free((void*)(tmpdir)); free(uncomp); free(body_buffer); return 2; } *backslash = 0; } wchar_t *tmpf = autoupdate_tmpf(tmpdir, "exe"); if (NULL == tmpf) { fprintf(stderr, "Auto-update Failed: no temporary file available\n"); free((void*)(tmpdir)); free(uncomp); free(body_buffer); return 2; } FILE *fp = _wfopen(tmpf, L"wb"); if (NULL == fp) { fprintf(stderr, "Auto-update Failed: cannot open temporary file %S\n", tmpf); free((void*)(tmpdir)); free((void*)(tmpf)); free(uncomp); free(body_buffer); return 2; } fprintf(stderr, " to %S\n", tmpf); size_t fwrite_ret = fwrite(uncomp, sizeof(char), strm.total_out, fp); if (fwrite_ret != strm.total_out) { fprintf(stderr, "Auto-update Failed: fwrite failed %S\n", tmpf); fclose(fp); DeleteFileW(tmpf); free((void*)(tmpdir)); free((void*)(tmpf)); free(uncomp); free(body_buffer); return 2; } fclose(fp); free(uncomp); free(body_buffer); // Backup wchar_t *selftmpf = autoupdate_tmpf(tmpdir, "exe"); if (NULL == selftmpf) { fprintf(stderr, "Auto-update Failed: no temporary file available\n"); DeleteFileW(tmpf); free((void*)(tmpdir)); free((void*)(tmpf)); return 2; } fprintf(stderr, "Moving the old version from %S to %S\n", exec_path, selftmpf); BOOL ret = MoveFileExW(exec_path, selftmpf, MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH); if (!ret) { fprintf(stderr, "Auto-update Failed: MoveFileW failed with GetLastError=%d\n", GetLastError()); DeleteFileW(tmpf); free((void*)(tmpdir)); free((void*)(tmpf)); free((void*)(selftmpf)); return 2; } // Move the new version into the original place fprintf(stderr, "Moving the new version from %S to %S \n", tmpf, exec_path); ret = MoveFileExW(tmpf, exec_path, MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH); if (!ret) { fprintf(stderr, "Auto-update Failed: MoveFileW failed with GetLastError=%d\n", GetLastError()); DeleteFileW(tmpf); free((void*)(tmpdir)); free((void*)(tmpf)); free((void*)(selftmpf)); return 2; } // Restarting fprintf(stderr, "Restarting...\n"); fflush(stderr); STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); ret = CreateProcess( NULL, // No module name (use command line) GetCommandLine(), // Command line NULL, // Process handle not inheritable NULL, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE 0, // No creation flags NULL, // Use parent's environment block NULL, // Use parent's starting directory &si, // Pointer to STARTUPINFO structure &pi // Pointer to PROCESS_INFORMATION structure ); if (!ret) { fprintf(stderr, "Auto-update Failed: CreateProcess failed with GetLastError=%d\n", GetLastError()); DeleteFileW(tmpf); free((void*)(tmpdir)); free((void*)(tmpf)); free((void*)(selftmpf)); return 3; } // Wait until child process exits. WaitForSingleObject(pi.hProcess, INFINITE); // Close process and thread handles. CloseHandle(pi.hProcess); CloseHandle(pi.hThread); fprintf(stderr, "Deleting %S\n", selftmpf); fflush(stderr); _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); // we should never reach here assert(0); return 3; } #else #include #include /* printf, sprintf */ #include /* exit */ #include /* read, write, close */ #include /* memcpy, memset */ #include /* socket, connect */ #include /* struct sockaddr_in, struct sockaddr */ #include /* struct hostent, gethostbyname */ #include #include #include /* PATH_MAX */ #include /* struct stat */ #include int autoupdate( int argc, char *argv[], const char *host, uint16_t port, const char *path, const char *current, short force ) { struct hostent *server; struct sockaddr_in serv_addr; int sockfd, bytes, total; char response[1024 * 10 + 1]; // 10KB if (!force && !autoupdate_should_proceed()) { return 1; } if (!force && !autoupdate_should_proceed_24_hours(argc, argv, 0)) { return 4; } sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { fprintf(stderr, "Auto-update Failed: socket creation failed\n"); return 2; } server = gethostbyname(host); if (server == NULL) { close(sockfd); fprintf(stderr, "Auto-update Failed: gethostbyname failed for %s\n", host); return 2; } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(port); memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length); if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { close(sockfd); fprintf(stderr, "Auto-update Failed: connect failed on %s and port %d\n", host, port); return 2; } if (5 != write(sockfd, "HEAD ", 5) || strlen(path) != write(sockfd, path, strlen(path)) || 11 != write(sockfd, " HTTP/1.0\r\n", 11) || 6 != write(sockfd, "Host: ", 6) || strlen(host) != write(sockfd, host, strlen(host)) || 4 != write(sockfd, "\r\n\r\n", 4)) { close(sockfd); fprintf(stderr, "Auto-update Failed: write failed\n"); return 2; } total = sizeof(response) - 2; long long received = 0; do { bytes = read(sockfd, response + received, total - received); if (bytes < 0) { close(sockfd); fprintf(stderr, "Auto-update Failed: read failed\n"); return 2; } if (bytes == 0) { /* EOF */ *(response + received) = 0; break; } received += bytes; } while (received < total); if (received == total) { close(sockfd); fprintf(stderr, "Auto-update Failed: read causes buffer full\n"); return 2; } close(sockfd); assert(received < total); size_t len = strlen(response); short again_302 = 0; parse_location_header: assert(len <= total); char *new_line = NULL; char *found = NULL; size_t i = 0; response[sizeof(response) - 1] = 0; while (i < len) { new_line = strstr(response + i, "\r\n"); if (NULL == new_line) { break; } *new_line = 0; if (0 == strncmp(response + i, "Location: ", 10)) { found = response + i + 10; break; } *new_line = '\r'; i = new_line - response + 2; } if (!found) { fprintf(stderr, "Auto-update Failed: failed to find a Location header\n"); return 2; } if (!again_302) { if (strstr(found, current)) { /* Latest version confirmed. No need to update */ autoupdate_should_proceed_24_hours(argc, argv, 1); return 0; } else { fprintf(stderr, "Hint: to disable auto-update, run with environment variable CI=true\n"); fflush(stderr); } } char *url = found; fprintf(stderr, "Downloading update from %s\n", url); fflush(stderr); char *host2; uint16_t port2 = 80; if (strlen(url) >= 8 && 0 == strncmp("https://", url, 8)) { host2 = url + 8; } else if (strlen(url) >= 7 && 0 == strncmp("http://", url, 7)) { host2 = url + 7; } else { fprintf(stderr, "Auto-update Failed: failed to find http:// or https:// at the beginning of URL %s\n", url); return 2; } char *found_slash = strchr(host2, '/'); char *request_path; if (NULL == found_slash) { request_path = "/"; } else { request_path = found_slash; *found_slash = 0; } sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { fprintf(stderr, "Auto-update Failed: socket creation failed\n"); return 2; } server = gethostbyname(host2); if (server == NULL) { close(sockfd); fprintf(stderr, "Auto-update Failed: gethostbyname failed for %s\n", host2); return 2; } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(port2); memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length); if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { close(sockfd); fprintf(stderr, "Auto-update Failed: connect failed on %s and port %d\n", host2, port2); return 2; } if (NULL != found_slash) { *found_slash = '/'; } if (4 != write(sockfd, "GET ", 4) || strlen(request_path) != write(sockfd, request_path, strlen(request_path)) || 11 != write(sockfd, " HTTP/1.0\r\n", 11)) { close(sockfd); fprintf(stderr, "Auto-update Failed: write failed\n"); return 2; } if (NULL != found_slash) { *found_slash = 0; } if (6 != write(sockfd, "Host: ", 6) || strlen(host2) != write(sockfd, host2, strlen(host2)) || 4 != write(sockfd, "\r\n\r\n", 4)) { close(sockfd); fprintf(stderr, "Auto-update Failed: write failed\n"); return 2; } // Read the header total = sizeof(response) - 2; response[sizeof(response) - 1] = 0; received = 0; char *header_end = NULL; do { bytes = read(sockfd, response + received, total - received); if (bytes < 0) { close(sockfd); fprintf(stderr, "Auto-update Failed: read failed\n"); return 2; } if (bytes == 0) { /* EOF */ *(response + received) = 0; break; } *(response + received + bytes) = 0; header_end = strstr(response + received, "\r\n\r\n"); received += bytes; if (header_end) { break; } } while (received < total); if (NULL == header_end) { close(sockfd); fprintf(stderr, "Auto-update Failed: failed to find the end of the response header\n"); return 2; } assert(received <= total); // Possible new 302 if (received > 13 && ( 0 == strncmp(response, "HTTP/1.1 302 ", 13) || 0 == strncmp(response, "HTTP/1.0 302 ", 13))) { len = received; again_302 = 1; goto parse_location_header; } // Parse the header len = received; assert(len <= total); new_line = NULL; long long found_length = -1; i = 0; response[sizeof(response) - 1] = 0; while (i < len) { new_line = strstr(response + i, "\r\n"); if (NULL == new_line) { break; } *new_line = 0; if (0 == strncmp(response + i, "Content-Length: ", 16)) { found_length = atoll(response + i + 16); break; } *new_line = '\r'; i = new_line - response + 2; } if (-1 == found_length) { close(sockfd); fprintf(stderr, "Auto-update Failed: failed to find a Content-Length header\n"); return 2; } if (0 == found_length) { close(sockfd); fprintf(stderr, "Auto-update Failed: found a Content-Length header of zero\n"); return 2; } assert(found_length > 0); // Read the body // header_end -> \r\n\r\n assert(header_end); assert(header_end + 4 <= response + received); // put the rest of over-read content when reading header size_t the_rest = response + received - (header_end + 4); char *body_buffer = (char *)(malloc(found_length)); if (NULL == body_buffer) { close(sockfd); fprintf(stderr, "Auto-update Failed: Insufficient memory\n"); return 2; } memcpy(body_buffer, (header_end + 4), the_rest); char *body_buffer_ptr = body_buffer + the_rest; char *body_buffer_end = body_buffer + found_length; // read the remaining body received = the_rest; fprintf(stderr, "\r%lld / %lld bytes finished (%lld%%)", received, found_length, received*100LL/found_length); fflush(stderr); while (received < found_length) { size_t space = 100 * 1024; if (space > body_buffer_end - body_buffer_ptr) { space = body_buffer_end - body_buffer_ptr; } bytes = read(sockfd, body_buffer_ptr, space); if (bytes < 0) { fprintf(stderr, "Auto-update Failed: read failed\n"); free(body_buffer); close(sockfd); return 2; } if (bytes == 0) { /* EOF */ break; } received += bytes; body_buffer_ptr += bytes; fprintf(stderr, "\r%lld / %lld bytes finished (%lld%%)", received, found_length, received*100LL/found_length); fflush(stderr); } if (received != found_length) { assert(received < found_length); fprintf(stderr, "Auto-update Failed: prematurely reached EOF after reading %lld bytes\n", received); close(sockfd); free(body_buffer); return 2; } fprintf(stderr, "\n"); fflush(stderr); close(sockfd); // Inflate to a file fprintf(stderr, "Inflating"); fflush(stderr); unsigned full_length = found_length; unsigned half_length = found_length / 2; unsigned uncompLength = full_length; char* uncomp = (char*) calloc( sizeof(char), uncompLength ); if (NULL == uncomp) { fprintf(stderr, "Auto-update Failed: Insufficient memory\n"); free(body_buffer); return 2; } z_stream strm; strm.next_in = (Bytef *)body_buffer; strm.avail_in = found_length; strm.total_out = 0; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; short done = 0; if (inflateInit2(&strm, (16+MAX_WBITS)) != Z_OK) { free(uncomp); free(body_buffer); fprintf(stderr, "Auto-update Failed: inflateInit2 failed\n"); return 2; } while (!done) { // If our output buffer is too small if (strm.total_out >= uncompLength ) { // Increase size of output buffer char* uncomp2 = (char*) calloc( sizeof(char), uncompLength + half_length ); if (NULL == uncomp2) { free(uncomp); free(body_buffer); fprintf(stderr, "Auto-update Failed: calloc failed\n"); return 2; } memcpy( uncomp2, uncomp, uncompLength ); uncompLength += half_length ; free( uncomp ); uncomp = uncomp2 ; } strm.next_out = (Bytef *) (uncomp + strm.total_out); strm.avail_out = uncompLength - strm.total_out; // Inflate another chunk. int err = inflate(&strm, Z_SYNC_FLUSH); if (err == Z_STREAM_END) { done = 1; } else if (err != Z_OK) { fprintf(stderr, "Auto-update Failed: inflate failed with %d\n", err); free(uncomp); free(body_buffer); return 2; } } if (inflateEnd (&strm) != Z_OK) { fprintf(stderr, "Auto-update Failed: inflateInit2 failed\n"); free(uncomp); free(body_buffer); return 2; } char *tmpdir = autoupdate_tmpdir(); if (NULL == tmpdir) { fprintf(stderr, "Auto-update Failed: no temporary folder found\n"); free(uncomp); free(body_buffer); return 2; } char *tmpf = autoupdate_tmpf(tmpdir, NULL); if (NULL == tmpf) { fprintf(stderr, "Auto-update Failed: no temporary file available\n"); free((void*)(tmpdir)); free(uncomp); free(body_buffer); return 2; } FILE *fp = fopen(tmpf, "wb"); if (NULL == fp) { fprintf(stderr, "Auto-update Failed: cannot open temporary file %s\n", tmpf); free((void*)(tmpdir)); free((void*)(tmpf)); free(uncomp); free(body_buffer); return 2; } fprintf(stderr, " to %s\n", tmpf); size_t fwrite_ret = fwrite(uncomp, sizeof(char), strm.total_out, fp); if (fwrite_ret != strm.total_out) { fprintf(stderr, "Auto-update Failed: fwrite failed %s\n", tmpf); fclose(fp); unlink(tmpf); free((void*)(tmpdir)); free((void*)(tmpf)); free(uncomp); free(body_buffer); return 2; } fclose(fp); free(uncomp); free(body_buffer); // chmod size_t exec_path_len = 2 * PATH_MAX; char* exec_path = (char*)(malloc(exec_path_len)); if (NULL == exec_path) { fprintf(stderr, "Auto-update Failed: Insufficient memory allocating exec_path\n"); free((void*)(tmpdir)); free((void*)(tmpf)); unlink(tmpf); return 2; } if (autoupdate_exepath(exec_path, &exec_path_len) != 0) { if (!argv[0]) { fprintf(stderr, "Auto-update Failed: missing argv[0]\n"); free((void*)(tmpdir)); free((void*)(tmpf)); unlink(tmpf); return 2; } assert(strlen(argv[0]) < 2 * PATH_MAX); memcpy(exec_path, argv[0], strlen(argv[0])); } struct stat current_st; int ret = stat(exec_path, ¤t_st); if (0 != ret) { fprintf(stderr, "Auto-update Failed: stat failed for %s\n", exec_path); free(exec_path); free((void*)(tmpdir)); free((void*)(tmpf)); unlink(tmpf); return 2; } ret = chmod(tmpf, current_st.st_mode | S_IXUSR); if (0 != ret) { fprintf(stderr, "Auto-update Failed: chmod failed for %s\n", tmpf); free(exec_path); free((void*)(tmpdir)); free((void*)(tmpf)); unlink(tmpf); return 2; } // Move the new version into the original place fprintf(stderr, "Moving the new version from %s to %s\n", tmpf, exec_path); ret = rename(tmpf, exec_path); if (0 != ret) { fprintf(stderr, "Auto-update Failed: failed calling rename %s to %s\n", tmpf, exec_path); free(exec_path); free((void*)(tmpdir)); free((void*)(tmpf)); unlink(tmpf); return 2; } fprintf(stderr, "Restarting...\n"); ret = execv(exec_path, argv); // we should not reach this point fprintf(stderr, "Auto-update Failed: execv failed with %d (errno %d)\n", ret, errno); free(exec_path); free((void*)(tmpdir)); free((void*)(tmpf)); unlink(tmpf); return 3; } #endif // _WIN32 ================================================ FILE: src/autoupdate_internal.h ================================================ /* * Copyright (c) 2020 Minqi Pan et al. * * This file is part of libautoupdate, distributed under the MIT License * For full terms see the included LICENSE file */ #ifndef AUTOUPDATE_INTERNAL_H_A40E122A #define AUTOUPDATE_INTERNAL_H_A40E122A #include #include #ifdef _WIN32 #define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) ) PACK( struct ZIPLocalFileHeader { uint32_t signature; uint16_t versionNeededToExtract; // unsupported uint16_t generalPurposeBitFlag; // unsupported uint16_t compressionMethod; uint16_t lastModFileTime; uint16_t lastModFileDate; uint32_t crc32; uint32_t compressedSize; uint32_t uncompressedSize; uint16_t fileNameLength; uint16_t extraFieldLength; // unsupported }); wchar_t* autoupdate_tmpdir(); wchar_t* autoupdate_tmpf(wchar_t *tmpdir, const char *ext_name); short autoupdate_should_proceed_24_hours(int argc, wchar_t *wargv[], short will_write); #else char* autoupdate_tmpdir(); char* autoupdate_tmpf(char *tmpdir, const char *ext_name); short autoupdate_should_proceed_24_hours(int argc, char *argv[], short will_write); #endif // _WIN32 short autoupdate_should_proceed(); int autoupdate_exepath(char* buffer, size_t* size); #endif /* end of include guard: AUTOUPDATE_INTERNAL_H_A40E122A */ ================================================ FILE: src/exepath.c ================================================ /* * Copyright (c) 2020 Minqi Pan et al. * * This file is part of libautoupdate, distributed under the MIT License * For full terms see the included LICENSE file */ /* * autoupdate_exepath is derived from uv_exepath of libuv. * libuv is licensed for use as follows: * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "autoupdate.h" #include "autoupdate_internal.h" #ifdef _WIN32 #include #include #include #include #include #include #include #include int autoupdate_exepath(char* buffer, size_t* size_ptr) { int utf8_len, utf16_buffer_len, utf16_len; WCHAR* utf16_buffer; int err; if (buffer == NULL || size_ptr == NULL || *size_ptr == 0) { return -1; } if (*size_ptr > 32768) { /* Windows paths can never be longer than this. */ utf16_buffer_len = 32768; } else { utf16_buffer_len = (int) *size_ptr; } utf16_buffer = (WCHAR*) malloc(sizeof(WCHAR) * utf16_buffer_len); if (!utf16_buffer) { return -1; } /* Get the path as UTF-16. */ utf16_len = GetModuleFileNameW(NULL, utf16_buffer, utf16_buffer_len); if (utf16_len <= 0) { err = GetLastError(); goto error; } /* utf16_len contains the length, *not* including the terminating null. */ utf16_buffer[utf16_len] = L'\0'; /* Convert to UTF-8 */ utf8_len = WideCharToMultiByte(CP_UTF8, 0, utf16_buffer, -1, buffer, (int) *size_ptr, NULL, NULL); if (utf8_len == 0) { err = GetLastError(); goto error; } free(utf16_buffer); /* utf8_len *does* include the terminating null at this point, but the */ /* returned size shouldn't. */ *size_ptr = utf8_len - 1; return 0; error: free(utf16_buffer); return -1; } #endif #ifdef __linux__ #include int autoupdate_exepath(char* buffer, size_t* size) { ssize_t n; if (buffer == NULL || size == NULL || *size == 0) return -1; n = *size - 1; if (n > 0) n = readlink("/proc/self/exe", buffer, n); if (n == -1) return -1; buffer[n] = '\0'; *size = n; return 0; } #endif #ifdef __APPLE__ #include #include #include #include #include // PATH_MAX int autoupdate_exepath(char* buffer, size_t* size) { /* realpath(exepath) may be > PATH_MAX so double it to be on the safe side. */ char abspath[PATH_MAX * 2 + 1]; char exepath[PATH_MAX + 1]; uint32_t exepath_size; size_t abspath_size; if (buffer == NULL || size == NULL || *size == 0) return -1; exepath_size = sizeof(exepath); if (_NSGetExecutablePath(exepath, &exepath_size)) return -1; if (realpath(exepath, abspath) != abspath) return -1; abspath_size = strlen(abspath); if (abspath_size == 0) return -1; *size -= 1; if (*size > abspath_size) *size = abspath_size; memcpy(buffer, abspath, *size); buffer[*size] = '\0'; return 0; } #endif ================================================ FILE: src/inflate.c ================================================ /* * Copyright (c) 2020 Minqi Pan et al. * * This file is part of libautoupdate, distributed under the MIT License * For full terms see the included LICENSE file */ #include "autoupdate.h" #include "autoupdate_internal.h" ================================================ FILE: src/tmpf.c ================================================ /* * Copyright (c) 2020 Minqi Pan et al. * * This file is part of libautoupdate, distributed under the MIT License * For full terms see the included LICENSE file */ #include "autoupdate.h" #include "autoupdate_internal.h" #include #include #ifdef _WIN32 #include #include #include #include wchar_t* autoupdate_tmpdir() { const int squash_win32_buf_sz = 32767; wchar_t squash_win32_buf[32767 + 1]; DWORD length; length = GetEnvironmentVariableW(L"TEMP", squash_win32_buf, squash_win32_buf_sz); if (length) { goto out; } length = GetEnvironmentVariableW(L"TMP", squash_win32_buf, squash_win32_buf_sz); if (length) { goto out; } length = GetEnvironmentVariableW(L"SystemRoot", squash_win32_buf, squash_win32_buf_sz); if (!length) { length = GetEnvironmentVariableW(L"windir", squash_win32_buf, squash_win32_buf_sz); } if (length) { if (length + 5 >= squash_win32_buf_sz) { return NULL; } squash_win32_buf[length] = L'\\'; squash_win32_buf[length + 1] = L't'; squash_win32_buf[length + 2] = L'e'; squash_win32_buf[length + 3] = L'm'; squash_win32_buf[length + 4] = L'p'; squash_win32_buf[length + 5] = 0; length += 5; goto out; } return NULL; out: if (length >= 2 && L'\\' == squash_win32_buf[length - 1] && L':' != squash_win32_buf[length - 2]) { squash_win32_buf[length - 1] = 0; length -= 1; } return wcsdup(squash_win32_buf); } wchar_t* autoupdate_tmpf(wchar_t *tmpdir, const char *ext_name) { const int squash_win32_buf_sz = 32767; wchar_t squash_win32_buf[32767 + 1]; size_t curlen, size_ret; int try_cnt = 0; srand(time(NULL) * getpid()); squash_win32_buf[squash_win32_buf_sz] = 0; while (try_cnt < 3) { squash_win32_buf[0] = 0; assert(0 == wcslen(squash_win32_buf)); wcsncat(squash_win32_buf + wcslen(squash_win32_buf), tmpdir, squash_win32_buf_sz - wcslen(squash_win32_buf)); wcsncat(squash_win32_buf + wcslen(squash_win32_buf), L"\\libautoupdate-", squash_win32_buf_sz - wcslen(squash_win32_buf)); // up to 33 characters for _itoa if (squash_win32_buf_sz - wcslen(squash_win32_buf) <= 33) { return NULL; } _itow(rand(), squash_win32_buf + wcslen(squash_win32_buf), 10); if (ext_name) { wcsncat(squash_win32_buf + wcslen(squash_win32_buf), L".", squash_win32_buf_sz - wcslen(squash_win32_buf)); } if (ext_name) { curlen = wcslen(squash_win32_buf); size_ret = mbstowcs((wchar_t*)(squash_win32_buf) + curlen, ext_name, squash_win32_buf_sz - curlen); if ((size_t)-1 == size_ret) { return NULL; } *((wchar_t*)(squash_win32_buf) + curlen + size_ret) = 0; } if (!PathFileExistsW(squash_win32_buf)) { return wcsdup(squash_win32_buf); } ++try_cnt; } return NULL; } #else #include #include #include #include #include char* autoupdate_tmpdir() { char *try_try; size_t length; try_try = getenv("TMPDIR"); if (try_try) { goto out; } try_try = getenv("TMP"); if (try_try) { goto out; } try_try = getenv("TEMP"); if (try_try) { goto out; } try_try = "/tmp"; out: try_try = strdup(try_try); length = strlen(try_try); if (length >= 2 && '/' == try_try[length - 1]) { try_try[length - 1] = 0; } return try_try; } char* autoupdate_tmpf(char *tmpdir, const char *ext_name) { const int squash_buf_sz = 32767; char squash_buf[squash_buf_sz + 1]; int ret, try_cnt = 0; struct stat statbuf; srand(time(NULL) * getpid()); while (try_cnt < 3) { if (ext_name) { ret = snprintf(squash_buf, squash_buf_sz, "%s/libautoupdate-%d.%s", tmpdir, rand(), ext_name); } else { ret = snprintf(squash_buf, squash_buf_sz, "%s/libautoupdate-%d", tmpdir, rand()); } if (-1 == ret) { return NULL; } if (-1 == stat(squash_buf, &statbuf)) { return strdup(squash_buf); } ++try_cnt; } return NULL; } #endif // _WIN32 ================================================ FILE: src/utils.c ================================================ /* * Copyright (c) 2020 Minqi Pan et al. * * This file is part of libautoupdate, distributed under the MIT License * For full terms see the included LICENSE file */ #include "autoupdate.h" #include "autoupdate_internal.h" #include #ifdef _WIN32 #include #include #include #include #include #include #include #include #include short autoupdate_should_proceed() { TCHAR lpBuffer[32767 + 1]; if (0 == GetEnvironmentVariable("CI", lpBuffer, 32767)) { return 1; } else { return 0; } } #else #include #include #include #include #include #include #include #include short autoupdate_should_proceed() { if (NULL == getenv("CI")) { return 1; } else { return 0; } } #endif // _WIN32 #ifdef _WIN32 short autoupdate_should_proceed_24_hours(int argc, wchar_t *wargv[], short will_write) { const KNOWNFOLDERID rfid = FOLDERID_Profile; PWSTR ppszPath = NULL; HRESULT hret; wchar_t filepath[2 * 32768]; const wchar_t *filename = L"\\.libautoupdate"; size_t exec_path_len = 2 * 32768; char exec_path[2 * 32768]; #else short autoupdate_should_proceed_24_hours(int argc, char *argv[], short will_write) { char *filepath = NULL; const char *filename = "/.libautoupdate"; size_t exec_path_len = 2 * PATH_MAX; char exec_path[2 * PATH_MAX]; struct passwd *pw; const char *homedir; #endif // _WIN32 short has_written = 0; time_t time_now; long item_time; char *item_string = NULL; char *item_space; char *cursor; char *string = NULL; char *string0 = NULL; long fsize; FILE *f = NULL; int ret; size_t size_t_ret; if (autoupdate_exepath(exec_path, &exec_path_len) != 0) { #ifdef _WIN32 goto exit; #else if (!argv[0]) { goto exit; } assert(strlen(argv[0]) < 2 * PATH_MAX); memcpy(exec_path, argv[0], strlen(argv[0])); #endif } time_now = time(NULL); if ((time_t)-1 == time_now) { goto exit; } #ifdef _WIN32 hret = SHGetKnownFolderPath( &rfid, 0, NULL, &ppszPath ); if (S_OK != hret) { goto exit; } memcpy(filepath, ppszPath, wcslen(ppszPath) * sizeof(wchar_t)); memcpy(filepath + wcslen(ppszPath), filename, wcslen(filename) * sizeof(wchar_t)); filepath[wcslen(ppszPath) + wcslen(filename)] = 0; f = _wfopen(filepath, L"rb"); #else pw = getpwuid(getuid()); if (NULL == pw) { goto exit; } homedir = pw->pw_dir; if (NULL == homedir) { goto exit; } filepath = malloc(strlen(homedir) + strlen(filename) + 1); if (NULL == filepath) { goto exit; } memcpy(filepath, homedir, strlen(homedir)); memcpy(filepath + strlen(homedir), filename, strlen(filename)); filepath[strlen(homedir) + strlen(filename)] = 0; f = fopen(filepath, "rb"); #endif // _WIN32 if (NULL == f) { if (will_write) { string0 = NULL; goto write; } else { goto exit; } } ret = fseek(f, 0, SEEK_END); if (0 != ret) { goto exit; } fsize = ftell(f); if (fsize <= 0) { goto exit; } ret = fseek(f, 0, SEEK_SET); if (0 != ret) { goto exit; } string = malloc(fsize + 1); if (NULL == string) { goto exit; } string0 = string; size_t_ret = fread(string, fsize, 1, f); if (1 != size_t_ret) { goto exit; } string[fsize] = 0; ret = fclose(f); if (0 != ret) { goto exit; } f = NULL; string[fsize] = 0; while (string < string0 + fsize) { cursor = strchr(string, '\n'); if (!cursor) { if (will_write) { string0 = NULL; goto write; } else { goto exit; } } *cursor = 0; item_space = strchr(string, ' '); if (!item_space) { goto exit; } *item_space = 0; item_time = atol(string); item_string = item_space + 1; if (exec_path_len == cursor - item_string && 0 == memcmp(item_string, exec_path, exec_path_len)) { if (will_write) { if (item_time >= 1000000000 && time_now >= 1000000000) { has_written = 1; #ifdef _WIN32 _ltoa(time_now, string, 10); #else ret = sprintf(string, "%ld", time_now); #endif // _WIN32 string[10] = ' '; *cursor = '\n'; break; } } else if (time_now - item_time < 24 * 3600) { return 0; } } *item_space = ' '; *cursor = '\n'; string = cursor + 1; } write: if (will_write) { #ifdef _WIN32 f = _wfopen(filepath, L"wb"); #else f = fopen(filepath, "wb"); #endif // _WIN32 if (NULL == f) { goto exit; } if (string0) { ret = fwrite(string0, fsize, 1, f); if (1 != ret) { goto exit; } } if (!has_written) { char writting[20]; #ifdef _WIN32 _ltoa(time_now, writting, 10); ret = fwrite(writting, strlen(writting), 1, f); if (1 != ret) { goto exit; } ret = fwrite(" ", 1, 1, f); if (1 != ret) { goto exit; } #else ret = sprintf(writting, "%ld ", time_now); ret = fwrite(writting, strlen(writting), 1, f); if (1 != ret) { goto exit; } #endif // _WIN32 ret = fwrite(exec_path, exec_path_len, 1, f); if (1 != ret) { goto exit; } ret = fwrite("\n", 1, 1, f); if (1 != ret) { goto exit; } } } exit: if (f) { fclose(f); } if (string0) { free(string0); } #ifdef _WIN32 if (ppszPath) { CoTaskMemFree(ppszPath); } #else if (filepath) { free(filepath); } #endif return 1; } ================================================ FILE: tests/main.c ================================================ /* * Copyright (c) 2020 Minqi Pan et al. * * This file is part of libautoupdate, distributed under the MIT License * For full terms see the included LICENSE file */ #include "autoupdate.h" #include "autoupdate_internal.h" #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #endif #ifdef __linux__ #include #endif #define EXPECT(condition) expect(condition, __FILE__, __LINE__) static void expect(short condition, const char *file, int line) { if (condition) { fprintf(stderr, "."); } else { fprintf(stderr, "x"); fprintf(stderr, "\nFAILED: %s line %d\n", file, line); exit(1); } fflush(stderr); } #ifdef _WIN32 int main(int argc, wchar_t *wargv[]) #else int main(int argc, char *argv[]) #endif { int ret; struct stat statbuf; size_t exec_path_len; char* exec_path; // test autoupdate_exepath #ifdef _WIN32 exec_path_len = 2 * MAX_PATH; #else exec_path_len = 2 * PATH_MAX; #endif exec_path = malloc(exec_path_len); ret = autoupdate_exepath(exec_path, &exec_path_len); EXPECT(0 == ret); ret = stat(exec_path, &statbuf); EXPECT(0 == ret); EXPECT(S_IFREG == (S_IFMT & statbuf.st_mode)); // test autoupdate_should_proceed() autoupdate_should_proceed(); // test autoupdate_should_proceed_24_hours() #ifdef _WIN32 autoupdate_should_proceed_24_hours(argc, wargv, 0); autoupdate_should_proceed_24_hours(argc, wargv, 1); autoupdate_should_proceed_24_hours(argc, wargv, 0); autoupdate_should_proceed_24_hours(argc, wargv, 1); #else autoupdate_should_proceed_24_hours(argc, argv, 0); autoupdate_should_proceed_24_hours(argc, argv, 1); autoupdate_should_proceed_24_hours(argc, argv, 0); autoupdate_should_proceed_24_hours(argc, argv, 1); #endif // test autoupdate() #ifdef _WIN32 autoupdate( argc, wargv, "enclose.io", "80", "/rubyc/rubyc-x64.zip", "---^_^---", 1 ); #endif #ifdef __linux__ autoupdate( argc, argv, "enclose.io", 80, "/rubyc/rubyc-linux-x64.gz", "---^_^---", 1 ); #endif #ifdef __APPLE__ autoupdate( argc, argv, "enclose.io", 80, "/rubyc/rubyc-darwin-x64.gz", "---^_^---", 1 ); #endif // should never reach this point return 1; }