/* lpm.c - Core of the lite package manager. LPM is a package manager for `lite-xl`, written in C (and packed-in lua). It has the following commands: lpm add lpm rm lpm update [] lpm install lpm uninstall lpm list It stores files in a cache directory in the follwoing format: : */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #include #endif /* 64bit fnv-1a hash */ typedef unsigned long long hash_t; static char hexDigits[] = "0123456789abcdef"; #define FNV_64_PRIME ((hash_t)0x100000001b3ULL) static int lpm_hash(lua_State* L) { hash_t hval = 0; size_t len; const char* data = luaL_checklstring(L, 1, &len); const unsigned char *bp = (unsigned char*)data; const unsigned char *be = data + len; while (bp < be) { hval *= FNV_64_PRIME; hval ^= (unsigned long long)*bp++; } char buffer[16]; for (size_t i = 0; i < len; ++i) { buffer[i*2+0] = hexDigits[data[i] >> 4]; buffer[i*2+1] = hexDigits[data[i] & 0xF]; } lua_pushlstring(L, buffer, 16); return 1; } /** BEGIN STOLEN LITE CODE **/ #if _WIN32 #define UTFCONV_ERROR_INVALID_CONVERSION "Input contains invalid byte sequences." static LPWSTR utfconv_utf8towc(const char *str) { LPWSTR output; int len; // len includes \0 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; // len includes \0 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); if (strchr("\\/", path[strlen(path) - 2]) != NULL) lua_pushstring(L, "*"); else lua_pushstring(L, "/*"); lua_concat(L, 2); path = lua_tostring(L, -1); LPWSTR wpath = utfconv_utf8towc(path); if (wpath == NULL) { lua_pushnil(L); lua_pushstring(L, UTFCONV_ERROR_INVALID_CONVERSION); return 2; } WIN32_FIND_DATAW fd; HANDLE find_handle = FindFirstFileExW(wpath, FindExInfoBasic, &fd, FindExSearchNameMatch, NULL, 0); free(wpath); if (find_handle == INVALID_HANDLE_VALUE) { lua_pushnil(L); lua_pushinteger(L, GetLastError()); return 2; } 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)); if (GetLastError() != ERROR_NO_MORE_FILES) { lua_pushnil(L); lua_pushinteger(L, GetLastError()); FindClose(find_handle); return 2; } FindClose(find_handle); return 1; #else DIR *dir = opendir(path); if (!dir) { lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; } 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 > 0) { lua_pushboolean(L, 1); } else { lua_pushboolean(L, 0); lua_pushinteger(L, GetLastError()); return 2; } #else int deleted = remove(path); if(deleted < 0) { lua_pushboolean(L, 0); lua_pushstring(L, strerror(errno)); return 2; } else { lua_pushboolean(L, 1); } #endif return 1; } static int lpm_mkdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); #ifdef _WIN32 LPWSTR wpath = utfconv_utf8towc(path); if (wpath == NULL) { lua_pushboolean(L, 0); lua_pushstring(L, UTFCONV_ERROR_INVALID_CONVERSION); return 2; } 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) { lua_pushboolean(L, 0); lua_pushstring(L, strerror(errno)); return 2; } lua_pushboolean(L, 1); return 1; } static int lpm_stat(lua_State *L) { const char *path = luaL_checkstring(L, 1); #ifdef _WIN32 struct _stat s; LPWSTR wpath = utfconv_utf8towc(path); if (wpath == NULL) { lua_pushnil(L); lua_pushstring(L, UTFCONV_ERROR_INVALID_CONVERSION); return 2; } int err = _wstat(wpath, &s); free(wpath); #else struct stat s; int err = stat(path, &s); #endif if (err < 0) { lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; } lua_newtable(L); 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"); #if __linux__ if (S_ISDIR(s.st_mode)) { if (lstat(path, &s) == 0) { lua_pushboolean(L, S_ISLNK(s.st_mode)); lua_setfield(L, -2, "symlink"); } } #endif 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)) return NULL; if (git_commit_lookup(&commit, repository, &commit_id)) return NULL; return commit; } 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; } 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) != 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; } 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; 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; } int lpm_status(lua_State* L) { const char* path = luaL_checkstring(L, 1); git_repository* repository; if (git_repository_open(&repository, path)) return luaL_error(L, "git open error: %s", git_error_last_string()); git_repository_free(repository); lua_newtable(L); lua_pushnil(L); lua_setfield(L, -2, "commit"); lua_pushnil(L); lua_setfield(L, -2, "branch"); return 1; } 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 hexhash. { "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. { "status", lpm_status } // Returns the git repository in question's current branch, if any, and commit hash. }; #ifndef LPM_VERSION #define LPM_VERSION "unknown" #endif extern const char lpm_lua[]; extern unsigned int lpm_lua_len; int main(int argc, char* argv[]) { 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_pushliteral(L, LPM_VERSION); lua_setglobal(L, "VERSION"); lua_setglobal(L, "ARGV"); #if _WIN32 lua_pushliteral(L, "\\"); #else lua_pushliteral(L, "/"); #endif lua_setglobal(L, "PATHSEP"); if (luaL_loadbuffer(L, lpm_lua, lpm_lua_len, "lpm.lua")) { //if (luaL_loadfile(L, "lpm.lua")) { fprintf(stderr, "internal error when starting the application: %s\n", lua_tostring(L, -1)); return -1; } lua_pcall(L, 0, 1, 0); int status = lua_tointeger(L, -1); lua_close(L); git_libgit2_shutdown(); return status; }