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 <pmq2001@gmail.com>
================================================
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 <pmq2001@gmail.com>
Venkat Ram <venkatpetit@gmail.com>
================================================
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.
[](https://travis-ci.org/pmq20/libautoupdate)
[](https://ci.appveyor.com/project/pmq20/libautoupdate/branch/master)

## 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 <autoupdate.h>
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 <autoupdate.h>
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 <wchar.h>
int autoupdate(
int argc,
wchar_t *wargv[],
const char *host,
const char *port,
const char *path,
const char *current,
short force
);
#else
#include <stdint.h>
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 <assert.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <conio.h>
#include <stdint.h>
#include <stdlib.h> /* exit */
#include <wchar.h>
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 <assert.h>
#include <stdio.h> /* printf, sprintf */
#include <stdlib.h> /* exit */
#include <unistd.h> /* read, write, close */
#include <string.h> /* memcpy, memset */
#include <sys/socket.h> /* socket, connect */
#include <netinet/in.h> /* struct sockaddr_in, struct sockaddr */
#include <netdb.h> /* struct hostent, gethostbyname */
#include <unistd.h>
#include <sys/select.h>
#include <limits.h> /* PATH_MAX */
#include <sys/stat.h> /* struct stat */
#include <errno.h>
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 <stddef.h>
#include <stdint.h>
#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 <assert.h>
#include <direct.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <wchar.h>
#include <windows.h>
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 <unistd.h>
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 <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <mach-o/dyld.h>
#include <limits.h> // 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 <time.h>
#include <stdlib.h>
#ifdef _WIN32
#include <Windows.h>
#include <Shlwapi.h>
#include <process.h>
#include <assert.h>
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 <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
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 <assert.h>
#ifdef _WIN32
#include <Windows.h>
#include <wchar.h>
#include <Shlobj.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <time.h>
#include <limits.h>
short autoupdate_should_proceed()
{
TCHAR lpBuffer[32767 + 1];
if (0 == GetEnvironmentVariable("CI", lpBuffer, 32767)) {
return 1;
} else {
return 0;
}
}
#else
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
#include <time.h>
#include <limits.h>
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 <limits.h>
#include <assert.h>
#include <sys/stat.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <Windows.h>
#include <wchar.h>
#endif
#ifdef __linux__
#include <linux/limits.h>
#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;
}
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
SYMBOL INDEX (14 symbols across 6 files)
FILE: src/autoupdate.c
function autoupdate (line 24) | int autoupdate(
function autoupdate (line 639) | int autoupdate(
FILE: src/autoupdate_internal.h
type ZIPLocalFileHeader (line 19) | struct ZIPLocalFileHeader
FILE: src/exepath.c
function autoupdate_exepath (line 45) | int autoupdate_exepath(char* buffer, size_t* size_ptr) {
function autoupdate_exepath (line 107) | int autoupdate_exepath(char* buffer, size_t* size) {
function autoupdate_exepath (line 134) | int autoupdate_exepath(char* buffer, size_t* size) {
FILE: src/tmpf.c
function wchar_t (line 21) | wchar_t* autoupdate_tmpdir()
function wchar_t (line 61) | wchar_t* autoupdate_tmpf(wchar_t *tmpdir, const char *ext_name)
type stat (line 137) | struct stat
FILE: src/utils.c
function autoupdate_should_proceed (line 25) | short autoupdate_should_proceed()
function autoupdate_should_proceed (line 46) | short autoupdate_should_proceed()
function autoupdate_should_proceed_24_hours (line 68) | short autoupdate_should_proceed_24_hours(int argc, char *argv[], short w...
FILE: tests/main.c
function expect (line 31) | static void expect(short condition, const char *file, int line)
type stat (line 51) | struct stat
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (64K chars).
[
{
"path": ".gitignore",
"chars": 439,
"preview": "/build/\n\n# Prerequisites\n*.d\n\n# Object files\n*.o\n*.ko\n*.obj\n*.elf\n\n# Linker output\n*.ilk\n*.map\n*.exp\n\n# Precompiled Head"
},
{
"path": ".mailmap",
"chars": 29,
"preview": "Minqi Pan <pmq2001@gmail.com>"
},
{
"path": ".travis.yml",
"chars": 193,
"preview": "language: c\n\nos:\n - linux\n - osx\n\ncompiler:\n - gcc\n - clang\n\nscript:\n - cmake --version\n - mkdir build && cd build"
},
{
"path": "AUTHORS",
"chars": 108,
"preview": "# Authors ordered by first contribution.\n\nMinqi Pan <pmq2001@gmail.com>\nVenkat Ram <venkatpetit@gmail.com>\n\n"
},
{
"path": "CMakeLists.txt",
"chars": 763,
"preview": "# Copyright (c) 2020 Minqi Pan et al.\n# \n# This file is part of libautoupdate, distributed under the MIT License\n# For f"
},
{
"path": "LICENSE",
"chars": 1073,
"preview": "MIT License\n\nCopyright (c) 2020 Minqi Pan et al.\n\nPermission is hereby granted, free of charge, to any person obtaining "
},
{
"path": "README.md",
"chars": 5176,
"preview": "# Libautoupdate\n\nCross-platform C library that enables your application to auto-update itself in place.\n\n[![Build Status"
},
{
"path": "appveyor.yml",
"chars": 894,
"preview": "version: '{build}'\r\n\r\nenvironment:\r\n matrix:\r\n - GENERATOR: \"Visual Studio 14 2015 Win64\"\r\n ARCH: 64\r\n PlatformT"
},
{
"path": "include/autoupdate.h",
"chars": 645,
"preview": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n *"
},
{
"path": "libautoupdate.gyp",
"chars": 559,
"preview": "# Copyright (c) 2020 Minqi Pan et al.\n# \n# This file is part of libautoupdate, distributed under the MIT License\n# For f"
},
{
"path": "src/autoupdate.c",
"chars": 30764,
"preview": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n *"
},
{
"path": "src/autoupdate_internal.h",
"chars": 1319,
"preview": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n *"
},
{
"path": "src/exepath.c",
"chars": 4203,
"preview": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n *"
},
{
"path": "src/inflate.c",
"chars": 228,
"preview": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n *"
},
{
"path": "src/tmpf.c",
"chars": 3898,
"preview": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n *"
},
{
"path": "src/utils.c",
"chars": 5283,
"preview": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n *"
},
{
"path": "tests/main.c",
"chars": 2630,
"preview": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n *"
}
]
About this extraction
This page contains the full source code of the pmq20/libautoupdate GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (56.8 KB), approximately 17.4k tokens, and a symbol index with 14 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.