--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/application/system.c Mon Jan 29 10:41:00 2024 +0100 @@ -0,0 +1,531 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <libidav/utils.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <errno.h> + +#include <cx/string.h> + +#ifndef _WIN32 +#include <unistd.h> +#endif + +#include "system.h" + +void sys_freedirent(SysDirEnt *ent) { + free(ent->name); + free(ent); +} + +#ifndef _WIN32 +/* ---------- POSIX implementation ---------- */ + +void sys_init(void) { + +} +void sys_uninit(void) { + +} + +SYS_DIR sys_opendir(const char *path) { + DIR *dir = opendir(path); + if(!dir) { + return NULL; + } + SysDir *d = malloc(sizeof(SysDir)); + d->dir = dir; + d->ent = NULL; + return d; +} + +SysDirEnt* sys_readdir(SYS_DIR dir) { + if(dir->ent) { + free(dir->ent->name); + free(dir->ent); + dir->ent = NULL; + } + struct dirent *ent = readdir(dir->dir); + if(ent) { + SysDirEnt *e = malloc(sizeof(SysDirEnt)); + e->name = strdup(ent->d_name); + dir->ent = e; + return e; + } + return NULL; +} + +void sys_closedir(SYS_DIR dir) { + closedir(dir->dir); + if(dir->ent) { + free(dir->ent->name); + free(dir->ent); + } + free(dir); +} + +FILE* sys_fopen(const char *path, const char *mode) { + return fopen(path, mode); +} + +int sys_stat(const char *path, SYS_STAT *s) { + return stat(path, s); +} + +int sys_lstat(const char *path, SYS_STAT *s) { + return lstat(path, s); +} + +int sys_islink(const char *path) { + struct stat s; + if(!lstat(path, &s)) { + return S_ISLNK(s.st_mode); + } + return 0; +} + +int sys_rename(const char *oldpath, const char *newpath) { + return rename(oldpath, newpath); +} + +int sys_unlink(const char *path) { + return unlink(path); +} + +int sys_mkdir(const char *path) { + return mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); +} + +char* sys_readlink(const char *path, SYS_STAT *s) { + char *ret = NULL; + + off_t l_sz = s->st_size + 16; + size_t lnksize = l_sz > 256 ? l_sz : 256; + char *lnkbuf = malloc(lnksize); + + ssize_t len = 0; + for(int i=0;i<4;i++) { + // we try to read the link at most 4 times + // only repeat if the buffer is too small + len = readlink(path, lnkbuf, lnksize); + if(len < lnksize) { + ret = lnkbuf; // success + lnkbuf[len] = 0; // terminate buffer + break; + } + lnksize *= 2; // retry with bigger buffer + lnkbuf = realloc(lnkbuf, lnksize); + } + + if(!ret) { + free(lnkbuf); + } + return ret; +} + +int sys_symlink(const char *target, const char *linkpath) { + int err = symlink(target, linkpath); + if(err && errno == EEXIST) { + if(unlink(linkpath)) { + return 1; + } + return sys_symlink(target, linkpath); + } + return err; +} + +int sys_truncate(const char* path, off_t length) { + return truncate(path, length); +} + +#else +/* ---------- Windows implementation ---------- */ + +#include <windows.h> +#include <winnls.h> +#include <shobjidl.h> +#include <objbase.h> +#include <objidl.h> + +#include <direct.h> +#include <wchar.h> + +void sys_init(void) { + HRESULT res = CoInitialize(NULL); + if(res != S_OK) { + fprintf(stderr, "Error: CoInitialize failed\n"); + } +} + +void sys_uninit(void) { + CoUninitialize(); +} + +static wchar_t* path2winpath(const char *path, int dir, int *newlen) { + size_t len = strlen(path); + size_t lenadd = dir ? 2 : 0; + + + wchar_t *wpath = calloc(len+lenadd+1, sizeof(wchar_t)); + int wlen = MultiByteToWideChar( + CP_UTF8, + 0, + path, + len, + wpath, + len+1 + ); + if(newlen) { + *newlen = wlen; + } + for(int i=0;i<wlen;i++) { + if(wpath[i] == L'/') { + wpath[i] = L'\\'; + } + } + + if(dir) { + if(wpath[wlen-1] != L'\\') { + wpath[wlen++] = L'\\'; + } + wpath[wlen++] = L'*'; + } + wpath[wlen] = 0; + + return wpath; +} + +static char* winpath2multibyte(const wchar_t *wpath, size_t wlen) { + size_t maxlen = wlen * 4; + char *ret = malloc(maxlen + 1); + int ret_len = WideCharToMultiByte( + CP_UTF8, + 0, + wpath, + wlen, + ret, + maxlen, + NULL, + NULL); + ret[ret_len] = 0; + return ret; +} + + + +SYS_DIR sys_opendir(const char *path) { + struct WinDir *dir = malloc(sizeof(struct WinDir)); + wchar_t *dirpath = path2winpath(path, TRUE, NULL); + if(!dirpath) { + fprintf(stderr, "Cannot convert path \"%s\" to UTF16\n", path); + free(dir); + return NULL; + } + dir->first = 1; + dir->handle = FindFirstFileW(dirpath, &dir->finddata); + free(dirpath); + if(dir->handle == INVALID_HANDLE_VALUE) { + free(dir); + return NULL; + } + dir->ent = NULL; + return dir; +} + +SysDirEnt* sys_readdir(SYS_DIR dir) { + if(dir->ent) { + free(dir->ent->name); + free(dir->ent); + dir->ent = NULL; + } + if(dir->first) { + dir->first = 0; + } else { + if(FindNextFileW(dir->handle, &dir->finddata) == 0) { + return NULL; + } + } + + size_t namelen = wcslen(dir->finddata.cFileName); + + char *name = malloc((namelen+1)*4); + int nlen = WideCharToMultiByte( + CP_UTF8, + 0, + dir->finddata.cFileName, + -1, + name, + 256, + NULL, + NULL); + if(nlen > 0) { + name[nlen] = 0; + SysDirEnt *ent = malloc(sizeof(SysDirEnt)); + ent->name = name; + dir->ent = ent; + return ent; + } else { + return NULL; + } +} + +void sys_closedir(SYS_DIR dir) { + if(dir->ent) { + free(dir->ent->name); + free(dir->ent); + } + FindClose(dir->handle); + free(dir); +} + +FILE* sys_fopen(const char *path, const char *mode) { + wchar_t *fpath = path2winpath(path, FALSE, NULL); + wchar_t *fmode = path2winpath(mode, FALSE, NULL); + + FILE *file = (fpath && fmode) ? _wfopen(fpath, fmode) : NULL; + free(fpath); + free(fmode); + return file; +} + +int sys_stat(const char *path, SYS_STAT *s) { + wchar_t *fpath = path2winpath(path, FALSE, NULL); + if(!fpath) { + fprintf(stderr, "Cannot convert path \"%s\" to UTF16\n", path); + return -1; + } + int ret = _wstat64(fpath, s); + free(fpath); + return ret; +} + +int sys_lstat(const char *path, SYS_STAT *s) { + return sys_stat(path, s); // unsupported on windows +} + +int sys_islink(const char *path) { + // don't use symlinks on windows, because it is not really useful + // however, we interpret .lnk files as symlinks + int ret = 0; + + cxstring path_s = cx_str(path); + if(cx_strsuffix(path_s, CX_STR(".lnk"))) { + // looks like a .lnk file + // check content + IShellLink *sl; + HRESULT hres; + hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID*)&sl); + if(SUCCEEDED(hres)) { + IPersistFile *file; + hres = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (void**)&file); + if(!SUCCEEDED(hres)) { + sl->lpVtbl->Release(sl); + return ret; + } + + int newlen = 0; + wchar_t *wpath = path2winpath(path, 0, &newlen); + + hres = file->lpVtbl->Load(file, wpath, STGM_READ); + if(SUCCEEDED(hres)) { + ret = 1; + file->lpVtbl->Release(file); + } + free(wpath); + + sl->lpVtbl->Release(sl); + } + } + return ret; +} + +int sys_rename(const char *oldpath, const char *newpath) { + wchar_t *o = path2winpath(oldpath, FALSE, NULL); + wchar_t *n = path2winpath(newpath, FALSE, NULL); + if(!o || !n) { + return -1; + } + + struct __stat64 s; + if(!_wstat64(n, &s)) { + if(_wunlink(n)) { + fprintf(stderr, "sys_rename: cannot delete existing file: %ls\n", n); + } + } + + int ret = _wrename(o, n); + free(o); + free(n); + return ret; +} + +int sys_unlink(const char *path) { + wchar_t *wpath = path2winpath(path, FALSE, NULL); + if(!wpath) { + fprintf(stderr, "sys_unlink: cannot convert path\n"); + return -1; + } + int ret = _wunlink(wpath); + free(wpath); + return ret; +} + +int sys_mkdir(const char *path) { + wchar_t *wpath = path2winpath(path, FALSE, NULL); + if(!wpath) { + fprintf(stderr, "sys_mkdir: cannot convert path\n"); + return -1; + } + int ret = _wmkdir(wpath); + free(wpath); + return ret; +} + +char* sys_readlink(const char *path, SYS_STAT *s) { + char *ret_link = NULL; + + // create COM object for using the ShellLink interface + IShellLinkW *sl; + HRESULT hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID*)&sl); + if(!SUCCEEDED(hres)) { + return NULL; + } + + IPersistFile *file; + hres = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (void**)&file); + if(!SUCCEEDED(hres)) { + sl->lpVtbl->Release(sl); + return NULL; + } + + // load .lnk file + int newlen = 0; + wchar_t *wpath = path2winpath(path, 0, &newlen); + hres = file->lpVtbl->Load(file, wpath, STGM_READ); + if(SUCCEEDED(hres)) { + WCHAR link_path[MAX_PATH]; + memset(link_path, 0, MAX_PATH); + + hres = sl->lpVtbl->Resolve(sl, 0, SLR_NO_UI); + if(SUCCEEDED(hres)) { + hres = sl->lpVtbl->GetPath(sl, link_path, MAX_PATH, NULL, SLGP_SHORTPATH); + if(SUCCEEDED(hres)) { + ret_link = winpath2multibyte(link_path, wcslen(link_path)); + } + } + } + // cleanup + free(wpath); + file->lpVtbl->Release(file); + sl->lpVtbl->Release(sl); + + return ret_link; +} + +int sys_symlink(const char *target, const char *linkpath) { + // convert relative target to absolut path + char *link_parent = util_parent_path(linkpath); + char *target_unnormalized = util_concat_path(link_parent, target); + char *target_normalized = util_path_normalize(target_unnormalized); + + free(link_parent); + free(target_unnormalized); + + // convert to wchar_t* + int wtargetlen = 0; + wchar_t *wtarget = path2winpath(target_normalized, FALSE, &wtargetlen); + free(target_normalized); + if(!wtarget) { + return 1; + } + + int wlinkpathlen = 0; + wchar_t *wlinkpath = path2winpath(linkpath, FALSE, &wlinkpathlen); + if(!wlinkpath) { + free(wtarget); + return 1; + } + + int ret = 1; + + // create COM object for using the ShellLink interface + IShellLinkW *sl; + HRESULT hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID*)&sl); + if(SUCCEEDED(hres)) { + IPersistFile *file; + hres = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (void**)&file); + if(SUCCEEDED(hres)) { + // try to load the shortcut + file->lpVtbl->Load(file, wlinkpath, STGM_READ); // ignore error + + // set path + hres = sl->lpVtbl->SetPath(sl, wtarget); + if(SUCCEEDED(hres)) { + hres = file->lpVtbl->Save(file, wlinkpath, TRUE); + if(SUCCEEDED(hres)) { + // successfully created/modified shortcut + ret = 0; // ok + } + } + + file->lpVtbl->Release(file); + } + + sl->lpVtbl->Release(sl); + } + + free(wtarget); + free(wlinkpath); + + return ret; +} + +int sys_truncate(const char* path, off_t length) { + wchar_t* wpath = path2winpath(path, FALSE, NULL); + if (!wpath) { + fprintf(stderr, "sys_truncate: cannot convert path\n"); + return -1; + } + + FILE* file = _wfopen(wpath, L"wb"); + int ret = 1; + if (file) { + ret = _chsize(fileno(file), length); + fclose(file); + } + + free(wpath); + return ret; +} + +#endif