amiga-rhash/win_utils.c

519 lines
13 KiB
C

/* win_utils.c - Windows-specific utility functions */
#ifdef _WIN32
#include <windows.h>
#include <share.h> /* for _SH_DENYWR */
#include <sys/stat.h>
#include <fcntl.h> /* for _O_RDONLY, _O_BINARY */
#include <io.h> /* for isatty */
#include <assert.h>
#include <errno.h>
#include <locale.h>
#include "common_func.h"
#include "parse_cmdline.h"
#include "rhash_main.h"
#include "win_utils.h"
/**
* Convert a c-string to wide character string using given codepage
*
* @param str the string to convert
* @param codepage the codepage to use
* @return converted string on success, NULL on fail
*/
static wchar_t* cstr_to_wchar(const char* str, int codepage)
{
wchar_t* buf;
int size = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, str, -1, NULL, 0);
if (size == 0) return NULL; /* conversion failed */
buf = (wchar_t*)rsh_malloc(size * sizeof(wchar_t));
MultiByteToWideChar(codepage, 0, str, -1, buf, size);
return buf;
}
/**
* Convert c-string to wide string using primary or secondary codepage.
*
* @param str the C-string to convert
* @param try_no 0 for primary codepage, 1 for a secondary one
* @return converted wide string on success, NULL on error
*/
wchar_t* c2w(const char* str, int try_no)
{
int is_utf = (try_no == (opt.flags & OPT_UTF8 ? 0 : 1));
int codepage = (is_utf ? CP_UTF8 : (opt.flags & OPT_OEM) ? CP_OEMCP : CP_ACP);
return cstr_to_wchar(str, codepage);
}
/**
* Convert a UTF8-encoded string to wide string.
*
* @param str the UTF8-encoded string to convert
* @return wide string on success, NULL on error
*/
static wchar_t* utf8_to_wchar(const char* utf8_str)
{
return cstr_to_wchar(utf8_str, CP_UTF8);
}
/**
* Convert a wide character string to c-string using given codepage.
* Optionally set a flag if conversion failed.
*
* @param wstr the wide string to convert
* @param codepage the codepage to use
* @param failed pointer to the flag, to on failed conversion, can be NULL
* @return converted string on success, NULL on fail
*/
char* wchar_to_cstr(const wchar_t* wstr, int codepage, int* failed)
{
int size;
char *buf;
BOOL bUsedDefChar, *lpUsedDefaultChar;
if (codepage == -1) {
codepage = (opt.flags & OPT_UTF8 ? CP_UTF8 : (opt.flags & OPT_OEM) ? CP_OEMCP : CP_ACP);
}
/* note: lpUsedDefaultChar must be NULL for CP_UTF8, otrherwise WideCharToMultiByte() will fail */
lpUsedDefaultChar = (failed && codepage != CP_UTF8 ? &bUsedDefChar : NULL);
size = WideCharToMultiByte(codepage, 0, wstr, -1, 0, 0, 0, 0);
if (size == 0) {
if (failed) *failed = 1;
return NULL; /* conversion failed */
}
buf = (char*)rsh_malloc(size);
WideCharToMultiByte(codepage, 0, wstr, -1, buf, size, 0, lpUsedDefaultChar);
if (failed) *failed = (lpUsedDefaultChar && *lpUsedDefaultChar);
return buf;
}
/**
* Convert wide string to multi-byte c-string using codepage specified
* by command line options.
*
* @param wstr the wide string to convert
* @return c-string on success, NULL on fail
*/
char* w2c(const wchar_t* wstr)
{
return wchar_to_cstr(wstr, -1, NULL);
}
/**
* Convert given C-string from encoding specified by
* command line options to utf8.
*
* @param str the string to convert
* @return converted string on success, NULL on fail
*/
char* win_to_utf8(const char* str)
{
char* res;
wchar_t* buf;
assert((opt.flags & (OPT_UTF8 | OPT_OEM | OPT_ANSI)) != 0);
if (opt.flags & OPT_UTF8) return rsh_strdup(str);
if ((buf = c2w(str, 0)) == NULL) return NULL;
res = wchar_to_cstr(buf, CP_UTF8, NULL);
free(buf);
return res;
}
/**
* Open file path given in the current encoding, using desired shared access.
*
* @param path file path
* @param mode string specifying file opening mode
* @param exclusive non-zero to prohibit write access to the file
* @return file descriptor on success, NULL on error
*/
FILE* win_fopen_ex(const char* path, const char* mode, int exclusive)
{
FILE* fd = 0;
int i;
wchar_t* wmode = utf8_to_wchar(mode);
assert(wmode != NULL);
/* try two code pages */
for (i = 0; i < 2; i++) {
wchar_t* wpath = c2w(path, i);
if (wpath == NULL) continue;
fd = _wfsopen(wpath, wmode, (exclusive ? _SH_DENYWR : _SH_DENYNO));
free(wpath);
if (fd || errno != ENOENT) break;
}
free(wmode);
return fd;
}
/**
* Check if given file can be opened with exclusive write access.
*
* @param path path to the file
* @return 1 if file can be opened, 0 otherwise
*/
int can_open_exclusive(const char* path)
{
int i, res = 0;
for (i = 0; i < 2 && res == 0; i++) {
int fd;
wchar_t* wpath = c2w(path, i);
if (wpath == NULL) continue;
fd = _wsopen(wpath, _O_RDONLY | _O_BINARY, _SH_DENYWR, 0);
if (fd >= 0) {
res = 1;
_close(fd);
}
free(wpath);
}
return res;
}
/* the range of error codes for access errors */
#define MIN_EACCES_RANGE ERROR_WRITE_PROTECT
#define MAX_EACCES_RANGE ERROR_SHARING_BUFFER_EXCEEDED
/**
* Convert the GetLastError() value to the assignable to errno.
*
* @return errno-compatible error code
*/
static int convert_last_error_to_errno(void)
{
DWORD error_code = GetLastError();
switch (error_code)
{
case NO_ERROR:
return 0;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_INVALID_DRIVE:
case ERROR_BAD_NETPATH:
case ERROR_BAD_PATHNAME:
case ERROR_FILENAME_EXCED_RANGE:
return ENOENT;
case ERROR_TOO_MANY_OPEN_FILES:
return EMFILE;
case ERROR_ACCESS_DENIED:
case ERROR_SHARING_VIOLATION:
return EACCES;
case ERROR_NETWORK_ACCESS_DENIED:
case ERROR_FAIL_I24:
case ERROR_SEEK_ON_DEVICE:
return EACCES;
case ERROR_LOCK_VIOLATION:
case ERROR_DRIVE_LOCKED:
case ERROR_NOT_LOCKED:
case ERROR_LOCK_FAILED:
return EACCES;
case ERROR_INVALID_HANDLE:
return EBADF;
case ERROR_NOT_ENOUGH_MEMORY:
case ERROR_INVALID_BLOCK:
case ERROR_NOT_ENOUGH_QUOTA:
return ENOMEM;
case ERROR_INVALID_ACCESS:
case ERROR_INVALID_DATA:
case ERROR_INVALID_PARAMETER:
return EINVAL;
case ERROR_BROKEN_PIPE:
case ERROR_NO_DATA:
return EPIPE;
case ERROR_DISK_FULL:
return ENOSPC;
case ERROR_ALREADY_EXISTS:
return EEXIST;
case ERROR_NESTING_NOT_ALLOWED:
return EAGAIN;
}
/* try to detect error by range */
if (MIN_EACCES_RANGE <= error_code && error_code <= MAX_EACCES_RANGE) {
return EACCES;
} else {
return EINVAL;
}
}
/**
* Assign errno to the error value converted from the GetLastError().
*/
void set_errno_from_last_file_error(void)
{
errno = convert_last_error_to_errno();
}
/**
* Concatenate directory path with filename, unicode version.
*
* @param dir_path directory path
* @param dir_len length of directory path in characters
* @param filename the file name to append to the directory
* @return concatenated path
*/
wchar_t* make_pathw(const wchar_t* dir_path, size_t dir_len, wchar_t* filename)
{
wchar_t* res;
size_t len;
if (dir_path == 0) dir_len = 0;
else {
/* remove leading path separators from filename */
while (IS_PATH_SEPARATOR_W(*filename)) filename++;
if (dir_len == (size_t)-1) dir_len = wcslen(dir_path);
}
len = wcslen(filename);
res = (wchar_t*)rsh_malloc((dir_len + len + 2) * sizeof(wchar_t));
if (dir_len > 0) {
memcpy(res, dir_path, dir_len * sizeof(wchar_t));
if (res[dir_len - 1] != (wchar_t)SYS_PATH_SEPARATOR) {
/* append path separator to the directory */
res[dir_len++] = (wchar_t)SYS_PATH_SEPARATOR;
}
}
/* append filename */
memcpy(res + dir_len, filename, (len + 1) * sizeof(wchar_t));
return res;
}
void set_benchmark_cpu_affinity(void)
{
DWORD_PTR dwProcessMask, dwSysMask, dwDesired;
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
if ( GetProcessAffinityMask(GetCurrentProcess(), &dwProcessMask, &dwSysMask) ) {
dwDesired = dwSysMask & (dwProcessMask & ~1); /* remove the first processor */
dwDesired = (dwDesired ? dwDesired : dwSysMask & ~1);
if (dwDesired != 0) {
SetProcessAffinityMask(GetCurrentProcess(), dwDesired);
}
}
}
/* functions to setup/restore console */
/**
* Prepare console on program initialization: change console font codepage
* according to program options and hide cursor.
*/
void setup_console(void)
{
HANDLE hOut;
CONSOLE_CURSOR_INFO cci;
int cp = (opt.flags & OPT_UTF8 ? CP_UTF8 : opt.flags & OPT_ANSI ? GetACP() : GetOEMCP());
rhash_data.saved_console_codepage = -1;
/* note: we are using numbers 1 = _fileno(stdout), 2 = _fileno(stderr) */
/* cause _fileno() is undefined, when compiling as strict ansi C. */
if (cp > 0 && IsValidCodePage(cp) && (isatty(1) || isatty(2)))
{
rhash_data.saved_console_codepage = GetConsoleOutputCP();
SetConsoleOutputCP(cp);
setlocale(LC_CTYPE, opt.flags & OPT_UTF8 ? "C" :
opt.flags & OPT_ANSI ? ".ACP" : ".OCP");
rsh_exit = rhash_exit;
}
if ((opt.flags & OPT_PERCENTS) != 0 && isatty(2)) {
hOut = GetStdHandle(STD_ERROR_HANDLE);
if (hOut != INVALID_HANDLE_VALUE && GetConsoleCursorInfo(hOut, &cci)) {
/* store current cursor size and visibility flag */
rhash_data.saved_cursor_size = (cci.bVisible ? cci.dwSize : 0);
/* now hide cursor */
cci.bVisible = 0;
SetConsoleCursorInfo(hOut, &cci); /* hide cursor */
}
}
}
/**
* Restore console on program exit.
*/
void restore_console(void)
{
HANDLE hOut;
CONSOLE_CURSOR_INFO cci;
if (rhash_data.saved_console_codepage > 0) {
SetConsoleOutputCP(rhash_data.saved_console_codepage);
}
hOut = GetStdHandle(STD_ERROR_HANDLE);
if (hOut != INVALID_HANDLE_VALUE && rhash_data.saved_cursor_size) {
/* restore cursor size and visibility */
cci.dwSize = rhash_data.saved_cursor_size;
cci.bVisible = 1;
SetConsoleCursorInfo(hOut, &cci);
}
}
/****************************************************************************
* Directory functions *
****************************************************************************/
struct WIN_DIR_t
{
WIN32_FIND_DATAW findFileData;
HANDLE hFind;
struct win_dirent dir;
int state; /* 0 - not started, -1 - ended, >=0 file index */
};
/**
* Open directory iterator for reading the directory content.
*
* @param dir_path directory path
* @return pointer to directory stream. On error, NULL is returned,
* and errno is set appropriately.
*/
WIN_DIR* win_opendir(const char* dir_path)
{
WIN_DIR* d;
wchar_t* wpath;
/* append '\*' to the dir_path */
size_t len = strlen(dir_path);
char *path = (char*)malloc(len + 3);
if (!path) return NULL; /* failed, malloc also set errno = ENOMEM */
strcpy(path, dir_path);
strcpy(path + len, "\\*");
d = (WIN_DIR*)malloc(sizeof(WIN_DIR));
if (!d) {
free(path);
return NULL;
}
memset(d, 0, sizeof(WIN_DIR));
wpath = c2w(path, 0);
d->hFind = (wpath != NULL ?
FindFirstFileW(wpath, &d->findFileData) : INVALID_HANDLE_VALUE);
free(wpath);
if (d->hFind == INVALID_HANDLE_VALUE && GetLastError() != ERROR_ACCESS_DENIED) {
wpath = c2w(path, 1); /* try to use secondary codepage */
if (wpath) {
d->hFind = FindFirstFileW(wpath, &d->findFileData);
free(wpath);
}
}
free(path);
if (d->hFind == INVALID_HANDLE_VALUE && GetLastError() == ERROR_ACCESS_DENIED) {
free(d);
errno = EACCES;
return NULL;
}
set_errno_from_last_file_error();
d->state = (d->hFind == INVALID_HANDLE_VALUE ? -1 : 0);
d->dir.d_name = NULL;
return d;
}
/**
* Open a directory for reading its content.
* For simplicity the function supposes that dir_path points to an
* existing directory and doesn't check for this error.
* The Unicode version of the function.
*
* @param dir_path directory path
* @return pointer to directory iterator
*/
WIN_DIR* win_wopendir(const wchar_t* dir_path)
{
WIN_DIR* d;
/* append '\*' to the dir_path */
wchar_t *wpath = make_pathw(dir_path, (size_t)-1, L"*");
d = (WIN_DIR*)rsh_malloc(sizeof(WIN_DIR));
d->hFind = FindFirstFileW(wpath, &d->findFileData);
free(wpath);
if (d->hFind == INVALID_HANDLE_VALUE && GetLastError() == ERROR_ACCESS_DENIED) {
free(d);
errno = EACCES;
return NULL;
}
/* note: we suppose if INVALID_HANDLE_VALUE was returned, then the file listing is empty */
d->state = (d->hFind == INVALID_HANDLE_VALUE ? -1 : 0);
d->dir.d_name = NULL;
return d;
}
/**
* Close a directory iterator.
*
* @param d pointer to the directory iterator
*/
void win_closedir(WIN_DIR* d)
{
if (d->hFind != INVALID_HANDLE_VALUE) {
FindClose(d->hFind);
}
free(d->dir.d_name);
free(d);
}
/**
* Read a directory content.
*
* @param d pointer to the directory iterator
* @return directory entry or NULL if no entries left
*/
struct win_dirent* win_readdir(WIN_DIR* d)
{
char* filename;
int failed;
if (d->state == -1) return NULL;
if (d->dir.d_name != NULL) {
free(d->dir.d_name);
d->dir.d_name = NULL;
}
for (;;) {
if (d->state > 0) {
if ( !FindNextFileW(d->hFind, &d->findFileData) ) {
/* the directory listing has ended */
d->state = -1;
return NULL;
}
}
d->state++;
if (d->findFileData.cFileName[0] == L'.' &&
(d->findFileData.cFileName[1] == 0 ||
(d->findFileData.cFileName[1] == L'.' &&
d->findFileData.cFileName[2] == 0))) {
/* simplified implementation, skips '.' and '..' names */
continue;
}
d->dir.d_name = filename = wchar_to_cstr(d->findFileData.cFileName, WIN_DEFAULT_ENCODING, &failed);
if (filename && !failed) {
d->dir.d_wname = d->findFileData.cFileName;
d->dir.d_isdir = (0 != (d->findFileData.dwFileAttributes &
FILE_ATTRIBUTE_DIRECTORY));
return &d->dir;
}
/* quietly skip an invalid filename and repeat the search */
if (filename) {
free(filename);
d->dir.d_name = NULL;
}
}
}
#endif /* _WIN32 */