#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #include #include #else #define MAX_PATH PATH_MAX #endif static char hex_digits[] = "0123456789abcdef"; static int lpm_hash(lua_State* L) { size_t len; const char* data = luaL_checklstring(L, 1, &len); const char* type = luaL_optstring(L, 2, "string"); unsigned char buffer[EVP_MAX_MD_SIZE]; EVP_MD_CTX* c = EVP_MD_CTX_new(); EVP_MD_CTX_init(c); EVP_DigestInit_ex(c, EVP_sha256(), NULL); if (strcmp(type, "file") == 0) { FILE* file = fopen(data, "rb"); if (!file) { EVP_DigestFinal(c, buffer, NULL); return luaL_error(L, "can't open %s", data); } while (1) { unsigned char chunk[4096]; size_t bytes = fread(chunk, 1, sizeof(chunk), file); EVP_DigestUpdate(c, chunk, bytes); if (bytes < 4096) break; } fclose(file); } else { EVP_DigestUpdate(c, data, len); } int digest_length; EVP_DigestFinal(c, buffer, &digest_length); EVP_MD_CTX_free(c); char hex_buffer[EVP_MAX_MD_SIZE * 2]; for (size_t i = 0; i < digest_length; ++i) { hex_buffer[i*2+0] = hex_digits[buffer[i] >> 4]; hex_buffer[i*2+1] = hex_digits[buffer[i] & 0xF]; } lua_pushlstring(L, hex_buffer, digest_length * 2); hex_buffer[digest_length*2]=0; return 1; } int lpm_symlink(lua_State* L) { #ifndef _WIN32 if (symlink(luaL_checkstring(L, 1), luaL_checkstring(L, 2))) return luaL_error(L, "can't create symlink %s: %s", luaL_checkstring(L, 2), strerror(errno)); return 0; #else return luaL_error(L, "can't create symbolic link %s: your operating system sucks", luaL_checkstring(L, 2)); #endif } int lpm_chmod(lua_State* L) { if (chmod(luaL_checkstring(L, 1), luaL_checkinteger(L, 2))) return luaL_error(L, "can't chmod %s: %s", luaL_checkstring(L, 1), strerror(errno)); return 0; } /** BEGIN STOLEN LITE CODE **/ #if _WIN32 static LPWSTR utfconv_utf8towc(const char *str) { LPWSTR output; int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); if (len == 0) return NULL; output = (LPWSTR) malloc(sizeof(WCHAR) * len); if (output == NULL) return NULL; len = MultiByteToWideChar(CP_UTF8, 0, str, -1, output, len); if (len == 0) { free(output); return NULL; } return output; } static char *utfconv_wctoutf8(LPCWSTR str) { char *output; int len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); if (len == 0) return NULL; output = (char *) malloc(sizeof(char) * len); if (output == NULL) return NULL; len = WideCharToMultiByte(CP_UTF8, 0, str, -1, output, len, NULL, NULL); if (len == 0) { free(output); return NULL; } return output; } #endif static int lpm_ls(lua_State *L) { const char *path = luaL_checkstring(L, 1); #ifdef _WIN32 lua_settop(L, 1); lua_pushstring(L, path[0] == 0 || strchr("\\/", path[strlen(path) - 1]) != NULL ? "*" : "/*"); lua_concat(L, 2); path = lua_tostring(L, -1); LPWSTR wpath = utfconv_utf8towc(path); if (wpath == NULL) return luaL_error(L, "can't ls %s: invalid utf8 character conversion", path); WIN32_FIND_DATAW fd; HANDLE find_handle = FindFirstFileExW(wpath, FindExInfoBasic, &fd, FindExSearchNameMatch, NULL, 0); free(wpath); if (find_handle == INVALID_HANDLE_VALUE) return luaL_error(L, "can't ls %s: %d", path, GetLastError()); char mbpath[MAX_PATH * 4]; // utf-8 spans 4 bytes at most int len, i = 1; lua_newtable(L); do { if (wcscmp(fd.cFileName, L".") == 0) { continue; } if (wcscmp(fd.cFileName, L"..") == 0) { continue; } len = WideCharToMultiByte(CP_UTF8, 0, fd.cFileName, -1, mbpath, MAX_PATH * 4, NULL, NULL); if (len == 0) { break; } lua_pushlstring(L, mbpath, len - 1); // len includes \0 lua_rawseti(L, -2, i++); } while (FindNextFileW(find_handle, &fd)); int err = GetLastError(); FindClose(find_handle); if (err != ERROR_NO_MORE_FILES) return luaL_error(L, "can't ls %s: %d", path, GetLastError()); return 1; #else DIR *dir = opendir(path); if (!dir) return luaL_error(L, "can't ls %s: %d", path, strerror(errno)); lua_newtable(L); int i = 1; struct dirent *entry; while ( (entry = readdir(dir)) ) { if (strcmp(entry->d_name, "." ) == 0) { continue; } if (strcmp(entry->d_name, "..") == 0) { continue; } lua_pushstring(L, entry->d_name); lua_rawseti(L, -2, i); i++; } closedir(dir); return 1; #endif } static int lpm_rmdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); #ifdef _WIN32 LPWSTR wpath = utfconv_utf8towc(path); int deleted = RemoveDirectoryW(wpath); free(wpath); if (!deleted) return luaL_error(L, "can't rmdir %s: %d", path, GetLastError()); #else if (remove(path)) return luaL_error(L, "can't rmdir %s: %s", path, strerror(errno)); #endif return 0; } static int lpm_mkdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); #ifdef _WIN32 LPWSTR wpath = utfconv_utf8towc(path); if (wpath == NULL) return luaL_error(L, "can't mkdir %s: invalid utf8 character conversion", path); int err = _wmkdir(wpath); free(wpath); #else int err = mkdir(path, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); #endif if (err < 0) return luaL_error(L, "can't mkdir %s: %s", path, strerror(errno)); return 0; } static int lpm_stat(lua_State *L) { const char *path = luaL_checkstring(L, 1); lua_newtable(L); #ifdef _WIN32 #define realpath(x, y) _wfullpath(y, x, MAX_PATH) struct _stat s; LPWSTR wpath = utfconv_utf8towc(path); if (wpath == NULL) return luaL_error(L, "can't stat %s: invalid utf8 character conversion", path); int err = _wstat(wpath, &s); LPWSTR wfullpath = realpath(wpath, NULL); free(wpath); if (!wfullpath) return 0; char *abs_path = utfconv_wctoutf8(wfullpath); free(wfullpath); #else struct stat s; int err = lstat(path, &s); char *abs_path = realpath(path, NULL); #endif if (err || !abs_path) { lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; } lua_pushstring(L, abs_path); lua_setfield(L, -2, "abs_path"); lua_pushvalue(L, 1); lua_setfield(L, -2, "path"); #if __linux__ if (S_ISLNK(s.st_mode)) { char buffer[PATH_MAX]; ssize_t len = readlink(path, buffer, sizeof(buffer)); if (len < 0) return 0; lua_pushlstring(L, buffer, len); } else lua_pushnil(L); lua_setfield(L, -2, "symlink"); if (S_ISLNK(s.st_mode)) err = stat(path, &s); if (err) return 1; #endif lua_pushinteger(L, s.st_mtime); lua_setfield(L, -2, "modified"); lua_pushinteger(L, s.st_size); lua_setfield(L, -2, "size"); if (S_ISREG(s.st_mode)) { lua_pushstring(L, "file"); } else if (S_ISDIR(s.st_mode)) { lua_pushstring(L, "dir"); } else { lua_pushnil(L); } lua_setfield(L, -2, "type"); return 1; } /** END STOLEN LITE CODE **/ static const char* git_error_last_string() { const git_error* last_error = git_error_last(); return last_error->message; } static int git_get_id(git_oid* commit_id, git_repository* repository, const char* name) { int length = strlen(name); int is_hex = length == 40; for (int i = 0; is_hex && i < length; ++i) is_hex = isxdigit(name[i]); if (!is_hex) return git_reference_name_to_id(commit_id, repository, name); return git_oid_fromstr(commit_id, name); } static git_repository* luaL_checkgitrepo(lua_State* L, int index) { const char* path = luaL_checkstring(L, index); git_repository* repository; if (git_repository_open(&repository, path)) return (void*)(long long)luaL_error(L, "git open error: %s", git_error_last_string()); return repository; } static git_commit* git_retrieve_commit(git_repository* repository, const char* commit_name) { git_oid commit_id; git_commit* commit; if (git_get_id(&commit_id, repository, commit_name) || git_commit_lookup(&commit, repository, &commit_id)) return NULL; return commit; } static int lpm_reset(lua_State* L) { git_repository* repository = luaL_checkgitrepo(L, 1); const char* commit_name = luaL_checkstring(L, 2); const char* type = luaL_checkstring(L, 3); git_commit* commit = git_retrieve_commit(repository, commit_name); if (!commit) { git_repository_free(repository); return luaL_error(L, "git retrieve commit error: %s", git_error_last_string()); } git_reset_t reset_type = GIT_RESET_SOFT; if (strcmp(type, "mixed") == 0) reset_type = GIT_RESET_MIXED; else if (strcmp(type, "hard") == 0) reset_type = GIT_RESET_HARD; int result = git_reset(repository, (git_object*)commit, reset_type, NULL); git_commit_free(commit); git_repository_free(repository); if (result) return luaL_error(L, "git reset error: %s", git_error_last_string()); return 0; } static int lpm_init(lua_State* L) { const char* path = luaL_checkstring(L, 1); const char* url = luaL_checkstring(L, 2); git_repository* repository; if (git_repository_init(&repository, path, 0)) return luaL_error(L, "git init error: %s", git_error_last_string()); git_remote* remote; if (git_remote_create(&remote, repository, "origin", url)) { git_repository_free(repository); return luaL_error(L, "git remote add error: %s", git_error_last_string()); } git_remote_free(remote); git_repository_free(repository); return 0; } static int lpm_fetch(lua_State* L) { git_repository* repository = luaL_checkgitrepo(L, 1); git_remote* remote; if (git_remote_lookup(&remote, repository, "origin")) { git_repository_free(repository); return luaL_error(L, "git remote fetch error: %s", git_error_last_string()); } git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; if (git_remote_fetch(remote, NULL, &fetch_opts, NULL)) { git_remote_free(remote); git_repository_free(repository); return luaL_error(L, "git remote fetch error: %s", git_error_last_string()); } git_remote_free(remote); git_repository_free(repository); return 0; } static CURL *curl; static int lpm_certs(lua_State* L) { const char* type = luaL_checkstring(L, 1); const char* path = luaL_checkstring(L, 2); if (strcmp(type, "dir") == 0) { git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, path); curl_easy_setopt(curl, CURLOPT_CAINFO, path); } else { git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, path, NULL); curl_easy_setopt(curl, CURLOPT_CAPATH, path); } return 0; } static int lpm_extract(lua_State* L) { const char* src = luaL_checkstring(L, 1); const char* dst = luaL_optstring(L, 2, "."); char error_buffer[1024] = {0}; struct archive_entry *entry; const void *buff; int flags = 0; int r; size_t size; #if ARCHIVE_VERSION_NUMBER >= 3000000 int64_t offset; #else off_t offset; #endif struct archive *ar = archive_read_new(); struct archive *aw = archive_write_disk_new(); archive_write_disk_set_options(aw, flags); archive_read_support_format_tar(ar); archive_read_support_format_zip(ar); archive_read_support_filter_gzip(ar); if ((r = archive_read_open_filename(ar, src, 10240))) { snprintf(error_buffer, sizeof(error_buffer), "error extracting archive %s: %s", src, archive_error_string(ar)); goto cleanup; } for (;;) { int r = archive_read_next_header(ar, &entry); if (r == ARCHIVE_EOF) break; if (r != ARCHIVE_OK) { snprintf(error_buffer, sizeof(error_buffer), "error extracting archive %s: %s", src, archive_error_string(ar)); goto cleanup; } char path[MAX_PATH]; strcpy(path, dst); strcat(path, "/"); strncat(path, archive_entry_pathname(entry), sizeof(path) - 3); path[MAX_PATH-1] = 0; archive_entry_set_pathname(entry, path); if (archive_write_header(aw, entry) != ARCHIVE_OK) { snprintf(error_buffer, sizeof(error_buffer), "error extracting archive %s: %s", src, archive_error_string(aw)); goto cleanup; } for (;;) { int r = archive_read_data_block(ar, &buff, &size, &offset); if (r == ARCHIVE_EOF) break; if (r != ARCHIVE_OK) { snprintf(error_buffer, sizeof(error_buffer), "error extracting archive %s: %s", src, archive_error_string(ar)); goto cleanup; } if (archive_write_data_block(aw, buff, size, offset) != ARCHIVE_OK) { snprintf(error_buffer, sizeof(error_buffer), "error extracting archive %s: %s", src, archive_error_string(aw)); goto cleanup; } } if (archive_write_finish_entry(aw) != ARCHIVE_OK) { snprintf(error_buffer, sizeof(error_buffer), "error extracting archive %s: %s", src, archive_error_string(aw)); goto cleanup; } } cleanup: archive_read_close(ar); archive_read_free(ar); archive_write_close(aw); archive_write_free(aw); if (error_buffer[0]) return luaL_error(L, "error extracting archive %s: %s", src, archive_error_string(ar)); return 0; } static size_t lpm_curl_write_callback(char *ptr, size_t size, size_t nmemb, void *BL) { luaL_Buffer* B = BL; luaL_addlstring(B, ptr, size*nmemb); return size*nmemb; } static int lpm_get(lua_State* L) { long response_code; const char* url = luaL_checkstring(L, 1); const char* path = luaL_optstring(L, 2, NULL); // curl_easy_reset(curl); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); #ifdef _WIN32 curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); #endif if (path) { FILE* file = fopen(path, "wb"); if (!file) return luaL_error(L, "error opening file %s: %s", path, strerror(errno)); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { fclose(file); return luaL_error(L, "curl error accessing %s: %s", url, curl_easy_strerror(res)); } fclose(file); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); if (response_code != 200) return luaL_error(L, "curl error accessing %s, non-200 response code: %d", url, response_code); lua_pushnil(L); lua_newtable(L); return 2; } else { luaL_Buffer B; luaL_buffinit(L, &B); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, lpm_curl_write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &B); CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) return luaL_error(L, "curl error accessing %s: %s", url, curl_easy_strerror(res)); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); if (response_code != 200) return luaL_error(L, "curl error accessing %s, non-200 response code: %d", url, response_code); luaL_pushresult(&B); lua_newtable(L); } return 2; } static const luaL_Reg system_lib[] = { { "ls", lpm_ls }, // Returns an array of files. { "stat", lpm_stat }, // Returns info about a single file. { "mkdir", lpm_mkdir }, // Makes a directory. { "rmdir", lpm_rmdir }, // Removes a directory. { "hash", lpm_hash }, // Returns a hex sha256 hash. { "symlink", lpm_symlink }, // Creates a symlink. { "chmod", lpm_chmod }, // Chmod's a file. { "init", lpm_init }, // Initializes a git repository with the specified remote. { "fetch", lpm_fetch }, // Updates a git repository with the specified remote. { "reset", lpm_reset }, // Updates a git repository to the specified commit/hash/branch. { "get", lpm_get }, // HTTP(s) GET request. { "extract", lpm_extract }, // Extracts .tar.gz, and .zip files. { "certs", lpm_certs }, // Sets the SSL certificate chain folder/file. { NULL, NULL } }; #ifndef LPM_VERSION #define LPM_VERSION "unknown" #endif #ifndef LITE_ARCH_TUPLE #if __x86_64__ || _WIN64 || __MINGW64__ #define ARCH_PROCESSOR "x86_64" #else #define ARCH_PROCESSOR "x86" #endif #if _WIN32 #define ARCH_PLATFORM "windows" #elif __linux__ #define ARCH_PLATFORM "linux" #elif __APPLE__ #define ARCH_PLATFORM "darwin" #else #error "Please define -DLITE_ARCH_TUPLE." #endif #define LITE_ARCH_TUPLE ARCH_PROCESSOR "-" ARCH_PLATFORM #endif extern const char src_lpm_luac[]; extern unsigned int src_lpm_luac_len; int main(int argc, char* argv[]) { curl = curl_easy_init(); if (!curl) return -1; git_libgit2_init(); lua_State* L = luaL_newstate(); luaL_openlibs(L); luaL_newlib(L, system_lib); lua_setglobal(L, "system"); lua_newtable(L); for (int i = 0; i < argc; ++i) { lua_pushstring(L, argv[i]); lua_rawseti(L, -2, i+1); } lua_setglobal(L, "ARGV"); lua_pushliteral(L, LPM_VERSION); lua_setglobal(L, "VERSION"); #if _WIN32 lua_pushliteral(L, "windows"); lua_pushliteral(L, "\\"); #else lua_pushliteral(L, "posix"); lua_pushliteral(L, "/"); #endif lua_setglobal(L, "PATHSEP"); lua_setglobal(L, "PLATFORM"); lua_pushliteral(L, LITE_ARCH_TUPLE); lua_setglobal(L, "ARCH"); #if LPM_LIVE if (luaL_loadfile(L, "src/lpm.lua") || lua_pcall(L, 0, 1, 0)) { #else if (luaL_loadbuffer(L, src_lpm_luac, src_lpm_luac_len, "lpm.lua") || lua_pcall(L, 0, 1, 0)) { #endif fprintf(stderr, "internal error when starting the application: %s\n", lua_tostring(L, -1)); return -1; } int status = lua_tointeger(L, -1); lua_close(L); git_libgit2_shutdown(); curl_easy_cleanup(curl); return status; }